Merge "Add asserts for new HAL axes in InputCommonConverter"
diff --git a/Android.bp b/Android.bp
index 615a7a8..3992f82 100644
--- a/Android.bp
+++ b/Android.bp
@@ -56,7 +56,8 @@
 
 cc_library_headers {
     name: "libandroid_sensor_headers",
-    vendor: true,
+    vendor_available: true,
+    host_supported: true,
     export_include_dirs: ["include_sensor"],
 }
 
diff --git a/aidl/gui/android/view/Surface.aidl b/aidl/gui/android/view/Surface.aidl
index 7e89220..bb3faaf 100644
--- a/aidl/gui/android/view/Surface.aidl
+++ b/aidl/gui/android/view/Surface.aidl
@@ -17,4 +17,4 @@
 
 package android.view;
 
-parcelable Surface cpp_header "gui/view/Surface.h";
+@JavaOnlyStableParcelable @NdkOnlyStableParcelable parcelable Surface cpp_header "gui/view/Surface.h" ndk_header "android/native_window_aidl.h";
diff --git a/cmds/dumpstate/Android.bp b/cmds/dumpstate/Android.bp
index a60972b..860a2d8 100644
--- a/cmds/dumpstate/Android.bp
+++ b/cmds/dumpstate/Android.bp
@@ -126,6 +126,7 @@
     ],
     required: [
         "atrace",
+        "bugreport_procdump",
         "dmabuf_dump",
         "ip",
         "iptables",
@@ -154,7 +155,10 @@
         "dumpstate.cpp",
         "tests/dumpstate_test.cpp",
     ],
-    static_libs: ["libgmock"],
+    static_libs: [
+        "libc++fs",
+        "libgmock",
+    ],
     test_config: "dumpstate_test.xml",
     data: [
         ":dumpstate_test_fixture",
diff --git a/cmds/dumpstate/DumpstateService.cpp b/cmds/dumpstate/DumpstateService.cpp
index e42ee05..42e9e0f 100644
--- a/cmds/dumpstate/DumpstateService.cpp
+++ b/cmds/dumpstate/DumpstateService.cpp
@@ -51,7 +51,7 @@
 
 // Creates a bugreport and exits, thus preserving the oneshot nature of the service.
 // Note: takes ownership of data.
-[[noreturn]] static void* dumpstate_thread_main(void* data) {
+[[noreturn]] static void* dumpstate_thread_bugreport(void* data) {
     std::unique_ptr<DumpstateInfo> ds_info(static_cast<DumpstateInfo*>(data));
     ds_info->ds->Run(ds_info->calling_uid, ds_info->calling_package);
     MYLOGD("Finished taking a bugreport. Exiting.\n");
@@ -84,11 +84,28 @@
     return android::OK;
 }
 
+binder::Status DumpstateService::preDumpUiData(const std::string&) {
+    std::lock_guard<std::mutex> lock(lock_);
+    MYLOGI("preDumpUiData()");
+
+    if (ds_ != nullptr) {
+        MYLOGE("Error! DumpstateService is currently already being used. Returning.");
+        return exception(binder::Status::EX_SERVICE_SPECIFIC,
+                         "DumpstateService is already being used");
+    }
+
+    ds_ = &(Dumpstate::GetInstance());
+    ds_->PreDumpUiData();
+
+    return binder::Status::ok();
+}
+
 binder::Status DumpstateService::startBugreport(int32_t calling_uid,
                                                 const std::string& calling_package,
                                                 android::base::unique_fd bugreport_fd,
                                                 android::base::unique_fd screenshot_fd,
                                                 int bugreport_mode,
+                                                int bugreport_flags,
                                                 const sp<IDumpstateListener>& listener,
                                                 bool is_screenshot_requested) {
     MYLOGI("startBugreport() with mode: %d\n", bugreport_mode);
@@ -96,12 +113,12 @@
     // Ensure there is only one bugreport in progress at a time.
     std::lock_guard<std::mutex> lock(lock_);
     if (ds_ != nullptr) {
-        MYLOGE("Error! There is already a bugreport in progress. Returning.");
+        MYLOGE("Error! DumpstateService is currently already being used. Returning.");
         if (listener != nullptr) {
             listener->onError(IDumpstateListener::BUGREPORT_ERROR_ANOTHER_REPORT_IN_PROGRESS);
         }
         return exception(binder::Status::EX_SERVICE_SPECIFIC,
-                         "There is already a bugreport in progress");
+                         "DumpstateService is already being used");
     }
 
     // From here on, all conditions that indicate we are done with this incoming request should
@@ -123,8 +140,8 @@
     }
 
     std::unique_ptr<Dumpstate::DumpOptions> options = std::make_unique<Dumpstate::DumpOptions>();
-    options->Initialize(static_cast<Dumpstate::BugreportMode>(bugreport_mode), bugreport_fd,
-                        screenshot_fd, is_screenshot_requested);
+    options->Initialize(static_cast<Dumpstate::BugreportMode>(bugreport_mode), bugreport_flags,
+                        bugreport_fd, screenshot_fd, is_screenshot_requested);
 
     if (bugreport_fd.get() == -1 || (options->do_screenshot && screenshot_fd.get() == -1)) {
         MYLOGE("Invalid filedescriptor");
@@ -148,10 +165,9 @@
     pthread_t thread;
     // Initialize dumpstate
     ds_->Initialize();
-    status_t err = pthread_create(&thread, nullptr, dumpstate_thread_main, ds_info);
+    status_t err = pthread_create(&thread, nullptr, dumpstate_thread_bugreport, ds_info);
     if (err != 0) {
         delete ds_info;
-        ds_info = nullptr;
         MYLOGE("Could not create a thread");
         signalErrorAndExit(listener, IDumpstateListener::BUGREPORT_ERROR_RUNTIME_ERROR);
     }
diff --git a/cmds/dumpstate/DumpstateService.h b/cmds/dumpstate/DumpstateService.h
index 3ec8471..997999c 100644
--- a/cmds/dumpstate/DumpstateService.h
+++ b/cmds/dumpstate/DumpstateService.h
@@ -38,13 +38,16 @@
 
     status_t dump(int fd, const Vector<String16>& args) override;
 
+    binder::Status preDumpUiData(const std::string& callingPackage) override;
+
     binder::Status startBugreport(int32_t calling_uid, const std::string& calling_package,
                                   android::base::unique_fd bugreport_fd,
                                   android::base::unique_fd screenshot_fd, int bugreport_mode,
-                                  const sp<IDumpstateListener>& listener,
+                                  int bugreport_flags, const sp<IDumpstateListener>& listener,
                                   bool is_screenshot_requested) override;
 
-    binder::Status cancelBugreport(int32_t calling_uid, const std::string& calling_package);
+    binder::Status cancelBugreport(int32_t calling_uid,
+                                   const std::string& calling_package) override;
 
   private:
     // Dumpstate object which contains all the bugreporting logic.
diff --git a/cmds/dumpstate/binder/android/os/IDumpstate.aidl b/cmds/dumpstate/binder/android/os/IDumpstate.aidl
index 0793f0b..d4323af 100644
--- a/cmds/dumpstate/binder/android/os/IDumpstate.aidl
+++ b/cmds/dumpstate/binder/android/os/IDumpstate.aidl
@@ -49,6 +49,23 @@
     // Default mode.
     const int BUGREPORT_MODE_DEFAULT = 6;
 
+    // Use pre-dumped data.
+    const int BUGREPORT_FLAG_USE_PREDUMPED_UI_DATA = 1;
+
+    /**
+     * Speculatively pre-dumps UI data for a bugreport request that might come later.
+     *
+     * <p>Triggers the dump of certain critical UI data, e.g. traces stored in short
+     * ring buffers that might get lost by the time the actual bugreport is requested.
+     *
+     * <p>{@code startBugreport} will then pick the pre-dumped data if:
+     * - {@link BUGREPORT_FLAG_USE_PREDUMPED_UI_DATA} is specified.
+     * - {@code preDumpUiData} and {@code startBugreport} were called by the same UID.
+     *
+     * @param callingPackage package of the original application that requested the report.
+     */
+    void preDumpUiData(@utf8InCpp String callingPackage);
+
     /**
      * Starts a bugreport in the background.
      *
@@ -63,13 +80,14 @@
      * @param bugreportFd the file to which the zipped bugreport should be written
      * @param screenshotFd the file to which screenshot should be written
      * @param bugreportMode the mode that specifies other run time options; must be one of above
+     * @param bugreportFlags flags to customize the bugreport generation
      * @param listener callback for updates; optional
      * @param isScreenshotRequested indicates screenshot is requested or not
      */
     void startBugreport(int callingUid, @utf8InCpp String callingPackage,
                         FileDescriptor bugreportFd, FileDescriptor screenshotFd,
-                        int bugreportMode, IDumpstateListener listener,
-                        boolean isScreenshotRequested);
+                        int bugreportMode, int bugreportFlags,
+                        IDumpstateListener listener, boolean isScreenshotRequested);
 
     /**
      * Cancels the bugreport currently in progress.
diff --git a/cmds/dumpstate/dumpstate.cpp b/cmds/dumpstate/dumpstate.cpp
index 60c2077..69a1df2 100644
--- a/cmds/dumpstate/dumpstate.cpp
+++ b/cmds/dumpstate/dumpstate.cpp
@@ -170,6 +170,7 @@
 #define RECOVERY_DIR "/cache/recovery"
 #define RECOVERY_DATA_DIR "/data/misc/recovery"
 #define UPDATE_ENGINE_LOG_DIR "/data/misc/update_engine_log"
+#define UPDATE_ENGINE_PREF_DIR "/data/misc/update_engine/prefs"
 #define LOGPERSIST_DATA_DIR "/data/misc/logd"
 #define PREREBOOT_DATA_DIR "/data/misc/prereboot"
 #define PROFILE_DATA_DIR_CUR "/data/misc/profiles/cur"
@@ -193,6 +194,8 @@
 static const std::string TOMBSTONE_FILE_PREFIX = "tombstone_";
 static const std::string ANR_DIR = "/data/anr/";
 static const std::string ANR_FILE_PREFIX = "anr_";
+static const std::string SHUTDOWN_CHECKPOINTS_DIR = "/data/system/shutdown-checkpoints/";
+static const std::string SHUTDOWN_CHECKPOINTS_FILE_PREFIX = "checkpoints-";
 
 // TODO: temporary variables and functions used during C++ refactoring
 
@@ -238,6 +241,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";
 
 namespace android {
 namespace os {
@@ -1108,6 +1112,16 @@
     RunCommand("IP6TABLES RAW", {"ip6tables", "-t", "raw", "-L", "-nvx"});
 }
 
+static void DumpShutdownCheckpoints() {
+    const bool shutdown_checkpoints_dumped = AddDumps(
+        ds.shutdown_checkpoints_.begin(), ds.shutdown_checkpoints_.end(),
+        "SHUTDOWN CHECKPOINTS", false /* add_to_zip */);
+    if (!shutdown_checkpoints_dumped) {
+        printf("*** NO SHUTDOWN CHECKPOINTS to dump in %s\n\n",
+            SHUTDOWN_CHECKPOINTS_DIR.c_str());
+    }
+}
+
 static void DumpDynamicPartitionInfo() {
     if (!::android::base::GetBoolProperty("ro.boot.dynamic_partitions", false)) {
         return;
@@ -1593,16 +1607,16 @@
 // via the consent they are shown. Ignores other errors that occur while running various
 // commands. The consent checking is currently done around long running tasks, which happen to
 // be distributed fairly evenly throughout the function.
-static Dumpstate::RunStatus dumpstate() {
+Dumpstate::RunStatus Dumpstate::dumpstate() {
     DurationReporter duration_reporter("DUMPSTATE");
 
     // 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;
+            dump_netstats_report, post_process_ui_traces;
     if (ds.dump_pool_) {
         // Pool was shutdown in DumpstateDefaultAfterCritical method in order to
-        // drop root user. Restarts it with two threads for the parallel run.
-        ds.dump_pool_->start(/* thread_counts = */2);
+        // drop root user. Restarts it.
+        ds.dump_pool_->start(/* thread_counts = */3);
 
         dump_hals = ds.dump_pool_->enqueueTaskWithFd(DUMP_HALS_TASK, &DumpHals, _1);
         dump_incident_report = ds.dump_pool_->enqueueTask(
@@ -1612,6 +1626,8 @@
         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
@@ -1624,7 +1640,8 @@
     RunCommand("CPU INFO", {"top", "-b", "-n", "1", "-H", "-s", "6", "-o",
                             "pid,tid,user,pr,ni,%cpu,s,virt,res,pcy,cmd,name"});
 
-    RUN_SLOW_FUNCTION_WITH_CONSENT_CHECK(RunCommand, "PROCRANK", {"procrank"}, AS_ROOT_20);
+    RUN_SLOW_FUNCTION_WITH_CONSENT_CHECK(RunCommand, "BUGREPORT_PROCDUMP", {"bugreport_procdump"},
+                                         CommandOptions::AS_ROOT);
 
     RUN_SLOW_FUNCTION_WITH_CONSENT_CHECK(DumpVisibleWindowViews);
 
@@ -1641,9 +1658,6 @@
     RunCommand("PROCESSES AND THREADS",
                {"ps", "-A", "-T", "-Z", "-O", "pri,nice,rtprio,sched,pcy,time"});
 
-    RUN_SLOW_FUNCTION_WITH_CONSENT_CHECK(RunCommand, "LIBRANK", {"librank"},
-                                         CommandOptions::AS_ROOT);
-
     if (ds.dump_pool_) {
         WAIT_TASK_WITH_CONSENT_CHECK(std::move(dump_hals));
     } else {
@@ -1673,8 +1687,6 @@
 
     RunCommand("LIST OF OPEN FILES", {"lsof"}, CommandOptions::AS_ROOT);
 
-    RUN_SLOW_FUNCTION_WITH_CONSENT_CHECK(for_each_pid, do_showmap, "SMAPS OF ALL PROCESSES");
-
     for_each_tid(show_wchan, "BLOCKED PROCESS WAIT-CHANNELS");
     for_each_pid(show_showtime, "PROCESS TIMES (pid cmd user system iowait+percentage)");
 
@@ -1704,6 +1716,8 @@
 
     DoKmsg();
 
+    DumpShutdownCheckpoints();
+
     DumpIpAddrAndRules();
 
     dump_route_tables();
@@ -1736,11 +1750,6 @@
     DumpFile("BINDER STATS", binder_logs_dir + "/stats");
     DumpFile("BINDER STATE", binder_logs_dir + "/state");
 
-    /* Add window and surface trace files. */
-    if (!PropertiesHelper::IsUserBuild()) {
-        ds.AddDir(WMTRACE_DATA_DIR, false);
-    }
-
     ds.AddDir(SNAPSHOTCTL_LOG_DIR, false);
 
     if (ds.dump_pool_) {
@@ -1820,6 +1829,14 @@
                 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;
 }
 
@@ -1858,11 +1875,14 @@
     if (!PropertiesHelper::IsDryRun()) {
         ds.tombstone_data_ = GetDumpFds(TOMBSTONE_DIR, TOMBSTONE_FILE_PREFIX);
         ds.anr_data_ = GetDumpFds(ANR_DIR, ANR_FILE_PREFIX);
+        ds.shutdown_checkpoints_ = GetDumpFds(
+            SHUTDOWN_CHECKPOINTS_DIR, SHUTDOWN_CHECKPOINTS_FILE_PREFIX);
     }
 
     ds.AddDir(RECOVERY_DIR, true);
     ds.AddDir(RECOVERY_DATA_DIR, true);
     ds.AddDir(UPDATE_ENGINE_LOG_DIR, true);
+    ds.AddDir(UPDATE_ENGINE_PREF_DIR, true);
     ds.AddDir(LOGPERSIST_DATA_DIR, false);
     if (!PropertiesHelper::IsUserBuild()) {
         ds.AddDir(PROFILE_DATA_DIR_CUR, true);
@@ -2791,9 +2811,11 @@
 }
 
 void Dumpstate::DumpOptions::Initialize(BugreportMode bugreport_mode,
+                                        int bugreport_flags,
                                         const android::base::unique_fd& bugreport_fd_in,
                                         const android::base::unique_fd& screenshot_fd_in,
                                         bool is_screenshot_requested) {
+    this->use_predumped_ui_data = bugreport_flags & BugreportFlag::BUGREPORT_USE_PREDUMPED_UI_DATA;
     // Duplicate the fds because the passed in fds don't outlive the binder transaction.
     bugreport_fd.reset(fcntl(bugreport_fd_in.get(), F_DUPFD_CLOEXEC, 0));
     screenshot_fd.reset(fcntl(screenshot_fd_in.get(), F_DUPFD_CLOEXEC, 0));
@@ -2909,6 +2931,7 @@
     }
     tombstone_data_.clear();
     anr_data_.clear();
+    shutdown_checkpoints_.clear();
 
     // Instead of shutdown the pool, we delete temporary files directly since
     // shutdown blocking the call.
@@ -2920,6 +2943,10 @@
     }
 }
 
+void Dumpstate::PreDumpUiData() {
+    MaybeSnapshotUiTraces();
+}
+
 /*
  * Dumps relevant information to a bugreport based on the given options.
  *
@@ -3111,9 +3138,9 @@
         // The trace file is added to the zip by MaybeAddSystemTraceToZip().
         MaybeSnapshotSystemTrace();
 
-        // If a winscope trace is running, snapshot it now. It will be pulled into bugreport later
-        // from WMTRACE_DATA_DIR.
-        MaybeSnapshotWinTrace();
+        // Snapshot the UI traces now (if running).
+        // The trace files will be added to bugreport later.
+        MaybeSnapshotUiTraces();
     }
     onUiIntensiveBugreportDumpsFinished(calling_uid);
     MaybeCheckUserConsent(calling_uid, calling_package);
@@ -3192,6 +3219,7 @@
 
     tombstone_data_.clear();
     anr_data_.clear();
+    shutdown_checkpoints_.clear();
 
     return (consent_callback_ != nullptr &&
             consent_callback_->getResult() == UserConsentResult::UNAVAILABLE)
@@ -3227,15 +3255,53 @@
     // file in the later stages.
 }
 
-void Dumpstate::MaybeSnapshotWinTrace() {
+void Dumpstate::MaybeSnapshotUiTraces() {
+    if (PropertiesHelper::IsUserBuild() || options_->use_predumped_ui_data) {
+        return;
+    }
+
     // Currently WindowManagerService and InputMethodManagerSerivice support WinScope protocol.
-    for (const auto& service : {"window", "input_method"}) {
+    for (const auto& service : {"input_method", "window"}) {
         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.
             "", {"cmd", service, "tracing", "save-for-bugreport"},
             CommandOptions::WithTimeout(10).Always().DropRoot().RedirectStderr().Build());
     }
+
+    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() {
+    if (PropertiesHelper::IsUserBuild()) {
+        return;
+    }
+
+    ds.AddDir(WMTRACE_DATA_DIR, false);
 }
 
 void Dumpstate::onUiIntensiveBugreportDumpsFinished(int32_t calling_uid) {
@@ -3922,15 +3988,6 @@
     return;
 }
 
-void do_showmap(int pid, const char *name) {
-    char title[255];
-    char arg[255];
-
-    snprintf(title, sizeof(title), "SHOW MAP %d (%s)", pid, name);
-    snprintf(arg, sizeof(arg), "%d", pid);
-    RunCommand(title, {"showmap", "-q", arg}, CommandOptions::AS_ROOT);
-}
-
 int Dumpstate::DumpFile(const std::string& title, const std::string& path) {
     DurationReporter duration_reporter(title);
 
diff --git a/cmds/dumpstate/dumpstate.h b/cmds/dumpstate/dumpstate.h
index ee6b1ae..9f894b5 100644
--- a/cmds/dumpstate/dumpstate.h
+++ b/cmds/dumpstate/dumpstate.h
@@ -204,6 +204,12 @@
         BUGREPORT_DEFAULT = android::os::IDumpstate::BUGREPORT_MODE_DEFAULT
     };
 
+    // The flags used to customize bugreport requests.
+    enum BugreportFlag {
+        BUGREPORT_USE_PREDUMPED_UI_DATA =
+          android::os::IDumpstate::BUGREPORT_FLAG_USE_PREDUMPED_UI_DATA
+    };
+
     static android::os::dumpstate::CommandOptions DEFAULT_DUMPSYS;
 
     static Dumpstate& GetInstance();
@@ -333,6 +339,12 @@
 
     struct DumpOptions;
 
+    /**
+     * Pre-dump critical UI data, e.g. data stored in short ring buffers that might get lost
+     * by the time the actual bugreport is requested.
+     */
+    void PreDumpUiData();
+
     /*
      * Main entry point for running a complete bugreport.
      *
@@ -396,6 +408,8 @@
         // TODO(b/148168577) get rid of the AIDL values, replace them with the HAL values instead.
         // The HAL is actually an API surface that can be validated, while the AIDL is not (@hide).
         BugreportMode bugreport_mode = Dumpstate::BugreportMode::BUGREPORT_DEFAULT;
+        // Will use data collected through a previous call to PreDumpUiData().
+        bool use_predumped_ui_data;
         // File descriptor to output zip file. Takes precedence over out_dir.
         android::base::unique_fd bugreport_fd;
         // File descriptor to screenshot file.
@@ -414,7 +428,8 @@
         RunStatus Initialize(int argc, char* argv[]);
 
         /* Initializes options from the requested mode. */
-        void Initialize(BugreportMode bugreport_mode, const android::base::unique_fd& bugreport_fd,
+        void Initialize(BugreportMode bugreport_mode, int bugreport_flags,
+                        const android::base::unique_fd& bugreport_fd,
                         const android::base::unique_fd& screenshot_fd,
                         bool is_screenshot_requested);
 
@@ -499,6 +514,9 @@
     // List of open ANR dump files.
     std::vector<DumpData> anr_data_;
 
+    // List of open shutdown checkpoint files.
+    std::vector<DumpData> shutdown_checkpoints_;
+
     // A thread pool to execute dump tasks simultaneously if the parallel run is enabled.
     std::unique_ptr<android::os::dumpstate::DumpPool> dump_pool_;
 
@@ -532,10 +550,13 @@
     RunStatus RunInternal(int32_t calling_uid, const std::string& calling_package);
 
     RunStatus DumpstateDefaultAfterCritical();
+    RunStatus dumpstate();
 
     void MaybeTakeEarlyScreenshot();
     void MaybeSnapshotSystemTrace();
-    void MaybeSnapshotWinTrace();
+    void MaybeSnapshotUiTraces();
+    void MaybePostProcessUiTraces();
+    void MaybeAddUiTracesToZip();
 
     void onUiIntensiveBugreportDumpsFinished(int32_t calling_uid);
 
@@ -619,9 +640,6 @@
 /* Displays a processes times */
 void show_showtime(int pid, const char *name);
 
-/* Runs "showmap" for a process */
-void do_showmap(int pid, const char *name);
-
 /* Gets the dmesg output for the kernel */
 void do_dmesg();
 
diff --git a/cmds/dumpstate/main.cpp b/cmds/dumpstate/main.cpp
index ec89c0d..a634f93 100644
--- a/cmds/dumpstate/main.cpp
+++ b/cmds/dumpstate/main.cpp
@@ -56,7 +56,7 @@
             MYLOGE("Unable to start 'dumpstate' service: %d", ret);
             exit(1);
         }
-        MYLOGI("'dumpstate' service started and will wait for a call to startBugreport()");
+        MYLOGI("'dumpstate' service started and will wait for a call");
 
         // Waits forever for an incoming connection.
         // TODO(b/111441001): should this time out?
diff --git a/cmds/dumpstate/tests/dumpstate_smoke_test.cpp b/cmds/dumpstate/tests/dumpstate_smoke_test.cpp
index 28e5ee2..b091c8e 100644
--- a/cmds/dumpstate/tests/dumpstate_smoke_test.cpp
+++ b/cmds/dumpstate/tests/dumpstate_smoke_test.cpp
@@ -497,14 +497,17 @@
     // Prepare arguments
     unique_fd bugreport_fd(OpenForWrite("/bugreports/tmp.zip"));
     unique_fd screenshot_fd(OpenForWrite("/bugreports/tmp.png"));
+    int flags = 0;
 
     EXPECT_NE(bugreport_fd.get(), -1);
     EXPECT_NE(screenshot_fd.get(), -1);
 
     sp<DumpstateListener> listener(new DumpstateListener(dup(fileno(stdout))));
     android::binder::Status status =
-        ds_binder->startBugreport(123, "com.dummy.package", std::move(bugreport_fd), std::move(screenshot_fd),
-                                  Dumpstate::BugreportMode::BUGREPORT_INTERACTIVE, listener, true);
+        ds_binder->startBugreport(123, "com.example.package", std::move(bugreport_fd),
+                                  std::move(screenshot_fd),
+                                  Dumpstate::BugreportMode::BUGREPORT_INTERACTIVE, flags, listener,
+                                  true);
     // startBugreport is an async call. Verify binder call succeeded first, then wait till listener
     // gets expected callbacks.
     EXPECT_TRUE(status.isOk());
@@ -532,6 +535,7 @@
     // Prepare arguments
     unique_fd bugreport_fd(OpenForWrite("/data/local/tmp/tmp.zip"));
     unique_fd screenshot_fd(OpenForWrite("/data/local/tmp/tmp.png"));
+    int flags = 0;
 
     EXPECT_NE(bugreport_fd.get(), -1);
     EXPECT_NE(screenshot_fd.get(), -1);
@@ -539,9 +543,9 @@
     // Call startBugreport with bad arguments.
     sp<DumpstateListener> listener(new DumpstateListener(dup(fileno(stdout))));
     android::binder::Status status =
-        ds_binder->startBugreport(123, "com.dummy.package", std::move(bugreport_fd), std::move(screenshot_fd),
-                                  2000,  // invalid bugreport mode
-                                  listener, false);
+        ds_binder->startBugreport(123, "com.example.package", std::move(bugreport_fd),
+                                  std::move(screenshot_fd), 2000,  // invalid bugreport mode
+                                  flags, listener, false);
     EXPECT_EQ(listener->getErrorCode(), IDumpstateListener::BUGREPORT_ERROR_INVALID_INPUT);
 
     // The service should have died, freeing itself up for a new invocation.
@@ -563,6 +567,7 @@
     unique_fd bugreport_fd2(dup(bugreport_fd.get()));
     unique_fd screenshot_fd(OpenForWrite("/data/local/tmp/tmp.png"));
     unique_fd screenshot_fd2(dup(screenshot_fd.get()));
+    int flags = 0;
 
     EXPECT_NE(bugreport_fd.get(), -1);
     EXPECT_NE(bugreport_fd2.get(), -1);
@@ -571,14 +576,18 @@
 
     sp<DumpstateListener> listener1(new DumpstateListener(dup(fileno(stdout))));
     android::binder::Status status =
-        ds_binder->startBugreport(123, "com.dummy.package", std::move(bugreport_fd), std::move(screenshot_fd),
-                                  Dumpstate::BugreportMode::BUGREPORT_INTERACTIVE, listener1, true);
+        ds_binder->startBugreport(123, "com.example.package", std::move(bugreport_fd),
+                                  std::move(screenshot_fd),
+                                  Dumpstate::BugreportMode::BUGREPORT_INTERACTIVE, flags, listener1,
+                                  true);
     EXPECT_TRUE(status.isOk());
 
     // try to make another call to startBugreport. This should fail.
     sp<DumpstateListener> listener2(new DumpstateListener(dup(fileno(stdout))));
-    status = ds_binder->startBugreport(123, "com.dummy.package", std::move(bugreport_fd2), std::move(screenshot_fd2),
-                                       Dumpstate::BugreportMode::BUGREPORT_INTERACTIVE, listener2, true);
+    status = ds_binder->startBugreport(123, "com.example.package", std::move(bugreport_fd2),
+                                        std::move(screenshot_fd2),
+                                       Dumpstate::BugreportMode::BUGREPORT_INTERACTIVE, flags,
+                                       listener2, true);
     EXPECT_FALSE(status.isOk());
     WaitTillExecutionComplete(listener2.get());
     EXPECT_EQ(listener2->getErrorCode(),
diff --git a/cmds/dumpstate/tests/dumpstate_test.cpp b/cmds/dumpstate/tests/dumpstate_test.cpp
index 70b4e5c..1ffcafa 100644
--- a/cmds/dumpstate/tests/dumpstate_test.cpp
+++ b/cmds/dumpstate/tests/dumpstate_test.cpp
@@ -31,6 +31,7 @@
 #include <signal.h>
 #include <sys/types.h>
 #include <unistd.h>
+#include <filesystem>
 #include <thread>
 
 #include <aidl/android/hardware/dumpstate/IDumpstateDevice.h>
@@ -237,7 +238,7 @@
 }
 
 TEST_F(DumpOptionsTest, InitializeFullBugReport) {
-    options_.Initialize(Dumpstate::BugreportMode::BUGREPORT_FULL, fd, fd, true);
+    options_.Initialize(Dumpstate::BugreportMode::BUGREPORT_FULL, 0, fd, fd, true);
     EXPECT_TRUE(options_.do_screenshot);
 
     // Other options retain default values
@@ -251,7 +252,7 @@
 }
 
 TEST_F(DumpOptionsTest, InitializeInteractiveBugReport) {
-    options_.Initialize(Dumpstate::BugreportMode::BUGREPORT_INTERACTIVE, fd, fd, true);
+    options_.Initialize(Dumpstate::BugreportMode::BUGREPORT_INTERACTIVE, 0, fd, fd, true);
     EXPECT_TRUE(options_.do_progress_updates);
     EXPECT_TRUE(options_.do_screenshot);
 
@@ -265,7 +266,7 @@
 }
 
 TEST_F(DumpOptionsTest, InitializeRemoteBugReport) {
-    options_.Initialize(Dumpstate::BugreportMode::BUGREPORT_REMOTE, fd, fd, false);
+    options_.Initialize(Dumpstate::BugreportMode::BUGREPORT_REMOTE, 0, fd, fd, false);
     EXPECT_TRUE(options_.is_remote_mode);
     EXPECT_FALSE(options_.do_vibrate);
     EXPECT_FALSE(options_.do_screenshot);
@@ -279,7 +280,7 @@
 }
 
 TEST_F(DumpOptionsTest, InitializeWearBugReport) {
-    options_.Initialize(Dumpstate::BugreportMode::BUGREPORT_WEAR, fd, fd, true);
+    options_.Initialize(Dumpstate::BugreportMode::BUGREPORT_WEAR, 0, fd, fd, true);
     EXPECT_TRUE(options_.do_screenshot);
     EXPECT_TRUE(options_.do_progress_updates);
 
@@ -294,7 +295,7 @@
 }
 
 TEST_F(DumpOptionsTest, InitializeTelephonyBugReport) {
-    options_.Initialize(Dumpstate::BugreportMode::BUGREPORT_TELEPHONY, fd, fd, false);
+    options_.Initialize(Dumpstate::BugreportMode::BUGREPORT_TELEPHONY, 0, fd, fd, false);
     EXPECT_FALSE(options_.do_screenshot);
     EXPECT_TRUE(options_.telephony_only);
     EXPECT_TRUE(options_.do_progress_updates);
@@ -309,7 +310,7 @@
 }
 
 TEST_F(DumpOptionsTest, InitializeWifiBugReport) {
-    options_.Initialize(Dumpstate::BugreportMode::BUGREPORT_WIFI, fd, fd, false);
+    options_.Initialize(Dumpstate::BugreportMode::BUGREPORT_WIFI, 0, fd, fd, false);
     EXPECT_FALSE(options_.do_screenshot);
     EXPECT_TRUE(options_.wifi_only);
 
@@ -982,6 +983,19 @@
     EXPECT_FALSE(ds.dump_pool_);
 }
 
+TEST_F(DumpstateBaseTest, PreDumpUiData) {
+    // SurfaceFlinger's transactions trace is always enabled, i.e. it is always pre-dumped
+    static const auto kTransactionsTrace =
+            std::filesystem::path {"/data/misc/wmtrace/transactions_trace.winscope"};
+
+    std::system(("rm " + kTransactionsTrace.string()).c_str());
+    EXPECT_FALSE(std::filesystem::exists(kTransactionsTrace));
+
+    Dumpstate& ds_ = Dumpstate::GetInstance();
+    ds_.PreDumpUiData();
+    EXPECT_TRUE(std::filesystem::exists(kTransactionsTrace));
+}
+
 class ZippedBugReportStreamTest : public DumpstateBaseTest {
   public:
     void SetUp() {
@@ -1045,11 +1059,6 @@
     VerifyEntry(handle_, bugreport_txt_name, &entry);
 }
 
-class DumpstateServiceTest : public DumpstateBaseTest {
-  public:
-    DumpstateService dss;
-};
-
 class ProgressTest : public DumpstateBaseTest {
   public:
     Progress GetInstance(int32_t max, double growth_factor, const std::string& path = "") {
diff --git a/cmds/dumpsys/tests/dumpsys_test.cpp b/cmds/dumpsys/tests/dumpsys_test.cpp
index f0c19b9..b8e5ce1 100644
--- a/cmds/dumpsys/tests/dumpsys_test.cpp
+++ b/cmds/dumpsys/tests/dumpsys_test.cpp
@@ -60,6 +60,7 @@
     MOCK_METHOD1(isDeclared, bool(const String16&));
     MOCK_METHOD1(getDeclaredInstances, Vector<String16>(const String16&));
     MOCK_METHOD1(updatableViaApex, std::optional<String16>(const String16&));
+    MOCK_METHOD1(getUpdatableNames, Vector<String16>(const String16&));
     MOCK_METHOD1(getConnectionInfo, std::optional<ConnectionInfo>(const String16&));
     MOCK_METHOD2(registerForNotifications, status_t(const String16&,
                                              const sp<LocalRegistrationCallback>&));
diff --git a/cmds/installd/InstalldNativeService.cpp b/cmds/installd/InstalldNativeService.cpp
index b1283eb..bb6639e 100644
--- a/cmds/installd/InstalldNativeService.cpp
+++ b/cmds/installd/InstalldNativeService.cpp
@@ -501,10 +501,6 @@
 }
 
 static bool prepare_app_profile_dir(const std::string& packageName, int32_t appId, int32_t userId) {
-    if (!property_get_bool("dalvik.vm.usejitprofiles", false)) {
-        return true;
-    }
-
     int32_t uid = multiuser_get_uid(userId, appId);
     int shared_app_gid = multiuser_get_shared_gid(userId, appId);
     if (shared_app_gid == -1) {
diff --git a/cmds/installd/dexopt.cpp b/cmds/installd/dexopt.cpp
index ebb7891..34ea759 100644
--- a/cmds/installd/dexopt.cpp
+++ b/cmds/installd/dexopt.cpp
@@ -1956,7 +1956,7 @@
                       join_fds(context_input_fds), swap_fd.get(), instruction_set, compiler_filter,
                       debuggable, boot_complete, for_restore, target_sdk_version,
                       enable_hidden_api_checks, generate_compact_dex, use_jitzygote_image,
-                      compilation_reason);
+                      background_job_compile, compilation_reason);
 
     bool cancelled = false;
     pid_t pid = dexopt_status_->check_cancellation_and_fork(&cancelled);
diff --git a/cmds/installd/otapreopt.rc b/cmds/installd/otapreopt.rc
index 059ae75..0bad0c5 100644
--- a/cmds/installd/otapreopt.rc
+++ b/cmds/installd/otapreopt.rc
@@ -5,4 +5,4 @@
     # The dalvik-cache was not moved itself, so as to restrict the rights of otapreopt_slot.
     # But now the relabeling is annoying as there is no force option available here. So
     # explicitly list all the ISAs we know.
-    restorecon_recursive /data/dalvik-cache/arm /data/dalvik-cache/arm64 /data/dalvik-cache/mips /data/dalvik-cache/mips64 /data/dalvik-cache/x86 /data/dalvik-cache/x86_64
+    restorecon_recursive /data/dalvik-cache/arm /data/dalvik-cache/arm64 /data/dalvik-cache/riscv64 /data/dalvik-cache/x86 /data/dalvik-cache/x86_64
diff --git a/cmds/installd/run_dex2oat.cpp b/cmds/installd/run_dex2oat.cpp
index 51c4589..4221a3a 100644
--- a/cmds/installd/run_dex2oat.cpp
+++ b/cmds/installd/run_dex2oat.cpp
@@ -81,6 +81,7 @@
                             bool enable_hidden_api_checks,
                             bool generate_compact_dex,
                             bool use_jitzygote,
+                            bool background_job_compile,
                             const char* compilation_reason) {
     PrepareBootImageFlags(use_jitzygote);
 
@@ -92,7 +93,8 @@
                                debuggable, target_sdk_version, enable_hidden_api_checks,
                                generate_compact_dex, compilation_reason);
 
-    PrepareCompilerRuntimeAndPerfConfigFlags(post_bootcomplete, for_restore);
+    PrepareCompilerRuntimeAndPerfConfigFlags(post_bootcomplete, for_restore,
+                                             background_job_compile);
 
     const std::string dex2oat_flags = GetProperty("dalvik.vm.dex2oat-flags", "");
     std::vector<std::string> dex2oat_flags_args = SplitBySpaces(dex2oat_flags);
@@ -296,7 +298,8 @@
 }
 
 void RunDex2Oat::PrepareCompilerRuntimeAndPerfConfigFlags(bool post_bootcomplete,
-                                                          bool for_restore) {
+                                                          bool for_restore,
+                                                          bool background_job_compile) {
     // CPU set
     {
         std::string cpu_set_format = "--cpu-set=%s";
@@ -306,7 +309,12 @@
                            "dalvik.vm.restore-dex2oat-cpu-set",
                            "dalvik.vm.dex2oat-cpu-set",
                            cpu_set_format)
-                   : MapPropertyToArg("dalvik.vm.dex2oat-cpu-set", cpu_set_format))
+                   : (background_job_compile
+                      ? MapPropertyToArgWithBackup(
+                              "dalvik.vm.background-dex2oat-cpu-set",
+                              "dalvik.vm.dex2oat-cpu-set",
+                              cpu_set_format)
+                      : MapPropertyToArg("dalvik.vm.dex2oat-cpu-set", cpu_set_format)))
                 : MapPropertyToArg("dalvik.vm.boot-dex2oat-cpu-set", cpu_set_format);
         AddArg(dex2oat_cpu_set_arg);
     }
@@ -320,7 +328,12 @@
                            "dalvik.vm.restore-dex2oat-threads",
                            "dalvik.vm.dex2oat-threads",
                            threads_format)
-                   : MapPropertyToArg("dalvik.vm.dex2oat-threads", threads_format))
+                   : (background_job_compile
+                      ? MapPropertyToArgWithBackup(
+                              "dalvik.vm.background-dex2oat-threads",
+                              "dalvik.vm.dex2oat-threads",
+                              threads_format)
+                      : MapPropertyToArg("dalvik.vm.dex2oat-threads", threads_format)))
                 : MapPropertyToArg("dalvik.vm.boot-dex2oat-threads", threads_format);
         AddArg(dex2oat_threads_arg);
     }
diff --git a/cmds/installd/run_dex2oat.h b/cmds/installd/run_dex2oat.h
index 559244f..c13e1f1 100644
--- a/cmds/installd/run_dex2oat.h
+++ b/cmds/installd/run_dex2oat.h
@@ -51,6 +51,7 @@
                     bool enable_hidden_api_checks,
                     bool generate_compact_dex,
                     bool use_jitzygote,
+                    bool background_job_compile,
                     const char* compilation_reason);
 
     void Exec(int exit_code);
@@ -76,7 +77,9 @@
                                     bool enable_hidden_api_checks,
                                     bool generate_compact_dex,
                                     const char* compilation_reason);
-    void PrepareCompilerRuntimeAndPerfConfigFlags(bool post_bootcomplete, bool for_restore);
+    void PrepareCompilerRuntimeAndPerfConfigFlags(bool post_bootcomplete,
+                                                  bool for_restore,
+                                                  bool background_job_compile);
 
     virtual std::string GetProperty(const std::string& key, const std::string& default_value);
     virtual bool GetBoolProperty(const std::string& key, bool default_value);
diff --git a/cmds/installd/run_dex2oat_test.cpp b/cmds/installd/run_dex2oat_test.cpp
index 2a8135a..304ba7b 100644
--- a/cmds/installd/run_dex2oat_test.cpp
+++ b/cmds/installd/run_dex2oat_test.cpp
@@ -115,6 +115,7 @@
         bool enable_hidden_api_checks = false;
         bool generate_compact_dex = true;
         bool use_jitzygote = false;
+        bool background_job_compile = false;
         const char* compilation_reason = nullptr;
     };
 
@@ -259,6 +260,7 @@
                           args->enable_hidden_api_checks,
                           args->generate_compact_dex,
                           args->use_jitzygote,
+                          args->background_job_compile,
                           args->compilation_reason);
         runner.Exec(/*exit_code=*/ 0);
     }
@@ -375,6 +377,30 @@
     VerifyExpectedFlags();
 }
 
+TEST_F(RunDex2OatTest, CpuSetPostBootCompleteBackground) {
+    setSystemProperty("dalvik.vm.background-dex2oat-cpu-set", "1,3");
+    setSystemProperty("dalvik.vm.dex2oat-cpu-set", "1,2");
+    auto args = RunDex2OatArgs::MakeDefaultTestArgs();
+    args->post_bootcomplete = true;
+    args->background_job_compile = true;
+    CallRunDex2Oat(std::move(args));
+
+    SetExpectedFlagUsed("--cpu-set", "=1,3");
+    VerifyExpectedFlags();
+}
+
+TEST_F(RunDex2OatTest, CpuSetPostBootCompleteBackground_Backup) {
+    setSystemProperty("dalvik.vm.background-dex2oat-cpu-set", "");
+    setSystemProperty("dalvik.vm.dex2oat-cpu-set", "1,2");
+    auto args = RunDex2OatArgs::MakeDefaultTestArgs();
+    args->post_bootcomplete = true;
+    args->background_job_compile = true;
+    CallRunDex2Oat(std::move(args));
+
+    SetExpectedFlagUsed("--cpu-set", "=1,2");
+    VerifyExpectedFlags();
+}
+
 TEST_F(RunDex2OatTest, CpuSetPostBootCompleteForRestore) {
     setSystemProperty("dalvik.vm.restore-dex2oat-cpu-set", "1,2");
     setSystemProperty("dalvik.vm.dex2oat-cpu-set", "2,3");
@@ -481,6 +507,30 @@
     VerifyExpectedFlags();
 }
 
+TEST_F(RunDex2OatTest, ThreadsPostBootCompleteBackground) {
+    setSystemProperty("dalvik.vm.background-dex2oat-threads", "2");
+    setSystemProperty("dalvik.vm.dex2oat-threads", "3");
+    auto args = RunDex2OatArgs::MakeDefaultTestArgs();
+    args->post_bootcomplete = true;
+    args->background_job_compile = true;
+    CallRunDex2Oat(std::move(args));
+
+    SetExpectedFlagUsed("-j", "2");
+    VerifyExpectedFlags();
+}
+
+TEST_F(RunDex2OatTest, ThreadsPostBootCompleteBackground_Backup) {
+    setSystemProperty("dalvik.vm.background-dex2oat-threads", "");
+    setSystemProperty("dalvik.vm.dex2oat-threads", "3");
+    auto args = RunDex2OatArgs::MakeDefaultTestArgs();
+    args->post_bootcomplete = true;
+    args->background_job_compile = true;
+    CallRunDex2Oat(std::move(args));
+
+    SetExpectedFlagUsed("-j", "3");
+    VerifyExpectedFlags();
+}
+
 TEST_F(RunDex2OatTest, ThreadsPostBootCompleteForRestore) {
     setSystemProperty("dalvik.vm.restore-dex2oat-threads", "4");
     setSystemProperty("dalvik.vm.dex2oat-threads", "5");
diff --git a/cmds/installd/tests/installd_dexopt_test.cpp b/cmds/installd/tests/installd_dexopt_test.cpp
index 6ef41e3..3b589dc 100644
--- a/cmds/installd/tests/installd_dexopt_test.cpp
+++ b/cmds/installd/tests/installd_dexopt_test.cpp
@@ -23,6 +23,7 @@
 
 #include <android-base/file.h>
 #include <android-base/logging.h>
+#include <android-base/macros.h>
 #include <android-base/properties.h>
 #include <android-base/scopeguard.h>
 #include <android-base/stringprintf.h>
@@ -52,22 +53,7 @@
 
 constexpr int kTimeoutMs = 60000;
 
-// TODO(calin): try to dedup this code.
-#if defined(__arm__)
-static const std::string kRuntimeIsa = "arm";
-#elif defined(__aarch64__)
-static const std::string kRuntimeIsa = "arm64";
-#elif defined(__mips__) && !defined(__LP64__)
-static const std::string kRuntimeIsa = "mips";
-#elif defined(__mips__) && defined(__LP64__)
-static const std::string kRuntimeIsa = "mips64";
-#elif defined(__i386__)
-static const std::string kRuntimeIsa = "x86";
-#elif defined(__x86_64__)
-static const std::string kRuntimeIsa = "x86_64";
-#else
-static const std::string kRuntimeIsa = "none";
-#endif
+static const std::string kRuntimeIsa = ABI_STRING;
 
 int get_property(const char *key, char *value, const char *default_value) {
     return property_get(key, value, default_value);
diff --git a/cmds/servicemanager/Android.bp b/cmds/servicemanager/Android.bp
index fd879c6..1386660 100644
--- a/cmds/servicemanager/Android.bp
+++ b/cmds/servicemanager/Android.bp
@@ -48,14 +48,6 @@
 }
 
 cc_binary {
-    name: "servicemanager.microdroid",
-    defaults: ["servicemanager_defaults"],
-    init_rc: ["servicemanager.microdroid.rc"],
-    srcs: ["main.cpp"],
-    bootstrap: true,
-}
-
-cc_binary {
     name: "servicemanager.recovery",
     stem: "servicemanager",
     recovery: true,
diff --git a/cmds/servicemanager/ServiceManager.cpp b/cmds/servicemanager/ServiceManager.cpp
index 3681d5b..cc038ae 100644
--- a/cmds/servicemanager/ServiceManager.cpp
+++ b/cmds/servicemanager/ServiceManager.cpp
@@ -136,12 +136,33 @@
             updatableViaApex = manifestInstance.updatableViaApex();
             return false; // break (libvintf uses opposite convention)
         });
+        if (updatableViaApex.has_value()) return true; // break (found match)
         return false; // continue
     });
 
     return updatableViaApex;
 }
 
+static std::vector<std::string> getVintfUpdatableInstances(const std::string& apexName) {
+    std::vector<std::string> instances;
+
+    forEachManifest([&](const ManifestWithDescription& mwd) {
+        mwd.manifest->forEachInstance([&](const auto& manifestInstance) {
+            if (manifestInstance.format() == vintf::HalFormat::AIDL &&
+                manifestInstance.updatableViaApex().has_value() &&
+                manifestInstance.updatableViaApex().value() == apexName) {
+                std::string aname = manifestInstance.package() + "." +
+                        manifestInstance.interface() + "/" + manifestInstance.instance();
+                instances.push_back(aname);
+            }
+            return true; // continue (libvintf uses opposite convention)
+        });
+        return false; // continue
+    });
+
+    return instances;
+}
+
 static std::optional<ConnectionInfo> getVintfConnectionInfo(const std::string& name) {
     AidlName aname;
     if (!AidlName::fill(name, &aname)) return std::nullopt;
@@ -512,6 +533,30 @@
     return Status::ok();
 }
 
+Status ServiceManager::getUpdatableNames([[maybe_unused]] const std::string& apexName,
+                                         std::vector<std::string>* outReturn) {
+    auto ctx = mAccess->getCallingContext();
+
+    std::vector<std::string> apexUpdatableInstances;
+#ifndef VENDORSERVICEMANAGER
+    apexUpdatableInstances = getVintfUpdatableInstances(apexName);
+#endif
+
+    outReturn->clear();
+
+    for (const std::string& instance : apexUpdatableInstances) {
+        if (mAccess->canFind(ctx, instance)) {
+            outReturn->push_back(instance);
+        }
+    }
+
+    if (outReturn->size() == 0 && apexUpdatableInstances.size() != 0) {
+        return Status::fromExceptionCode(Status::EX_SECURITY, "SELinux denial");
+    }
+
+    return Status::ok();
+}
+
 Status ServiceManager::getConnectionInfo(const std::string& name,
                                          std::optional<ConnectionInfo>* outReturn) {
     auto ctx = mAccess->getCallingContext();
@@ -568,7 +613,8 @@
 }
 
 void ServiceManager::tryStartService(const std::string& name) {
-    ALOGI("Since '%s' could not be found, trying to start it as a lazy AIDL service",
+    ALOGI("Since '%s' could not be found, trying to start it as a lazy AIDL service. (if it's not "
+          "configured to be a lazy service, it may be stuck starting or still starting).",
           name.c_str());
 
     std::thread([=] {
diff --git a/cmds/servicemanager/ServiceManager.h b/cmds/servicemanager/ServiceManager.h
index 07b79f8..b24c11c 100644
--- a/cmds/servicemanager/ServiceManager.h
+++ b/cmds/servicemanager/ServiceManager.h
@@ -49,6 +49,8 @@
     binder::Status getDeclaredInstances(const std::string& interface, std::vector<std::string>* outReturn) override;
     binder::Status updatableViaApex(const std::string& name,
                                     std::optional<std::string>* outReturn) override;
+    binder::Status getUpdatableNames(const std::string& apexName,
+                                     std::vector<std::string>* outReturn) override;
     binder::Status getConnectionInfo(const std::string& name,
                                      std::optional<ConnectionInfo>* outReturn) override;
     binder::Status registerClientCallback(const std::string& name, const sp<IBinder>& service,
diff --git a/cmds/servicemanager/main.cpp b/cmds/servicemanager/main.cpp
index a831d1b..c1a04dd 100644
--- a/cmds/servicemanager/main.cpp
+++ b/cmds/servicemanager/main.cpp
@@ -111,9 +111,7 @@
 };
 
 int main(int argc, char** argv) {
-#ifdef __ANDROID_RECOVERY__
     android::base::InitLogging(argv, android::base::KernelLogger);
-#endif
 
     if (argc > 2) {
         LOG(FATAL) << "usage: " << argv[0] << " [binder driver]";
diff --git a/cmds/servicemanager/servicemanager.microdroid.rc b/cmds/servicemanager/servicemanager.microdroid.rc
deleted file mode 100644
index c516043..0000000
--- a/cmds/servicemanager/servicemanager.microdroid.rc
+++ /dev/null
@@ -1,9 +0,0 @@
-service servicemanager /system/bin/servicemanager.microdroid
-    class core
-    user system
-    group system readproc
-    critical
-    onrestart setprop servicemanager.ready false
-    onrestart restart apexd
-    task_profiles ServiceCapacityLow
-    shutdown critical
diff --git a/cmds/servicemanager/servicemanager.rc b/cmds/servicemanager/servicemanager.rc
index 6b35265..3bd6db5 100644
--- a/cmds/servicemanager/servicemanager.rc
+++ b/cmds/servicemanager/servicemanager.rc
@@ -3,6 +3,7 @@
     user system
     group system readproc
     critical
+    file /dev/kmsg w
     onrestart setprop servicemanager.ready false
     onrestart restart apexd
     onrestart restart audioserver
diff --git a/cmds/servicemanager/test_sm.cpp b/cmds/servicemanager/test_sm.cpp
index 5d5a75e..0fd8d8e 100644
--- a/cmds/servicemanager/test_sm.cpp
+++ b/cmds/servicemanager/test_sm.cpp
@@ -14,13 +14,15 @@
  * limitations under the License.
  */
 
+#include <android-base/properties.h>
+#include <android-base/strings.h>
 #include <android/os/BnServiceCallback.h>
 #include <binder/Binder.h>
-#include <binder/ProcessState.h>
 #include <binder/IServiceManager.h>
+#include <binder/ProcessState.h>
 #include <cutils/android_filesystem_config.h>
-#include <gtest/gtest.h>
 #include <gmock/gmock.h>
+#include <gtest/gtest.h>
 
 #include "Access.h"
 #include "ServiceManager.h"
@@ -75,6 +77,11 @@
     return sm;
 }
 
+static bool isCuttlefish() {
+    return android::base::StartsWith(android::base::GetProperty("ro.product.vendor.device", ""),
+                                     "vsoc_");
+}
+
 TEST(AddService, HappyHappy) {
     auto sm = getPermissiveServiceManager();
     EXPECT_TRUE(sm->addService("foo", getBinder(), false /*allowIsolated*/,
@@ -306,6 +313,49 @@
     EXPECT_THAT(out, ElementsAre("sa"));
 }
 
+TEST(Vintf, UpdatableViaApex) {
+    if (!isCuttlefish()) GTEST_SKIP() << "Skipping non-Cuttlefish devices";
+
+    auto sm = getPermissiveServiceManager();
+    std::optional<std::string> updatableViaApex;
+    EXPECT_TRUE(sm->updatableViaApex("android.hardware.camera.provider.ICameraProvider/internal/0",
+                                     &updatableViaApex)
+                        .isOk());
+    EXPECT_EQ(std::make_optional<std::string>("com.google.emulated.camera.provider.hal"),
+              updatableViaApex);
+}
+
+TEST(Vintf, UpdatableViaApex_InvalidNameReturnsNullOpt) {
+    if (!isCuttlefish()) GTEST_SKIP() << "Skipping non-Cuttlefish devices";
+
+    auto sm = getPermissiveServiceManager();
+    std::optional<std::string> updatableViaApex;
+    EXPECT_TRUE(sm->updatableViaApex("android.hardware.camera.provider.ICameraProvider",
+                                     &updatableViaApex)
+                        .isOk()); // missing instance name
+    EXPECT_EQ(std::nullopt, updatableViaApex);
+}
+
+TEST(Vintf, GetUpdatableNames) {
+    if (!isCuttlefish()) GTEST_SKIP() << "Skipping non-Cuttlefish devices";
+
+    auto sm = getPermissiveServiceManager();
+    std::vector<std::string> names;
+    EXPECT_TRUE(sm->getUpdatableNames("com.google.emulated.camera.provider.hal", &names).isOk());
+    EXPECT_EQ(std::vector<
+                      std::string>{"android.hardware.camera.provider.ICameraProvider/internal/0"},
+              names);
+}
+
+TEST(Vintf, GetUpdatableNames_InvalidApexNameReturnsEmpty) {
+    if (!isCuttlefish()) GTEST_SKIP() << "Skipping non-Cuttlefish devices";
+
+    auto sm = getPermissiveServiceManager();
+    std::vector<std::string> names;
+    EXPECT_TRUE(sm->getUpdatableNames("non.existing.apex.name", &names).isOk());
+    EXPECT_EQ(std::vector<std::string>{}, names);
+}
+
 class CallbackHistorian : public BnServiceCallback {
     Status onRegistration(const std::string& name, const sp<IBinder>& binder) override {
         registrations.push_back(name);
diff --git a/cmds/servicemanager/vndservicemanager.rc b/cmds/servicemanager/vndservicemanager.rc
index c9305a1..80af1d1 100644
--- a/cmds/servicemanager/vndservicemanager.rc
+++ b/cmds/servicemanager/vndservicemanager.rc
@@ -2,6 +2,7 @@
     class core
     user system
     group system readproc
+    file /dev/kmsg w
     task_profiles ServiceCapacityLow
     onrestart class_restart main
     onrestart class_restart hal
diff --git a/data/etc/android.software.opengles.deqp.level-2023-03-01.xml b/data/etc/android.software.opengles.deqp.level-2023-03-01.xml
new file mode 100644
index 0000000..d0b594c
--- /dev/null
+++ b/data/etc/android.software.opengles.deqp.level-2023-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="132580097" />
+</permissions>
diff --git a/data/etc/android.software.vulkan.deqp.level-2023-03-01.xml b/data/etc/android.software.vulkan.deqp.level-2023-03-01.xml
new file mode 100644
index 0000000..6ae248a
--- /dev/null
+++ b/data/etc/android.software.vulkan.deqp.level-2023-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="132580097" />
+</permissions>
diff --git a/include/android/font.h b/include/android/font.h
index 8a3a474..0225725 100644
--- a/include/android/font.h
+++ b/include/android/font.h
@@ -31,6 +31,7 @@
 
 #include <stdbool.h>
 #include <stddef.h>
+#include <stdint.h>
 #include <sys/cdefs.h>
 
 /******************************************************************
@@ -86,10 +87,11 @@
     AFONT_WEIGHT_MAX = 1000
 };
 
+struct AFont;
 /**
  * AFont provides information of the single font configuration.
  */
-struct AFont;
+typedef struct AFont AFont;
 
 /**
  * Close an AFont.
diff --git a/include/android/font_matcher.h b/include/android/font_matcher.h
index 4417422..60ff95e 100644
--- a/include/android/font_matcher.h
+++ b/include/android/font_matcher.h
@@ -75,6 +75,7 @@
 
 #include <stdbool.h>
 #include <stddef.h>
+#include <stdint.h>
 #include <sys/cdefs.h>
 
 #include <android/font.h>
@@ -116,11 +117,12 @@
     AFAMILY_VARIANT_ELEGANT = 2,
 };
 
+struct AFontMatcher;
 /**
  * AFontMatcher performs match operation on given parameters and available font files.
  * This matcher is not a thread-safe object. Do not pass this matcher to other threads.
  */
-struct AFontMatcher;
+typedef struct AFontMatcher AFontMatcher;
 
 /**
  * Select the best font from given parameters.
diff --git a/include/android/performance_hint.h b/include/android/performance_hint.h
index 5fa47f6..eed6b33 100644
--- a/include/android/performance_hint.h
+++ b/include/android/performance_hint.h
@@ -88,6 +88,36 @@
 typedef struct APerformanceHintSession APerformanceHintSession;
 
 /**
+ * Hints for the session used by {@link APerformanceHint_sendHint} to signal upcoming changes
+ * in the mode or workload.
+ */
+enum SessionHint {
+    /**
+     * This hint indicates a sudden increase in CPU workload intensity. It means
+     * that this hint session needs extra CPU resources immediately to meet the
+     * target duration for the current work cycle.
+     */
+    CPU_LOAD_UP = 0,
+    /**
+     * This hint indicates a decrease in CPU workload intensity. It means that
+     * this hint session can reduce CPU resources and still meet the target duration.
+     */
+    CPU_LOAD_DOWN = 1,
+    /*
+     * This hint indicates an upcoming CPU workload that is completely changed and
+     * unknown. It means that the hint session should reset CPU resources to a known
+     * baseline to prepare for an arbitrary load, and must wake up if inactive.
+     */
+    CPU_LOAD_RESET = 2,
+    /*
+     * This hint indicates that the most recent CPU workload is resuming after a
+     * period of inactivity. It means that the hint session should allocate similar
+     * CPU resources to what was used previously, and must wake up if inactive.
+     */
+    CPU_LOAD_RESUME = 3,
+};
+
+/**
   * Acquire an instance of the performance hint manager.
   *
   * @return manager instance on success, nullptr on failure.
@@ -159,6 +189,17 @@
 void APerformanceHint_closeSession(
         APerformanceHintSession* session) __INTRODUCED_IN(__ANDROID_API_T__);
 
+/**
+ * Sends performance hints to inform the hint session of changes in the workload.
+ *
+ * @param session The performance hint session instance to update.
+ * @param hint The hint to send to the session.
+ * @return 0 on success
+ *         EPIPE if communication with the system service has failed.
+ */
+int APerformanceHint_sendHint(
+        APerformanceHintSession* session, int hint) __INTRODUCED_IN(__ANDROID_API_U__);
+
 __END_DECLS
 
 #endif // ANDROID_NATIVE_PERFORMANCE_HINT_H
diff --git a/include/android/sensor.h b/include/android/sensor.h
index 105f952..085fc27 100644
--- a/include/android/sensor.h
+++ b/include/android/sensor.h
@@ -601,12 +601,15 @@
     float accuracy;
 } AHeadingEvent;
 
+// LINT.IfChange
 /**
  * Information that describes a sensor event, refer to
  * <a href="/reference/android/hardware/SensorEvent">SensorEvent</a> for additional
  * documentation.
+ *
+ * NOTE: changes to this struct has to be backward compatible and reflected in
+ * sensors_event_t
  */
-/* NOTE: changes to this struct has to be backward compatible */
 typedef struct ASensorEvent {
     int32_t version; /* sizeof(struct ASensorEvent) */
     int32_t sensor;  /** The sensor that generates this event */
@@ -651,6 +654,7 @@
     uint32_t flags;
     int32_t reserved1[3];
 } ASensorEvent;
+// LINT.ThenChange (hardware/libhardware/include/hardware/sensors.h)
 
 struct ASensorManager;
 /**
diff --git a/include/android/surface_control_jni.h b/include/android/surface_control_jni.h
index 67e3a9f..840f6e7 100644
--- a/include/android/surface_control_jni.h
+++ b/include/android/surface_control_jni.h
@@ -36,14 +36,15 @@
 /**
  * Return the ASurfaceControl wrapped by a Java SurfaceControl object.
  *
- * This method does not acquire any additional reference to the ASurfaceControl
- * that is returned. To keep the ASurfaceControl alive after the Java
- * SurfaceControl object is closed, explicitly or by the garbage collector, be
- * sure to use ASurfaceControl_acquire() to acquire an additional reference.
+ * The caller takes ownership of the returned ASurfaceControl returned and must
+ * release it * using ASurfaceControl_release.
+ *
+ * surfaceControlObj must be a non-null instance of android.view.SurfaceControl
+ * and isValid() must be true.
  *
  * Available since API level 34.
  */
-ASurfaceControl* _Nullable ASurfaceControl_fromSurfaceControl(JNIEnv* _Nonnull env,
+ASurfaceControl* _Nonnull ASurfaceControl_fromJava(JNIEnv* _Nonnull env,
         jobject _Nonnull surfaceControlObj) __INTRODUCED_IN(__ANDROID_API_U__);
 
 /**
@@ -52,11 +53,13 @@
  * The returned ASurfaceTransaction is still owned by the Java Transaction object is only
  * valid while the Java Transaction object is alive. In particular, the returned transaction
  * must NOT be deleted with ASurfaceTransaction_delete.
- * May return nullptr on error.
+ *
+ * transactionObj must be a non-null instance of
+ * android.view.SurfaceControl.Transaction and close() must not already be called.
  *
  * Available since API level 34.
  */
-ASurfaceTransaction* _Nullable ASurfaceTransaction_fromTransaction(JNIEnv* _Nonnull env,
+ASurfaceTransaction* _Nonnull ASurfaceTransaction_fromJava(JNIEnv* _Nonnull env,
         jobject _Nonnull transactionObj) __INTRODUCED_IN(__ANDROID_API_U__);
 
 __END_DECLS
diff --git a/include/android/system_fonts.h b/include/android/system_fonts.h
index b0bbb95..94484ea 100644
--- a/include/android/system_fonts.h
+++ b/include/android/system_fonts.h
@@ -87,13 +87,14 @@
 
 __BEGIN_DECLS
 
+struct ASystemFontIterator;
 /**
  * ASystemFontIterator provides access to the system font configuration.
  *
  * ASystemFontIterator is an iterator for all available system font settings.
  * This iterator is not a thread-safe object. Do not pass this iterator to other threads.
  */
-struct ASystemFontIterator;
+typedef struct ASystemFontIterator ASystemFontIterator;
 
 /**
  * Create a system font iterator.
diff --git a/include/attestation/HmacKeyManager.h b/include/attestation/HmacKeyManager.h
index 571a361..d725be1 100644
--- a/include/attestation/HmacKeyManager.h
+++ b/include/attestation/HmacKeyManager.h
@@ -14,6 +14,9 @@
  * limitations under the License.
  */
 
+#ifndef ATTESTATION_HMACKEYMANAGER_H
+#define ATTESTATION_HMACKEYMANAGER_H
+
 #include <array>
 
 namespace android {
@@ -29,4 +32,6 @@
 private:
     const std::array<uint8_t, 128> mHmacKey;
 };
-} // namespace android
\ No newline at end of file
+} // namespace android
+
+#endif // ATTESTATION_HMACKEYMANAGER_H
\ No newline at end of file
diff --git a/include/ftl/concat.h b/include/ftl/concat.h
index ded48f7..e0774d3 100644
--- a/include/ftl/concat.h
+++ b/include/ftl/concat.h
@@ -20,7 +20,9 @@
 
 namespace android::ftl {
 
-// Lightweight (not allocating nor sprintf-based) concatenation.
+// Lightweight (not allocating nor sprintf-based) concatenation. The variadic arguments can be
+// values of integral type (including bool and char), string literals, or strings whose length
+// is constrained:
 //
 //   std::string_view name = "Volume";
 //   ftl::Concat string(ftl::truncated<3>(name), ": ", -3, " dB");
diff --git a/include/ftl/details/concat.h b/include/ftl/details/concat.h
index 8ce949e..726ba02 100644
--- a/include/ftl/details/concat.h
+++ b/include/ftl/details/concat.h
@@ -19,6 +19,7 @@
 #include <functional>
 #include <string_view>
 
+#include <ftl/details/type_traits.h>
 #include <ftl/string.h>
 
 namespace android::ftl::details {
@@ -26,16 +27,42 @@
 template <typename T, typename = void>
 struct StaticString;
 
+// Booleans.
 template <typename T>
-struct StaticString<T, std::enable_if_t<std::is_integral_v<T>>> {
-  static constexpr std::size_t N = to_chars_length_v<T>;
+struct StaticString<T, std::enable_if_t<is_bool_v<T>>> {
+  static constexpr std::size_t N = 5;  // Length of "false".
 
-  explicit StaticString(T v) : view(to_chars(buffer, v)) {}
+  explicit constexpr StaticString(bool b) : view(b ? "true" : "false") {}
 
-  to_chars_buffer_t<T> buffer;
   const std::string_view view;
 };
 
+// Characters.
+template <typename T>
+struct StaticString<T, std::enable_if_t<is_char_v<T>>> {
+  static constexpr std::size_t N = 1;
+
+  explicit constexpr StaticString(char c) : character(c) {}
+
+  const char character;
+  const std::string_view view{&character, 1u};
+};
+
+// Integers, including the integer value of other character types like char32_t.
+template <typename T>
+struct StaticString<
+    T, std::enable_if_t<std::is_integral_v<remove_cvref_t<T>> && !is_bool_v<T> && !is_char_v<T>>> {
+  using U = remove_cvref_t<T>;
+  static constexpr std::size_t N = to_chars_length_v<U>;
+
+  // TODO: Mark this and to_chars as `constexpr` in C++23.
+  explicit StaticString(U v) : view(to_chars(buffer, v)) {}
+
+  to_chars_buffer_t<U> buffer;
+  const std::string_view view;
+};
+
+// Character arrays.
 template <std::size_t M>
 struct StaticString<const char (&)[M], void> {
   static constexpr std::size_t N = M - 1;
@@ -50,6 +77,7 @@
   std::string_view view;
 };
 
+// Strings with constrained length.
 template <std::size_t M>
 struct StaticString<Truncated<M>, void> {
   static constexpr std::size_t N = M;
diff --git a/include/ftl/details/mixins.h b/include/ftl/details/mixins.h
new file mode 100644
index 0000000..9ab9e08
--- /dev/null
+++ b/include/ftl/details/mixins.h
@@ -0,0 +1,30 @@
+/*
+ * 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
+
+namespace android::ftl::details {
+
+template <typename Self, template <typename> class>
+class Mixin {
+ protected:
+  constexpr Self& self() { return *static_cast<Self*>(this); }
+  constexpr const Self& self() const { return *static_cast<const Self*>(this); }
+
+  constexpr auto& mut() { return self().value_; }
+};
+
+}  // namespace android::ftl::details
diff --git a/include/ftl/details/type_traits.h b/include/ftl/details/type_traits.h
index 7092ec5..47bebc5 100644
--- a/include/ftl/details/type_traits.h
+++ b/include/ftl/details/type_traits.h
@@ -24,4 +24,10 @@
 template <typename U>
 using remove_cvref_t = std::remove_cv_t<std::remove_reference_t<U>>;
 
+template <typename T>
+constexpr bool is_bool_v = std::is_same_v<remove_cvref_t<T>, bool>;
+
+template <typename T>
+constexpr bool is_char_v = std::is_same_v<remove_cvref_t<T>, char>;
+
 }  // namespace android::ftl::details
diff --git a/include/ftl/enum.h b/include/ftl/enum.h
index 82af1d6..075d12b 100644
--- a/include/ftl/enum.h
+++ b/include/ftl/enum.h
@@ -92,7 +92,7 @@
 //   enum class E { A, B, C };
 //   static_assert(ftl::to_underlying(E::B) == 1);
 //
-template <typename E>
+template <typename E, typename = std::enable_if_t<std::is_enum_v<E>>>
 constexpr auto to_underlying(E v) {
   return static_cast<std::underlying_type_t<E>>(v);
 }
diff --git a/include/ftl/flags.h b/include/ftl/flags.h
index 70aaa0e..cdb4e84 100644
--- a/include/ftl/flags.h
+++ b/include/ftl/flags.h
@@ -125,7 +125,7 @@
     /* Tests whether all of the given flags are set */
     bool all(Flags<F> f) const { return (mFlags & f.mFlags) == f.mFlags; }
 
-    Flags<F> operator|(Flags<F> rhs) const { return static_cast<F>(mFlags | rhs.mFlags); }
+    constexpr Flags<F> operator|(Flags<F> rhs) const { return static_cast<F>(mFlags | rhs.mFlags); }
     Flags<F>& operator|=(Flags<F> rhs) {
         mFlags = mFlags | rhs.mFlags;
         return *this;
@@ -217,7 +217,7 @@
 }
 
 template <typename F, typename = std::enable_if_t<is_scoped_enum_v<F>>>
-Flags<F> operator|(F lhs, F rhs) {
+constexpr Flags<F> operator|(F lhs, F rhs) {
     return static_cast<F>(to_underlying(lhs) | to_underlying(rhs));
 }
 
diff --git a/include/ftl/mixins.h b/include/ftl/mixins.h
new file mode 100644
index 0000000..0e1d200
--- /dev/null
+++ b/include/ftl/mixins.h
@@ -0,0 +1,148 @@
+/*
+ * 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 <ftl/details/mixins.h>
+
+namespace android::ftl {
+
+// CRTP mixins for defining type-safe wrappers that are distinct from their underlying type. Common
+// uses are IDs, opaque handles, and physical quantities. The constructor is provided by (and must
+// be inherited from) the `Constructible` mixin, whereas operators (equality, ordering, arithmetic,
+// etc.) are enabled through inheritance:
+//
+//   struct Id : ftl::Constructible<Id, std::int32_t>, ftl::Equatable<Id> {
+//     using Constructible::Constructible;
+//   };
+//
+//   static_assert(!std::is_default_constructible_v<Id>);
+//
+// Unlike `Constructible`, `DefaultConstructible` allows default construction. The default value is
+// zero-initialized unless specified:
+//
+//   struct Color : ftl::DefaultConstructible<Color, std::uint8_t>,
+//                  ftl::Equatable<Color>,
+//                  ftl::Orderable<Color> {
+//     using DefaultConstructible::DefaultConstructible;
+//   };
+//
+//   static_assert(Color() == Color(0u));
+//   static_assert(ftl::to_underlying(Color(-1)) == 255u);
+//   static_assert(Color(1u) < Color(2u));
+//
+//   struct Sequence : ftl::DefaultConstructible<Sequence, std::int8_t, -1>,
+//                     ftl::Equatable<Sequence>,
+//                     ftl::Orderable<Sequence>,
+//                     ftl::Incrementable<Sequence> {
+//     using DefaultConstructible::DefaultConstructible;
+//   };
+//
+//   static_assert(Sequence() == Sequence(-1));
+//
+// The underlying type need not be a fundamental type:
+//
+//   struct Timeout : ftl::DefaultConstructible<Timeout, std::chrono::seconds, 10>,
+//                    ftl::Equatable<Timeout>,
+//                    ftl::Addable<Timeout> {
+//     using DefaultConstructible::DefaultConstructible;
+//   };
+//
+//   using namespace std::chrono_literals;
+//   static_assert(Timeout() + Timeout(5s) == Timeout(15s));
+//
+template <typename Self, typename T>
+struct Constructible {
+  explicit constexpr Constructible(T value) : value_(value) {}
+
+  explicit constexpr operator const T&() const { return value_; }
+
+ private:
+  template <typename, template <typename> class>
+  friend class details::Mixin;
+
+  T value_;
+};
+
+template <typename Self, typename T, auto kDefault = T{}>
+struct DefaultConstructible : Constructible<Self, T> {
+  using Constructible<Self, T>::Constructible;
+  constexpr DefaultConstructible() : DefaultConstructible(T{kDefault}) {}
+};
+
+// Shorthand for casting a type-safe wrapper to its underlying value.
+template <typename Self, typename T>
+constexpr const T& to_underlying(const Constructible<Self, T>& c) {
+  return static_cast<const T&>(c);
+}
+
+// Comparison operators for equality.
+template <typename Self>
+struct Equatable : details::Mixin<Self, Equatable> {
+  constexpr bool operator==(const Self& other) const {
+    return to_underlying(this->self()) == to_underlying(other);
+  }
+
+  constexpr bool operator!=(const Self& other) const { return !(*this == other); }
+};
+
+// Comparison operators for ordering.
+template <typename Self>
+struct Orderable : details::Mixin<Self, Orderable> {
+  constexpr bool operator<(const Self& other) const {
+    return to_underlying(this->self()) < to_underlying(other);
+  }
+
+  constexpr bool operator>(const Self& other) const { return other < this->self(); }
+  constexpr bool operator>=(const Self& other) const { return !(*this < other); }
+  constexpr bool operator<=(const Self& other) const { return !(*this > other); }
+};
+
+// Pre-increment and post-increment operators.
+template <typename Self>
+struct Incrementable : details::Mixin<Self, Incrementable> {
+  constexpr Self& operator++() {
+    ++this->mut();
+    return this->self();
+  }
+
+  constexpr Self operator++(int) {
+    const Self tmp = this->self();
+    operator++();
+    return tmp;
+  }
+};
+
+// Additive operators, including incrementing.
+template <typename Self>
+struct Addable : details::Mixin<Self, Addable>, Incrementable<Self> {
+  constexpr Self& operator+=(const Self& other) {
+    this->mut() += to_underlying(other);
+    return this->self();
+  }
+
+  constexpr Self operator+(const Self& other) const {
+    Self tmp = this->self();
+    return tmp += other;
+  }
+
+ private:
+  using Base = details::Mixin<Self, Addable>;
+  using Base::mut;
+  using Base::self;
+};
+
+}  // namespace android::ftl
diff --git a/include/ftl/non_null.h b/include/ftl/non_null.h
new file mode 100644
index 0000000..35d09d7
--- /dev/null
+++ b/include/ftl/non_null.h
@@ -0,0 +1,116 @@
+/*
+ * 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 <cstdlib>
+#include <type_traits>
+#include <utility>
+
+namespace android::ftl {
+
+// Enforces and documents non-null pre/post-condition for (raw or smart) pointers.
+//
+//   void get_length(const ftl::NonNull<std::shared_ptr<std::string>>& string_ptr,
+//                   ftl::NonNull<std::size_t*> length_ptr) {
+//     // No need for `nullptr` checks.
+//     *length_ptr = string_ptr->length();
+//   }
+//
+//   const auto string_ptr = ftl::as_non_null(std::make_shared<std::string>("android"));
+//   std::size_t size;
+//   get_length(string_ptr, ftl::as_non_null(&size));
+//   assert(size == 7u);
+//
+// For compatibility with std::unique_ptr<T> and performance with std::shared_ptr<T>, move
+// operations are allowed despite breaking the invariant:
+//
+//   using Pair = std::pair<ftl::NonNull<std::shared_ptr<int>>, std::shared_ptr<int>>;
+//
+//   Pair dupe_if(ftl::NonNull<std::unique_ptr<int>> non_null_ptr, bool condition) {
+//     // Move the underlying pointer out, so `non_null_ptr` must not be accessed after this point.
+//     auto unique_ptr = std::move(non_null_ptr).take();
+//
+//     auto non_null_shared_ptr = ftl::as_non_null(std::shared_ptr<int>(std::move(unique_ptr)));
+//     auto nullable_shared_ptr = condition ? non_null_shared_ptr.get() : nullptr;
+//
+//     return {std::move(non_null_shared_ptr), std::move(nullable_shared_ptr)};
+//   }
+//
+//   auto ptr = ftl::as_non_null(std::make_unique<int>(42));
+//   const auto [ptr1, ptr2] = dupe_if(std::move(ptr), true);
+//   assert(ptr1.get() == ptr2);
+//
+template <typename Pointer>
+class NonNull final {
+  struct Passkey {};
+
+ public:
+  // Disallow `nullptr` explicitly for clear compilation errors.
+  NonNull() = delete;
+  NonNull(std::nullptr_t) = delete;
+
+  // Copy operations.
+
+  constexpr NonNull(const NonNull&) = default;
+  constexpr NonNull& operator=(const NonNull&) = default;
+
+  constexpr const Pointer& get() const { return pointer_; }
+  constexpr explicit operator const Pointer&() const { return get(); }
+
+  // Move operations. These break the invariant, so care must be taken to avoid subsequent access.
+
+  constexpr NonNull(NonNull&&) = default;
+  constexpr NonNull& operator=(NonNull&&) = default;
+
+  constexpr Pointer take() && { return std::move(pointer_); }
+  constexpr explicit operator Pointer() && { return take(); }
+
+  // Dereferencing.
+  constexpr decltype(auto) operator*() const { return *get(); }
+  constexpr decltype(auto) operator->() const { return get(); }
+
+  // Private constructor for ftl::as_non_null. Excluded from candidate constructors for conversions
+  // through the passkey idiom, for clear compilation errors.
+  template <typename P>
+  constexpr NonNull(Passkey, P&& pointer) : pointer_(std::forward<P>(pointer)) {
+    if (!pointer_) std::abort();
+  }
+
+ private:
+  template <typename P>
+  friend constexpr auto as_non_null(P&&) -> NonNull<std::decay_t<P>>;
+
+  Pointer pointer_;
+};
+
+template <typename P>
+constexpr auto as_non_null(P&& pointer) -> NonNull<std::decay_t<P>> {
+  using Passkey = typename NonNull<std::decay_t<P>>::Passkey;
+  return {Passkey{}, std::forward<P>(pointer)};
+}
+
+template <typename P, typename Q>
+constexpr bool operator==(const NonNull<P>& lhs, const NonNull<Q>& rhs) {
+  return lhs.get() == rhs.get();
+}
+
+template <typename P, typename Q>
+constexpr bool operator!=(const NonNull<P>& lhs, const NonNull<Q>& rhs) {
+  return !operator==(lhs, rhs);
+}
+
+}  // namespace android::ftl
diff --git a/include/ftl/optional.h b/include/ftl/optional.h
index 626507f..a818128 100644
--- a/include/ftl/optional.h
+++ b/include/ftl/optional.h
@@ -95,8 +95,26 @@
     if (has_value()) return std::invoke(std::forward<F>(f), std::move(value()));
     return R();
   }
+
+  // Delete new for this class. Its base doesn't have a virtual destructor, and
+  // if it got deleted via base class pointer, it would cause undefined
+  // behavior. There's not a good reason to allocate this object on the heap
+  // anyway.
+  static void* operator new(size_t) = delete;
+  static void* operator new[](size_t) = delete;
+
 };
 
+template <typename T, typename U>
+constexpr bool operator==(const Optional<T>& lhs, const Optional<U>& rhs) {
+  return static_cast<std::optional<T>>(lhs) == static_cast<std::optional<U>>(rhs);
+}
+
+template <typename T, typename U>
+constexpr bool operator!=(const Optional<T>& lhs, const Optional<U>& rhs) {
+  return !(lhs == rhs);
+}
+
 // Deduction guides.
 template <typename T>
 Optional(T) -> Optional<T>;
diff --git a/include/ftl/shared_mutex.h b/include/ftl/shared_mutex.h
new file mode 100644
index 0000000..146f5ba
--- /dev/null
+++ b/include/ftl/shared_mutex.h
@@ -0,0 +1,47 @@
+/*
+ * 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 <shared_mutex>
+
+namespace android::ftl {
+
+// Wrapper around std::shared_mutex to provide capabilities for thread-safety
+// annotations.
+// TODO(b/257958323): This class is no longer needed once b/135688034 is fixed (currently blocked on
+// b/175635923).
+class [[clang::capability("shared_mutex")]] SharedMutex final {
+ public:
+  [[clang::acquire_capability()]] void lock() {
+    mutex_.lock();
+  }
+  [[clang::release_capability()]] void unlock() {
+    mutex_.unlock();
+  }
+
+  [[clang::acquire_shared_capability()]] void lock_shared() {
+    mutex_.lock_shared();
+  }
+  [[clang::release_shared_capability()]] void unlock_shared() {
+    mutex_.unlock_shared();
+  }
+
+ private:
+  std::shared_mutex mutex_;
+};
+
+}  // namespace android::ftl
diff --git a/include/input/DisplayViewport.h b/include/input/DisplayViewport.h
index 98a18c9..7457496 100644
--- a/include/input/DisplayViewport.h
+++ b/include/input/DisplayViewport.h
@@ -21,6 +21,7 @@
 #include <ftl/string.h>
 #include <gui/constants.h>
 #include <input/Input.h>
+#include <ui/Rotation.h>
 
 #include <cinttypes>
 #include <optional>
@@ -29,13 +30,6 @@
 
 namespace android {
 
-enum {
-    DISPLAY_ORIENTATION_0 = 0,
-    DISPLAY_ORIENTATION_90 = 1,
-    DISPLAY_ORIENTATION_180 = 2,
-    DISPLAY_ORIENTATION_270 = 3
-};
-
 /**
  * Describes the different type of viewports supported by input flinger.
  * Keep in sync with values in InputManagerService.java.
@@ -54,7 +48,7 @@
  */
 struct DisplayViewport {
     int32_t displayId; // -1 if invalid
-    int32_t orientation;
+    ui::Rotation orientation;
     int32_t logicalLeft;
     int32_t logicalTop;
     int32_t logicalRight;
@@ -74,7 +68,7 @@
 
     DisplayViewport()
           : displayId(ADISPLAY_ID_NONE),
-            orientation(DISPLAY_ORIENTATION_0),
+            orientation(ui::ROTATION_0),
             logicalLeft(0),
             logicalTop(0),
             logicalRight(0),
@@ -111,7 +105,7 @@
 
     void setNonDisplayViewport(int32_t width, int32_t height) {
         displayId = ADISPLAY_ID_NONE;
-        orientation = DISPLAY_ORIENTATION_0;
+        orientation = ui::ROTATION_0;
         logicalLeft = 0;
         logicalTop = 0;
         logicalRight = width;
diff --git a/include/input/Input.h b/include/input/Input.h
index 172e5b4..015efdd 100644
--- a/include/input/Input.h
+++ b/include/input/Input.h
@@ -209,6 +209,8 @@
 
 bool isFromSource(uint32_t source, uint32_t test);
 
+bool isStylusToolType(uint32_t toolType);
+
 /*
  * Flags that flow alongside events in the input dispatch system to help with certain
  * policy decisions such as waking from device sleep.
@@ -366,7 +368,7 @@
 
     // Values of axes that are stored in this structure packed in order by axis id
     // for each axis that is present in the structure according to 'bits'.
-    float values[MAX_AXES];
+    std::array<float, MAX_AXES> values;
 
     inline void clear() {
         BitSet64::clear(bits);
@@ -406,7 +408,8 @@
         return !(*this == other);
     }
 
-    void copyFrom(const PointerCoords& other);
+    inline void copyFrom(const PointerCoords& other) { *this = other; }
+    PointerCoords& operator=(const PointerCoords&) = default;
 
 private:
     void tooManyAxes(int axis);
@@ -574,7 +577,7 @@
 
     inline const ui::Transform& getTransform() const { return mTransform; }
 
-    int getSurfaceRotation() const;
+    std::optional<ui::Rotation> getSurfaceRotation() const;
 
     inline float getXPrecision() const { return mXPrecision; }
 
diff --git a/include/input/InputDevice.h b/include/input/InputDevice.h
index 0026e82..e911734 100644
--- a/include/input/InputDevice.h
+++ b/include/input/InputDevice.h
@@ -23,6 +23,7 @@
 #include <unordered_map>
 #include <vector>
 
+#include <android/os/IInputConstants.h>
 #include "android/hardware/input/InputDeviceCountryCode.h"
 
 namespace android {
@@ -57,6 +58,9 @@
     // reuse values that are not associated with an input anymore.
     uint16_t nonce;
 
+    // The bluetooth address of the device, if known.
+    std::optional<std::string> bluetoothAddress;
+
     /**
      * Return InputDeviceIdentifier.name that has been adjusted as follows:
      *     - all characters besides alphanumerics, dash,
@@ -280,6 +284,9 @@
 
     std::vector<InputDeviceLightInfo> getLights();
 
+    inline void setSupportsUsi(bool supportsUsi) { mSupportsUsi = supportsUsi; }
+    inline bool supportsUsi() const { return mSupportsUsi; }
+
 private:
     int32_t mId;
     int32_t mGeneration;
@@ -292,6 +299,8 @@
     uint32_t mSources;
     int32_t mKeyboardType;
     std::shared_ptr<KeyCharacterMap> mKeyCharacterMap;
+    // Whether this device supports the Universal Stylus Initiative (USI) protocol for styluses.
+    bool mSupportsUsi;
 
     bool mHasVibrator;
     bool mHasBattery;
@@ -341,6 +350,8 @@
         const std::string& name, InputDeviceConfigurationFileType type);
 
 enum ReservedInputDeviceId : int32_t {
+    // Device id representing an invalid device
+    INVALID_INPUT_DEVICE_ID = android::os::IInputConstants::INVALID_INPUT_DEVICE_ID,
     // Device id of a special "virtual" keyboard that is always present.
     VIRTUAL_KEYBOARD_ID = -1,
     // Device id of the "built-in" keyboard if there is one.
diff --git a/include/input/PrintTools.h b/include/input/PrintTools.h
index 55f730b..a20544b 100644
--- a/include/input/PrintTools.h
+++ b/include/input/PrintTools.h
@@ -16,6 +16,7 @@
 
 #pragma once
 
+#include <bitset>
 #include <map>
 #include <optional>
 #include <set>
@@ -23,17 +24,26 @@
 
 namespace android {
 
+template <size_t N>
+std::string bitsetToString(const std::bitset<N>& bitset) {
+    return bitset.to_string();
+}
+
 template <typename T>
-std::string constToString(const T& v) {
+inline std::string constToString(const T& v) {
     return std::to_string(v);
 }
 
+inline std::string constToString(const std::string& s) {
+    return s;
+}
+
 /**
  * Convert an optional type to string.
  */
 template <typename T>
-std::string toString(const std::optional<T>& optional,
-                     std::string (*toString)(const T&) = constToString) {
+inline std::string toString(const std::optional<T>& optional,
+                            std::string (*toString)(const T&) = constToString) {
     return optional ? toString(*optional) : "<not set>";
 }
 
diff --git a/include/input/TouchVideoFrame.h b/include/input/TouchVideoFrame.h
index a616a95..1e4f6e7 100644
--- a/include/input/TouchVideoFrame.h
+++ b/include/input/TouchVideoFrame.h
@@ -16,6 +16,8 @@
 
 #pragma once
 
+#include <ui/Rotation.h>
+
 #include <stdint.h>
 #include <sys/time.h>
 #include <vector>
@@ -58,7 +60,7 @@
      * Rotate the video frame.
      * The rotation value is an enum from ui/Rotation.h
      */
-    void rotate(int32_t orientation);
+    void rotate(ui::Rotation orientation);
 
 private:
     uint32_t mHeight;
diff --git a/include/input/VelocityControl.h b/include/input/VelocityControl.h
index f72a1bd..f3c201e 100644
--- a/include/input/VelocityControl.h
+++ b/include/input/VelocityControl.h
@@ -16,10 +16,13 @@
 
 #pragma once
 
+#include <android-base/stringprintf.h>
 #include <input/Input.h>
 #include <input/VelocityTracker.h>
 #include <utils/Timers.h>
 
+using android::base::StringPrintf;
+
 namespace android {
 
 /*
@@ -69,6 +72,12 @@
             scale(scale), lowThreshold(lowThreshold),
             highThreshold(highThreshold), acceleration(acceleration) {
     }
+
+    std::string dump() const {
+        return StringPrintf("scale=%0.3f, lowThreshold=%0.3f, highThreshold=%0.3f, "
+                            "acceleration=%0.3f\n",
+                            scale, lowThreshold, highThreshold, acceleration);
+    }
 };
 
 /*
@@ -78,6 +87,9 @@
 public:
     VelocityControl();
 
+    /* Gets the various parameters. */
+    VelocityControlParameters& getParameters();
+
     /* Sets the various parameters. */
     void setParameters(const VelocityControlParameters& parameters);
 
diff --git a/include/input/VelocityTracker.h b/include/input/VelocityTracker.h
index 294879e..62c3ae1 100644
--- a/include/input/VelocityTracker.h
+++ b/include/input/VelocityTracker.h
@@ -106,6 +106,9 @@
 
     ~VelocityTracker();
 
+    /** Return true if the axis is supported for velocity tracking, false otherwise. */
+    static bool isAxisSupported(int32_t axis);
+
     // Resets the velocity tracker state.
     void clear();
 
diff --git a/include/powermanager/PowerHalLoader.h b/include/powermanager/PowerHalLoader.h
index ed6f6f3..e0384f3 100644
--- a/include/powermanager/PowerHalLoader.h
+++ b/include/powermanager/PowerHalLoader.h
@@ -19,6 +19,8 @@
 
 #include <android-base/thread_annotations.h>
 #include <android/hardware/power/1.1/IPower.h>
+#include <android/hardware/power/1.2/IPower.h>
+#include <android/hardware/power/1.3/IPower.h>
 #include <android/hardware/power/IPower.h>
 
 namespace android {
@@ -32,12 +34,16 @@
     static sp<hardware::power::IPower> loadAidl();
     static sp<hardware::power::V1_0::IPower> loadHidlV1_0();
     static sp<hardware::power::V1_1::IPower> loadHidlV1_1();
+    static sp<hardware::power::V1_2::IPower> loadHidlV1_2();
+    static sp<hardware::power::V1_3::IPower> loadHidlV1_3();
 
 private:
     static std::mutex gHalMutex;
     static sp<hardware::power::IPower> gHalAidl GUARDED_BY(gHalMutex);
     static sp<hardware::power::V1_0::IPower> gHalHidlV1_0 GUARDED_BY(gHalMutex);
     static sp<hardware::power::V1_1::IPower> gHalHidlV1_1 GUARDED_BY(gHalMutex);
+    static sp<hardware::power::V1_2::IPower> gHalHidlV1_2 GUARDED_BY(gHalMutex);
+    static sp<hardware::power::V1_3::IPower> gHalHidlV1_3 GUARDED_BY(gHalMutex);
 
     static sp<hardware::power::V1_0::IPower> loadHidlV1_0Locked()
             EXCLUSIVE_LOCKS_REQUIRED(gHalMutex);
diff --git a/include/powermanager/PowerHalWrapper.h b/include/powermanager/PowerHalWrapper.h
index dfb0ff5..8028aa8 100644
--- a/include/powermanager/PowerHalWrapper.h
+++ b/include/powermanager/PowerHalWrapper.h
@@ -19,6 +19,8 @@
 
 #include <android-base/thread_annotations.h>
 #include <android/hardware/power/1.1/IPower.h>
+#include <android/hardware/power/1.2/IPower.h>
+#include <android/hardware/power/1.3/IPower.h>
 #include <android/hardware/power/Boost.h>
 #include <android/hardware/power/IPower.h>
 #include <android/hardware/power/IPowerHintSession.h>
@@ -142,8 +144,8 @@
 // Wrapper for the HIDL Power HAL v1.0.
 class HidlHalWrapperV1_0 : public HalWrapper {
 public:
-    explicit HidlHalWrapperV1_0(sp<hardware::power::V1_0::IPower> Hal)
-          : mHandleV1_0(std::move(Hal)) {}
+    explicit HidlHalWrapperV1_0(sp<hardware::power::V1_0::IPower> handleV1_0)
+          : mHandleV1_0(std::move(handleV1_0)) {}
     virtual ~HidlHalWrapperV1_0() = default;
 
     virtual HalResult<void> setBoost(hardware::power::Boost boost, int32_t durationMs) override;
@@ -154,10 +156,10 @@
     virtual HalResult<int64_t> getHintSessionPreferredRate() override;
 
 protected:
-    virtual HalResult<void> sendPowerHint(hardware::power::V1_0::PowerHint hintId, uint32_t data);
+    const sp<hardware::power::V1_0::IPower> mHandleV1_0;
+    virtual HalResult<void> sendPowerHint(hardware::power::V1_3::PowerHint hintId, uint32_t data);
 
 private:
-    sp<hardware::power::V1_0::IPower> mHandleV1_0;
     HalResult<void> setInteractive(bool enabled);
     HalResult<void> setFeature(hardware::power::V1_0::Feature feature, bool enabled);
 };
@@ -165,17 +167,40 @@
 // Wrapper for the HIDL Power HAL v1.1.
 class HidlHalWrapperV1_1 : public HidlHalWrapperV1_0 {
 public:
-    HidlHalWrapperV1_1(sp<hardware::power::V1_0::IPower> handleV1_0,
-                       sp<hardware::power::V1_1::IPower> handleV1_1)
-          : HidlHalWrapperV1_0(std::move(handleV1_0)), mHandleV1_1(std::move(handleV1_1)) {}
+    HidlHalWrapperV1_1(sp<hardware::power::V1_1::IPower> handleV1_1)
+          : HidlHalWrapperV1_0(std::move(handleV1_1)) {}
     virtual ~HidlHalWrapperV1_1() = default;
 
 protected:
-    virtual HalResult<void> sendPowerHint(hardware::power::V1_0::PowerHint hintId,
+    virtual HalResult<void> sendPowerHint(hardware::power::V1_3::PowerHint hintId,
                                           uint32_t data) override;
+};
 
-private:
-    sp<hardware::power::V1_1::IPower> mHandleV1_1;
+// Wrapper for the HIDL Power HAL v1.2.
+class HidlHalWrapperV1_2 : public HidlHalWrapperV1_1 {
+public:
+    virtual HalResult<void> setBoost(hardware::power::Boost boost, int32_t durationMs) override;
+    virtual HalResult<void> setMode(hardware::power::Mode mode, bool enabled) override;
+    HidlHalWrapperV1_2(sp<hardware::power::V1_2::IPower> handleV1_2)
+          : HidlHalWrapperV1_1(std::move(handleV1_2)) {}
+    virtual ~HidlHalWrapperV1_2() = default;
+
+protected:
+    virtual HalResult<void> sendPowerHint(hardware::power::V1_3::PowerHint hintId,
+                                          uint32_t data) override;
+};
+
+// Wrapper for the HIDL Power HAL v1.3.
+class HidlHalWrapperV1_3 : public HidlHalWrapperV1_2 {
+public:
+    virtual HalResult<void> setMode(hardware::power::Mode mode, bool enabled) override;
+    HidlHalWrapperV1_3(sp<hardware::power::V1_3::IPower> handleV1_3)
+          : HidlHalWrapperV1_2(std::move(handleV1_3)) {}
+    virtual ~HidlHalWrapperV1_3() = default;
+
+protected:
+    virtual HalResult<void> sendPowerHint(hardware::power::V1_3::PowerHint hintId,
+                                          uint32_t data) override;
 };
 
 // Wrapper for the AIDL Power HAL.
diff --git a/libs/arect/Android.bp b/libs/arect/Android.bp
index 76e3e66..5e539f2 100644
--- a/libs/arect/Android.bp
+++ b/libs/arect/Android.bp
@@ -49,6 +49,9 @@
         "com.android.media",
         "com.android.media.swcodec",
     ],
+    llndk: {
+        llndk_headers: true,
+    },
 }
 
 cc_library_static {
diff --git a/libs/attestation/Android.bp b/libs/attestation/Android.bp
index 2bf15d4..fddecc0 100644
--- a/libs/attestation/Android.bp
+++ b/libs/attestation/Android.bp
@@ -22,6 +22,7 @@
 
 cc_library_static {
     name: "libattestation",
+    host_supported: true,
     cflags: [
         "-Wall",
         "-Wextra",
diff --git a/libs/binder/Android.bp b/libs/binder/Android.bp
index c4bb6d0..f17bb7d 100644
--- a/libs/binder/Android.bp
+++ b/libs/binder/Android.bp
@@ -161,7 +161,7 @@
     ],
 
     header_libs: [
-        "libandroid_runtime_vm_headers",
+        "jni_headers",
     ],
 
     export_header_lib_headers: [
@@ -315,7 +315,7 @@
         },
         recovery: {
             exclude_header_libs: [
-                "libandroid_runtime_vm_headers",
+                "jni_headers",
             ],
         },
     },
@@ -484,9 +484,6 @@
         java: {
             enabled: false,
         },
-        cpp: {
-            gen_trace: false,
-        },
     },
 }
 
@@ -498,6 +495,7 @@
         "libbase",
         "libbinder",
         "libbinder_ndk",
+        "libcutils_sockets",
         "liblog",
         "libutils",
     ],
diff --git a/libs/binder/IPCThreadState.cpp b/libs/binder/IPCThreadState.cpp
index 11c8e5d..7770374 100644
--- a/libs/binder/IPCThreadState.cpp
+++ b/libs/binder/IPCThreadState.cpp
@@ -45,11 +45,11 @@
 
 #define IF_LOG_TRANSACTIONS() if (false)
 #define IF_LOG_COMMANDS() if (false)
-#define LOG_REMOTEREFS(...) 
+#define LOG_REMOTEREFS(...)
 #define IF_LOG_REMOTEREFS() if (false)
 
-#define LOG_THREADPOOL(...) 
-#define LOG_ONEWAY(...) 
+#define LOG_THREADPOOL(...)
+#define LOG_ONEWAY(...)
 
 #else
 
@@ -394,14 +394,92 @@
     // context, so we don't abort
 }
 
+constexpr uint32_t encodeExplicitIdentity(bool hasExplicitIdentity, pid_t callingPid) {
+    uint32_t as_unsigned = static_cast<uint32_t>(callingPid);
+    if (hasExplicitIdentity) {
+        return as_unsigned | (1 << 30);
+    } else {
+        return as_unsigned & ~(1 << 30);
+    }
+}
+
+constexpr int64_t packCallingIdentity(bool hasExplicitIdentity, uid_t callingUid,
+                                      pid_t callingPid) {
+    // Calling PID is a 32-bit signed integer, but doesn't consume the entire 32 bit space.
+    // To future-proof this and because we have extra capacity, we decided to also support -1,
+    // since this constant is used to represent invalid UID in other places of the system.
+    // Thus, we pack hasExplicitIdentity into the 2nd bit from the left.  This allows us to
+    // preserve the (left-most) bit for the sign while also encoding the value of
+    // hasExplicitIdentity.
+    //               32b     |        1b         |         1b            |        30b
+    // token = [ calling uid | calling pid(sign) | has explicit identity | calling pid(rest) ]
+    uint64_t token = (static_cast<uint64_t>(callingUid) << 32) |
+            encodeExplicitIdentity(hasExplicitIdentity, callingPid);
+    return static_cast<int64_t>(token);
+}
+
+constexpr bool unpackHasExplicitIdentity(int64_t token) {
+    return static_cast<int32_t>(token) & (1 << 30);
+}
+
+constexpr uid_t unpackCallingUid(int64_t token) {
+    return static_cast<uid_t>(token >> 32);
+}
+
+constexpr pid_t unpackCallingPid(int64_t token) {
+    int32_t encodedPid = static_cast<int32_t>(token);
+    if (encodedPid & (1 << 31)) {
+        return encodedPid | (1 << 30);
+    } else {
+        return encodedPid & ~(1 << 30);
+    }
+}
+
+static_assert(unpackHasExplicitIdentity(packCallingIdentity(true, 1000, 9999)) == true,
+              "pack true hasExplicit");
+
+static_assert(unpackCallingUid(packCallingIdentity(true, 1000, 9999)) == 1000, "pack true uid");
+
+static_assert(unpackCallingPid(packCallingIdentity(true, 1000, 9999)) == 9999, "pack true pid");
+
+static_assert(unpackHasExplicitIdentity(packCallingIdentity(false, 1000, 9999)) == false,
+              "pack false hasExplicit");
+
+static_assert(unpackCallingUid(packCallingIdentity(false, 1000, 9999)) == 1000, "pack false uid");
+
+static_assert(unpackCallingPid(packCallingIdentity(false, 1000, 9999)) == 9999, "pack false pid");
+
+static_assert(unpackHasExplicitIdentity(packCallingIdentity(true, 1000, -1)) == true,
+              "pack true (negative) hasExplicit");
+
+static_assert(unpackCallingUid(packCallingIdentity(true, 1000, -1)) == 1000,
+              "pack true (negative) uid");
+
+static_assert(unpackCallingPid(packCallingIdentity(true, 1000, -1)) == -1,
+              "pack true (negative) pid");
+
+static_assert(unpackHasExplicitIdentity(packCallingIdentity(false, 1000, -1)) == false,
+              "pack false (negative) hasExplicit");
+
+static_assert(unpackCallingUid(packCallingIdentity(false, 1000, -1)) == 1000,
+              "pack false (negative) uid");
+
+static_assert(unpackCallingPid(packCallingIdentity(false, 1000, -1)) == -1,
+              "pack false (negative) pid");
+
 int64_t IPCThreadState::clearCallingIdentity()
 {
     // ignore mCallingSid for legacy reasons
-    int64_t token = ((int64_t)mCallingUid<<32) | mCallingPid;
+    int64_t token = packCallingIdentity(mHasExplicitIdentity, mCallingUid, mCallingPid);
     clearCaller();
+    mHasExplicitIdentity = true;
     return token;
 }
 
+bool IPCThreadState::hasExplicitIdentity() {
+    return mHasExplicitIdentity;
+}
+
 void IPCThreadState::setStrictModePolicy(int32_t policy)
 {
     mStrictModePolicy = policy;
@@ -474,9 +552,10 @@
 
 void IPCThreadState::restoreCallingIdentity(int64_t token)
 {
-    mCallingUid = (int)(token>>32);
+    mCallingUid = unpackCallingUid(token);
     mCallingSid = nullptr;  // not enough data to restore
-    mCallingPid = (int)token;
+    mCallingPid = unpackCallingPid(token);
+    mHasExplicitIdentity = unpackHasExplicitIdentity(token);
 }
 
 void IPCThreadState::clearCaller()
@@ -889,6 +968,7 @@
         mCallRestriction(mProcess->mCallRestriction) {
     pthread_setspecific(gTLS, this);
     clearCaller();
+    mHasExplicitIdentity = false;
     mIn.setDataCapacity(256);
     mOut.setDataCapacity(256);
 }
@@ -1279,6 +1359,7 @@
             const pid_t origPid = mCallingPid;
             const char* origSid = mCallingSid;
             const uid_t origUid = mCallingUid;
+            const bool origHasExplicitIdentity = mHasExplicitIdentity;
             const int32_t origStrictModePolicy = mStrictModePolicy;
             const int32_t origTransactionBinderFlags = mLastTransactionBinderFlags;
             const int32_t origWorkSource = mWorkSource;
@@ -1292,6 +1373,7 @@
             mCallingPid = tr.sender_pid;
             mCallingSid = reinterpret_cast<const char*>(tr_secctx.secctx);
             mCallingUid = tr.sender_euid;
+            mHasExplicitIdentity = false;
             mLastTransactionBinderFlags = tr.flags;
 
             // ALOGI(">>>> TRANSACT from pid %d sid %s uid %d\n", mCallingPid,
@@ -1367,6 +1449,7 @@
             mCallingPid = origPid;
             mCallingSid = origSid;
             mCallingUid = origUid;
+            mHasExplicitIdentity = origHasExplicitIdentity;
             mStrictModePolicy = origStrictModePolicy;
             mLastTransactionBinderFlags = origTransactionBinderFlags;
             mWorkSource = origWorkSource;
diff --git a/libs/binder/IServiceManager.cpp b/libs/binder/IServiceManager.cpp
index 05db774..a0c4334 100644
--- a/libs/binder/IServiceManager.cpp
+++ b/libs/binder/IServiceManager.cpp
@@ -81,6 +81,7 @@
     bool isDeclared(const String16& name) override;
     Vector<String16> getDeclaredInstances(const String16& interface) override;
     std::optional<String16> updatableViaApex(const String16& name) override;
+    Vector<String16> getUpdatableNames(const String16& apexName) override;
     std::optional<IServiceManager::ConnectionInfo> getConnectionInfo(const String16& name) override;
     class RegistrationWaiter : public android::os::BnServiceCallback {
     public:
@@ -479,6 +480,23 @@
     return declared ? std::optional<String16>(String16(declared.value().c_str())) : std::nullopt;
 }
 
+Vector<String16> ServiceManagerShim::getUpdatableNames(const String16& apexName) {
+    std::vector<std::string> out;
+    if (Status status = mTheRealServiceManager->getUpdatableNames(String8(apexName).c_str(), &out);
+        !status.isOk()) {
+        ALOGW("Failed to getUpdatableNames for %s: %s", String8(apexName).c_str(),
+              status.toString8().c_str());
+        return {};
+    }
+
+    Vector<String16> res;
+    res.setCapacity(out.size());
+    for (const std::string& instance : out) {
+        res.push(String16(instance.c_str()));
+    }
+    return res;
+}
+
 std::optional<IServiceManager::ConnectionInfo> ServiceManagerShim::getConnectionInfo(
         const String16& name) {
     std::optional<os::ConnectionInfo> connectionInfo;
diff --git a/libs/binder/OS.cpp b/libs/binder/OS.cpp
index 77e401f..ce60e33 100644
--- a/libs/binder/OS.cpp
+++ b/libs/binder/OS.cpp
@@ -67,7 +67,7 @@
     return RpcTransportCtxFactoryRaw::make();
 }
 
-int sendMessageOnSocket(
+ssize_t sendMessageOnSocket(
         const RpcTransportFd& socket, iovec* iovs, int niovs,
         const std::vector<std::variant<base::unique_fd, base::borrowed_fd>>* ancillaryFds) {
     if (ancillaryFds != nullptr && !ancillaryFds->empty()) {
@@ -112,7 +112,7 @@
     return TEMP_FAILURE_RETRY(sendmsg(socket.fd.get(), &msg, MSG_NOSIGNAL));
 }
 
-int receiveMessageFromSocket(
+ssize_t receiveMessageFromSocket(
         const RpcTransportFd& socket, iovec* iovs, int niovs,
         std::vector<std::variant<base::unique_fd, base::borrowed_fd>>* ancillaryFds) {
     if (ancillaryFds != nullptr) {
diff --git a/libs/binder/OS.h b/libs/binder/OS.h
index 0d38968..fecae31 100644
--- a/libs/binder/OS.h
+++ b/libs/binder/OS.h
@@ -33,11 +33,11 @@
 
 std::unique_ptr<RpcTransportCtxFactory> makeDefaultRpcTransportCtxFactory();
 
-int sendMessageOnSocket(
+ssize_t sendMessageOnSocket(
         const RpcTransportFd& socket, iovec* iovs, int niovs,
         const std::vector<std::variant<base::unique_fd, base::borrowed_fd>>* ancillaryFds);
 
-int receiveMessageFromSocket(
+ssize_t receiveMessageFromSocket(
         const RpcTransportFd& socket, iovec* iovs, int niovs,
         std::vector<std::variant<base::unique_fd, base::borrowed_fd>>* ancillaryFds);
 
diff --git a/libs/binder/Parcel.cpp b/libs/binder/Parcel.cpp
index 07d0a65..ee081c4 100644
--- a/libs/binder/Parcel.cpp
+++ b/libs/binder/Parcel.cpp
@@ -614,11 +614,14 @@
                 if (status_t status = readInt32(&fdIndex); status != OK) {
                     return status;
                 }
-                const auto& oldFd = otherRpcFields->mFds->at(fdIndex);
+                int oldFd = toRawFd(otherRpcFields->mFds->at(fdIndex));
                 // To match kernel binder behavior, we always dup, even if the
                 // FD was unowned in the source parcel.
-                rpcFields->mFds->emplace_back(
-                        base::unique_fd(fcntl(toRawFd(oldFd), F_DUPFD_CLOEXEC, 0)));
+                int newFd = -1;
+                if (status_t status = dupFileDescriptor(oldFd, &newFd); status != OK) {
+                    ALOGW("Failed to duplicate file descriptor %d: %s", oldFd, strerror(-status));
+                }
+                rpcFields->mFds->emplace_back(base::unique_fd(newFd));
                 // Fixup the index in the data.
                 mDataPos = newDataPos + 4;
                 if (status_t status = writeInt32(rpcFields->mFds->size() - 1); status != OK) {
@@ -966,7 +969,15 @@
     }
 }
 
+void Parcel::setEnforceNoDataAvail(bool enforceNoDataAvail) {
+    mEnforceNoDataAvail = enforceNoDataAvail;
+}
+
 binder::Status Parcel::enforceNoDataAvail() const {
+    if (!mEnforceNoDataAvail) {
+        return binder::Status::ok();
+    }
+
     const auto n = dataAvail();
     if (n == 0) {
         return binder::Status::ok();
@@ -3077,6 +3088,7 @@
     mAllowFds = true;
     mDeallocZero = false;
     mOwner = nullptr;
+    mEnforceNoDataAvail = true;
 }
 
 void Parcel::scanForFds() const {
diff --git a/libs/binder/RpcServer.cpp b/libs/binder/RpcServer.cpp
index 83d0de7..0820cd1 100644
--- a/libs/binder/RpcServer.cpp
+++ b/libs/binder/RpcServer.cpp
@@ -531,33 +531,35 @@
     LOG_RPC_DETAIL("Setting up socket server %s", addr.toString().c_str());
     LOG_ALWAYS_FATAL_IF(hasServer(), "Each RpcServer can only have one server.");
 
-    RpcTransportFd transportFd(unique_fd(TEMP_FAILURE_RETRY(
-            socket(addr.addr()->sa_family, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0))));
-    if (!transportFd.fd.ok()) {
+    unique_fd socket_fd(TEMP_FAILURE_RETRY(
+            socket(addr.addr()->sa_family, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0)));
+    if (!socket_fd.ok()) {
         int savedErrno = errno;
         ALOGE("Could not create socket: %s", strerror(savedErrno));
         return -savedErrno;
     }
-
-    if (0 != TEMP_FAILURE_RETRY(bind(transportFd.fd.get(), addr.addr(), addr.addrSize()))) {
+    if (0 != TEMP_FAILURE_RETRY(bind(socket_fd.get(), addr.addr(), addr.addrSize()))) {
         int savedErrno = errno;
         ALOGE("Could not bind socket at %s: %s", addr.toString().c_str(), strerror(savedErrno));
         return -savedErrno;
     }
 
+    return setupRawSocketServer(std::move(socket_fd));
+}
+
+status_t RpcServer::setupRawSocketServer(unique_fd socket_fd) {
+    LOG_ALWAYS_FATAL_IF(!socket_fd.ok(), "Socket must be setup to listen.");
+
     // Right now, we create all threads at once, making accept4 slow. To avoid hanging the client,
     // the backlog is increased to a large number.
     // TODO(b/189955605): Once we create threads dynamically & lazily, the backlog can be reduced
     //  to 1.
-    if (0 != TEMP_FAILURE_RETRY(listen(transportFd.fd.get(), 50 /*backlog*/))) {
+    if (0 != TEMP_FAILURE_RETRY(listen(socket_fd.get(), 50 /*backlog*/))) {
         int savedErrno = errno;
-        ALOGE("Could not listen socket at %s: %s", addr.toString().c_str(), strerror(savedErrno));
+        ALOGE("Could not listen initialized Unix socket: %s", strerror(savedErrno));
         return -savedErrno;
     }
-
-    LOG_RPC_DETAIL("Successfully setup socket server %s", addr.toString().c_str());
-
-    if (status_t status = setupExternalServer(std::move(transportFd.fd)); status != OK) {
+    if (status_t status = setupExternalServer(std::move(socket_fd)); status != OK) {
         ALOGE("Another thread has set up server while calling setupSocketServer. Race?");
         return status;
     }
diff --git a/libs/binder/RpcSession.cpp b/libs/binder/RpcSession.cpp
index 7d6bcfc..ce6ef2b 100644
--- a/libs/binder/RpcSession.cpp
+++ b/libs/binder/RpcSession.cpp
@@ -46,8 +46,8 @@
 #include "Utils.h"
 
 #if defined(__ANDROID__) && !defined(__ANDROID_RECOVERY__)
-#include <android_runtime/vm.h>
 #include <jni.h>
+extern "C" JavaVM* AndroidRuntimeGetJavaVM();
 #endif
 
 namespace android {
@@ -408,10 +408,11 @@
                             "Unable to detach thread. No JavaVM, but it was present before!");
 
         LOG_RPC_DETAIL("Detaching current thread from JVM");
-        if (vm->DetachCurrentThread() != JNI_OK) {
+        int ret = vm->DetachCurrentThread();
+        if (ret == JNI_OK) {
             mAttached = false;
         } else {
-            ALOGW("Unable to detach current thread from JVM");
+            ALOGW("Unable to detach current thread from JVM (%d)", ret);
         }
     }
 
diff --git a/libs/binder/RpcTransportRaw.cpp b/libs/binder/RpcTransportRaw.cpp
index 1912d14..cd067bf 100644
--- a/libs/binder/RpcTransportRaw.cpp
+++ b/libs/binder/RpcTransportRaw.cpp
@@ -61,7 +61,8 @@
             override {
         bool sentFds = false;
         auto send = [&](iovec* iovs, int niovs) -> ssize_t {
-            int ret = sendMessageOnSocket(mSocket, iovs, niovs, sentFds ? nullptr : ancillaryFds);
+            ssize_t ret =
+                    sendMessageOnSocket(mSocket, iovs, niovs, sentFds ? nullptr : ancillaryFds);
             sentFds |= ret > 0;
             return ret;
         };
diff --git a/libs/binder/aidl/android/os/IServiceManager.aidl b/libs/binder/aidl/android/os/IServiceManager.aidl
index 5880c0a..0fb1615 100644
--- a/libs/binder/aidl/android/os/IServiceManager.aidl
+++ b/libs/binder/aidl/android/os/IServiceManager.aidl
@@ -114,6 +114,12 @@
     @nullable @utf8InCpp String updatableViaApex(@utf8InCpp String name);
 
     /**
+     * Returns all instances which are updatable via the APEX. Instance names are fully qualified
+     * like `pack.age.IFoo/default`.
+     */
+    @utf8InCpp String[] getUpdatableNames(@utf8InCpp String apexName);
+
+    /**
      * If connection info is available for the given instance, returns the ConnectionInfo
      */
     @nullable ConnectionInfo getConnectionInfo(@utf8InCpp String name);
diff --git a/libs/binder/include/binder/IPCThreadState.h b/libs/binder/include/binder/IPCThreadState.h
index c01e92f..65b77c6 100644
--- a/libs/binder/include/binder/IPCThreadState.h
+++ b/libs/binder/include/binder/IPCThreadState.h
@@ -139,6 +139,7 @@
             int64_t             clearCallingIdentity();
             // Restores PID/UID (not SID)
             void                restoreCallingIdentity(int64_t token);
+            bool hasExplicitIdentity();
 
             status_t            setupPolling(int* fd);
             status_t            handlePolledCommands();
@@ -241,6 +242,7 @@
             bool                mPropagateWorkSource;
             bool                mIsLooper;
             bool mIsFlushing;
+            bool mHasExplicitIdentity;
             int32_t             mStrictModePolicy;
             int32_t             mLastTransactionBinderFlags;
             CallRestriction     mCallRestriction;
diff --git a/libs/binder/include/binder/IServiceManager.h b/libs/binder/include/binder/IServiceManager.h
index 413c97f..79e771f 100644
--- a/libs/binder/include/binder/IServiceManager.h
+++ b/libs/binder/include/binder/IServiceManager.h
@@ -115,6 +115,12 @@
     virtual std::optional<String16> updatableViaApex(const String16& name) = 0;
 
     /**
+     * Returns all instances which are updatable via the APEX. Instance names are fully qualified
+     * like `pack.age.IFoo/default`.
+     */
+    virtual Vector<String16> getUpdatableNames(const String16& apexName) = 0;
+
+    /**
      * If this instance has declared remote connection information, returns
      * the ConnectionInfo.
      */
diff --git a/libs/binder/include/binder/Parcel.h b/libs/binder/include/binder/Parcel.h
index 6de6ce8..f730acb 100644
--- a/libs/binder/include/binder/Parcel.h
+++ b/libs/binder/include/binder/Parcel.h
@@ -150,6 +150,9 @@
     // Returns Status(EX_BAD_PARCELABLE) when the Parcel is not consumed.
     binder::Status enforceNoDataAvail() const;
 
+    // This Api is used by fuzzers to skip dataAvail checks.
+    void setEnforceNoDataAvail(bool enforceNoDataAvail);
+
     void                freeData();
 
     size_t              objectsCount() const;
@@ -1329,6 +1332,9 @@
     // data to be overridden with zero when deallocated
     mutable bool        mDeallocZero;
 
+    // Set this to false to skip dataAvail checks.
+    bool mEnforceNoDataAvail;
+
     release_func        mOwner;
 
     size_t mReserved;
diff --git a/libs/binder/include/binder/RpcServer.h b/libs/binder/include/binder/RpcServer.h
index 81ae26a3..4ad0a47 100644
--- a/libs/binder/include/binder/RpcServer.h
+++ b/libs/binder/include/binder/RpcServer.h
@@ -71,6 +71,16 @@
     [[nodiscard]] status_t setupUnixDomainServer(const char* path);
 
     /**
+     * Sets up an RPC server with a raw socket file descriptor.
+     * The socket should be created and bound to a socket address already, e.g.
+     * the socket can be created in init.rc.
+     *
+     * This method is used in the libbinder_rpc_unstable API
+     * RunInitUnixDomainRpcServer().
+     */
+    [[nodiscard]] status_t setupRawSocketServer(base::unique_fd socket_fd);
+
+    /**
      * Creates an RPC server at the current port.
      */
     [[nodiscard]] status_t setupVsockServer(unsigned int port);
diff --git a/libs/binder/include_rpc_unstable/binder_rpc_unstable.hpp b/libs/binder/include_rpc_unstable/binder_rpc_unstable.hpp
index 5baa4d7..f08bde8 100644
--- a/libs/binder/include_rpc_unstable/binder_rpc_unstable.hpp
+++ b/libs/binder/include_rpc_unstable/binder_rpc_unstable.hpp
@@ -17,30 +17,56 @@
 #pragma once
 
 #include <sys/socket.h>
+#include <stdint.h>
 
 extern "C" {
 
 struct AIBinder;
+struct ARpcServer;
 
 // Starts an RPC server on a given port and a given root IBinder object.
-// This function sets up the server and joins before returning.
-bool RunRpcServer(AIBinder* service, unsigned int port);
+// Returns an opaque handle to the running server instance, or null if the server
+// could not be started.
+[[nodiscard]] ARpcServer* ARpcServer_newVsock(AIBinder* service, unsigned int port);
 
-// Starts an RPC server on a given port and a given root IBinder object.
-// This function sets up the server, calls readyCallback with a given param, and
-// then joins before returning.
-bool RunRpcServerCallback(AIBinder* service, unsigned int port, void (*readyCallback)(void* param),
-                          void* param);
+// Starts a Unix domain RPC server with a given init-managed Unix domain `name`
+// and a given root IBinder object.
+// The socket should be created in init.rc with the same `name`.
+// Returns an opaque handle to the running server instance, or null if the server
+// could not be started.
+[[nodiscard]] ARpcServer* ARpcServer_newInitUnixDomain(AIBinder* service, const char* name);
+
+// Runs ARpcServer_join() in a background thread. Immediately returns.
+void ARpcServer_start(ARpcServer* server);
+
+// Joins the thread of a running RpcServer instance. At any given point, there
+// can only be one thread calling ARpcServer_join().
+// If a client needs to actively terminate join, call ARpcServer_shutdown() in
+// a separate thread.
+void ARpcServer_join(ARpcServer* server);
+
+// Shuts down any running ARpcServer_join().
+void ARpcServer_shutdown(ARpcServer* server);
+
+// Frees the ARpcServer handle and drops the reference count on the underlying
+// RpcServer instance. The handle must not be reused afterwards.
+// This automatically calls ARpcServer_shutdown().
+void ARpcServer_free(ARpcServer* server);
 
 // Starts an RPC server on a given port and a given root IBinder factory.
-// RunRpcServerWithFactory acts like RunRpcServerCallback, but instead of
+// RunVsockRpcServerWithFactory acts like RunVsockRpcServerCallback, but instead of
 // assigning single root IBinder object to all connections, factory is called
 // whenever a client connects, making it possible to assign unique IBinder
 // object to each client.
-bool RunRpcServerWithFactory(AIBinder* (*factory)(unsigned int cid, void* context),
-                          void* factoryContext, unsigned int port);
+bool RunVsockRpcServerWithFactory(AIBinder* (*factory)(unsigned int cid, void* context),
+                                  void* factoryContext, unsigned int port);
 
-AIBinder* RpcClient(unsigned int cid, unsigned int port);
+AIBinder* VsockRpcClient(unsigned int cid, unsigned int port);
+
+// Gets the service via the RPC binder with Unix domain socket with the given
+// Unix socket `name`.
+// The final Unix domain socket path name is /dev/socket/`name`.
+AIBinder* UnixDomainRpcClient(const char* name);
 
 // Connect to an RPC server with preconnected file descriptors.
 //
@@ -50,5 +76,4 @@
 // param will be passed to requestFd. Callers can use param to pass contexts to
 // the requestFd function.
 AIBinder* RpcPreconnectedClient(int (*requestFd)(void* param), void* param);
-
 }
diff --git a/libs/binder/libbinder_rpc_unstable.cpp b/libs/binder/libbinder_rpc_unstable.cpp
index a3d42b7..f55c779 100644
--- a/libs/binder/libbinder_rpc_unstable.cpp
+++ b/libs/binder/libbinder_rpc_unstable.cpp
@@ -14,24 +14,47 @@
  * limitations under the License.
  */
 
+#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 <cutils/sockets.h>
 #include <linux/vm_sockets.h>
 
 using android::OK;
 using android::RpcServer;
 using android::RpcSession;
+using android::sp;
 using android::status_t;
 using android::statusToString;
 using android::base::unique_fd;
 
+// Opaque handle for RpcServer.
+struct ARpcServer {};
+
+static sp<RpcServer> toRpcServer(ARpcServer* handle) {
+    auto ref = reinterpret_cast<RpcServer*>(handle);
+    return sp<RpcServer>::fromExisting(ref);
+}
+
+static ARpcServer* createRpcServerHandle(sp<RpcServer>& server) {
+    auto ref = server.get();
+    ref->incStrong(ref);
+    return reinterpret_cast<ARpcServer*>(ref);
+}
+
+static void freeRpcServerHandle(ARpcServer* handle) {
+    auto ref = reinterpret_cast<RpcServer*>(handle);
+    ref->decStrong(ref);
+}
+
 extern "C" {
 
-bool RunRpcServerWithFactory(AIBinder* (*factory)(unsigned int cid, void* context),
-                             void* factoryContext, unsigned int port) {
+bool RunVsockRpcServerWithFactory(AIBinder* (*factory)(unsigned int cid, void* context),
+                                  void* factoryContext, unsigned int port) {
     auto server = RpcServer::make();
     if (status_t status = server->setupVsockServer(port); status != OK) {
         LOG(ERROR) << "Failed to set up vsock server with port " << port
@@ -52,29 +75,50 @@
     return true;
 }
 
-bool RunRpcServerCallback(AIBinder* service, unsigned int port, void (*readyCallback)(void* param),
-                          void* param) {
+ARpcServer* ARpcServer_newVsock(AIBinder* service, unsigned int port) {
     auto server = RpcServer::make();
     if (status_t status = server->setupVsockServer(port); status != OK) {
         LOG(ERROR) << "Failed to set up vsock server with port " << port
                    << " error: " << statusToString(status).c_str();
-        return false;
+        return nullptr;
     }
     server->setRootObject(AIBinder_toPlatformBinder(service));
-
-    if (readyCallback) readyCallback(param);
-    server->join();
-
-    // Shutdown any open sessions since server failed.
-    (void)server->shutdown();
-    return true;
+    return createRpcServerHandle(server);
 }
 
-bool RunRpcServer(AIBinder* service, unsigned int port) {
-    return RunRpcServerCallback(service, port, nullptr, nullptr);
+ARpcServer* ARpcServer_newInitUnixDomain(AIBinder* service, const char* name) {
+    auto server = RpcServer::make();
+    auto fd = unique_fd(android_get_control_socket(name));
+    if (!fd.ok()) {
+        LOG(ERROR) << "Failed to get fd for the socket:" << name;
+        return nullptr;
+    }
+    if (status_t status = server->setupRawSocketServer(std::move(fd)); status != OK) {
+        LOG(ERROR) << "Failed to set up Unix Domain RPC server with name " << name
+                   << " error: " << statusToString(status).c_str();
+        return nullptr;
+    }
+    server->setRootObject(AIBinder_toPlatformBinder(service));
+    return createRpcServerHandle(server);
 }
 
-AIBinder* RpcClient(unsigned int cid, unsigned int port) {
+void ARpcServer_start(ARpcServer* handle) {
+    toRpcServer(handle)->start();
+}
+
+void ARpcServer_join(ARpcServer* handle) {
+    toRpcServer(handle)->join();
+}
+
+void ARpcServer_shutdown(ARpcServer* handle) {
+    toRpcServer(handle)->shutdown();
+}
+
+void ARpcServer_free(ARpcServer* handle) {
+    freeRpcServerHandle(handle);
+}
+
+AIBinder* VsockRpcClient(unsigned int cid, unsigned int port) {
     auto session = RpcSession::make();
     if (status_t status = session->setupVsockClient(cid, port); status != OK) {
         LOG(ERROR) << "Failed to set up vsock client with CID " << cid << " and port " << port
@@ -84,6 +128,18 @@
     return AIBinder_fromPlatformBinder(session->getRootObject());
 }
 
+AIBinder* UnixDomainRpcClient(const char* name) {
+    std::string pathname(name);
+    pathname = ANDROID_SOCKET_DIR "/" + pathname;
+    auto session = RpcSession::make();
+    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();
+        return nullptr;
+    }
+    return AIBinder_fromPlatformBinder(session->getRootObject());
+}
+
 AIBinder* RpcPreconnectedClient(int (*requestFd)(void* param), void* param) {
     auto session = RpcSession::make();
     auto request = [=] { return unique_fd{requestFd(param)}; };
diff --git a/libs/binder/libbinder_rpc_unstable.map.txt b/libs/binder/libbinder_rpc_unstable.map.txt
index e856569..1bc2416 100644
--- a/libs/binder/libbinder_rpc_unstable.map.txt
+++ b/libs/binder/libbinder_rpc_unstable.map.txt
@@ -1,8 +1,13 @@
 LIBBINDER_RPC_UNSTABLE_SHIM { # platform-only
   global:
-    RunRpcServer;
-    RunRpcServerCallback;
-    RpcClient;
+    ARpcServer_free;
+    ARpcServer_join;
+    ARpcServer_newInitUnixDomain;
+    ARpcServer_newVsock;
+    ARpcServer_shutdown;
+    ARpcServer_start;
+    VsockRpcClient;
+    UnixDomainRpcClient;
     RpcPreconnectedClient;
   local:
     *;
diff --git a/libs/binder/ndk/include_cpp/android/binder_auto_utils.h b/libs/binder/ndk/include_cpp/android/binder_auto_utils.h
index fccc0af..d6937c2 100644
--- a/libs/binder/ndk/include_cpp/android/binder_auto_utils.h
+++ b/libs/binder/ndk/include_cpp/android/binder_auto_utils.h
@@ -30,11 +30,11 @@
 #include <android/binder_internal_logging.h>
 #include <android/binder_parcel.h>
 #include <android/binder_status.h>
-
 #include <assert.h>
-
 #include <unistd.h>
+
 #include <cstddef>
+#include <iostream>
 #include <string>
 
 namespace ndk {
@@ -270,14 +270,19 @@
     std::string getDescription() const {
 #ifdef __ANDROID_UNAVAILABLE_SYMBOLS_ARE_WEAK__
         if (__builtin_available(android 30, *)) {
-#else
-        if (__ANDROID_API__ >= 30) {
 #endif
+
+#if defined(__ANDROID_UNAVAILABLE_SYMBOLS_ARE_WEAK__) || __ANDROID_API__ >= 30
             const char* cStr = AStatus_getDescription(get());
             std::string ret = cStr;
             AStatus_deleteDescription(cStr);
             return ret;
+#endif
+
+#ifdef __ANDROID_UNAVAILABLE_SYMBOLS_ARE_WEAK__
         }
+#endif
+
         binder_exception_t exception = getExceptionCode();
         std::string desc = std::to_string(exception);
         if (exception == EX_SERVICE_SPECIFIC) {
@@ -315,6 +320,11 @@
     }
 };
 
+static inline std::ostream& operator<<(std::ostream& os, const ScopedAStatus& status) {
+    return os << status.getDescription();
+    return os;
+}
+
 /**
  * Convenience wrapper. See AIBinder_DeathRecipient.
  */
diff --git a/libs/binder/ndk/include_cpp/android/binder_interface_utils.h b/libs/binder/ndk/include_cpp/android/binder_interface_utils.h
index 78bcb43..9949de2 100644
--- a/libs/binder/ndk/include_cpp/android/binder_interface_utils.h
+++ b/libs/binder/ndk/include_cpp/android/binder_interface_utils.h
@@ -196,6 +196,10 @@
 
     bool isRemote() override final { return false; }
 
+    static std::string makeServiceName(std::string_view instance) {
+        return INTERFACE::descriptor + ("/" + std::string(instance));
+    }
+
    protected:
     /**
      * This function should only be called by asBinder. Otherwise, there is a possibility of
@@ -291,7 +295,10 @@
 binder_status_t ICInterface::ICInterfaceData::onDump(AIBinder* binder, int fd, const char** args,
                                                      uint32_t numArgs) {
     std::shared_ptr<ICInterface> interface = getInterface(binder);
-    return interface->dump(fd, args, numArgs);
+    if (interface != nullptr) {
+        return interface->dump(fd, args, numArgs);
+    }
+    return STATUS_DEAD_OBJECT;
 }
 
 #ifdef HAS_BINDER_SHELL_COMMAND
@@ -299,7 +306,10 @@
                                                                  int err, const char** argv,
                                                                  uint32_t argc) {
     std::shared_ptr<ICInterface> interface = getInterface(binder);
-    return interface->handleShellCommand(in, out, err, argv, argc);
+    if (interface != nullptr) {
+        return interface->handleShellCommand(in, out, err, argv, argc);
+    }
+    return STATUS_DEAD_OBJECT;
 }
 #endif
 
diff --git a/libs/binder/ndk/include_cpp/android/binder_parcelable_utils.h b/libs/binder/ndk/include_cpp/android/binder_parcelable_utils.h
index c1f2620..caee471 100644
--- a/libs/binder/ndk/include_cpp/android/binder_parcelable_utils.h
+++ b/libs/binder/ndk/include_cpp/android/binder_parcelable_utils.h
@@ -41,68 +41,34 @@
         if (_status != STATUS_OK) return _status; \
     } while (false)
 
+// AParcelableHolder has been introduced in 31.
+#if __ANDROID_API__ >= 31
 class AParcelableHolder {
    public:
     AParcelableHolder() = delete;
     explicit AParcelableHolder(parcelable_stability_t stability)
         : mParcel(AParcel_create()), mStability(stability) {}
 
-#if __ANDROID_API__ >= 31
     AParcelableHolder(const AParcelableHolder& other)
         : mParcel(AParcel_create()), mStability(other.mStability) {
-        // AParcelableHolder has been introduced in 31.
-#ifdef __ANDROID_UNAVAILABLE_SYMBOLS_ARE_WEAK__
-        if (__builtin_available(android 31, *)) {
-#else
-        if (__ANDROID_API__ >= 31) {
-#endif
-            AParcel_appendFrom(other.mParcel.get(), this->mParcel.get(), 0,
-                               AParcel_getDataSize(other.mParcel.get()));
-        } else {
-            syslog(LOG_ERR,
-                   "sdk_version not compatible, AParcelableHolder need sdk_version >= 31!");
-        }
+        AParcel_appendFrom(other.mParcel.get(), this->mParcel.get(), 0,
+                           AParcel_getDataSize(other.mParcel.get()));
     }
-#endif
 
     AParcelableHolder(AParcelableHolder&& other) = default;
     virtual ~AParcelableHolder() = default;
 
     binder_status_t writeToParcel(AParcel* parcel) const {
         RETURN_ON_FAILURE(AParcel_writeInt32(parcel, static_cast<int32_t>(this->mStability)));
-#ifdef __ANDROID_UNAVAILABLE_SYMBOLS_ARE_WEAK__
-        if (__builtin_available(android 31, *)) {
-#else
-        if (__ANDROID_API__ >= 31) {
-#endif
-            int32_t size = AParcel_getDataSize(this->mParcel.get());
-            RETURN_ON_FAILURE(AParcel_writeInt32(parcel, size));
-        } else {
-            return STATUS_INVALID_OPERATION;
-        }
-#ifdef __ANDROID_UNAVAILABLE_SYMBOLS_ARE_WEAK__
-        if (__builtin_available(android 31, *)) {
-#else
-        if (__ANDROID_API__ >= 31) {
-#endif
-            int32_t size = AParcel_getDataSize(this->mParcel.get());
-            RETURN_ON_FAILURE(AParcel_appendFrom(this->mParcel.get(), parcel, 0, size));
-        } else {
-            return STATUS_INVALID_OPERATION;
-        }
+        int32_t size = AParcel_getDataSize(this->mParcel.get());
+        RETURN_ON_FAILURE(AParcel_writeInt32(parcel, size));
+        size = AParcel_getDataSize(this->mParcel.get());
+        RETURN_ON_FAILURE(AParcel_appendFrom(this->mParcel.get(), parcel, 0, size));
         return STATUS_OK;
     }
 
     binder_status_t readFromParcel(const AParcel* parcel) {
-#ifdef __ANDROID_UNAVAILABLE_SYMBOLS_ARE_WEAK__
-        if (__builtin_available(android 31, *)) {
-#else
-        if (__ANDROID_API__ >= 31) {
-#endif
-            AParcel_reset(mParcel.get());
-        } else {
-            return STATUS_INVALID_OPERATION;
-        }
+        AParcel_reset(mParcel.get());
 
         parcelable_stability_t wireStability;
         RETURN_ON_FAILURE(AParcel_readInt32(parcel, &wireStability));
@@ -123,15 +89,7 @@
             return STATUS_BAD_VALUE;
         }
 
-#ifdef __ANDROID_UNAVAILABLE_SYMBOLS_ARE_WEAK__
-        if (__builtin_available(android 31, *)) {
-#else
-        if (__ANDROID_API__ >= 31) {
-#endif
-            status = AParcel_appendFrom(parcel, mParcel.get(), dataStartPos, dataSize);
-        } else {
-            status = STATUS_INVALID_OPERATION;
-        }
+        status = AParcel_appendFrom(parcel, mParcel.get(), dataStartPos, dataSize);
         if (status != STATUS_OK) {
             return status;
         }
@@ -143,15 +101,7 @@
         if (this->mStability > T::_aidl_stability) {
             return STATUS_BAD_VALUE;
         }
-#ifdef __ANDROID_UNAVAILABLE_SYMBOLS_ARE_WEAK__
-        if (__builtin_available(android 31, *)) {
-#else
-        if (__ANDROID_API__ >= 31) {
-#endif
-            AParcel_reset(mParcel.get());
-        } else {
-            return STATUS_INVALID_OPERATION;
-        }
+        AParcel_reset(mParcel.get());
         AParcel_writeString(mParcel.get(), T::descriptor, strlen(T::descriptor));
         p.writeToParcel(mParcel.get());
         return STATUS_OK;
@@ -161,17 +111,9 @@
     binder_status_t getParcelable(std::optional<T>* ret) const {
         const std::string parcelableDesc(T::descriptor);
         AParcel_setDataPosition(mParcel.get(), 0);
-#ifdef __ANDROID_UNAVAILABLE_SYMBOLS_ARE_WEAK__
-        if (__builtin_available(android 31, *)) {
-#else
-        if (__ANDROID_API__ >= 31) {
-#endif
-            if (AParcel_getDataSize(mParcel.get()) == 0) {
-                *ret = std::nullopt;
-                return STATUS_OK;
-            }
-        } else {
-            return STATUS_INVALID_OPERATION;
+        if (AParcel_getDataSize(mParcel.get()) == 0) {
+            *ret = std::nullopt;
+            return STATUS_OK;
         }
         std::string parcelableDescInParcel;
         binder_status_t status = AParcel_readString(mParcel.get(), &parcelableDescInParcel);
@@ -188,18 +130,7 @@
         return STATUS_OK;
     }
 
-    void reset() {
-#ifdef __ANDROID_UNAVAILABLE_SYMBOLS_ARE_WEAK__
-        if (__builtin_available(android 31, *)) {
-#else
-        if (__ANDROID_API__ >= 31) {
-#endif
-            AParcel_reset(mParcel.get());
-        } else {
-            syslog(LOG_ERR,
-                   "sdk_version not compatible, AParcelableHolder need sdk_version >= 31!");
-        }
-    }
+    void reset() { AParcel_reset(mParcel.get()); }
 
     inline bool operator!=(const AParcelableHolder& rhs) const { return this != &rhs; }
     inline bool operator<(const AParcelableHolder& rhs) const { return this < &rhs; }
@@ -207,34 +138,23 @@
     inline bool operator==(const AParcelableHolder& rhs) const { return this == &rhs; }
     inline bool operator>(const AParcelableHolder& rhs) const { return this > &rhs; }
     inline bool operator>=(const AParcelableHolder& rhs) const { return this >= &rhs; }
-#if __ANDROID_API__ >= 31
     inline AParcelableHolder& operator=(const AParcelableHolder& rhs) {
-        // AParcelableHolder has been introduced in 31.
-#ifdef __ANDROID_UNAVAILABLE_SYMBOLS_ARE_WEAK__
-        if (__builtin_available(android 31, *)) {
-#else
-        if (__ANDROID_API__ >= 31) {
-#endif
-            this->reset();
-            if (this->mStability != rhs.mStability) {
-                syslog(LOG_ERR, "AParcelableHolder stability mismatch: this %d rhs %d!",
-                       this->mStability, rhs.mStability);
-                abort();
-            }
-            AParcel_appendFrom(rhs.mParcel.get(), this->mParcel.get(), 0,
-                               AParcel_getDataSize(rhs.mParcel.get()));
-        } else {
-            syslog(LOG_ERR,
-                   "sdk_version not compatible, AParcelableHolder need sdk_version >= 31!");
+        this->reset();
+        if (this->mStability != rhs.mStability) {
+            syslog(LOG_ERR, "AParcelableHolder stability mismatch: this %d rhs %d!",
+                   this->mStability, rhs.mStability);
+            abort();
         }
+        AParcel_appendFrom(rhs.mParcel.get(), this->mParcel.get(), 0,
+                           AParcel_getDataSize(rhs.mParcel.get()));
         return *this;
     }
-#endif
 
    private:
     mutable ndk::ScopedAParcel mParcel;
     parcelable_stability_t mStability;
 };
+#endif  // __ANDROID_API__ >= 31
 
 #undef RETURN_ON_FAILURE
 }  // namespace ndk
diff --git a/libs/binder/ndk/include_cpp/android/binder_to_string.h b/libs/binder/ndk/include_cpp/android/binder_to_string.h
index d7840ec..2a00736 100644
--- a/libs/binder/ndk/include_cpp/android/binder_to_string.h
+++ b/libs/binder/ndk/include_cpp/android/binder_to_string.h
@@ -136,8 +136,10 @@
     template <typename _U>
     static std::enable_if_t<
 #ifdef HAS_NDK_INTERFACE
-            std::is_base_of_v<::ndk::ICInterface, _U> ||
-                    std::is_same_v<::ndk::AParcelableHolder, _U>
+            std::is_base_of_v<::ndk::ICInterface, _U>
+#if __ANDROID_API__ >= 31
+                    || std::is_same_v<::ndk::AParcelableHolder, _U>
+#endif
 #else
             std::is_base_of_v<IInterface, _U> || std::is_same_v<IBinder, _U> ||
                     std::is_same_v<os::ParcelFileDescriptor, _U> ||
@@ -158,7 +160,7 @@
 template <typename _T>
 std::string ToString(const _T& t) {
     if constexpr (details::ToEmptyString<_T>::value) {
-        return "";
+        return "<unimplemented>";
     } else if constexpr (std::is_same_v<bool, _T>) {
         return t ? "true" : "false";
     } else if constexpr (std::is_same_v<char16_t, _T>) {
@@ -174,9 +176,11 @@
         return t;
 #ifdef HAS_NDK_INTERFACE
     } else if constexpr (std::is_same_v<::ndk::SpAIBinder, _T>) {
-        return (t.get() == nullptr) ? "(null)" : "";
+        std::stringstream ss;
+        ss << "binder:" << std::hex << t.get();
+        return ss.str();
     } else if constexpr (std::is_same_v<::ndk::ScopedFileDescriptor, _T>) {
-        return (t.get() == -1) ? "(null)" : "";
+        return "fd:" + std::to_string(t.get());
 #endif
 #ifdef HAS_STRING16
     } else if constexpr (std::is_same_v<String16, _T>) {
diff --git a/libs/binder/ndk/include_platform/android/binder_manager.h b/libs/binder/ndk/include_platform/android/binder_manager.h
index dfa8ea2..ad4188f 100644
--- a/libs/binder/ndk/include_platform/android/binder_manager.h
+++ b/libs/binder/ndk/include_platform/android/binder_manager.h
@@ -68,6 +68,7 @@
  *
  * \param instance identifier of the service used to lookup the service.
  */
+[[deprecated("this polls 5s, use AServiceManager_waitForService or AServiceManager_checkService")]]
 __attribute__((warn_unused_result)) AIBinder* AServiceManager_getService(const char* instance)
         __INTRODUCED_IN(29);
 
@@ -108,6 +109,67 @@
         __INTRODUCED_IN(31);
 
 /**
+ * Function to call when a service is registered. The instance is passed as well as
+ * ownership of the binder named 'registered'.
+ *
+ * WARNING: a lock is held when this method is called in order to prevent races with
+ * AServiceManager_NotificationRegistration_delete. Do not make synchronous binder calls when
+ * implementing this method to avoid deadlocks.
+ *
+ * \param instance instance name of service registered
+ * \param registered ownership-passed instance of service registered
+ * \param cookie data passed during registration for notifications
+ */
+typedef void (*AServiceManager_onRegister)(const char* instance, AIBinder* registered,
+                                           void* cookie);
+
+/**
+ * Represents a registration to servicemanager which can be cleared anytime.
+ */
+struct AServiceManager_NotificationRegistration;
+
+/**
+ * Get notifications when a service is registered. If the service is already registered,
+ * you will immediately get a notification.
+ *
+ * WARNING: it is strongly recommended to use AServiceManager_waitForService API instead.
+ * That API will wait synchronously, which is what you usually want in cases, including
+ * using some feature or during boot up. There is a history of bugs where waiting for
+ * notifications like this races with service startup. Also, when this API is used, a service
+ * bug will result in silent failure (rather than a debuggable deadlock). Furthermore, there
+ * is a history of this API being used to know when a service is up as a proxy for whethre
+ * that service should be started. This should only be used if you are intending to get
+ * ahold of the service as a client. For lazy services, whether a service is registered
+ * should not be used as a proxy for when it should be registered, which is only known
+ * by the real client.
+ *
+ * WARNING: if you use this API, you must also ensure that you check missing services are
+ * started and crash otherwise. If service failures are ignored, the system rots.
+ *
+ * \param instance name of service to wait for notifications about
+ * \param onRegister callback for when service is registered
+ * \param cookie data associated with this callback
+ *
+ * \return the token for this registration. Deleting this token will unregister.
+ */
+__attribute__((warn_unused_result)) AServiceManager_NotificationRegistration*
+AServiceManager_registerForServiceNotifications(const char* instance,
+                                                AServiceManager_onRegister onRegister, void* cookie)
+        __INTRODUCED_IN(34);
+
+/**
+ * Unregister for notifications and delete the object.
+ *
+ * After this method is called, the callback is guaranteed to no longer be invoked. This will block
+ * until any in-progress onRegister callbacks have completed. It is therefore safe to immediately
+ * destroy the void* cookie that was registered when this method returns.
+ *
+ * \param notification object to dismiss
+ */
+void AServiceManager_NotificationRegistration_delete(
+        AServiceManager_NotificationRegistration* notification) __INTRODUCED_IN(34);
+
+/**
  * Check if a service is declared (e.g. VINTF manifest).
  *
  * \param instance identifier of the service.
@@ -143,6 +205,17 @@
 bool AServiceManager_isUpdatableViaApex(const char* instance) __INTRODUCED_IN(31);
 
 /**
+ * Returns the APEX name if a service is declared as updatable via an APEX module.
+ *
+ * \param instance identifier of the service
+ * \param context to pass to callback
+ * \param callback taking the APEX name (e.g. 'com.android.foo') and context
+ */
+void AServiceManager_getUpdatableApexName(const char* instance, void* context,
+                                          void (*callback)(const char*, void*))
+        __INTRODUCED_IN(__ANDROID_API_U__);
+
+/**
  * Prevent lazy services without client from shutting down their process
  *
  * This should only be used if it is every eventually set to false. If a
diff --git a/libs/binder/ndk/libbinder_ndk.map.txt b/libs/binder/ndk/libbinder_ndk.map.txt
index 259a736..5c7005c 100644
--- a/libs/binder/ndk/libbinder_ndk.map.txt
+++ b/libs/binder/ndk/libbinder_ndk.map.txt
@@ -152,6 +152,13 @@
     AParcel_unmarshal;
 };
 
+LIBBINDER_NDK34 { # introduced=UpsideDownCake
+  global:
+    AServiceManager_getUpdatableApexName; # systemapi
+    AServiceManager_registerForServiceNotifications; # systemapi llndk
+    AServiceManager_NotificationRegistration_delete; # systemapi llndk
+};
+
 LIBBINDER_NDK_PLATFORM {
   global:
     AParcel_getAllowFds;
diff --git a/libs/binder/ndk/parcel.cpp b/libs/binder/ndk/parcel.cpp
index c320e8d..8693022 100644
--- a/libs/binder/ndk/parcel.cpp
+++ b/libs/binder/ndk/parcel.cpp
@@ -129,7 +129,13 @@
     }
 
     T* array;
-    if (!allocator(arrayData, length, &array)) return STATUS_NO_MEMORY;
+    if (!allocator(arrayData, length, &array)) {
+        if (length < 0) {
+            return STATUS_UNEXPECTED_NULL;
+        } else {
+            return STATUS_NO_MEMORY;
+        }
+    }
 
     if (length <= 0) return STATUS_OK;
     if (array == nullptr) return STATUS_NO_MEMORY;
@@ -157,7 +163,13 @@
     }
 
     char16_t* array;
-    if (!allocator(arrayData, length, &array)) return STATUS_NO_MEMORY;
+    if (!allocator(arrayData, length, &array)) {
+        if (length < 0) {
+            return STATUS_UNEXPECTED_NULL;
+        } else {
+            return STATUS_NO_MEMORY;
+        }
+    }
 
     if (length <= 0) return STATUS_OK;
     if (array == nullptr) return STATUS_NO_MEMORY;
@@ -204,7 +216,13 @@
         return status;
     }
 
-    if (!allocator(arrayData, length)) return STATUS_NO_MEMORY;
+    if (!allocator(arrayData, length)) {
+        if (length < 0) {
+            return STATUS_UNEXPECTED_NULL;
+        } else {
+            return STATUS_NO_MEMORY;
+        }
+    }
 
     if (length <= 0) return STATUS_OK;
 
diff --git a/libs/binder/ndk/service_manager.cpp b/libs/binder/ndk/service_manager.cpp
index 7649a26..e107c83 100644
--- a/libs/binder/ndk/service_manager.cpp
+++ b/libs/binder/ndk/service_manager.cpp
@@ -28,6 +28,7 @@
 using ::android::IServiceManager;
 using ::android::sp;
 using ::android::status_t;
+using ::android::statusToString;
 using ::android::String16;
 using ::android::String8;
 
@@ -86,6 +87,67 @@
     AIBinder_incStrong(ret.get());
     return ret.get();
 }
+typedef void (*AServiceManager_onRegister)(const char* instance, AIBinder* registered,
+                                           void* cookie);
+
+struct AServiceManager_NotificationRegistration
+    : public IServiceManager::LocalRegistrationCallback {
+    std::mutex m;
+    const char* instance = nullptr;
+    void* cookie = nullptr;
+    AServiceManager_onRegister onRegister = nullptr;
+
+    virtual void onServiceRegistration(const String16& smInstance, const sp<IBinder>& binder) {
+        std::lock_guard<std::mutex> l(m);
+        if (onRegister == nullptr) return;
+
+        CHECK_EQ(String8(smInstance), instance);
+
+        sp<AIBinder> ret = ABpBinder::lookupOrCreateFromBinder(binder);
+        AIBinder_incStrong(ret.get());
+
+        onRegister(instance, ret.get(), cookie);
+    }
+
+    void clear() {
+        std::lock_guard<std::mutex> l(m);
+        instance = nullptr;
+        cookie = nullptr;
+        onRegister = nullptr;
+    }
+};
+
+__attribute__((warn_unused_result)) AServiceManager_NotificationRegistration*
+AServiceManager_registerForServiceNotifications(const char* instance,
+                                                AServiceManager_onRegister onRegister,
+                                                void* cookie) {
+    CHECK_NE(instance, nullptr);
+    CHECK_NE(onRegister, nullptr) << instance;
+    // cookie can be nullptr
+
+    auto cb = sp<AServiceManager_NotificationRegistration>::make();
+    cb->instance = instance;
+    cb->onRegister = onRegister;
+    cb->cookie = cookie;
+
+    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);
+        return nullptr;
+    }
+
+    cb->incStrong(nullptr);
+    return cb.get();
+}
+
+void AServiceManager_NotificationRegistration_delete(
+        AServiceManager_NotificationRegistration* notification) {
+    CHECK_NE(notification, nullptr);
+    notification->clear();
+    notification->decStrong(nullptr);
+}
+
 bool AServiceManager_isDeclared(const char* instance) {
     if (instance == nullptr) {
         return false;
@@ -113,6 +175,18 @@
     sp<IServiceManager> sm = defaultServiceManager();
     return sm->updatableViaApex(String16(instance)) != std::nullopt;
 }
+void AServiceManager_getUpdatableApexName(const char* instance, void* context,
+                                          void (*callback)(const char*, void*)) {
+    CHECK_NE(instance, nullptr);
+    // context may be nullptr
+    CHECK_NE(callback, nullptr);
+
+    sp<IServiceManager> sm = defaultServiceManager();
+    std::optional<String16> updatableViaApex = sm->updatableViaApex(String16(instance));
+    if (updatableViaApex.has_value()) {
+        callback(String8(updatableViaApex.value()).c_str(), context);
+    }
+}
 void AServiceManager_forceLazyServicesPersist(bool persist) {
     auto serviceRegistrar = android::binder::LazyServiceRegistrar::getInstance();
     serviceRegistrar.forcePersist(persist);
diff --git a/libs/binder/ndk/tests/binderVendorDoubleLoadTest.cpp b/libs/binder/ndk/tests/binderVendorDoubleLoadTest.cpp
index f3cd218..43b2cb8 100644
--- a/libs/binder/ndk/tests/binderVendorDoubleLoadTest.cpp
+++ b/libs/binder/ndk/tests/binderVendorDoubleLoadTest.cpp
@@ -106,7 +106,7 @@
         std::string outString;
         ScopedAStatus status = server->RepeatString("foo", &outString);
         EXPECT_EQ(STATUS_OK, AStatus_getExceptionCode(status.get()))
-                << serviceName << " " << status.getDescription();
+                << serviceName << " " << status;
         EXPECT_EQ("foo", outString) << serviceName;
     }
 }
diff --git a/libs/binder/ndk/tests/libbinder_ndk_unit_test.cpp b/libs/binder/ndk/tests/libbinder_ndk_unit_test.cpp
index 01b9472..9d5ef68 100644
--- a/libs/binder/ndk/tests/libbinder_ndk_unit_test.cpp
+++ b/libs/binder/ndk/tests/libbinder_ndk_unit_test.cpp
@@ -33,13 +33,15 @@
 #include <binder/IResultReceiver.h>
 #include <binder/IServiceManager.h>
 #include <binder/IShellCallback.h>
-
 #include <sys/prctl.h>
+
 #include <chrono>
 #include <condition_variable>
 #include <iostream>
 #include <mutex>
+#include <optional>
 #include <thread>
+
 #include "android/binder_ibinder.h"
 
 using namespace android;
@@ -252,6 +254,47 @@
     AIBinder_decStrong(binder);
 }
 
+struct ServiceData {
+    std::string instance;
+    ndk::SpAIBinder binder;
+
+    static void fillOnRegister(const char* instance, AIBinder* binder, void* cookie) {
+        ServiceData* d = reinterpret_cast<ServiceData*>(cookie);
+        d->instance = instance;
+        d->binder = ndk::SpAIBinder(binder);
+    }
+};
+
+TEST(NdkBinder, RegisterForServiceNotificationsNonExisting) {
+    ServiceData data;
+    auto* notif = AServiceManager_registerForServiceNotifications(
+            "DOES_NOT_EXIST", ServiceData::fillOnRegister, (void*)&data);
+    ASSERT_NE(notif, nullptr);
+
+    sleep(1);  // give us a chance to fail
+    AServiceManager_NotificationRegistration_delete(notif);
+
+    // checking after deleting to avoid needing a mutex over the data - otherwise
+    // in an environment w/ multiple threads, you would need to guard access
+    EXPECT_EQ(data.instance, "");
+    EXPECT_EQ(data.binder, nullptr);
+}
+
+TEST(NdkBinder, RegisterForServiceNotificationsExisting) {
+    ServiceData data;
+    auto* notif = AServiceManager_registerForServiceNotifications(
+            kExistingNonNdkService, ServiceData::fillOnRegister, (void*)&data);
+    ASSERT_NE(notif, nullptr);
+
+    sleep(1);  // give us a chance to fail
+    AServiceManager_NotificationRegistration_delete(notif);
+
+    // checking after deleting to avoid needing a mutex over the data - otherwise
+    // in an environment w/ multiple threads, you would need to guard access
+    EXPECT_EQ(data.instance, kExistingNonNdkService);
+    EXPECT_EQ(data.binder, ndk::SpAIBinder(AServiceManager_checkService(kExistingNonNdkService)));
+}
+
 TEST(NdkBinder, UnimplementedDump) {
     sp<IFoo> foo = IFoo::getService(IFoo::kSomeInstanceName);
     ASSERT_NE(foo, nullptr);
@@ -337,6 +380,16 @@
     EXPECT_EQ(isUpdatable, false);
 }
 
+TEST(NdkBinder, GetUpdatableViaApex) {
+    std::optional<std::string> updatableViaApex;
+    AServiceManager_getUpdatableApexName(
+            "android.hardware.light.ILights/default", &updatableViaApex,
+            [](const char* apexName, void* context) {
+                *static_cast<std::optional<std::string>*>(context) = apexName;
+            });
+    EXPECT_EQ(updatableViaApex, std::nullopt) << *updatableViaApex;
+}
+
 // This is too slow
 TEST(NdkBinder, CheckLazyServiceShutDown) {
     ndk::SpAIBinder binder(AServiceManager_waitForService(kLazyBinderNdkUnitTestService));
diff --git a/libs/binder/rust/Android.bp b/libs/binder/rust/Android.bp
index a135796..afd414a 100644
--- a/libs/binder/rust/Android.bp
+++ b/libs/binder/rust/Android.bp
@@ -17,7 +17,6 @@
     rustlibs: [
         "libbinder_ndk_sys",
         "libdowncast_rs",
-        "liblazy_static",
         "liblibc",
     ],
     host_supported: true,
@@ -88,6 +87,7 @@
     min_sdk_version: "Tiramisu",
     lints: "none",
     clippy_lints: "none",
+    visibility: [":__subpackages__"],
 }
 
 rust_bindgen {
@@ -159,7 +159,6 @@
     rustlibs: [
         "libbinder_ndk_sys",
         "libdowncast_rs",
-        "liblazy_static",
         "liblibc",
     ],
 }
diff --git a/libs/binder/rust/rpcbinder/Android.bp b/libs/binder/rust/rpcbinder/Android.bp
index 5ebc27f..f70ebfc 100644
--- a/libs/binder/rust/rpcbinder/Android.bp
+++ b/libs/binder/rust/rpcbinder/Android.bp
@@ -19,7 +19,9 @@
         "libbinder_rpc_unstable_bindgen_sys",
         "libbinder_rs",
         "libdowncast_rs",
+        "libforeign_types",
         "liblibc",
+        "liblog_rust",
     ],
     apex_available: [
         "com.android.compos",
diff --git a/libs/binder/rust/rpcbinder/src/client.rs b/libs/binder/rust/rpcbinder/src/client.rs
index 743800b..48c787b 100644
--- a/libs/binder/rust/rpcbinder/src/client.rs
+++ b/libs/binder/rust/rpcbinder/src/client.rs
@@ -15,6 +15,7 @@
  */
 
 use binder::{unstable_api::new_spibinder, FromIBinder, SpIBinder, StatusCode, Strong};
+use std::ffi::CString;
 use std::os::{
     raw::{c_int, c_void},
     unix::io::RawFd,
@@ -22,9 +23,9 @@
 
 /// Connects to an RPC Binder server over vsock.
 pub fn get_vsock_rpc_service(cid: u32, port: u32) -> Option<SpIBinder> {
-    // SAFETY: AIBinder returned by RpcClient has correct reference count, and the ownership can
-    // safely be taken by new_spibinder.
-    unsafe { new_spibinder(binder_rpc_unstable_bindgen::RpcClient(cid, port)) }
+    // SAFETY: AIBinder returned by VsockRpcClient has correct reference count,
+    // and the ownership can safely be taken by new_spibinder.
+    unsafe { new_spibinder(binder_rpc_unstable_bindgen::VsockRpcClient(cid, port)) }
 }
 
 /// Connects to an RPC Binder server for a particular interface over vsock.
@@ -35,6 +36,27 @@
     interface_cast(get_vsock_rpc_service(cid, port))
 }
 
+/// Connects to an RPC Binder server over Unix domain socket.
+pub fn get_unix_domain_rpc_service(socket_name: &str) -> Option<SpIBinder> {
+    let socket_name = match CString::new(socket_name) {
+        Ok(s) => s,
+        Err(e) => {
+            log::error!("Cannot convert {} to CString. Error: {:?}", socket_name, e);
+            return None;
+        }
+    };
+    // SAFETY: AIBinder returned by UnixDomainRpcClient has correct reference count,
+    // and the ownership can safely be taken by new_spibinder.
+    unsafe { new_spibinder(binder_rpc_unstable_bindgen::UnixDomainRpcClient(socket_name.as_ptr())) }
+}
+
+/// Connects to an RPC Binder server for a particular interface over Unix domain socket.
+pub fn get_unix_domain_rpc_interface<T: FromIBinder + ?Sized>(
+    socket_name: &str,
+) -> Result<Strong<T>, StatusCode> {
+    interface_cast(get_unix_domain_rpc_service(socket_name))
+}
+
 /// Connects to an RPC Binder server, using the given callback to get (and take ownership of)
 /// file descriptors already connected to it.
 pub fn get_preconnected_rpc_service(
diff --git a/libs/binder/rust/rpcbinder/src/lib.rs b/libs/binder/rust/rpcbinder/src/lib.rs
index a5eea61..1b719aa 100644
--- a/libs/binder/rust/rpcbinder/src/lib.rs
+++ b/libs/binder/rust/rpcbinder/src/lib.rs
@@ -20,7 +20,7 @@
 mod server;
 
 pub use client::{
-    get_preconnected_rpc_interface, get_preconnected_rpc_service, get_vsock_rpc_interface,
-    get_vsock_rpc_service,
+    get_preconnected_rpc_interface, get_preconnected_rpc_service, get_unix_domain_rpc_interface,
+    get_unix_domain_rpc_service, get_vsock_rpc_interface, get_vsock_rpc_service,
 };
-pub use server::{run_rpc_server, run_rpc_server_with_factory};
+pub use server::{run_vsock_rpc_server_with_factory, RpcServer, RpcServerRef};
diff --git a/libs/binder/rust/rpcbinder/src/server.rs b/libs/binder/rust/rpcbinder/src/server.rs
index aeb23c6..42f5567 100644
--- a/libs/binder/rust/rpcbinder/src/server.rs
+++ b/libs/binder/rust/rpcbinder/src/server.rs
@@ -18,68 +18,89 @@
     unstable_api::{AIBinder, AsNative},
     SpIBinder,
 };
-use std::{os::raw, ptr::null_mut};
+use binder_rpc_unstable_bindgen::ARpcServer;
+use foreign_types::{foreign_type, ForeignType, ForeignTypeRef};
+use std::io::{Error, ErrorKind};
+use std::{ffi::CString, os::raw, ptr::null_mut};
 
-/// Runs a binder RPC server, serving the supplied binder service implementation on the given vsock
-/// port.
-///
-/// If and when the server is ready for connections (it is listening on the port), `on_ready` is
-/// called to allow appropriate action to be taken - e.g. to notify clients that they may now
-/// attempt to connect.
-///
-/// The current thread is joined to the binder thread pool to handle incoming messages.
-///
-/// Returns true if the server has shutdown normally, false if it failed in some way.
-pub fn run_rpc_server<F>(service: SpIBinder, port: u32, on_ready: F) -> bool
-where
-    F: FnOnce(),
-{
-    let mut ready_notifier = ReadyNotifier(Some(on_ready));
-    ready_notifier.run_server(service, port)
+foreign_type! {
+    type CType = binder_rpc_unstable_bindgen::ARpcServer;
+    fn drop = binder_rpc_unstable_bindgen::ARpcServer_free;
+
+    /// A type that represents a foreign instance of RpcServer.
+    #[derive(Debug)]
+    pub struct RpcServer;
+    /// A borrowed RpcServer.
+    pub struct RpcServerRef;
 }
 
-struct ReadyNotifier<F>(Option<F>)
-where
-    F: FnOnce();
+/// SAFETY - The opaque handle can be cloned freely.
+unsafe impl Send for RpcServer {}
+/// SAFETY - The underlying C++ RpcServer class is thread-safe.
+unsafe impl Sync for RpcServer {}
 
-impl<F> ReadyNotifier<F>
-where
-    F: FnOnce(),
-{
-    fn run_server(&mut self, mut service: SpIBinder, port: u32) -> bool {
+impl RpcServer {
+    /// Creates a binder RPC server, serving the supplied binder service implementation on the given
+    /// vsock port.
+    pub fn new_vsock(mut service: SpIBinder, port: u32) -> Result<RpcServer, Error> {
         let service = service.as_native_mut();
-        let param = self.as_void_ptr();
 
         // SAFETY: Service ownership is transferring to the server and won't be valid afterward.
         // Plus the binder objects are threadsafe.
-        // RunRpcServerCallback does not retain a reference to `ready_callback` or `param`; it only
-        // uses them before it returns, which is during the lifetime of `self`.
         unsafe {
-            binder_rpc_unstable_bindgen::RunRpcServerCallback(
+            Self::checked_from_ptr(binder_rpc_unstable_bindgen::ARpcServer_newVsock(service, port))
+        }
+    }
+
+    /// Creates a binder RPC server, serving the supplied binder service implementation on the given
+    /// socket file name. The socket should be initialized in init.rc with the same name.
+    pub fn new_init_unix_domain(
+        mut service: SpIBinder,
+        socket_name: &str,
+    ) -> Result<RpcServer, Error> {
+        let socket_name = match CString::new(socket_name) {
+            Ok(s) => s,
+            Err(e) => {
+                log::error!("Cannot convert {} to CString. Error: {:?}", socket_name, e);
+                return Err(Error::from(ErrorKind::InvalidInput));
+            }
+        };
+        let service = service.as_native_mut();
+
+        // SAFETY: Service ownership is transferring to the server and won't be valid afterward.
+        // Plus the binder objects are threadsafe.
+        unsafe {
+            Self::checked_from_ptr(binder_rpc_unstable_bindgen::ARpcServer_newInitUnixDomain(
                 service,
-                port,
-                Some(Self::ready_callback),
-                param,
-            )
+                socket_name.as_ptr(),
+            ))
         }
     }
 
-    fn as_void_ptr(&mut self) -> *mut raw::c_void {
-        self as *mut _ as *mut raw::c_void
-    }
-
-    unsafe extern "C" fn ready_callback(param: *mut raw::c_void) {
-        // SAFETY: This is only ever called by `RunRpcServerCallback`, within the lifetime of the
-        // `ReadyNotifier`, with `param` taking the value returned by `as_void_ptr` (so a properly
-        // aligned non-null pointer to an initialized instance).
-        let ready_notifier = param as *mut Self;
-        ready_notifier.as_mut().unwrap().notify()
-    }
-
-    fn notify(&mut self) {
-        if let Some(on_ready) = self.0.take() {
-            on_ready();
+    unsafe fn checked_from_ptr(ptr: *mut ARpcServer) -> Result<RpcServer, Error> {
+        if ptr.is_null() {
+            return Err(Error::new(ErrorKind::Other, "Failed to start server"));
         }
+        Ok(RpcServer::from_ptr(ptr))
+    }
+}
+
+impl RpcServerRef {
+    /// Starts a new background thread and calls join(). Returns immediately.
+    pub fn start(&self) {
+        unsafe { binder_rpc_unstable_bindgen::ARpcServer_start(self.as_ptr()) };
+    }
+
+    /// Joins the RpcServer thread. The call blocks until the server terminates.
+    /// This must be called from exactly one thread.
+    pub fn join(&self) {
+        unsafe { binder_rpc_unstable_bindgen::ARpcServer_join(self.as_ptr()) };
+    }
+
+    /// Shuts down the running RpcServer. Can be called multiple times and from
+    /// multiple threads. Called automatically during drop().
+    pub fn shutdown(&self) {
+        unsafe { binder_rpc_unstable_bindgen::ARpcServer_shutdown(self.as_ptr()) };
     }
 }
 
@@ -91,7 +112,7 @@
 /// The current thread is joined to the binder thread pool to handle incoming messages.
 ///
 /// Returns true if the server has shutdown normally, false if it failed in some way.
-pub fn run_rpc_server_with_factory(
+pub fn run_vsock_rpc_server_with_factory(
     port: u32,
     mut factory: impl FnMut(u32) -> Option<SpIBinder> + Send + Sync,
 ) -> bool {
@@ -100,18 +121,22 @@
     let mut factory_ref: RpcServerFactoryRef = &mut factory;
     let context = &mut factory_ref as *mut RpcServerFactoryRef as *mut raw::c_void;
 
-    // SAFETY: `factory_wrapper` is only ever called by `RunRpcServerWithFactory`, with context
+    // SAFETY: `factory_wrapper` is only ever called by `RunVsockRpcServerWithFactory`, with context
     // taking the pointer value above (so a properly aligned non-null pointer to an initialized
     // `RpcServerFactoryRef`), within the lifetime of `factory_ref` (i.e. no more calls will be made
-    // after `RunRpcServerWithFactory` returns).
+    // after `RunVsockRpcServerWithFactory` returns).
     unsafe {
-        binder_rpc_unstable_bindgen::RunRpcServerWithFactory(Some(factory_wrapper), context, port)
+        binder_rpc_unstable_bindgen::RunVsockRpcServerWithFactory(
+            Some(factory_wrapper),
+            context,
+            port,
+        )
     }
 }
 
 unsafe extern "C" fn factory_wrapper(cid: u32, context: *mut raw::c_void) -> *mut AIBinder {
     // SAFETY: `context` was created from an `&mut RpcServerFactoryRef` by
-    // `run_rpc_server_with_factory`, and we are still within the lifetime of the value it is
+    // `run_vsock_rpc_server_with_factory`, and we are still within the lifetime of the value it is
     // pointing to.
     let factory_ptr = context as *mut RpcServerFactoryRef;
     let factory = factory_ptr.as_mut().unwrap();
diff --git a/libs/binder/rust/src/lib.rs b/libs/binder/rust/src/lib.rs
index 195d9ac..a0e61d9 100644
--- a/libs/binder/rust/src/lib.rs
+++ b/libs/binder/rust/src/lib.rs
@@ -148,4 +148,5 @@
     pub use crate::binder::AsNative;
     pub use crate::proxy::unstable_api::new_spibinder;
     pub use crate::sys::AIBinder;
+    pub use crate::sys::AParcel;
 }
diff --git a/libs/binder/rust/src/native.rs b/libs/binder/rust/src/native.rs
index dee05d0..6f686fb 100644
--- a/libs/binder/rust/src/native.rs
+++ b/libs/binder/rust/src/native.rs
@@ -22,7 +22,6 @@
 use crate::proxy::SpIBinder;
 use crate::sys;
 
-use lazy_static::lazy_static;
 use std::convert::TryFrom;
 use std::ffi::{c_void, CStr, CString};
 use std::fs::File;
@@ -508,10 +507,8 @@
     _private: (),
 }
 
-lazy_static! {
-    // Count of how many LazyServiceGuard objects are in existence.
-    static ref GUARD_COUNT: Mutex<u64> = Mutex::new(0);
-}
+// Count of how many LazyServiceGuard objects are in existence.
+static GUARD_COUNT: Mutex<u64> = Mutex::new(0);
 
 impl LazyServiceGuard {
     /// Create a new LazyServiceGuard to prevent the service manager prematurely killing this
diff --git a/libs/binder/rust/tests/integration.rs b/libs/binder/rust/tests/integration.rs
index 4e10fa9..ca2cedc 100644
--- a/libs/binder/rust/tests/integration.rs
+++ b/libs/binder/rust/tests/integration.rs
@@ -502,7 +502,7 @@
         let instances = binder::get_declared_instances("android.hardware.light.ILights")
             .expect("Could not get declared instances");
 
-        let expected_defaults = if has_lights { 1 } else { 0 };
+        let expected_defaults = usize::from(has_lights);
         assert_eq!(expected_defaults, instances.iter().filter(|i| i.as_str() == "default").count());
     }
 
diff --git a/libs/binder/rust/tests/parcel_fuzzer/Android.bp b/libs/binder/rust/tests/parcel_fuzzer/Android.bp
new file mode 100644
index 0000000..28e0200
--- /dev/null
+++ b/libs/binder/rust/tests/parcel_fuzzer/Android.bp
@@ -0,0 +1,25 @@
+package {
+    // See: http://go/android-license-faq
+    default_applicable_licenses: ["frameworks_native_license"],
+}
+
+rust_fuzz {
+    name: "parcel_fuzzer_rs",
+    srcs: [
+        "parcel_fuzzer.rs",
+    ],
+    rustlibs: [
+        "libarbitrary",
+        "libnum_traits",
+        "libbinder_rs",
+        "libbinder_random_parcel_rs",
+        "binderReadParcelIface-rust",
+    ],
+
+    fuzz_config: {
+        cc: [
+            "waghpawan@google.com",
+            "smoreland@google.com",
+        ],
+    },
+}
diff --git a/libs/binder/rust/tests/parcel_fuzzer/parcel_fuzzer.rs b/libs/binder/rust/tests/parcel_fuzzer/parcel_fuzzer.rs
new file mode 100644
index 0000000..c5c7719
--- /dev/null
+++ b/libs/binder/rust/tests/parcel_fuzzer/parcel_fuzzer.rs
@@ -0,0 +1,161 @@
+/*
+ * 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.
+ */
+
+#![allow(missing_docs)]
+#![no_main]
+
+#[macro_use]
+extern crate libfuzzer_sys;
+
+mod read_utils;
+
+use crate::read_utils::READ_FUNCS;
+use binder::binder_impl::{
+    Binder, BorrowedParcel, IBinderInternal, Parcel, Stability, TransactionCode,
+};
+use binder::{
+    declare_binder_interface, BinderFeatures, Interface, Parcelable, ParcelableHolder, SpIBinder,
+    StatusCode,
+};
+use binder_random_parcel_rs::create_random_parcel;
+use libfuzzer_sys::arbitrary::Arbitrary;
+
+#[derive(Arbitrary, Debug)]
+enum ReadOperation {
+    SetDataPosition { pos: i32 },
+    GetDataSize,
+    ReadParcelableHolder { is_vintf: bool },
+    ReadBasicTypes { instructions: Vec<usize> },
+}
+
+#[derive(Arbitrary, Debug)]
+enum Operation<'a> {
+    Transact { code: u32, flag: u32, data: &'a [u8] },
+    Append { start: i32, len: i32, data1: &'a [u8], data2: &'a [u8], append_all: bool },
+    Read { read_operations: Vec<ReadOperation>, data: &'a [u8] },
+}
+
+/// Interface to fuzz transact with random parcel
+pub trait BinderTransactTest: Interface {}
+
+declare_binder_interface! {
+    BinderTransactTest["Binder_Transact_Test"] {
+        native: BnBinderTransactTest(on_transact),
+        proxy: BpBinderTransactTest,
+    }
+}
+
+impl BinderTransactTest for Binder<BnBinderTransactTest> {}
+
+impl BinderTransactTest for BpBinderTransactTest {}
+
+impl BinderTransactTest for () {}
+
+fn on_transact(
+    _service: &dyn BinderTransactTest,
+    _code: TransactionCode,
+    _parcel: &BorrowedParcel<'_>,
+    _reply: &mut BorrowedParcel<'_>,
+) -> Result<(), StatusCode> {
+    Err(StatusCode::UNKNOWN_ERROR)
+}
+
+fn do_transact(code: u32, data: &[u8], flag: u32) {
+    let p: Parcel = create_random_parcel(data);
+    let spibinder: Option<SpIBinder> =
+        Some(BnBinderTransactTest::new_binder((), BinderFeatures::default()).as_binder());
+    let _reply = spibinder.submit_transact(code, p, flag);
+}
+
+fn do_append_fuzz(start: i32, len: i32, data1: &[u8], data2: &[u8], append_all: bool) {
+    let mut p1 = create_random_parcel(data1);
+    let p2 = create_random_parcel(data2);
+
+    // Fuzz both append methods
+    if append_all {
+        match p1.append_all_from(&p2) {
+            Ok(result) => result,
+            Err(e) => {
+                println!("Error occurred while appending a parcel using append_all_from: {:?}", e)
+            }
+        }
+    } else {
+        match p1.append_from(&p2, start, len) {
+            Ok(result) => result,
+            Err(e) => {
+                println!("Error occurred while appending a parcel using append_from: {:?}", e)
+            }
+        }
+    };
+}
+
+fn do_read_fuzz(read_operations: Vec<ReadOperation>, data: &[u8]) {
+    let parcel = create_random_parcel(data);
+
+    for operation in read_operations {
+        match operation {
+            ReadOperation::SetDataPosition { pos } => {
+                unsafe {
+                    // Safety: Safe if pos is less than current size of the parcel.
+                    // It relies on C++ code for bound checks
+                    match parcel.set_data_position(pos) {
+                        Ok(result) => result,
+                        Err(e) => println!("error occurred while setting data position: {:?}", e),
+                    }
+                }
+            }
+
+            ReadOperation::GetDataSize => {
+                let data_size = parcel.get_data_size();
+                println!("data size from parcel: {:?}", data_size);
+            }
+
+            ReadOperation::ReadParcelableHolder { is_vintf } => {
+                let stability = if is_vintf { Stability::Vintf } else { Stability::Local };
+                let mut holder: ParcelableHolder = ParcelableHolder::new(stability);
+                match holder.read_from_parcel(parcel.borrowed_ref()) {
+                    Ok(result) => result,
+                    Err(err) => {
+                        println!("error occurred while reading from parcel: {:?}", err)
+                    }
+                }
+            }
+
+            ReadOperation::ReadBasicTypes { instructions } => {
+                for instruction in instructions.iter() {
+                    let read_index = instruction % READ_FUNCS.len();
+                    READ_FUNCS[read_index](parcel.borrowed_ref());
+                }
+            }
+        }
+    }
+}
+
+fuzz_target!(|operation: Operation| {
+    match operation {
+        Operation::Transact { code, flag, data } => {
+            do_transact(code, data, flag);
+        }
+
+        Operation::Append { start, len, data1, data2, append_all } => {
+            do_append_fuzz(start, len, data1, data2, append_all);
+        }
+
+        Operation::Read { read_operations, data } => {
+            do_read_fuzz(read_operations, data);
+        }
+    }
+});
diff --git a/libs/binder/rust/tests/parcel_fuzzer/random_parcel/Android.bp b/libs/binder/rust/tests/parcel_fuzzer/random_parcel/Android.bp
new file mode 100644
index 0000000..43a3094
--- /dev/null
+++ b/libs/binder/rust/tests/parcel_fuzzer/random_parcel/Android.bp
@@ -0,0 +1,52 @@
+package {
+    // See: http://go/android-license-faq
+    default_applicable_licenses: ["frameworks_native_license"],
+}
+
+rust_bindgen {
+    name: "libbinder_random_parcel_bindgen",
+    crate_name: "binder_random_parcel_bindgen",
+    host_supported: true,
+    wrapper_src: "wrappers/RandomParcelWrapper.hpp",
+    source_stem: "bindings",
+    visibility: [":__subpackages__"],
+    bindgen_flags: [
+        "--size_t-is-usize",
+        "--allowlist-function",
+        "createRandomParcel",
+        "--allowlist-function",
+        "fuzzRustService",
+    ],
+    shared_libs: [
+        "libc++",
+        "libbinder_ndk",
+    ],
+    rustlibs: [
+        "libbinder_rs",
+    ],
+}
+
+rust_library {
+    name: "libbinder_random_parcel_rs",
+    crate_name: "binder_random_parcel_rs",
+    host_supported: true,
+    srcs: [
+        "src/lib.rs",
+    ],
+    shared_libs: [
+        "libbinder",
+        "libutils",
+        "libcutils",
+        "libc++",
+    ],
+    static_libs: [
+        "libbinder_create_parcel",
+        "libbinder_random_parcel",
+    ],
+    rustlibs: [
+        "libbinder_rs",
+        "libbinder_random_parcel_bindgen",
+    ],
+    lints: "none",
+    clippy_lints: "none",
+}
diff --git a/libs/binder/rust/tests/parcel_fuzzer/random_parcel/fuzz_service_test/Android.bp b/libs/binder/rust/tests/parcel_fuzzer/random_parcel/fuzz_service_test/Android.bp
new file mode 100644
index 0000000..43e407c
--- /dev/null
+++ b/libs/binder/rust/tests/parcel_fuzzer/random_parcel/fuzz_service_test/Android.bp
@@ -0,0 +1,33 @@
+package {
+    // See: http://go/android-license-faq
+    default_applicable_licenses: ["frameworks_native_license"],
+}
+
+aidl_interface {
+    name: "testServiceInterface",
+    srcs: ["ITestService.aidl"],
+    unstable: true,
+    backend: {
+        rust: {
+            enabled: true,
+        },
+    },
+}
+
+rust_fuzz {
+    name: "example_service_fuzzer",
+    srcs: [
+        "service_fuzzer.rs",
+    ],
+    rustlibs: [
+        "libbinder_rs",
+        "libbinder_random_parcel_rs",
+        "testServiceInterface-rust",
+    ],
+    fuzz_config: {
+        cc: [
+            "waghpawan@google.com",
+            "smoreland@google.com",
+        ],
+    },
+}
diff --git a/libs/binder/tests/parcel_fuzzer/SingleDataParcelable.aidl b/libs/binder/rust/tests/parcel_fuzzer/random_parcel/fuzz_service_test/ITestService.aidl
similarity index 89%
copy from libs/binder/tests/parcel_fuzzer/SingleDataParcelable.aidl
copy to libs/binder/rust/tests/parcel_fuzzer/random_parcel/fuzz_service_test/ITestService.aidl
index d62891b..8ce6558 100644
--- a/libs/binder/tests/parcel_fuzzer/SingleDataParcelable.aidl
+++ b/libs/binder/rust/tests/parcel_fuzzer/random_parcel/fuzz_service_test/ITestService.aidl
@@ -14,6 +14,6 @@
  * limitations under the License.
  */
 
-parcelable SingleDataParcelable{
-   int data;
+interface ITestService {
+    boolean repeatData(boolean token);
 }
\ No newline at end of file
diff --git a/libs/binder/rust/tests/parcel_fuzzer/random_parcel/fuzz_service_test/service_fuzzer.rs b/libs/binder/rust/tests/parcel_fuzzer/random_parcel/fuzz_service_test/service_fuzzer.rs
new file mode 100644
index 0000000..a427f28
--- /dev/null
+++ b/libs/binder/rust/tests/parcel_fuzzer/random_parcel/fuzz_service_test/service_fuzzer.rs
@@ -0,0 +1,39 @@
+/*
+ * 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.
+ */
+
+#![allow(missing_docs)]
+#![no_main]
+#[macro_use]
+extern crate libfuzzer_sys;
+
+use binder::{self, BinderFeatures, Interface};
+use binder_random_parcel_rs::fuzz_service;
+use testServiceInterface::aidl::ITestService::{self, BnTestService};
+
+struct TestService;
+
+impl Interface for TestService {}
+
+impl ITestService::ITestService for TestService {
+    fn repeatData(&self, token: bool) -> binder::Result<bool> {
+        Ok(token)
+    }
+}
+
+fuzz_target!(|data: &[u8]| {
+    let service = BnTestService::new_binder(TestService, BinderFeatures::default());
+    fuzz_service(&mut service.as_binder(), data);
+});
diff --git a/libs/binder/rust/tests/parcel_fuzzer/random_parcel/src/lib.rs b/libs/binder/rust/tests/parcel_fuzzer/random_parcel/src/lib.rs
new file mode 100644
index 0000000..1bbd674
--- /dev/null
+++ b/libs/binder/rust/tests/parcel_fuzzer/random_parcel/src/lib.rs
@@ -0,0 +1,44 @@
+/*
+ * 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.
+ */
+
+use binder::binder_impl::Parcel;
+use binder::unstable_api::{AParcel, AsNative};
+use binder::SpIBinder;
+use binder_random_parcel_bindgen::{createRandomParcel, fuzzRustService};
+use std::os::raw::c_void;
+
+/// This API creates a random parcel to be used by fuzzers
+pub fn create_random_parcel(fuzzer_data: &[u8]) -> Parcel {
+    let mut parcel = Parcel::new();
+    let aparcel_ptr: *mut AParcel = parcel.as_native_mut();
+    let ptr = aparcel_ptr as *mut c_void;
+    unsafe {
+        // Safety: `Parcel::as_native_mut` and `slice::as_ptr` always
+        // return valid pointers.
+        createRandomParcel(ptr, fuzzer_data.as_ptr(), fuzzer_data.len());
+    }
+    parcel
+}
+
+/// This API automatically fuzzes provided service
+pub fn fuzz_service(binder: &mut SpIBinder, fuzzer_data: &[u8]) {
+    let ptr = binder.as_native_mut() as *mut c_void;
+    unsafe {
+        // Safety: `SpIBinder::as_native_mut` and `slice::as_ptr` always
+        // return valid pointers.
+        fuzzRustService(ptr, fuzzer_data.as_ptr(), fuzzer_data.len());
+    }
+}
diff --git a/libs/binder/tests/parcel_fuzzer/GenericDataParcelable.aidl b/libs/binder/rust/tests/parcel_fuzzer/random_parcel/wrappers/RandomParcelWrapper.hpp
similarity index 64%
copy from libs/binder/tests/parcel_fuzzer/GenericDataParcelable.aidl
copy to libs/binder/rust/tests/parcel_fuzzer/random_parcel/wrappers/RandomParcelWrapper.hpp
index fc2542b..831bd56 100644
--- a/libs/binder/tests/parcel_fuzzer/GenericDataParcelable.aidl
+++ b/libs/binder/rust/tests/parcel_fuzzer/random_parcel/wrappers/RandomParcelWrapper.hpp
@@ -13,12 +13,13 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+#include <cstdint>
+#include <cstddef>
 
-parcelable GenericDataParcelable {
-    int data;
-    float majorVersion;
-    float minorVersion;
-    IBinder binder;
-    ParcelFileDescriptor fileDescriptor;
-    int[] array;
+extern "C" {
+    // This API is used by rust to fill random parcel.
+    void createRandomParcel(void* aParcel, const uint8_t* data, size_t len);
+
+    // This API is used by fuzzers to automatically fuzz aidl services
+    void fuzzRustService(void* binder, const uint8_t* data, size_t len);
 }
\ No newline at end of file
diff --git a/libs/binder/rust/tests/parcel_fuzzer/read_utils.rs b/libs/binder/rust/tests/parcel_fuzzer/read_utils.rs
new file mode 100644
index 0000000..a2d48b6
--- /dev/null
+++ b/libs/binder/rust/tests/parcel_fuzzer/read_utils.rs
@@ -0,0 +1,133 @@
+/*
+ * 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.
+ */
+
+use binder::binder_impl::BorrowedParcel;
+use binder::{ParcelFileDescriptor, Parcelable, SpIBinder};
+use binderReadParcelIface::aidl::parcelables::EmptyParcelable::EmptyParcelable;
+use binderReadParcelIface::aidl::parcelables::GenericDataParcelable::GenericDataParcelable;
+use binderReadParcelIface::aidl::parcelables::SingleDataParcelable::SingleDataParcelable;
+
+macro_rules! read_parcel_interface {
+    ($data_type:ty) => {
+        |parcel: &BorrowedParcel<'_>| {
+            let _res = parcel.read::<$data_type>();
+        }
+    };
+}
+
+#[derive(Debug, Default)]
+pub struct SomeParcelable {
+    pub data: i32,
+}
+
+impl binder::Parcelable for SomeParcelable {
+    fn write_to_parcel(
+        &self,
+        parcel: &mut binder::binder_impl::BorrowedParcel,
+    ) -> std::result::Result<(), binder::StatusCode> {
+        parcel.sized_write(|subparcel| subparcel.write(&self.data))
+    }
+
+    fn read_from_parcel(
+        &mut self,
+        parcel: &binder::binder_impl::BorrowedParcel,
+    ) -> std::result::Result<(), binder::StatusCode> {
+        parcel.sized_read(|subparcel| match subparcel.read() {
+            Ok(result) => {
+                self.data = result;
+                Ok(())
+            }
+            Err(e) => Err(e),
+        })
+    }
+}
+
+binder::impl_deserialize_for_parcelable!(SomeParcelable);
+
+pub const READ_FUNCS: &[fn(&BorrowedParcel<'_>)] = &[
+    //read basic types
+    read_parcel_interface!(bool),
+    read_parcel_interface!(i8),
+    read_parcel_interface!(i32),
+    read_parcel_interface!(i64),
+    read_parcel_interface!(f32),
+    read_parcel_interface!(f64),
+    read_parcel_interface!(u16),
+    read_parcel_interface!(u32),
+    read_parcel_interface!(u64),
+    read_parcel_interface!(String),
+    //read vec of basic types
+    read_parcel_interface!(Vec<i8>),
+    read_parcel_interface!(Vec<i32>),
+    read_parcel_interface!(Vec<i64>),
+    read_parcel_interface!(Vec<f32>),
+    read_parcel_interface!(Vec<f64>),
+    read_parcel_interface!(Vec<u16>),
+    read_parcel_interface!(Vec<u32>),
+    read_parcel_interface!(Vec<u64>),
+    read_parcel_interface!(Vec<String>),
+    read_parcel_interface!(Option<Vec<i8>>),
+    read_parcel_interface!(Option<Vec<i32>>),
+    read_parcel_interface!(Option<Vec<i64>>),
+    read_parcel_interface!(Option<Vec<f32>>),
+    read_parcel_interface!(Option<Vec<f64>>),
+    read_parcel_interface!(Option<Vec<u16>>),
+    read_parcel_interface!(Option<Vec<u32>>),
+    read_parcel_interface!(Option<Vec<u64>>),
+    read_parcel_interface!(Option<Vec<String>>),
+    read_parcel_interface!(ParcelFileDescriptor),
+    read_parcel_interface!(Vec<Option<ParcelFileDescriptor>>),
+    read_parcel_interface!(Option<Vec<ParcelFileDescriptor>>),
+    read_parcel_interface!(Option<Vec<Option<ParcelFileDescriptor>>>),
+    read_parcel_interface!(SpIBinder),
+    read_parcel_interface!(Vec<Option<SpIBinder>>),
+    read_parcel_interface!(Option<Vec<SpIBinder>>),
+    read_parcel_interface!(Option<Vec<Option<SpIBinder>>>),
+    read_parcel_interface!(SomeParcelable),
+    read_parcel_interface!(Vec<Option<SomeParcelable>>),
+    read_parcel_interface!(Option<Vec<SomeParcelable>>),
+    read_parcel_interface!(Option<Vec<Option<SomeParcelable>>>),
+    // Fuzz read_from_parcel for AIDL generated parcelables
+    |parcel| {
+        let mut empty_parcelable: EmptyParcelable = EmptyParcelable::default();
+        match empty_parcelable.read_from_parcel(parcel) {
+            Ok(result) => result,
+            Err(e) => {
+                println!("EmptyParcelable: error occurred while reading from a parcel: {:?}", e)
+            }
+        }
+    },
+    |parcel| {
+        let mut single_parcelable: SingleDataParcelable = SingleDataParcelable::default();
+        match single_parcelable.read_from_parcel(parcel) {
+            Ok(result) => result,
+            Err(e) => println!(
+                "SingleDataParcelable: error occurred while reading from a parcel: {:?}",
+                e
+            ),
+        }
+    },
+    |parcel| {
+        let mut generic_parcelable: GenericDataParcelable = GenericDataParcelable::default();
+        match generic_parcelable.read_from_parcel(parcel) {
+            Ok(result) => result,
+            Err(e) => println!(
+                "GenericDataParcelable: error occurred while reading from a parcel: {:?}",
+                e
+            ),
+        }
+    },
+];
diff --git a/libs/binder/servicedispatcher.cpp b/libs/binder/servicedispatcher.cpp
index 777f3c9..692cc95 100644
--- a/libs/binder/servicedispatcher.cpp
+++ b/libs/binder/servicedispatcher.cpp
@@ -156,6 +156,10 @@
                                              std::optional<std::string>* _aidl_return) override {
         return mImpl->updatableViaApex(name, _aidl_return);
     }
+    android::binder::Status getUpdatableNames(const std::string& apexName,
+                                              std::vector<std::string>* _aidl_return) override {
+        return mImpl->getUpdatableNames(apexName, _aidl_return);
+    }
     android::binder::Status getConnectionInfo(
             const std::string& name,
             std::optional<android::os::ConnectionInfo>* _aidl_return) override {
diff --git a/libs/binder/tests/Android.bp b/libs/binder/tests/Android.bp
index 92d132f..a999d59 100644
--- a/libs/binder/tests/Android.bp
+++ b/libs/binder/tests/Android.bp
@@ -232,6 +232,7 @@
     srcs: [
         "binderRpcTest.cpp",
         "binderRpcTestCommon.cpp",
+        "binderRpcUniversalTests.cpp",
     ],
 
     test_suites: ["general-tests"],
@@ -722,5 +723,7 @@
             "smoreland@google.com",
             "waghpawan@google.com",
         ],
+        // Adds bugs to hotlist "AIDL fuzzers bugs" on buganizer
+        hotlists: ["4637097"],
     },
 }
diff --git a/libs/binder/tests/BinderRpcTestServerConfig.aidl b/libs/binder/tests/BinderRpcTestServerConfig.aidl
index 4cdeac4..b2e0ef2 100644
--- a/libs/binder/tests/BinderRpcTestServerConfig.aidl
+++ b/libs/binder/tests/BinderRpcTestServerConfig.aidl
@@ -21,6 +21,6 @@
     int rpcSecurity;
     int serverVersion;
     int vsockPort;
-    int unixBootstrapFd; // Inherited from parent
+    int socketFd; // Inherited from the parent process.
     @utf8InCpp String addr;
 }
diff --git a/libs/binder/tests/binderRpcTest.cpp b/libs/binder/tests/binderRpcTest.cpp
index 7294305..02aa45f 100644
--- a/libs/binder/tests/binderRpcTest.cpp
+++ b/libs/binder/tests/binderRpcTest.cpp
@@ -15,7 +15,6 @@
  */
 
 #include <android-base/stringprintf.h>
-#include <gtest/gtest.h>
 
 #include <chrono>
 #include <cstdlib>
@@ -29,6 +28,7 @@
 #include <sys/socket.h>
 
 #include "binderRpcTestCommon.h"
+#include "binderRpcTestFixture.h"
 
 using namespace std::chrono_literals;
 using namespace std::placeholders;
@@ -44,37 +44,6 @@
 constexpr bool kEnableSharedLibs = true;
 #endif
 
-static_assert(RPC_WIRE_PROTOCOL_VERSION + 1 == RPC_WIRE_PROTOCOL_VERSION_NEXT ||
-              RPC_WIRE_PROTOCOL_VERSION == RPC_WIRE_PROTOCOL_VERSION_EXPERIMENTAL);
-
-TEST(BinderRpcParcel, EntireParcelFormatted) {
-    Parcel p;
-    p.writeInt32(3);
-
-    EXPECT_DEATH(p.markForBinder(sp<BBinder>::make()), "format must be set before data is written");
-}
-
-TEST(BinderRpc, CannotUseNextWireVersion) {
-    auto session = RpcSession::make();
-    EXPECT_FALSE(session->setProtocolVersion(RPC_WIRE_PROTOCOL_VERSION_NEXT));
-    EXPECT_FALSE(session->setProtocolVersion(RPC_WIRE_PROTOCOL_VERSION_NEXT + 1));
-    EXPECT_FALSE(session->setProtocolVersion(RPC_WIRE_PROTOCOL_VERSION_NEXT + 2));
-    EXPECT_FALSE(session->setProtocolVersion(RPC_WIRE_PROTOCOL_VERSION_NEXT + 15));
-}
-
-TEST(BinderRpc, CanUseExperimentalWireVersion) {
-    auto session = RpcSession::make();
-    EXPECT_TRUE(session->setProtocolVersion(RPC_WIRE_PROTOCOL_VERSION_EXPERIMENTAL));
-}
-
-using android::binder::Status;
-
-#define EXPECT_OK(status)                 \
-    do {                                  \
-        Status stat = (status);           \
-        EXPECT_TRUE(stat.isOk()) << stat; \
-    } while (false)
-
 static std::string WaitStatusToString(int wstatus) {
     if (WIFEXITED(wstatus)) {
         return base::StringPrintf("exit status %d", WEXITSTATUS(wstatus));
@@ -92,7 +61,15 @@
 
 class Process {
 public:
-    Process(Process&&) = default;
+    Process(Process&& other)
+          : mCustomExitStatusCheck(std::move(other.mCustomExitStatusCheck)),
+            mReadEnd(std::move(other.mReadEnd)),
+            mWriteEnd(std::move(other.mWriteEnd)) {
+        // The default move constructor doesn't clear mPid after moving it,
+        // which we need to do because the destructor checks for mPid!=0
+        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;
@@ -152,21 +129,26 @@
     return vsockPort++;
 }
 
-struct ProcessSession {
+static base::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())));
+    return fd;
+}
+
+// Destructors need to be defined, even if pure virtual
+ProcessSession::~ProcessSession() {}
+
+class LinuxProcessSession : public ProcessSession {
+public:
     // reference to process hosting a socket server
     Process host;
 
-    struct SessionInfo {
-        sp<RpcSession> session;
-        sp<IBinder> root;
-    };
-
-    // client session objects associated with other process
-    // each one represents a separate session
-    std::vector<SessionInfo> sessions;
-
-    ProcessSession(ProcessSession&&) = default;
-    ~ProcessSession() {
+    LinuxProcessSession(LinuxProcessSession&&) = default;
+    LinuxProcessSession(Process&& host) : host(std::move(host)) {}
+    ~LinuxProcessSession() override {
         for (auto& session : sessions) {
             session.root = nullptr;
         }
@@ -197,46 +179,12 @@
             }
         }
     }
-};
 
-// Process session where the process hosts IBinderRpcTest, the server used
-// for most testing here
-struct BinderRpcTestProcessSession {
-    ProcessSession proc;
-
-    // pre-fetched root object (for first session)
-    sp<IBinder> rootBinder;
-
-    // pre-casted root object (for first session)
-    sp<IBinderRpcTest> rootIface;
-
-    // whether session should be invalidated by end of run
-    bool expectAlreadyShutdown = false;
-
-    BinderRpcTestProcessSession(BinderRpcTestProcessSession&&) = default;
-    ~BinderRpcTestProcessSession() {
-        if (!expectAlreadyShutdown) {
-            EXPECT_NE(nullptr, rootIface);
-            if (rootIface == nullptr) return;
-
-            std::vector<int32_t> remoteCounts;
-            // calling over any sessions counts across all sessions
-            EXPECT_OK(rootIface->countBinders(&remoteCounts));
-            EXPECT_EQ(remoteCounts.size(), proc.sessions.size());
-            for (auto remoteCount : remoteCounts) {
-                EXPECT_EQ(remoteCount, 1);
-            }
-
-            // even though it is on another thread, shutdown races with
-            // the transaction reply being written
-            if (auto status = rootIface->scheduleShutdown(); !status.isOk()) {
-                EXPECT_EQ(DEAD_OBJECT, status.transactionError()) << status;
-            }
-        }
-
-        rootIface = nullptr;
-        rootBinder = nullptr;
+    void setCustomExitStatusCheck(std::function<void(int wstatus)> f) override {
+        host.setCustomExitStatusCheck(std::move(f));
     }
+
+    void terminate() override { host.terminate(); }
 };
 
 static base::unique_fd connectTo(const RpcSocketAddress& addr) {
@@ -273,552 +221,138 @@
     return std::move(sockClient);
 }
 
-using RunServiceFn = void (*)(android::base::borrowed_fd writeEnd,
-                              android::base::borrowed_fd readEnd);
-
-class BinderRpc : public ::testing::TestWithParam<
-                          std::tuple<SocketType, RpcSecurity, uint32_t, uint32_t, bool, bool>> {
-public:
-    SocketType socketType() const { return std::get<0>(GetParam()); }
-    RpcSecurity rpcSecurity() const { return std::get<1>(GetParam()); }
-    uint32_t clientVersion() const { return std::get<2>(GetParam()); }
-    uint32_t serverVersion() const { return std::get<3>(GetParam()); }
-    bool serverSingleThreaded() const { return std::get<4>(GetParam()); }
-    bool noKernel() const { return std::get<5>(GetParam()); }
-
-    bool clientOrServerSingleThreaded() const {
-        return !kEnableRpcThreads || serverSingleThreaded();
+std::string BinderRpc::PrintParamInfo(const testing::TestParamInfo<ParamType>& info) {
+    auto [type, security, clientVersion, serverVersion, singleThreaded, noKernel] = info.param;
+    auto ret = PrintToString(type) + "_" + newFactory(security)->toCString() + "_clientV" +
+            std::to_string(clientVersion) + "_serverV" + std::to_string(serverVersion);
+    if (singleThreaded) {
+        ret += "_single_threaded";
     }
-
-    // Whether the test params support sending FDs in parcels.
-    bool supportsFdTransport() const {
-        return clientVersion() >= 1 && serverVersion() >= 1 && rpcSecurity() != RpcSecurity::TLS &&
-                (socketType() == SocketType::PRECONNECTED || socketType() == SocketType::UNIX ||
-                 socketType() == SocketType::UNIX_BOOTSTRAP);
+    if (noKernel) {
+        ret += "_no_kernel";
     }
+    return ret;
+}
 
-    void SetUp() override {
-        if (socketType() == SocketType::UNIX_BOOTSTRAP && rpcSecurity() == RpcSecurity::TLS) {
-            GTEST_SKIP() << "Unix bootstrap not supported over a TLS transport";
-        }
-    }
+// This creates a new process serving an interface on a certain number of
+// threads.
+std::unique_ptr<ProcessSession> BinderRpc::createRpcTestSocketServerProcessEtc(
+        const BinderRpcOptions& options) {
+    CHECK_GE(options.numSessions, 1) << "Must have at least one session to a server";
 
-    static inline std::string PrintParamInfo(const testing::TestParamInfo<ParamType>& info) {
-        auto [type, security, clientVersion, serverVersion, singleThreaded, noKernel] = info.param;
-        auto ret = PrintToString(type) + "_" + newFactory(security)->toCString() + "_clientV" +
-                std::to_string(clientVersion) + "_serverV" + std::to_string(serverVersion);
-        if (singleThreaded) {
-            ret += "_single_threaded";
-        }
-        if (noKernel) {
-            ret += "_no_kernel";
-        }
-        return ret;
-    }
+    SocketType socketType = std::get<0>(GetParam());
+    RpcSecurity rpcSecurity = std::get<1>(GetParam());
+    uint32_t clientVersion = std::get<2>(GetParam());
+    uint32_t serverVersion = std::get<3>(GetParam());
+    bool singleThreaded = std::get<4>(GetParam());
+    bool noKernel = std::get<5>(GetParam());
 
-    // This creates a new process serving an interface on a certain number of
-    // threads.
-    ProcessSession createRpcTestSocketServerProcessEtc(const BinderRpcOptions& options) {
-        CHECK_GE(options.numSessions, 1) << "Must have at least one session to a server";
+    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" : "");
 
-        SocketType socketType = std::get<0>(GetParam());
-        RpcSecurity rpcSecurity = std::get<1>(GetParam());
-        uint32_t clientVersion = std::get<2>(GetParam());
-        uint32_t serverVersion = std::get<3>(GetParam());
-        bool singleThreaded = std::get<4>(GetParam());
-        bool noKernel = std::get<5>(GetParam());
+    base::unique_fd bootstrapClientFd, socketFd;
 
-        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" : "");
-
-        base::unique_fd bootstrapClientFd, bootstrapServerFd;
+    auto addr = allocateSocketAddress();
+    // Initializes the socket before the fork/exec.
+    if (socketType == SocketType::UNIX_RAW) {
+        socketFd = initUnixSocket(addr);
+    } 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, &bootstrapServerFd)) {
+        if (!base::Socketpair(SOCK_STREAM, &bootstrapClientFd, &socketFd)) {
             int savedErrno = errno;
             LOG(FATAL) << "Failed socketpair(): " << strerror(savedErrno);
         }
+    }
 
-        auto ret = ProcessSession{
-                .host = Process([=](android::base::borrowed_fd writeEnd,
-                                    android::base::borrowed_fd readEnd) {
-                    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 ret = std::make_unique<LinuxProcessSession>(
+            Process([=](android::base::borrowed_fd writeEnd, android::base::borrowed_fd readEnd) {
+                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);
+            }));
 
-        BinderRpcTestServerConfig serverConfig;
-        serverConfig.numThreads = options.numThreads;
-        serverConfig.socketType = static_cast<int32_t>(socketType);
-        serverConfig.rpcSecurity = static_cast<int32_t>(rpcSecurity);
-        serverConfig.serverVersion = serverVersion;
-        serverConfig.vsockPort = allocateVsockPort();
-        serverConfig.addr = allocateSocketAddress();
-        serverConfig.unixBootstrapFd = bootstrapServerFd.get();
-        for (auto mode : options.serverSupportedFileDescriptorTransportModes) {
-            serverConfig.serverSupportedFileDescriptorTransportModes.push_back(
-                    static_cast<int32_t>(mode));
-        }
-        writeToFd(ret.host.writeEnd(), serverConfig);
+    BinderRpcTestServerConfig serverConfig;
+    serverConfig.numThreads = options.numThreads;
+    serverConfig.socketType = static_cast<int32_t>(socketType);
+    serverConfig.rpcSecurity = static_cast<int32_t>(rpcSecurity);
+    serverConfig.serverVersion = serverVersion;
+    serverConfig.vsockPort = allocateVsockPort();
+    serverConfig.addr = addr;
+    serverConfig.socketFd = socketFd.get();
+    for (auto mode : options.serverSupportedFileDescriptorTransportModes) {
+        serverConfig.serverSupportedFileDescriptorTransportModes.push_back(
+                static_cast<int32_t>(mode));
+    }
+    writeToFd(ret->host.writeEnd(), serverConfig);
 
-        std::vector<sp<RpcSession>> sessions;
-        auto certVerifier = std::make_shared<RpcCertificateVerifierSimple>();
-        for (size_t i = 0; i < options.numSessions; i++) {
-            sessions.emplace_back(RpcSession::make(newFactory(rpcSecurity, certVerifier)));
-        }
+    std::vector<sp<RpcSession>> sessions;
+    auto certVerifier = std::make_shared<RpcCertificateVerifierSimple>();
+    for (size_t i = 0; i < options.numSessions; i++) {
+        sessions.emplace_back(RpcSession::make(newFactory(rpcSecurity, certVerifier)));
+    }
 
-        auto serverInfo = readFromFd<BinderRpcTestServerInfo>(ret.host.readEnd());
-        BinderRpcTestClientInfo clientInfo;
-        for (const auto& session : sessions) {
-            auto& parcelableCert = clientInfo.certs.emplace_back();
-            parcelableCert.data = session->getCertificate(RpcCertificateFormat::PEM);
-        }
-        writeToFd(ret.host.writeEnd(), clientInfo);
+    auto serverInfo = readFromFd<BinderRpcTestServerInfo>(ret->host.readEnd());
+    BinderRpcTestClientInfo clientInfo;
+    for (const auto& session : sessions) {
+        auto& parcelableCert = clientInfo.certs.emplace_back();
+        parcelableCert.data = session->getCertificate(RpcCertificateFormat::PEM);
+    }
+    writeToFd(ret->host.writeEnd(), clientInfo);
 
-        CHECK_LE(serverInfo.port, std::numeric_limits<unsigned int>::max());
-        if (socketType == SocketType::INET) {
-            CHECK_NE(0, serverInfo.port);
-        }
+    CHECK_LE(serverInfo.port, std::numeric_limits<unsigned int>::max());
+    if (socketType == SocketType::INET) {
+        CHECK_NE(0, serverInfo.port);
+    }
 
-        if (rpcSecurity == RpcSecurity::TLS) {
-            const auto& serverCert = serverInfo.cert.data;
-            CHECK_EQ(OK,
-                     certVerifier->addTrustedPeerCertificate(RpcCertificateFormat::PEM,
-                                                             serverCert));
-        }
+    if (rpcSecurity == RpcSecurity::TLS) {
+        const auto& serverCert = serverInfo.cert.data;
+        CHECK_EQ(OK,
+                 certVerifier->addTrustedPeerCertificate(RpcCertificateFormat::PEM, serverCert));
+    }
 
-        status_t status;
+    status_t status;
 
-        for (const auto& session : sessions) {
-            CHECK(session->setProtocolVersion(clientVersion));
-            session->setMaxIncomingThreads(options.numIncomingConnections);
-            session->setMaxOutgoingThreads(options.numOutgoingConnections);
-            session->setFileDescriptorTransportMode(options.clientFileDescriptorTransportMode);
+    for (const auto& session : sessions) {
+        CHECK(session->setProtocolVersion(clientVersion));
+        session->setMaxIncomingThreads(options.numIncomingConnections);
+        session->setMaxOutgoingThreads(options.numOutgoingConnections);
+        session->setFileDescriptorTransportMode(options.clientFileDescriptorTransportMode);
 
-            switch (socketType) {
-                case SocketType::PRECONNECTED:
-                    status = session->setupPreconnectedClient({}, [=]() {
-                        return connectTo(UnixSocketAddress(serverConfig.addr.c_str()));
-                    });
-                    break;
-                case SocketType::UNIX:
-                    status = session->setupUnixDomainClient(serverConfig.addr.c_str());
-                    break;
-                case SocketType::UNIX_BOOTSTRAP:
-                    status = session->setupUnixDomainSocketBootstrapClient(
-                            base::unique_fd(dup(bootstrapClientFd.get())));
-                    break;
-                case SocketType::VSOCK:
-                    status = session->setupVsockClient(VMADDR_CID_LOCAL, serverConfig.vsockPort);
-                    break;
-                case SocketType::INET:
-                    status = session->setupInetClient("127.0.0.1", serverInfo.port);
-                    break;
-                default:
-                    LOG_ALWAYS_FATAL("Unknown socket type");
-            }
-            if (options.allowConnectFailure && status != OK) {
-                ret.sessions.clear();
+        switch (socketType) {
+            case SocketType::PRECONNECTED:
+                status = session->setupPreconnectedClient({}, [=]() {
+                    return connectTo(UnixSocketAddress(serverConfig.addr.c_str()));
+                });
                 break;
-            }
-            CHECK_EQ(status, OK) << "Could not connect: " << statusToString(status);
-            ret.sessions.push_back({session, session->getRootObject()});
+            case SocketType::UNIX_RAW:
+            case SocketType::UNIX:
+                status = session->setupUnixDomainClient(serverConfig.addr.c_str());
+                break;
+            case SocketType::UNIX_BOOTSTRAP:
+                status = session->setupUnixDomainSocketBootstrapClient(
+                        base::unique_fd(dup(bootstrapClientFd.get())));
+                break;
+            case SocketType::VSOCK:
+                status = session->setupVsockClient(VMADDR_CID_LOCAL, serverConfig.vsockPort);
+                break;
+            case SocketType::INET:
+                status = session->setupInetClient("127.0.0.1", serverInfo.port);
+                break;
+            default:
+                LOG_ALWAYS_FATAL("Unknown socket type");
         }
-        return ret;
+        if (options.allowConnectFailure && status != OK) {
+            ret->sessions.clear();
+            break;
+        }
+        CHECK_EQ(status, OK) << "Could not connect: " << statusToString(status);
+        ret->sessions.push_back({session, session->getRootObject()});
     }
-
-    BinderRpcTestProcessSession createRpcTestSocketServerProcess(const BinderRpcOptions& options) {
-        BinderRpcTestProcessSession ret{
-                .proc = createRpcTestSocketServerProcessEtc(options),
-        };
-
-        ret.rootBinder = ret.proc.sessions.empty() ? nullptr : ret.proc.sessions.at(0).root;
-        ret.rootIface = interface_cast<IBinderRpcTest>(ret.rootBinder);
-
-        return ret;
-    }
-
-    void testThreadPoolOverSaturated(sp<IBinderRpcTest> iface, size_t numCalls,
-                                     size_t sleepMs = 500);
-};
-
-TEST_P(BinderRpc, Ping) {
-    auto proc = createRpcTestSocketServerProcess({});
-    ASSERT_NE(proc.rootBinder, nullptr);
-    EXPECT_EQ(OK, proc.rootBinder->pingBinder());
-}
-
-TEST_P(BinderRpc, GetInterfaceDescriptor) {
-    auto proc = createRpcTestSocketServerProcess({});
-    ASSERT_NE(proc.rootBinder, nullptr);
-    EXPECT_EQ(IBinderRpcTest::descriptor, proc.rootBinder->getInterfaceDescriptor());
-}
-
-TEST_P(BinderRpc, MultipleSessions) {
-    if (serverSingleThreaded()) {
-        // Tests with multiple sessions require a multi-threaded service,
-        // but work fine on a single-threaded client
-        GTEST_SKIP() << "This test requires a multi-threaded service";
-    }
-
-    auto proc = createRpcTestSocketServerProcess({.numThreads = 1, .numSessions = 5});
-    for (auto session : proc.proc.sessions) {
-        ASSERT_NE(nullptr, session.root);
-        EXPECT_EQ(OK, session.root->pingBinder());
-    }
-}
-
-TEST_P(BinderRpc, SeparateRootObject) {
-    if (serverSingleThreaded()) {
-        GTEST_SKIP() << "This test requires a multi-threaded service";
-    }
-
-    SocketType type = std::get<0>(GetParam());
-    if (type == SocketType::PRECONNECTED || type == SocketType::UNIX ||
-        type == SocketType::UNIX_BOOTSTRAP) {
-        // we can't get port numbers for unix sockets
-        return;
-    }
-
-    auto proc = createRpcTestSocketServerProcess({.numSessions = 2});
-
-    int port1 = 0;
-    EXPECT_OK(proc.rootIface->getClientPort(&port1));
-
-    sp<IBinderRpcTest> rootIface2 = interface_cast<IBinderRpcTest>(proc.proc.sessions.at(1).root);
-    int port2;
-    EXPECT_OK(rootIface2->getClientPort(&port2));
-
-    // we should have a different IBinderRpcTest object created for each
-    // session, because we use setPerSessionRootObject
-    EXPECT_NE(port1, port2);
-}
-
-TEST_P(BinderRpc, TransactionsMustBeMarkedRpc) {
-    auto proc = createRpcTestSocketServerProcess({});
-    Parcel data;
-    Parcel reply;
-    EXPECT_EQ(BAD_TYPE, proc.rootBinder->transact(IBinder::PING_TRANSACTION, data, &reply, 0));
-}
-
-TEST_P(BinderRpc, AppendSeparateFormats) {
-    auto proc1 = createRpcTestSocketServerProcess({});
-    auto proc2 = createRpcTestSocketServerProcess({});
-
-    Parcel pRaw;
-
-    Parcel p1;
-    p1.markForBinder(proc1.rootBinder);
-    p1.writeInt32(3);
-
-    EXPECT_EQ(BAD_TYPE, p1.appendFrom(&pRaw, 0, pRaw.dataSize()));
-    EXPECT_EQ(BAD_TYPE, pRaw.appendFrom(&p1, 0, p1.dataSize()));
-
-    Parcel p2;
-    p2.markForBinder(proc2.rootBinder);
-    p2.writeInt32(7);
-
-    EXPECT_EQ(BAD_TYPE, p1.appendFrom(&p2, 0, p2.dataSize()));
-    EXPECT_EQ(BAD_TYPE, p2.appendFrom(&p1, 0, p1.dataSize()));
-}
-
-TEST_P(BinderRpc, UnknownTransaction) {
-    auto proc = createRpcTestSocketServerProcess({});
-    Parcel data;
-    data.markForBinder(proc.rootBinder);
-    Parcel reply;
-    EXPECT_EQ(UNKNOWN_TRANSACTION, proc.rootBinder->transact(1337, data, &reply, 0));
-}
-
-TEST_P(BinderRpc, SendSomethingOneway) {
-    auto proc = createRpcTestSocketServerProcess({});
-    EXPECT_OK(proc.rootIface->sendString("asdf"));
-}
-
-TEST_P(BinderRpc, SendAndGetResultBack) {
-    auto proc = createRpcTestSocketServerProcess({});
-    std::string doubled;
-    EXPECT_OK(proc.rootIface->doubleString("cool ", &doubled));
-    EXPECT_EQ("cool cool ", doubled);
-}
-
-TEST_P(BinderRpc, SendAndGetResultBackBig) {
-    auto proc = createRpcTestSocketServerProcess({});
-    std::string single = std::string(1024, 'a');
-    std::string doubled;
-    EXPECT_OK(proc.rootIface->doubleString(single, &doubled));
-    EXPECT_EQ(single + single, doubled);
-}
-
-TEST_P(BinderRpc, InvalidNullBinderReturn) {
-    auto proc = createRpcTestSocketServerProcess({});
-
-    sp<IBinder> outBinder;
-    EXPECT_EQ(proc.rootIface->getNullBinder(&outBinder).transactionError(), UNEXPECTED_NULL);
-}
-
-TEST_P(BinderRpc, CallMeBack) {
-    auto proc = createRpcTestSocketServerProcess({});
-
-    int32_t pingResult;
-    EXPECT_OK(proc.rootIface->pingMe(new MyBinderRpcSession("foo"), &pingResult));
-    EXPECT_EQ(OK, pingResult);
-
-    EXPECT_EQ(0, MyBinderRpcSession::gNum);
-}
-
-TEST_P(BinderRpc, RepeatBinder) {
-    auto proc = createRpcTestSocketServerProcess({});
-
-    sp<IBinder> inBinder = new MyBinderRpcSession("foo");
-    sp<IBinder> outBinder;
-    EXPECT_OK(proc.rootIface->repeatBinder(inBinder, &outBinder));
-    EXPECT_EQ(inBinder, outBinder);
-
-    wp<IBinder> weak = inBinder;
-    inBinder = nullptr;
-    outBinder = nullptr;
-
-    // Force reading a reply, to process any pending dec refs from the other
-    // process (the other process will process dec refs there before processing
-    // the ping here).
-    EXPECT_EQ(OK, proc.rootBinder->pingBinder());
-
-    EXPECT_EQ(nullptr, weak.promote());
-
-    EXPECT_EQ(0, MyBinderRpcSession::gNum);
-}
-
-TEST_P(BinderRpc, RepeatTheirBinder) {
-    auto proc = createRpcTestSocketServerProcess({});
-
-    sp<IBinderRpcSession> session;
-    EXPECT_OK(proc.rootIface->openSession("aoeu", &session));
-
-    sp<IBinder> inBinder = IInterface::asBinder(session);
-    sp<IBinder> outBinder;
-    EXPECT_OK(proc.rootIface->repeatBinder(inBinder, &outBinder));
-    EXPECT_EQ(inBinder, outBinder);
-
-    wp<IBinder> weak = inBinder;
-    session = nullptr;
-    inBinder = nullptr;
-    outBinder = nullptr;
-
-    // Force reading a reply, to process any pending dec refs from the other
-    // process (the other process will process dec refs there before processing
-    // the ping here).
-    EXPECT_EQ(OK, proc.rootBinder->pingBinder());
-
-    EXPECT_EQ(nullptr, weak.promote());
-}
-
-TEST_P(BinderRpc, RepeatBinderNull) {
-    auto proc = createRpcTestSocketServerProcess({});
-
-    sp<IBinder> outBinder;
-    EXPECT_OK(proc.rootIface->repeatBinder(nullptr, &outBinder));
-    EXPECT_EQ(nullptr, outBinder);
-}
-
-TEST_P(BinderRpc, HoldBinder) {
-    auto proc = createRpcTestSocketServerProcess({});
-
-    IBinder* ptr = nullptr;
-    {
-        sp<IBinder> binder = new BBinder();
-        ptr = binder.get();
-        EXPECT_OK(proc.rootIface->holdBinder(binder));
-    }
-
-    sp<IBinder> held;
-    EXPECT_OK(proc.rootIface->getHeldBinder(&held));
-
-    EXPECT_EQ(held.get(), ptr);
-
-    // stop holding binder, because we test to make sure references are cleaned
-    // up
-    EXPECT_OK(proc.rootIface->holdBinder(nullptr));
-    // and flush ref counts
-    EXPECT_EQ(OK, proc.rootBinder->pingBinder());
-}
-
-// START TESTS FOR LIMITATIONS OF SOCKET BINDER
-// These are behavioral differences form regular binder, where certain usecases
-// aren't supported.
-
-TEST_P(BinderRpc, CannotMixBindersBetweenUnrelatedSocketSessions) {
-    auto proc1 = createRpcTestSocketServerProcess({});
-    auto proc2 = createRpcTestSocketServerProcess({});
-
-    sp<IBinder> outBinder;
-    EXPECT_EQ(INVALID_OPERATION,
-              proc1.rootIface->repeatBinder(proc2.rootBinder, &outBinder).transactionError());
-}
-
-TEST_P(BinderRpc, CannotMixBindersBetweenTwoSessionsToTheSameServer) {
-    if (serverSingleThreaded()) {
-        GTEST_SKIP() << "This test requires a multi-threaded service";
-    }
-
-    auto proc = createRpcTestSocketServerProcess({.numThreads = 1, .numSessions = 2});
-
-    sp<IBinder> outBinder;
-    EXPECT_EQ(INVALID_OPERATION,
-              proc.rootIface->repeatBinder(proc.proc.sessions.at(1).root, &outBinder)
-                      .transactionError());
-}
-
-TEST_P(BinderRpc, CannotSendRegularBinderOverSocketBinder) {
-    if (!kEnableKernelIpc || noKernel()) {
-        GTEST_SKIP() << "Test disabled because Binder kernel driver was disabled "
-                        "at build time.";
-    }
-
-    auto proc = createRpcTestSocketServerProcess({});
-
-    sp<IBinder> someRealBinder = IInterface::asBinder(defaultServiceManager());
-    sp<IBinder> outBinder;
-    EXPECT_EQ(INVALID_OPERATION,
-              proc.rootIface->repeatBinder(someRealBinder, &outBinder).transactionError());
-}
-
-TEST_P(BinderRpc, CannotSendSocketBinderOverRegularBinder) {
-    if (!kEnableKernelIpc || noKernel()) {
-        GTEST_SKIP() << "Test disabled because Binder kernel driver was disabled "
-                        "at build time.";
-    }
-
-    auto proc = createRpcTestSocketServerProcess({});
-
-    // for historical reasons, IServiceManager interface only returns the
-    // exception code
-    EXPECT_EQ(binder::Status::EX_TRANSACTION_FAILED,
-              defaultServiceManager()->addService(String16("not_suspicious"), proc.rootBinder));
-}
-
-// END TESTS FOR LIMITATIONS OF SOCKET BINDER
-
-TEST_P(BinderRpc, RepeatRootObject) {
-    auto proc = createRpcTestSocketServerProcess({});
-
-    sp<IBinder> outBinder;
-    EXPECT_OK(proc.rootIface->repeatBinder(proc.rootBinder, &outBinder));
-    EXPECT_EQ(proc.rootBinder, outBinder);
-}
-
-TEST_P(BinderRpc, NestedTransactions) {
-    auto proc = createRpcTestSocketServerProcess({
-            // Enable FD support because it uses more stack space and so represents
-            // something closer to a worst case scenario.
-            .clientFileDescriptorTransportMode = RpcSession::FileDescriptorTransportMode::UNIX,
-            .serverSupportedFileDescriptorTransportModes =
-                    {RpcSession::FileDescriptorTransportMode::UNIX},
-    });
-
-    auto nastyNester = sp<MyBinderRpcTest>::make();
-    EXPECT_OK(proc.rootIface->nestMe(nastyNester, 10));
-
-    wp<IBinder> weak = nastyNester;
-    nastyNester = nullptr;
-    EXPECT_EQ(nullptr, weak.promote());
-}
-
-TEST_P(BinderRpc, SameBinderEquality) {
-    auto proc = createRpcTestSocketServerProcess({});
-
-    sp<IBinder> a;
-    EXPECT_OK(proc.rootIface->alwaysGiveMeTheSameBinder(&a));
-
-    sp<IBinder> b;
-    EXPECT_OK(proc.rootIface->alwaysGiveMeTheSameBinder(&b));
-
-    EXPECT_EQ(a, b);
-}
-
-TEST_P(BinderRpc, SameBinderEqualityWeak) {
-    auto proc = createRpcTestSocketServerProcess({});
-
-    sp<IBinder> a;
-    EXPECT_OK(proc.rootIface->alwaysGiveMeTheSameBinder(&a));
-    wp<IBinder> weak = a;
-    a = nullptr;
-
-    sp<IBinder> b;
-    EXPECT_OK(proc.rootIface->alwaysGiveMeTheSameBinder(&b));
-
-    // this is the wrong behavior, since BpBinder
-    // doesn't implement onIncStrongAttempted
-    // but make sure there is no crash
-    EXPECT_EQ(nullptr, weak.promote());
-
-    GTEST_SKIP() << "Weak binders aren't currently re-promotable for RPC binder.";
-
-    // In order to fix this:
-    // - need to have incStrongAttempted reflected across IPC boundary (wait for
-    //   response to promote - round trip...)
-    // - sendOnLastWeakRef, to delete entries out of RpcState table
-    EXPECT_EQ(b, weak.promote());
-}
-
-#define expectSessions(expected, iface)                   \
-    do {                                                  \
-        int session;                                      \
-        EXPECT_OK((iface)->getNumOpenSessions(&session)); \
-        EXPECT_EQ(expected, session);                     \
-    } while (false)
-
-TEST_P(BinderRpc, SingleSession) {
-    auto proc = createRpcTestSocketServerProcess({});
-
-    sp<IBinderRpcSession> session;
-    EXPECT_OK(proc.rootIface->openSession("aoeu", &session));
-    std::string out;
-    EXPECT_OK(session->getName(&out));
-    EXPECT_EQ("aoeu", out);
-
-    expectSessions(1, proc.rootIface);
-    session = nullptr;
-    expectSessions(0, proc.rootIface);
-}
-
-TEST_P(BinderRpc, ManySessions) {
-    auto proc = createRpcTestSocketServerProcess({});
-
-    std::vector<sp<IBinderRpcSession>> sessions;
-
-    for (size_t i = 0; i < 15; i++) {
-        expectSessions(i, proc.rootIface);
-        sp<IBinderRpcSession> session;
-        EXPECT_OK(proc.rootIface->openSession(std::to_string(i), &session));
-        sessions.push_back(session);
-    }
-    expectSessions(sessions.size(), proc.rootIface);
-    for (size_t i = 0; i < sessions.size(); i++) {
-        std::string out;
-        EXPECT_OK(sessions.at(i)->getName(&out));
-        EXPECT_EQ(std::to_string(i), out);
-    }
-    expectSessions(sessions.size(), proc.rootIface);
-
-    while (!sessions.empty()) {
-        sessions.pop_back();
-        expectSessions(sessions.size(), proc.rootIface);
-    }
-    expectSessions(0, proc.rootIface);
-}
-
-size_t epochMillis() {
-    using std::chrono::duration_cast;
-    using std::chrono::milliseconds;
-    using std::chrono::seconds;
-    using std::chrono::system_clock;
-    return duration_cast<milliseconds>(system_clock::now().time_since_epoch()).count();
+    return ret;
 }
 
 TEST_P(BinderRpc, ThreadPoolGreaterThanEqualRequested) {
@@ -857,8 +391,8 @@
     for (auto& t : ts) t.join();
 }
 
-void BinderRpc::testThreadPoolOverSaturated(sp<IBinderRpcTest> iface, size_t numCalls,
-                                            size_t sleepMs) {
+static void testThreadPoolOverSaturated(sp<IBinderRpcTest> iface, size_t numCalls,
+                                        size_t sleepMs = 500) {
     size_t epochMsBefore = epochMillis();
 
     std::vector<std::thread> ts;
@@ -958,20 +492,6 @@
     saturateThreadPool(kNumServerThreads, proc.rootIface);
 }
 
-TEST_P(BinderRpc, OnewayCallDoesNotWait) {
-    constexpr size_t kReallyLongTimeMs = 100;
-    constexpr size_t kSleepMs = kReallyLongTimeMs * 5;
-
-    auto proc = createRpcTestSocketServerProcess({});
-
-    size_t epochMsBefore = epochMillis();
-
-    EXPECT_OK(proc.rootIface->sleepMsAsync(kSleepMs));
-
-    size_t epochMsAfter = epochMillis();
-    EXPECT_LT(epochMsAfter, epochMsBefore + kReallyLongTimeMs);
-}
-
 TEST_P(BinderRpc, OnewayCallQueueingWithFds) {
     if (!supportsFdTransport()) {
         GTEST_SKIP() << "Would fail trivially (which is tested elsewhere)";
@@ -1057,7 +577,7 @@
     // Build up oneway calls on the second session to make sure it terminates
     // and shuts down. The first session should be unaffected (proc destructor
     // checks the first session).
-    auto iface = interface_cast<IBinderRpcTest>(proc.proc.sessions.at(1).root);
+    auto iface = interface_cast<IBinderRpcTest>(proc.proc->sessions.at(1).root);
 
     std::vector<std::thread> threads;
     for (size_t i = 0; i < kNumClients; i++) {
@@ -1085,66 +605,7 @@
     // any pending commands). We need to erase this session from the record
     // here, so that the destructor for our session won't check that this
     // session is valid, but we still want it to test the other session.
-    proc.proc.sessions.erase(proc.proc.sessions.begin() + 1);
-}
-
-TEST_P(BinderRpc, Callbacks) {
-    const static std::string kTestString = "good afternoon!";
-
-    for (bool callIsOneway : {true, false}) {
-        for (bool callbackIsOneway : {true, false}) {
-            for (bool delayed : {true, false}) {
-                if (clientOrServerSingleThreaded() &&
-                    (callIsOneway || callbackIsOneway || delayed)) {
-                    // we have no incoming connections to receive the callback
-                    continue;
-                }
-
-                size_t numIncomingConnections = clientOrServerSingleThreaded() ? 0 : 1;
-                auto proc = createRpcTestSocketServerProcess(
-                        {.numThreads = 1,
-                         .numSessions = 1,
-                         .numIncomingConnections = numIncomingConnections});
-                auto cb = sp<MyBinderRpcCallback>::make();
-
-                if (callIsOneway) {
-                    EXPECT_OK(proc.rootIface->doCallbackAsync(cb, callbackIsOneway, delayed,
-                                                              kTestString));
-                } else {
-                    EXPECT_OK(
-                            proc.rootIface->doCallback(cb, callbackIsOneway, delayed, kTestString));
-                }
-
-                // if both transactions are synchronous and the response is sent back on the
-                // same thread, everything should have happened in a nested call. Otherwise,
-                // the callback will be processed on another thread.
-                if (callIsOneway || callbackIsOneway || delayed) {
-                    using std::literals::chrono_literals::operator""s;
-                    RpcMutexUniqueLock _l(cb->mMutex);
-                    cb->mCv.wait_for(_l, 1s, [&] { return !cb->mValues.empty(); });
-                }
-
-                EXPECT_EQ(cb->mValues.size(), 1)
-                        << "callIsOneway: " << callIsOneway
-                        << " callbackIsOneway: " << callbackIsOneway << " delayed: " << delayed;
-                if (cb->mValues.empty()) continue;
-                EXPECT_EQ(cb->mValues.at(0), kTestString)
-                        << "callIsOneway: " << callIsOneway
-                        << " callbackIsOneway: " << callbackIsOneway << " delayed: " << delayed;
-
-                // since we are severing the connection, we need to go ahead and
-                // tell the server to shutdown and exit so that waitpid won't hang
-                if (auto status = proc.rootIface->scheduleShutdown(); !status.isOk()) {
-                    EXPECT_EQ(DEAD_OBJECT, status.transactionError()) << status;
-                }
-
-                // since this session has an incoming connection w/ a threadpool, we
-                // need to manually shut it down
-                EXPECT_TRUE(proc.proc.sessions.at(0).session->shutdownAndWait(true));
-                proc.expectAlreadyShutdown = true;
-            }
-        }
-    }
+    proc.proc->sessions.erase(proc.proc->sessions.begin() + 1);
 }
 
 TEST_P(BinderRpc, SingleDeathRecipient) {
@@ -1177,7 +638,7 @@
     ASSERT_TRUE(dr->mCv.wait_for(lock, 100ms, [&]() { return dr->dead; }));
 
     // need to wait for the session to shutdown so we don't "Leak session"
-    EXPECT_TRUE(proc.proc.sessions.at(0).session->shutdownAndWait(true));
+    EXPECT_TRUE(proc.proc->sessions.at(0).session->shutdownAndWait(true));
     proc.expectAlreadyShutdown = true;
 }
 
@@ -1205,7 +666,7 @@
 
     // Explicitly calling shutDownAndWait will cause the death recipients
     // to be called.
-    EXPECT_TRUE(proc.proc.sessions.at(0).session->shutdownAndWait(true));
+    EXPECT_TRUE(proc.proc->sessions.at(0).session->shutdownAndWait(true));
 
     std::unique_lock<std::mutex> lock(dr->mMtx);
     if (!dr->dead) {
@@ -1213,8 +674,8 @@
     }
     EXPECT_TRUE(dr->dead) << "Failed to receive the death notification.";
 
-    proc.proc.host.terminate();
-    proc.proc.host.setCustomExitStatusCheck([](int wstatus) {
+    proc.proc->terminate();
+    proc.proc->setCustomExitStatusCheck([](int wstatus) {
         EXPECT_TRUE(WIFSIGNALED(wstatus) && WTERMSIG(wstatus) == SIGTERM)
                 << "server process failed incorrectly: " << WaitStatusToString(wstatus);
     });
@@ -1259,18 +720,10 @@
     }
 
     // need to wait for the session to shutdown so we don't "Leak session"
-    EXPECT_TRUE(proc.proc.sessions.at(0).session->shutdownAndWait(true));
+    EXPECT_TRUE(proc.proc->sessions.at(0).session->shutdownAndWait(true));
     proc.expectAlreadyShutdown = true;
 }
 
-TEST_P(BinderRpc, OnewayCallbackWithNoThread) {
-    auto proc = createRpcTestSocketServerProcess({});
-    auto cb = sp<MyBinderRpcCallback>::make();
-
-    Status status = proc.rootIface->doCallback(cb, true /*oneway*/, false /*delayed*/, "anything");
-    EXPECT_EQ(WOULD_BLOCK, status.transactionError());
-}
-
 TEST_P(BinderRpc, Die) {
     for (bool doDeathCleanup : {true, false}) {
         auto proc = createRpcTestSocketServerProcess({});
@@ -1286,7 +739,7 @@
         EXPECT_EQ(DEAD_OBJECT, proc.rootIface->die(doDeathCleanup).transactionError())
                 << "Do death cleanup: " << doDeathCleanup;
 
-        proc.proc.host.setCustomExitStatusCheck([](int wstatus) {
+        proc.proc->setCustomExitStatusCheck([](int wstatus) {
             EXPECT_TRUE(WIFEXITED(wstatus) && WEXITSTATUS(wstatus) == 1)
                     << "server process failed incorrectly: " << WaitStatusToString(wstatus);
         });
@@ -1316,7 +769,7 @@
     // second time! we catch the error :)
     EXPECT_EQ(DEAD_OBJECT, proc.rootIface->useKernelBinderCallingId().transactionError());
 
-    proc.proc.host.setCustomExitStatusCheck([](int wstatus) {
+    proc.proc->setCustomExitStatusCheck([](int wstatus) {
         EXPECT_TRUE(WIFSIGNALED(wstatus) && WTERMSIG(wstatus) == SIGABRT)
                 << "server process failed incorrectly: " << WaitStatusToString(wstatus);
     });
@@ -1330,9 +783,9 @@
                     {RpcSession::FileDescriptorTransportMode::UNIX},
             .allowConnectFailure = true,
     });
-    EXPECT_TRUE(proc.proc.sessions.empty()) << "session connections should have failed";
-    proc.proc.host.terminate();
-    proc.proc.host.setCustomExitStatusCheck([](int wstatus) {
+    EXPECT_TRUE(proc.proc->sessions.empty()) << "session connections should have failed";
+    proc.proc->terminate();
+    proc.proc->setCustomExitStatusCheck([](int wstatus) {
         EXPECT_TRUE(WIFSIGNALED(wstatus) && WTERMSIG(wstatus) == SIGTERM)
                 << "server process failed incorrectly: " << WaitStatusToString(wstatus);
     });
@@ -1346,9 +799,9 @@
                     {RpcSession::FileDescriptorTransportMode::NONE},
             .allowConnectFailure = true,
     });
-    EXPECT_TRUE(proc.proc.sessions.empty()) << "session connections should have failed";
-    proc.proc.host.terminate();
-    proc.proc.host.setCustomExitStatusCheck([](int wstatus) {
+    EXPECT_TRUE(proc.proc->sessions.empty()) << "session connections should have failed";
+    proc.proc->terminate();
+    proc.proc->setCustomExitStatusCheck([](int wstatus) {
         EXPECT_TRUE(WIFSIGNALED(wstatus) && WTERMSIG(wstatus) == SIGTERM)
                 << "server process failed incorrectly: " << WaitStatusToString(wstatus);
     });
@@ -1460,6 +913,33 @@
     EXPECT_EQ(status.transactionError(), BAD_VALUE) << status;
 }
 
+TEST_P(BinderRpc, AppendInvalidFd) {
+    auto proc = createRpcTestSocketServerProcess({
+            .clientFileDescriptorTransportMode = RpcSession::FileDescriptorTransportMode::UNIX,
+            .serverSupportedFileDescriptorTransportModes =
+                    {RpcSession::FileDescriptorTransportMode::UNIX},
+    });
+
+    int badFd = fcntl(STDERR_FILENO, F_DUPFD_CLOEXEC, 0);
+    ASSERT_NE(badFd, -1);
+
+    // Close the file descriptor so it becomes invalid for dup
+    close(badFd);
+
+    Parcel p1;
+    p1.markForBinder(proc.rootBinder);
+    p1.writeInt32(3);
+    EXPECT_EQ(OK, p1.writeFileDescriptor(badFd, false));
+
+    Parcel pRaw;
+    pRaw.markForBinder(proc.rootBinder);
+    EXPECT_EQ(OK, pRaw.appendFrom(&p1, 0, p1.dataSize()));
+
+    pRaw.setDataPosition(0);
+    EXPECT_EQ(3, pRaw.readInt32());
+    ASSERT_EQ(-1, pRaw.readFileDescriptor());
+}
+
 TEST_P(BinderRpc, WorksWithLibbinderNdkPing) {
     if constexpr (!kEnableSharedLibs) {
         GTEST_SKIP() << "Test disabled because Binder was built as a static library";
@@ -1516,16 +996,6 @@
     ASSERT_EQ(beforeFds, countFds()) << (system("ls -l /proc/self/fd/"), "fd leak?");
 }
 
-TEST_P(BinderRpc, AidlDelegatorTest) {
-    auto proc = createRpcTestSocketServerProcess({});
-    auto myDelegator = sp<IBinderRpcTestDelegator>::make(proc.rootIface);
-    ASSERT_NE(nullptr, myDelegator);
-
-    std::string doubled;
-    EXPECT_OK(myDelegator->doubleString("cool ", &doubled));
-    EXPECT_EQ("cool cool ", doubled);
-}
-
 static bool testSupportVsockLoopback() {
     // We don't need to enable TLS to know if vsock is supported.
     unsigned int vsockPort = allocateVsockPort();
@@ -1616,7 +1086,8 @@
 }
 
 static std::vector<SocketType> testSocketTypes(bool hasPreconnected = true) {
-    std::vector<SocketType> ret = {SocketType::UNIX, SocketType::UNIX_BOOTSTRAP, SocketType::INET};
+    std::vector<SocketType> ret = {SocketType::UNIX, SocketType::UNIX_BOOTSTRAP, SocketType::INET,
+                                   SocketType::UNIX_RAW};
 
     if (hasPreconnected) ret.push_back(SocketType::PRECONNECTED);
 
@@ -1858,6 +1329,17 @@
                     mAcceptConnection = &Server::recvmsgServerConnection;
                     mConnectToServer = [this] { return connectToUnixBootstrap(mBootstrapSocket); };
                 } break;
+                case SocketType::UNIX_RAW: {
+                    auto addr = allocateSocketAddress();
+                    auto status = rpcServer->setupRawSocketServer(initUnixSocket(addr));
+                    if (status != OK) {
+                        return AssertionFailure()
+                                << "setupRawSocketServer: " << statusToString(status);
+                    }
+                    mConnectToServer = [addr] {
+                        return connectTo(UnixSocketAddress(addr.c_str()));
+                    };
+                } break;
                 case SocketType::VSOCK: {
                     auto port = allocateVsockPort();
                     auto status = rpcServer->setupVsockServer(port);
diff --git a/libs/binder/tests/binderRpcTestCommon.h b/libs/binder/tests/binderRpcTestCommon.h
index 823bbf6..654e16c 100644
--- a/libs/binder/tests/binderRpcTestCommon.h
+++ b/libs/binder/tests/binderRpcTestCommon.h
@@ -69,6 +69,7 @@
     PRECONNECTED,
     UNIX,
     UNIX_BOOTSTRAP,
+    UNIX_RAW,
     VSOCK,
     INET,
 };
@@ -81,6 +82,8 @@
             return "unix_domain_socket";
         case SocketType::UNIX_BOOTSTRAP:
             return "unix_domain_socket_bootstrap";
+        case SocketType::UNIX_RAW:
+            return "raw_uds";
         case SocketType::VSOCK:
             return "vm_socket";
         case SocketType::INET:
@@ -91,6 +94,14 @@
     }
 }
 
+static inline size_t epochMillis() {
+    using std::chrono::duration_cast;
+    using std::chrono::milliseconds;
+    using std::chrono::seconds;
+    using std::chrono::system_clock;
+    return duration_cast<milliseconds>(system_clock::now().time_since_epoch()).count();
+}
+
 struct BinderRpcOptions {
     size_t numThreads = 1;
     size_t numSessions = 1;
diff --git a/libs/binder/tests/binderRpcTestFixture.h b/libs/binder/tests/binderRpcTestFixture.h
new file mode 100644
index 0000000..5a78782
--- /dev/null
+++ b/libs/binder/tests/binderRpcTestFixture.h
@@ -0,0 +1,139 @@
+/*
+ * 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
+
+#include <gtest/gtest.h>
+
+#include "binderRpcTestCommon.h"
+
+#define EXPECT_OK(status)                        \
+    do {                                         \
+        android::binder::Status stat = (status); \
+        EXPECT_TRUE(stat.isOk()) << stat;        \
+    } while (false)
+
+namespace android {
+
+// Abstract base class with a virtual destructor that handles the
+// ownership of a process session for BinderRpcTestSession below
+class ProcessSession {
+public:
+    struct SessionInfo {
+        sp<RpcSession> session;
+        sp<IBinder> root;
+    };
+
+    // client session objects associated with other process
+    // each one represents a separate session
+    std::vector<SessionInfo> sessions;
+
+    virtual ~ProcessSession() = 0;
+
+    // If the process exits with a status, run the given callback on that value.
+    virtual void setCustomExitStatusCheck(std::function<void(int wstatus)> f) = 0;
+
+    // Kill the process. Avoid if possible. Shutdown gracefully via an RPC instead.
+    virtual void terminate() = 0;
+};
+
+// Process session where the process hosts IBinderRpcTest, the server used
+// for most testing here
+struct BinderRpcTestProcessSession {
+    std::unique_ptr<ProcessSession> proc;
+
+    // pre-fetched root object (for first session)
+    sp<IBinder> rootBinder;
+
+    // pre-casted root object (for first session)
+    sp<IBinderRpcTest> rootIface;
+
+    // whether session should be invalidated by end of run
+    bool expectAlreadyShutdown = false;
+
+    BinderRpcTestProcessSession(BinderRpcTestProcessSession&&) = default;
+    ~BinderRpcTestProcessSession() {
+        if (!expectAlreadyShutdown) {
+            EXPECT_NE(nullptr, rootIface);
+            if (rootIface == nullptr) return;
+
+            std::vector<int32_t> remoteCounts;
+            // calling over any sessions counts across all sessions
+            EXPECT_OK(rootIface->countBinders(&remoteCounts));
+            EXPECT_EQ(remoteCounts.size(), proc->sessions.size());
+            for (auto remoteCount : remoteCounts) {
+                EXPECT_EQ(remoteCount, 1);
+            }
+
+            // even though it is on another thread, shutdown races with
+            // the transaction reply being written
+            if (auto status = rootIface->scheduleShutdown(); !status.isOk()) {
+                EXPECT_EQ(DEAD_OBJECT, status.transactionError()) << status;
+            }
+        }
+
+        rootIface = nullptr;
+        rootBinder = nullptr;
+    }
+};
+
+class BinderRpc : public ::testing::TestWithParam<
+                          std::tuple<SocketType, RpcSecurity, uint32_t, uint32_t, bool, bool>> {
+public:
+    SocketType socketType() const { return std::get<0>(GetParam()); }
+    RpcSecurity rpcSecurity() const { return std::get<1>(GetParam()); }
+    uint32_t clientVersion() const { return std::get<2>(GetParam()); }
+    uint32_t serverVersion() const { return std::get<3>(GetParam()); }
+    bool serverSingleThreaded() const { return std::get<4>(GetParam()); }
+    bool noKernel() const { return std::get<5>(GetParam()); }
+
+    bool clientOrServerSingleThreaded() const {
+        return !kEnableRpcThreads || serverSingleThreaded();
+    }
+
+    // Whether the test params support sending FDs in parcels.
+    bool supportsFdTransport() const {
+        return clientVersion() >= 1 && serverVersion() >= 1 && rpcSecurity() != RpcSecurity::TLS &&
+                (socketType() == SocketType::PRECONNECTED || socketType() == SocketType::UNIX ||
+                 socketType() == SocketType::UNIX_BOOTSTRAP ||
+                 socketType() == SocketType::UNIX_RAW);
+    }
+
+    void SetUp() override {
+        if (socketType() == SocketType::UNIX_BOOTSTRAP && rpcSecurity() == RpcSecurity::TLS) {
+            GTEST_SKIP() << "Unix bootstrap not supported over a TLS transport";
+        }
+    }
+
+    BinderRpcTestProcessSession createRpcTestSocketServerProcess(const BinderRpcOptions& options) {
+        BinderRpcTestProcessSession ret{
+                .proc = createRpcTestSocketServerProcessEtc(options),
+        };
+
+        ret.rootBinder = ret.proc->sessions.empty() ? nullptr : ret.proc->sessions.at(0).root;
+        ret.rootIface = interface_cast<IBinderRpcTest>(ret.rootBinder);
+
+        return ret;
+    }
+
+    static std::string PrintParamInfo(const testing::TestParamInfo<ParamType>& info);
+
+protected:
+    std::unique_ptr<ProcessSession> createRpcTestSocketServerProcessEtc(
+            const BinderRpcOptions& options);
+};
+
+} // namespace android
diff --git a/libs/binder/tests/binderRpcTestService.cpp b/libs/binder/tests/binderRpcTestService.cpp
index a922b21..995e761 100644
--- a/libs/binder/tests/binderRpcTestService.cpp
+++ b/libs/binder/tests/binderRpcTestService.cpp
@@ -42,7 +42,7 @@
     server->setSupportedFileDescriptorTransportModes(serverSupportedFileDescriptorTransportModes);
 
     unsigned int outPort = 0;
-    base::unique_fd unixBootstrapFd(serverConfig.unixBootstrapFd);
+    base::unique_fd socketFd(serverConfig.socketFd);
 
     switch (socketType) {
         case SocketType::PRECONNECTED:
@@ -52,7 +52,10 @@
                     << serverConfig.addr;
             break;
         case SocketType::UNIX_BOOTSTRAP:
-            CHECK_EQ(OK, server->setupUnixDomainSocketBootstrapServer(std::move(unixBootstrapFd)));
+            CHECK_EQ(OK, server->setupUnixDomainSocketBootstrapServer(std::move(socketFd)));
+            break;
+        case SocketType::UNIX_RAW:
+            CHECK_EQ(OK, server->setupRawSocketServer(std::move(socketFd)));
             break;
         case SocketType::VSOCK:
             CHECK_EQ(OK, server->setupVsockServer(serverConfig.vsockPort));
diff --git a/libs/binder/tests/binderRpcUniversalTests.cpp b/libs/binder/tests/binderRpcUniversalTests.cpp
new file mode 100644
index 0000000..f960442
--- /dev/null
+++ b/libs/binder/tests/binderRpcUniversalTests.cpp
@@ -0,0 +1,513 @@
+/*
+ * 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 <chrono>
+#include <cstdlib>
+#include <type_traits>
+
+#include "binderRpcTestCommon.h"
+#include "binderRpcTestFixture.h"
+
+using namespace std::chrono_literals;
+using namespace std::placeholders;
+using testing::AssertionFailure;
+using testing::AssertionResult;
+using testing::AssertionSuccess;
+
+namespace android {
+
+static_assert(RPC_WIRE_PROTOCOL_VERSION + 1 == RPC_WIRE_PROTOCOL_VERSION_NEXT ||
+              RPC_WIRE_PROTOCOL_VERSION == RPC_WIRE_PROTOCOL_VERSION_EXPERIMENTAL);
+
+TEST(BinderRpcParcel, EntireParcelFormatted) {
+    Parcel p;
+    p.writeInt32(3);
+
+    EXPECT_DEATH_IF_SUPPORTED(p.markForBinder(sp<BBinder>::make()),
+                              "format must be set before data is written");
+}
+
+TEST(BinderRpc, CannotUseNextWireVersion) {
+    auto session = RpcSession::make();
+    EXPECT_FALSE(session->setProtocolVersion(RPC_WIRE_PROTOCOL_VERSION_NEXT));
+    EXPECT_FALSE(session->setProtocolVersion(RPC_WIRE_PROTOCOL_VERSION_NEXT + 1));
+    EXPECT_FALSE(session->setProtocolVersion(RPC_WIRE_PROTOCOL_VERSION_NEXT + 2));
+    EXPECT_FALSE(session->setProtocolVersion(RPC_WIRE_PROTOCOL_VERSION_NEXT + 15));
+}
+
+TEST(BinderRpc, CanUseExperimentalWireVersion) {
+    auto session = RpcSession::make();
+    EXPECT_TRUE(session->setProtocolVersion(RPC_WIRE_PROTOCOL_VERSION_EXPERIMENTAL));
+}
+
+TEST_P(BinderRpc, Ping) {
+    auto proc = createRpcTestSocketServerProcess({});
+    ASSERT_NE(proc.rootBinder, nullptr);
+    EXPECT_EQ(OK, proc.rootBinder->pingBinder());
+}
+
+TEST_P(BinderRpc, GetInterfaceDescriptor) {
+    auto proc = createRpcTestSocketServerProcess({});
+    ASSERT_NE(proc.rootBinder, nullptr);
+    EXPECT_EQ(IBinderRpcTest::descriptor, proc.rootBinder->getInterfaceDescriptor());
+}
+
+TEST_P(BinderRpc, MultipleSessions) {
+    if (serverSingleThreaded()) {
+        // Tests with multiple sessions require a multi-threaded service,
+        // but work fine on a single-threaded client
+        GTEST_SKIP() << "This test requires a multi-threaded service";
+    }
+
+    auto proc = createRpcTestSocketServerProcess({.numThreads = 1, .numSessions = 5});
+    for (auto session : proc.proc->sessions) {
+        ASSERT_NE(nullptr, session.root);
+        EXPECT_EQ(OK, session.root->pingBinder());
+    }
+}
+
+TEST_P(BinderRpc, SeparateRootObject) {
+    if (serverSingleThreaded()) {
+        GTEST_SKIP() << "This test requires a multi-threaded service";
+    }
+
+    SocketType type = std::get<0>(GetParam());
+    if (type == SocketType::PRECONNECTED || type == SocketType::UNIX ||
+        type == SocketType::UNIX_BOOTSTRAP || type == SocketType::UNIX_RAW) {
+        // we can't get port numbers for unix sockets
+        return;
+    }
+
+    auto proc = createRpcTestSocketServerProcess({.numSessions = 2});
+
+    int port1 = 0;
+    EXPECT_OK(proc.rootIface->getClientPort(&port1));
+
+    sp<IBinderRpcTest> rootIface2 = interface_cast<IBinderRpcTest>(proc.proc->sessions.at(1).root);
+    int port2;
+    EXPECT_OK(rootIface2->getClientPort(&port2));
+
+    // we should have a different IBinderRpcTest object created for each
+    // session, because we use setPerSessionRootObject
+    EXPECT_NE(port1, port2);
+}
+
+TEST_P(BinderRpc, TransactionsMustBeMarkedRpc) {
+    auto proc = createRpcTestSocketServerProcess({});
+    Parcel data;
+    Parcel reply;
+    EXPECT_EQ(BAD_TYPE, proc.rootBinder->transact(IBinder::PING_TRANSACTION, data, &reply, 0));
+}
+
+TEST_P(BinderRpc, AppendSeparateFormats) {
+    auto proc1 = createRpcTestSocketServerProcess({});
+    auto proc2 = createRpcTestSocketServerProcess({});
+
+    Parcel pRaw;
+
+    Parcel p1;
+    p1.markForBinder(proc1.rootBinder);
+    p1.writeInt32(3);
+
+    EXPECT_EQ(BAD_TYPE, p1.appendFrom(&pRaw, 0, pRaw.dataSize()));
+    EXPECT_EQ(BAD_TYPE, pRaw.appendFrom(&p1, 0, p1.dataSize()));
+
+    Parcel p2;
+    p2.markForBinder(proc2.rootBinder);
+    p2.writeInt32(7);
+
+    EXPECT_EQ(BAD_TYPE, p1.appendFrom(&p2, 0, p2.dataSize()));
+    EXPECT_EQ(BAD_TYPE, p2.appendFrom(&p1, 0, p1.dataSize()));
+}
+
+TEST_P(BinderRpc, UnknownTransaction) {
+    auto proc = createRpcTestSocketServerProcess({});
+    Parcel data;
+    data.markForBinder(proc.rootBinder);
+    Parcel reply;
+    EXPECT_EQ(UNKNOWN_TRANSACTION, proc.rootBinder->transact(1337, data, &reply, 0));
+}
+
+TEST_P(BinderRpc, SendSomethingOneway) {
+    auto proc = createRpcTestSocketServerProcess({});
+    EXPECT_OK(proc.rootIface->sendString("asdf"));
+}
+
+TEST_P(BinderRpc, SendAndGetResultBack) {
+    auto proc = createRpcTestSocketServerProcess({});
+    std::string doubled;
+    EXPECT_OK(proc.rootIface->doubleString("cool ", &doubled));
+    EXPECT_EQ("cool cool ", doubled);
+}
+
+TEST_P(BinderRpc, SendAndGetResultBackBig) {
+    auto proc = createRpcTestSocketServerProcess({});
+    std::string single = std::string(1024, 'a');
+    std::string doubled;
+    EXPECT_OK(proc.rootIface->doubleString(single, &doubled));
+    EXPECT_EQ(single + single, doubled);
+}
+
+TEST_P(BinderRpc, InvalidNullBinderReturn) {
+    auto proc = createRpcTestSocketServerProcess({});
+
+    sp<IBinder> outBinder;
+    EXPECT_EQ(proc.rootIface->getNullBinder(&outBinder).transactionError(), UNEXPECTED_NULL);
+}
+
+TEST_P(BinderRpc, CallMeBack) {
+    auto proc = createRpcTestSocketServerProcess({});
+
+    int32_t pingResult;
+    EXPECT_OK(proc.rootIface->pingMe(new MyBinderRpcSession("foo"), &pingResult));
+    EXPECT_EQ(OK, pingResult);
+
+    EXPECT_EQ(0, MyBinderRpcSession::gNum);
+}
+
+TEST_P(BinderRpc, RepeatBinder) {
+    auto proc = createRpcTestSocketServerProcess({});
+
+    sp<IBinder> inBinder = new MyBinderRpcSession("foo");
+    sp<IBinder> outBinder;
+    EXPECT_OK(proc.rootIface->repeatBinder(inBinder, &outBinder));
+    EXPECT_EQ(inBinder, outBinder);
+
+    wp<IBinder> weak = inBinder;
+    inBinder = nullptr;
+    outBinder = nullptr;
+
+    // Force reading a reply, to process any pending dec refs from the other
+    // process (the other process will process dec refs there before processing
+    // the ping here).
+    EXPECT_EQ(OK, proc.rootBinder->pingBinder());
+
+    EXPECT_EQ(nullptr, weak.promote());
+
+    EXPECT_EQ(0, MyBinderRpcSession::gNum);
+}
+
+TEST_P(BinderRpc, RepeatTheirBinder) {
+    auto proc = createRpcTestSocketServerProcess({});
+
+    sp<IBinderRpcSession> session;
+    EXPECT_OK(proc.rootIface->openSession("aoeu", &session));
+
+    sp<IBinder> inBinder = IInterface::asBinder(session);
+    sp<IBinder> outBinder;
+    EXPECT_OK(proc.rootIface->repeatBinder(inBinder, &outBinder));
+    EXPECT_EQ(inBinder, outBinder);
+
+    wp<IBinder> weak = inBinder;
+    session = nullptr;
+    inBinder = nullptr;
+    outBinder = nullptr;
+
+    // Force reading a reply, to process any pending dec refs from the other
+    // process (the other process will process dec refs there before processing
+    // the ping here).
+    EXPECT_EQ(OK, proc.rootBinder->pingBinder());
+
+    EXPECT_EQ(nullptr, weak.promote());
+}
+
+TEST_P(BinderRpc, RepeatBinderNull) {
+    auto proc = createRpcTestSocketServerProcess({});
+
+    sp<IBinder> outBinder;
+    EXPECT_OK(proc.rootIface->repeatBinder(nullptr, &outBinder));
+    EXPECT_EQ(nullptr, outBinder);
+}
+
+TEST_P(BinderRpc, HoldBinder) {
+    auto proc = createRpcTestSocketServerProcess({});
+
+    IBinder* ptr = nullptr;
+    {
+        sp<IBinder> binder = new BBinder();
+        ptr = binder.get();
+        EXPECT_OK(proc.rootIface->holdBinder(binder));
+    }
+
+    sp<IBinder> held;
+    EXPECT_OK(proc.rootIface->getHeldBinder(&held));
+
+    EXPECT_EQ(held.get(), ptr);
+
+    // stop holding binder, because we test to make sure references are cleaned
+    // up
+    EXPECT_OK(proc.rootIface->holdBinder(nullptr));
+    // and flush ref counts
+    EXPECT_EQ(OK, proc.rootBinder->pingBinder());
+}
+
+// START TESTS FOR LIMITATIONS OF SOCKET BINDER
+// These are behavioral differences form regular binder, where certain usecases
+// aren't supported.
+
+TEST_P(BinderRpc, CannotMixBindersBetweenUnrelatedSocketSessions) {
+    auto proc1 = createRpcTestSocketServerProcess({});
+    auto proc2 = createRpcTestSocketServerProcess({});
+
+    sp<IBinder> outBinder;
+    EXPECT_EQ(INVALID_OPERATION,
+              proc1.rootIface->repeatBinder(proc2.rootBinder, &outBinder).transactionError());
+}
+
+TEST_P(BinderRpc, CannotMixBindersBetweenTwoSessionsToTheSameServer) {
+    if (serverSingleThreaded()) {
+        GTEST_SKIP() << "This test requires a multi-threaded service";
+    }
+
+    auto proc = createRpcTestSocketServerProcess({.numThreads = 1, .numSessions = 2});
+
+    sp<IBinder> outBinder;
+    EXPECT_EQ(INVALID_OPERATION,
+              proc.rootIface->repeatBinder(proc.proc->sessions.at(1).root, &outBinder)
+                      .transactionError());
+}
+
+TEST_P(BinderRpc, CannotSendRegularBinderOverSocketBinder) {
+    if (!kEnableKernelIpc || noKernel()) {
+        GTEST_SKIP() << "Test disabled because Binder kernel driver was disabled "
+                        "at build time.";
+    }
+
+    auto proc = createRpcTestSocketServerProcess({});
+
+    sp<IBinder> someRealBinder = IInterface::asBinder(defaultServiceManager());
+    sp<IBinder> outBinder;
+    EXPECT_EQ(INVALID_OPERATION,
+              proc.rootIface->repeatBinder(someRealBinder, &outBinder).transactionError());
+}
+
+TEST_P(BinderRpc, CannotSendSocketBinderOverRegularBinder) {
+    if (!kEnableKernelIpc || noKernel()) {
+        GTEST_SKIP() << "Test disabled because Binder kernel driver was disabled "
+                        "at build time.";
+    }
+
+    auto proc = createRpcTestSocketServerProcess({});
+
+    // for historical reasons, IServiceManager interface only returns the
+    // exception code
+    EXPECT_EQ(binder::Status::EX_TRANSACTION_FAILED,
+              defaultServiceManager()->addService(String16("not_suspicious"), proc.rootBinder));
+}
+
+// END TESTS FOR LIMITATIONS OF SOCKET BINDER
+
+TEST_P(BinderRpc, RepeatRootObject) {
+    auto proc = createRpcTestSocketServerProcess({});
+
+    sp<IBinder> outBinder;
+    EXPECT_OK(proc.rootIface->repeatBinder(proc.rootBinder, &outBinder));
+    EXPECT_EQ(proc.rootBinder, outBinder);
+}
+
+TEST_P(BinderRpc, NestedTransactions) {
+    auto proc = createRpcTestSocketServerProcess({
+            // Enable FD support because it uses more stack space and so represents
+            // something closer to a worst case scenario.
+            .clientFileDescriptorTransportMode = RpcSession::FileDescriptorTransportMode::UNIX,
+            .serverSupportedFileDescriptorTransportModes =
+                    {RpcSession::FileDescriptorTransportMode::UNIX},
+    });
+
+    auto nastyNester = sp<MyBinderRpcTest>::make();
+    EXPECT_OK(proc.rootIface->nestMe(nastyNester, 10));
+
+    wp<IBinder> weak = nastyNester;
+    nastyNester = nullptr;
+    EXPECT_EQ(nullptr, weak.promote());
+}
+
+TEST_P(BinderRpc, SameBinderEquality) {
+    auto proc = createRpcTestSocketServerProcess({});
+
+    sp<IBinder> a;
+    EXPECT_OK(proc.rootIface->alwaysGiveMeTheSameBinder(&a));
+
+    sp<IBinder> b;
+    EXPECT_OK(proc.rootIface->alwaysGiveMeTheSameBinder(&b));
+
+    EXPECT_EQ(a, b);
+}
+
+TEST_P(BinderRpc, SameBinderEqualityWeak) {
+    auto proc = createRpcTestSocketServerProcess({});
+
+    sp<IBinder> a;
+    EXPECT_OK(proc.rootIface->alwaysGiveMeTheSameBinder(&a));
+    wp<IBinder> weak = a;
+    a = nullptr;
+
+    sp<IBinder> b;
+    EXPECT_OK(proc.rootIface->alwaysGiveMeTheSameBinder(&b));
+
+    // this is the wrong behavior, since BpBinder
+    // doesn't implement onIncStrongAttempted
+    // but make sure there is no crash
+    EXPECT_EQ(nullptr, weak.promote());
+
+    GTEST_SKIP() << "Weak binders aren't currently re-promotable for RPC binder.";
+
+    // In order to fix this:
+    // - need to have incStrongAttempted reflected across IPC boundary (wait for
+    //   response to promote - round trip...)
+    // - sendOnLastWeakRef, to delete entries out of RpcState table
+    EXPECT_EQ(b, weak.promote());
+}
+
+#define expectSessions(expected, iface)                   \
+    do {                                                  \
+        int session;                                      \
+        EXPECT_OK((iface)->getNumOpenSessions(&session)); \
+        EXPECT_EQ(expected, session);                     \
+    } while (false)
+
+TEST_P(BinderRpc, SingleSession) {
+    auto proc = createRpcTestSocketServerProcess({});
+
+    sp<IBinderRpcSession> session;
+    EXPECT_OK(proc.rootIface->openSession("aoeu", &session));
+    std::string out;
+    EXPECT_OK(session->getName(&out));
+    EXPECT_EQ("aoeu", out);
+
+    expectSessions(1, proc.rootIface);
+    session = nullptr;
+    expectSessions(0, proc.rootIface);
+}
+
+TEST_P(BinderRpc, ManySessions) {
+    auto proc = createRpcTestSocketServerProcess({});
+
+    std::vector<sp<IBinderRpcSession>> sessions;
+
+    for (size_t i = 0; i < 15; i++) {
+        expectSessions(i, proc.rootIface);
+        sp<IBinderRpcSession> session;
+        EXPECT_OK(proc.rootIface->openSession(std::to_string(i), &session));
+        sessions.push_back(session);
+    }
+    expectSessions(sessions.size(), proc.rootIface);
+    for (size_t i = 0; i < sessions.size(); i++) {
+        std::string out;
+        EXPECT_OK(sessions.at(i)->getName(&out));
+        EXPECT_EQ(std::to_string(i), out);
+    }
+    expectSessions(sessions.size(), proc.rootIface);
+
+    while (!sessions.empty()) {
+        sessions.pop_back();
+        expectSessions(sessions.size(), proc.rootIface);
+    }
+    expectSessions(0, proc.rootIface);
+}
+
+TEST_P(BinderRpc, OnewayCallDoesNotWait) {
+    constexpr size_t kReallyLongTimeMs = 100;
+    constexpr size_t kSleepMs = kReallyLongTimeMs * 5;
+
+    auto proc = createRpcTestSocketServerProcess({});
+
+    size_t epochMsBefore = epochMillis();
+
+    EXPECT_OK(proc.rootIface->sleepMsAsync(kSleepMs));
+
+    size_t epochMsAfter = epochMillis();
+    EXPECT_LT(epochMsAfter, epochMsBefore + kReallyLongTimeMs);
+}
+
+TEST_P(BinderRpc, Callbacks) {
+    const static std::string kTestString = "good afternoon!";
+
+    for (bool callIsOneway : {true, false}) {
+        for (bool callbackIsOneway : {true, false}) {
+            for (bool delayed : {true, false}) {
+                if (clientOrServerSingleThreaded() &&
+                    (callIsOneway || callbackIsOneway || delayed)) {
+                    // we have no incoming connections to receive the callback
+                    continue;
+                }
+
+                size_t numIncomingConnections = clientOrServerSingleThreaded() ? 0 : 1;
+                auto proc = createRpcTestSocketServerProcess(
+                        {.numThreads = 1,
+                         .numSessions = 1,
+                         .numIncomingConnections = numIncomingConnections});
+                auto cb = sp<MyBinderRpcCallback>::make();
+
+                if (callIsOneway) {
+                    EXPECT_OK(proc.rootIface->doCallbackAsync(cb, callbackIsOneway, delayed,
+                                                              kTestString));
+                } else {
+                    EXPECT_OK(
+                            proc.rootIface->doCallback(cb, callbackIsOneway, delayed, kTestString));
+                }
+
+                // if both transactions are synchronous and the response is sent back on the
+                // same thread, everything should have happened in a nested call. Otherwise,
+                // the callback will be processed on another thread.
+                if (callIsOneway || callbackIsOneway || delayed) {
+                    using std::literals::chrono_literals::operator""s;
+                    RpcMutexUniqueLock _l(cb->mMutex);
+                    cb->mCv.wait_for(_l, 1s, [&] { return !cb->mValues.empty(); });
+                }
+
+                EXPECT_EQ(cb->mValues.size(), 1)
+                        << "callIsOneway: " << callIsOneway
+                        << " callbackIsOneway: " << callbackIsOneway << " delayed: " << delayed;
+                if (cb->mValues.empty()) continue;
+                EXPECT_EQ(cb->mValues.at(0), kTestString)
+                        << "callIsOneway: " << callIsOneway
+                        << " callbackIsOneway: " << callbackIsOneway << " delayed: " << delayed;
+
+                // since we are severing the connection, we need to go ahead and
+                // tell the server to shutdown and exit so that waitpid won't hang
+                if (auto status = proc.rootIface->scheduleShutdown(); !status.isOk()) {
+                    EXPECT_EQ(DEAD_OBJECT, status.transactionError()) << status;
+                }
+
+                // since this session has an incoming connection w/ a threadpool, we
+                // need to manually shut it down
+                EXPECT_TRUE(proc.proc->sessions.at(0).session->shutdownAndWait(true));
+                proc.expectAlreadyShutdown = true;
+            }
+        }
+    }
+}
+
+TEST_P(BinderRpc, OnewayCallbackWithNoThread) {
+    auto proc = createRpcTestSocketServerProcess({});
+    auto cb = sp<MyBinderRpcCallback>::make();
+
+    Status status = proc.rootIface->doCallback(cb, true /*oneway*/, false /*delayed*/, "anything");
+    EXPECT_EQ(WOULD_BLOCK, status.transactionError());
+}
+
+TEST_P(BinderRpc, AidlDelegatorTest) {
+    auto proc = createRpcTestSocketServerProcess({});
+    auto myDelegator = sp<IBinderRpcTestDelegator>::make(proc.rootIface);
+    ASSERT_NE(nullptr, myDelegator);
+
+    std::string doubled;
+    EXPECT_OK(myDelegator->doubleString("cool ", &doubled));
+    EXPECT_EQ("cool cool ", doubled);
+}
+
+} // namespace android
diff --git a/libs/binder/tests/parcel_fuzzer/Android.bp b/libs/binder/tests/parcel_fuzzer/Android.bp
index 3904e1d..35866ad 100644
--- a/libs/binder/tests/parcel_fuzzer/Android.bp
+++ b/libs/binder/tests/parcel_fuzzer/Android.bp
@@ -12,13 +12,17 @@
     host_supported: true,
     unstable: true,
     srcs: [
-        "EmptyParcelable.aidl",
-        "SingleDataParcelable.aidl",
-        "GenericDataParcelable.aidl",
+        "parcelables/EmptyParcelable.aidl",
+        "parcelables/SingleDataParcelable.aidl",
+        "parcelables/GenericDataParcelable.aidl",
     ],
     backend: {
         java: {
-            enabled: false,
+            enabled: true,
+            platform_apis: true,
+        },
+        rust: {
+            enabled: true,
         },
     },
 }
diff --git a/libs/binder/tests/parcel_fuzzer/EmptyParcelable.aidl b/libs/binder/tests/parcel_fuzzer/EmptyParcelable.aidl
deleted file mode 100644
index 96d6223..0000000
--- a/libs/binder/tests/parcel_fuzzer/EmptyParcelable.aidl
+++ /dev/null
@@ -1,18 +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.
- */
-
-parcelable EmptyParcelable{
-}
\ No newline at end of file
diff --git a/libs/binder/tests/parcel_fuzzer/binder.cpp b/libs/binder/tests/parcel_fuzzer/binder.cpp
index 9dac2c9..768fbe1 100644
--- a/libs/binder/tests/parcel_fuzzer/binder.cpp
+++ b/libs/binder/tests/parcel_fuzzer/binder.cpp
@@ -16,9 +16,9 @@
 #define FUZZ_LOG_TAG "binder"
 
 #include "binder.h"
-#include "EmptyParcelable.h"
-#include "GenericDataParcelable.h"
-#include "SingleDataParcelable.h"
+#include "parcelables/EmptyParcelable.h"
+#include "parcelables/GenericDataParcelable.h"
+#include "parcelables/SingleDataParcelable.h"
 #include "util.h"
 
 #include <android-base/hex.h>
@@ -359,19 +359,19 @@
     },
     [] (const ::android::Parcel& p, FuzzedDataProvider& /*provider*/) {
         FUZZ_LOG() << "about to call readFromParcel() with status for EmptyParcelable";
-        EmptyParcelable emptyParcelable{};
+        parcelables::EmptyParcelable emptyParcelable{};
         status_t status = emptyParcelable.readFromParcel(&p);
         FUZZ_LOG() << " status: " << status;
     },
     [] (const ::android::Parcel& p , FuzzedDataProvider& /*provider*/) {
         FUZZ_LOG() << "about to call readFromParcel() with status for SingleDataParcelable";
-        SingleDataParcelable singleDataParcelable;
+        parcelables::SingleDataParcelable singleDataParcelable;
         status_t status = singleDataParcelable.readFromParcel(&p);
         FUZZ_LOG() <<" status: " << status;
     },
     [] (const ::android::Parcel& p, FuzzedDataProvider& /*provider*/) {
         FUZZ_LOG() << "about to call readFromParcel() with status for GenericDataParcelable";
-        GenericDataParcelable genericDataParcelable;
+        parcelables::GenericDataParcelable genericDataParcelable;
         status_t status = genericDataParcelable.readFromParcel(&p);
         FUZZ_LOG() <<" status: " << status;
     },
diff --git a/libs/binder/tests/parcel_fuzzer/binder_ndk.cpp b/libs/binder/tests/parcel_fuzzer/binder_ndk.cpp
index af773a0..53e7de4 100644
--- a/libs/binder/tests/parcel_fuzzer/binder_ndk.cpp
+++ b/libs/binder/tests/parcel_fuzzer/binder_ndk.cpp
@@ -16,9 +16,9 @@
 #define FUZZ_LOG_TAG "binder_ndk"
 
 #include "binder_ndk.h"
-#include "aidl/EmptyParcelable.h"
-#include "aidl/GenericDataParcelable.h"
-#include "aidl/SingleDataParcelable.h"
+#include "aidl/parcelables/EmptyParcelable.h"
+#include "aidl/parcelables/GenericDataParcelable.h"
+#include "aidl/parcelables/SingleDataParcelable.h"
 
 #include <android/binder_parcel_utils.h>
 #include <android/binder_parcelable_utils.h>
@@ -183,19 +183,19 @@
 
         [](const NdkParcelAdapter& p, FuzzedDataProvider& /*provider*/) {
             FUZZ_LOG() << "about to read parcel using readFromParcel for EmptyParcelable";
-            aidl::EmptyParcelable emptyParcelable;
+            aidl::parcelables::EmptyParcelable emptyParcelable;
             binder_status_t status = emptyParcelable.readFromParcel(p.aParcel());
             FUZZ_LOG() << "status: " << status;
         },
         [](const NdkParcelAdapter& p, FuzzedDataProvider& /*provider*/) {
             FUZZ_LOG() << "about to read parcel using readFromParcel for SingleDataParcelable";
-            aidl::SingleDataParcelable singleDataParcelable;
+            aidl::parcelables::SingleDataParcelable singleDataParcelable;
             binder_status_t status = singleDataParcelable.readFromParcel(p.aParcel());
             FUZZ_LOG() << "status: " << status;
         },
         [](const NdkParcelAdapter& p, FuzzedDataProvider& /*provider*/) {
             FUZZ_LOG() << "about to read parcel using readFromParcel for GenericDataParcelable";
-            aidl::GenericDataParcelable genericDataParcelable;
+            aidl::parcelables::GenericDataParcelable genericDataParcelable;
             binder_status_t status = genericDataParcelable.readFromParcel(p.aParcel());
             FUZZ_LOG() << "status: " << status;
         },
diff --git a/libs/binder/tests/parcel_fuzzer/libbinder_driver.cpp b/libs/binder/tests/parcel_fuzzer/libbinder_driver.cpp
index 25f6096..86461c8 100644
--- a/libs/binder/tests/parcel_fuzzer/libbinder_driver.cpp
+++ b/libs/binder/tests/parcel_fuzzer/libbinder_driver.cpp
@@ -18,6 +18,7 @@
 #include <fuzzbinder/random_parcel.h>
 
 #include <android-base/logging.h>
+#include <binder/IPCThreadState.h>
 #include <binder/ProcessState.h>
 
 namespace android {
@@ -30,10 +31,17 @@
             .extraFds = {},
     };
 
+    if (provider.ConsumeBool()) {
+        // set calling uid
+        IPCThreadState::self()->restoreCallingIdentity(provider.ConsumeIntegral<int64_t>());
+    }
+
     while (provider.remaining_bytes() > 0) {
         uint32_t code = provider.ConsumeIntegral<uint32_t>();
         uint32_t flags = provider.ConsumeIntegral<uint32_t>();
         Parcel data;
+        // for increased fuzz coverage
+        data.setEnforceNoDataAvail(provider.ConsumeBool());
 
         sp<IBinder> target = options.extraBinders.at(
                 provider.ConsumeIntegralInRange<size_t>(0, options.extraBinders.size() - 1));
@@ -50,6 +58,8 @@
         fillRandomParcel(&data, FuzzedDataProvider(subData.data(), subData.size()), &options);
 
         Parcel reply;
+        // for increased fuzz coverage
+        reply.setEnforceNoDataAvail(provider.ConsumeBool());
         (void)target->transact(code, data, &reply, flags);
 
         // feed back in binders and fds that are returned from the service, so that
diff --git a/libs/binder/tests/parcel_fuzzer/libbinder_ndk_driver.cpp b/libs/binder/tests/parcel_fuzzer/libbinder_ndk_driver.cpp
index 462ef9a..a1fb701 100644
--- a/libs/binder/tests/parcel_fuzzer/libbinder_ndk_driver.cpp
+++ b/libs/binder/tests/parcel_fuzzer/libbinder_ndk_driver.cpp
@@ -29,3 +29,12 @@
 }
 
 } // namespace android
+
+extern "C" {
+// This API is used by fuzzers to automatically fuzz aidl services
+void fuzzRustService(void* binder, const uint8_t* data, size_t len) {
+    AIBinder* aiBinder = static_cast<AIBinder*>(binder);
+    FuzzedDataProvider provider(data, len);
+    android::fuzzService(aiBinder, std::move(provider));
+}
+} // extern "C"
diff --git a/libs/binder/tests/parcel_fuzzer/SingleDataParcelable.aidl b/libs/binder/tests/parcel_fuzzer/parcelables/EmptyParcelable.aidl
similarity index 92%
copy from libs/binder/tests/parcel_fuzzer/SingleDataParcelable.aidl
copy to libs/binder/tests/parcel_fuzzer/parcelables/EmptyParcelable.aidl
index d62891b..1216250 100644
--- a/libs/binder/tests/parcel_fuzzer/SingleDataParcelable.aidl
+++ b/libs/binder/tests/parcel_fuzzer/parcelables/EmptyParcelable.aidl
@@ -13,7 +13,6 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
-parcelable SingleDataParcelable{
-   int data;
+package parcelables;
+parcelable EmptyParcelable {
 }
\ No newline at end of file
diff --git a/libs/binder/tests/parcel_fuzzer/GenericDataParcelable.aidl b/libs/binder/tests/parcel_fuzzer/parcelables/GenericDataParcelable.aidl
similarity index 97%
rename from libs/binder/tests/parcel_fuzzer/GenericDataParcelable.aidl
rename to libs/binder/tests/parcel_fuzzer/parcelables/GenericDataParcelable.aidl
index fc2542b..f1079e9 100644
--- a/libs/binder/tests/parcel_fuzzer/GenericDataParcelable.aidl
+++ b/libs/binder/tests/parcel_fuzzer/parcelables/GenericDataParcelable.aidl
@@ -13,6 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package parcelables;
 
 parcelable GenericDataParcelable {
     int data;
diff --git a/libs/binder/tests/parcel_fuzzer/SingleDataParcelable.aidl b/libs/binder/tests/parcel_fuzzer/parcelables/SingleDataParcelable.aidl
similarity index 96%
rename from libs/binder/tests/parcel_fuzzer/SingleDataParcelable.aidl
rename to libs/binder/tests/parcel_fuzzer/parcelables/SingleDataParcelable.aidl
index d62891b..0187168 100644
--- a/libs/binder/tests/parcel_fuzzer/SingleDataParcelable.aidl
+++ b/libs/binder/tests/parcel_fuzzer/parcelables/SingleDataParcelable.aidl
@@ -13,6 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package parcelables;
 
 parcelable SingleDataParcelable{
    int data;
diff --git a/libs/binder/tests/parcel_fuzzer/random_parcel.cpp b/libs/binder/tests/parcel_fuzzer/random_parcel.cpp
index edc695f..f0beed2 100644
--- a/libs/binder/tests/parcel_fuzzer/random_parcel.cpp
+++ b/libs/binder/tests/parcel_fuzzer/random_parcel.cpp
@@ -73,6 +73,11 @@
                                                                                 1));
                         CHECK(OK == p->writeFileDescriptor(fd.get(), false /*takeOwnership*/));
                     } else {
+                        // b/260119717 - Adding more FDs can eventually lead to FD limit exhaustion
+                        if (options->extraFds.size() > 1000) {
+                            return;
+                        }
+
                         std::vector<base::unique_fd> fds = getRandomFds(&provider);
                         CHECK(OK ==
                               p->writeFileDescriptor(fds.begin()->release(),
diff --git a/libs/binder/tests/parcel_fuzzer/rust_interface/Android.bp b/libs/binder/tests/parcel_fuzzer/rust_interface/Android.bp
new file mode 100644
index 0000000..b48dc27
--- /dev/null
+++ b/libs/binder/tests/parcel_fuzzer/rust_interface/Android.bp
@@ -0,0 +1,24 @@
+package {
+    default_applicable_licenses: ["frameworks_native_license"],
+}
+
+cc_library_static {
+    name: "libbinder_create_parcel",
+    host_supported: true,
+    target: {
+        darwin: {
+            enabled: false,
+        },
+    },
+    srcs: [
+        "RandomParcelWrapper.cpp",
+    ],
+    shared_libs: [
+        "libbase",
+        "libbinder",
+        "libbinder_ndk",
+    ],
+    static_libs: [
+        "libbinder_random_parcel",
+    ],
+}
diff --git a/libs/binder/tests/parcel_fuzzer/rust_interface/RandomParcelWrapper.cpp b/libs/binder/tests/parcel_fuzzer/rust_interface/RandomParcelWrapper.cpp
new file mode 100644
index 0000000..2fb7820
--- /dev/null
+++ b/libs/binder/tests/parcel_fuzzer/rust_interface/RandomParcelWrapper.cpp
@@ -0,0 +1,34 @@
+/*
+ * 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-base/logging.h>
+#include <android/binder_libbinder.h>
+#include <android/binder_parcel.h>
+#include <fuzzbinder/random_parcel.h>
+
+extern "C" {
+
+void createRandomParcel(void* aParcel, const uint8_t* data, size_t len) {
+    CHECK_NE(aParcel, nullptr);
+    AParcel* parcel = static_cast<AParcel*>(aParcel);
+    FuzzedDataProvider provider(data, len);
+    android::RandomParcelOptions options;
+
+    android::Parcel* platformParcel = AParcel_viewPlatformParcel(parcel);
+    fillRandomParcel(platformParcel, std::move(provider), &options);
+}
+
+} // extern "C"
\ No newline at end of file
diff --git a/libs/binder/trusty/OS.cpp b/libs/binder/trusty/OS.cpp
index 397ff41..8ec9823 100644
--- a/libs/binder/trusty/OS.cpp
+++ b/libs/binder/trusty/OS.cpp
@@ -59,14 +59,14 @@
     return RpcTransportCtxFactoryTipcTrusty::make();
 }
 
-int sendMessageOnSocket(
+ssize_t sendMessageOnSocket(
         const RpcTransportFd& /* socket */, iovec* /* iovs */, int /* niovs */,
         const std::vector<std::variant<base::unique_fd, base::borrowed_fd>>* /* ancillaryFds */) {
     errno = ENOTSUP;
     return -1;
 }
 
-int receiveMessageFromSocket(
+ssize_t receiveMessageFromSocket(
         const RpcTransportFd& /* socket */, iovec* /* iovs */, int /* niovs */,
         std::vector<std::variant<base::unique_fd, base::borrowed_fd>>* /* ancillaryFds */) {
     errno = ENOTSUP;
diff --git a/libs/binder/trusty/include/log/log.h b/libs/binder/trusty/include/log/log.h
index bf877a3..de84617 100644
--- a/libs/binder/trusty/include/log/log.h
+++ b/libs/binder/trusty/include/log/log.h
@@ -120,3 +120,9 @@
     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/dumputils/dump_utils.cpp b/libs/dumputils/dump_utils.cpp
index a6585c5..067ce17 100644
--- a/libs/dumputils/dump_utils.cpp
+++ b/libs/dumputils/dump_utils.cpp
@@ -208,5 +208,5 @@
     cmdline = std::string(cmdline.c_str());
 
     return cmdline == "zygote" || cmdline == "zygote64" || cmdline == "usap32" ||
-            cmdline == "usap64";
+            cmdline == "usap64" || cmdline == "webview_zygote";
 }
diff --git a/libs/fakeservicemanager/ServiceManager.cpp b/libs/fakeservicemanager/ServiceManager.cpp
index 6c6d7f3..1109ad8 100644
--- a/libs/fakeservicemanager/ServiceManager.cpp
+++ b/libs/fakeservicemanager/ServiceManager.cpp
@@ -36,6 +36,9 @@
 status_t ServiceManager::addService(const String16& name, const sp<IBinder>& service,
                                 bool /*allowIsolated*/,
                                 int /*dumpsysFlags*/) {
+    if (service == nullptr) {
+        return UNEXPECTED_NULL;
+    }
     mNameToService[name] = service;
     return NO_ERROR;
 }
@@ -78,6 +81,11 @@
     return std::nullopt;
 }
 
+Vector<String16> ServiceManager::getUpdatableNames(const String16& apexName) {
+    (void)apexName;
+    return {};
+}
+
 std::optional<IServiceManager::ConnectionInfo> ServiceManager::getConnectionInfo(
         const String16& name) {
     (void)name;
@@ -98,4 +106,8 @@
     std::vector<IServiceManager::ServiceDebugInfo> ret;
     return ret;
 }
+
+void ServiceManager::clear() {
+    mNameToService.clear();
+}
 }  // namespace android
diff --git a/libs/fakeservicemanager/include/fakeservicemanager/ServiceManager.h b/libs/fakeservicemanager/include/fakeservicemanager/ServiceManager.h
index e0af5d4..ba6bb7d 100644
--- a/libs/fakeservicemanager/include/fakeservicemanager/ServiceManager.h
+++ b/libs/fakeservicemanager/include/fakeservicemanager/ServiceManager.h
@@ -52,6 +52,8 @@
 
     std::optional<String16> updatableViaApex(const String16& name) override;
 
+    Vector<String16> getUpdatableNames(const String16& apexName) override;
+
     std::optional<IServiceManager::ConnectionInfo> getConnectionInfo(const String16& name) override;
 
     status_t registerForNotifications(const String16& name,
@@ -62,6 +64,9 @@
 
     std::vector<IServiceManager::ServiceDebugInfo> getServiceDebugInfo() override;
 
+    // Clear all of the registered services
+    void clear();
+
 private:
     std::map<String16, sp<IBinder>> mNameToService;
 };
diff --git a/libs/fakeservicemanager/test_sm.cpp b/libs/fakeservicemanager/test_sm.cpp
index 71e5abe..8682c1c 100644
--- a/libs/fakeservicemanager/test_sm.cpp
+++ b/libs/fakeservicemanager/test_sm.cpp
@@ -50,6 +50,12 @@
         IServiceManager::DUMP_FLAG_PRIORITY_DEFAULT), OK);
 }
 
+TEST(AddService, SadNullBinder) {
+    auto sm = new ServiceManager();
+    EXPECT_EQ(sm->addService(String16("foo"), nullptr, false /*allowIsolated*/,
+        IServiceManager::DUMP_FLAG_PRIORITY_DEFAULT), android::UNEXPECTED_NULL);
+}
+
 TEST(AddService, HappyOverExistingService) {
     auto sm = new ServiceManager();
     EXPECT_EQ(sm->addService(String16("foo"), getBinder(), false /*allowIsolated*/,
@@ -58,6 +64,15 @@
         IServiceManager::DUMP_FLAG_PRIORITY_DEFAULT), OK);
 }
 
+TEST(AddService, HappyClearAddedService) {
+    auto sm = new ServiceManager();
+    EXPECT_EQ(sm->addService(String16("foo"), getBinder(), false /*allowIsolated*/,
+        IServiceManager::DUMP_FLAG_PRIORITY_DEFAULT), OK);
+    EXPECT_NE(sm->getService(String16("foo")), nullptr);
+    sm->clear();
+    EXPECT_EQ(sm->getService(String16("foo")), nullptr);
+}
+
 TEST(GetService, HappyHappy) {
     auto sm = new ServiceManager();
     sp<IBinder> service = getBinder();
diff --git a/libs/ftl/Android.bp b/libs/ftl/Android.bp
index c1945fd..8e57152 100644
--- a/libs/ftl/Android.bp
+++ b/libs/ftl/Android.bp
@@ -22,7 +22,10 @@
         "flags_test.cpp",
         "future_test.cpp",
         "match_test.cpp",
+        "mixins_test.cpp",
+        "non_null_test.cpp",
         "optional_test.cpp",
+        "shared_mutex_test.cpp",
         "small_map_test.cpp",
         "small_vector_test.cpp",
         "static_vector_test.cpp",
diff --git a/libs/ftl/concat_test.cpp b/libs/ftl/concat_test.cpp
index 8ecb1b2..771f054 100644
--- a/libs/ftl/concat_test.cpp
+++ b/libs/ftl/concat_test.cpp
@@ -28,8 +28,25 @@
   EXPECT_EQ(string.c_str()[string.size()], '\0');
 }
 
+TEST(Concat, Characters) {
+  EXPECT_EQ(ftl::Concat(u'a', ' ', U'b').str(), "97 98");
+}
+
+TEST(Concat, References) {
+  int i[] = {-1, 2};
+  unsigned u = 3;
+  EXPECT_EQ(ftl::Concat(i[0], std::as_const(i[1]), u).str(), "-123");
+
+  const bool b = false;
+  const char c = 'o';
+  EXPECT_EQ(ftl::Concat(b, "tt", c).str(), "falsetto");
+}
+
 namespace {
 
+static_assert(ftl::Concat{true, false, true}.str() == "truefalsetrue");
+static_assert(ftl::Concat{':', '-', ')'}.str() == ":-)");
+
 static_assert(ftl::Concat{"foo"}.str() == "foo");
 static_assert(ftl::Concat{ftl::truncated<3>("foobar")}.str() == "foo");
 
diff --git a/libs/ftl/mixins_test.cpp b/libs/ftl/mixins_test.cpp
new file mode 100644
index 0000000..2c9f9df
--- /dev/null
+++ b/libs/ftl/mixins_test.cpp
@@ -0,0 +1,185 @@
+/*
+ * 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/mixins.h>
+#include <gtest/gtest.h>
+
+#include <chrono>
+#include <functional>
+#include <type_traits>
+#include <utility>
+
+namespace android::test {
+namespace {
+
+// Keep in sync with example usage in header file.
+
+struct Id : ftl::Constructible<Id, std::int32_t>, ftl::Equatable<Id> {
+  using Constructible::Constructible;
+};
+
+static_assert(!std::is_default_constructible_v<Id>);
+
+struct Color : ftl::DefaultConstructible<Color, std::uint8_t>,
+               ftl::Equatable<Color>,
+               ftl::Orderable<Color> {
+  using DefaultConstructible::DefaultConstructible;
+};
+
+static_assert(Color() == Color(0u));
+static_assert(ftl::to_underlying(Color(-1)) == 255u);
+static_assert(Color(1u) < Color(2u));
+
+struct Sequence : ftl::DefaultConstructible<Sequence, std::int8_t, -1>,
+                  ftl::Equatable<Sequence>,
+                  ftl::Orderable<Sequence>,
+                  ftl::Incrementable<Sequence> {
+  using DefaultConstructible::DefaultConstructible;
+};
+
+static_assert(Sequence() == Sequence(-1));
+
+struct Timeout : ftl::DefaultConstructible<Timeout, std::chrono::seconds, 10>,
+                 ftl::Equatable<Timeout>,
+                 ftl::Addable<Timeout> {
+  using DefaultConstructible::DefaultConstructible;
+};
+
+using namespace std::chrono_literals;
+static_assert(Timeout() + Timeout(5s) == Timeout(15s));
+
+// Construction.
+constexpr Id kId{1234};
+constexpr Sequence kSequence;
+
+// Underlying value.
+static_assert(ftl::to_underlying(Id(-42)) == -42);
+static_assert(ftl::to_underlying(kSequence) == -1);
+
+// Casting.
+static_assert(static_cast<std::int32_t>(Id(-1)) == -1);
+static_assert(static_cast<std::int8_t>(kSequence) == -1);
+
+static_assert(!std::is_convertible_v<std::int32_t, Id>);
+static_assert(!std::is_convertible_v<Id, std::int32_t>);
+
+// Equality.
+static_assert(kId == Id(1234));
+static_assert(kId != Id(123));
+static_assert(kSequence == Sequence(-1));
+
+// Ordering.
+static_assert(Sequence(1) < Sequence(2));
+static_assert(Sequence(2) > Sequence(1));
+static_assert(Sequence(3) <= Sequence(4));
+static_assert(Sequence(4) >= Sequence(3));
+static_assert(Sequence(5) <= Sequence(5));
+static_assert(Sequence(6) >= Sequence(6));
+
+// Incrementing.
+template <typename Op, typename T, typename... Ts>
+constexpr auto mutable_op(Op op, T lhs, Ts... rhs) {
+  const T result = op(lhs, rhs...);
+  return std::make_pair(lhs, result);
+}
+
+static_assert(mutable_op([](auto& lhs) { return ++lhs; }, Sequence()) ==
+              std::make_pair(Sequence(0), Sequence(0)));
+
+static_assert(mutable_op([](auto& lhs) { return lhs++; }, Sequence()) ==
+              std::make_pair(Sequence(0), Sequence(-1)));
+
+// Addition.
+
+// `Addable` implies `Incrementable`.
+static_assert(mutable_op([](auto& lhs) { return ++lhs; }, Timeout()) ==
+              std::make_pair(Timeout(11s), Timeout(11s)));
+
+static_assert(mutable_op([](auto& lhs) { return lhs++; }, Timeout()) ==
+              std::make_pair(Timeout(11s), Timeout(10s)));
+
+static_assert(Timeout(5s) + Timeout(6s) == Timeout(11s));
+
+static_assert(mutable_op([](auto& lhs, const auto& rhs) { return lhs += rhs; }, Timeout(7s),
+                         Timeout(8s)) == std::make_pair(Timeout(15s), Timeout(15s)));
+
+// Type safety.
+
+namespace traits {
+
+template <typename, typename = void>
+struct is_incrementable : std::false_type {};
+
+template <typename T>
+struct is_incrementable<T, std::void_t<decltype(++std::declval<T&>())>> : std::true_type {};
+
+template <typename T>
+constexpr bool is_incrementable_v = is_incrementable<T>{};
+
+template <typename, typename, typename, typename = void>
+struct has_binary_op : std::false_type {};
+
+template <typename Op, typename T, typename U>
+struct has_binary_op<Op, T, U, std::void_t<decltype(Op{}(std::declval<T&>(), std::declval<U&>()))>>
+    : std::true_type {};
+
+template <typename T, typename U>
+constexpr bool is_equatable_v =
+    has_binary_op<std::equal_to<void>, T, U>{} && has_binary_op<std::not_equal_to<void>, T, U>{};
+
+template <typename T, typename U>
+constexpr bool is_orderable_v =
+    has_binary_op<std::less<void>, T, U>{} && has_binary_op<std::less_equal<void>, T, U>{} &&
+    has_binary_op<std::greater<void>, T, U>{} && has_binary_op<std::greater_equal<void>, T, U>{};
+
+template <typename T, typename U>
+constexpr bool is_addable_v = has_binary_op<std::plus<void>, T, U>{};
+
+}  // namespace traits
+
+struct Real : ftl::Constructible<Real, float> {
+  using Constructible::Constructible;
+};
+
+static_assert(traits::is_equatable_v<Id, Id>);
+static_assert(!traits::is_equatable_v<Real, Real>);
+static_assert(!traits::is_equatable_v<Id, Color>);
+static_assert(!traits::is_equatable_v<Sequence, Id>);
+static_assert(!traits::is_equatable_v<Id, std::int32_t>);
+static_assert(!traits::is_equatable_v<std::chrono::seconds, Timeout>);
+
+static_assert(traits::is_orderable_v<Color, Color>);
+static_assert(!traits::is_orderable_v<Id, Id>);
+static_assert(!traits::is_orderable_v<Real, Real>);
+static_assert(!traits::is_orderable_v<Color, Sequence>);
+static_assert(!traits::is_orderable_v<Color, std::uint8_t>);
+static_assert(!traits::is_orderable_v<std::chrono::seconds, Timeout>);
+
+static_assert(traits::is_incrementable_v<Sequence>);
+static_assert(traits::is_incrementable_v<Timeout>);
+static_assert(!traits::is_incrementable_v<Id>);
+static_assert(!traits::is_incrementable_v<Color>);
+static_assert(!traits::is_incrementable_v<Real>);
+
+static_assert(traits::is_addable_v<Timeout, Timeout>);
+static_assert(!traits::is_addable_v<Id, Id>);
+static_assert(!traits::is_addable_v<Real, Real>);
+static_assert(!traits::is_addable_v<Sequence, Sequence>);
+static_assert(!traits::is_addable_v<Timeout, Sequence>);
+static_assert(!traits::is_addable_v<Color, Timeout>);
+
+}  // namespace
+}  // namespace android::test
diff --git a/libs/ftl/non_null_test.cpp b/libs/ftl/non_null_test.cpp
new file mode 100644
index 0000000..bd0462b
--- /dev/null
+++ b/libs/ftl/non_null_test.cpp
@@ -0,0 +1,75 @@
+/*
+ * 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/non_null.h>
+#include <gtest/gtest.h>
+
+#include <memory>
+#include <string>
+#include <string_view>
+
+namespace android::test {
+namespace {
+
+void get_length(const ftl::NonNull<std::shared_ptr<std::string>>& string_ptr,
+                ftl::NonNull<std::size_t*> length_ptr) {
+  // No need for `nullptr` checks.
+  *length_ptr = string_ptr->length();
+}
+
+using Pair = std::pair<ftl::NonNull<std::shared_ptr<int>>, std::shared_ptr<int>>;
+
+Pair dupe_if(ftl::NonNull<std::unique_ptr<int>> non_null_ptr, bool condition) {
+  // Move the underlying pointer out, so `non_null_ptr` must not be accessed after this point.
+  auto unique_ptr = std::move(non_null_ptr).take();
+
+  auto non_null_shared_ptr = ftl::as_non_null(std::shared_ptr<int>(std::move(unique_ptr)));
+  auto nullable_shared_ptr = condition ? non_null_shared_ptr.get() : nullptr;
+
+  return {std::move(non_null_shared_ptr), std::move(nullable_shared_ptr)};
+}
+
+}  // namespace
+
+// Keep in sync with example usage in header file.
+TEST(NonNull, Example) {
+  const auto string_ptr = ftl::as_non_null(std::make_shared<std::string>("android"));
+  std::size_t size;
+  get_length(string_ptr, ftl::as_non_null(&size));
+  EXPECT_EQ(size, 7u);
+
+  auto ptr = ftl::as_non_null(std::make_unique<int>(42));
+  const auto [ptr1, ptr2] = dupe_if(std::move(ptr), true);
+  EXPECT_EQ(ptr1.get(), ptr2);
+}
+
+namespace {
+
+constexpr std::string_view kApple = "apple";
+constexpr std::string_view kOrange = "orange";
+
+using StringViewPtr = ftl::NonNull<const std::string_view*>;
+constexpr StringViewPtr kApplePtr = ftl::as_non_null(&kApple);
+constexpr StringViewPtr kOrangePtr = ftl::as_non_null(&kOrange);
+
+constexpr StringViewPtr longest(StringViewPtr ptr1, StringViewPtr ptr2) {
+  return ptr1->length() > ptr2->length() ? ptr1 : ptr2;
+}
+
+static_assert(longest(kApplePtr, kOrangePtr) == kOrangePtr);
+
+}  // namespace
+}  // namespace android::test
diff --git a/libs/ftl/optional_test.cpp b/libs/ftl/optional_test.cpp
index f7410c2..6b3b6c4 100644
--- a/libs/ftl/optional_test.cpp
+++ b/libs/ftl/optional_test.cpp
@@ -164,4 +164,35 @@
                      }));
 }
 
+// Comparison.
+namespace {
+
+constexpr Optional<int> kOptional1 = 1;
+constexpr Optional<int> kAnotherOptional1 = 1;
+constexpr Optional<int> kOptional2 = 2;
+constexpr Optional<int> kOptionalEmpty, kAnotherOptionalEmpty;
+
+constexpr std::optional<int> kStdOptional1 = 1;
+
+static_assert(kOptional1 == kAnotherOptional1);
+
+static_assert(kOptional1 != kOptional2);
+static_assert(kOptional2 != kOptional1);
+
+static_assert(kOptional1 != kOptionalEmpty);
+static_assert(kOptionalEmpty != kOptional1);
+
+static_assert(kOptionalEmpty == kAnotherOptionalEmpty);
+
+static_assert(kOptional1 == kStdOptional1);
+static_assert(kStdOptional1 == kOptional1);
+
+static_assert(kOptional2 != kStdOptional1);
+static_assert(kStdOptional1 != kOptional2);
+
+static_assert(kOptional2 != kOptionalEmpty);
+static_assert(kOptionalEmpty != kOptional2);
+
+} // namespace
+
 }  // namespace android::test
diff --git a/libs/ftl/shared_mutex_test.cpp b/libs/ftl/shared_mutex_test.cpp
new file mode 100644
index 0000000..6da7061
--- /dev/null
+++ b/libs/ftl/shared_mutex_test.cpp
@@ -0,0 +1,60 @@
+/*
+ * 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/shared_mutex.h>
+#include <gtest/gtest.h>
+#include <ftl/fake_guard.h>
+
+namespace android::test {
+
+TEST(SharedMutex, SharedLock) {
+  ftl::SharedMutex mutex;
+  std::shared_lock shared_lock(mutex);
+
+  { std::shared_lock shared_lock2(mutex); }
+}
+
+TEST(SharedMutex, ExclusiveLock) {
+  ftl::SharedMutex mutex;
+  std::unique_lock unique_lock(mutex);
+}
+
+TEST(SharedMutex, Annotations) {
+  struct {
+    void foo() FTL_ATTRIBUTE(requires_shared_capability(mutex)) { num++; }
+    void bar() FTL_ATTRIBUTE(requires_capability(mutex)) { num++; }
+    void baz() {
+      std::shared_lock shared_lock(mutex);
+      num++;
+    }
+    ftl::SharedMutex mutex;
+    int num = 0;
+
+  } s;
+
+  {
+    // TODO(b/257958323): Use an RAII class instead of locking manually.
+    s.mutex.lock_shared();
+    s.foo();
+    s.baz();
+    s.mutex.unlock_shared();
+  }
+  s.mutex.lock();
+  s.bar();
+  s.mutex.unlock();
+}
+
+}  // namespace android::test
diff --git a/libs/gralloc/types/Android.bp b/libs/gralloc/types/Android.bp
index 3d81c32..6d1dfe8 100644
--- a/libs/gralloc/types/Android.bp
+++ b/libs/gralloc/types/Android.bp
@@ -58,7 +58,7 @@
     ],
 
     export_shared_lib_headers: [
-        "android.hardware.graphics.common-V3-ndk",
+        "android.hardware.graphics.common-V4-ndk",
         "android.hardware.graphics.mapper@4.0",
         "libhidlbase",
     ],
diff --git a/libs/graphicsenv/Android.bp b/libs/graphicsenv/Android.bp
index a96a07a..af50a29 100644
--- a/libs/graphicsenv/Android.bp
+++ b/libs/graphicsenv/Android.bp
@@ -27,10 +27,13 @@
     srcs: [
         "GpuStatsInfo.cpp",
         "GraphicsEnv.cpp",
-        "IGpuService.cpp"
+        "IGpuService.cpp",
     ],
 
-    cflags: ["-Wall", "-Werror"],
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
 
     shared_libs: [
         "libbase",
@@ -46,4 +49,13 @@
     ],
 
     export_include_dirs: ["include"],
+
+    product_variables: {
+        // `debuggable` is set for eng and userdebug builds
+        debuggable: {
+            cflags: [
+                "-DANDROID_DEBUGGABLE",
+            ],
+        },
+    },
 }
diff --git a/libs/graphicsenv/GraphicsEnv.cpp b/libs/graphicsenv/GraphicsEnv.cpp
index 4a0a839..5f5f85a 100644
--- a/libs/graphicsenv/GraphicsEnv.cpp
+++ b/libs/graphicsenv/GraphicsEnv.cpp
@@ -126,7 +126,20 @@
 }
 
 bool GraphicsEnv::isDebuggable() {
-    return prctl(PR_GET_DUMPABLE, 0, 0, 0, 0) > 0;
+    // This flag determines if the application is marked debuggable
+    bool appDebuggable = prctl(PR_GET_DUMPABLE, 0, 0, 0, 0) > 0;
+
+    // This flag is set only in `debuggable` builds of the platform
+#if defined(ANDROID_DEBUGGABLE)
+    bool platformDebuggable = true;
+#else
+    bool platformDebuggable = false;
+#endif
+
+    ALOGV("GraphicsEnv::isDebuggable returning appDebuggable=%s || platformDebuggable=%s",
+          appDebuggable ? "true" : "false", platformDebuggable ? "true" : "false");
+
+    return appDebuggable || platformDebuggable;
 }
 
 void GraphicsEnv::setDriverPathAndSphalLibraries(const std::string path,
diff --git a/libs/graphicsenv/include/graphicsenv/GraphicsEnv.h b/libs/graphicsenv/include/graphicsenv/GraphicsEnv.h
index 82a6b6c..73d3196 100644
--- a/libs/graphicsenv/include/graphicsenv/GraphicsEnv.h
+++ b/libs/graphicsenv/include/graphicsenv/GraphicsEnv.h
@@ -35,7 +35,7 @@
 
     // Check if the process is debuggable. It returns false except in any of the
     // following circumstances:
-    // 1. ro.debuggable=1 (global debuggable enabled).
+    // 1. ANDROID_DEBUGGABLE is defined (global debuggable enabled).
     // 2. android:debuggable="true" in the manifest for an individual app.
     // 3. An app which explicitly calls prctl(PR_SET_DUMPABLE, 1).
     // 4. GraphicsEnv calls prctl(PR_SET_DUMPABLE, 1) in the presence of
diff --git a/libs/gui/Android.bp b/libs/gui/Android.bp
index 23a2181..a988e39 100644
--- a/libs/gui/Android.bp
+++ b/libs/gui/Android.bp
@@ -191,8 +191,6 @@
 
         "BitTube.cpp",
         "BLASTBufferQueue.cpp",
-        "BufferHubConsumer.cpp",
-        "BufferHubProducer.cpp",
         "BufferItemConsumer.cpp",
         "CompositorTiming.cpp",
         "ConsumerBase.cpp",
@@ -228,9 +226,6 @@
 
     shared_libs: [
         "libbinder",
-        "libbufferhub",
-        "libbufferhubqueue", // TODO(b/70046255): Remove this once BufferHub is integrated into libgui.
-        "libpdx_default_transport",
     ],
 
     export_shared_lib_headers: [
@@ -241,24 +236,6 @@
         "libgui_aidl_headers",
     ],
 
-    // bufferhub is not used when building libgui for vendors
-    target: {
-        vendor: {
-            cflags: [
-                "-DNO_BUFFERHUB",
-            ],
-            exclude_srcs: [
-                "BufferHubConsumer.cpp",
-                "BufferHubProducer.cpp",
-            ],
-            exclude_shared_libs: [
-                "libbufferhub",
-                "libbufferhubqueue",
-                "libpdx_default_transport",
-            ],
-        },
-    },
-
     aidl: {
         export_aidl_headers: true,
     },
@@ -288,7 +265,6 @@
     min_sdk_version: "29",
 
     cflags: [
-        "-DNO_BUFFERHUB",
         "-DNO_BINDER",
     ],
 
diff --git a/libs/gui/BLASTBufferQueue.cpp b/libs/gui/BLASTBufferQueue.cpp
index 8b9a878..97e45c6 100644
--- a/libs/gui/BLASTBufferQueue.cpp
+++ b/libs/gui/BLASTBufferQueue.cpp
@@ -167,14 +167,15 @@
     mNumFrameAvailable = 0;
 
     TransactionCompletedListener::getInstance()->addQueueStallListener(
-        [&]() {
-            std::function<void(bool)> callbackCopy;
-            {
-                std::unique_lock _lock{mMutex};
-                callbackCopy = mTransactionHangCallback;
-            }
-            if (callbackCopy) callbackCopy(true);
-        }, this);
+            [&](const std::string& reason) {
+                std::function<void(const std::string&)> callbackCopy;
+                {
+                    std::unique_lock _lock{mMutex};
+                    callbackCopy = mTransactionHangCallback;
+                }
+                if (callbackCopy) callbackCopy(reason);
+            },
+            this);
 
     BQA_LOGV("BLASTBufferQueue created");
 }
@@ -334,9 +335,11 @@
             std::optional<SurfaceControlStats> statsOptional = findMatchingStat(stats, pendingSC);
             if (statsOptional) {
                 SurfaceControlStats stat = *statsOptional;
-                mTransformHint = stat.transformHint;
-                mBufferItemConsumer->setTransformHint(mTransformHint);
-                BQA_LOGV("updated mTransformHint=%d", mTransformHint);
+                if (stat.transformHint) {
+                    mTransformHint = *stat.transformHint;
+                    mBufferItemConsumer->setTransformHint(mTransformHint);
+                    BQA_LOGV("updated mTransformHint=%d", mTransformHint);
+                }
                 // Update frametime stamps if the frame was latched and presented, indicated by a
                 // valid latch time.
                 if (stat.latchTime > 0) {
@@ -357,11 +360,12 @@
                     }
                 }
                 for (const auto& staleRelease : staleReleases) {
-                    BQA_LOGE("Faking releaseBufferCallback from transactionCompleteCallback");
-                    BBQ_TRACE("FakeReleaseCallback");
                     releaseBufferCallbackLocked(staleRelease,
-                        stat.previousReleaseFence ? stat.previousReleaseFence : Fence::NO_FENCE,
-                        stat.currentMaxAcquiredBufferCount);
+                                                stat.previousReleaseFence
+                                                        ? stat.previousReleaseFence
+                                                        : Fence::NO_FENCE,
+                                                stat.currentMaxAcquiredBufferCount,
+                                                true /* fakeRelease */);
                 }
             } else {
                 BQA_LOGE("Failed to find matching SurfaceControl in transactionCallback");
@@ -405,11 +409,13 @@
     BBQ_TRACE();
 
     std::unique_lock _lock{mMutex};
-    releaseBufferCallbackLocked(id, releaseFence, currentMaxAcquiredBufferCount);
+    releaseBufferCallbackLocked(id, releaseFence, currentMaxAcquiredBufferCount,
+                                false /* fakeRelease */);
 }
 
-void BLASTBufferQueue::releaseBufferCallbackLocked(const ReleaseCallbackId& id,
-        const sp<Fence>& releaseFence, std::optional<uint32_t> currentMaxAcquiredBufferCount) {
+void BLASTBufferQueue::releaseBufferCallbackLocked(
+        const ReleaseCallbackId& id, const sp<Fence>& releaseFence,
+        std::optional<uint32_t> currentMaxAcquiredBufferCount, bool fakeRelease) {
     ATRACE_CALL();
     BQA_LOGV("releaseBufferCallback %s", id.to_string().c_str());
 
@@ -432,6 +438,11 @@
     auto rb = ReleasedBuffer{id, releaseFence};
     if (std::find(mPendingRelease.begin(), mPendingRelease.end(), rb) == mPendingRelease.end()) {
         mPendingRelease.emplace_back(rb);
+        if (fakeRelease) {
+            BQA_LOGE("Faking releaseBufferCallback from transactionCompleteCallback %" PRIu64,
+                     id.framenumber);
+            BBQ_TRACE("FakeReleaseCallback");
+        }
     }
 
     // Release all buffers that are beyond the ones that we need to hold
@@ -1112,7 +1123,8 @@
     return SurfaceControl::isSameSurface(mSurfaceControl, surfaceControl);
 }
 
-void BLASTBufferQueue::setTransactionHangCallback(std::function<void(bool)> callback) {
+void BLASTBufferQueue::setTransactionHangCallback(
+        std::function<void(const std::string&)> callback) {
     std::unique_lock _lock{mMutex};
     mTransactionHangCallback = callback;
 }
diff --git a/libs/gui/BufferHubConsumer.cpp b/libs/gui/BufferHubConsumer.cpp
deleted file mode 100644
index b5cdeb2..0000000
--- a/libs/gui/BufferHubConsumer.cpp
+++ /dev/null
@@ -1,161 +0,0 @@
-/*
- * Copyright 2018 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 <gui/BufferHubConsumer.h>
-
-namespace android {
-
-using namespace dvr;
-
-/* static */
-sp<BufferHubConsumer> BufferHubConsumer::Create(const std::shared_ptr<ConsumerQueue>& queue) {
-    sp<BufferHubConsumer> consumer = new BufferHubConsumer;
-    consumer->mQueue = queue;
-    return consumer;
-}
-
-/* static */ sp<BufferHubConsumer> BufferHubConsumer::Create(ConsumerQueueParcelable parcelable) {
-    if (!parcelable.IsValid()) {
-        ALOGE("BufferHubConsumer::Create: Invalid consumer parcelable.");
-        return nullptr;
-    }
-
-    sp<BufferHubConsumer> consumer = new BufferHubConsumer;
-    consumer->mQueue = ConsumerQueue::Import(parcelable.TakeChannelHandle());
-    return consumer;
-}
-
-status_t BufferHubConsumer::acquireBuffer(BufferItem* /*buffer*/, nsecs_t /*presentWhen*/,
-                                          uint64_t /*maxFrameNumber*/) {
-    ALOGE("BufferHubConsumer::acquireBuffer: not implemented.");
-    return INVALID_OPERATION;
-}
-
-status_t BufferHubConsumer::detachBuffer(int /*slot*/) {
-    ALOGE("BufferHubConsumer::detachBuffer: not implemented.");
-    return INVALID_OPERATION;
-}
-
-status_t BufferHubConsumer::attachBuffer(int* /*outSlot*/, const sp<GraphicBuffer>& /*buffer*/) {
-    ALOGE("BufferHubConsumer::attachBuffer: not implemented.");
-    return INVALID_OPERATION;
-}
-
-status_t BufferHubConsumer::releaseBuffer(int /*buf*/, uint64_t /*frameNumber*/,
-                                          EGLDisplay /*display*/, EGLSyncKHR /*fence*/,
-                                          const sp<Fence>& /*releaseFence*/) {
-    ALOGE("BufferHubConsumer::releaseBuffer: not implemented.");
-    return INVALID_OPERATION;
-}
-
-status_t BufferHubConsumer::consumerConnect(const sp<IConsumerListener>& /*consumer*/,
-                                            bool /*controlledByApp*/) {
-    ALOGE("BufferHubConsumer::consumerConnect: not implemented.");
-
-    // TODO(b/73267953): Make BufferHub honor producer and consumer connection. Returns NO_ERROR to
-    // make IGraphicBufferConsumer_test happy.
-    return NO_ERROR;
-}
-
-status_t BufferHubConsumer::consumerDisconnect() {
-    ALOGE("BufferHubConsumer::consumerDisconnect: not implemented.");
-
-    // TODO(b/73267953): Make BufferHub honor producer and consumer connection. Returns NO_ERROR to
-    // make IGraphicBufferConsumer_test happy.
-    return NO_ERROR;
-}
-
-status_t BufferHubConsumer::getReleasedBuffers(uint64_t* /*slotMask*/) {
-    ALOGE("BufferHubConsumer::getReleasedBuffers: not implemented.");
-    return INVALID_OPERATION;
-}
-
-status_t BufferHubConsumer::setDefaultBufferSize(uint32_t /*w*/, uint32_t /*h*/) {
-    ALOGE("BufferHubConsumer::setDefaultBufferSize: not implemented.");
-    return INVALID_OPERATION;
-}
-
-status_t BufferHubConsumer::setMaxBufferCount(int /*bufferCount*/) {
-    ALOGE("BufferHubConsumer::setMaxBufferCount: not implemented.");
-    return INVALID_OPERATION;
-}
-
-status_t BufferHubConsumer::setMaxAcquiredBufferCount(int /*maxAcquiredBuffers*/) {
-    ALOGE("BufferHubConsumer::setMaxAcquiredBufferCount: not implemented.");
-
-    // TODO(b/73267953): Make BufferHub honor producer and consumer connection. Returns NO_ERROR to
-    // make IGraphicBufferConsumer_test happy.
-    return NO_ERROR;
-}
-
-status_t BufferHubConsumer::setConsumerName(const String8& /*name*/) {
-    ALOGE("BufferHubConsumer::setConsumerName: not implemented.");
-    return INVALID_OPERATION;
-}
-
-status_t BufferHubConsumer::setDefaultBufferFormat(PixelFormat /*defaultFormat*/) {
-    ALOGE("BufferHubConsumer::setDefaultBufferFormat: not implemented.");
-    return INVALID_OPERATION;
-}
-
-status_t BufferHubConsumer::setDefaultBufferDataSpace(android_dataspace /*defaultDataSpace*/) {
-    ALOGE("BufferHubConsumer::setDefaultBufferDataSpace: not implemented.");
-    return INVALID_OPERATION;
-}
-
-status_t BufferHubConsumer::setConsumerUsageBits(uint64_t /*usage*/) {
-    ALOGE("BufferHubConsumer::setConsumerUsageBits: not implemented.");
-    return INVALID_OPERATION;
-}
-
-status_t BufferHubConsumer::setConsumerIsProtected(bool /*isProtected*/) {
-    ALOGE("BufferHubConsumer::setConsumerIsProtected: not implemented.");
-    return INVALID_OPERATION;
-}
-
-status_t BufferHubConsumer::setTransformHint(uint32_t /*hint*/) {
-    ALOGE("BufferHubConsumer::setTransformHint: not implemented.");
-    return INVALID_OPERATION;
-}
-
-status_t BufferHubConsumer::getSidebandStream(sp<NativeHandle>* /*outStream*/) const {
-    ALOGE("BufferHubConsumer::getSidebandStream: not implemented.");
-    return INVALID_OPERATION;
-}
-
-status_t BufferHubConsumer::getOccupancyHistory(
-        bool /*forceFlush*/, std::vector<OccupancyTracker::Segment>* /*outHistory*/) {
-    ALOGE("BufferHubConsumer::getOccupancyHistory: not implemented.");
-    return INVALID_OPERATION;
-}
-
-status_t BufferHubConsumer::discardFreeBuffers() {
-    ALOGE("BufferHubConsumer::discardFreeBuffers: not implemented.");
-    return INVALID_OPERATION;
-}
-
-status_t BufferHubConsumer::dumpState(const String8& /*prefix*/, String8* /*outResult*/) const {
-    ALOGE("BufferHubConsumer::dumpState: not implemented.");
-    return INVALID_OPERATION;
-}
-
-IBinder* BufferHubConsumer::onAsBinder() {
-    ALOGE("BufferHubConsumer::onAsBinder: BufferHubConsumer should never be used as an Binder "
-          "object.");
-    return nullptr;
-}
-
-} // namespace android
diff --git a/libs/gui/BufferHubProducer.cpp b/libs/gui/BufferHubProducer.cpp
deleted file mode 100644
index 1f71e23..0000000
--- a/libs/gui/BufferHubProducer.cpp
+++ /dev/null
@@ -1,868 +0,0 @@
-/*
- * Copyright 2018 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 <dvr/dvr_api.h>
-#include <gui/BufferHubProducer.h>
-#include <inttypes.h>
-#include <log/log.h>
-#include <system/window.h>
-
-namespace android {
-
-using namespace dvr;
-
-/* static */
-sp<BufferHubProducer> BufferHubProducer::Create(const std::shared_ptr<ProducerQueue>& queue) {
-    sp<BufferHubProducer> producer = new BufferHubProducer;
-    producer->queue_ = queue;
-    return producer;
-}
-
-/* static */
-sp<BufferHubProducer> BufferHubProducer::Create(ProducerQueueParcelable parcelable) {
-    if (!parcelable.IsValid()) {
-        ALOGE("BufferHubProducer::Create: Invalid producer parcelable.");
-        return nullptr;
-    }
-
-    sp<BufferHubProducer> producer = new BufferHubProducer;
-    producer->queue_ = ProducerQueue::Import(parcelable.TakeChannelHandle());
-    return producer;
-}
-
-status_t BufferHubProducer::requestBuffer(int slot, sp<GraphicBuffer>* buf) {
-    ALOGV("requestBuffer: slot=%d", slot);
-
-    std::unique_lock<std::mutex> lock(mutex_);
-
-    if (connected_api_ == kNoConnectedApi) {
-        ALOGE("requestBuffer: BufferHubProducer has no connected producer");
-        return NO_INIT;
-    }
-
-    if (slot < 0 || slot >= max_buffer_count_) {
-        ALOGE("requestBuffer: slot index %d out of range [0, %d)", slot, max_buffer_count_);
-        return BAD_VALUE;
-    } else if (!buffers_[slot].mBufferState.isDequeued()) {
-        ALOGE("requestBuffer: slot %d is not owned by the producer (state = %s)", slot,
-              buffers_[slot].mBufferState.string());
-        return BAD_VALUE;
-    } else if (buffers_[slot].mGraphicBuffer != nullptr) {
-        ALOGE("requestBuffer: slot %d is not empty.", slot);
-        return BAD_VALUE;
-    } else if (buffers_[slot].mProducerBuffer == nullptr) {
-        ALOGE("requestBuffer: slot %d is not dequeued.", slot);
-        return BAD_VALUE;
-    }
-
-    const auto& producer_buffer = buffers_[slot].mProducerBuffer;
-    sp<GraphicBuffer> graphic_buffer = producer_buffer->buffer()->buffer();
-
-    buffers_[slot].mGraphicBuffer = graphic_buffer;
-    buffers_[slot].mRequestBufferCalled = true;
-
-    *buf = graphic_buffer;
-    return NO_ERROR;
-}
-
-status_t BufferHubProducer::setMaxDequeuedBufferCount(int max_dequeued_buffers) {
-    ALOGV("setMaxDequeuedBufferCount: max_dequeued_buffers=%d", max_dequeued_buffers);
-
-    std::unique_lock<std::mutex> lock(mutex_);
-
-    if (max_dequeued_buffers <= 0 ||
-        max_dequeued_buffers >
-                int(BufferHubQueue::kMaxQueueCapacity - kDefaultUndequeuedBuffers)) {
-        ALOGE("setMaxDequeuedBufferCount: %d out of range (0, %zu]", max_dequeued_buffers,
-              BufferHubQueue::kMaxQueueCapacity);
-        return BAD_VALUE;
-    }
-
-    // The new dequeued_buffers count should not be violated by the number
-    // of currently dequeued buffers.
-    int dequeued_count = 0;
-    for (const auto& buf : buffers_) {
-        if (buf.mBufferState.isDequeued()) {
-            dequeued_count++;
-        }
-    }
-    if (dequeued_count > max_dequeued_buffers) {
-        ALOGE("setMaxDequeuedBufferCount: the requested dequeued_buffers"
-              "count (%d) exceeds the current dequeued buffer count (%d)",
-              max_dequeued_buffers, dequeued_count);
-        return BAD_VALUE;
-    }
-
-    max_dequeued_buffer_count_ = max_dequeued_buffers;
-    return NO_ERROR;
-}
-
-status_t BufferHubProducer::setAsyncMode(bool async) {
-    if (async) {
-        // TODO(b/36724099) BufferHubQueue's consumer end always acquires the buffer
-        // automatically and behaves differently from IGraphicBufferConsumer. Thus,
-        // android::BufferQueue's async mode (a.k.a. allocating an additional buffer
-        // to prevent dequeueBuffer from being blocking) technically does not apply
-        // here.
-        //
-        // In Daydream, non-blocking producer side dequeue is guaranteed by careful
-        // buffer consumer implementations. In another word, BufferHubQueue based
-        // dequeueBuffer should never block whether setAsyncMode(true) is set or
-        // not.
-        //
-        // See: IGraphicBufferProducer::setAsyncMode and
-        // BufferQueueProducer::setAsyncMode for more about original implementation.
-        ALOGW("BufferHubProducer::setAsyncMode: BufferHubQueue should always be "
-              "asynchronous. This call makes no effact.");
-        return NO_ERROR;
-    }
-    return NO_ERROR;
-}
-
-status_t BufferHubProducer::dequeueBuffer(int* out_slot, sp<Fence>* out_fence, uint32_t width,
-                                          uint32_t height, PixelFormat format, uint64_t usage,
-                                          uint64_t* /*outBufferAge*/,
-                                          FrameEventHistoryDelta* /* out_timestamps */) {
-    ALOGV("dequeueBuffer: w=%u, h=%u, format=%d, usage=%" PRIu64, width, height, format, usage);
-
-    status_t ret;
-    std::unique_lock<std::mutex> lock(mutex_);
-
-    if (connected_api_ == kNoConnectedApi) {
-        ALOGE("dequeueBuffer: BufferQueue has no connected producer");
-        return NO_INIT;
-    }
-
-    const uint32_t kLayerCount = 1;
-    if (int32_t(queue_->capacity()) < max_dequeued_buffer_count_ + kDefaultUndequeuedBuffers) {
-        // Lazy allocation. When the capacity of |queue_| has not reached
-        // |max_dequeued_buffer_count_|, allocate new buffer.
-        // TODO(jwcai) To save memory, the really reasonable thing to do is to go
-        // over existing slots and find first existing one to dequeue.
-        ret = AllocateBuffer(width, height, kLayerCount, format, usage);
-        if (ret < 0) return ret;
-    }
-
-    size_t slot = 0;
-    std::shared_ptr<ProducerBuffer> producer_buffer;
-
-    for (size_t retry = 0; retry < BufferHubQueue::kMaxQueueCapacity; retry++) {
-        LocalHandle fence;
-        auto buffer_status = queue_->Dequeue(dequeue_timeout_ms_, &slot, &fence);
-        if (!buffer_status) return NO_MEMORY;
-
-        producer_buffer = buffer_status.take();
-        if (!producer_buffer) return NO_MEMORY;
-
-        if (width == producer_buffer->width() && height == producer_buffer->height() &&
-            uint32_t(format) == producer_buffer->format()) {
-            // The producer queue returns a producer buffer matches the request.
-            break;
-        }
-
-        // Needs reallocation.
-        // TODO(jwcai) Consider use VLOG instead if we find this log is not useful.
-        ALOGI("dequeueBuffer: requested buffer (w=%u, h=%u, format=%u) is different "
-              "from the buffer returned at slot: %zu (w=%u, h=%u, format=%u). Need "
-              "re-allocattion.",
-              width, height, format, slot, producer_buffer->width(), producer_buffer->height(),
-              producer_buffer->format());
-        // Mark the slot as reallocating, so that later we can set
-        // BUFFER_NEEDS_REALLOCATION when the buffer actually get dequeued.
-        buffers_[slot].mIsReallocating = true;
-
-        // Remove the old buffer once the allocation before allocating its
-        // replacement.
-        RemoveBuffer(slot);
-
-        // Allocate a new producer buffer with new buffer configs. Note that if
-        // there are already multiple buffers in the queue, the next one returned
-        // from |queue_->Dequeue| may not be the new buffer we just reallocated.
-        // Retry up to BufferHubQueue::kMaxQueueCapacity times.
-        ret = AllocateBuffer(width, height, kLayerCount, format, usage);
-        if (ret < 0) return ret;
-    }
-
-    // With the BufferHub backed solution. Buffer slot returned from
-    // |queue_->Dequeue| is guaranteed to avaiable for producer's use.
-    // It's either in free state (if the buffer has never been used before) or
-    // in queued state (if the buffer has been dequeued and queued back to
-    // BufferHubQueue).
-    LOG_ALWAYS_FATAL_IF((!buffers_[slot].mBufferState.isFree() &&
-                         !buffers_[slot].mBufferState.isQueued()),
-                        "dequeueBuffer: slot %zu is not free or queued, actual state: %s.", slot,
-                        buffers_[slot].mBufferState.string());
-
-    buffers_[slot].mBufferState.freeQueued();
-    buffers_[slot].mBufferState.dequeue();
-    ALOGV("dequeueBuffer: slot=%zu", slot);
-
-    // TODO(jwcai) Handle fence properly. |BufferHub| has full fence support, we
-    // just need to exopose that through |BufferHubQueue| once we need fence.
-    *out_fence = Fence::NO_FENCE;
-    *out_slot = int(slot);
-    ret = NO_ERROR;
-
-    if (buffers_[slot].mIsReallocating) {
-        ret |= BUFFER_NEEDS_REALLOCATION;
-        buffers_[slot].mIsReallocating = false;
-    }
-
-    return ret;
-}
-
-status_t BufferHubProducer::detachBuffer(int slot) {
-    ALOGV("detachBuffer: slot=%d", slot);
-    std::unique_lock<std::mutex> lock(mutex_);
-
-    return DetachBufferLocked(static_cast<size_t>(slot));
-}
-
-status_t BufferHubProducer::DetachBufferLocked(size_t slot) {
-    if (connected_api_ == kNoConnectedApi) {
-        ALOGE("detachBuffer: BufferHubProducer is not connected.");
-        return NO_INIT;
-    }
-
-    if (slot >= static_cast<size_t>(max_buffer_count_)) {
-        ALOGE("detachBuffer: slot index %zu out of range [0, %d)", slot, max_buffer_count_);
-        return BAD_VALUE;
-    } else if (!buffers_[slot].mBufferState.isDequeued()) {
-        ALOGE("detachBuffer: slot %zu is not owned by the producer (state = %s)", slot,
-              buffers_[slot].mBufferState.string());
-        return BAD_VALUE;
-    } else if (!buffers_[slot].mRequestBufferCalled) {
-        ALOGE("detachBuffer: buffer in slot %zu has not been requested", slot);
-        return BAD_VALUE;
-    }
-    std::shared_ptr<ProducerBuffer> producer_buffer = queue_->GetBuffer(slot);
-    if (producer_buffer == nullptr || producer_buffer->buffer() == nullptr) {
-        ALOGE("detachBuffer: Invalid ProducerBuffer at slot %zu.", slot);
-        return BAD_VALUE;
-    }
-    sp<GraphicBuffer> graphic_buffer = producer_buffer->buffer()->buffer();
-    if (graphic_buffer == nullptr) {
-        ALOGE("detachBuffer: Invalid GraphicBuffer at slot %zu.", slot);
-        return BAD_VALUE;
-    }
-
-    // Remove the ProducerBuffer from the ProducerQueue.
-    status_t error = RemoveBuffer(slot);
-    if (error != NO_ERROR) {
-        ALOGE("detachBuffer: Failed to remove buffer, slot=%zu, error=%d.", slot, error);
-        return error;
-    }
-
-    // Here we need to convert the existing ProducerBuffer into a DetachedBufferHandle and inject
-    // the handle into the GraphicBuffer object at the requested slot.
-    auto status_or_handle = producer_buffer->Detach();
-    if (!status_or_handle.ok()) {
-        ALOGE("detachBuffer: Failed to detach from a ProducerBuffer at slot %zu, error=%d.", slot,
-              status_or_handle.error());
-        return BAD_VALUE;
-    }
-
-    // TODO(b/70912269): Reimplement BufferHubProducer::DetachBufferLocked() once GraphicBuffer can
-    // be directly backed by BufferHub.
-    return INVALID_OPERATION;
-}
-
-status_t BufferHubProducer::detachNextBuffer(sp<GraphicBuffer>* out_buffer, sp<Fence>* out_fence) {
-    ALOGV("detachNextBuffer.");
-
-    if (out_buffer == nullptr || out_fence == nullptr) {
-        ALOGE("detachNextBuffer: Invalid parameter: out_buffer=%p, out_fence=%p", out_buffer,
-              out_fence);
-        return BAD_VALUE;
-    }
-
-    std::unique_lock<std::mutex> lock(mutex_);
-
-    if (connected_api_ == kNoConnectedApi) {
-        ALOGE("detachNextBuffer: BufferHubProducer is not connected.");
-        return NO_INIT;
-    }
-
-    // detachNextBuffer is equivalent to calling dequeueBuffer, requestBuffer, and detachBuffer in
-    // sequence, except for two things:
-    //
-    // 1) It is unnecessary to know the dimensions, format, or usage of the next buffer, i.e. the
-    // function just returns whatever ProducerBuffer is available from the ProducerQueue and no
-    // buffer allocation or re-allocation will happen.
-    // 2) It will not block, since if it cannot find an appropriate buffer to return, it will return
-    // an error instead.
-    size_t slot = 0;
-    LocalHandle fence;
-
-    // First, dequeue a ProducerBuffer from the ProducerQueue with no timeout. Report error
-    // immediately if ProducerQueue::Dequeue() fails.
-    auto status_or_buffer = queue_->Dequeue(/*timeout=*/0, &slot, &fence);
-    if (!status_or_buffer.ok()) {
-        ALOGE("detachNextBuffer: Failed to dequeue buffer, error=%d.", status_or_buffer.error());
-        return NO_MEMORY;
-    }
-
-    std::shared_ptr<ProducerBuffer> producer_buffer = status_or_buffer.take();
-    if (producer_buffer == nullptr) {
-        ALOGE("detachNextBuffer: Dequeued buffer is null.");
-        return NO_MEMORY;
-    }
-
-    // With the BufferHub backed solution, slot returned from |queue_->Dequeue| is guaranteed to
-    // be available for producer's use. It's either in free state (if the buffer has never been used
-    // before) or in queued state (if the buffer has been dequeued and queued back to
-    // BufferHubQueue).
-    if (!buffers_[slot].mBufferState.isFree() && !buffers_[slot].mBufferState.isQueued()) {
-        ALOGE("detachNextBuffer: slot %zu is not free or queued, actual state: %s.", slot,
-              buffers_[slot].mBufferState.string());
-        return BAD_VALUE;
-    }
-    if (buffers_[slot].mProducerBuffer == nullptr) {
-        ALOGE("detachNextBuffer: ProducerBuffer at slot %zu is null.", slot);
-        return BAD_VALUE;
-    }
-    if (buffers_[slot].mProducerBuffer->id() != producer_buffer->id()) {
-        ALOGE("detachNextBuffer: ProducerBuffer at slot %zu has mismatched id, actual: "
-              "%d, expected: %d.",
-              slot, buffers_[slot].mProducerBuffer->id(), producer_buffer->id());
-        return BAD_VALUE;
-    }
-
-    ALOGV("detachNextBuffer: slot=%zu", slot);
-    buffers_[slot].mBufferState.freeQueued();
-    buffers_[slot].mBufferState.dequeue();
-
-    // Second, request the buffer.
-    sp<GraphicBuffer> graphic_buffer = producer_buffer->buffer()->buffer();
-    buffers_[slot].mGraphicBuffer = producer_buffer->buffer()->buffer();
-
-    // Finally, detach the buffer and then return.
-    status_t error = DetachBufferLocked(slot);
-    if (error == NO_ERROR) {
-        *out_fence = new Fence(fence.Release());
-        *out_buffer = graphic_buffer;
-    }
-    return error;
-}
-
-status_t BufferHubProducer::attachBuffer(int* out_slot, const sp<GraphicBuffer>& buffer) {
-    // In the BufferHub design, all buffers are allocated and owned by the BufferHub. Thus only
-    // GraphicBuffers that are originated from BufferHub can be attached to a BufferHubProducer.
-    ALOGV("queueBuffer: buffer=%p", buffer.get());
-
-    if (out_slot == nullptr) {
-        ALOGE("attachBuffer: out_slot cannot be NULL.");
-        return BAD_VALUE;
-    }
-    if (buffer == nullptr) {
-        ALOGE("attachBuffer: invalid GraphicBuffer.");
-        return BAD_VALUE;
-    }
-
-    std::unique_lock<std::mutex> lock(mutex_);
-
-    if (connected_api_ == kNoConnectedApi) {
-        ALOGE("attachBuffer: BufferQueue has no connected producer");
-        return NO_INIT;
-    }
-
-    // Before attaching the buffer, caller is supposed to call
-    // IGraphicBufferProducer::setGenerationNumber to inform the
-    // BufferHubProducer the next generation number.
-    if (buffer->getGenerationNumber() != generation_number_) {
-        ALOGE("attachBuffer: Mismatched generation number, buffer: %u, queue: %u.",
-              buffer->getGenerationNumber(), generation_number_);
-        return BAD_VALUE;
-    }
-
-    // TODO(b/70912269): Reimplement BufferHubProducer::DetachBufferLocked() once GraphicBuffer can
-    // be directly backed by BufferHub.
-    return INVALID_OPERATION;
-}
-
-status_t BufferHubProducer::queueBuffer(int slot, const QueueBufferInput& input,
-                                        QueueBufferOutput* output) {
-    ALOGV("queueBuffer: slot %d", slot);
-
-    if (output == nullptr) {
-        return BAD_VALUE;
-    }
-
-    int64_t timestamp;
-    bool is_auto_timestamp;
-    android_dataspace dataspace;
-    Rect crop(Rect::EMPTY_RECT);
-    int scaling_mode;
-    uint32_t transform;
-    sp<Fence> fence;
-
-    input.deflate(&timestamp, &is_auto_timestamp, &dataspace, &crop, &scaling_mode, &transform,
-                  &fence);
-
-    // Check input scaling mode is valid.
-    switch (scaling_mode) {
-        case NATIVE_WINDOW_SCALING_MODE_FREEZE:
-        case NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW:
-        case NATIVE_WINDOW_SCALING_MODE_SCALE_CROP:
-        case NATIVE_WINDOW_SCALING_MODE_NO_SCALE_CROP:
-            break;
-        default:
-            ALOGE("queueBuffer: unknown scaling mode %d", scaling_mode);
-            return BAD_VALUE;
-    }
-
-    // Check input fence is valid.
-    if (fence == nullptr) {
-        ALOGE("queueBuffer: fence is NULL");
-        return BAD_VALUE;
-    }
-
-    std::unique_lock<std::mutex> lock(mutex_);
-
-    if (connected_api_ == kNoConnectedApi) {
-        ALOGE("queueBuffer: BufferQueue has no connected producer");
-        return NO_INIT;
-    }
-
-    if (slot < 0 || slot >= max_buffer_count_) {
-        ALOGE("queueBuffer: slot index %d out of range [0, %d)", slot, max_buffer_count_);
-        return BAD_VALUE;
-    } else if (!buffers_[slot].mBufferState.isDequeued()) {
-        ALOGE("queueBuffer: slot %d is not owned by the producer (state = %s)", slot,
-              buffers_[slot].mBufferState.string());
-        return BAD_VALUE;
-    } else if ((!buffers_[slot].mRequestBufferCalled || buffers_[slot].mGraphicBuffer == nullptr)) {
-        ALOGE("queueBuffer: slot %d is not requested (mRequestBufferCalled=%d, "
-              "mGraphicBuffer=%p)",
-              slot, buffers_[slot].mRequestBufferCalled, buffers_[slot].mGraphicBuffer.get());
-        return BAD_VALUE;
-    }
-
-    // Post the producer buffer with timestamp in the metadata.
-    const auto& producer_buffer = buffers_[slot].mProducerBuffer;
-
-    // Check input crop is not out of boundary of current buffer.
-    Rect buffer_rect(producer_buffer->width(), producer_buffer->height());
-    Rect cropped_rect(Rect::EMPTY_RECT);
-    crop.intersect(buffer_rect, &cropped_rect);
-    if (cropped_rect != crop) {
-        ALOGE("queueBuffer: slot %d has out-of-boundary crop.", slot);
-        return BAD_VALUE;
-    }
-
-    LocalHandle fence_fd(fence->isValid() ? fence->dup() : -1);
-
-    DvrNativeBufferMetadata meta_data;
-    meta_data.timestamp = timestamp;
-    meta_data.is_auto_timestamp = int32_t(is_auto_timestamp);
-    meta_data.dataspace = int32_t(dataspace);
-    meta_data.crop_left = crop.left;
-    meta_data.crop_top = crop.top;
-    meta_data.crop_right = crop.right;
-    meta_data.crop_bottom = crop.bottom;
-    meta_data.scaling_mode = int32_t(scaling_mode);
-    meta_data.transform = int32_t(transform);
-
-    producer_buffer->PostAsync(&meta_data, fence_fd);
-    buffers_[slot].mBufferState.queue();
-
-    output->width = producer_buffer->width();
-    output->height = producer_buffer->height();
-    output->transformHint = 0; // default value, we don't use it yet.
-
-    // |numPendingBuffers| counts of the number of buffers that has been enqueued
-    // by the producer but not yet acquired by the consumer. Due to the nature
-    // of BufferHubQueue design, this is hard to trace from the producer's client
-    // side, but it's safe to assume it's zero.
-    output->numPendingBuffers = 0;
-
-    // Note that we are not setting nextFrameNumber here as it seems to be only
-    // used by surface flinger. See more at b/22802885, ag/791760.
-    output->nextFrameNumber = 0;
-
-    return NO_ERROR;
-}
-
-status_t BufferHubProducer::cancelBuffer(int slot, const sp<Fence>& fence) {
-    ALOGV(__FUNCTION__);
-
-    std::unique_lock<std::mutex> lock(mutex_);
-
-    if (connected_api_ == kNoConnectedApi) {
-        ALOGE("cancelBuffer: BufferQueue has no connected producer");
-        return NO_INIT;
-    }
-
-    if (slot < 0 || slot >= max_buffer_count_) {
-        ALOGE("cancelBuffer: slot index %d out of range [0, %d)", slot, max_buffer_count_);
-        return BAD_VALUE;
-    } else if (!buffers_[slot].mBufferState.isDequeued()) {
-        ALOGE("cancelBuffer: slot %d is not owned by the producer (state = %s)", slot,
-              buffers_[slot].mBufferState.string());
-        return BAD_VALUE;
-    } else if (fence == nullptr) {
-        ALOGE("cancelBuffer: fence is NULL");
-        return BAD_VALUE;
-    }
-
-    auto producer_buffer = buffers_[slot].mProducerBuffer;
-    queue_->Enqueue(producer_buffer, size_t(slot), 0U);
-    buffers_[slot].mBufferState.cancel();
-    buffers_[slot].mFence = fence;
-    ALOGV("cancelBuffer: slot %d", slot);
-
-    return NO_ERROR;
-}
-
-status_t BufferHubProducer::query(int what, int* out_value) {
-    ALOGV(__FUNCTION__);
-
-    std::unique_lock<std::mutex> lock(mutex_);
-
-    if (out_value == nullptr) {
-        ALOGE("query: out_value was NULL");
-        return BAD_VALUE;
-    }
-
-    int value = 0;
-    switch (what) {
-        case NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS:
-            // TODO(b/36187402) This should be the maximum number of buffers that this
-            // producer queue's consumer can acquire. Set to be at least one. Need to
-            // find a way to set from the consumer side.
-            value = kDefaultUndequeuedBuffers;
-            break;
-        case NATIVE_WINDOW_BUFFER_AGE:
-            value = 0;
-            break;
-        case NATIVE_WINDOW_WIDTH:
-            value = int32_t(queue_->default_width());
-            break;
-        case NATIVE_WINDOW_HEIGHT:
-            value = int32_t(queue_->default_height());
-            break;
-        case NATIVE_WINDOW_FORMAT:
-            value = int32_t(queue_->default_format());
-            break;
-        case NATIVE_WINDOW_CONSUMER_RUNNING_BEHIND:
-            // BufferHubQueue is always operating in async mode, thus semantically
-            // consumer can never be running behind. See BufferQueueCore.cpp core
-            // for more information about the original meaning of this flag.
-            value = 0;
-            break;
-        case NATIVE_WINDOW_CONSUMER_USAGE_BITS:
-            // TODO(jwcai) This is currently not implement as we don't need
-            // IGraphicBufferConsumer parity.
-            value = 0;
-            break;
-        case NATIVE_WINDOW_DEFAULT_DATASPACE:
-            // TODO(jwcai) Return the default value android::BufferQueue is using as
-            // there is no way dvr::ConsumerQueue can set it.
-            value = 0; // HAL_DATASPACE_UNKNOWN
-            break;
-        case NATIVE_WINDOW_STICKY_TRANSFORM:
-            // TODO(jwcai) Return the default value android::BufferQueue is using as
-            // there is no way dvr::ConsumerQueue can set it.
-            value = 0;
-            break;
-        case NATIVE_WINDOW_CONSUMER_IS_PROTECTED:
-            // In Daydream's implementation, the consumer end (i.e. VR Compostior)
-            // knows how to handle protected buffers.
-            value = 1;
-            break;
-        default:
-            return BAD_VALUE;
-    }
-
-    ALOGV("query: key=%d, v=%d", what, value);
-    *out_value = value;
-    return NO_ERROR;
-}
-
-status_t BufferHubProducer::connect(const sp<IProducerListener>& /* listener */, int api,
-                                    bool /* producer_controlled_by_app */,
-                                    QueueBufferOutput* output) {
-    // Consumer interaction are actually handled by buffer hub, and we need
-    // to maintain consumer operations here. We only need to perform basic input
-    // parameter checks here.
-    ALOGV(__FUNCTION__);
-
-    if (output == nullptr) {
-        return BAD_VALUE;
-    }
-
-    std::unique_lock<std::mutex> lock(mutex_);
-
-    if (connected_api_ != kNoConnectedApi) {
-        return BAD_VALUE;
-    }
-
-    if (!queue_->is_connected()) {
-        ALOGE("BufferHubProducer::connect: This BufferHubProducer is not "
-              "connected to bufferhud. Has it been taken out as a parcelable?");
-        return BAD_VALUE;
-    }
-
-    switch (api) {
-        case NATIVE_WINDOW_API_EGL:
-        case NATIVE_WINDOW_API_CPU:
-        case NATIVE_WINDOW_API_MEDIA:
-        case NATIVE_WINDOW_API_CAMERA:
-            connected_api_ = api;
-
-            output->width = queue_->default_width();
-            output->height = queue_->default_height();
-
-            // default values, we don't use them yet.
-            output->transformHint = 0;
-            output->numPendingBuffers = 0;
-            output->nextFrameNumber = 0;
-            output->bufferReplaced = false;
-
-            break;
-        default:
-            ALOGE("BufferHubProducer::connect: unknow API %d", api);
-            return BAD_VALUE;
-    }
-
-    return NO_ERROR;
-}
-
-status_t BufferHubProducer::disconnect(int api, DisconnectMode /*mode*/) {
-    // Consumer interaction are actually handled by buffer hub, and we need
-    // to maintain consumer operations here.  We only need to perform basic input
-    // parameter checks here.
-    ALOGV(__FUNCTION__);
-
-    std::unique_lock<std::mutex> lock(mutex_);
-
-    if (kNoConnectedApi == connected_api_) {
-        return NO_INIT;
-    } else if (api != connected_api_) {
-        return BAD_VALUE;
-    }
-
-    FreeAllBuffers();
-    connected_api_ = kNoConnectedApi;
-    return NO_ERROR;
-}
-
-status_t BufferHubProducer::setSidebandStream(const sp<NativeHandle>& stream) {
-    if (stream != nullptr) {
-        // TODO(jwcai) Investigate how is is used, maybe use BufferHubBuffer's
-        // metadata.
-        ALOGE("SidebandStream is not currently supported.");
-        return INVALID_OPERATION;
-    }
-    return NO_ERROR;
-}
-
-void BufferHubProducer::allocateBuffers(uint32_t /* width */, uint32_t /* height */,
-                                        PixelFormat /* format */, uint64_t /* usage */) {
-    // TODO(jwcai) |allocateBuffers| aims to preallocate up to the maximum number
-    // of buffers permitted by the current BufferQueue configuration (aka
-    // |max_buffer_count_|).
-    ALOGE("BufferHubProducer::allocateBuffers not implemented.");
-}
-
-status_t BufferHubProducer::allowAllocation(bool /* allow */) {
-    ALOGE("BufferHubProducer::allowAllocation not implemented.");
-    return INVALID_OPERATION;
-}
-
-status_t BufferHubProducer::setGenerationNumber(uint32_t generation_number) {
-    ALOGV(__FUNCTION__);
-
-    std::unique_lock<std::mutex> lock(mutex_);
-    generation_number_ = generation_number;
-    return NO_ERROR;
-}
-
-String8 BufferHubProducer::getConsumerName() const {
-    // BufferHub based implementation could have one to many producer/consumer
-    // relationship, thus |getConsumerName| from the producer side does not
-    // make any sense.
-    ALOGE("BufferHubProducer::getConsumerName not supported.");
-    return String8("BufferHubQueue::StubConsumer");
-}
-
-status_t BufferHubProducer::setSharedBufferMode(bool shared_buffer_mode) {
-    if (shared_buffer_mode) {
-        ALOGE("BufferHubProducer::setSharedBufferMode(true) is not supported.");
-        // TODO(b/36373181) Front buffer mode for buffer hub queue as ANativeWindow.
-        return INVALID_OPERATION;
-    }
-    // Setting to default should just work as a no-op.
-    return NO_ERROR;
-}
-
-status_t BufferHubProducer::setAutoRefresh(bool auto_refresh) {
-    if (auto_refresh) {
-        ALOGE("BufferHubProducer::setAutoRefresh(true) is not supported.");
-        return INVALID_OPERATION;
-    }
-    // Setting to default should just work as a no-op.
-    return NO_ERROR;
-}
-
-status_t BufferHubProducer::setDequeueTimeout(nsecs_t timeout) {
-    ALOGV(__FUNCTION__);
-
-    std::unique_lock<std::mutex> lock(mutex_);
-    dequeue_timeout_ms_ = static_cast<int>(timeout / (1000 * 1000));
-    return NO_ERROR;
-}
-
-status_t BufferHubProducer::getLastQueuedBuffer(sp<GraphicBuffer>* /* out_buffer */,
-                                                sp<Fence>* /* out_fence */,
-                                                float /*out_transform_matrix*/[16]) {
-    ALOGE("BufferHubProducer::getLastQueuedBuffer not implemented.");
-    return INVALID_OPERATION;
-}
-
-void BufferHubProducer::getFrameTimestamps(FrameEventHistoryDelta* /*outDelta*/) {
-    ALOGE("BufferHubProducer::getFrameTimestamps not implemented.");
-}
-
-status_t BufferHubProducer::getUniqueId(uint64_t* out_id) const {
-    ALOGV(__FUNCTION__);
-
-    *out_id = unique_id_;
-    return NO_ERROR;
-}
-
-status_t BufferHubProducer::getConsumerUsage(uint64_t* out_usage) const {
-    ALOGV(__FUNCTION__);
-
-    // same value as returned by querying NATIVE_WINDOW_CONSUMER_USAGE_BITS
-    *out_usage = 0;
-    return NO_ERROR;
-}
-
-status_t BufferHubProducer::TakeAsParcelable(ProducerQueueParcelable* out_parcelable) {
-    if (!out_parcelable || out_parcelable->IsValid()) return BAD_VALUE;
-
-    if (connected_api_ != kNoConnectedApi) {
-        ALOGE("BufferHubProducer::TakeAsParcelable: BufferHubProducer has "
-              "connected client. Must disconnect first.");
-        return BAD_VALUE;
-    }
-
-    if (!queue_->is_connected()) {
-        ALOGE("BufferHubProducer::TakeAsParcelable: This BufferHubProducer "
-              "is not connected to bufferhud. Has it been taken out as a "
-              "parcelable?");
-        return BAD_VALUE;
-    }
-
-    auto status = queue_->TakeAsParcelable();
-    if (!status) {
-        ALOGE("BufferHubProducer::TakeAsParcelable: Failed to take out "
-              "ProducuerQueueParcelable from the producer queue, error: %s.",
-              status.GetErrorMessage().c_str());
-        return BAD_VALUE;
-    }
-
-    *out_parcelable = status.take();
-    return NO_ERROR;
-}
-
-status_t BufferHubProducer::AllocateBuffer(uint32_t width, uint32_t height, uint32_t layer_count,
-                                           PixelFormat format, uint64_t usage) {
-    auto status = queue_->AllocateBuffer(width, height, layer_count, uint32_t(format), usage);
-    if (!status) {
-        ALOGE("BufferHubProducer::AllocateBuffer: Failed to allocate buffer: %s",
-              status.GetErrorMessage().c_str());
-        return NO_MEMORY;
-    }
-
-    size_t slot = status.get();
-    auto producer_buffer = queue_->GetBuffer(slot);
-
-    LOG_ALWAYS_FATAL_IF(producer_buffer == nullptr,
-                        "Failed to get the producer buffer at slot: %zu", slot);
-
-    buffers_[slot].mProducerBuffer = producer_buffer;
-
-    return NO_ERROR;
-}
-
-status_t BufferHubProducer::RemoveBuffer(size_t slot) {
-    auto status = queue_->RemoveBuffer(slot);
-    if (!status) {
-        ALOGE("BufferHubProducer::RemoveBuffer: Failed to remove buffer at slot: %zu, error: %s.",
-              slot, status.GetErrorMessage().c_str());
-        return INVALID_OPERATION;
-    }
-
-    // Reset in memory objects related the the buffer.
-    buffers_[slot].mProducerBuffer = nullptr;
-    buffers_[slot].mBufferState.detachProducer();
-    buffers_[slot].mFence = Fence::NO_FENCE;
-    buffers_[slot].mGraphicBuffer = nullptr;
-    buffers_[slot].mRequestBufferCalled = false;
-    return NO_ERROR;
-}
-
-status_t BufferHubProducer::FreeAllBuffers() {
-    for (size_t slot = 0; slot < BufferHubQueue::kMaxQueueCapacity; slot++) {
-        // Reset in memory objects related the the buffer.
-        buffers_[slot].mProducerBuffer = nullptr;
-        buffers_[slot].mBufferState.reset();
-        buffers_[slot].mFence = Fence::NO_FENCE;
-        buffers_[slot].mGraphicBuffer = nullptr;
-        buffers_[slot].mRequestBufferCalled = false;
-    }
-
-    auto status = queue_->FreeAllBuffers();
-    if (!status) {
-        ALOGE("BufferHubProducer::FreeAllBuffers: Failed to free all buffers on "
-              "the queue: %s",
-              status.GetErrorMessage().c_str());
-    }
-
-    if (queue_->capacity() != 0 || queue_->count() != 0) {
-        LOG_ALWAYS_FATAL("BufferHubProducer::FreeAllBuffers: Not all buffers are freed.");
-    }
-
-    return NO_ERROR;
-}
-
-status_t BufferHubProducer::exportToParcel(Parcel* parcel) {
-    status_t res = TakeAsParcelable(&pending_producer_parcelable_);
-    if (res != NO_ERROR) return res;
-
-    if (!pending_producer_parcelable_.IsValid()) {
-        ALOGE("BufferHubProducer::exportToParcel: Invalid parcelable object.");
-        return BAD_VALUE;
-    }
-
-    res = parcel->writeUint32(USE_BUFFER_HUB);
-    if (res != NO_ERROR) {
-        ALOGE("BufferHubProducer::exportToParcel: Cannot write magic, res=%d.", res);
-        return res;
-    }
-
-    return pending_producer_parcelable_.writeToParcel(parcel);
-}
-
-IBinder* BufferHubProducer::onAsBinder() {
-    ALOGE("BufferHubProducer::onAsBinder: BufferHubProducer should never be used as an Binder "
-          "object.");
-    return nullptr;
-}
-
-} // namespace android
diff --git a/libs/gui/BufferQueue.cpp b/libs/gui/BufferQueue.cpp
index c1d92a2..66cad03 100644
--- a/libs/gui/BufferQueue.cpp
+++ b/libs/gui/BufferQueue.cpp
@@ -18,11 +18,6 @@
 #define ATRACE_TAG ATRACE_TAG_GRAPHICS
 //#define LOG_NDEBUG 0
 
-#ifndef NO_BUFFERHUB
-#include <gui/BufferHubConsumer.h>
-#include <gui/BufferHubProducer.h>
-#endif
-
 #include <gui/BufferQueue.h>
 #include <gui/BufferQueueConsumer.h>
 #include <gui/BufferQueueCore.h>
@@ -127,32 +122,4 @@
     *outConsumer = consumer;
 }
 
-#ifndef NO_BUFFERHUB
-void BufferQueue::createBufferHubQueue(sp<IGraphicBufferProducer>* outProducer,
-                                       sp<IGraphicBufferConsumer>* outConsumer) {
-    LOG_ALWAYS_FATAL_IF(outProducer == nullptr, "BufferQueue: outProducer must not be NULL");
-    LOG_ALWAYS_FATAL_IF(outConsumer == nullptr, "BufferQueue: outConsumer must not be NULL");
-
-    sp<IGraphicBufferProducer> producer;
-    sp<IGraphicBufferConsumer> consumer;
-
-    dvr::ProducerQueueConfigBuilder configBuilder;
-    std::shared_ptr<dvr::ProducerQueue> producerQueue =
-            dvr::ProducerQueue::Create(configBuilder.Build(), dvr::UsagePolicy{});
-    LOG_ALWAYS_FATAL_IF(producerQueue == nullptr, "BufferQueue: failed to create ProducerQueue.");
-
-    std::shared_ptr<dvr::ConsumerQueue> consumerQueue = producerQueue->CreateConsumerQueue();
-    LOG_ALWAYS_FATAL_IF(consumerQueue == nullptr, "BufferQueue: failed to create ConsumerQueue.");
-
-    producer = BufferHubProducer::Create(producerQueue);
-    consumer = BufferHubConsumer::Create(consumerQueue);
-
-    LOG_ALWAYS_FATAL_IF(producer == nullptr, "BufferQueue: failed to create BufferQueueProducer");
-    LOG_ALWAYS_FATAL_IF(consumer == nullptr, "BufferQueue: failed to create BufferQueueConsumer");
-
-    *outProducer = producer;
-    *outConsumer = consumer;
-}
-#endif
-
 }; // namespace android
diff --git a/libs/gui/DisplayInfo.cpp b/libs/gui/DisplayInfo.cpp
index 52d9540..bd640df 100644
--- a/libs/gui/DisplayInfo.cpp
+++ b/libs/gui/DisplayInfo.cpp
@@ -20,8 +20,13 @@
 #include <gui/DisplayInfo.h>
 #include <private/gui/ParcelUtils.h>
 
+#include <android-base/stringprintf.h>
 #include <log/log.h>
 
+#include <inttypes.h>
+
+#define INDENT "  "
+
 namespace android::gui {
 
 // --- DisplayInfo ---
@@ -67,4 +72,17 @@
     return OK;
 }
 
+void DisplayInfo::dump(std::string& out, const char* prefix) const {
+    using android::base::StringAppendF;
+
+    out += prefix;
+    StringAppendF(&out, "DisplayViewport[id=%" PRId32 "]\n", displayId);
+    out += prefix;
+    StringAppendF(&out, INDENT "Width=%" PRId32 ", Height=%" PRId32 "\n", logicalWidth,
+                  logicalHeight);
+    std::string transformPrefix(prefix);
+    transformPrefix.append(INDENT);
+    transform.dump(out, "Transform", transformPrefix.c_str());
+}
+
 } // namespace android::gui
diff --git a/libs/gui/IGraphicBufferProducer.cpp b/libs/gui/IGraphicBufferProducer.cpp
index 797069c..918ff2d 100644
--- a/libs/gui/IGraphicBufferProducer.cpp
+++ b/libs/gui/IGraphicBufferProducer.cpp
@@ -27,10 +27,6 @@
 #include <binder/Parcel.h>
 #include <binder/IInterface.h>
 
-#ifndef NO_BUFFERHUB
-#include <gui/BufferHubProducer.h>
-#endif
-
 #include <gui/bufferqueue/1.0/H2BGraphicBufferProducer.h>
 #include <gui/bufferqueue/2.0/H2BGraphicBufferProducer.h>
 #include <gui/BufferQueueDefs.h>
@@ -1012,17 +1008,7 @@
         }
         case USE_BUFFER_HUB: {
             ALOGE("createFromParcel: BufferHub not implemented.");
-#ifndef NO_BUFFERHUB
-            dvr::ProducerQueueParcelable producerParcelable;
-            res = producerParcelable.readFromParcel(parcel);
-            if (res != NO_ERROR) {
-                ALOGE("createFromParcel: Failed to read from parcel, error=%d", res);
-                return nullptr;
-            }
-            return BufferHubProducer::Create(std::move(producerParcelable));
-#else
             return nullptr;
-#endif
         }
         default: {
             ALOGE("createFromParcel: Unexpected mgaic: 0x%x.", outMagic);
diff --git a/libs/gui/ISurfaceComposer.cpp b/libs/gui/ISurfaceComposer.cpp
index 4c887ec..a77ca04 100644
--- a/libs/gui/ISurfaceComposer.cpp
+++ b/libs/gui/ISurfaceComposer.cpp
@@ -60,11 +60,11 @@
     virtual ~BpSurfaceComposer();
 
     status_t setTransactionState(const FrameTimelineInfo& frameTimelineInfo,
-                                 const Vector<ComposerState>& state,
-                                 const Vector<DisplayState>& displays, uint32_t flags,
-                                 const sp<IBinder>& applyToken, const InputWindowCommands& commands,
-                                 int64_t desiredPresentTime, bool isAutoTimestamp,
-                                 const client_cache_t& uncacheBuffer, bool hasListenerCallbacks,
+                                 Vector<ComposerState>& state, const Vector<DisplayState>& displays,
+                                 uint32_t flags, const sp<IBinder>& applyToken,
+                                 const InputWindowCommands& commands, int64_t desiredPresentTime,
+                                 bool isAutoTimestamp, const client_cache_t& uncacheBuffer,
+                                 bool hasListenerCallbacks,
                                  const std::vector<ListenerCallbacks>& listenerCallbacks,
                                  uint64_t transactionId) override {
         Parcel data, reply;
diff --git a/libs/gui/ITransactionCompletedListener.cpp b/libs/gui/ITransactionCompletedListener.cpp
index e4b8bad..2b25b61 100644
--- a/libs/gui/ITransactionCompletedListener.cpp
+++ b/libs/gui/ITransactionCompletedListener.cpp
@@ -17,6 +17,9 @@
 #define LOG_TAG "ITransactionCompletedListener"
 //#define LOG_NDEBUG 0
 
+#include <cstdint>
+#include <optional>
+
 #include <gui/ISurfaceComposer.h>
 #include <gui/ITransactionCompletedListener.h>
 #include <gui/LayerState.h>
@@ -30,7 +33,7 @@
     ON_TRANSACTION_COMPLETED = IBinder::FIRST_CALL_TRANSACTION,
     ON_RELEASE_BUFFER,
     ON_TRANSACTION_QUEUE_STALLED,
-    LAST = ON_RELEASE_BUFFER,
+    LAST = ON_TRANSACTION_QUEUE_STALLED,
 };
 
 } // Anonymous namespace
@@ -126,7 +129,12 @@
     } else {
         SAFE_PARCEL(output->writeBool, false);
     }
-    SAFE_PARCEL(output->writeUint32, transformHint);
+
+    SAFE_PARCEL(output->writeBool, transformHint.has_value());
+    if (transformHint.has_value()) {
+        output->writeUint32(transformHint.value());
+    }
+
     SAFE_PARCEL(output->writeUint32, currentMaxAcquiredBufferCount);
     SAFE_PARCEL(output->writeParcelable, eventStats);
     SAFE_PARCEL(output->writeInt32, static_cast<int32_t>(jankData.size()));
@@ -156,7 +164,16 @@
         previousReleaseFence = new Fence();
         SAFE_PARCEL(input->read, *previousReleaseFence);
     }
-    SAFE_PARCEL(input->readUint32, &transformHint);
+    bool hasTransformHint = false;
+    SAFE_PARCEL(input->readBool, &hasTransformHint);
+    if (hasTransformHint) {
+        uint32_t tempTransformHint;
+        SAFE_PARCEL(input->readUint32, &tempTransformHint);
+        transformHint = std::make_optional(tempTransformHint);
+    } else {
+        transformHint = std::nullopt;
+    }
+
     SAFE_PARCEL(input->readUint32, &currentMaxAcquiredBufferCount);
     SAFE_PARCEL(input->readParcelable, &eventStats);
 
@@ -273,15 +290,17 @@
 
     void onReleaseBuffer(ReleaseCallbackId callbackId, sp<Fence> releaseFence,
                          uint32_t currentMaxAcquiredBufferCount) override {
-        callRemoteAsync<decltype(
-                &ITransactionCompletedListener::onReleaseBuffer)>(Tag::ON_RELEASE_BUFFER,
-                                                                  callbackId, releaseFence,
-                                                                  currentMaxAcquiredBufferCount);
+        callRemoteAsync<decltype(&ITransactionCompletedListener::
+                                         onReleaseBuffer)>(Tag::ON_RELEASE_BUFFER, callbackId,
+                                                           releaseFence,
+                                                           currentMaxAcquiredBufferCount);
     }
 
-    void onTransactionQueueStalled() override {
-        callRemoteAsync<decltype(&ITransactionCompletedListener::onTransactionQueueStalled)>(
-            Tag::ON_TRANSACTION_QUEUE_STALLED);
+    void onTransactionQueueStalled(const String8& reason) override {
+        callRemoteAsync<
+                decltype(&ITransactionCompletedListener::
+                                 onTransactionQueueStalled)>(Tag::ON_TRANSACTION_QUEUE_STALLED,
+                                                             reason);
     }
 };
 
diff --git a/libs/gui/LayerState.cpp b/libs/gui/LayerState.cpp
index 2759c58..95962af 100644
--- a/libs/gui/LayerState.cpp
+++ b/libs/gui/LayerState.cpp
@@ -24,10 +24,31 @@
 #include <binder/Parcel.h>
 #include <gui/IGraphicBufferProducer.h>
 #include <gui/LayerState.h>
+#include <gui/SurfaceControl.h>
 #include <private/gui/ParcelUtils.h>
 #include <system/window.h>
 #include <utils/Errors.h>
 
+#define CHECK_DIFF(DIFF_RESULT, CHANGE_FLAG, OTHER, FIELD)          \
+    {                                                               \
+        if ((OTHER.what & CHANGE_FLAG) && (FIELD != OTHER.FIELD)) { \
+            DIFF_RESULT |= CHANGE_FLAG;                             \
+        }                                                           \
+    }
+
+#define CHECK_DIFF2(DIFF_RESULT, CHANGE_FLAG, OTHER, FIELD1, FIELD2) \
+    {                                                                \
+        CHECK_DIFF(DIFF_RESULT, CHANGE_FLAG, OTHER, FIELD1)          \
+        CHECK_DIFF(DIFF_RESULT, CHANGE_FLAG, OTHER, FIELD2)          \
+    }
+
+#define CHECK_DIFF3(DIFF_RESULT, CHANGE_FLAG, OTHER, FIELD1, FIELD2, FIELD3) \
+    {                                                                        \
+        CHECK_DIFF(DIFF_RESULT, CHANGE_FLAG, OTHER, FIELD1)                  \
+        CHECK_DIFF(DIFF_RESULT, CHANGE_FLAG, OTHER, FIELD2)                  \
+        CHECK_DIFF(DIFF_RESULT, CHANGE_FLAG, OTHER, FIELD3)                  \
+    }
+
 namespace android {
 
 using gui::FocusRequest;
@@ -40,13 +61,13 @@
         x(0),
         y(0),
         z(0),
-        alpha(0),
         flags(0),
         mask(0),
         reserved(0),
         cornerRadius(0.0f),
         backgroundBlurRadius(0),
-        transform(0),
+        color(0),
+        bufferTransform(0),
         transformToDisplayInverse(false),
         crop(Rect::INVALID_RECT),
         dataspace(ui::Dataspace::UNKNOWN),
@@ -83,20 +104,19 @@
     SAFE_PARCEL(output.writeFloat, y);
     SAFE_PARCEL(output.writeInt32, z);
     SAFE_PARCEL(output.writeUint32, layerStack.id);
-    SAFE_PARCEL(output.writeFloat, alpha);
     SAFE_PARCEL(output.writeUint32, flags);
     SAFE_PARCEL(output.writeUint32, mask);
     SAFE_PARCEL(matrix.write, output);
     SAFE_PARCEL(output.write, crop);
-    SAFE_PARCEL(SurfaceControl::writeNullableToParcel, output, reparentSurfaceControl);
     SAFE_PARCEL(SurfaceControl::writeNullableToParcel, output, relativeLayerSurfaceControl);
     SAFE_PARCEL(SurfaceControl::writeNullableToParcel, output, parentSurfaceControlForChild);
     SAFE_PARCEL(output.writeFloat, color.r);
     SAFE_PARCEL(output.writeFloat, color.g);
     SAFE_PARCEL(output.writeFloat, color.b);
+    SAFE_PARCEL(output.writeFloat, color.a);
     SAFE_PARCEL(windowInfoHandle->writeToParcel, &output);
     SAFE_PARCEL(output.write, transparentRegion);
-    SAFE_PARCEL(output.writeUint32, transform);
+    SAFE_PARCEL(output.writeUint32, bufferTransform);
     SAFE_PARCEL(output.writeBool, transformToDisplayInverse);
     SAFE_PARCEL(output.writeBool, borderEnabled);
     SAFE_PARCEL(output.writeFloat, borderWidth);
@@ -177,7 +197,6 @@
     SAFE_PARCEL(input.readFloat, &y);
     SAFE_PARCEL(input.readInt32, &z);
     SAFE_PARCEL(input.readUint32, &layerStack.id);
-    SAFE_PARCEL(input.readFloat, &alpha);
 
     SAFE_PARCEL(input.readUint32, &flags);
 
@@ -185,7 +204,6 @@
 
     SAFE_PARCEL(matrix.read, input);
     SAFE_PARCEL(input.read, crop);
-    SAFE_PARCEL(SurfaceControl::readNullableFromParcel, input, &reparentSurfaceControl);
 
     SAFE_PARCEL(SurfaceControl::readNullableFromParcel, input, &relativeLayerSurfaceControl);
     SAFE_PARCEL(SurfaceControl::readNullableFromParcel, input, &parentSurfaceControlForChild);
@@ -197,10 +215,13 @@
     color.g = tmpFloat;
     SAFE_PARCEL(input.readFloat, &tmpFloat);
     color.b = tmpFloat;
+    SAFE_PARCEL(input.readFloat, &tmpFloat);
+    color.a = tmpFloat;
+
     SAFE_PARCEL(windowInfoHandle->readFromParcel, &input);
 
     SAFE_PARCEL(input.read, transparentRegion);
-    SAFE_PARCEL(input.readUint32, &transform);
+    SAFE_PARCEL(input.readUint32, &bufferTransform);
     SAFE_PARCEL(input.readBool, &transformToDisplayInverse);
     SAFE_PARCEL(input.readBool, &borderEnabled);
     SAFE_PARCEL(input.readFloat, &tmpFloat);
@@ -453,7 +474,7 @@
     }
     if (other.what & eAlphaChanged) {
         what |= eAlphaChanged;
-        alpha = other.alpha;
+        color.a = other.color.a;
     }
     if (other.what & eMatrixChanged) {
         what |= eMatrixChanged;
@@ -495,12 +516,9 @@
         what |= eReparent;
         parentSurfaceControlForChild = other.parentSurfaceControlForChild;
     }
-    if (other.what & eDestroySurface) {
-        what |= eDestroySurface;
-    }
-    if (other.what & eTransformChanged) {
-        what |= eTransformChanged;
-        transform = other.transform;
+    if (other.what & eBufferTransformChanged) {
+        what |= eBufferTransformChanged;
+        bufferTransform = other.bufferTransform;
     }
     if (other.what & eTransformToDisplayInverseChanged) {
         what |= eTransformToDisplayInverseChanged;
@@ -547,7 +565,7 @@
     }
     if (other.what & eBackgroundColorChanged) {
         what |= eBackgroundColorChanged;
-        color = other.color;
+        color.rgb = other.color.rgb;
         bgColorAlpha = other.bgColorAlpha;
         bgColorDataspace = other.bgColorDataspace;
     }
@@ -612,7 +630,7 @@
     }
     if (other.what & eColorChanged) {
         what |= eColorChanged;
-        color = other.color;
+        color.rgb = other.color.rgb;
     }
     if (other.what & eColorSpaceAgnosticChanged) {
         what |= eColorSpaceAgnosticChanged;
@@ -629,6 +647,74 @@
     }
 }
 
+uint64_t layer_state_t::diff(const layer_state_t& other) const {
+    uint64_t diff = 0;
+    CHECK_DIFF2(diff, ePositionChanged, other, x, y);
+    if (other.what & eLayerChanged) {
+        diff |= eLayerChanged;
+        diff &= ~eRelativeLayerChanged;
+    }
+    CHECK_DIFF(diff, eAlphaChanged, other, color.a);
+    CHECK_DIFF(diff, eMatrixChanged, other, matrix);
+    if (other.what & eTransparentRegionChanged &&
+        (!transparentRegion.hasSameRects(other.transparentRegion))) {
+        diff |= eTransparentRegionChanged;
+    }
+    if (other.what & eFlagsChanged) {
+        uint64_t changedFlags = (flags & other.mask) ^ (other.flags & other.mask);
+        if (changedFlags) diff |= eFlagsChanged;
+    }
+    CHECK_DIFF(diff, eLayerStackChanged, other, layerStack);
+    CHECK_DIFF(diff, eCornerRadiusChanged, other, cornerRadius);
+    CHECK_DIFF(diff, eBackgroundBlurRadiusChanged, other, backgroundBlurRadius);
+    if (other.what & eBlurRegionsChanged) diff |= eBlurRegionsChanged;
+    if (other.what & eRelativeLayerChanged) {
+        diff |= eRelativeLayerChanged;
+        diff &= ~eLayerChanged;
+    }
+    if (other.what & eReparent &&
+        !SurfaceControl::isSameSurface(parentSurfaceControlForChild,
+                                       other.parentSurfaceControlForChild)) {
+        diff |= eReparent;
+    }
+    CHECK_DIFF(diff, eBufferTransformChanged, other, bufferTransform);
+    CHECK_DIFF(diff, eTransformToDisplayInverseChanged, other, transformToDisplayInverse);
+    CHECK_DIFF(diff, eCropChanged, other, crop);
+    if (other.what & eBufferChanged) diff |= eBufferChanged;
+    CHECK_DIFF(diff, eDataspaceChanged, other, dataspace);
+    CHECK_DIFF(diff, eHdrMetadataChanged, other, hdrMetadata);
+    if (other.what & eSurfaceDamageRegionChanged &&
+        (!surfaceDamageRegion.hasSameRects(other.surfaceDamageRegion))) {
+        diff |= eSurfaceDamageRegionChanged;
+    }
+    CHECK_DIFF(diff, eApiChanged, other, api);
+    if (other.what & eSidebandStreamChanged) diff |= eSidebandStreamChanged;
+    CHECK_DIFF(diff, eApiChanged, other, api);
+    CHECK_DIFF(diff, eColorTransformChanged, other, colorTransform);
+    if (other.what & eHasListenerCallbacksChanged) diff |= eHasListenerCallbacksChanged;
+    if (other.what & eInputInfoChanged) diff |= eInputInfoChanged;
+    CHECK_DIFF3(diff, eBackgroundColorChanged, other, color.rgb, bgColorAlpha, bgColorDataspace);
+    if (other.what & eMetadataChanged) diff |= eMetadataChanged;
+    CHECK_DIFF(diff, eShadowRadiusChanged, other, shadowRadius);
+    CHECK_DIFF3(diff, eRenderBorderChanged, other, borderEnabled, borderWidth, borderColor);
+    CHECK_DIFF(diff, eDefaultFrameRateCompatibilityChanged, other, defaultFrameRateCompatibility);
+    CHECK_DIFF(diff, eFrameRateSelectionPriority, other, frameRateSelectionPriority);
+    CHECK_DIFF3(diff, eFrameRateChanged, other, frameRate, frameRateCompatibility,
+                changeFrameRateStrategy);
+    CHECK_DIFF(diff, eFixedTransformHintChanged, other, fixedTransformHint);
+    CHECK_DIFF(diff, eAutoRefreshChanged, other, autoRefresh);
+    CHECK_DIFF(diff, eTrustedOverlayChanged, other, isTrustedOverlay);
+    CHECK_DIFF(diff, eStretchChanged, other, stretchEffect);
+    CHECK_DIFF(diff, eBufferCropChanged, other, bufferCrop);
+    CHECK_DIFF(diff, eDestinationFrameChanged, other, destinationFrame);
+    if (other.what & eProducerDisconnect) diff |= eProducerDisconnect;
+    CHECK_DIFF(diff, eDropInputModeChanged, other, dropInputMode);
+    CHECK_DIFF(diff, eColorChanged, other, color.rgb);
+    CHECK_DIFF(diff, eColorSpaceAgnosticChanged, other, colorSpaceAgnostic);
+    CHECK_DIFF(diff, eDimmingEnabledChanged, other, dimmingEnabled);
+    return diff;
+}
+
 bool layer_state_t::hasBufferChanges() const {
     return what & layer_state_t::eBufferChanged;
 }
diff --git a/libs/gui/Surface.cpp b/libs/gui/Surface.cpp
index c4fb1cf..edb18a8 100644
--- a/libs/gui/Surface.cpp
+++ b/libs/gui/Surface.cpp
@@ -1108,9 +1108,12 @@
     ATRACE_CALL();
     auto& mapper = GraphicBufferMapper::get();
     mapper.setDataspace(buffer->handle, static_cast<ui::Dataspace>(queueBufferInput.dataSpace));
-    mapper.setSmpte2086(buffer->handle, queueBufferInput.getHdrMetadata().getSmpte2086());
-    mapper.setCta861_3(buffer->handle, queueBufferInput.getHdrMetadata().getCta8613());
-    mapper.setSmpte2094_40(buffer->handle, queueBufferInput.getHdrMetadata().getHdr10Plus());
+    if (mHdrMetadataIsSet & HdrMetadata::SMPTE2086)
+        mapper.setSmpte2086(buffer->handle, queueBufferInput.getHdrMetadata().getSmpte2086());
+    if (mHdrMetadataIsSet & HdrMetadata::CTA861_3)
+        mapper.setCta861_3(buffer->handle, queueBufferInput.getHdrMetadata().getCta8613());
+    if (mHdrMetadataIsSet & HdrMetadata::HDR10PLUS)
+        mapper.setSmpte2094_40(buffer->handle, queueBufferInput.getHdrMetadata().getHdr10Plus());
 }
 
 void Surface::onBufferQueuedLocked(int slot, sp<Fence> fence,
@@ -2252,6 +2255,7 @@
 int Surface::setBuffersSmpte2086Metadata(const android_smpte2086_metadata* metadata) {
     ALOGV("Surface::setBuffersSmpte2086Metadata");
     Mutex::Autolock lock(mMutex);
+    mHdrMetadataIsSet |= HdrMetadata::SMPTE2086;
     if (metadata) {
         mHdrMetadata.smpte2086 = *metadata;
         mHdrMetadata.validTypes |= HdrMetadata::SMPTE2086;
@@ -2264,6 +2268,7 @@
 int Surface::setBuffersCta8613Metadata(const android_cta861_3_metadata* metadata) {
     ALOGV("Surface::setBuffersCta8613Metadata");
     Mutex::Autolock lock(mMutex);
+    mHdrMetadataIsSet |= HdrMetadata::CTA861_3;
     if (metadata) {
         mHdrMetadata.cta8613 = *metadata;
         mHdrMetadata.validTypes |= HdrMetadata::CTA861_3;
@@ -2276,6 +2281,7 @@
 int Surface::setBuffersHdr10PlusMetadata(const size_t size, const uint8_t* metadata) {
     ALOGV("Surface::setBuffersBlobMetadata");
     Mutex::Autolock lock(mMutex);
+    mHdrMetadataIsSet |= HdrMetadata::HDR10PLUS;
     if (size > 0) {
         mHdrMetadata.hdr10plus.assign(metadata, metadata + size);
         mHdrMetadata.validTypes |= HdrMetadata::HDR10PLUS;
diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp
index 81b4d3d..325c294 100644
--- a/libs/gui/SurfaceComposerClient.cpp
+++ b/libs/gui/SurfaceComposerClient.cpp
@@ -25,6 +25,7 @@
 #include <android/gui/ISurfaceComposerClient.h>
 #include <android/gui/IWindowInfosListener.h>
 #include <android/os/IInputConstants.h>
+#include <gui/TraceUtils.h>
 #include <utils/Errors.h>
 #include <utils/Log.h>
 #include <utils/SortedVector.h>
@@ -385,10 +386,11 @@
                                       surfaceStats.previousReleaseFence, surfaceStats.transformHint,
                                       surfaceStats.eventStats,
                                       surfaceStats.currentMaxAcquiredBufferCount);
-                if (callbacksMap[callbackId].surfaceControls[surfaceStats.surfaceControl]) {
+                if (callbacksMap[callbackId].surfaceControls[surfaceStats.surfaceControl] &&
+                    surfaceStats.transformHint.has_value()) {
                     callbacksMap[callbackId]
                             .surfaceControls[surfaceStats.surfaceControl]
-                            ->setTransformHint(surfaceStats.transformHint);
+                            ->setTransformHint(*surfaceStats.transformHint);
                 }
                 // If there is buffer id set, we look up any pending client release buffer callbacks
                 // and call them. This is a performance optimization when we have a transaction
@@ -454,23 +456,24 @@
     }
 }
 
-void TransactionCompletedListener::onTransactionQueueStalled() {
-      std::unordered_map<void*, std::function<void()>> callbackCopy;
-      {
-          std::scoped_lock<std::mutex> lock(mMutex);
-          callbackCopy = mQueueStallListeners;
-      }
-      for (auto const& it : callbackCopy) {
-          it.second();
-      }
+void TransactionCompletedListener::onTransactionQueueStalled(const String8& reason) {
+    std::unordered_map<void*, std::function<void(const std::string&)>> callbackCopy;
+    {
+        std::scoped_lock<std::mutex> lock(mMutex);
+        callbackCopy = mQueueStallListeners;
+    }
+    for (auto const& it : callbackCopy) {
+        it.second(reason.c_str());
+    }
 }
 
-void TransactionCompletedListener::addQueueStallListener(std::function<void()> stallListener,
-                                                         void* id) {
+void TransactionCompletedListener::addQueueStallListener(
+        std::function<void(const std::string&)> stallListener, void* id) {
     std::scoped_lock<std::mutex> lock(mMutex);
     mQueueStallListeners[id] = stallListener;
 }
-void TransactionCompletedListener::removeQueueStallListener(void *id) {
+
+void TransactionCompletedListener::removeQueueStallListener(void* id) {
     std::scoped_lock<std::mutex> lock(mMutex);
     mQueueStallListeners.erase(id);
 }
@@ -909,10 +912,15 @@
     client_cache_t uncacheBuffer;
     uncacheBuffer.token = BufferCache::getInstance().getToken();
     uncacheBuffer.id = cacheId;
-
-    sf->setTransactionState(FrameTimelineInfo{}, {}, {}, ISurfaceComposer::eOneWay,
-                            Transaction::getDefaultApplyToken(), {}, systemTime(), true,
-                            uncacheBuffer, false, {}, generateId());
+    Vector<ComposerState> composerStates;
+    status_t status =
+            sf->setTransactionState(FrameTimelineInfo{}, composerStates, {},
+                                    ISurfaceComposer::eOneWay, Transaction::getDefaultApplyToken(),
+                                    {}, systemTime(), true, uncacheBuffer, false, {}, generateId());
+    if (status != NO_ERROR) {
+        ALOGE_AND_TRACE("SurfaceComposerClient::doUncacheBufferTransaction - %s",
+                        strerror(-status));
+    }
 }
 
 void SurfaceComposerClient::Transaction::cacheBuffers() {
@@ -1242,6 +1250,7 @@
     if ((mask & layer_state_t::eLayerOpaque) || (mask & layer_state_t::eLayerHidden) ||
         (mask & layer_state_t::eLayerSecure) || (mask & layer_state_t::eLayerSkipScreenshot) ||
         (mask & layer_state_t::eEnableBackpressure) ||
+        (mask & layer_state_t::eIgnoreDestinationFrame) ||
         (mask & layer_state_t::eLayerIsDisplayDecoration)) {
         s->what |= layer_state_t::eFlagsChanged;
     }
@@ -1293,7 +1302,7 @@
         ALOGE("SurfaceComposerClient::Transaction::setAlpha: invalid alpha %f, clamping", alpha);
     }
     s->what |= layer_state_t::eAlphaChanged;
-    s->alpha = std::clamp(alpha, 0.f, 1.f);
+    s->color.a = std::clamp(alpha, 0.f, 1.f);
 
     registerSurfaceControlForCallback(sc);
     return *this;
@@ -1424,7 +1433,7 @@
         return *this;
     }
     s->what |= layer_state_t::eColorChanged;
-    s->color = color;
+    s->color.rgb = color;
 
     registerSurfaceControlForCallback(sc);
     return *this;
@@ -1439,7 +1448,7 @@
     }
 
     s->what |= layer_state_t::eBackgroundColorChanged;
-    s->color = color;
+    s->color.rgb = color;
     s->bgColorAlpha = alpha;
     s->bgColorDataspace = dataspace;
 
@@ -1454,8 +1463,8 @@
         mStatus = BAD_INDEX;
         return *this;
     }
-    s->what |= layer_state_t::eTransformChanged;
-    s->transform = transform;
+    s->what |= layer_state_t::eBufferTransformChanged;
+    s->bufferTransform = transform;
 
     registerSurfaceControlForCallback(sc);
     return *this;
@@ -2321,10 +2330,14 @@
             outMode.sfVsyncOffset = mode.sfVsyncOffset;
             outMode.presentationDeadline = mode.presentationDeadline;
             outMode.group = mode.group;
+            std::transform(mode.supportedHdrTypes.begin(), mode.supportedHdrTypes.end(),
+                           std::back_inserter(outMode.supportedHdrTypes),
+                           [](const int32_t& value) { return static_cast<ui::Hdr>(value); });
             outInfo->supportedDisplayModes.push_back(outMode);
         }
 
         outInfo->activeDisplayModeId = ginfo.activeDisplayModeId;
+        outInfo->renderFrameRate = ginfo.renderFrameRate;
 
         outInfo->supportedColorModes.clear();
         outInfo->supportedColorModes.reserve(ginfo.supportedColorModes.size());
@@ -2367,42 +2380,22 @@
     return NAME_NOT_FOUND;
 }
 
-status_t SurfaceComposerClient::setDesiredDisplayModeSpecs(
-        const sp<IBinder>& displayToken, ui::DisplayModeId defaultMode, bool allowGroupSwitching,
-        float primaryRefreshRateMin, float primaryRefreshRateMax, float appRequestRefreshRateMin,
-        float appRequestRefreshRateMax) {
+status_t SurfaceComposerClient::setDesiredDisplayModeSpecs(const sp<IBinder>& displayToken,
+                                                           const gui::DisplayModeSpecs& specs) {
     binder::Status status =
-            ComposerServiceAIDL::getComposerService()
-                    ->setDesiredDisplayModeSpecs(displayToken, defaultMode, allowGroupSwitching,
-                                                 primaryRefreshRateMin, primaryRefreshRateMax,
-                                                 appRequestRefreshRateMin,
-                                                 appRequestRefreshRateMax);
+            ComposerServiceAIDL::getComposerService()->setDesiredDisplayModeSpecs(displayToken,
+                                                                                  specs);
     return statusTFromBinderStatus(status);
 }
 
 status_t SurfaceComposerClient::getDesiredDisplayModeSpecs(const sp<IBinder>& displayToken,
-                                                           ui::DisplayModeId* outDefaultMode,
-                                                           bool* outAllowGroupSwitching,
-                                                           float* outPrimaryRefreshRateMin,
-                                                           float* outPrimaryRefreshRateMax,
-                                                           float* outAppRequestRefreshRateMin,
-                                                           float* outAppRequestRefreshRateMax) {
-    if (!outDefaultMode || !outAllowGroupSwitching || !outPrimaryRefreshRateMin ||
-        !outPrimaryRefreshRateMax || !outAppRequestRefreshRateMin || !outAppRequestRefreshRateMax) {
+                                                           gui::DisplayModeSpecs* outSpecs) {
+    if (!outSpecs) {
         return BAD_VALUE;
     }
-    gui::DisplayModeSpecs specs;
     binder::Status status =
             ComposerServiceAIDL::getComposerService()->getDesiredDisplayModeSpecs(displayToken,
-                                                                                  &specs);
-    if (status.isOk()) {
-        *outDefaultMode = specs.defaultMode;
-        *outAllowGroupSwitching = specs.allowGroupSwitching;
-        *outPrimaryRefreshRateMin = specs.primaryRefreshRateMin;
-        *outPrimaryRefreshRateMax = specs.primaryRefreshRateMax;
-        *outAppRequestRefreshRateMin = specs.appRequestRefreshRateMin;
-        *outAppRequestRefreshRateMax = specs.appRequestRefreshRateMax;
-    }
+                                                                                  outSpecs);
     return statusTFromBinderStatus(status);
 }
 
@@ -2445,6 +2438,12 @@
     return statusTFromBinderStatus(status);
 }
 
+status_t SurfaceComposerClient::getOverlaySupport(gui::OverlayProperties* outProperties) {
+    binder::Status status =
+            ComposerServiceAIDL::getComposerService()->getOverlaySupport(outProperties);
+    return statusTFromBinderStatus(status);
+}
+
 status_t SurfaceComposerClient::setBootDisplayMode(const sp<IBinder>& display,
                                                    ui::DisplayModeId displayModeId) {
     binder::Status status = ComposerServiceAIDL::getComposerService()
diff --git a/libs/gui/SyncFeatures.cpp b/libs/gui/SyncFeatures.cpp
index 1a8fc1a..2d863c2 100644
--- a/libs/gui/SyncFeatures.cpp
+++ b/libs/gui/SyncFeatures.cpp
@@ -36,8 +36,12 @@
         mHasFenceSync(false),
         mHasWaitSync(false) {
     EGLDisplay dpy = eglGetDisplay(EGL_DEFAULT_DISPLAY);
-    // This can only be called after EGL has been initialized; otherwise the
-    // check below will abort.
+    // eglQueryString can only be called after EGL has been initialized;
+    // otherwise the check below will abort.  If RenderEngine is using SkiaVk,
+    // EGL will not have been initialized.  There's no problem with initializing
+    // it again here (it is ref counted), and then terminating it later.
+    EGLBoolean initialized = eglInitialize(dpy, nullptr, nullptr);
+    LOG_ALWAYS_FATAL_IF(!initialized, "eglInitialize failed");
     const char* exts = eglQueryString(dpy, EGL_EXTENSIONS);
     LOG_ALWAYS_FATAL_IF(exts == nullptr, "eglQueryString failed");
     if (strstr(exts, "EGL_ANDROID_native_fence_sync")) {
@@ -63,6 +67,8 @@
         mString.append(" EGL_KHR_wait_sync");
     }
     mString.append("]");
+    // Terminate EGL to match the eglInitialize above
+    eglTerminate(dpy);
 }
 
 bool SyncFeatures::useNativeFenceSync() const {
diff --git a/libs/gui/aidl/android/gui/DisplayMode.aidl b/libs/gui/aidl/android/gui/DisplayMode.aidl
index 3cd77f8..ce30426 100644
--- a/libs/gui/aidl/android/gui/DisplayMode.aidl
+++ b/libs/gui/aidl/android/gui/DisplayMode.aidl
@@ -27,6 +27,7 @@
     Size resolution;
     float xDpi = 0.0f;
     float yDpi = 0.0f;
+    int[] supportedHdrTypes;
 
     float refreshRate = 0.0f;
     long appVsyncOffset = 0;
diff --git a/libs/gui/aidl/android/gui/DisplayModeSpecs.aidl b/libs/gui/aidl/android/gui/DisplayModeSpecs.aidl
index fb4fcdf..af138c7 100644
--- a/libs/gui/aidl/android/gui/DisplayModeSpecs.aidl
+++ b/libs/gui/aidl/android/gui/DisplayModeSpecs.aidl
@@ -18,10 +18,58 @@
 
 /** @hide */
 parcelable DisplayModeSpecs {
+    /**
+     * Defines the refresh rates ranges that should be used by SF.
+     */
+    parcelable RefreshRateRanges {
+        /**
+         * Defines a range of refresh rates.
+         */
+        parcelable RefreshRateRange {
+            float min;
+            float max;
+        }
+
+        /**
+         *  The range of refresh rates that the display should run at.
+         */
+        RefreshRateRange physical;
+
+        /**
+         *  The range of refresh rates that apps should render at.
+         */
+        RefreshRateRange render;
+    }
+
+    /**
+     * Base mode ID. This is what system defaults to for all other settings, or
+     * if the refresh rate range is not available.
+     */
     int defaultMode;
+
+    /**
+     * If true this will allow switching between modes in different display configuration
+     * groups. This way the user may see visual interruptions when the display mode changes.
+     */
+
     boolean allowGroupSwitching;
-    float primaryRefreshRateMin;
-    float primaryRefreshRateMax;
-    float appRequestRefreshRateMin;
-    float appRequestRefreshRateMax;
+
+    /**
+     * The primary physical and render refresh rate ranges represent DisplayManager's general
+     * guidance on the display modes SurfaceFlinger will consider when switching refresh
+     * rates and scheduling the frame rate. Unless SurfaceFlinger has a specific reason to do
+     * otherwise, it will stay within this range.
+     */
+    RefreshRateRanges primaryRanges;
+
+    /**
+     * The app request physical and render refresh rate ranges allow SurfaceFlinger to consider
+     * more display modes when switching refresh rates. Although SurfaceFlinger will
+     * generally stay within the primary range, specific considerations, such as layer frame
+     * rate settings specified via the setFrameRate() API, may cause SurfaceFlinger to go
+     * outside the primary range. SurfaceFlinger never goes outside the app request range.
+     * The app request range will be greater than or equal to the primary refresh rate range,
+     * never smaller.
+     */
+    RefreshRateRanges appRequestRanges;
 }
diff --git a/libs/gui/aidl/android/gui/DynamicDisplayInfo.aidl b/libs/gui/aidl/android/gui/DynamicDisplayInfo.aidl
index 57e6081..3114929 100644
--- a/libs/gui/aidl/android/gui/DynamicDisplayInfo.aidl
+++ b/libs/gui/aidl/android/gui/DynamicDisplayInfo.aidl
@@ -27,6 +27,7 @@
     List<DisplayMode> supportedDisplayModes;
 
     int activeDisplayModeId;
+    float renderFrameRate;
 
     int[] supportedColorModes;
     int activeColorMode;
diff --git a/libs/gui/aidl/android/gui/ISurfaceComposer.aidl b/libs/gui/aidl/android/gui/ISurfaceComposer.aidl
index ca0b97f..40410fb 100644
--- a/libs/gui/aidl/android/gui/ISurfaceComposer.aidl
+++ b/libs/gui/aidl/android/gui/ISurfaceComposer.aidl
@@ -40,6 +40,7 @@
 import android.gui.IWindowInfosListener;
 import android.gui.LayerCaptureArgs;
 import android.gui.LayerDebugInfo;
+import android.gui.OverlayProperties;
 import android.gui.PullAtomData;
 import android.gui.ARect;
 import android.gui.StaticDisplayInfo;
@@ -326,25 +327,9 @@
     /**
      * Sets the refresh rate boundaries for the display.
      *
-     * The primary refresh rate range represents display manager's general guidance on the display
-     * modes we'll consider when switching refresh rates. Unless we get an explicit signal from an
-     * app, we should stay within this range.
-     *
-     * The app request refresh rate range allows us to consider more display modes when switching
-     * refresh rates. Although we should generally stay within the primary range, specific
-     * considerations, such as layer frame rate settings specified via the setFrameRate() api, may
-     * cause us to go outside the primary range. We never go outside the app request range. The app
-     * request range will be greater than or equal to the primary refresh rate range, never smaller.
-     *
-     * defaultMode is used to narrow the list of display modes SurfaceFlinger will consider
-     * switching between. Only modes with a mode group and resolution matching defaultMode
-     * will be considered for switching. The defaultMode corresponds to an ID of mode in the list
-     * of supported modes returned from getDynamicDisplayInfo().
+     * @see DisplayModeSpecs.aidl for details.
      */
-    void setDesiredDisplayModeSpecs(
-            IBinder displayToken, int defaultMode,
-            boolean allowGroupSwitching, float primaryRefreshRateMin, float primaryRefreshRateMax,
-            float appRequestRefreshRateMin, float appRequestRefreshRateMax);
+    void setDesiredDisplayModeSpecs(IBinder displayToken, in DisplayModeSpecs specs);
 
     DisplayModeSpecs getDesiredDisplayModeSpecs(IBinder displayToken);
 
@@ -472,4 +457,6 @@
     void addWindowInfosListener(IWindowInfosListener windowInfosListener);
 
     void removeWindowInfosListener(IWindowInfosListener windowInfosListener);
+
+    OverlayProperties getOverlaySupport();
 }
diff --git a/libs/binder/tests/parcel_fuzzer/GenericDataParcelable.aidl b/libs/gui/aidl/android/gui/OverlayProperties.aidl
similarity index 67%
copy from libs/binder/tests/parcel_fuzzer/GenericDataParcelable.aidl
copy to libs/gui/aidl/android/gui/OverlayProperties.aidl
index fc2542b..75cea15 100644
--- a/libs/binder/tests/parcel_fuzzer/GenericDataParcelable.aidl
+++ b/libs/gui/aidl/android/gui/OverlayProperties.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2022 The Android Open Source Project
+ * 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.
@@ -14,11 +14,13 @@
  * limitations under the License.
  */
 
-parcelable GenericDataParcelable {
-    int data;
-    float majorVersion;
-    float minorVersion;
-    IBinder binder;
-    ParcelFileDescriptor fileDescriptor;
-    int[] array;
-}
\ No newline at end of file
+package android.gui;
+
+/** @hide */
+parcelable OverlayProperties {
+    parcelable SupportedBufferCombinations {
+        int[] pixelFormats;
+        int[] dataspaces;
+    }
+    SupportedBufferCombinations[] combinations;
+}
diff --git a/libs/gui/fuzzer/Android.bp b/libs/gui/fuzzer/Android.bp
index cdc9376..82e1b5a 100644
--- a/libs/gui/fuzzer/Android.bp
+++ b/libs/gui/fuzzer/Android.bp
@@ -46,7 +46,7 @@
         "android.hardware.configstore-utils",
         "android.hardware.graphics.bufferqueue@1.0",
         "android.hardware.graphics.bufferqueue@2.0",
-        "android.hardware.power-V2-cpp",
+        "android.hardware.power-V4-cpp",
         "android.hidl.token@1.0",
         "libSurfaceFlingerProp",
         "libgui",
@@ -62,7 +62,6 @@
         "libutils",
         "libnativewindow",
         "libvndksupport",
-        "libbufferhubqueue",
     ],
     header_libs: [
         "libdvr_headers",
diff --git a/libs/gui/fuzzer/libgui_fuzzer_utils.h b/libs/gui/fuzzer/libgui_fuzzer_utils.h
index 315b925..9d1ee8f 100644
--- a/libs/gui/fuzzer/libgui_fuzzer_utils.h
+++ b/libs/gui/fuzzer/libgui_fuzzer_utils.h
@@ -127,9 +127,7 @@
     MOCK_METHOD(binder::Status, removeTunnelModeEnabledListener,
                 (const sp<gui::ITunnelModeEnabledListener>&), (override));
     MOCK_METHOD(binder::Status, setDesiredDisplayModeSpecs,
-                (const sp<IBinder>&, int32_t, bool, float, float, float,
-                 float appRequestRefreshRateMax),
-                (override));
+                (const sp<IBinder>&, const gui::DisplayModeSpecs&), (override));
     MOCK_METHOD(binder::Status, getDesiredDisplayModeSpecs,
                 (const sp<IBinder>&, gui::DisplayModeSpecs*), (override));
     MOCK_METHOD(binder::Status, getDisplayBrightnessSupport, (const sp<IBinder>&, bool*),
@@ -152,6 +150,7 @@
                 (override));
     MOCK_METHOD(binder::Status, removeWindowInfosListener, (const sp<gui::IWindowInfosListener>&),
                 (override));
+    MOCK_METHOD(binder::Status, getOverlaySupport, (gui::OverlayProperties*), (override));
 };
 
 class FakeBnSurfaceComposerClient : public gui::BnSurfaceComposerClient {
diff --git a/libs/gui/fuzzer/libgui_surfaceComposerClient_fuzzer.cpp b/libs/gui/fuzzer/libgui_surfaceComposerClient_fuzzer.cpp
index eecbe0f..57720dd 100644
--- a/libs/gui/fuzzer/libgui_surfaceComposerClient_fuzzer.cpp
+++ b/libs/gui/fuzzer/libgui_surfaceComposerClient_fuzzer.cpp
@@ -123,10 +123,37 @@
     sp<SurfaceControl> makeSurfaceControl();
     BlurRegion getBlurRegion();
     void fuzzOnPullAtom();
+    gui::DisplayModeSpecs getDisplayModeSpecs();
 
     FuzzedDataProvider mFdp;
 };
 
+gui::DisplayModeSpecs SurfaceComposerClientFuzzer::getDisplayModeSpecs() {
+    const auto getRefreshRateRange = [&] {
+        gui::DisplayModeSpecs::RefreshRateRanges::RefreshRateRange range;
+        range.min = mFdp.ConsumeFloatingPoint<float>();
+        range.max = mFdp.ConsumeFloatingPoint<float>();
+        return range;
+    };
+
+    const auto getRefreshRateRanges = [&] {
+        gui::DisplayModeSpecs::RefreshRateRanges ranges;
+        ranges.physical = getRefreshRateRange();
+        ranges.render = getRefreshRateRange();
+        return ranges;
+    };
+
+    String8 displayName((mFdp.ConsumeRandomLengthString(kRandomStringMaxBytes)).c_str());
+    sp<IBinder> displayToken =
+            SurfaceComposerClient::createDisplay(displayName, mFdp.ConsumeBool() /*secure*/);
+    gui::DisplayModeSpecs specs;
+    specs.defaultMode = mFdp.ConsumeIntegral<int32_t>();
+    specs.allowGroupSwitching = mFdp.ConsumeBool();
+    specs.primaryRanges = getRefreshRateRanges();
+    specs.appRequestRanges = getRefreshRateRanges();
+    return specs;
+}
+
 BlurRegion SurfaceComposerClientFuzzer::getBlurRegion() {
     int32_t left = mFdp.ConsumeIntegral<int32_t>();
     int32_t right = mFdp.ConsumeIntegral<int32_t>();
@@ -247,12 +274,7 @@
     String8 displayName((mFdp.ConsumeRandomLengthString(kRandomStringMaxBytes)).c_str());
     sp<IBinder> displayToken =
             SurfaceComposerClient::createDisplay(displayName, mFdp.ConsumeBool() /*secure*/);
-    SurfaceComposerClient::setDesiredDisplayModeSpecs(displayToken, mFdp.ConsumeIntegral<int32_t>(),
-                                                      mFdp.ConsumeBool() /*allowGroupSwitching*/,
-                                                      mFdp.ConsumeFloatingPoint<float>(),
-                                                      mFdp.ConsumeFloatingPoint<float>(),
-                                                      mFdp.ConsumeFloatingPoint<float>(),
-                                                      mFdp.ConsumeFloatingPoint<float>());
+    SurfaceComposerClient::setDesiredDisplayModeSpecs(displayToken, getDisplayModeSpecs());
 
     ui::ColorMode colorMode = mFdp.PickValueInArray(kColormodes);
     SurfaceComposerClient::setActiveColorMode(displayToken, colorMode);
diff --git a/libs/gui/include/gui/BLASTBufferQueue.h b/libs/gui/include/gui/BLASTBufferQueue.h
index 827a6cc..47dcc42 100644
--- a/libs/gui/include/gui/BLASTBufferQueue.h
+++ b/libs/gui/include/gui/BLASTBufferQueue.h
@@ -93,7 +93,8 @@
     void releaseBufferCallback(const ReleaseCallbackId& id, const sp<Fence>& releaseFence,
                                std::optional<uint32_t> currentMaxAcquiredBufferCount);
     void releaseBufferCallbackLocked(const ReleaseCallbackId& id, const sp<Fence>& releaseFence,
-                               std::optional<uint32_t> currentMaxAcquiredBufferCount);
+                                     std::optional<uint32_t> currentMaxAcquiredBufferCount,
+                                     bool fakeRelease);
     void syncNextTransaction(std::function<void(SurfaceComposerClient::Transaction*)> callback,
                              bool acquireSingleBuffer = true);
     void stopContinuousSyncTransaction();
@@ -113,12 +114,10 @@
     uint64_t getLastAcquiredFrameNum();
 
     /**
-     * Set a callback to be invoked when we are hung. The boolean parameter
-     * indicates whether the hang is due to an unfired fence.
-     * TODO: The boolean is always true atm, unfired fence is
-     * the only case we detect.
+     * Set a callback to be invoked when we are hung. The string parameter
+     * indicates the reason for the hang.
      */
-    void setTransactionHangCallback(std::function<void(bool)> callback);
+    void setTransactionHangCallback(std::function<void(const std::string&)> callback);
 
     virtual ~BLASTBufferQueue();
 
@@ -282,7 +281,7 @@
     bool mAppliedLastTransaction = false;
     uint64_t mLastAppliedFrameNumber = 0;
 
-    std::function<void(bool)> mTransactionHangCallback;
+    std::function<void(const std::string&)> mTransactionHangCallback;
 
     std::unordered_set<uint64_t> mSyncedFrameNumbers GUARDED_BY(mMutex);
 };
diff --git a/libs/gui/include/gui/BufferHubConsumer.h b/libs/gui/include/gui/BufferHubConsumer.h
deleted file mode 100644
index d380770..0000000
--- a/libs/gui/include/gui/BufferHubConsumer.h
+++ /dev/null
@@ -1,112 +0,0 @@
-/*
- * Copyright 2018 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 ANDROID_GUI_BUFFERHUBCONSUMER_H_
-#define ANDROID_GUI_BUFFERHUBCONSUMER_H_
-
-#include <gui/IGraphicBufferConsumer.h>
-#include <private/dvr/buffer_hub_queue_client.h>
-#include <private/dvr/buffer_hub_queue_parcelable.h>
-
-namespace android {
-
-class BufferHubConsumer : public IGraphicBufferConsumer {
-public:
-    // Creates a BufferHubConsumer instance by importing an existing producer queue.
-    static sp<BufferHubConsumer> Create(const std::shared_ptr<dvr::ConsumerQueue>& queue);
-
-    // Creates a BufferHubConsumer instance by importing an existing producer
-    // parcelable. Note that this call takes the ownership of the parcelable
-    // object and is guaranteed to succeed if parcelable object is valid.
-    static sp<BufferHubConsumer> Create(dvr::ConsumerQueueParcelable parcelable);
-
-    // See |IGraphicBufferConsumer::acquireBuffer|
-    status_t acquireBuffer(BufferItem* buffer, nsecs_t presentWhen,
-                           uint64_t maxFrameNumber = 0) override;
-
-    // See |IGraphicBufferConsumer::detachBuffer|
-    status_t detachBuffer(int slot) override;
-
-    // See |IGraphicBufferConsumer::attachBuffer|
-    status_t attachBuffer(int* outSlot, const sp<GraphicBuffer>& buffer) override;
-
-    // See |IGraphicBufferConsumer::releaseBuffer|
-    status_t releaseBuffer(int buf, uint64_t frameNumber, EGLDisplay display, EGLSyncKHR fence,
-                           const sp<Fence>& releaseFence) override;
-
-    // See |IGraphicBufferConsumer::consumerConnect|
-    status_t consumerConnect(const sp<IConsumerListener>& consumer, bool controlledByApp) override;
-
-    // See |IGraphicBufferConsumer::consumerDisconnect|
-    status_t consumerDisconnect() override;
-
-    // See |IGraphicBufferConsumer::getReleasedBuffers|
-    status_t getReleasedBuffers(uint64_t* slotMask) override;
-
-    // See |IGraphicBufferConsumer::setDefaultBufferSize|
-    status_t setDefaultBufferSize(uint32_t w, uint32_t h) override;
-
-    // See |IGraphicBufferConsumer::setMaxBufferCount|
-    status_t setMaxBufferCount(int bufferCount) override;
-
-    // See |IGraphicBufferConsumer::setMaxAcquiredBufferCount|
-    status_t setMaxAcquiredBufferCount(int maxAcquiredBuffers) override;
-
-    // See |IGraphicBufferConsumer::setConsumerName|
-    status_t setConsumerName(const String8& name) override;
-
-    // See |IGraphicBufferConsumer::setDefaultBufferFormat|
-    status_t setDefaultBufferFormat(PixelFormat defaultFormat) override;
-
-    // See |IGraphicBufferConsumer::setDefaultBufferDataSpace|
-    status_t setDefaultBufferDataSpace(android_dataspace defaultDataSpace) override;
-
-    // See |IGraphicBufferConsumer::setConsumerUsageBits|
-    status_t setConsumerUsageBits(uint64_t usage) override;
-
-    // See |IGraphicBufferConsumer::setConsumerIsProtected|
-    status_t setConsumerIsProtected(bool isProtected) override;
-
-    // See |IGraphicBufferConsumer::setTransformHint|
-    status_t setTransformHint(uint32_t hint) override;
-
-    // See |IGraphicBufferConsumer::getSidebandStream|
-    status_t getSidebandStream(sp<NativeHandle>* outStream) const override;
-
-    // See |IGraphicBufferConsumer::getOccupancyHistory|
-    status_t getOccupancyHistory(bool forceFlush,
-                                 std::vector<OccupancyTracker::Segment>* outHistory) override;
-
-    // See |IGraphicBufferConsumer::discardFreeBuffers|
-    status_t discardFreeBuffers() override;
-
-    // See |IGraphicBufferConsumer::dumpState|
-    status_t dumpState(const String8& prefix, String8* outResult) const override;
-
-    // BufferHubConsumer provides its own logic to cast to a binder object.
-    IBinder* onAsBinder() override;
-
-private:
-    // Private constructor to force use of |Create|.
-    BufferHubConsumer() = default;
-
-    // Concrete implementation backed by BufferHubBuffer.
-    std::shared_ptr<dvr::ConsumerQueue> mQueue;
-};
-
-} // namespace android
-
-#endif // ANDROID_GUI_BUFFERHUBCONSUMER_H_
diff --git a/libs/gui/include/gui/BufferHubProducer.h b/libs/gui/include/gui/BufferHubProducer.h
deleted file mode 100644
index 0e925ce..0000000
--- a/libs/gui/include/gui/BufferHubProducer.h
+++ /dev/null
@@ -1,223 +0,0 @@
-/*
- * Copyright 2018 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 ANDROID_GUI_BUFFERHUBPRODUCER_H_
-#define ANDROID_GUI_BUFFERHUBPRODUCER_H_
-
-#include <gui/BufferSlot.h>
-#include <gui/IGraphicBufferProducer.h>
-#include <private/dvr/buffer_hub_queue_client.h>
-#include <private/dvr/buffer_hub_queue_parcelable.h>
-
-namespace android {
-
-class BufferHubProducer : public IGraphicBufferProducer {
-public:
-    static constexpr int kNoConnectedApi = -1;
-
-    // TODO(b/36187402) The actual implementation of BufferHubQueue's consumer
-    // side logic doesn't limit the number of buffer it can acquire
-    // simultaneously. We need a way for consumer logic to configure and enforce
-    // that.
-    static constexpr int kDefaultUndequeuedBuffers = 1;
-
-    // Creates a BufferHubProducer instance by importing an existing prodcuer
-    // queue.
-    static sp<BufferHubProducer> Create(const std::shared_ptr<dvr::ProducerQueue>& producer);
-
-    // Creates a BufferHubProducer instance by importing an existing prodcuer
-    // parcelable. Note that this call takes the ownership of the parcelable
-    // object and is guaranteed to succeed if parcelable object is valid.
-    static sp<BufferHubProducer> Create(dvr::ProducerQueueParcelable parcelable);
-
-    // See |IGraphicBufferProducer::requestBuffer|
-    status_t requestBuffer(int slot, sp<GraphicBuffer>* buf) override;
-
-    // For the BufferHub based implementation. All buffers in the queue are
-    // allowed to be dequeued from the consumer side. It call always returns
-    // 0 for |NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS| query. Thus setting
-    // |max_dequeued_buffers| here can be considered the same as setting queue
-    // capacity.
-    //
-    // See |IGraphicBufferProducer::setMaxDequeuedBufferCount| for more info
-    status_t setMaxDequeuedBufferCount(int max_dequeued_buffers) override;
-
-    // See |IGraphicBufferProducer::setAsyncMode|
-    status_t setAsyncMode(bool async) override;
-
-    // See |IGraphicBufferProducer::dequeueBuffer|
-    status_t dequeueBuffer(int* out_slot, sp<Fence>* out_fence, uint32_t width, uint32_t height,
-                           PixelFormat format, uint64_t usage, uint64_t* outBufferAge,
-                           FrameEventHistoryDelta* outTimestamps) override;
-
-    // See |IGraphicBufferProducer::detachBuffer|
-    status_t detachBuffer(int slot) override;
-
-    // See |IGraphicBufferProducer::detachNextBuffer|
-    status_t detachNextBuffer(sp<GraphicBuffer>* out_buffer, sp<Fence>* out_fence) override;
-
-    // See |IGraphicBufferProducer::attachBuffer|
-    status_t attachBuffer(int* out_slot, const sp<GraphicBuffer>& buffer) override;
-
-    // See |IGraphicBufferProducer::queueBuffer|
-    status_t queueBuffer(int slot, const QueueBufferInput& input,
-                         QueueBufferOutput* output) override;
-
-    // See |IGraphicBufferProducer::cancelBuffer|
-    status_t cancelBuffer(int slot, const sp<Fence>& fence) override;
-
-    // See |IGraphicBufferProducer::query|
-    status_t query(int what, int* out_value) override;
-
-    // See |IGraphicBufferProducer::connect|
-    status_t connect(const sp<IProducerListener>& listener, int api,
-                     bool producer_controlled_by_app, QueueBufferOutput* output) override;
-
-    // See |IGraphicBufferProducer::disconnect|
-    status_t disconnect(int api, DisconnectMode mode = DisconnectMode::Api) override;
-
-    // See |IGraphicBufferProducer::setSidebandStream|
-    status_t setSidebandStream(const sp<NativeHandle>& stream) override;
-
-    // See |IGraphicBufferProducer::allocateBuffers|
-    void allocateBuffers(uint32_t width, uint32_t height, PixelFormat format,
-                         uint64_t usage) override;
-
-    // See |IGraphicBufferProducer::allowAllocation|
-    status_t allowAllocation(bool allow) override;
-
-    // See |IGraphicBufferProducer::setGenerationNumber|
-    status_t setGenerationNumber(uint32_t generation_number) override;
-
-    // See |IGraphicBufferProducer::getConsumerName|
-    String8 getConsumerName() const override;
-
-    // See |IGraphicBufferProducer::setSharedBufferMode|
-    status_t setSharedBufferMode(bool shared_buffer_mode) override;
-
-    // See |IGraphicBufferProducer::setAutoRefresh|
-    status_t setAutoRefresh(bool auto_refresh) override;
-
-    // See |IGraphicBufferProducer::setDequeueTimeout|
-    status_t setDequeueTimeout(nsecs_t timeout) override;
-
-    // See |IGraphicBufferProducer::getLastQueuedBuffer|
-    status_t getLastQueuedBuffer(sp<GraphicBuffer>* out_buffer, sp<Fence>* out_fence,
-                                 float out_transform_matrix[16]) override;
-
-    // See |IGraphicBufferProducer::getFrameTimestamps|
-    void getFrameTimestamps(FrameEventHistoryDelta* /*outDelta*/) override;
-
-    // See |IGraphicBufferProducer::getUniqueId|
-    status_t getUniqueId(uint64_t* out_id) const override;
-
-    // See |IGraphicBufferProducer::getConsumerUsage|
-    status_t getConsumerUsage(uint64_t* out_usage) const override;
-
-    // Takes out the current producer as a binder parcelable object. Note that the
-    // producer must be disconnected to be exportable. After successful export,
-    // the producer queue can no longer be connected again. Returns NO_ERROR when
-    // takeout is successful and out_parcelable will hold the new parcelable
-    // object. Also note that out_parcelable cannot be NULL and must points to an
-    // invalid parcelable.
-    status_t TakeAsParcelable(dvr::ProducerQueueParcelable* out_parcelable);
-
-    IBinder* onAsBinder() override;
-
-protected:
-    // See |IGraphicBufferProducer::exportToParcel|
-    status_t exportToParcel(Parcel* parcel) override;
-
-private:
-    using LocalHandle = pdx::LocalHandle;
-
-    // Private constructor to force use of |Create|.
-    BufferHubProducer() {}
-
-    static uint64_t genUniqueId() {
-        static std::atomic<uint32_t> counter{0};
-        static uint64_t id = static_cast<uint64_t>(getpid()) << 32;
-        return id | counter++;
-    }
-
-    // Allocate new buffer through BufferHub and add it into |queue_| for
-    // bookkeeping.
-    status_t AllocateBuffer(uint32_t width, uint32_t height, uint32_t layer_count,
-                            PixelFormat format, uint64_t usage);
-
-    // Remove a buffer via BufferHubRPC.
-    status_t RemoveBuffer(size_t slot);
-
-    // Free all buffers which are owned by the prodcuer. Note that if graphic
-    // buffers are acquired by the consumer, we can't .
-    status_t FreeAllBuffers();
-
-    // Helper function that implements the detachBuffer() call, but assuming |mutex_| has been
-    // locked already.
-    status_t DetachBufferLocked(size_t slot);
-
-    // Concreate implementation backed by BufferHubBuffer.
-    std::shared_ptr<dvr::ProducerQueue> queue_;
-
-    // Mutex for thread safety.
-    std::mutex mutex_;
-
-    // Connect client API, should be one of the NATIVE_WINDOW_API_* flags.
-    int connected_api_{kNoConnectedApi};
-
-    // |max_buffer_count_| sets the capacity of the underlying buffer queue.
-    int32_t max_buffer_count_{dvr::BufferHubQueue::kMaxQueueCapacity};
-
-    // |max_dequeued_buffer_count_| set the maximum number of buffers that can
-    // be dequeued at the same momment.
-    int32_t max_dequeued_buffer_count_{1};
-
-    // Sets how long dequeueBuffer or attachBuffer will block if a buffer or
-    // slot is not yet available. The timeout is stored in milliseconds.
-    int dequeue_timeout_ms_{dvr::BufferHubQueue::kNoTimeOut};
-
-    // |generation_number_| stores the current generation number of the attached
-    // producer. Any attempt to attach a buffer with a different generation
-    // number will fail.
-    // TOOD(b/38137191) Currently not used as we don't support
-    // IGraphicBufferProducer::detachBuffer.
-    uint32_t generation_number_{0};
-
-    // |buffers_| stores the buffers that have been dequeued from
-    // |dvr::BufferHubQueue|, It is initialized to invalid buffers, and gets
-    // filled in with the result of |Dequeue|.
-    // TODO(jwcai) The buffer allocated to a slot will also be replaced if the
-    // requested buffer usage or geometry differs from that of the buffer
-    // allocated to a slot.
-    struct BufferHubSlot : public BufferSlot {
-        BufferHubSlot() : mProducerBuffer(nullptr), mIsReallocating(false) {}
-        // BufferSlot comes from android framework, using m prefix to comply with
-        // the name convention with the reset of data fields from BufferSlot.
-        std::shared_ptr<dvr::ProducerBuffer> mProducerBuffer;
-        bool mIsReallocating;
-    };
-    BufferHubSlot buffers_[dvr::BufferHubQueue::kMaxQueueCapacity];
-
-    // A uniqueId used by IGraphicBufferProducer interface.
-    const uint64_t unique_id_{genUniqueId()};
-
-    // A pending parcelable object which keeps the bufferhub channel alive.
-    dvr::ProducerQueueParcelable pending_producer_parcelable_;
-};
-
-} // namespace android
-
-#endif // ANDROID_GUI_BUFFERHUBPRODUCER_H_
diff --git a/libs/gui/include/gui/BufferQueue.h b/libs/gui/include/gui/BufferQueue.h
index 91f80d2..690587f 100644
--- a/libs/gui/include/gui/BufferQueue.h
+++ b/libs/gui/include/gui/BufferQueue.h
@@ -82,12 +82,6 @@
             sp<IGraphicBufferConsumer>* outConsumer,
             bool consumerIsSurfaceFlinger = false);
 
-#ifndef NO_BUFFERHUB
-    // Creates an IGraphicBufferProducer and IGraphicBufferConsumer pair backed by BufferHub.
-    static void createBufferHubQueue(sp<IGraphicBufferProducer>* outProducer,
-                                     sp<IGraphicBufferConsumer>* outConsumer);
-#endif
-
     BufferQueue() = delete; // Create through createBufferQueue
 };
 
diff --git a/libs/gui/include/gui/DisplayInfo.h b/libs/gui/include/gui/DisplayInfo.h
index 74f33a2..42b62c7 100644
--- a/libs/gui/include/gui/DisplayInfo.h
+++ b/libs/gui/include/gui/DisplayInfo.h
@@ -41,6 +41,8 @@
     status_t writeToParcel(android::Parcel*) const override;
 
     status_t readFromParcel(const android::Parcel*) override;
+
+    void dump(std::string& result, const char* prefix = "") const;
 };
 
 } // namespace android::gui
\ No newline at end of file
diff --git a/libs/gui/include/gui/ISurfaceComposer.h b/libs/gui/include/gui/ISurfaceComposer.h
index e91d754..d517e99 100644
--- a/libs/gui/include/gui/ISurfaceComposer.h
+++ b/libs/gui/include/gui/ISurfaceComposer.h
@@ -55,7 +55,7 @@
 namespace android {
 
 struct client_cache_t;
-struct ComposerState;
+class ComposerState;
 struct DisplayStatInfo;
 struct DisplayState;
 struct InputWindowCommands;
@@ -110,7 +110,7 @@
 
     /* open/close transactions. requires ACCESS_SURFACE_FLINGER permission */
     virtual status_t setTransactionState(
-            const FrameTimelineInfo& frameTimelineInfo, const Vector<ComposerState>& state,
+            const FrameTimelineInfo& frameTimelineInfo, Vector<ComposerState>& state,
             const Vector<DisplayState>& displays, uint32_t flags, const sp<IBinder>& applyToken,
             const InputWindowCommands& inputWindowCommands, int64_t desiredPresentTime,
             bool isAutoTimestamp, const client_cache_t& uncacheBuffer, bool hasListenerCallbacks,
diff --git a/libs/gui/include/gui/ITransactionCompletedListener.h b/libs/gui/include/gui/ITransactionCompletedListener.h
index cc136bb..453e8f3 100644
--- a/libs/gui/include/gui/ITransactionCompletedListener.h
+++ b/libs/gui/include/gui/ITransactionCompletedListener.h
@@ -132,7 +132,7 @@
 
     SurfaceStats() = default;
     SurfaceStats(const sp<IBinder>& sc, std::variant<nsecs_t, sp<Fence>> acquireTimeOrFence,
-                 const sp<Fence>& prevReleaseFence, uint32_t hint,
+                 const sp<Fence>& prevReleaseFence, std::optional<uint32_t> hint,
                  uint32_t currentMaxAcquiredBuffersCount, FrameEventHistoryStats frameEventStats,
                  std::vector<JankData> jankData, ReleaseCallbackId previousReleaseCallbackId)
           : surfaceControl(sc),
@@ -147,7 +147,7 @@
     sp<IBinder> surfaceControl;
     std::variant<nsecs_t, sp<Fence>> acquireTimeOrFence = -1;
     sp<Fence> previousReleaseFence;
-    uint32_t transformHint = 0;
+    std::optional<uint32_t> transformHint = 0;
     uint32_t currentMaxAcquiredBufferCount = 0;
     FrameEventHistoryStats eventStats;
     std::vector<JankData> jankData;
@@ -194,7 +194,8 @@
 
     virtual void onReleaseBuffer(ReleaseCallbackId callbackId, sp<Fence> releaseFence,
                                  uint32_t currentMaxAcquiredBufferCount) = 0;
-    virtual void onTransactionQueueStalled() = 0;
+
+    virtual void onTransactionQueueStalled(const String8& name) = 0;
 };
 
 class BnTransactionCompletedListener : public SafeBnInterface<ITransactionCompletedListener> {
diff --git a/libs/gui/include/gui/LayerMetadata.h b/libs/gui/include/gui/LayerMetadata.h
index 5af5989..9cf62bc 100644
--- a/libs/gui/include/gui/LayerMetadata.h
+++ b/libs/gui/include/gui/LayerMetadata.h
@@ -30,7 +30,8 @@
     METADATA_ACCESSIBILITY_ID = 5,
     METADATA_OWNER_PID = 6,
     METADATA_DEQUEUE_TIME = 7,
-    METADATA_GAME_MODE = 8
+    METADATA_GAME_MODE = 8,
+    METADATA_CALLING_UID = 9,
 };
 
 struct LayerMetadata : public Parcelable {
@@ -65,8 +66,9 @@
     Standard = 1,
     Performance = 2,
     Battery = 3,
+    Custom = 4,
 
-    ftl_last = Battery
+    ftl_last = Custom
 };
 
 } // namespace android::gui
diff --git a/libs/gui/include/gui/LayerState.h b/libs/gui/include/gui/LayerState.h
index c8927ad..6ec6bd7 100644
--- a/libs/gui/include/gui/LayerState.h
+++ b/libs/gui/include/gui/LayerState.h
@@ -148,12 +148,14 @@
     enum {
         ePositionChanged = 0x00000001,
         eLayerChanged = 0x00000002,
-        // unused = 0x00000004,
+        /* unused = 0x00000004, */
         eAlphaChanged = 0x00000008,
         eMatrixChanged = 0x00000010,
         eTransparentRegionChanged = 0x00000020,
         eFlagsChanged = 0x00000040,
         eLayerStackChanged = 0x00000080,
+        /* unused = 0x00000100, */
+        /* unused = 0x00000200, */
         eDimmingEnabledChanged = 0x00000400,
         eShadowRadiusChanged = 0x00000800,
         eRenderBorderChanged = 0x00001000,
@@ -161,8 +163,8 @@
         eRelativeLayerChanged = 0x00004000,
         eReparent = 0x00008000,
         eColorChanged = 0x00010000,
-        eDestroySurface = 0x00020000,
-        eTransformChanged = 0x00040000,
+        /* unused = 0x00020000, */
+        eBufferTransformChanged = 0x00040000,
         eTransformToDisplayInverseChanged = 0x00080000,
         eCropChanged = 0x00100000,
         eBufferChanged = 0x00200000,
@@ -199,7 +201,32 @@
     void merge(const layer_state_t& other);
     status_t write(Parcel& output) const;
     status_t read(const Parcel& input);
+    // Compares two layer_state_t structs and returns a set of change flags describing all the
+    // states that are different.
+    uint64_t diff(const layer_state_t& other) const;
     bool hasBufferChanges() const;
+
+    // Changes to the tree structure.
+    static constexpr uint64_t HIERARCHY_CHANGES = layer_state_t::eLayerChanged |
+            layer_state_t::eRelativeLayerChanged | layer_state_t::eReparent |
+            layer_state_t::eBackgroundColorChanged;
+    // Content updates.
+    static constexpr uint64_t CONTENT_CHANGES = layer_state_t::eAlphaChanged |
+            layer_state_t::eTransparentRegionChanged | layer_state_t::eShadowRadiusChanged |
+            layer_state_t::eRenderBorderChanged | layer_state_t::eColorChanged |
+            layer_state_t::eBufferChanged | layer_state_t::eDataspaceChanged |
+            layer_state_t::eApiChanged | layer_state_t::eSidebandStreamChanged |
+            layer_state_t::eColorTransformChanged | layer_state_t::eCornerRadiusChanged |
+            layer_state_t::eBackgroundColorChanged | layer_state_t::eColorSpaceAgnosticChanged |
+            layer_state_t::eBackgroundBlurRadiusChanged | layer_state_t::eBlurRegionsChanged |
+            layer_state_t::eAutoRefreshChanged | layer_state_t::eStretchChanged;
+    // Changes to content or children size.
+    static constexpr uint64_t GEOMETRY_CHANGES = layer_state_t::ePositionChanged |
+            layer_state_t::eMatrixChanged | layer_state_t::eTransparentRegionChanged |
+            layer_state_t::eBufferCropChanged | layer_state_t::eBufferTransformChanged |
+            layer_state_t::eTransformToDisplayInverseChanged | layer_state_t::eCropChanged |
+            layer_state_t::eDestinationFrameChanged;
+
     bool hasValidBuffer() const;
     void sanitize(int32_t permissions);
 
@@ -210,6 +237,11 @@
         float dsdy{0};
         status_t write(Parcel& output) const;
         status_t read(const Parcel& input);
+        inline bool operator==(const matrix22_t& other) const {
+            return std::tie(dsdx, dtdx, dtdy, dsdy) ==
+                    std::tie(other.dsdx, other.dtdx, other.dtdy, other.dsdy);
+        }
+        inline bool operator!=(const matrix22_t& other) const { return !(*this == other); }
     };
     sp<IBinder> surface;
     int32_t layerId;
@@ -218,25 +250,22 @@
     float y;
     int32_t z;
     ui::LayerStack layerStack = ui::DEFAULT_LAYER_STACK;
-    float alpha;
     uint32_t flags;
     uint32_t mask;
     uint8_t reserved;
     matrix22_t matrix;
     float cornerRadius;
     uint32_t backgroundBlurRadius;
-    sp<SurfaceControl> reparentSurfaceControl;
 
     sp<SurfaceControl> relativeLayerSurfaceControl;
 
     sp<SurfaceControl> parentSurfaceControlForChild;
 
-    half3 color;
+    half4 color;
 
     // non POD must be last. see write/read
     Region transparentRegion;
-
-    uint32_t transform;
+    uint32_t bufferTransform;
     bool transformToDisplayInverse;
     Rect crop;
     std::shared_ptr<BufferData> bufferData = nullptr;
@@ -312,7 +341,8 @@
     bool dimmingEnabled;
 };
 
-struct ComposerState {
+class ComposerState {
+public:
     layer_state_t state;
     status_t write(Parcel& output) const;
     status_t read(const Parcel& input);
diff --git a/libs/gui/include/gui/Surface.h b/libs/gui/include/gui/Surface.h
index 1f19f4e..b9ccdc9 100644
--- a/libs/gui/include/gui/Surface.h
+++ b/libs/gui/include/gui/Surface.h
@@ -113,6 +113,24 @@
         return surface != nullptr && surface->getIGraphicBufferProducer() != nullptr;
     }
 
+    static sp<IGraphicBufferProducer> getIGraphicBufferProducer(ANativeWindow* window) {
+        int val;
+        if (window->query(window, NATIVE_WINDOW_CONCRETE_TYPE, &val) >= 0 &&
+            val == NATIVE_WINDOW_SURFACE) {
+            return ((Surface*) window)->mGraphicBufferProducer;
+        }
+        return nullptr;
+    }
+
+    static sp<IBinder> getSurfaceControlHandle(ANativeWindow* window) {
+        int val;
+        if (window->query(window, NATIVE_WINDOW_CONCRETE_TYPE, &val) >= 0 &&
+            val == NATIVE_WINDOW_SURFACE) {
+            return ((Surface*) window)->mSurfaceControlHandle;
+        }
+        return nullptr;
+    }
+
     /* Attaches a sideband buffer stream to the Surface's IGraphicBufferProducer.
      *
      * A sideband stream is a device-specific mechanism for passing buffers
@@ -468,6 +486,11 @@
     // queue operation.  There is no HDR metadata by default.
     HdrMetadata mHdrMetadata;
 
+    // mHdrMetadataIsSet is a bitfield to track which HDR metadata has been set.
+    // Prevent Surface from resetting HDR metadata that was set on a bufer when
+    // HDR metadata is not set on this Surface.
+    uint32_t mHdrMetadataIsSet{0};
+
     // mCrop is the crop rectangle that will be used for the next buffer
     // that gets queued. It is set by calling setCrop.
     Rect mCrop;
diff --git a/libs/gui/include/gui/SurfaceComposerClient.h b/libs/gui/include/gui/SurfaceComposerClient.h
index d138d68..2038f14 100644
--- a/libs/gui/include/gui/SurfaceComposerClient.h
+++ b/libs/gui/include/gui/SurfaceComposerClient.h
@@ -69,7 +69,7 @@
     SurfaceControlStats(const sp<SurfaceControl>& sc, nsecs_t latchTime,
                         std::variant<nsecs_t, sp<Fence>> acquireTimeOrFence,
                         const sp<Fence>& presentFence, const sp<Fence>& prevReleaseFence,
-                        uint32_t hint, FrameEventHistoryStats eventStats,
+                        std::optional<uint32_t> hint, FrameEventHistoryStats eventStats,
                         uint32_t currentMaxAcquiredBufferCount)
           : surfaceControl(sc),
             latchTime(latchTime),
@@ -85,7 +85,7 @@
     std::variant<nsecs_t, sp<Fence>> acquireTimeOrFence = -1;
     sp<Fence> presentFence;
     sp<Fence> previousReleaseFence;
-    uint32_t transformHint = 0;
+    std::optional<uint32_t> transformHint = 0;
     FrameEventHistoryStats frameEventStats;
     uint32_t currentMaxAcquiredBufferCount = 0;
 };
@@ -159,18 +159,11 @@
     static status_t getActiveDisplayMode(const sp<IBinder>& display, ui::DisplayMode*);
 
     // Sets the refresh rate boundaries for the display.
-    static status_t setDesiredDisplayModeSpecs(
-            const sp<IBinder>& displayToken, ui::DisplayModeId defaultMode,
-            bool allowGroupSwitching, float primaryRefreshRateMin, float primaryRefreshRateMax,
-            float appRequestRefreshRateMin, float appRequestRefreshRateMax);
+    static status_t setDesiredDisplayModeSpecs(const sp<IBinder>& displayToken,
+                                               const gui::DisplayModeSpecs&);
     // Gets the refresh rate boundaries for the display.
     static status_t getDesiredDisplayModeSpecs(const sp<IBinder>& displayToken,
-                                               ui::DisplayModeId* outDefaultMode,
-                                               bool* outAllowGroupSwitching,
-                                               float* outPrimaryRefreshRateMin,
-                                               float* outPrimaryRefreshRateMax,
-                                               float* outAppRequestRefreshRateMin,
-                                               float* outAppRequestRefreshRateMax);
+                                               gui::DisplayModeSpecs*);
 
     // Get the coordinates of the display's native color primaries
     static status_t getDisplayNativePrimaries(const sp<IBinder>& display,
@@ -182,6 +175,10 @@
 
     // Gets if boot display mode operations are supported on a device
     static status_t getBootDisplayModeSupport(bool* support);
+
+    // Gets the overlay properties of the device
+    static status_t getOverlaySupport(gui::OverlayProperties* outProperties);
+
     // Sets the user-preferred display mode that a device should boot in
     static status_t setBootDisplayMode(const sp<IBinder>& display, ui::DisplayModeId);
     // Clears the user-preferred display mode
@@ -782,7 +779,7 @@
     // This is protected by mSurfaceStatsListenerMutex, but GUARDED_BY isn't supported for
     // std::recursive_mutex
     std::multimap<int32_t, SurfaceStatsCallbackEntry> mSurfaceStatsListeners;
-    std::unordered_map<void*, std::function<void()>> mQueueStallListeners;
+    std::unordered_map<void*, std::function<void(const std::string&)>> mQueueStallListeners;
 
 public:
     static sp<TransactionCompletedListener> getInstance();
@@ -800,7 +797,7 @@
             const sp<SurfaceControl>& surfaceControl,
             const std::unordered_set<CallbackId, CallbackIdHash>& callbackIds);
 
-    void addQueueStallListener(std::function<void()> stallListener, void* id);
+    void addQueueStallListener(std::function<void(const std::string&)> stallListener, void* id);
     void removeQueueStallListener(void *id);
 
     /*
@@ -831,7 +828,7 @@
     // For Testing Only
     static void setInstance(const sp<TransactionCompletedListener>&);
 
-    void onTransactionQueueStalled() override;
+    void onTransactionQueueStalled(const String8& reason) override;
 
 private:
     ReleaseBufferCallback popReleaseBufferCallbackLocked(const ReleaseCallbackId&);
diff --git a/libs/gui/include/gui/TraceUtils.h b/libs/gui/include/gui/TraceUtils.h
index 4c01683..441b833 100644
--- a/libs/gui/include/gui/TraceUtils.h
+++ b/libs/gui/include/gui/TraceUtils.h
@@ -30,6 +30,12 @@
 #define ATRACE_FORMAT_INSTANT(fmt, ...) \
     (CC_UNLIKELY(ATRACE_ENABLED()) && (TraceUtils::instantFormat(fmt, ##__VA_ARGS__), true))
 
+#define ALOGE_AND_TRACE(fmt, ...)                  \
+    do {                                           \
+        ALOGE(fmt, ##__VA_ARGS__);                 \
+        ATRACE_FORMAT_INSTANT(fmt, ##__VA_ARGS__); \
+    } while (false)
+
 namespace android {
 
 class TraceUtils {
diff --git a/libs/gui/include/gui/WindowInfo.h b/libs/gui/include/gui/WindowInfo.h
index ac74c8a..b01a3db 100644
--- a/libs/gui/include/gui/WindowInfo.h
+++ b/libs/gui/include/gui/WindowInfo.h
@@ -272,6 +272,7 @@
     WindowInfoHandle(const WindowInfo& other);
 
     inline const WindowInfo* getInfo() const { return &mInfo; }
+    inline WindowInfo* editInfo() { return &mInfo; }
 
     sp<IBinder> getToken() const;
 
diff --git a/libs/gui/tests/Android.bp b/libs/gui/tests/Android.bp
index 7b37b25..183acc1 100644
--- a/libs/gui/tests/Android.bp
+++ b/libs/gui/tests/Android.bp
@@ -93,39 +93,6 @@
     header_libs: ["libsurfaceflinger_headers"],
 }
 
-// Build a separate binary to $(TARGET_OUT_DATA_NATIVE_TESTS)/$(LOCAL_MODULE)
-// This test has a main method, and requires a separate binary to be built.
-// To add move tests like this, just add additional cc_test statements,
-// as opposed to adding more source files to this one.
-cc_test {
-    name: "SurfaceParcelable_test",
-    test_suites: ["device-tests"],
-
-    cflags: [
-        "-Wall",
-        "-Werror",
-    ],
-
-    srcs: [
-        "SurfaceParcelable_test.cpp",
-    ],
-
-    shared_libs: [
-        "liblog",
-        "libbinder",
-        "libcutils",
-        "libgui",
-        "libui",
-        "libutils",
-        "libbufferhubqueue", // TODO(b/70046255): Remove these once BufferHub is integrated into libgui.
-        "libpdx_default_transport",
-    ],
-
-    header_libs: [
-        "libdvr_headers",
-    ],
-}
-
 cc_test {
     name: "SamplingDemo",
 
diff --git a/libs/gui/tests/IGraphicBufferProducer_test.cpp b/libs/gui/tests/IGraphicBufferProducer_test.cpp
index 2af2fe1..3427731 100644
--- a/libs/gui/tests/IGraphicBufferProducer_test.cpp
+++ b/libs/gui/tests/IGraphicBufferProducer_test.cpp
@@ -42,10 +42,6 @@
 #define TEST_CONTROLLED_BY_APP false
 #define TEST_PRODUCER_USAGE_BITS (0)
 
-#ifndef USE_BUFFER_HUB_AS_BUFFER_QUEUE
-#define USE_BUFFER_HUB_AS_BUFFER_QUEUE 0
-#endif
-
 namespace android {
 
 namespace {
@@ -77,7 +73,6 @@
     // Enums to control which IGraphicBufferProducer backend to test.
     enum IGraphicBufferProducerTestCode {
         USE_BUFFER_QUEUE_PRODUCER = 0,
-        USE_BUFFER_HUB_PRODUCER,
     };
 }; // namespace anonymous
 
@@ -99,10 +94,6 @@
                 BufferQueue::createBufferQueue(&mProducer, &mConsumer);
                 break;
             }
-            case USE_BUFFER_HUB_PRODUCER: {
-                BufferQueue::createBufferHubQueue(&mProducer, &mConsumer);
-                break;
-            }
             default: {
                 // Should never reach here.
                 LOG_ALWAYS_FATAL("Invalid test params: %u", GetParam());
@@ -880,11 +871,6 @@
 }
 
 TEST_P(IGraphicBufferProducerTest, SetAsyncMode_Succeeds) {
-    if (GetParam() == USE_BUFFER_HUB_PRODUCER) {
-        // TODO(b/36724099): Add support for BufferHubProducer::setAsyncMode(true)
-        return;
-    }
-
     ASSERT_OK(mConsumer->setMaxAcquiredBufferCount(1)) << "maxAcquire: " << 1;
     ASSERT_NO_FATAL_FAILURE(ConnectProducer());
     ASSERT_OK(mProducer->setAsyncMode(true)) << "async mode: " << true;
@@ -1117,14 +1103,7 @@
     }
 }
 
-#if USE_BUFFER_HUB_AS_BUFFER_QUEUE
-INSTANTIATE_TEST_CASE_P(IGraphicBufferProducerBackends, IGraphicBufferProducerTest,
-                        ::testing::Values(USE_BUFFER_QUEUE_PRODUCER, USE_BUFFER_HUB_PRODUCER));
-#else
-// TODO(b/70046255): Remove the #ifdef here and always tests both backends once BufferHubQueue can
-// pass all existing libgui tests.
 INSTANTIATE_TEST_CASE_P(IGraphicBufferProducerBackends, IGraphicBufferProducerTest,
                         ::testing::Values(USE_BUFFER_QUEUE_PRODUCER));
-#endif
 
 } // namespace android
diff --git a/libs/gui/tests/SurfaceParcelable_test.cpp b/libs/gui/tests/SurfaceParcelable_test.cpp
deleted file mode 100644
index 686dc82..0000000
--- a/libs/gui/tests/SurfaceParcelable_test.cpp
+++ /dev/null
@@ -1,168 +0,0 @@
-/*
- * Copyright (C) 2018 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 "SurfaceParcelable_test"
-
-#include <gtest/gtest.h>
-
-#include <binder/IServiceManager.h>
-#include <binder/ProcessState.h>
-#include <gui/BufferHubProducer.h>
-#include <gui/BufferQueue.h>
-#include <gui/view/Surface.h>
-#include <utils/Log.h>
-
-namespace android {
-
-static const String16 kTestServiceName = String16("SurfaceParcelableTestService");
-static const String16 kSurfaceName = String16("TEST_SURFACE");
-static const uint32_t kBufferWidth = 100;
-static const uint32_t kBufferHeight = 1;
-static const uint32_t kBufferFormat = HAL_PIXEL_FORMAT_BLOB;
-
-enum SurfaceParcelableTestServiceCode {
-    CREATE_BUFFER_QUEUE_SURFACE = IBinder::FIRST_CALL_TRANSACTION,
-    CREATE_BUFFER_HUB_SURFACE,
-};
-
-class SurfaceParcelableTestService : public BBinder {
-public:
-    SurfaceParcelableTestService() {
-        // BufferQueue
-        BufferQueue::createBufferQueue(&mBufferQueueProducer, &mBufferQueueConsumer);
-
-        // BufferHub
-        dvr::ProducerQueueConfigBuilder configBuilder;
-        mProducerQueue = dvr::ProducerQueue::Create(configBuilder.SetDefaultWidth(kBufferWidth)
-                                                            .SetDefaultHeight(kBufferHeight)
-                                                            .SetDefaultFormat(kBufferFormat)
-                                                            .Build(),
-                                                    dvr::UsagePolicy{});
-        mBufferHubProducer = BufferHubProducer::Create(mProducerQueue);
-    }
-
-    ~SurfaceParcelableTestService() = default;
-
-    virtual status_t onTransact(uint32_t code, const Parcel& /*data*/, Parcel* reply,
-                                uint32_t /*flags*/ = 0) {
-        switch (code) {
-            case CREATE_BUFFER_QUEUE_SURFACE: {
-                view::Surface surfaceShim;
-                surfaceShim.name = kSurfaceName;
-                surfaceShim.graphicBufferProducer = mBufferQueueProducer;
-                return surfaceShim.writeToParcel(reply);
-            }
-            case CREATE_BUFFER_HUB_SURFACE: {
-                view::Surface surfaceShim;
-                surfaceShim.name = kSurfaceName;
-                surfaceShim.graphicBufferProducer = mBufferHubProducer;
-                return surfaceShim.writeToParcel(reply);
-            }
-            default:
-                return UNKNOWN_TRANSACTION;
-        };
-    }
-
-protected:
-    sp<IGraphicBufferProducer> mBufferQueueProducer;
-    sp<IGraphicBufferConsumer> mBufferQueueConsumer;
-
-    std::shared_ptr<dvr::ProducerQueue> mProducerQueue;
-    sp<IGraphicBufferProducer> mBufferHubProducer;
-};
-
-static int runBinderServer() {
-    ProcessState::self()->startThreadPool();
-
-    sp<IServiceManager> sm = defaultServiceManager();
-    sp<SurfaceParcelableTestService> service = new SurfaceParcelableTestService;
-    sm->addService(kTestServiceName, service, false);
-
-    ALOGI("Binder server running...");
-
-    while (true) {
-        int stat, retval;
-        retval = wait(&stat);
-        if (retval == -1 && errno == ECHILD) {
-            break;
-        }
-    }
-
-    ALOGI("Binder server exiting...");
-    return 0;
-}
-
-class SurfaceParcelableTest : public ::testing::TestWithParam<uint32_t> {
-protected:
-    virtual void SetUp() {
-        mService = defaultServiceManager()->getService(kTestServiceName);
-        if (mService == nullptr) {
-            ALOGE("Failed to connect to the test service.");
-            return;
-        }
-
-        ALOGI("Binder service is ready for client.");
-    }
-
-    status_t GetSurface(view::Surface* surfaceShim) {
-        ALOGI("...Test: %d", GetParam());
-
-        uint32_t opCode = GetParam();
-        Parcel data;
-        Parcel reply;
-        status_t error = mService->transact(opCode, data, &reply);
-        if (error != NO_ERROR) {
-            ALOGE("Failed to get surface over binder, error=%d.", error);
-            return error;
-        }
-
-        error = surfaceShim->readFromParcel(&reply);
-        if (error != NO_ERROR) {
-            ALOGE("Failed to get surface from parcel, error=%d.", error);
-            return error;
-        }
-
-        return NO_ERROR;
-    }
-
-private:
-    sp<IBinder> mService;
-};
-
-TEST_P(SurfaceParcelableTest, SendOverBinder) {
-    view::Surface surfaceShim;
-    EXPECT_EQ(GetSurface(&surfaceShim), NO_ERROR);
-    EXPECT_EQ(surfaceShim.name, kSurfaceName);
-    EXPECT_FALSE(surfaceShim.graphicBufferProducer == nullptr);
-}
-
-INSTANTIATE_TEST_CASE_P(SurfaceBackends, SurfaceParcelableTest,
-                        ::testing::Values(CREATE_BUFFER_QUEUE_SURFACE, CREATE_BUFFER_HUB_SURFACE));
-
-} // namespace android
-
-int main(int argc, char** argv) {
-    pid_t pid = fork();
-    if (pid == 0) {
-        android::ProcessState::self()->startThreadPool();
-        ::testing::InitGoogleTest(&argc, argv);
-        return RUN_ALL_TESTS();
-
-    } else {
-        ALOGI("Test process pid: %d.", pid);
-        return android::runBinderServer();
-    }
-}
diff --git a/libs/gui/tests/Surface_test.cpp b/libs/gui/tests/Surface_test.cpp
index 72d76ee..6d3b425 100644
--- a/libs/gui/tests/Surface_test.cpp
+++ b/libs/gui/tests/Surface_test.cpp
@@ -696,7 +696,7 @@
     }
 
     status_t setTransactionState(const FrameTimelineInfo& /*frameTimelineInfo*/,
-                                 const Vector<ComposerState>& /*state*/,
+                                 Vector<ComposerState>& /*state*/,
                                  const Vector<DisplayState>& /*displays*/, uint32_t /*flags*/,
                                  const sp<IBinder>& /*applyToken*/,
                                  const InputWindowCommands& /*inputWindowCommands*/,
@@ -920,16 +920,12 @@
     }
 
     binder::Status setDesiredDisplayModeSpecs(const sp<IBinder>& /*displayToken*/,
-                                              int32_t /*defaultMode*/, bool /*allowGroupSwitching*/,
-                                              float /*primaryRefreshRateMin*/,
-                                              float /*primaryRefreshRateMax*/,
-                                              float /*appRequestRefreshRateMin*/,
-                                              float /*appRequestRefreshRateMax*/) override {
+                                              const gui::DisplayModeSpecs&) override {
         return binder::Status::ok();
     }
 
     binder::Status getDesiredDisplayModeSpecs(const sp<IBinder>& /*displayToken*/,
-                                              gui::DisplayModeSpecs* /*outSpecs*/) override {
+                                              gui::DisplayModeSpecs*) override {
         return binder::Status::ok();
     }
 
@@ -991,6 +987,10 @@
         return binder::Status::ok();
     }
 
+    binder::Status getOverlaySupport(gui::OverlayProperties* /*properties*/) override {
+        return binder::Status::ok();
+    }
+
 protected:
     IBinder* onAsBinder() override { return nullptr; }
 
diff --git a/libs/input/Android.bp b/libs/input/Android.bp
index 29e02cf..34ef7b4 100644
--- a/libs/input/Android.bp
+++ b/libs/input/Android.bp
@@ -89,7 +89,6 @@
             shared_libs: [
                 "libutils",
                 "libbinder",
-                "libui",
             ],
 
             static_libs: [
diff --git a/libs/input/Input.cpp b/libs/input/Input.cpp
index 579b28e..9e8ebf3 100644
--- a/libs/input/Input.cpp
+++ b/libs/input/Input.cpp
@@ -21,7 +21,9 @@
 #include <cutils/compiler.h>
 #include <inttypes.h>
 #include <string.h>
+#include <optional>
 
+#include <android-base/file.h>
 #include <android-base/logging.h>
 #include <android-base/stringprintf.h>
 #include <cutils/compiler.h>
@@ -34,7 +36,7 @@
 #ifdef __linux__
 #include <binder/Parcel.h>
 #endif
-#ifdef __ANDROID__
+#if defined(__ANDROID__)
 #include <sys/random.h>
 #endif
 
@@ -112,15 +114,31 @@
 }
 
 // --- IdGenerator ---
+#if defined(__ANDROID__)
+[[maybe_unused]]
+#endif
+static 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) {
+        return -errno;
+    }
+
+    base::unique_fd fd(ret);
+    if (!base::ReadFully(fd, data, size)) {
+        return -errno;
+    }
+    return OK;
+}
+
 IdGenerator::IdGenerator(Source source) : mSource(source) {}
 
 int32_t IdGenerator::nextId() const {
     constexpr uint32_t SEQUENCE_NUMBER_MASK = ~SOURCE_MASK;
     int32_t id = 0;
 
-// Avoid building against syscall getrandom(2) on host, which will fail build on Mac. Host doesn't
-// use sequence number so just always return mSource.
-#ifdef __ANDROID__
+#if defined(__ANDROID__)
+    // On device, prefer 'getrandom' to '/dev/urandom' because it's faster.
     constexpr size_t BUF_LEN = sizeof(id);
     size_t totalBytes = 0;
     while (totalBytes < BUF_LEN) {
@@ -132,8 +150,17 @@
         }
         totalBytes += bytes;
     }
+#else
+#if defined(__linux__)
+    // On host, <sys/random.h> / GRND_NONBLOCK is not available
+    while (true) {
+        status_t result = getRandomBytes(reinterpret_cast<uint8_t*>(&id), sizeof(id));
+        if (result == OK) {
+            break;
+        }
+    }
+#endif // __linux__
 #endif // __ANDROID__
-
     return (id & SEQUENCE_NUMBER_MASK) | static_cast<int32_t>(mSource);
 }
 
@@ -212,6 +239,10 @@
     return (source & test) == test;
 }
 
+bool isStylusToolType(uint32_t toolType) {
+    return toolType == AMOTION_EVENT_TOOL_TYPE_STYLUS || toolType == AMOTION_EVENT_TOOL_TYPE_ERASER;
+}
+
 VerifiedKeyEvent verifiedKeyEventFromKeyEvent(const KeyEvent& event) {
     return {{VerifiedInputEvent::Type::KEY, event.getDeviceId(), event.getEventTime(),
              event.getSource(), event.getDisplayId()},
@@ -412,14 +443,6 @@
     return true;
 }
 
-void PointerCoords::copyFrom(const PointerCoords& other) {
-    bits = other.bits;
-    uint32_t count = BitSet64::count(bits);
-    for (uint32_t i = 0; i < count; i++) {
-        values[i] = other.values[i];
-    }
-}
-
 void PointerCoords::transform(const ui::Transform& transform) {
     const vec2 xy = transform.transform(getXYValue());
     setAxisValue(AMOTION_EVENT_AXIS_X, xy.x);
@@ -530,21 +553,21 @@
                                 &pointerCoords[getPointerCount()]);
 }
 
-int MotionEvent::getSurfaceRotation() const {
+std::optional<ui::Rotation> MotionEvent::getSurfaceRotation() const {
     // The surface rotation is the rotation from the window's coordinate space to that of the
     // display. Since the event's transform takes display space coordinates to window space, the
     // returned surface rotation is the inverse of the rotation for the surface.
     switch (mTransform.getOrientation()) {
         case ui::Transform::ROT_0:
-            return DISPLAY_ORIENTATION_0;
+            return ui::ROTATION_0;
         case ui::Transform::ROT_90:
-            return DISPLAY_ORIENTATION_270;
+            return ui::ROTATION_270;
         case ui::Transform::ROT_180:
-            return DISPLAY_ORIENTATION_180;
+            return ui::ROTATION_180;
         case ui::Transform::ROT_270:
-            return DISPLAY_ORIENTATION_90;
+            return ui::ROTATION_90;
         default:
-            return -1;
+            return std::nullopt;
     }
 }
 
diff --git a/libs/input/InputDevice.cpp b/libs/input/InputDevice.cpp
index 3fe03c7..4751a7d 100644
--- a/libs/input/InputDevice.cpp
+++ b/libs/input/InputDevice.cpp
@@ -182,6 +182,7 @@
         mSources(other.mSources),
         mKeyboardType(other.mKeyboardType),
         mKeyCharacterMap(other.mKeyCharacterMap),
+        mSupportsUsi(other.mSupportsUsi),
         mHasVibrator(other.mHasVibrator),
         mHasBattery(other.mHasBattery),
         mHasButtonUnderPad(other.mHasButtonUnderPad),
@@ -210,6 +211,7 @@
     mHasBattery = false;
     mHasButtonUnderPad = false;
     mHasSensor = false;
+    mSupportsUsi = false;
     mMotionRanges.clear();
     mSensors.clear();
     mLights.clear();
diff --git a/libs/input/PropertyMap.cpp b/libs/input/PropertyMap.cpp
index 16ffa10..ed9ac9f 100644
--- a/libs/input/PropertyMap.cpp
+++ b/libs/input/PropertyMap.cpp
@@ -116,25 +116,24 @@
 
     Tokenizer* rawTokenizer;
     status_t status = Tokenizer::open(String8(filename), &rawTokenizer);
-    std::unique_ptr<Tokenizer> tokenizer(rawTokenizer);
     if (status) {
-        ALOGE("Error %d opening property file %s.", status, filename);
-    } else {
-#if DEBUG_PARSER_PERFORMANCE
-            nsecs_t startTime = systemTime(SYSTEM_TIME_MONOTONIC);
-#endif
-            Parser parser(outMap.get(), tokenizer.get());
-            status = parser.parse();
-#if DEBUG_PARSER_PERFORMANCE
-            nsecs_t elapsedTime = systemTime(SYSTEM_TIME_MONOTONIC) - startTime;
-            ALOGD("Parsed property file '%s' %d lines in %0.3fms.",
-                  tokenizer->getFilename().string(), tokenizer->getLineNumber(),
-                  elapsedTime / 1000000.0);
-#endif
-            if (status) {
-                return android::base::Error(BAD_VALUE) << "Could not parse " << filename;
-            }
+        return android::base::Error(-status) << "Could not open file: " << filename;
     }
+#if DEBUG_PARSER_PERFORMANCE
+    nsecs_t startTime = systemTime(SYSTEM_TIME_MONOTONIC);
+#endif
+    std::unique_ptr<Tokenizer> tokenizer(rawTokenizer);
+    Parser parser(outMap.get(), tokenizer.get());
+    status = parser.parse();
+#if DEBUG_PARSER_PERFORMANCE
+    nsecs_t elapsedTime = systemTime(SYSTEM_TIME_MONOTONIC) - startTime;
+    ALOGD("Parsed property file '%s' %d lines in %0.3fms.", tokenizer->getFilename().string(),
+          tokenizer->getLineNumber(), elapsedTime / 1000000.0);
+#endif
+    if (status) {
+        return android::base::Error(BAD_VALUE) << "Could not parse " << filename;
+    }
+
     return std::move(outMap);
 }
 
diff --git a/libs/input/TouchVideoFrame.cpp b/libs/input/TouchVideoFrame.cpp
index c62e098..c9393f4 100644
--- a/libs/input/TouchVideoFrame.cpp
+++ b/libs/input/TouchVideoFrame.cpp
@@ -40,17 +40,20 @@
 
 const struct timeval& TouchVideoFrame::getTimestamp() const { return mTimestamp; }
 
-void TouchVideoFrame::rotate(int32_t orientation) {
+void TouchVideoFrame::rotate(ui::Rotation orientation) {
     switch (orientation) {
-        case DISPLAY_ORIENTATION_90:
+        case ui::ROTATION_90:
             rotateQuarterTurn(false /*clockwise*/);
             break;
-        case DISPLAY_ORIENTATION_180:
+        case ui::ROTATION_180:
             rotate180();
             break;
-        case DISPLAY_ORIENTATION_270:
+        case ui::ROTATION_270:
             rotateQuarterTurn(true /*clockwise*/);
             break;
+        case ui::ROTATION_0:
+            // No need to rotate if there's no rotation.
+            break;
     }
 }
 
diff --git a/libs/input/VelocityControl.cpp b/libs/input/VelocityControl.cpp
index e2bfb50..5c008b1 100644
--- a/libs/input/VelocityControl.cpp
+++ b/libs/input/VelocityControl.cpp
@@ -37,6 +37,10 @@
     reset();
 }
 
+VelocityControlParameters& VelocityControl::getParameters() {
+    return mParameters;
+}
+
 void VelocityControl::setParameters(const VelocityControlParameters& parameters) {
     mParameters = parameters;
     reset();
diff --git a/libs/input/VelocityTracker.cpp b/libs/input/VelocityTracker.cpp
index 4a4f734..19b4684 100644
--- a/libs/input/VelocityTracker.cpp
+++ b/libs/input/VelocityTracker.cpp
@@ -150,6 +150,10 @@
 VelocityTracker::~VelocityTracker() {
 }
 
+bool VelocityTracker::isAxisSupported(int32_t axis) {
+    return DEFAULT_STRATEGY_BY_AXIS.find(axis) != DEFAULT_STRATEGY_BY_AXIS.end();
+}
+
 void VelocityTracker::configureStrategy(int32_t axis) {
     const bool isDifferentialAxis = DIFFERENTIAL_AXES.find(axis) != DIFFERENTIAL_AXES.end();
 
diff --git a/libs/input/android/os/IInputConstants.aidl b/libs/input/android/os/IInputConstants.aidl
index 5ce10a4..dab843b 100644
--- a/libs/input/android/os/IInputConstants.aidl
+++ b/libs/input/android/os/IInputConstants.aidl
@@ -35,6 +35,14 @@
     const int INVALID_INPUT_EVENT_ID = 0;
 
     /**
+     * Every input device has an id. This constant value is used when a valid input device id is not
+     * available.
+     * The virtual keyboard uses -1 as the input device id. Therefore, we use -2 as the value for
+     * an invalid input device.
+     */
+    const int INVALID_INPUT_DEVICE_ID = -2;
+
+    /**
      * The input event was injected from accessibility. Used in policyFlags for input event
      * injection.
      */
diff --git a/libs/input/tests/Android.bp b/libs/input/tests/Android.bp
index c53811a..5aae37d 100644
--- a/libs/input/tests/Android.bp
+++ b/libs/input/tests/Android.bp
@@ -10,6 +10,7 @@
 
 cc_test {
     name: "libinput_tests",
+    host_supported: true,
     srcs: [
         "IdGenerator_test.cpp",
         "InputChannel_test.cpp",
@@ -24,6 +25,7 @@
     static_libs: [
         "libgui_window_info_static",
         "libinput",
+        "libui-types",
     ],
     cflags: [
         "-Wall",
@@ -35,11 +37,13 @@
         "libbinder",
         "libcutils",
         "liblog",
-        "libui",
         "libutils",
         "libvintf",
     ],
     data: ["data/*"],
+    test_options: {
+        unit_test: true,
+    },
     test_suites: ["device-tests"],
 }
 
@@ -60,7 +64,6 @@
         "libcutils",
         "libutils",
         "libbinder",
-        "libui",
         "libbase",
     ],
 }
diff --git a/libs/input/tests/InputDevice_test.cpp b/libs/input/tests/InputDevice_test.cpp
index e872fa4..2344463 100644
--- a/libs/input/tests/InputDevice_test.cpp
+++ b/libs/input/tests/InputDevice_test.cpp
@@ -65,6 +65,9 @@
     }
 
     void SetUp() override {
+#if !defined(__ANDROID__)
+        GTEST_SKIP() << "b/253299089 Generic files are currently read directly from device.";
+#endif
         loadKeyLayout("Generic");
         loadKeyCharacterMap("Generic");
     }
@@ -131,6 +134,9 @@
 }
 
 TEST(InputDeviceKeyLayoutTest, DoesNotLoadWhenRequiredKernelConfigIsMissing) {
+#if !defined(__ANDROID__)
+    GTEST_SKIP() << "Can't check kernel configs on host";
+#endif
     std::string klPath = base::GetExecutableDirectory() + "/data/kl_with_required_fake_config.kl";
     base::Result<std::shared_ptr<KeyLayoutMap>> ret = KeyLayoutMap::load(klPath);
     ASSERT_FALSE(ret.ok()) << "Should not be able to load KeyLayout at " << klPath;
@@ -139,6 +145,9 @@
 }
 
 TEST(InputDeviceKeyLayoutTest, LoadsWhenRequiredKernelConfigIsPresent) {
+#if !defined(__ANDROID__)
+    GTEST_SKIP() << "Can't check kernel configs on host";
+#endif
     std::string klPath = base::GetExecutableDirectory() + "/data/kl_with_required_real_config.kl";
     base::Result<std::shared_ptr<KeyLayoutMap>> ret = KeyLayoutMap::load(klPath);
     ASSERT_TRUE(ret.ok()) << "Cannot load KeyLayout at " << klPath;
diff --git a/libs/input/tests/TouchVideoFrame_test.cpp b/libs/input/tests/TouchVideoFrame_test.cpp
index 654b236..081a995 100644
--- a/libs/input/tests/TouchVideoFrame_test.cpp
+++ b/libs/input/tests/TouchVideoFrame_test.cpp
@@ -73,38 +73,38 @@
 TEST(TouchVideoFrame, Rotate90_0x0) {
     TouchVideoFrame frame(0, 0, {}, TIMESTAMP);
     TouchVideoFrame frameRotated(0, 0, {}, TIMESTAMP);
-    frame.rotate(DISPLAY_ORIENTATION_90);
+    frame.rotate(ui::ROTATION_90);
     ASSERT_EQ(frame, frameRotated);
 }
 
 TEST(TouchVideoFrame, Rotate90_1x1) {
     TouchVideoFrame frame(1, 1, {1}, TIMESTAMP);
     TouchVideoFrame frameRotated(1, 1, {1}, TIMESTAMP);
-    frame.rotate(DISPLAY_ORIENTATION_90);
+    frame.rotate(ui::ROTATION_90);
     ASSERT_EQ(frame, frameRotated);
 }
 
 TEST(TouchVideoFrame, Rotate90_2x2) {
     TouchVideoFrame frame(2, 2, {1, 2, 3, 4}, TIMESTAMP);
     TouchVideoFrame frameRotated(2, 2, {2, 4, 1, 3}, TIMESTAMP);
-    frame.rotate(DISPLAY_ORIENTATION_90);
+    frame.rotate(ui::ROTATION_90);
     ASSERT_EQ(frame, frameRotated);
 }
 
 TEST(TouchVideoFrame, Rotate90_3x2) {
     TouchVideoFrame frame(3, 2, {1, 2, 3, 4, 5, 6}, TIMESTAMP);
     TouchVideoFrame frameRotated(2, 3, {2, 4, 6, 1, 3, 5}, TIMESTAMP);
-    frame.rotate(DISPLAY_ORIENTATION_90);
+    frame.rotate(ui::ROTATION_90);
     ASSERT_EQ(frame, frameRotated);
 }
 
 TEST(TouchVideoFrame, Rotate90_3x2_4times) {
     TouchVideoFrame frame(3, 2, {1, 2, 3, 4, 5, 6}, TIMESTAMP);
     TouchVideoFrame frameOriginal(3, 2, {1, 2, 3, 4, 5, 6}, TIMESTAMP);
-    frame.rotate(DISPLAY_ORIENTATION_90);
-    frame.rotate(DISPLAY_ORIENTATION_90);
-    frame.rotate(DISPLAY_ORIENTATION_90);
-    frame.rotate(DISPLAY_ORIENTATION_90);
+    frame.rotate(ui::ROTATION_90);
+    frame.rotate(ui::ROTATION_90);
+    frame.rotate(ui::ROTATION_90);
+    frame.rotate(ui::ROTATION_90);
     ASSERT_EQ(frame, frameOriginal);
 }
 
@@ -113,43 +113,43 @@
 TEST(TouchVideoFrame, Rotate180_0x0) {
     TouchVideoFrame frame(0, 0, {}, TIMESTAMP);
     TouchVideoFrame frameRotated(0, 0, {}, TIMESTAMP);
-    frame.rotate(DISPLAY_ORIENTATION_180);
+    frame.rotate(ui::ROTATION_180);
     ASSERT_EQ(frame, frameRotated);
 }
 
 TEST(TouchVideoFrame, Rotate180_1x1) {
     TouchVideoFrame frame(1, 1, {1}, TIMESTAMP);
     TouchVideoFrame frameRotated(1, 1, {1}, TIMESTAMP);
-    frame.rotate(DISPLAY_ORIENTATION_180);
+    frame.rotate(ui::ROTATION_180);
     ASSERT_EQ(frame, frameRotated);
 }
 
 TEST(TouchVideoFrame, Rotate180_2x2) {
     TouchVideoFrame frame(2, 2, {1, 2, 3, 4}, TIMESTAMP);
     TouchVideoFrame frameRotated(2, 2, {4, 3, 2, 1}, TIMESTAMP);
-    frame.rotate(DISPLAY_ORIENTATION_180);
+    frame.rotate(ui::ROTATION_180);
     ASSERT_EQ(frame, frameRotated);
 }
 
 TEST(TouchVideoFrame, Rotate180_3x2) {
     TouchVideoFrame frame(3, 2, {1, 2, 3, 4, 5, 6}, TIMESTAMP);
     TouchVideoFrame frameRotated(3, 2, {6, 5, 4, 3, 2, 1}, TIMESTAMP);
-    frame.rotate(DISPLAY_ORIENTATION_180);
+    frame.rotate(ui::ROTATION_180);
     ASSERT_EQ(frame, frameRotated);
 }
 
 TEST(TouchVideoFrame, Rotate180_3x2_2times) {
     TouchVideoFrame frame(3, 2, {1, 2, 3, 4, 5, 6}, TIMESTAMP);
     TouchVideoFrame frameOriginal(3, 2, {1, 2, 3, 4, 5, 6}, TIMESTAMP);
-    frame.rotate(DISPLAY_ORIENTATION_180);
-    frame.rotate(DISPLAY_ORIENTATION_180);
+    frame.rotate(ui::ROTATION_180);
+    frame.rotate(ui::ROTATION_180);
     ASSERT_EQ(frame, frameOriginal);
 }
 
 TEST(TouchVideoFrame, Rotate180_3x3) {
     TouchVideoFrame frame(3, 3, {1, 2, 3, 4, 5, 6, 7, 8, 9}, TIMESTAMP);
     TouchVideoFrame frameRotated(3, 3, {9, 8, 7, 6, 5, 4, 3, 2, 1}, TIMESTAMP);
-    frame.rotate(DISPLAY_ORIENTATION_180);
+    frame.rotate(ui::ROTATION_180);
     ASSERT_EQ(frame, frameRotated);
 }
 
@@ -158,38 +158,38 @@
 TEST(TouchVideoFrame, Rotate270_0x0) {
     TouchVideoFrame frame(0, 0, {}, TIMESTAMP);
     TouchVideoFrame frameRotated(0, 0, {}, TIMESTAMP);
-    frame.rotate(DISPLAY_ORIENTATION_270);
+    frame.rotate(ui::ROTATION_270);
     ASSERT_EQ(frame, frameRotated);
 }
 
 TEST(TouchVideoFrame, Rotate270_1x1) {
     TouchVideoFrame frame(1, 1, {1}, TIMESTAMP);
     TouchVideoFrame frameRotated(1, 1, {1}, TIMESTAMP);
-    frame.rotate(DISPLAY_ORIENTATION_270);
+    frame.rotate(ui::ROTATION_270);
     ASSERT_EQ(frame, frameRotated);
 }
 
 TEST(TouchVideoFrame, Rotate270_2x2) {
     TouchVideoFrame frame(2, 2, {1, 2, 3, 4}, TIMESTAMP);
     TouchVideoFrame frameRotated(2, 2, {3, 1, 4, 2}, TIMESTAMP);
-    frame.rotate(DISPLAY_ORIENTATION_270);
+    frame.rotate(ui::ROTATION_270);
     ASSERT_EQ(frame, frameRotated);
 }
 
 TEST(TouchVideoFrame, Rotate270_3x2) {
     TouchVideoFrame frame(3, 2, {1, 2, 3, 4, 5, 6}, TIMESTAMP);
     TouchVideoFrame frameRotated(2, 3, {5, 3, 1, 6, 4, 2}, TIMESTAMP);
-    frame.rotate(DISPLAY_ORIENTATION_270);
+    frame.rotate(ui::ROTATION_270);
     ASSERT_EQ(frame, frameRotated);
 }
 
 TEST(TouchVideoFrame, Rotate270_3x2_4times) {
     TouchVideoFrame frame(3, 2, {1, 2, 3, 4, 5, 6}, TIMESTAMP);
     TouchVideoFrame frameOriginal(3, 2, {1, 2, 3, 4, 5, 6}, TIMESTAMP);
-    frame.rotate(DISPLAY_ORIENTATION_270);
-    frame.rotate(DISPLAY_ORIENTATION_270);
-    frame.rotate(DISPLAY_ORIENTATION_270);
-    frame.rotate(DISPLAY_ORIENTATION_270);
+    frame.rotate(ui::ROTATION_270);
+    frame.rotate(ui::ROTATION_270);
+    frame.rotate(ui::ROTATION_270);
+    frame.rotate(ui::ROTATION_270);
     ASSERT_EQ(frame, frameOriginal);
 }
 
diff --git a/libs/input/tests/VelocityTracker_test.cpp b/libs/input/tests/VelocityTracker_test.cpp
index bd12663..54feea2 100644
--- a/libs/input/tests/VelocityTracker_test.cpp
+++ b/libs/input/tests/VelocityTracker_test.cpp
@@ -299,6 +299,26 @@
 }
 
 /*
+ *================== VelocityTracker tests that do not require test motion data ====================
+ */
+TEST(SimpleVelocityTrackerTest, TestSupportedAxis) {
+    // Note that we are testing up to the max possible axis value, plus 3 more values. We are going
+    // beyond the max value to add a bit more protection. "3" is chosen arbitrarily to cover a few
+    // more values beyond the max.
+    for (int32_t axis = 0; axis <= AMOTION_EVENT_MAXIMUM_VALID_AXIS_VALUE + 3; axis++) {
+        switch (axis) {
+            case AMOTION_EVENT_AXIS_X:
+            case AMOTION_EVENT_AXIS_Y:
+            case AMOTION_EVENT_AXIS_SCROLL:
+                EXPECT_TRUE(VelocityTracker::isAxisSupported(axis)) << axis << " is supported";
+                break;
+            default:
+                EXPECT_FALSE(VelocityTracker::isAxisSupported(axis)) << axis << " is NOT supported";
+        }
+    }
+}
+
+/*
  * ================== VelocityTracker tests generated manually =====================================
  */
 TEST_F(VelocityTrackerTest, TestDefaultStrategiesForPlanarAxes) {
diff --git a/libs/jpegrecoverymap/Android.bp b/libs/jpegrecoverymap/Android.bp
new file mode 100644
index 0000000..54af7c9
--- /dev/null
+++ b/libs/jpegrecoverymap/Android.bp
@@ -0,0 +1,70 @@
+// 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.
+
+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_static {
+    name: "libjpegrecoverymap",
+    host_supported: true,
+
+    export_include_dirs: ["include"],
+    local_include_dirs: ["include"],
+
+    srcs: [
+        "recoverymap.cpp",
+        "recoverymapmath.cpp",
+        "recoverymaputils.cpp",
+    ],
+
+    shared_libs: [
+        "libimage_io",
+        "libjpeg",
+        "libutils",
+    ],
+}
+
+cc_library_static {
+    name: "libjpegencoder",
+
+    shared_libs: [
+        "libjpeg",
+    ],
+
+    export_include_dirs: ["include"],
+
+    srcs: [
+        "jpegencoder.cpp",
+    ],
+}
+
+cc_library_static {
+    name: "libjpegdecoder",
+
+    shared_libs: [
+        "libjpeg",
+    ],
+
+    export_include_dirs: ["include"],
+
+    srcs: [
+        "jpegdecoder.cpp",
+    ],
+}
diff --git a/libs/jpegrecoverymap/OWNERS b/libs/jpegrecoverymap/OWNERS
new file mode 100644
index 0000000..133af5b
--- /dev/null
+++ b/libs/jpegrecoverymap/OWNERS
@@ -0,0 +1,4 @@
+arifdikici@google.com
+deakin@google.com
+dichenzhang@google.com
+kyslov@google.com
\ No newline at end of file
diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegdecoder.h b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegdecoder.h
new file mode 100644
index 0000000..5c9c8b6
--- /dev/null
+++ b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegdecoder.h
@@ -0,0 +1,99 @@
+
+/*
+ * 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.
+ */
+
+#ifndef ANDROID_JPEGRECOVERYMAP_JPEGDECODER_H
+#define ANDROID_JPEGRECOVERYMAP_JPEGDECODER_H
+
+// We must include cstdio before jpeglib.h. It is a requirement of libjpeg.
+#include <cstdio>
+extern "C" {
+#include <jerror.h>
+#include <jpeglib.h>
+}
+#include <utils/Errors.h>
+#include <vector>
+namespace android::recoverymap {
+/*
+ * Encapsulates a converter from JPEG to raw image (YUV420planer or grey-scale) format.
+ * This class is not thread-safe.
+ */
+class JpegDecoder {
+public:
+    JpegDecoder();
+    ~JpegDecoder();
+    /*
+     * Decompresses JPEG image to raw image (YUV420planer or grey-scale) format. After calling
+     * this method, call getDecompressedImage() to get the image.
+     * Returns false if decompressing the image fails.
+     */
+    bool decompressImage(const void* image, int length);
+    /*
+     * Returns the decompressed raw image buffer pointer. This method must be called only after
+     * calling decompressImage().
+     */
+    void* getDecompressedImagePtr();
+    /*
+     * Returns the decompressed raw image buffer size. This method must be called only after
+     * calling decompressImage().
+     */
+    size_t getDecompressedImageSize();
+    /*
+     * Returns the image width in pixels. This method must be called only after calling
+     * decompressImage().
+     */
+    size_t getDecompressedImageWidth();
+    /*
+     * Returns the image width in pixels. This method must be called only after calling
+     * decompressImage().
+     */
+    size_t getDecompressedImageHeight();
+    /*
+     * Returns the XMP data from the image.
+     */
+    void* getXMPPtr();
+    /*
+     * Returns the decompressed XMP buffer size. This method must be called only after
+     * calling decompressImage().
+     */
+    size_t getXMPSize();
+
+    bool getCompressedImageParameters(const void* image, int length,
+                              size_t* pWidth, size_t* pHeight,
+                              std::vector<uint8_t>* &iccData,
+                              std::vector<uint8_t>* &exifData);
+
+private:
+    bool decode(const void* image, int length);
+    // Returns false if errors occur.
+    bool decompress(jpeg_decompress_struct* cinfo, const uint8_t* dest, bool isSingleChannel);
+    bool decompressYUV(jpeg_decompress_struct* cinfo, const uint8_t* dest);
+    bool decompressSingleChannel(jpeg_decompress_struct* cinfo, const uint8_t* dest);
+    // Process 16 lines of Y and 16 lines of U/V each time.
+    // We must pass at least 16 scanlines according to libjpeg documentation.
+    static const int kCompressBatchSize = 16;
+    // The buffer that holds the decompressed result.
+    std::vector<JOCTET> mResultBuffer;
+    // The buffer that holds XMP Data.
+    std::vector<JOCTET> mXMPBuffer;
+
+    // Resolution of the decompressed image.
+    size_t mWidth;
+    size_t mHeight;
+};
+} /* namespace android  */
+
+#endif // ANDROID_JPEGRECOVERYMAP_JPEGDECODER_H
diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegencoder.h b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegencoder.h
new file mode 100644
index 0000000..61aeb8a
--- /dev/null
+++ b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegencoder.h
@@ -0,0 +1,95 @@
+/*
+ * 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.
+ */
+
+#ifndef ANDROID_JPEGRECOVERYMAP_JPEGENCODER_H
+#define ANDROID_JPEGRECOVERYMAP_JPEGENCODER_H
+
+// We must include cstdio before jpeglib.h. It is a requirement of libjpeg.
+#include <cstdio>
+
+extern "C" {
+#include <jerror.h>
+#include <jpeglib.h>
+}
+
+#include <utils/Errors.h>
+#include <vector>
+
+namespace android::recoverymap {
+
+/*
+ * Encapsulates a converter from raw image (YUV420planer or grey-scale) to JPEG format.
+ * This class is not thread-safe.
+ */
+class JpegEncoder {
+public:
+    JpegEncoder();
+    ~JpegEncoder();
+
+    /*
+     * Compresses YUV420Planer image to JPEG format. After calling this method, call
+     * getCompressedImage() to get the image. |quality| is the jpeg image quality parameter to use.
+     * It ranges from 1 (poorest quality) to 100 (highest quality). |iccBuffer| is the buffer of
+     * ICC segment which will be added to the compressed image.
+     * Returns false if errors occur during compression.
+     */
+    bool compressImage(const void* image, int width, int height, int quality,
+                       const void* iccBuffer, unsigned int iccSize, bool isSingleChannel = false);
+
+    /*
+     * Returns the compressed JPEG buffer pointer. This method must be called only after calling
+     * compressImage().
+     */
+    void* getCompressedImagePtr();
+
+    /*
+     * Returns the compressed JPEG buffer size. This method must be called only after calling
+     * compressImage().
+     */
+    size_t getCompressedImageSize();
+
+private:
+    // initDestination(), emptyOutputBuffer() and emptyOutputBuffer() are callback functions to be
+    // passed into jpeg library.
+    static void initDestination(j_compress_ptr cinfo);
+    static boolean emptyOutputBuffer(j_compress_ptr cinfo);
+    static void terminateDestination(j_compress_ptr cinfo);
+    static void outputErrorMessage(j_common_ptr cinfo);
+
+    // Returns false if errors occur.
+    bool encode(const void* inYuv, int width, int height, int jpegQuality,
+                const void* iccBuffer, unsigned int iccSize, bool isSingleChannel);
+    void setJpegDestination(jpeg_compress_struct* cinfo);
+    void setJpegCompressStruct(int width, int height, int quality, jpeg_compress_struct* cinfo,
+                               bool isSingleChannel);
+    // Returns false if errors occur.
+    bool compress(jpeg_compress_struct* cinfo, const uint8_t* image, bool isSingleChannel);
+    bool compressYuv(jpeg_compress_struct* cinfo, const uint8_t* yuv);
+    bool compressSingleChannel(jpeg_compress_struct* cinfo, const uint8_t* image);
+
+    // The block size for encoded jpeg image buffer.
+    static const int kBlockSize = 16384;
+    // Process 16 lines of Y and 16 lines of U/V each time.
+    // We must pass at least 16 scanlines according to libjpeg documentation.
+    static const int kCompressBatchSize = 16;
+
+    // The buffer that holds the compressed result.
+    std::vector<JOCTET> mResultBuffer;
+};
+
+} /* namespace android  */
+
+#endif // ANDROID_JPEGRECOVERYMAP_JPEGENCODER_H
diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegrerrorcode.h b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegrerrorcode.h
new file mode 100644
index 0000000..9f53a57
--- /dev/null
+++ b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegrerrorcode.h
@@ -0,0 +1,49 @@
+/*
+ * 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 <utils/Errors.h>
+
+namespace android::recoverymap {
+
+enum {
+    // status_t map for errors in the media framework
+    // OK or NO_ERROR or 0 represents no error.
+
+    // See system/core/include/utils/Errors.h
+    // System standard errors from -1 through (possibly) -133
+    //
+    // Errors with special meanings and side effects.
+    // INVALID_OPERATION:  Operation attempted in an illegal state (will try to signal to app).
+    // DEAD_OBJECT:        Signal from CodecBase to MediaCodec that MediaServer has died.
+    // NAME_NOT_FOUND:     Signal from CodecBase to MediaCodec that the component was not found.
+
+    // JPEGR errors
+    JPEGR_IO_ERROR_BASE                 = -10000,
+    ERROR_JPEGR_INVALID_INPUT_TYPE      = JPEGR_IO_ERROR_BASE,
+    ERROR_JPEGR_INVALID_OUTPUT_TYPE     = JPEGR_IO_ERROR_BASE - 1,
+    ERROR_JPEGR_INVALID_NULL_PTR        = JPEGR_IO_ERROR_BASE - 2,
+    ERROR_JPEGR_RESOLUTION_MISMATCH     = JPEGR_IO_ERROR_BASE - 3,
+    ERROR_JPEGR_BUFFER_TOO_SMALL        = JPEGR_IO_ERROR_BASE - 4,
+    ERROR_JPEGR_INVALID_COLORGAMUT      = JPEGR_IO_ERROR_BASE - 5,
+
+    JPEGR_RUNTIME_ERROR_BASE            = -20000,
+    ERROR_JPEGR_ENCODE_ERROR            = JPEGR_RUNTIME_ERROR_BASE - 1,
+    ERROR_JPEGR_DECODE_ERROR            = JPEGR_RUNTIME_ERROR_BASE - 2,
+    ERROR_JPEGR_CALCULATION_ERROR       = JPEGR_RUNTIME_ERROR_BASE - 3,
+    ERROR_JPEGR_METADATA_ERROR          = JPEGR_RUNTIME_ERROR_BASE - 4,
+};
+
+}  // namespace android::recoverymap
diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h
new file mode 100644
index 0000000..5597303
--- /dev/null
+++ b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h
@@ -0,0 +1,363 @@
+/*
+ * 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.
+ */
+
+#ifndef ANDROID_JPEGRECOVERYMAP_RECOVERYMAP_H
+#define ANDROID_JPEGRECOVERYMAP_RECOVERYMAP_H
+
+#include "jpegrerrorcode.h"
+
+namespace android::recoverymap {
+
+typedef enum {
+  JPEGR_COLORGAMUT_UNSPECIFIED,
+  JPEGR_COLORGAMUT_BT709,
+  JPEGR_COLORGAMUT_P3,
+  JPEGR_COLORGAMUT_BT2100,
+} jpegr_color_gamut;
+
+// Transfer functions as defined for XMP metadata
+typedef enum {
+  JPEGR_TF_HLG = 0,
+  JPEGR_TF_PQ = 1,
+} jpegr_transfer_function;
+
+struct jpegr_info_struct {
+    size_t width;
+    size_t height;
+    std::vector<uint8_t>* iccData;
+    std::vector<uint8_t>* exifData;
+};
+
+/*
+ * Holds information for uncompressed image or recovery map.
+ */
+struct jpegr_uncompressed_struct {
+    // Pointer to the data location.
+    void* data;
+    // Width of the recovery map or image in pixels.
+    int width;
+    // Height of the recovery map or image in pixels.
+    int height;
+    // Color gamut.
+    jpegr_color_gamut colorGamut;
+};
+
+/*
+ * Holds information for compressed image or recovery map.
+ */
+struct jpegr_compressed_struct {
+    // Pointer to the data location.
+    void* data;
+    // Used data length in bytes.
+    int length;
+    // Maximum available data length in bytes.
+    int maxLength;
+    // Color gamut.
+    jpegr_color_gamut colorGamut;
+};
+
+/*
+ * Holds information for EXIF metadata.
+ */
+struct jpegr_exif_struct {
+    // Pointer to the data location.
+    void* data;
+    // Data length;
+    int length;
+};
+
+struct chromaticity_coord {
+  float x;
+  float y;
+};
+
+
+struct st2086_metadata {
+  // xy chromaticity coordinate of the red primary of the mastering display
+  chromaticity_coord redPrimary;
+  // xy chromaticity coordinate of the green primary of the mastering display
+  chromaticity_coord greenPrimary;
+  // xy chromaticity coordinate of the blue primary of the mastering display
+  chromaticity_coord bluePrimary;
+  // xy chromaticity coordinate of the white point of the mastering display
+  chromaticity_coord whitePoint;
+  // Maximum luminance in nits of the mastering display
+  uint32_t maxLuminance;
+  // Minimum luminance in nits of the mastering display
+  float minLuminance;
+};
+
+struct hdr10_metadata {
+  // Mastering display color volume
+  st2086_metadata st2086Metadata;
+  // Max frame average light level in nits
+  float maxFALL;
+  // Max content light level in nits
+  float maxCLL;
+};
+
+struct jpegr_metadata {
+  // JPEG/R version
+  uint32_t version;
+  // Range scaling factor for the map
+  float rangeScalingFactor;
+  // The transfer function for decoding the HDR representation of the image
+  jpegr_transfer_function transferFunction;
+  // HDR10 metadata, only applicable for transferFunction of JPEGR_TF_PQ
+  hdr10_metadata hdr10Metadata;
+};
+
+typedef struct jpegr_uncompressed_struct* jr_uncompressed_ptr;
+typedef struct jpegr_compressed_struct* jr_compressed_ptr;
+typedef struct jpegr_exif_struct* jr_exif_ptr;
+typedef struct jpegr_metadata* jr_metadata_ptr;
+typedef struct jpegr_info_struct* jr_info_ptr;
+
+class RecoveryMap {
+public:
+    /*
+     * Compress JPEGR image from 10-bit HDR YUV and 8-bit SDR YUV.
+     *
+     * Generate recovery map from the HDR and SDR inputs, compress SDR YUV to 8-bit JPEG and append
+     * the recovery map to the end of the compressed JPEG. HDR and SDR inputs must be the same
+     * resolution.
+     * @param uncompressed_p010_image uncompressed HDR image in P010 color format
+     * @param uncompressed_yuv_420_image uncompressed SDR image in YUV_420 color format
+     * @param hdr_tf transfer function of the HDR image
+     * @param dest destination of the compressed JPEGR image
+     * @param quality target quality of the JPEG encoding, must be in range of 0-100 where 100 is
+     *                the highest quality
+     * @param exif pointer to the exif metadata.
+     * @return NO_ERROR if encoding succeeds, error code if error occurs.
+     */
+    status_t encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
+                         jr_uncompressed_ptr uncompressed_yuv_420_image,
+                         jpegr_transfer_function hdr_tf,
+                         jr_compressed_ptr dest,
+                         int quality,
+                         jr_exif_ptr exif);
+
+    /*
+     * Compress JPEGR image from 10-bit HDR YUV, 8-bit SDR YUV and compressed 8-bit JPEG.
+     *
+     * This method requires HAL Hardware JPEG encoder.
+     *
+     * Generate recovery map from the HDR and SDR inputs, append the recovery map to the end of the
+     * compressed JPEG. HDR and SDR inputs must be the same resolution and color space.
+     * @param uncompressed_p010_image uncompressed HDR image in P010 color format
+     * @param uncompressed_yuv_420_image uncompressed SDR image in YUV_420 color format
+     * @param compressed_jpeg_image compressed 8-bit JPEG image
+     * @param hdr_tf transfer function of the HDR image
+     * @param dest destination of the compressed JPEGR image
+     * @return NO_ERROR if encoding succeeds, error code if error occurs.
+     */
+    status_t encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
+                         jr_uncompressed_ptr uncompressed_yuv_420_image,
+                         jr_compressed_ptr compressed_jpeg_image,
+                         jpegr_transfer_function hdr_tf,
+                         jr_compressed_ptr dest);
+
+    /*
+     * Compress JPEGR image from 10-bit HDR YUV and 8-bit SDR YUV.
+     *
+     * This method requires HAL Hardware JPEG encoder.
+     *
+     * Decode the compressed 8-bit JPEG image to YUV SDR, generate recovery map from the HDR input
+     * and the decoded SDR result, append the recovery map to the end of the compressed JPEG. HDR
+     * and SDR inputs must be the same resolution.
+     * @param uncompressed_p010_image uncompressed HDR image in P010 color format
+     * @param compressed_jpeg_image compressed 8-bit JPEG image
+     * @param hdr_tf transfer function of the HDR image
+     * @param dest destination of the compressed JPEGR image
+     * @return NO_ERROR if encoding succeeds, error code if error occurs.
+     */
+    status_t encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
+                         jr_compressed_ptr compressed_jpeg_image,
+                         jpegr_transfer_function hdr_tf,
+                         jr_compressed_ptr dest);
+
+    /*
+     * Decompress JPEGR image.
+     *
+     * The output JPEGR image is in RGBA_1010102 data format if decoding to HDR.
+     * @param compressed_jpegr_image compressed JPEGR image
+     * @param dest destination of the uncompressed JPEGR image
+     * @param exif destination of the decoded EXIF metadata.
+     * @param request_sdr flag that request SDR output. If set to true, decoder will only decode
+     *                    the primary image which is SDR. Setting of request_sdr and input source
+     *                    (HDR or SDR) can be found in the table below:
+     *                    |  input source  |  request_sdr  |  output of decoding  |
+     *                    |       HDR      |     true      |          SDR         |
+     *                    |       HDR      |     false     |          HDR         |
+     *                    |       SDR      |     true      |          SDR         |
+     *                    |       SDR      |     false     |          SDR         |
+     * @return NO_ERROR if decoding succeeds, error code if error occurs.
+     */
+    status_t decodeJPEGR(jr_compressed_ptr compressed_jpegr_image,
+                         jr_uncompressed_ptr dest,
+                         jr_exif_ptr exif = nullptr,
+                         bool request_sdr = false);
+
+    /*
+    * Gets Info from JPEGR file without decoding it.
+    *
+    * The output is filled jpegr_info structure
+    * @param compressed_jpegr_image compressed JPEGR image
+    * @param jpegr_info pointer to output JPEGR info
+    * @return NO_ERROR if JPEGR parsing succeeds, error code otherwise
+    */
+    status_t getJPEGRInfo(jr_compressed_ptr compressed_jpegr_image,
+                          jr_info_ptr jpegr_info);
+private:
+    /*
+     * This method is called in the decoding pipeline. It will decode the recovery map.
+     *
+     * @param compressed_recovery_map compressed recovery map
+     * @param dest decoded recover map
+     * @return NO_ERROR if decoding succeeds, error code if error occurs.
+     */
+    status_t decompressRecoveryMap(jr_compressed_ptr compressed_recovery_map,
+                               jr_uncompressed_ptr dest);
+
+    /*
+     * This method is called in the encoding pipeline. It will encode the recovery map.
+     *
+     * @param uncompressed_recovery_map uncompressed recovery map
+     * @param dest encoded recover map
+     * @return NO_ERROR if encoding succeeds, error code if error occurs.
+     */
+    status_t compressRecoveryMap(jr_uncompressed_ptr uncompressed_recovery_map,
+                               jr_compressed_ptr dest);
+
+    /*
+     * This method is called in the encoding pipeline. It will take the uncompressed 8-bit and
+     * 10-bit yuv images as input, and calculate the uncompressed recovery map. The input images
+     * must be the same resolution.
+     *
+     * @param uncompressed_yuv_420_image uncompressed SDR image in YUV_420 color format
+     * @param uncompressed_p010_image uncompressed HDR image in P010 color format
+     * @param dest recovery map; caller responsible for memory of data
+     * @param metadata metadata provides the transfer function for the HDR
+     *                 image; range_scaling_factor and hdr10 FALL and CLL will
+     *                 be updated.
+     * @return NO_ERROR if calculation succeeds, error code if error occurs.
+     */
+    status_t generateRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_image,
+                                 jr_uncompressed_ptr uncompressed_p010_image,
+                                 jr_metadata_ptr metadata,
+                                 jr_uncompressed_ptr dest);
+
+    /*
+     * This method is called in the decoding pipeline. It will take the uncompressed (decoded)
+     * 8-bit yuv image, the uncompressed (decoded) recovery map, and extracted JPEG/R metadata as
+     * input, and calculate the 10-bit recovered image. The recovered output image is the same
+     * color gamut as the SDR image, with the transfer function specified in the JPEG/R metadata,
+     * and is in RGBA1010102 data format.
+     *
+     * @param uncompressed_yuv_420_image uncompressed SDR image in YUV_420 color format
+     * @param uncompressed_recovery_map uncompressed recovery map
+     * @param metadata JPEG/R metadata extracted from XMP.
+     * @param dest reconstructed HDR image
+     * @return NO_ERROR if calculation succeeds, error code if error occurs.
+     */
+    status_t applyRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_image,
+                              jr_uncompressed_ptr uncompressed_recovery_map,
+                              jr_metadata_ptr metadata,
+                              jr_uncompressed_ptr dest);
+
+    /*
+     * This methoud is called to separate primary image and recovery map image from JPEGR
+     *
+     * @param compressed_jpegr_image compressed JPEGR image
+     * @param primary_image destination of primary image
+     * @param recovery_map destination of compressed recovery map
+     * @return NO_ERROR if calculation succeeds, error code if error occurs.
+    */
+    status_t extractPrimaryImageAndRecoveryMap(jr_compressed_ptr compressed_jpegr_image,
+                                               jr_compressed_ptr primary_image,
+                                               jr_compressed_ptr recovery_map);
+    /*
+     * This method is called in the decoding pipeline. It will read XMP metadata to find the start
+     * position of the compressed recovery map, and will extract the compressed recovery map.
+     *
+     * @param compressed_jpegr_image compressed JPEGR image
+     * @param dest destination of compressed recovery map
+     * @return NO_ERROR if calculation succeeds, error code if error occurs.
+     */
+    status_t extractRecoveryMap(jr_compressed_ptr compressed_jpegr_image,
+                                jr_compressed_ptr dest);
+
+    /*
+     * This method is called in the encoding pipeline. It will take the standard 8-bit JPEG image
+     * and the compressed recovery map as input, and update the XMP metadata with the end of JPEG
+     * marker, and append the compressed gian map after the JPEG.
+     *
+     * @param compressed_jpeg_image compressed 8-bit JPEG image
+     * @param compress_recovery_map compressed recover map
+     * @param metadata JPEG/R metadata to encode in XMP of the jpeg
+     * @param dest compressed JPEGR image
+     * @return NO_ERROR if calculation succeeds, error code if error occurs.
+     */
+    status_t appendRecoveryMap(jr_compressed_ptr compressed_jpeg_image,
+                               jr_compressed_ptr compressed_recovery_map,
+                               jr_metadata_ptr metadata,
+                               jr_compressed_ptr dest);
+
+    /*
+     * This method generates XMP metadata.
+     *
+     * below is an example of the XMP metadata that this function generates where
+     * secondary_image_length = 1000
+     * range_scaling_factor = 1.25
+     *
+     * <x:xmpmeta
+     *   xmlns:x="adobe:ns:meta/"
+     *   x:xmptk="Adobe XMP Core 5.1.2">
+     *   <rdf:RDF
+     *     xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
+     *     <rdf:Description
+     *       xmlns:GContainer="http://ns.google.com/photos/1.0/container/">
+     *       <GContainer:Version>1</GContainer:Version>
+     *       <GContainer:RangeScalingFactor>1.25</GContainer:RangeScalingFactor>
+     *       <GContainer:Directory>
+     *         <rdf:Seq>
+     *           <rdf:li>
+     *             <GContainer:Item
+     *               Item:Semantic="Primary"
+     *               Item:Mime="image/jpeg"/>
+     *           </rdf:li>
+     *           <rdf:li>
+     *             <GContainer:Item
+     *               Item:Semantic="RecoveryMap"
+     *               Item:Mime="image/jpeg"
+     *               Item:Length="1000"/>
+     *           </rdf:li>
+     *         </rdf:Seq>
+     *       </GContainer:Directory>
+     *     </rdf:Description>
+     *   </rdf:RDF>
+     * </x:xmpmeta>
+     *
+     * @param secondary_image_length length of secondary image
+     * @param metadata JPEG/R metadata to encode as XMP
+     * @return XMP metadata in type of string
+     */
+    std::string generateXmp(int secondary_image_length, jpegr_metadata& metadata);
+};
+
+} // namespace android::recoverymap
+
+#endif // ANDROID_JPEGRECOVERYMAP_RECOVERYMAP_H
diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymapmath.h b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymapmath.h
new file mode 100644
index 0000000..0fb64d3
--- /dev/null
+++ b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymapmath.h
@@ -0,0 +1,296 @@
+/*
+ * 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.
+ */
+
+#ifndef ANDROID_JPEGRECOVERYMAP_RECOVERYMAPMATH_H
+#define ANDROID_JPEGRECOVERYMAP_RECOVERYMAPMATH_H
+
+#include <stdint.h>
+
+#include <jpegrecoverymap/recoverymap.h>
+
+namespace android::recoverymap {
+
+////////////////////////////////////////////////////////////////////////////////
+// Framework
+
+const float kSdrWhiteNits = 100.0f;
+const float kHlgMaxNits = 1000.0f;
+const float kPqMaxNits = 10000.0f;
+
+struct Color {
+  union {
+    struct {
+      float r;
+      float g;
+      float b;
+    };
+    struct {
+      float y;
+      float u;
+      float v;
+    };
+  };
+};
+
+typedef Color (*ColorTransformFn)(Color);
+typedef float (*ColorCalculationFn)(Color);
+
+inline Color operator+=(Color& lhs, const Color& rhs) {
+  lhs.r += rhs.r;
+  lhs.g += rhs.g;
+  lhs.b += rhs.b;
+  return lhs;
+}
+inline Color operator-=(Color& lhs, const Color& rhs) {
+  lhs.r -= rhs.r;
+  lhs.g -= rhs.g;
+  lhs.b -= rhs.b;
+  return lhs;
+}
+
+inline Color operator+(const Color& lhs, const Color& rhs) {
+  Color temp = lhs;
+  return temp += rhs;
+}
+inline Color operator-(const Color& lhs, const Color& rhs) {
+  Color temp = lhs;
+  return temp -= rhs;
+}
+
+inline Color operator+=(Color& lhs, const float rhs) {
+  lhs.r += rhs;
+  lhs.g += rhs;
+  lhs.b += rhs;
+  return lhs;
+}
+inline Color operator-=(Color& lhs, const float rhs) {
+  lhs.r -= rhs;
+  lhs.g -= rhs;
+  lhs.b -= rhs;
+  return lhs;
+}
+inline Color operator*=(Color& lhs, const float rhs) {
+  lhs.r *= rhs;
+  lhs.g *= rhs;
+  lhs.b *= rhs;
+  return lhs;
+}
+inline Color operator/=(Color& lhs, const float rhs) {
+  lhs.r /= rhs;
+  lhs.g /= rhs;
+  lhs.b /= rhs;
+  return lhs;
+}
+
+inline Color operator+(const Color& lhs, const float rhs) {
+  Color temp = lhs;
+  return temp += rhs;
+}
+inline Color operator-(const Color& lhs, const float rhs) {
+  Color temp = lhs;
+  return temp -= rhs;
+}
+inline Color operator*(const Color& lhs, const float rhs) {
+  Color temp = lhs;
+  return temp *= rhs;
+}
+inline Color operator/(const Color& lhs, const float rhs) {
+  Color temp = lhs;
+  return temp /= rhs;
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// sRGB transformations
+// NOTE: sRGB has the same color primaries as BT.709, but different transfer
+// function. For this reason, all sRGB transformations here apply to BT.709,
+// except for those concerning transfer functions.
+
+/*
+ * Calculate the luminance of a linear RGB sRGB pixel, according to IEC 61966-2-1.
+ *
+ * [0.0, 1.0] range in and out.
+ */
+float srgbLuminance(Color e);
+
+/*
+ * Convert from OETF'd srgb YUV to RGB, according to ECMA TR/98.
+ */
+Color srgbYuvToRgb(Color e_gamma);
+
+/*
+ * Convert from OETF'd srgb RGB to YUV, according to ECMA TR/98.
+ */
+Color srgbRgbToYuv(Color e_gamma);
+
+/*
+ * Convert from srgb to linear, according to IEC 61966-2-1.
+ *
+ * [0.0, 1.0] range in and out.
+ */
+float srgbInvOetf(float e_gamma);
+Color srgbInvOetf(Color e_gamma);
+
+
+////////////////////////////////////////////////////////////////////////////////
+// Display-P3 transformations
+
+/*
+ * Calculated the luminance of a linear RGB P3 pixel, according to SMPTE EG 432-1.
+ *
+ * [0.0, 1.0] range in and out.
+ */
+float p3Luminance(Color e);
+
+
+////////////////////////////////////////////////////////////////////////////////
+// BT.2100 transformations - according to ITU-R BT.2100-2
+
+/*
+ * Calculate the luminance of a linear RGB BT.2100 pixel.
+ *
+ * [0.0, 1.0] range in and out.
+ */
+float bt2100Luminance(Color e);
+
+/*
+ * Convert from OETF'd BT.2100 RGB to YUV.
+ */
+Color bt2100RgbToYuv(Color e_gamma);
+
+/*
+ * Convert from OETF'd BT.2100 YUV to RGB.
+ */
+Color bt2100YuvToRgb(Color e_gamma);
+
+/*
+ * Convert from scene luminance to HLG.
+ *
+ * [0.0, 1.0] range in and out.
+ */
+float hlgOetf(float e);
+Color hlgOetf(Color e);
+
+/*
+ * Convert from HLG to scene luminance.
+ *
+ * [0.0, 1.0] range in and out.
+ */
+float hlgInvOetf(float e_gamma);
+Color hlgInvOetf(Color e_gamma);
+
+/*
+ * Convert from scene luminance to PQ.
+ *
+ * [0.0, 1.0] range in and out.
+ */
+float pqOetf(float e);
+Color pqOetf(Color e);
+
+/*
+ * Convert from PQ to scene luminance in nits.
+ *
+ * [0.0, 1.0] range in and out.
+ */
+float pqInvOetf(float e_gamma);
+Color pqInvOetf(Color e_gamma);
+
+
+////////////////////////////////////////////////////////////////////////////////
+// Color space conversions
+
+/*
+ * Convert between color spaces with linear RGB data, according to ITU-R BT.2407 and EG 432-1.
+ *
+ * All conversions are derived from multiplying the matrix for XYZ to output RGB color gamut by the
+ * matrix for input RGB color gamut to XYZ. The matrix for converting from XYZ to an RGB gamut is
+ * always the inverse of the RGB gamut to XYZ matrix.
+ */
+Color bt709ToP3(Color e);
+Color bt709ToBt2100(Color e);
+Color p3ToBt709(Color e);
+Color p3ToBt2100(Color e);
+Color bt2100ToBt709(Color e);
+Color bt2100ToP3(Color e);
+
+/*
+ * Identity conversion.
+ */
+inline Color identityConversion(Color e) { return e; }
+
+/*
+ * Get the conversion to apply to the HDR image for recovery map generation
+ */
+ColorTransformFn getHdrConversionFn(jpegr_color_gamut sdr_gamut, jpegr_color_gamut hdr_gamut);
+
+
+////////////////////////////////////////////////////////////////////////////////
+// Recovery map calculations
+
+/*
+ * Calculate the 8-bit unsigned integer recovery value for the given SDR and HDR
+ * luminances in linear space, and the hdr ratio to encode against.
+ */
+uint8_t encodeRecovery(float y_sdr, float y_hdr, float hdr_ratio);
+
+/*
+ * Calculates the linear luminance in nits after applying the given recovery
+ * value, with the given hdr ratio, to the given sdr input in the range [0, 1].
+ */
+Color applyRecovery(Color e, float recovery, float hdr_ratio);
+
+/*
+ * Helper for sampling from YUV 420 images.
+ */
+Color getYuv420Pixel(jr_uncompressed_ptr image, size_t x, size_t y);
+
+/*
+ * Helper for sampling from P010 images.
+ *
+ * Expect narrow-range image data for P010.
+ */
+Color getP010Pixel(jr_uncompressed_ptr image, size_t x, size_t y);
+
+/*
+ * Sample the image at the provided location, with a weighting based on nearby
+ * pixels and the map scale factor.
+ */
+Color sampleYuv420(jr_uncompressed_ptr map, size_t map_scale_factor, size_t x, size_t y);
+
+/*
+ * Sample the image at the provided location, with a weighting based on nearby
+ * pixels and the map scale factor.
+ *
+ * Expect narrow-range image data for P010.
+ */
+Color sampleP010(jr_uncompressed_ptr map, size_t map_scale_factor, size_t x, size_t y);
+
+/*
+ * Sample the recovery value for the map from a given x,y coordinate on a scale
+ * that is map scale factor larger than the map size.
+ */
+float sampleMap(jr_uncompressed_ptr map, size_t map_scale_factor, size_t x, size_t y);
+
+/*
+ * Convert from Color to RGBA1010102.
+ *
+ * Alpha always set to 1.0.
+ */
+uint32_t colorToRgba1010102(Color e_gamma);
+
+} // namespace android::recoverymap
+
+#endif // ANDROID_JPEGRECOVERYMAP_RECOVERYMAPMATH_H
diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymaputils.h b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymaputils.h
new file mode 100644
index 0000000..e35f2d7
--- /dev/null
+++ b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymaputils.h
@@ -0,0 +1,40 @@
+/*
+ * 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.
+ */
+
+#ifndef ANDROID_JPEGRECOVERYMAP_RECOVERYMAPUTILS_H
+#define ANDROID_JPEGRECOVERYMAP_RECOVERYMAPUTILS_H
+
+#include <stdint.h>
+#include <cstdio>
+
+
+namespace android::recoverymap {
+
+struct jpegr_metadata;
+
+/*
+ * Parses XMP packet and fills metadata with data from XMP
+ *
+ * @param xmp_data pointer to XMP packet
+ * @param xmp_size size of XMP packet
+ * @param metadata place to store HDR metadata values
+ * @return true if metadata is successfully retrieved, false otherwise
+*/
+bool getMetadataFromXMP(uint8_t* xmp_data, size_t xmp_size, jpegr_metadata* metadata);
+
+}
+
+#endif //ANDROID_JPEGRECOVERYMAP_RECOVERYMAPUTILS_H
\ No newline at end of file
diff --git a/libs/jpegrecoverymap/jpegdecoder.cpp b/libs/jpegrecoverymap/jpegdecoder.cpp
new file mode 100644
index 0000000..0185e55
--- /dev/null
+++ b/libs/jpegrecoverymap/jpegdecoder.cpp
@@ -0,0 +1,309 @@
+/*
+ * 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 <jpegrecoverymap/jpegdecoder.h>
+
+#include <cutils/log.h>
+
+#include <errno.h>
+#include <setjmp.h>
+#include <string>
+
+using namespace std;
+
+namespace android::recoverymap {
+
+const uint32_t kExifMarker = JPEG_APP0 + 1;
+const uint32_t kICCMarker = JPEG_APP0 + 2;
+
+struct jpegr_source_mgr : jpeg_source_mgr {
+    jpegr_source_mgr(const uint8_t* ptr, int len);
+    ~jpegr_source_mgr();
+
+    const uint8_t* mBufferPtr;
+    size_t mBufferLength;
+};
+
+struct jpegrerror_mgr {
+    struct jpeg_error_mgr pub;
+    jmp_buf setjmp_buffer;
+};
+
+static void jpegr_init_source(j_decompress_ptr cinfo) {
+    jpegr_source_mgr* src = static_cast<jpegr_source_mgr*>(cinfo->src);
+    src->next_input_byte = static_cast<const JOCTET*>(src->mBufferPtr);
+    src->bytes_in_buffer = src->mBufferLength;
+}
+
+static boolean jpegr_fill_input_buffer(j_decompress_ptr /* cinfo */) {
+    ALOGE("%s : should not get here", __func__);
+    return FALSE;
+}
+
+static void jpegr_skip_input_data(j_decompress_ptr cinfo, long num_bytes) {
+    jpegr_source_mgr* src = static_cast<jpegr_source_mgr*>(cinfo->src);
+
+    if (num_bytes > static_cast<long>(src->bytes_in_buffer)) {
+        ALOGE("jpegr_skip_input_data - num_bytes > (long)src->bytes_in_buffer");
+    } else {
+        src->next_input_byte += num_bytes;
+        src->bytes_in_buffer -= num_bytes;
+    }
+}
+
+static void jpegr_term_source(j_decompress_ptr /*cinfo*/) {}
+
+jpegr_source_mgr::jpegr_source_mgr(const uint8_t* ptr, int len) :
+        mBufferPtr(ptr), mBufferLength(len) {
+    init_source = jpegr_init_source;
+    fill_input_buffer = jpegr_fill_input_buffer;
+    skip_input_data = jpegr_skip_input_data;
+    resync_to_restart = jpeg_resync_to_restart;
+    term_source = jpegr_term_source;
+}
+
+jpegr_source_mgr::~jpegr_source_mgr() {}
+
+static void jpegrerror_exit(j_common_ptr cinfo) {
+    jpegrerror_mgr* err = reinterpret_cast<jpegrerror_mgr*>(cinfo->err);
+    longjmp(err->setjmp_buffer, 1);
+}
+
+JpegDecoder::JpegDecoder() {
+}
+
+JpegDecoder::~JpegDecoder() {
+}
+
+bool JpegDecoder::decompressImage(const void* image, int length) {
+    if (image == nullptr || length <= 0) {
+        ALOGE("Image size can not be handled: %d", length);
+        return false;
+    }
+
+    mResultBuffer.clear();
+    mXMPBuffer.clear();
+    if (!decode(image, length)) {
+        return false;
+    }
+
+    return true;
+}
+
+void* JpegDecoder::getDecompressedImagePtr() {
+    return mResultBuffer.data();
+}
+
+size_t JpegDecoder::getDecompressedImageSize() {
+    return mResultBuffer.size();
+}
+
+void* JpegDecoder::getXMPPtr() {
+    return mXMPBuffer.data();
+}
+
+size_t JpegDecoder::getXMPSize() {
+    return mXMPBuffer.size();
+}
+
+
+size_t JpegDecoder::getDecompressedImageWidth() {
+    return mWidth;
+}
+
+size_t JpegDecoder::getDecompressedImageHeight() {
+    return mHeight;
+}
+
+bool JpegDecoder::decode(const void* image, int length) {
+    jpeg_decompress_struct cinfo;
+    jpegr_source_mgr mgr(static_cast<const uint8_t*>(image), length);
+    jpegrerror_mgr myerr;
+    string nameSpace = "http://ns.adobe.com/xap/1.0/";
+
+    cinfo.err = jpeg_std_error(&myerr.pub);
+    myerr.pub.error_exit = jpegrerror_exit;
+
+    if (setjmp(myerr.setjmp_buffer)) {
+        jpeg_destroy_decompress(&cinfo);
+        return false;
+    }
+    jpeg_create_decompress(&cinfo);
+
+    jpeg_save_markers(&cinfo, kExifMarker, 0xFFFF);
+
+    cinfo.src = &mgr;
+    jpeg_read_header(&cinfo, TRUE);
+
+    // Save XMP Data
+    for (jpeg_marker_struct* marker = cinfo.marker_list; marker; marker = marker->next) {
+        if (marker->marker == kExifMarker) {
+            const unsigned int len = marker->data_length;
+            if (len > nameSpace.size() &&
+                !strncmp(reinterpret_cast<const char*>(marker->data),
+                         nameSpace.c_str(), nameSpace.size())) {
+                mXMPBuffer.resize(len+1, 0);
+                memcpy(static_cast<void*>(mXMPBuffer.data()), marker->data, len);
+                break;
+            }
+        }
+    }
+
+
+    mWidth = cinfo.image_width;
+    mHeight = cinfo.image_height;
+
+    if (cinfo.jpeg_color_space == JCS_YCbCr) {
+        mResultBuffer.resize(cinfo.image_width * cinfo.image_height * 3 / 2, 0);
+    } else if (cinfo.jpeg_color_space == JCS_GRAYSCALE) {
+        mResultBuffer.resize(cinfo.image_width * cinfo.image_height, 0);
+    }
+
+    cinfo.raw_data_out = TRUE;
+    cinfo.dct_method = JDCT_IFAST;
+    cinfo.out_color_space = cinfo.jpeg_color_space;
+
+    jpeg_start_decompress(&cinfo);
+
+    if (!decompress(&cinfo, static_cast<const uint8_t*>(mResultBuffer.data()),
+            cinfo.jpeg_color_space == JCS_GRAYSCALE)) {
+        return false;
+    }
+
+    jpeg_finish_decompress(&cinfo);
+    jpeg_destroy_decompress(&cinfo);
+
+    return true;
+}
+
+bool JpegDecoder::decompress(jpeg_decompress_struct* cinfo, const uint8_t* dest,
+        bool isSingleChannel) {
+    if (isSingleChannel) {
+        return decompressSingleChannel(cinfo, dest);
+    }
+    return decompressYUV(cinfo, dest);
+}
+
+bool JpegDecoder::getCompressedImageParameters(const void* image, int length,
+                              size_t *pWidth, size_t *pHeight,
+                              std::vector<uint8_t> *&iccData , std::vector<uint8_t> *&exifData) {
+    jpeg_decompress_struct cinfo;
+    jpegr_source_mgr mgr(static_cast<const uint8_t*>(image), length);
+    jpegrerror_mgr myerr;
+    cinfo.err = jpeg_std_error(&myerr.pub);
+    myerr.pub.error_exit = jpegrerror_exit;
+
+    if (setjmp(myerr.setjmp_buffer)) {
+        jpeg_destroy_decompress(&cinfo);
+        return false;
+    }
+    jpeg_create_decompress(&cinfo);
+
+    jpeg_save_markers(&cinfo, kExifMarker, 0xFFFF);
+    jpeg_save_markers(&cinfo, kICCMarker, 0xFFFF);
+
+    cinfo.src = &mgr;
+    if (jpeg_read_header(&cinfo, TRUE) != JPEG_HEADER_OK) {
+        jpeg_destroy_decompress(&cinfo);
+        return false;
+    }
+
+    *pWidth = cinfo.image_width;
+    *pHeight = cinfo.image_height;
+
+    //TODO: Parse iccProfile and exifData
+    (void)iccData;
+    (void)exifData;
+
+
+    jpeg_destroy_decompress(&cinfo);
+    return true;
+}
+
+
+bool JpegDecoder::decompressYUV(jpeg_decompress_struct* cinfo, const uint8_t* dest) {
+
+    JSAMPROW y[kCompressBatchSize];
+    JSAMPROW cb[kCompressBatchSize / 2];
+    JSAMPROW cr[kCompressBatchSize / 2];
+    JSAMPARRAY planes[3] {y, cb, cr};
+
+    size_t y_plane_size = cinfo->image_width * cinfo->image_height;
+    size_t uv_plane_size = y_plane_size / 4;
+    uint8_t* y_plane = const_cast<uint8_t*>(dest);
+    uint8_t* u_plane = const_cast<uint8_t*>(dest + y_plane_size);
+    uint8_t* v_plane = const_cast<uint8_t*>(dest + y_plane_size + uv_plane_size);
+    std::unique_ptr<uint8_t[]> empty(new uint8_t[cinfo->image_width]);
+    memset(empty.get(), 0, cinfo->image_width);
+
+    while (cinfo->output_scanline < cinfo->image_height) {
+        for (int i = 0; i < kCompressBatchSize; ++i) {
+            size_t scanline = cinfo->output_scanline + i;
+            if (scanline < cinfo->image_height) {
+                y[i] = y_plane + scanline * cinfo->image_width;
+            } else {
+                y[i] = empty.get();
+            }
+        }
+        // cb, cr only have half scanlines
+        for (int i = 0; i < kCompressBatchSize / 2; ++i) {
+            size_t scanline = cinfo->output_scanline / 2 + i;
+            if (scanline < cinfo->image_height / 2) {
+                int offset = scanline * (cinfo->image_width / 2);
+                cb[i] = u_plane + offset;
+                cr[i] = v_plane + offset;
+            } else {
+                cb[i] = cr[i] = empty.get();
+            }
+        }
+
+        int processed = jpeg_read_raw_data(cinfo, planes, kCompressBatchSize);
+        if (processed != kCompressBatchSize) {
+            ALOGE("Number of processed lines does not equal input lines.");
+            return false;
+        }
+    }
+    return true;
+}
+
+bool JpegDecoder::decompressSingleChannel(jpeg_decompress_struct* cinfo, const uint8_t* dest) {
+    JSAMPROW y[kCompressBatchSize];
+    JSAMPARRAY planes[1] {y};
+
+    uint8_t* y_plane = const_cast<uint8_t*>(dest);
+    std::unique_ptr<uint8_t[]> empty(new uint8_t[cinfo->image_width]);
+    memset(empty.get(), 0, cinfo->image_width);
+
+    while (cinfo->output_scanline < cinfo->image_height) {
+        for (int i = 0; i < kCompressBatchSize; ++i) {
+            size_t scanline = cinfo->output_scanline + i;
+            if (scanline < cinfo->image_height) {
+                y[i] = y_plane + scanline * cinfo->image_width;
+            } else {
+                y[i] = empty.get();
+            }
+        }
+
+        int processed = jpeg_read_raw_data(cinfo, planes, kCompressBatchSize);
+        if (processed != kCompressBatchSize / 2) {
+            ALOGE("Number of processed lines does not equal input lines.");
+            return false;
+        }
+    }
+    return true;
+}
+
+} // namespace android
diff --git a/libs/jpegrecoverymap/jpegencoder.cpp b/libs/jpegrecoverymap/jpegencoder.cpp
new file mode 100644
index 0000000..1997bf9
--- /dev/null
+++ b/libs/jpegrecoverymap/jpegencoder.cpp
@@ -0,0 +1,239 @@
+/*
+ * 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 <jpegrecoverymap/jpegencoder.h>
+
+#include <cutils/log.h>
+
+#include <errno.h>
+
+namespace android::recoverymap {
+
+// The destination manager that can access |mResultBuffer| in JpegEncoder.
+struct destination_mgr {
+public:
+    struct jpeg_destination_mgr mgr;
+    JpegEncoder* encoder;
+};
+
+JpegEncoder::JpegEncoder() {
+}
+
+JpegEncoder::~JpegEncoder() {
+}
+
+bool JpegEncoder::compressImage(const void* image, int width, int height, int quality,
+                                   const void* iccBuffer, unsigned int iccSize,
+                                   bool isSingleChannel) {
+    if (width % 8 != 0 || height % 2 != 0) {
+        ALOGE("Image size can not be handled: %dx%d", width, height);
+        return false;
+    }
+
+    mResultBuffer.clear();
+    if (!encode(image, width, height, quality, iccBuffer, iccSize, isSingleChannel)) {
+        return false;
+    }
+    ALOGI("Compressed JPEG: %d[%dx%d] -> %zu bytes",
+        (width * height * 12) / 8, width, height, mResultBuffer.size());
+    return true;
+}
+
+void* JpegEncoder::getCompressedImagePtr() {
+    return mResultBuffer.data();
+}
+
+size_t JpegEncoder::getCompressedImageSize() {
+    return mResultBuffer.size();
+}
+
+void JpegEncoder::initDestination(j_compress_ptr cinfo) {
+    destination_mgr* dest = reinterpret_cast<destination_mgr*>(cinfo->dest);
+    std::vector<JOCTET>& buffer = dest->encoder->mResultBuffer;
+    buffer.resize(kBlockSize);
+    dest->mgr.next_output_byte = &buffer[0];
+    dest->mgr.free_in_buffer = buffer.size();
+}
+
+boolean JpegEncoder::emptyOutputBuffer(j_compress_ptr cinfo) {
+    destination_mgr* dest = reinterpret_cast<destination_mgr*>(cinfo->dest);
+    std::vector<JOCTET>& buffer = dest->encoder->mResultBuffer;
+    size_t oldsize = buffer.size();
+    buffer.resize(oldsize + kBlockSize);
+    dest->mgr.next_output_byte = &buffer[oldsize];
+    dest->mgr.free_in_buffer = kBlockSize;
+    return true;
+}
+
+void JpegEncoder::terminateDestination(j_compress_ptr cinfo) {
+    destination_mgr* dest = reinterpret_cast<destination_mgr*>(cinfo->dest);
+    std::vector<JOCTET>& buffer = dest->encoder->mResultBuffer;
+    buffer.resize(buffer.size() - dest->mgr.free_in_buffer);
+}
+
+void JpegEncoder::outputErrorMessage(j_common_ptr cinfo) {
+    char buffer[JMSG_LENGTH_MAX];
+
+    /* Create the message */
+    (*cinfo->err->format_message) (cinfo, buffer);
+    ALOGE("%s\n", buffer);
+}
+
+bool JpegEncoder::encode(const void* image, int width, int height, int jpegQuality,
+                         const void* iccBuffer, unsigned int iccSize, bool isSingleChannel) {
+    jpeg_compress_struct cinfo;
+    jpeg_error_mgr jerr;
+
+    cinfo.err = jpeg_std_error(&jerr);
+    // Override output_message() to print error log with ALOGE().
+    cinfo.err->output_message = &outputErrorMessage;
+    jpeg_create_compress(&cinfo);
+    setJpegDestination(&cinfo);
+
+    setJpegCompressStruct(width, height, jpegQuality, &cinfo, isSingleChannel);
+    jpeg_start_compress(&cinfo, TRUE);
+
+    if (iccBuffer != nullptr && iccSize > 0) {
+        jpeg_write_marker(&cinfo, JPEG_APP0 + 2, static_cast<const JOCTET*>(iccBuffer), iccSize);
+    }
+
+    if (!compress(&cinfo, static_cast<const uint8_t*>(image), isSingleChannel)) {
+        return false;
+    }
+    jpeg_finish_compress(&cinfo);
+    jpeg_destroy_compress(&cinfo);
+    return true;
+}
+
+void JpegEncoder::setJpegDestination(jpeg_compress_struct* cinfo) {
+    destination_mgr* dest = static_cast<struct destination_mgr *>((*cinfo->mem->alloc_small) (
+            (j_common_ptr) cinfo, JPOOL_PERMANENT, sizeof(destination_mgr)));
+    dest->encoder = this;
+    dest->mgr.init_destination = &initDestination;
+    dest->mgr.empty_output_buffer = &emptyOutputBuffer;
+    dest->mgr.term_destination = &terminateDestination;
+    cinfo->dest = reinterpret_cast<struct jpeg_destination_mgr*>(dest);
+}
+
+void JpegEncoder::setJpegCompressStruct(int width, int height, int quality,
+                                        jpeg_compress_struct* cinfo, bool isSingleChannel) {
+    cinfo->image_width = width;
+    cinfo->image_height = height;
+    if (isSingleChannel) {
+        cinfo->input_components = 1;
+        cinfo->in_color_space = JCS_GRAYSCALE;
+    } else {
+        cinfo->input_components = 3;
+        cinfo->in_color_space = JCS_YCbCr;
+    }
+    jpeg_set_defaults(cinfo);
+
+    jpeg_set_quality(cinfo, quality, TRUE);
+    jpeg_set_colorspace(cinfo, isSingleChannel ? JCS_GRAYSCALE : JCS_YCbCr);
+    cinfo->raw_data_in = TRUE;
+    cinfo->dct_method = JDCT_IFAST;
+
+    if (!isSingleChannel) {
+        // Configure sampling factors. The sampling factor is JPEG subsampling 420 because the
+        // source format is YUV420.
+        cinfo->comp_info[0].h_samp_factor = 2;
+        cinfo->comp_info[0].v_samp_factor = 2;
+        cinfo->comp_info[1].h_samp_factor = 1;
+        cinfo->comp_info[1].v_samp_factor = 1;
+        cinfo->comp_info[2].h_samp_factor = 1;
+        cinfo->comp_info[2].v_samp_factor = 1;
+    }
+}
+
+bool JpegEncoder::compress(
+        jpeg_compress_struct* cinfo, const uint8_t* image, bool isSingleChannel) {
+    if (isSingleChannel) {
+        return compressSingleChannel(cinfo, image);
+    }
+    return compressYuv(cinfo, image);
+}
+
+bool JpegEncoder::compressYuv(jpeg_compress_struct* cinfo, const uint8_t* yuv) {
+    JSAMPROW y[kCompressBatchSize];
+    JSAMPROW cb[kCompressBatchSize / 2];
+    JSAMPROW cr[kCompressBatchSize / 2];
+    JSAMPARRAY planes[3] {y, cb, cr};
+
+    size_t y_plane_size = cinfo->image_width * cinfo->image_height;
+    size_t uv_plane_size = y_plane_size / 4;
+    uint8_t* y_plane = const_cast<uint8_t*>(yuv);
+    uint8_t* u_plane = const_cast<uint8_t*>(yuv + y_plane_size);
+    uint8_t* v_plane = const_cast<uint8_t*>(yuv + y_plane_size + uv_plane_size);
+    std::unique_ptr<uint8_t[]> empty(new uint8_t[cinfo->image_width]);
+    memset(empty.get(), 0, cinfo->image_width);
+
+    while (cinfo->next_scanline < cinfo->image_height) {
+        for (int i = 0; i < kCompressBatchSize; ++i) {
+            size_t scanline = cinfo->next_scanline + i;
+            if (scanline < cinfo->image_height) {
+                y[i] = y_plane + scanline * cinfo->image_width;
+            } else {
+                y[i] = empty.get();
+            }
+        }
+        // cb, cr only have half scanlines
+        for (int i = 0; i < kCompressBatchSize / 2; ++i) {
+            size_t scanline = cinfo->next_scanline / 2 + i;
+            if (scanline < cinfo->image_height / 2) {
+                int offset = scanline * (cinfo->image_width / 2);
+                cb[i] = u_plane + offset;
+                cr[i] = v_plane + offset;
+            } else {
+                cb[i] = cr[i] = empty.get();
+            }
+        }
+
+        int processed = jpeg_write_raw_data(cinfo, planes, kCompressBatchSize);
+        if (processed != kCompressBatchSize) {
+            ALOGE("Number of processed lines does not equal input lines.");
+            return false;
+        }
+    }
+    return true;
+}
+
+bool JpegEncoder::compressSingleChannel(jpeg_compress_struct* cinfo, const uint8_t* image) {
+    JSAMPROW y[kCompressBatchSize];
+    JSAMPARRAY planes[1] {y};
+
+    uint8_t* y_plane = const_cast<uint8_t*>(image);
+    std::unique_ptr<uint8_t[]> empty(new uint8_t[cinfo->image_width]);
+    memset(empty.get(), 0, cinfo->image_width);
+
+    while (cinfo->next_scanline < cinfo->image_height) {
+        for (int i = 0; i < kCompressBatchSize; ++i) {
+            size_t scanline = cinfo->next_scanline + i;
+            if (scanline < cinfo->image_height) {
+                y[i] = y_plane + scanline * cinfo->image_width;
+            } else {
+                y[i] = empty.get();
+            }
+        }
+        int processed = jpeg_write_raw_data(cinfo, planes, kCompressBatchSize);
+        if (processed != kCompressBatchSize / 2) {
+            ALOGE("Number of processed lines does not equal input lines.");
+            return false;
+        }
+    }
+    return true;
+}
+
+} // namespace android
diff --git a/libs/jpegrecoverymap/recoverymap.cpp b/libs/jpegrecoverymap/recoverymap.cpp
new file mode 100644
index 0000000..f7f3622
--- /dev/null
+++ b/libs/jpegrecoverymap/recoverymap.cpp
@@ -0,0 +1,679 @@
+/*
+ * 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 <jpegrecoverymap/recoverymap.h>
+#include <jpegrecoverymap/jpegencoder.h>
+#include <jpegrecoverymap/jpegdecoder.h>
+#include <jpegrecoverymap/recoverymapmath.h>
+#include <jpegrecoverymap/recoverymaputils.h>
+
+#include <image_io/jpeg/jpeg_marker.h>
+#include <image_io/xml/xml_writer.h>
+#include <image_io/jpeg/jpeg_info.h>
+#include <image_io/jpeg/jpeg_scanner.h>
+#include <image_io/jpeg/jpeg_info_builder.h>
+#include <image_io/base/data_segment_data_source.h>
+#include <utils/Log.h>
+
+#include <memory>
+#include <sstream>
+#include <string>
+#include <cmath>
+
+using namespace std;
+using namespace photos_editing_formats::image_io;
+
+namespace android::recoverymap {
+
+#define JPEGR_CHECK(x)          \
+  {                             \
+    status_t status = (x);      \
+    if ((status) != NO_ERROR) { \
+      return status;            \
+    }                           \
+  }
+
+// The current JPEGR version that we encode to
+static const uint32_t kJpegrVersion = 1;
+
+// Map is quarter res / sixteenth size
+static const size_t kMapDimensionScaleFactor = 4;
+// JPEG compress quality (0 ~ 100) for recovery map
+static const int kMapCompressQuality = 85;
+
+// TODO: fill in st2086 metadata
+static const st2086_metadata kSt2086Metadata = {
+  {0.0f, 0.0f},
+  {0.0f, 0.0f},
+  {0.0f, 0.0f},
+  {0.0f, 0.0f},
+  0,
+  1.0f,
+};
+
+/*
+ * Helper function used for generating XMP metadata.
+ *
+ * @param prefix The prefix part of the name.
+ * @param suffix The suffix part of the name.
+ * @return A name of the form "prefix:suffix".
+ */
+string Name(const string &prefix, const string &suffix) {
+  std::stringstream ss;
+  ss << prefix << ":" << suffix;
+  return ss.str();
+}
+
+/*
+ * Helper function used for writing data to destination.
+ *
+ * @param destination destination of the data to be written.
+ * @param source source of data being written.
+ * @param length length of the data to be written.
+ * @param position cursor in desitination where the data is to be written.
+ * @return status of succeed or error code.
+ */
+status_t Write(jr_compressed_ptr destination, const void* source, size_t length, int &position) {
+  if (position + length > destination->maxLength) {
+    return ERROR_JPEGR_BUFFER_TOO_SMALL;
+  }
+
+  memcpy((uint8_t*)destination->data + sizeof(uint8_t) * position, source, length);
+  position += length;
+  return NO_ERROR;
+}
+
+status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
+                                  jr_uncompressed_ptr uncompressed_yuv_420_image,
+                                  jpegr_transfer_function hdr_tf,
+                                  jr_compressed_ptr dest,
+                                  int quality,
+                                  jr_exif_ptr /* exif */) {
+  if (uncompressed_p010_image == nullptr
+   || uncompressed_yuv_420_image == nullptr
+   || dest == nullptr) {
+    return ERROR_JPEGR_INVALID_NULL_PTR;
+  }
+
+  if (quality < 0 || quality > 100) {
+    return ERROR_JPEGR_INVALID_INPUT_TYPE;
+  }
+
+  if (uncompressed_p010_image->width != uncompressed_yuv_420_image->width
+   || uncompressed_p010_image->height != uncompressed_yuv_420_image->height) {
+    return ERROR_JPEGR_RESOLUTION_MISMATCH;
+  }
+
+  jpegr_metadata metadata;
+  metadata.version = kJpegrVersion;
+  metadata.transferFunction = hdr_tf;
+  if (hdr_tf == JPEGR_TF_PQ) {
+    metadata.hdr10Metadata.st2086Metadata = kSt2086Metadata;
+  }
+
+  jpegr_uncompressed_struct map;
+  JPEGR_CHECK(generateRecoveryMap(
+      uncompressed_yuv_420_image, uncompressed_p010_image, &metadata, &map));
+  std::unique_ptr<uint8_t[]> map_data;
+  map_data.reset(reinterpret_cast<uint8_t*>(map.data));
+
+  jpegr_compressed_struct compressed_map;
+  compressed_map.maxLength = map.width * map.height;
+  unique_ptr<uint8_t[]> compressed_map_data = make_unique<uint8_t[]>(compressed_map.maxLength);
+  compressed_map.data = compressed_map_data.get();
+  JPEGR_CHECK(compressRecoveryMap(&map, &compressed_map));
+
+  JpegEncoder jpeg_encoder;
+  // TODO: determine ICC data based on color gamut information
+  if (!jpeg_encoder.compressImage(uncompressed_yuv_420_image->data,
+                                  uncompressed_yuv_420_image->width,
+                                  uncompressed_yuv_420_image->height, quality, nullptr, 0)) {
+    return ERROR_JPEGR_ENCODE_ERROR;
+  }
+  jpegr_compressed_struct jpeg;
+  jpeg.data = jpeg_encoder.getCompressedImagePtr();
+  jpeg.length = jpeg_encoder.getCompressedImageSize();
+
+  JPEGR_CHECK(appendRecoveryMap(&jpeg, &compressed_map, &metadata, dest));
+
+  return NO_ERROR;
+}
+
+status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
+                                  jr_uncompressed_ptr uncompressed_yuv_420_image,
+                                  jr_compressed_ptr compressed_jpeg_image,
+                                  jpegr_transfer_function hdr_tf,
+                                  jr_compressed_ptr dest) {
+  if (uncompressed_p010_image == nullptr
+   || uncompressed_yuv_420_image == nullptr
+   || compressed_jpeg_image == nullptr
+   || dest == nullptr) {
+    return ERROR_JPEGR_INVALID_NULL_PTR;
+  }
+
+  if (uncompressed_p010_image->width != uncompressed_yuv_420_image->width
+   || uncompressed_p010_image->height != uncompressed_yuv_420_image->height) {
+    return ERROR_JPEGR_RESOLUTION_MISMATCH;
+  }
+
+  jpegr_metadata metadata;
+  metadata.version = kJpegrVersion;
+  metadata.transferFunction = hdr_tf;
+  if (hdr_tf == JPEGR_TF_PQ) {
+    metadata.hdr10Metadata.st2086Metadata = kSt2086Metadata;
+  }
+
+  jpegr_uncompressed_struct map;
+  JPEGR_CHECK(generateRecoveryMap(
+      uncompressed_yuv_420_image, uncompressed_p010_image, &metadata, &map));
+  std::unique_ptr<uint8_t[]> map_data;
+  map_data.reset(reinterpret_cast<uint8_t*>(map.data));
+
+  jpegr_compressed_struct compressed_map;
+  compressed_map.maxLength = map.width * map.height;
+  unique_ptr<uint8_t[]> compressed_map_data = make_unique<uint8_t[]>(compressed_map.maxLength);
+  compressed_map.data = compressed_map_data.get();
+  JPEGR_CHECK(compressRecoveryMap(&map, &compressed_map));
+
+  JPEGR_CHECK(appendRecoveryMap(compressed_jpeg_image, &compressed_map, &metadata, dest));
+
+  return NO_ERROR;
+}
+
+status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
+                                  jr_compressed_ptr compressed_jpeg_image,
+                                  jpegr_transfer_function hdr_tf,
+                                  jr_compressed_ptr dest) {
+  if (uncompressed_p010_image == nullptr
+   || compressed_jpeg_image == nullptr
+   || dest == nullptr) {
+    return ERROR_JPEGR_INVALID_NULL_PTR;
+  }
+
+  JpegDecoder jpeg_decoder;
+  if (!jpeg_decoder.decompressImage(compressed_jpeg_image->data, compressed_jpeg_image->length)) {
+    return ERROR_JPEGR_DECODE_ERROR;
+  }
+  jpegr_uncompressed_struct uncompressed_yuv_420_image;
+  uncompressed_yuv_420_image.data = jpeg_decoder.getDecompressedImagePtr();
+  uncompressed_yuv_420_image.width = jpeg_decoder.getDecompressedImageWidth();
+  uncompressed_yuv_420_image.height = jpeg_decoder.getDecompressedImageHeight();
+  uncompressed_yuv_420_image.colorGamut = compressed_jpeg_image->colorGamut;
+
+  if (uncompressed_p010_image->width != uncompressed_yuv_420_image.width
+   || uncompressed_p010_image->height != uncompressed_yuv_420_image.height) {
+    return ERROR_JPEGR_RESOLUTION_MISMATCH;
+  }
+
+  jpegr_metadata metadata;
+  metadata.version = kJpegrVersion;
+  metadata.transferFunction = hdr_tf;
+  if (hdr_tf == JPEGR_TF_PQ) {
+    metadata.hdr10Metadata.st2086Metadata = kSt2086Metadata;
+  }
+
+  jpegr_uncompressed_struct map;
+  JPEGR_CHECK(generateRecoveryMap(
+      &uncompressed_yuv_420_image, uncompressed_p010_image, &metadata, &map));
+  std::unique_ptr<uint8_t[]> map_data;
+  map_data.reset(reinterpret_cast<uint8_t*>(map.data));
+
+  jpegr_compressed_struct compressed_map;
+  compressed_map.maxLength = map.width * map.height;
+  unique_ptr<uint8_t[]> compressed_map_data = make_unique<uint8_t[]>(compressed_map.maxLength);
+  compressed_map.data = compressed_map_data.get();
+  JPEGR_CHECK(compressRecoveryMap(&map, &compressed_map));
+
+  JPEGR_CHECK(appendRecoveryMap(compressed_jpeg_image, &compressed_map, &metadata, dest));
+
+  return NO_ERROR;
+}
+
+status_t RecoveryMap::getJPEGRInfo(jr_compressed_ptr compressed_jpegr_image,
+                                   jr_info_ptr jpegr_info) {
+  if (compressed_jpegr_image == nullptr || jpegr_info == nullptr) {
+    return ERROR_JPEGR_INVALID_NULL_PTR;
+  }
+
+  jpegr_compressed_struct primary_image, recovery_map;
+  JPEGR_CHECK(extractPrimaryImageAndRecoveryMap(compressed_jpegr_image,
+                                                &primary_image, &recovery_map));
+
+  JpegDecoder jpeg_decoder;
+  if (!jpeg_decoder.getCompressedImageParameters(primary_image.data, primary_image.length,
+                                                 &jpegr_info->width, &jpegr_info->height,
+                                                 jpegr_info->iccData, jpegr_info->exifData)) {
+    return ERROR_JPEGR_DECODE_ERROR;
+  }
+
+  return NO_ERROR;
+}
+
+
+status_t RecoveryMap::decodeJPEGR(jr_compressed_ptr compressed_jpegr_image,
+                                  jr_uncompressed_ptr dest,
+                                  jr_exif_ptr exif,
+                                  bool request_sdr) {
+  if (compressed_jpegr_image == nullptr || dest == nullptr) {
+    return ERROR_JPEGR_INVALID_NULL_PTR;
+  }
+
+  // TODO: fill EXIF data
+  (void) exif;
+
+  jpegr_compressed_struct compressed_map;
+  jpegr_metadata metadata;
+  JPEGR_CHECK(extractRecoveryMap(compressed_jpegr_image, &compressed_map));
+
+  jpegr_uncompressed_struct map;
+  JPEGR_CHECK(decompressRecoveryMap(&compressed_map, &map));
+
+  JpegDecoder jpeg_decoder;
+  if (!jpeg_decoder.decompressImage(compressed_jpegr_image->data, compressed_jpegr_image->length)) {
+    return ERROR_JPEGR_DECODE_ERROR;
+  }
+
+  jpegr_uncompressed_struct uncompressed_yuv_420_image;
+  uncompressed_yuv_420_image.data = jpeg_decoder.getDecompressedImagePtr();
+  uncompressed_yuv_420_image.width = jpeg_decoder.getDecompressedImageWidth();
+  uncompressed_yuv_420_image.height = jpeg_decoder.getDecompressedImageHeight();
+
+  if (!getMetadataFromXMP(static_cast<uint8_t*>(jpeg_decoder.getXMPPtr()),
+                                       jpeg_decoder.getXMPSize(), &metadata)) {
+    return ERROR_JPEGR_DECODE_ERROR;
+  }
+
+  if (request_sdr) {
+    memcpy(dest->data, uncompressed_yuv_420_image.data,
+            uncompressed_yuv_420_image.width*uncompressed_yuv_420_image.height *3 / 2);
+    dest->width = uncompressed_yuv_420_image.width;
+    dest->height = uncompressed_yuv_420_image.height;
+  } else {
+    JPEGR_CHECK(applyRecoveryMap(&uncompressed_yuv_420_image, &map, &metadata, dest));
+  }
+
+  return NO_ERROR;
+}
+
+status_t RecoveryMap::decompressRecoveryMap(jr_compressed_ptr compressed_recovery_map,
+                                            jr_uncompressed_ptr dest) {
+  if (compressed_recovery_map == nullptr || dest == nullptr) {
+    return ERROR_JPEGR_INVALID_NULL_PTR;
+  }
+
+  JpegDecoder jpeg_decoder;
+  if (!jpeg_decoder.decompressImage(compressed_recovery_map->data,
+                                    compressed_recovery_map->length)) {
+    return ERROR_JPEGR_DECODE_ERROR;
+  }
+
+  dest->data = jpeg_decoder.getDecompressedImagePtr();
+  dest->width = jpeg_decoder.getDecompressedImageWidth();
+  dest->height = jpeg_decoder.getDecompressedImageHeight();
+
+  return NO_ERROR;
+}
+
+status_t RecoveryMap::compressRecoveryMap(jr_uncompressed_ptr uncompressed_recovery_map,
+                                          jr_compressed_ptr dest) {
+  if (uncompressed_recovery_map == nullptr || dest == nullptr) {
+    return ERROR_JPEGR_INVALID_NULL_PTR;
+  }
+
+  // TODO: should we have ICC data for the map?
+  JpegEncoder jpeg_encoder;
+  if (!jpeg_encoder.compressImage(uncompressed_recovery_map->data,
+                                  uncompressed_recovery_map->width,
+                                  uncompressed_recovery_map->height,
+                                  kMapCompressQuality,
+                                  nullptr,
+                                  0,
+                                  true /* isSingleChannel */)) {
+    return ERROR_JPEGR_ENCODE_ERROR;
+  }
+
+  if (dest->maxLength < jpeg_encoder.getCompressedImageSize()) {
+    return ERROR_JPEGR_BUFFER_TOO_SMALL;
+  }
+
+  memcpy(dest->data, jpeg_encoder.getCompressedImagePtr(), jpeg_encoder.getCompressedImageSize());
+  dest->length = jpeg_encoder.getCompressedImageSize();
+  dest->colorGamut = JPEGR_COLORGAMUT_UNSPECIFIED;
+
+  return NO_ERROR;
+}
+
+status_t RecoveryMap::generateRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_image,
+                                          jr_uncompressed_ptr uncompressed_p010_image,
+                                          jr_metadata_ptr metadata,
+                                          jr_uncompressed_ptr dest) {
+  if (uncompressed_yuv_420_image == nullptr
+   || uncompressed_p010_image == nullptr
+   || metadata == nullptr
+   || dest == nullptr) {
+    return ERROR_JPEGR_INVALID_NULL_PTR;
+  }
+
+  if (uncompressed_yuv_420_image->width != uncompressed_p010_image->width
+   || uncompressed_yuv_420_image->height != uncompressed_p010_image->height) {
+    return ERROR_JPEGR_RESOLUTION_MISMATCH;
+  }
+
+  if (uncompressed_yuv_420_image->colorGamut == JPEGR_COLORGAMUT_UNSPECIFIED
+   || uncompressed_p010_image->colorGamut == JPEGR_COLORGAMUT_UNSPECIFIED) {
+    return ERROR_JPEGR_INVALID_COLORGAMUT;
+  }
+
+  size_t image_width = uncompressed_yuv_420_image->width;
+  size_t image_height = uncompressed_yuv_420_image->height;
+  size_t map_width = image_width / kMapDimensionScaleFactor;
+  size_t map_height = image_height / kMapDimensionScaleFactor;
+
+  dest->width = map_width;
+  dest->height = map_height;
+  dest->colorGamut = JPEGR_COLORGAMUT_UNSPECIFIED;
+  dest->data = new uint8_t[map_width * map_height];
+  std::unique_ptr<uint8_t[]> map_data;
+  map_data.reset(reinterpret_cast<uint8_t*>(dest->data));
+
+  ColorTransformFn hdrInvOetf = nullptr;
+  float hdr_white_nits = 0.0f;
+  switch (metadata->transferFunction) {
+    case JPEGR_TF_HLG:
+      hdrInvOetf = hlgInvOetf;
+      hdr_white_nits = kHlgMaxNits;
+      break;
+    case JPEGR_TF_PQ:
+      hdrInvOetf = pqInvOetf;
+      hdr_white_nits = kPqMaxNits;
+      break;
+  }
+
+  ColorTransformFn hdrGamutConversionFn = getHdrConversionFn(
+      uncompressed_yuv_420_image->colorGamut, uncompressed_p010_image->colorGamut);
+
+  ColorCalculationFn luminanceFn = nullptr;
+  switch (uncompressed_yuv_420_image->colorGamut) {
+    case JPEGR_COLORGAMUT_BT709:
+      luminanceFn = srgbLuminance;
+      break;
+    case JPEGR_COLORGAMUT_P3:
+      luminanceFn = p3Luminance;
+      break;
+    case JPEGR_COLORGAMUT_BT2100:
+      luminanceFn = bt2100Luminance;
+      break;
+    case JPEGR_COLORGAMUT_UNSPECIFIED:
+      // Should be impossible to hit after input validation.
+      return ERROR_JPEGR_INVALID_COLORGAMUT;
+  }
+
+  float hdr_y_nits_max = 0.0f;
+  double hdr_y_nits_avg = 0.0f;
+  for (size_t y = 0; y < image_height; ++y) {
+    for (size_t x = 0; x < image_width; ++x) {
+      Color hdr_yuv_gamma = getP010Pixel(uncompressed_p010_image, x, y);
+      Color hdr_rgb_gamma = bt2100YuvToRgb(hdr_yuv_gamma);
+      Color hdr_rgb = hdrInvOetf(hdr_rgb_gamma);
+      hdr_rgb = hdrGamutConversionFn(hdr_rgb);
+      float hdr_y_nits = luminanceFn(hdr_rgb) * hdr_white_nits;
+
+      hdr_y_nits_avg += hdr_y_nits;
+      if (hdr_y_nits > hdr_y_nits_max) {
+        hdr_y_nits_max = hdr_y_nits;
+      }
+    }
+  }
+  hdr_y_nits_avg /= image_width * image_height;
+
+  metadata->rangeScalingFactor = hdr_y_nits_max / kSdrWhiteNits;
+  if (metadata->transferFunction == JPEGR_TF_PQ) {
+    metadata->hdr10Metadata.maxFALL = hdr_y_nits_avg;
+    metadata->hdr10Metadata.maxCLL = hdr_y_nits_max;
+  }
+
+  for (size_t y = 0; y < map_height; ++y) {
+    for (size_t x = 0; x < map_width; ++x) {
+      Color sdr_yuv_gamma = sampleYuv420(uncompressed_yuv_420_image,
+                                         kMapDimensionScaleFactor, x, y);
+      Color sdr_rgb_gamma = srgbYuvToRgb(sdr_yuv_gamma);
+      Color sdr_rgb = srgbInvOetf(sdr_rgb_gamma);
+      float sdr_y_nits = luminanceFn(sdr_rgb) * kSdrWhiteNits;
+
+      Color hdr_yuv_gamma = sampleP010(uncompressed_p010_image, kMapDimensionScaleFactor, x, y);
+      Color hdr_rgb_gamma = bt2100YuvToRgb(hdr_yuv_gamma);
+      Color hdr_rgb = hdrInvOetf(hdr_rgb_gamma);
+      hdr_rgb = hdrGamutConversionFn(hdr_rgb);
+      float hdr_y_nits = luminanceFn(hdr_rgb) * hdr_white_nits;
+
+      size_t pixel_idx =  x + y * map_width;
+      reinterpret_cast<uint8_t*>(dest->data)[pixel_idx] =
+          encodeRecovery(sdr_y_nits, hdr_y_nits, metadata->rangeScalingFactor);
+    }
+  }
+
+  map_data.release();
+  return NO_ERROR;
+}
+
+status_t RecoveryMap::applyRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_image,
+                                       jr_uncompressed_ptr uncompressed_recovery_map,
+                                       jr_metadata_ptr metadata,
+                                       jr_uncompressed_ptr dest) {
+  if (uncompressed_yuv_420_image == nullptr
+   || uncompressed_recovery_map == nullptr
+   || metadata == nullptr
+   || dest == nullptr) {
+    return ERROR_JPEGR_INVALID_NULL_PTR;
+  }
+
+  size_t width = uncompressed_yuv_420_image->width;
+  size_t height = uncompressed_yuv_420_image->height;
+
+  dest->width = width;
+  dest->height = height;
+  size_t pixel_count = width * height;
+
+  ColorTransformFn hdrOetf = nullptr;
+  switch (metadata->transferFunction) {
+    case JPEGR_TF_HLG:
+      hdrOetf = hlgOetf;
+      break;
+    case JPEGR_TF_PQ:
+      hdrOetf = pqOetf;
+      break;
+  }
+
+  for (size_t y = 0; y < height; ++y) {
+    for (size_t x = 0; x < width; ++x) {
+      Color yuv_gamma_sdr = getYuv420Pixel(uncompressed_yuv_420_image, x, y);
+      Color rgb_gamma_sdr = srgbYuvToRgb(yuv_gamma_sdr);
+      Color rgb_sdr = srgbInvOetf(rgb_gamma_sdr);
+
+      // TODO: determine map scaling factor based on actual map dims
+      float recovery = sampleMap(uncompressed_recovery_map, kMapDimensionScaleFactor, x, y);
+      Color rgb_hdr = applyRecovery(rgb_sdr, recovery, metadata->rangeScalingFactor);
+
+      Color rgb_gamma_hdr = hdrOetf(rgb_hdr);
+      uint32_t rgba1010102 = colorToRgba1010102(rgb_gamma_hdr);
+
+      size_t pixel_idx =  x + y * width;
+      reinterpret_cast<uint32_t*>(dest->data)[pixel_idx] = rgba1010102;
+    }
+  }
+  return NO_ERROR;
+}
+
+status_t RecoveryMap::extractPrimaryImageAndRecoveryMap(jr_compressed_ptr compressed_jpegr_image,
+                                               jr_compressed_ptr primary_image,
+                                               jr_compressed_ptr recovery_map) {
+  if (compressed_jpegr_image == nullptr) {
+    return ERROR_JPEGR_INVALID_NULL_PTR;
+  }
+
+  MessageHandler msg_handler;
+  std::shared_ptr<DataSegment> seg =
+                  DataSegment::Create(DataRange(0, compressed_jpegr_image->length),
+                                      static_cast<const uint8_t*>(compressed_jpegr_image->data),
+                                      DataSegment::BufferDispositionPolicy::kDontDelete);
+  DataSegmentDataSource data_source(seg);
+  JpegInfoBuilder jpeg_info_builder;
+  jpeg_info_builder.SetImageLimit(2);
+  JpegScanner jpeg_scanner(&msg_handler);
+  jpeg_scanner.Run(&data_source, &jpeg_info_builder);
+  data_source.Reset();
+
+  if (jpeg_scanner.HasError()) {
+    return ERROR_JPEGR_INVALID_INPUT_TYPE;
+  }
+
+  const auto& jpeg_info = jpeg_info_builder.GetInfo();
+  const auto& image_ranges = jpeg_info.GetImageRanges();
+  if (image_ranges.empty()) {
+    return ERROR_JPEGR_INVALID_INPUT_TYPE;
+  }
+
+  if (image_ranges.size() != 2) {
+    // Must be 2 JPEG Images
+    return ERROR_JPEGR_INVALID_INPUT_TYPE;
+  }
+
+  if (primary_image != nullptr) {
+    primary_image->data = static_cast<uint8_t*>(compressed_jpegr_image->data) +
+                                               image_ranges[0].GetBegin();
+    primary_image->length = image_ranges[0].GetLength();
+  }
+
+  if (recovery_map != nullptr) {
+    recovery_map->data = static_cast<uint8_t*>(compressed_jpegr_image->data) +
+                                              image_ranges[1].GetBegin();
+    recovery_map->length = image_ranges[1].GetLength();
+  }
+
+  return NO_ERROR;
+}
+
+
+status_t RecoveryMap::extractRecoveryMap(jr_compressed_ptr compressed_jpegr_image,
+                                         jr_compressed_ptr dest) {
+  if (compressed_jpegr_image == nullptr || dest == nullptr) {
+    return ERROR_JPEGR_INVALID_NULL_PTR;
+  }
+
+  return extractPrimaryImageAndRecoveryMap(compressed_jpegr_image, nullptr, dest);
+}
+
+status_t RecoveryMap::appendRecoveryMap(jr_compressed_ptr compressed_jpeg_image,
+                                        jr_compressed_ptr compressed_recovery_map,
+                                        jr_metadata_ptr metadata,
+                                        jr_compressed_ptr dest) {
+  if (compressed_jpeg_image == nullptr
+   || compressed_recovery_map == nullptr
+   || metadata == nullptr
+   || dest == nullptr) {
+    return ERROR_JPEGR_INVALID_NULL_PTR;
+  }
+
+  const string xmp = generateXmp(compressed_recovery_map->length, *metadata);
+  const string nameSpace = "http://ns.adobe.com/xap/1.0/\0";
+  const int nameSpaceLength = nameSpace.size() + 1;  // need to count the null terminator
+
+  // 2 bytes: APP1 sign (ff e1)
+  // 29 bytes: length of name space "http://ns.adobe.com/xap/1.0/\0",
+  // x bytes: length of xmp packet
+
+  const int length = 3 + nameSpaceLength + xmp.size();
+  const uint8_t lengthH = ((length >> 8) & 0xff);
+  const uint8_t lengthL = (length & 0xff);
+
+  int pos = 0;
+
+  // JPEG/R structure:
+  // SOI (ff d8)
+  // APP1 (ff e1)
+  // 2 bytes of length (2 + 29 + length of xmp packet)
+  // name space ("http://ns.adobe.com/xap/1.0/\0")
+  // xmp
+  // primary image (without the first two bytes, the SOI sign)
+  // secondary image (the recovery map)
+  JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
+  JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kSOI, 1, pos));
+  JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
+  JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP1, 1, pos));
+  JPEGR_CHECK(Write(dest, &lengthH, 1, pos));
+  JPEGR_CHECK(Write(dest, &lengthL, 1, pos));
+  JPEGR_CHECK(Write(dest, (void*)nameSpace.c_str(), nameSpaceLength, pos));
+  JPEGR_CHECK(Write(dest, (void*)xmp.c_str(), xmp.size(), pos));
+  JPEGR_CHECK(Write(dest,
+      (uint8_t*)compressed_jpeg_image->data + 2, compressed_jpeg_image->length - 2, pos));
+  JPEGR_CHECK(Write(dest, compressed_recovery_map->data, compressed_recovery_map->length, pos));
+  dest->length = pos;
+
+  return NO_ERROR;
+}
+
+string RecoveryMap::generateXmp(int secondary_image_length, jpegr_metadata& metadata) {
+  const string kContainerPrefix = "GContainer";
+  const string kContainerUri    = "http://ns.google.com/photos/1.0/container/";
+  const string kItemPrefix      = "Item";
+  const string kRecoveryMap     = "RecoveryMap";
+  const string kDirectory       = "Directory";
+  const string kImageJpeg       = "image/jpeg";
+  const string kItem            = "Item";
+  const string kLength          = "Length";
+  const string kMime            = "Mime";
+  const string kPrimary         = "Primary";
+  const string kSemantic        = "Semantic";
+  const string kVersion         = "Version";
+
+  const string kConDir          = Name(kContainerPrefix, kDirectory);
+  const string kContainerItem   = Name(kContainerPrefix, kItem);
+  const string kItemLength      = Name(kItemPrefix, kLength);
+  const string kItemMime        = Name(kItemPrefix, kMime);
+  const string kItemSemantic    = Name(kItemPrefix, kSemantic);
+
+  const vector<string> kConDirSeq({kConDir, string("rdf:Seq")});
+  const vector<string> kLiItem({string("rdf:li"), kContainerItem});
+
+  std::stringstream ss;
+  photos_editing_formats::image_io::XmlWriter writer(ss);
+  writer.StartWritingElement("x:xmpmeta");
+  writer.WriteXmlns("x", "adobe:ns:meta/");
+  writer.WriteAttributeNameAndValue("x:xmptk", "Adobe XMP Core 5.1.2");
+  writer.StartWritingElement("rdf:RDF");
+  writer.WriteXmlns("rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#");
+  writer.StartWritingElement("rdf:Description");
+  writer.WriteXmlns(kContainerPrefix, kContainerUri);
+  writer.WriteElementAndContent(Name(kContainerPrefix, kVersion), metadata.version);
+  writer.WriteElementAndContent(Name(kContainerPrefix, "rangeScalingFactor"),
+                                metadata.rangeScalingFactor);
+  // TODO: determine structure for hdr10 metadata
+  // TODO: write rest of metadata
+  writer.StartWritingElements(kConDirSeq);
+  size_t item_depth = writer.StartWritingElements(kLiItem);
+  writer.WriteAttributeNameAndValue(kItemSemantic, kPrimary);
+  writer.WriteAttributeNameAndValue(kItemMime, kImageJpeg);
+  writer.FinishWritingElementsToDepth(item_depth);
+  writer.StartWritingElements(kLiItem);
+  writer.WriteAttributeNameAndValue(kItemSemantic, kRecoveryMap);
+  writer.WriteAttributeNameAndValue(kItemMime, kImageJpeg);
+  writer.WriteAttributeNameAndValue(kItemLength, secondary_image_length);
+  writer.FinishWriting();
+
+  return ss.str();
+}
+
+} // namespace android::recoverymap
diff --git a/libs/jpegrecoverymap/recoverymapmath.cpp b/libs/jpegrecoverymap/recoverymapmath.cpp
new file mode 100644
index 0000000..e838f43
--- /dev/null
+++ b/libs/jpegrecoverymap/recoverymapmath.cpp
@@ -0,0 +1,437 @@
+/*
+ * 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 <cmath>
+
+#include <jpegrecoverymap/recoverymapmath.h>
+
+namespace android::recoverymap {
+
+////////////////////////////////////////////////////////////////////////////////
+// sRGB transformations
+
+// See IEC 61966-2-1, Equation F.7.
+static const float kSrgbR = 0.2126f, kSrgbG = 0.7152f, kSrgbB = 0.0722f;
+
+float srgbLuminance(Color e) {
+  return kSrgbR * e.r + kSrgbG * e.g + kSrgbB * e.b;
+}
+
+// See ECMA TR/98, Section 7.
+static const float kSrgbRCr = 1.402f, kSrgbGCb = 0.34414f, kSrgbGCr = 0.71414f, kSrgbBCb = 1.772f;
+
+Color srgbYuvToRgb(Color e_gamma) {
+  return {{{ e_gamma.y + kSrgbRCr * e_gamma.v,
+             e_gamma.y - kSrgbGCb * e_gamma.u - kSrgbGCr * e_gamma.v,
+             e_gamma.y + kSrgbBCb * e_gamma.u }}};
+}
+
+// See ECMA TR/98, Section 7.
+static const float kSrgbYR = 0.299f, kSrgbYG = 0.587f, kSrgbYB = 0.114f;
+static const float kSrgbUR = -0.1687f, kSrgbUG = -0.3313f, kSrgbUB = 0.5f;
+static const float kSrgbVR = 0.5f, kSrgbVG = -0.4187f, kSrgbVB = -0.0813f;
+
+Color srgbRgbToYuv(Color e_gamma) {
+  return {{{ kSrgbYR * e_gamma.r + kSrgbYG * e_gamma.g + kSrgbYB * e_gamma.b,
+             kSrgbUR * e_gamma.r + kSrgbUG * e_gamma.g + kSrgbUB * e_gamma.b,
+             kSrgbVR * e_gamma.r + kSrgbVG * e_gamma.g + kSrgbVB * e_gamma.b }}};
+}
+
+// See IEC 61966-2-1, Equations F.5 and F.6.
+float srgbInvOetf(float e_gamma) {
+  if (e_gamma <= 0.04045f) {
+    return e_gamma / 12.92f;
+  } else {
+    return pow((e_gamma + 0.055f) / 1.055f, 2.4);
+  }
+}
+
+Color srgbInvOetf(Color e_gamma) {
+  return {{{ srgbInvOetf(e_gamma.r),
+             srgbInvOetf(e_gamma.g),
+             srgbInvOetf(e_gamma.b) }}};
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// Display-P3 transformations
+
+// See SMPTE EG 432-1, Table 7-2.
+static const float kP3R = 0.20949f, kP3G = 0.72160f, kP3B = 0.06891f;
+
+float p3Luminance(Color e) {
+  return kP3R * e.r + kP3G * e.g + kP3B * e.b;
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// BT.2100 transformations - according to ITU-R BT.2100-2
+
+// See ITU-R BT.2100-2, Table 5, HLG Reference OOTF
+static const float kBt2100R = 0.2627f, kBt2100G = 0.6780f, kBt2100B = 0.0593f;
+
+float bt2100Luminance(Color e) {
+  return kBt2100R * e.r + kBt2100G * e.g + kBt2100B * e.b;
+}
+
+// See ITU-R BT.2100-2, Table 6, Derivation of colour difference signals.
+static const float kBt2100Cb = 1.8814f, kBt2100Cr = 1.4746f;
+
+Color bt2100RgbToYuv(Color e_gamma) {
+  float y_gamma = bt2100Luminance(e_gamma);
+  return {{{ y_gamma,
+             (e_gamma.b - y_gamma) / kBt2100Cb,
+             (e_gamma.r - y_gamma) / kBt2100Cr }}};
+}
+
+// Derived by inversing bt2100RgbToYuv. The derivation for R and B are  pretty
+// straight forward; we just invert the formulas for U and V above. But deriving
+// the formula for G is a bit more complicated:
+//
+// Start with equation for luminance:
+//   Y = kBt2100R * R + kBt2100G * G + kBt2100B * B
+// Solve for G:
+//   G = (Y - kBt2100R * R - kBt2100B * B) / kBt2100B
+// Substitute equations for R and B in terms YUV:
+//   G = (Y - kBt2100R * (Y + kBt2100Cr * V) - kBt2100B * (Y + kBt2100Cb * U)) / kBt2100B
+// Simplify:
+//   G = Y * ((1 - kBt2100R - kBt2100B) / kBt2100G)
+//     + U * (kBt2100B * kBt2100Cb / kBt2100G)
+//     + V * (kBt2100R * kBt2100Cr / kBt2100G)
+//
+// We then get the following coeficients for calculating G from YUV:
+//
+// Coef for Y = (1 - kBt2100R - kBt2100B) / kBt2100G = 1
+// Coef for U = kBt2100B * kBt2100Cb / kBt2100G = kBt2100GCb = ~0.1645
+// Coef for V = kBt2100R * kBt2100Cr / kBt2100G = kBt2100GCr = ~0.5713
+
+static const float kBt2100GCb = kBt2100B * kBt2100Cb / kBt2100G;
+static const float kBt2100GCr = kBt2100R * kBt2100Cr / kBt2100G;
+
+Color bt2100YuvToRgb(Color e_gamma) {
+  return {{{ e_gamma.y + kBt2100Cr * e_gamma.v,
+             e_gamma.y - kBt2100GCb * e_gamma.u - kBt2100GCr * e_gamma.v,
+             e_gamma.y + kBt2100Cb * e_gamma.u }}};
+}
+
+// See ITU-R BT.2100-2, Table 5, HLG Reference OETF.
+static const float kHlgA = 0.17883277f, kHlgB = 0.28466892f, kHlgC = 0.55991073;
+
+float hlgOetf(float e) {
+  if (e <= 1.0f/12.0f) {
+    return sqrt(3.0f * e);
+  } else {
+    return kHlgA * log(12.0f * e - kHlgB) + kHlgC;
+  }
+}
+
+Color hlgOetf(Color e) {
+  return {{{ hlgOetf(e.r), hlgOetf(e.g), hlgOetf(e.b) }}};
+}
+
+// See ITU-R BT.2100-2, Table 5, HLG Reference EOTF.
+float hlgInvOetf(float e_gamma) {
+  if (e_gamma <= 0.5f) {
+    return pow(e_gamma, 2.0f) / 3.0f;
+  } else {
+    return (exp((e_gamma - kHlgC) / kHlgA) + kHlgB) / 12.0f;
+  }
+}
+
+Color hlgInvOetf(Color e_gamma) {
+  return {{{ hlgInvOetf(e_gamma.r),
+             hlgInvOetf(e_gamma.g),
+             hlgInvOetf(e_gamma.b) }}};
+}
+
+// See ITU-R BT.2100-2, Table 4, Reference PQ OETF.
+static const float kPqM1 = 2610.0f / 16384.0f, kPqM2 = 2523.0f / 4096.0f * 128.0f;
+static const float kPqC1 = 3424.0f / 4096.0f, kPqC2 = 2413.0f / 4096.0f * 32.0f,
+                   kPqC3 = 2392.0f / 4096.0f * 32.0f;
+
+float pqOetf(float e) {
+  if (e <= 0.0f) return 0.0f;
+  return pow((kPqC1 + kPqC2 * pow(e, kPqM1)) / (1 + kPqC3 * pow(e, kPqM1)),
+             kPqM2);
+}
+
+Color pqOetf(Color e) {
+  return {{{ pqOetf(e.r), pqOetf(e.g), pqOetf(e.b) }}};
+}
+
+// Derived from the inverse of the Reference PQ OETF.
+static const float kPqInvA = 128.0f, kPqInvB = 107.0f, kPqInvC = 2413.0f, kPqInvD = 2392.0f,
+                   kPqInvE = 6.2773946361f, kPqInvF = 0.0126833f;
+
+float pqInvOetf(float e_gamma) {
+  // This equation blows up if e_gamma is 0.0, and checking on <= 0.0 doesn't
+  // always catch 0.0. So, check on 0.0001, since anything this small will
+  // effectively be crushed to zero anyways.
+  if (e_gamma <= 0.0001f) return 0.0f;
+  return pow((kPqInvA * pow(e_gamma, kPqInvF) - kPqInvB)
+           / (kPqInvC - kPqInvD * pow(e_gamma, kPqInvF)),
+             kPqInvE);
+}
+
+Color pqInvOetf(Color e_gamma) {
+  return {{{ pqInvOetf(e_gamma.r),
+             pqInvOetf(e_gamma.g),
+             pqInvOetf(e_gamma.b) }}};
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// Color conversions
+
+Color bt709ToP3(Color e) {
+ return {{{ 0.82254f * e.r + 0.17755f * e.g + 0.00006f * e.b,
+            0.03312f * e.r + 0.96684f * e.g + -0.00001f * e.b,
+            0.01706f * e.r + 0.07240f * e.g + 0.91049f * e.b }}};
+}
+
+Color bt709ToBt2100(Color e) {
+ return {{{ 0.62740f * e.r + 0.32930f * e.g + 0.04332f * e.b,
+            0.06904f * e.r + 0.91958f * e.g + 0.01138f * e.b,
+            0.01636f * e.r + 0.08799f * e.g + 0.89555f * e.b }}};
+}
+
+Color p3ToBt709(Color e) {
+ return {{{ 1.22482f * e.r + -0.22490f * e.g + -0.00007f * e.b,
+            -0.04196f * e.r + 1.04199f * e.g + 0.00001f * e.b,
+            -0.01961f * e.r + -0.07865f * e.g + 1.09831f * e.b }}};
+}
+
+Color p3ToBt2100(Color e) {
+ return {{{ 0.75378f * e.r + 0.19862f * e.g + 0.04754f * e.b,
+            0.04576f * e.r + 0.94177f * e.g + 0.01250f * e.b,
+            -0.00121f * e.r + 0.01757f * e.g + 0.98359f * e.b }}};
+}
+
+Color bt2100ToBt709(Color e) {
+ return {{{ 1.66045f * e.r + -0.58764f * e.g + -0.07286f * e.b,
+            -0.12445f * e.r + 1.13282f * e.g + -0.00837f * e.b,
+            -0.01811f * e.r + -0.10057f * e.g + 1.11878f * e.b }}};
+}
+
+Color bt2100ToP3(Color e) {
+ return {{{ 1.34369f * e.r + -0.28223f * e.g + -0.06135f * e.b,
+            -0.06533f * e.r + 1.07580f * e.g + -0.01051f * e.b,
+            0.00283f * e.r + -0.01957f * e.g + 1.01679f * e.b
+ }}};
+}
+
+// TODO: confirm we always want to convert like this before calculating
+// luminance.
+ColorTransformFn getHdrConversionFn(jpegr_color_gamut sdr_gamut, jpegr_color_gamut hdr_gamut) {
+  switch (sdr_gamut) {
+    case JPEGR_COLORGAMUT_BT709:
+      switch (hdr_gamut) {
+        case JPEGR_COLORGAMUT_BT709:
+          return identityConversion;
+        case JPEGR_COLORGAMUT_P3:
+          return p3ToBt709;
+        case JPEGR_COLORGAMUT_BT2100:
+          return bt2100ToBt709;
+        case JPEGR_COLORGAMUT_UNSPECIFIED:
+          return nullptr;
+      }
+      break;
+    case JPEGR_COLORGAMUT_P3:
+      switch (hdr_gamut) {
+        case JPEGR_COLORGAMUT_BT709:
+          return bt709ToP3;
+        case JPEGR_COLORGAMUT_P3:
+          return identityConversion;
+        case JPEGR_COLORGAMUT_BT2100:
+          return bt2100ToP3;
+        case JPEGR_COLORGAMUT_UNSPECIFIED:
+          return nullptr;
+      }
+      break;
+    case JPEGR_COLORGAMUT_BT2100:
+      switch (hdr_gamut) {
+        case JPEGR_COLORGAMUT_BT709:
+          return bt709ToBt2100;
+        case JPEGR_COLORGAMUT_P3:
+          return p3ToBt2100;
+        case JPEGR_COLORGAMUT_BT2100:
+          return identityConversion;
+        case JPEGR_COLORGAMUT_UNSPECIFIED:
+          return nullptr;
+      }
+      break;
+    case JPEGR_COLORGAMUT_UNSPECIFIED:
+      return nullptr;
+  }
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// Recovery map calculations
+
+uint8_t encodeRecovery(float y_sdr, float y_hdr, float hdr_ratio) {
+  float gain = 1.0f;
+  if (y_sdr > 0.0f) {
+    gain = y_hdr / y_sdr;
+  }
+
+  if (gain < (1.0f / hdr_ratio)) gain = 1.0f / hdr_ratio;
+  if (gain > hdr_ratio) gain = hdr_ratio;
+
+  return static_cast<uint8_t>(log2(gain) / log2(hdr_ratio) * 127.5f  + 127.5f);
+}
+
+static float applyRecovery(float e, float recovery, float hdr_ratio) {
+  if (e <= 0.0f) return 0.0f;
+  return exp2(log2(e) + recovery * log2(hdr_ratio));
+}
+
+Color applyRecovery(Color e, float recovery, float hdr_ratio) {
+  return {{{ applyRecovery(e.r, recovery, hdr_ratio),
+             applyRecovery(e.g, recovery, hdr_ratio),
+             applyRecovery(e.b, recovery, hdr_ratio) }}};
+}
+
+Color getYuv420Pixel(jr_uncompressed_ptr image, size_t x, size_t y) {
+  size_t pixel_count = image->width * image->height;
+
+  size_t pixel_y_idx = x + y * image->width;
+  size_t pixel_uv_idx = x / 2 + (y / 2) * (image->width / 2);
+
+  uint8_t y_uint = reinterpret_cast<uint8_t*>(image->data)[pixel_y_idx];
+  uint8_t u_uint = reinterpret_cast<uint8_t*>(image->data)[pixel_count + pixel_uv_idx];
+  uint8_t v_uint = reinterpret_cast<uint8_t*>(image->data)[pixel_count * 5 / 4 + pixel_uv_idx];
+
+  // 128 bias for UV given we are using jpeglib; see:
+  // https://github.com/kornelski/libjpeg/blob/master/structure.doc
+  return {{{ static_cast<float>(y_uint) / 255.0f,
+             (static_cast<float>(u_uint) - 128.0f) / 255.0f,
+             (static_cast<float>(v_uint) - 128.0f) / 255.0f }}};
+}
+
+Color getP010Pixel(jr_uncompressed_ptr image, size_t x, size_t y) {
+  size_t pixel_count = image->width * image->height;
+
+  size_t pixel_y_idx = x + y * image->width;
+  size_t pixel_uv_idx = x / 2 + (y / 2) * (image->width / 2);
+
+  uint16_t y_uint = reinterpret_cast<uint16_t*>(image->data)[pixel_y_idx]
+                  >> 6;
+  uint16_t u_uint = reinterpret_cast<uint16_t*>(image->data)[pixel_count + pixel_uv_idx * 2]
+                  >> 6;
+  uint16_t v_uint = reinterpret_cast<uint16_t*>(image->data)[pixel_count + pixel_uv_idx * 2 + 1]
+                  >> 6;
+
+  // Conversions include taking narrow-range into account.
+  return {{{ static_cast<float>(y_uint) / 940.0f,
+             (static_cast<float>(u_uint) - 64.0f) / 940.0f - 0.5f,
+             (static_cast<float>(v_uint) - 64.0f) / 940.0f - 0.5f }}};
+}
+
+typedef Color (*getPixelFn)(jr_uncompressed_ptr, size_t, size_t);
+
+static Color samplePixels(jr_uncompressed_ptr image, size_t map_scale_factor, size_t x, size_t y,
+                          getPixelFn get_pixel_fn) {
+  Color e = {{{ 0.0f, 0.0f, 0.0f }}};
+  for (size_t dy = 0; dy < map_scale_factor; ++dy) {
+    for (size_t dx = 0; dx < map_scale_factor; ++dx) {
+      e += get_pixel_fn(image, x * map_scale_factor + dx, y * map_scale_factor + dy);
+    }
+  }
+
+  return e / static_cast<float>(map_scale_factor * map_scale_factor);
+}
+
+Color sampleYuv420(jr_uncompressed_ptr image, size_t map_scale_factor, size_t x, size_t y) {
+  return samplePixels(image, map_scale_factor, x, y, getYuv420Pixel);
+}
+
+Color sampleP010(jr_uncompressed_ptr image, size_t map_scale_factor, size_t x, size_t y) {
+  return samplePixels(image, map_scale_factor, x, y, getP010Pixel);
+}
+
+// TODO: do we need something more clever for filtering either the map or images
+// to generate the map?
+
+static size_t clamp(const size_t& val, const size_t& low, const size_t& high) {
+  return val < low ? low : (high < val ? high : val);
+}
+
+static float mapUintToFloat(uint8_t map_uint) {
+  return (static_cast<float>(map_uint) - 127.5f) / 127.5f;
+}
+
+static float pythDistance(float x_diff, float y_diff) {
+  return sqrt(pow(x_diff, 2.0f) + pow(y_diff, 2.0f));
+}
+
+float sampleMap(jr_uncompressed_ptr map, size_t map_scale_factor, size_t x, size_t y) {
+  float x_map = static_cast<float>(x) / static_cast<float>(map_scale_factor);
+  float y_map = static_cast<float>(y) / static_cast<float>(map_scale_factor);
+
+  size_t x_lower = static_cast<size_t>(floor(x_map));
+  size_t x_upper = x_lower + 1;
+  size_t y_lower = static_cast<size_t>(floor(y_map));
+  size_t y_upper = y_lower + 1;
+
+  x_lower = clamp(x_lower, 0, map->width - 1);
+  x_upper = clamp(x_upper, 0, map->width - 1);
+  y_lower = clamp(y_lower, 0, map->height - 1);
+  y_upper = clamp(y_upper, 0, map->height - 1);
+
+  // Use Shepard's method for inverse distance weighting. For more information:
+  // en.wikipedia.org/wiki/Inverse_distance_weighting#Shepard's_method
+
+  float e1 = mapUintToFloat(reinterpret_cast<uint8_t*>(map->data)[x_lower + y_lower * map->width]);
+  float e1_dist = pythDistance(x_map - static_cast<float>(x_lower),
+                               y_map - static_cast<float>(y_lower));
+  if (e1_dist == 0.0f) return e1;
+
+  float e2 = mapUintToFloat(reinterpret_cast<uint8_t*>(map->data)[x_lower + y_upper * map->width]);
+  float e2_dist = pythDistance(x_map - static_cast<float>(x_lower),
+                               y_map - static_cast<float>(y_upper));
+  if (e2_dist == 0.0f) return e2;
+
+  float e3 = mapUintToFloat(reinterpret_cast<uint8_t*>(map->data)[x_upper + y_lower * map->width]);
+  float e3_dist = pythDistance(x_map - static_cast<float>(x_upper),
+                               y_map - static_cast<float>(y_lower));
+  if (e3_dist == 0.0f) return e3;
+
+  float e4 = mapUintToFloat(reinterpret_cast<uint8_t*>(map->data)[x_upper + y_upper * map->width]);
+  float e4_dist = pythDistance(x_map - static_cast<float>(x_upper),
+                               y_map - static_cast<float>(y_upper));
+  if (e4_dist == 0.0f) return e2;
+
+  float e1_weight = 1.0f / e1_dist;
+  float e2_weight = 1.0f / e2_dist;
+  float e3_weight = 1.0f / e3_dist;
+  float e4_weight = 1.0f / e4_dist;
+  float total_weight = e1_weight + e2_weight + e3_weight + e4_weight;
+
+  return e1 * (e1_weight / total_weight)
+       + e2 * (e2_weight / total_weight)
+       + e3 * (e3_weight / total_weight)
+       + e4 * (e4_weight / total_weight);
+}
+
+uint32_t colorToRgba1010102(Color e_gamma) {
+  return (0x3ff & static_cast<uint32_t>(e_gamma.r * 1023.0f))
+       | ((0x3ff & static_cast<uint32_t>(e_gamma.g * 1023.0f)) << 10)
+       | ((0x3ff & static_cast<uint32_t>(e_gamma.b * 1023.0f)) << 20)
+       | (0x3 << 30);  // Set alpha to 1.0
+}
+
+} // namespace android::recoverymap
diff --git a/libs/jpegrecoverymap/recoverymaputils.cpp b/libs/jpegrecoverymap/recoverymaputils.cpp
new file mode 100644
index 0000000..fe46cba
--- /dev/null
+++ b/libs/jpegrecoverymap/recoverymaputils.cpp
@@ -0,0 +1,153 @@
+/*
+ * 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 <jpegrecoverymap/recoverymaputils.h>
+#include <jpegrecoverymap/recoverymap.h>
+#include <image_io/xml/xml_reader.h>
+#include <image_io/base/message_handler.h>
+#include <image_io/xml/xml_element_rules.h>
+#include <image_io/xml/xml_handler.h>
+#include <image_io/xml/xml_rule.h>
+
+#include <string>
+#include <sstream>
+
+using namespace photos_editing_formats::image_io;
+using namespace std;
+
+namespace android::recoverymap {
+
+
+// Extremely simple XML Handler - just searches for interesting elements
+class XMPXmlHandler : public XmlHandler {
+public:
+
+    XMPXmlHandler() : XmlHandler() {
+        rangeScalingFactorState = NotStrarted;
+    }
+
+    enum ParseState {
+        NotStrarted,
+        Started,
+        Done
+    };
+
+    virtual DataMatchResult StartElement(const XmlTokenContext& context) {
+        string val;
+        if (context.BuildTokenValue(&val)) {
+            if (!val.compare(rangeScalingFactorName)) {
+                rangeScalingFactorState = Started;
+            } else {
+                if (rangeScalingFactorState != Done) {
+                    rangeScalingFactorState = NotStrarted;
+                }
+            }
+        }
+        return context.GetResult();
+    }
+
+    virtual DataMatchResult FinishElement(const XmlTokenContext& context) {
+        if (rangeScalingFactorState == Started) {
+            rangeScalingFactorState = Done;
+        }
+        return context.GetResult();
+    }
+
+    virtual DataMatchResult ElementContent(const XmlTokenContext& context) {
+        string val;
+        if (rangeScalingFactorState == Started) {
+            if (context.BuildTokenValue(&val)) {
+                rangeScalingFactorStr.assign(val);
+            }
+        }
+        return context.GetResult();
+    }
+
+    bool getRangeScalingFactor(float* scaling_factor) {
+        if (rangeScalingFactorState == Done) {
+            stringstream ss(rangeScalingFactorStr);
+            float val;
+            if (ss >> val) {
+                *scaling_factor = val;
+                return true;
+            } else {
+                return false;
+            }
+        } else {
+            return false;
+        }
+    }
+
+    bool getTransferFunction(jpegr_transfer_function* transfer_function) {
+        *transfer_function = JPEGR_TF_HLG;
+        return true;
+    }
+
+private:
+    static const string rangeScalingFactorName;
+    string              rangeScalingFactorStr;
+    ParseState          rangeScalingFactorState;
+};
+
+const string XMPXmlHandler::rangeScalingFactorName = "GContainer:rangeScalingFactor";
+
+
+bool getMetadataFromXMP(uint8_t* xmp_data, size_t xmp_size, jpegr_metadata* metadata) {
+    string nameSpace = "http://ns.adobe.com/xap/1.0/\0";
+
+    if (xmp_size < nameSpace.size()+2) {
+        // Data too short
+        return false;
+    }
+
+    if (strncmp(reinterpret_cast<char*>(xmp_data), nameSpace.c_str(), nameSpace.size())) {
+        // Not correct namespace
+        return false;
+    }
+
+    // Position the pointers to the start of XMP XML portion
+    xmp_data += nameSpace.size()+1;
+    xmp_size -= nameSpace.size()+1;
+    XMPXmlHandler handler;
+
+    // We need to remove tail data until the closing tag. Otherwise parser will throw an error.
+    while(xmp_data[xmp_size-1]!='>' && xmp_size > 1) {
+        xmp_size--;
+    }
+
+    string str(reinterpret_cast<const char*>(xmp_data), xmp_size);
+    MessageHandler msg_handler;
+    unique_ptr<XmlRule> rule(new XmlElementRule);
+    XmlReader reader(&handler, &msg_handler);
+    reader.StartParse(std::move(rule));
+    reader.Parse(str);
+    reader.FinishParse();
+    if (reader.HasErrors()) {
+        // Parse error
+        return false;
+    }
+
+    if (!handler.getRangeScalingFactor(&metadata->rangeScalingFactor)) {
+        return false;
+    }
+
+    if (!handler.getTransferFunction(&metadata->transferFunction)) {
+        return false;
+    }
+    return true;
+}
+
+} // namespace android::recoverymap
\ No newline at end of file
diff --git a/libs/jpegrecoverymap/tests/Android.bp b/libs/jpegrecoverymap/tests/Android.bp
new file mode 100644
index 0000000..b509478
--- /dev/null
+++ b/libs/jpegrecoverymap/tests/Android.bp
@@ -0,0 +1,75 @@
+// 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.
+
+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_test {
+    name: "libjpegrecoverymap_test",
+    test_suites: ["device-tests"],
+    srcs: [
+        "recoverymap_test.cpp",
+        "recoverymapmath_test.cpp",
+    ],
+    shared_libs: [
+        "libjpeg",
+        "liblog",
+    ],
+    static_libs: [
+        "libimage_io",
+        "libgmock",
+        "libgtest",
+        "libjpegdecoder",
+        "libjpegencoder",
+        "libjpegrecoverymap",
+    ],
+}
+
+cc_test {
+    name: "libjpegencoder_test",
+    test_suites: ["device-tests"],
+    srcs: [
+        "jpegencoder_test.cpp",
+    ],
+    shared_libs: [
+        "libjpeg",
+        "liblog",
+    ],
+    static_libs: [
+        "libjpegencoder",
+        "libgtest",
+    ],
+}
+
+cc_test {
+    name: "libjpegdecoder_test",
+    test_suites: ["device-tests"],
+    srcs: [
+        "jpegdecoder_test.cpp",
+    ],
+    shared_libs: [
+        "libjpeg",
+        "liblog",
+    ],
+    static_libs: [
+        "libjpegdecoder",
+        "libgtest",
+    ],
+}
diff --git a/libs/jpegrecoverymap/tests/data/jpeg_image.jpg b/libs/jpegrecoverymap/tests/data/jpeg_image.jpg
new file mode 100644
index 0000000..e285742
--- /dev/null
+++ b/libs/jpegrecoverymap/tests/data/jpeg_image.jpg
Binary files differ
diff --git a/libs/jpegrecoverymap/tests/data/minnie-318x240.yu12 b/libs/jpegrecoverymap/tests/data/minnie-318x240.yu12
new file mode 100644
index 0000000..7b2fc71
--- /dev/null
+++ b/libs/jpegrecoverymap/tests/data/minnie-318x240.yu12
Binary files differ
diff --git a/libs/jpegrecoverymap/tests/data/minnie-320x240-y.jpg b/libs/jpegrecoverymap/tests/data/minnie-320x240-y.jpg
new file mode 100644
index 0000000..20b5a2c
--- /dev/null
+++ b/libs/jpegrecoverymap/tests/data/minnie-320x240-y.jpg
Binary files differ
diff --git a/libs/jpegrecoverymap/tests/data/minnie-320x240-yuv.jpg b/libs/jpegrecoverymap/tests/data/minnie-320x240-yuv.jpg
new file mode 100644
index 0000000..41300f4
--- /dev/null
+++ b/libs/jpegrecoverymap/tests/data/minnie-320x240-yuv.jpg
Binary files differ
diff --git a/libs/jpegrecoverymap/tests/data/minnie-320x240.y b/libs/jpegrecoverymap/tests/data/minnie-320x240.y
new file mode 100644
index 0000000..f9d8371
--- /dev/null
+++ b/libs/jpegrecoverymap/tests/data/minnie-320x240.y
Binary files differ
diff --git a/libs/jpegrecoverymap/tests/data/minnie-320x240.yu12 b/libs/jpegrecoverymap/tests/data/minnie-320x240.yu12
new file mode 100644
index 0000000..0d66f53
--- /dev/null
+++ b/libs/jpegrecoverymap/tests/data/minnie-320x240.yu12
Binary files differ
diff --git a/libs/jpegrecoverymap/tests/data/raw_p010_image.p010 b/libs/jpegrecoverymap/tests/data/raw_p010_image.p010
new file mode 100644
index 0000000..01673bf
--- /dev/null
+++ b/libs/jpegrecoverymap/tests/data/raw_p010_image.p010
Binary files differ
diff --git a/libs/jpegrecoverymap/tests/jpegdecoder_test.cpp b/libs/jpegrecoverymap/tests/jpegdecoder_test.cpp
new file mode 100644
index 0000000..8e01351
--- /dev/null
+++ b/libs/jpegrecoverymap/tests/jpegdecoder_test.cpp
@@ -0,0 +1,102 @@
+/*
+ * 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 <jpegrecoverymap/jpegdecoder.h>
+#include <gtest/gtest.h>
+#include <utils/Log.h>
+
+#include <fcntl.h>
+
+namespace android::recoverymap {
+
+#define YUV_IMAGE "/sdcard/Documents/minnie-320x240-yuv.jpg"
+#define YUV_IMAGE_SIZE 20193
+#define GREY_IMAGE "/sdcard/Documents/minnie-320x240-y.jpg"
+#define GREY_IMAGE_SIZE 20193
+
+class JpegDecoderTest : public testing::Test {
+public:
+    struct Image {
+        std::unique_ptr<uint8_t[]> buffer;
+        size_t size;
+    };
+    JpegDecoderTest();
+    ~JpegDecoderTest();
+protected:
+    virtual void SetUp();
+    virtual void TearDown();
+
+    Image mYuvImage, mGreyImage;
+};
+
+JpegDecoderTest::JpegDecoderTest() {}
+
+JpegDecoderTest::~JpegDecoderTest() {}
+
+static size_t getFileSize(int fd) {
+    struct stat st;
+    if (fstat(fd, &st) < 0) {
+        ALOGW("%s : fstat failed", __func__);
+        return 0;
+    }
+    return st.st_size; // bytes
+}
+
+static bool loadFile(const char filename[], JpegDecoderTest::Image* result) {
+    int fd = open(filename, O_CLOEXEC);
+    if (fd < 0) {
+        return false;
+    }
+    int length = getFileSize(fd);
+    if (length == 0) {
+        close(fd);
+        return false;
+    }
+    result->buffer.reset(new uint8_t[length]);
+    if (read(fd, result->buffer.get(), length) != static_cast<ssize_t>(length)) {
+        close(fd);
+        return false;
+    }
+    close(fd);
+    return true;
+}
+
+void JpegDecoderTest::SetUp() {
+    if (!loadFile(YUV_IMAGE, &mYuvImage)) {
+        FAIL() << "Load file " << YUV_IMAGE << " failed";
+    }
+    mYuvImage.size = YUV_IMAGE_SIZE;
+    if (!loadFile(GREY_IMAGE, &mGreyImage)) {
+        FAIL() << "Load file " << GREY_IMAGE << " failed";
+    }
+    mGreyImage.size = GREY_IMAGE_SIZE;
+}
+
+void JpegDecoderTest::TearDown() {}
+
+TEST_F(JpegDecoderTest, decodeYuvImage) {
+    JpegDecoder decoder;
+    EXPECT_TRUE(decoder.decompressImage(mYuvImage.buffer.get(), mYuvImage.size));
+    ASSERT_GT(decoder.getDecompressedImageSize(), static_cast<uint32_t>(0));
+}
+
+TEST_F(JpegDecoderTest, decodeGreyImage) {
+    JpegDecoder decoder;
+    EXPECT_TRUE(decoder.decompressImage(mGreyImage.buffer.get(), mGreyImage.size));
+    ASSERT_GT(decoder.getDecompressedImageSize(), static_cast<uint32_t>(0));
+}
+
+}
\ No newline at end of file
diff --git a/libs/jpegrecoverymap/tests/jpegencoder_test.cpp b/libs/jpegrecoverymap/tests/jpegencoder_test.cpp
new file mode 100644
index 0000000..4cd2a5e
--- /dev/null
+++ b/libs/jpegrecoverymap/tests/jpegencoder_test.cpp
@@ -0,0 +1,125 @@
+/*
+ * 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 <jpegrecoverymap/jpegencoder.h>
+#include <gtest/gtest.h>
+#include <utils/Log.h>
+
+#include <fcntl.h>
+
+namespace android::recoverymap {
+
+#define VALID_IMAGE "/sdcard/Documents/minnie-320x240.yu12"
+#define VALID_IMAGE_WIDTH 320
+#define VALID_IMAGE_HEIGHT 240
+#define SINGLE_CHANNEL_IMAGE "/sdcard/Documents/minnie-320x240.y"
+#define SINGLE_CHANNEL_IMAGE_WIDTH VALID_IMAGE_WIDTH
+#define SINGLE_CHANNEL_IMAGE_HEIGHT VALID_IMAGE_HEIGHT
+#define INVALID_SIZE_IMAGE "/sdcard/Documents/minnie-318x240.yu12"
+#define INVALID_SIZE_IMAGE_WIDTH 318
+#define INVALID_SIZE_IMAGE_HEIGHT 240
+#define JPEG_QUALITY 90
+
+class JpegEncoderTest : public testing::Test {
+public:
+    struct Image {
+        std::unique_ptr<uint8_t[]> buffer;
+        size_t width;
+        size_t height;
+    };
+    JpegEncoderTest();
+    ~JpegEncoderTest();
+protected:
+    virtual void SetUp();
+    virtual void TearDown();
+
+    Image mValidImage, mInvalidSizeImage, mSingleChannelImage;
+};
+
+JpegEncoderTest::JpegEncoderTest() {}
+
+JpegEncoderTest::~JpegEncoderTest() {}
+
+static size_t getFileSize(int fd) {
+    struct stat st;
+    if (fstat(fd, &st) < 0) {
+        ALOGW("%s : fstat failed", __func__);
+        return 0;
+    }
+    return st.st_size; // bytes
+}
+
+static bool loadFile(const char filename[], JpegEncoderTest::Image* result) {
+    int fd = open(filename, O_CLOEXEC);
+    if (fd < 0) {
+        return false;
+    }
+    int length = getFileSize(fd);
+    if (length == 0) {
+        close(fd);
+        return false;
+    }
+    result->buffer.reset(new uint8_t[length]);
+    if (read(fd, result->buffer.get(), length) != static_cast<ssize_t>(length)) {
+        close(fd);
+        return false;
+    }
+    close(fd);
+    return true;
+}
+
+void JpegEncoderTest::SetUp() {
+    if (!loadFile(VALID_IMAGE, &mValidImage)) {
+        FAIL() << "Load file " << VALID_IMAGE << " failed";
+    }
+    mValidImage.width = VALID_IMAGE_WIDTH;
+    mValidImage.height = VALID_IMAGE_HEIGHT;
+    if (!loadFile(INVALID_SIZE_IMAGE, &mInvalidSizeImage)) {
+        FAIL() << "Load file " << INVALID_SIZE_IMAGE << " failed";
+    }
+    mInvalidSizeImage.width = INVALID_SIZE_IMAGE_WIDTH;
+    mInvalidSizeImage.height = INVALID_SIZE_IMAGE_HEIGHT;
+    if (!loadFile(SINGLE_CHANNEL_IMAGE, &mSingleChannelImage)) {
+        FAIL() << "Load file " << SINGLE_CHANNEL_IMAGE << " failed";
+    }
+    mSingleChannelImage.width = SINGLE_CHANNEL_IMAGE_WIDTH;
+    mSingleChannelImage.height = SINGLE_CHANNEL_IMAGE_HEIGHT;
+}
+
+void JpegEncoderTest::TearDown() {}
+
+TEST_F(JpegEncoderTest, validImage) {
+    JpegEncoder encoder;
+    EXPECT_TRUE(encoder.compressImage(mValidImage.buffer.get(), mValidImage.width,
+                                         mValidImage.height, JPEG_QUALITY, NULL, 0));
+    ASSERT_GT(encoder.getCompressedImageSize(), static_cast<uint32_t>(0));
+}
+
+TEST_F(JpegEncoderTest, invalidSizeImage) {
+    JpegEncoder encoder;
+    EXPECT_FALSE(encoder.compressImage(mInvalidSizeImage.buffer.get(), mInvalidSizeImage.width,
+                                          mInvalidSizeImage.height, JPEG_QUALITY, NULL, 0));
+}
+
+TEST_F(JpegEncoderTest, singleChannelImage) {
+    JpegEncoder encoder;
+    EXPECT_TRUE(encoder.compressImage(mSingleChannelImage.buffer.get(), mSingleChannelImage.width,
+                                         mSingleChannelImage.height, JPEG_QUALITY, NULL, 0, true));
+    ASSERT_GT(encoder.getCompressedImageSize(), static_cast<uint32_t>(0));
+}
+
+}
+
diff --git a/libs/jpegrecoverymap/tests/recoverymap_test.cpp b/libs/jpegrecoverymap/tests/recoverymap_test.cpp
new file mode 100644
index 0000000..ade33a0
--- /dev/null
+++ b/libs/jpegrecoverymap/tests/recoverymap_test.cpp
@@ -0,0 +1,153 @@
+/*
+ * 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 <jpegrecoverymap/recoverymap.h>
+#include <fcntl.h>
+#include <fstream>
+#include <gtest/gtest.h>
+#include <utils/Log.h>
+
+#define RAW_P010_IMAGE "/sdcard/Documents/raw_p010_image.p010"
+#define RAW_P010_IMAGE_WIDTH 1280
+#define RAW_P010_IMAGE_HEIGHT 720
+#define JPEG_IMAGE "/sdcard/Documents/jpeg_image.jpg"
+
+#define SAVE_ENCODING_RESULT true
+#define SAVE_DECODING_RESULT true
+
+namespace android::recoverymap {
+
+class RecoveryMapTest : public testing::Test {
+public:
+  RecoveryMapTest();
+  ~RecoveryMapTest();
+protected:
+  virtual void SetUp();
+  virtual void TearDown();
+
+  struct jpegr_uncompressed_struct mRawP010Image;
+  struct jpegr_compressed_struct mJpegImage;
+};
+
+RecoveryMapTest::RecoveryMapTest() {}
+RecoveryMapTest::~RecoveryMapTest() {}
+
+void RecoveryMapTest::SetUp() {}
+void RecoveryMapTest::TearDown() {
+  free(mRawP010Image.data);
+  free(mJpegImage.data);
+}
+
+static size_t getFileSize(int fd) {
+  struct stat st;
+  if (fstat(fd, &st) < 0) {
+    ALOGW("%s : fstat failed", __func__);
+    return 0;
+  }
+  return st.st_size; // bytes
+}
+
+static bool loadFile(const char filename[], void*& result, int* fileLength) {
+  int fd = open(filename, O_CLOEXEC);
+  if (fd < 0) {
+    return false;
+  }
+  int length = getFileSize(fd);
+  if (length == 0) {
+    close(fd);
+    return false;
+  }
+  if (fileLength != nullptr) {
+    *fileLength = length;
+  }
+  result = malloc(length);
+  if (read(fd, result, length) != static_cast<ssize_t>(length)) {
+    close(fd);
+    return false;
+  }
+  close(fd);
+  return true;
+}
+
+TEST_F(RecoveryMapTest, build) {
+  // Force all of the recovery map lib to be linked by calling all public functions.
+  RecoveryMap recovery_map;
+  recovery_map.encodeJPEGR(nullptr, nullptr, static_cast<jpegr_transfer_function>(0),
+                           nullptr, 0, nullptr);
+  recovery_map.encodeJPEGR(nullptr, nullptr, nullptr, static_cast<jpegr_transfer_function>(0),
+                           nullptr);
+  recovery_map.encodeJPEGR(nullptr, nullptr, static_cast<jpegr_transfer_function>(0), nullptr);
+  recovery_map.decodeJPEGR(nullptr, nullptr, nullptr, false);
+}
+
+TEST_F(RecoveryMapTest, encodeFromJpegThenDecode) {
+  int ret;
+
+  // Load input files.
+  if (!loadFile(RAW_P010_IMAGE, mRawP010Image.data, nullptr)) {
+    FAIL() << "Load file " << RAW_P010_IMAGE << " failed";
+  }
+  mRawP010Image.width = RAW_P010_IMAGE_WIDTH;
+  mRawP010Image.height = RAW_P010_IMAGE_HEIGHT;
+  mRawP010Image.colorGamut = jpegr_color_gamut::JPEGR_COLORGAMUT_BT2100;
+
+  if (!loadFile(JPEG_IMAGE, mJpegImage.data, &mJpegImage.length)) {
+    FAIL() << "Load file " << JPEG_IMAGE << " failed";
+  }
+  mJpegImage.colorGamut = jpegr_color_gamut::JPEGR_COLORGAMUT_BT709;
+
+  RecoveryMap recoveryMap;
+
+  jpegr_compressed_struct jpegR;
+  jpegR.maxLength = RAW_P010_IMAGE_WIDTH * RAW_P010_IMAGE_HEIGHT * sizeof(uint8_t);
+  jpegR.data = malloc(jpegR.maxLength);
+  ret = recoveryMap.encodeJPEGR(
+      &mRawP010Image, &mJpegImage, jpegr_transfer_function::JPEGR_TF_HLG, &jpegR);
+  if (ret != OK) {
+    FAIL() << "Error code is " << ret;
+  }
+  if (SAVE_ENCODING_RESULT) {
+    // Output image data to file
+    std::string filePath = "/sdcard/Documents/encoded_from_jpeg_input.jpgr";
+    std::ofstream imageFile(filePath.c_str(), std::ofstream::binary);
+    if (!imageFile.is_open()) {
+      ALOGE("%s: Unable to create file %s", __FUNCTION__, filePath.c_str());
+    }
+    imageFile.write((const char*)jpegR.data, jpegR.length);
+  }
+
+  jpegr_uncompressed_struct decodedJpegR;
+  int decodedJpegRSize = RAW_P010_IMAGE_WIDTH * RAW_P010_IMAGE_HEIGHT * 4;
+  decodedJpegR.data = malloc(decodedJpegRSize);
+  ret = recoveryMap.decodeJPEGR(&jpegR, &decodedJpegR);
+  if (ret != OK) {
+    FAIL() << "Error code is " << ret;
+  }
+  if (SAVE_DECODING_RESULT) {
+    // Output image data to file
+    std::string filePath = "/sdcard/Documents/decoded_from_jpeg_input.rgb10";
+    std::ofstream imageFile(filePath.c_str(), std::ofstream::binary);
+    if (!imageFile.is_open()) {
+      ALOGE("%s: Unable to create file %s", __FUNCTION__, filePath.c_str());
+    }
+    imageFile.write((const char*)decodedJpegR.data, decodedJpegRSize);
+  }
+
+  free(jpegR.data);
+  free(decodedJpegR.data);
+}
+
+} // namespace android::recoverymap
diff --git a/libs/jpegrecoverymap/tests/recoverymapmath_test.cpp b/libs/jpegrecoverymap/tests/recoverymapmath_test.cpp
new file mode 100644
index 0000000..169201c
--- /dev/null
+++ b/libs/jpegrecoverymap/tests/recoverymapmath_test.cpp
@@ -0,0 +1,882 @@
+/*
+ * 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 <cmath>
+#include <gtest/gtest.h>
+#include <gmock/gmock.h>
+#include <jpegrecoverymap/recoverymapmath.h>
+
+namespace android::recoverymap {
+
+class RecoveryMapMathTest : public testing::Test {
+public:
+  RecoveryMapMathTest();
+  ~RecoveryMapMathTest();
+
+  float ComparisonEpsilon() { return 1e-4f; }
+  float LuminanceEpsilon() { return 1e-2f; }
+
+  Color Yuv420(uint8_t y, uint8_t u, uint8_t v) {
+      return {{{ static_cast<float>(y) / 255.0f,
+                 (static_cast<float>(u) - 128.0f) / 255.0f,
+                 (static_cast<float>(v) - 128.0f) / 255.0f }}};
+  }
+
+  Color P010(uint16_t y, uint16_t u, uint16_t v) {
+      return {{{ static_cast<float>(y) / 940.0f,
+                 (static_cast<float>(u) - 64.0f) / 940.0f - 0.5f,
+                 (static_cast<float>(v) - 64.0f) / 940.0f - 0.5f }}};
+  }
+
+  float Map(uint8_t e) {
+    return (static_cast<float>(e) - 127.5f) / 127.5f;
+  }
+
+  Color ColorMin(Color e1, Color e2) {
+    return {{{ fmin(e1.r, e2.r), fmin(e1.g, e2.g), fmin(e1.b, e2.b) }}};
+  }
+
+  Color ColorMax(Color e1, Color e2) {
+    return {{{ fmax(e1.r, e2.r), fmax(e1.g, e2.g), fmax(e1.b, e2.b) }}};
+  }
+
+  Color RgbBlack() { return {{{ 0.0f, 0.0f, 0.0f }}}; }
+  Color RgbWhite() { return {{{ 1.0f, 1.0f, 1.0f }}}; }
+
+  Color RgbRed() { return {{{ 1.0f, 0.0f, 0.0f }}}; }
+  Color RgbGreen() { return {{{ 0.0f, 1.0f, 0.0f }}}; }
+  Color RgbBlue() { return {{{ 0.0f, 0.0f, 1.0f }}}; }
+
+  Color YuvBlack() { return {{{ 0.0f, 0.0f, 0.0f }}}; }
+  Color YuvWhite() { return {{{ 1.0f, 0.0f, 0.0f }}}; }
+
+  Color SrgbYuvRed() { return {{{ 0.299f, -0.1687f, 0.5f }}}; }
+  Color SrgbYuvGreen() { return {{{ 0.587f, -0.3313f, -0.4187f }}}; }
+  Color SrgbYuvBlue() { return {{{ 0.114f, 0.5f, -0.0813f }}}; }
+
+  Color Bt2100YuvRed() { return {{{ 0.2627f, -0.13963f, 0.5f }}}; }
+  Color Bt2100YuvGreen() { return {{{ 0.6780f, -0.36037f, -0.45979f }}}; }
+  Color Bt2100YuvBlue() { return {{{ 0.0593f, 0.5f, -0.04021f }}}; }
+
+  float SrgbYuvToLuminance(Color yuv_gamma, ColorCalculationFn luminanceFn) {
+    Color rgb_gamma = srgbYuvToRgb(yuv_gamma);
+    Color rgb = srgbInvOetf(rgb_gamma);
+    float luminance_scaled = luminanceFn(rgb);
+    return luminance_scaled * kSdrWhiteNits;
+  }
+
+  float Bt2100YuvToLuminance(Color yuv_gamma, ColorTransformFn hdrInvOetf,
+                             ColorTransformFn gamutConversionFn, ColorCalculationFn luminanceFn,
+                             float scale_factor) {
+    Color rgb_gamma = bt2100YuvToRgb(yuv_gamma);
+    Color rgb = hdrInvOetf(rgb_gamma);
+    rgb = gamutConversionFn(rgb);
+    float luminance_scaled = luminanceFn(rgb);
+    return luminance_scaled * scale_factor;
+  }
+
+  Color Recover(Color yuv_gamma, float recovery, float range_scaling_factor) {
+    Color rgb_gamma = srgbYuvToRgb(yuv_gamma);
+    Color rgb = srgbInvOetf(rgb_gamma);
+    return applyRecovery(rgb, recovery, range_scaling_factor);
+  }
+
+  jpegr_uncompressed_struct Yuv420Image() {
+    static uint8_t pixels[] = {
+      // Y
+      0x00, 0x10, 0x20, 0x30,
+      0x01, 0x11, 0x21, 0x31,
+      0x02, 0x12, 0x22, 0x32,
+      0x03, 0x13, 0x23, 0x33,
+      // U
+      0xA0, 0xA1,
+      0xA2, 0xA3,
+      // V
+      0xB0, 0xB1,
+      0xB2, 0xB3,
+    };
+    return { pixels, 4, 4, JPEGR_COLORGAMUT_BT709 };
+  }
+
+  Color (*Yuv420Colors())[4] {
+    static Color colors[4][4] = {
+      {
+        Yuv420(0x00, 0xA0, 0xB0), Yuv420(0x10, 0xA0, 0xB0),
+        Yuv420(0x20, 0xA1, 0xB1), Yuv420(0x30, 0xA1, 0xB1),
+      }, {
+        Yuv420(0x01, 0xA0, 0xB0), Yuv420(0x11, 0xA0, 0xB0),
+        Yuv420(0x21, 0xA1, 0xB1), Yuv420(0x31, 0xA1, 0xB1),
+      }, {
+        Yuv420(0x02, 0xA2, 0xB2), Yuv420(0x12, 0xA2, 0xB2),
+        Yuv420(0x22, 0xA3, 0xB3), Yuv420(0x32, 0xA3, 0xB3),
+      }, {
+        Yuv420(0x03, 0xA2, 0xB2), Yuv420(0x13, 0xA2, 0xB2),
+        Yuv420(0x23, 0xA3, 0xB3), Yuv420(0x33, 0xA3, 0xB3),
+      },
+    };
+    return colors;
+  }
+
+  jpegr_uncompressed_struct P010Image() {
+    static uint16_t pixels[] = {
+      // Y
+      0x00 << 6, 0x10 << 6, 0x20 << 6, 0x30 << 6,
+      0x01 << 6, 0x11 << 6, 0x21 << 6, 0x31 << 6,
+      0x02 << 6, 0x12 << 6, 0x22 << 6, 0x32 << 6,
+      0x03 << 6, 0x13 << 6, 0x23 << 6, 0x33 << 6,
+      // UV
+      0xA0 << 6, 0xB0 << 6, 0xA1 << 6, 0xB1 << 6,
+      0xA2 << 6, 0xB2 << 6, 0xA3 << 6, 0xB3 << 6,
+    };
+    return { pixels, 4, 4, JPEGR_COLORGAMUT_BT709 };
+  }
+
+  Color (*P010Colors())[4] {
+    static Color colors[4][4] = {
+      {
+        P010(0x00, 0xA0, 0xB0), P010(0x10, 0xA0, 0xB0),
+        P010(0x20, 0xA1, 0xB1), P010(0x30, 0xA1, 0xB1),
+      }, {
+        P010(0x01, 0xA0, 0xB0), P010(0x11, 0xA0, 0xB0),
+        P010(0x21, 0xA1, 0xB1), P010(0x31, 0xA1, 0xB1),
+      }, {
+        P010(0x02, 0xA2, 0xB2), P010(0x12, 0xA2, 0xB2),
+        P010(0x22, 0xA3, 0xB3), P010(0x32, 0xA3, 0xB3),
+      }, {
+        P010(0x03, 0xA2, 0xB2), P010(0x13, 0xA2, 0xB2),
+        P010(0x23, 0xA3, 0xB3), P010(0x33, 0xA3, 0xB3),
+      },
+    };
+    return colors;
+  }
+
+  jpegr_uncompressed_struct MapImage() {
+    static uint8_t pixels[] = {
+      0x00, 0x10, 0x20, 0x30,
+      0x01, 0x11, 0x21, 0x31,
+      0x02, 0x12, 0x22, 0x32,
+      0x03, 0x13, 0x23, 0x33,
+    };
+    return { pixels, 4, 4, JPEGR_COLORGAMUT_UNSPECIFIED };
+  }
+
+  float (*MapValues())[4] {
+    static float values[4][4] = {
+      {
+        Map(0x00), Map(0x10), Map(0x20), Map(0x30),
+      }, {
+        Map(0x01), Map(0x11), Map(0x21), Map(0x31),
+      }, {
+        Map(0x02), Map(0x12), Map(0x22), Map(0x32),
+      }, {
+        Map(0x03), Map(0x13), Map(0x23), Map(0x33),
+      },
+    };
+    return values;
+  }
+
+protected:
+  virtual void SetUp();
+  virtual void TearDown();
+};
+
+RecoveryMapMathTest::RecoveryMapMathTest() {}
+RecoveryMapMathTest::~RecoveryMapMathTest() {}
+
+void RecoveryMapMathTest::SetUp() {}
+void RecoveryMapMathTest::TearDown() {}
+
+#define EXPECT_RGB_EQ(e1, e2)       \
+  EXPECT_FLOAT_EQ((e1).r, (e2).r);  \
+  EXPECT_FLOAT_EQ((e1).g, (e2).g);  \
+  EXPECT_FLOAT_EQ((e1).b, (e2).b)
+
+#define EXPECT_RGB_NEAR(e1, e2)                     \
+  EXPECT_NEAR((e1).r, (e2).r, ComparisonEpsilon()); \
+  EXPECT_NEAR((e1).g, (e2).g, ComparisonEpsilon()); \
+  EXPECT_NEAR((e1).b, (e2).b, ComparisonEpsilon())
+
+#define EXPECT_RGB_CLOSE(e1, e2)                            \
+  EXPECT_NEAR((e1).r, (e2).r, ComparisonEpsilon() * 10.0f); \
+  EXPECT_NEAR((e1).g, (e2).g, ComparisonEpsilon() * 10.0f); \
+  EXPECT_NEAR((e1).b, (e2).b, ComparisonEpsilon() * 10.0f)
+
+#define EXPECT_YUV_EQ(e1, e2)       \
+  EXPECT_FLOAT_EQ((e1).y, (e2).y);  \
+  EXPECT_FLOAT_EQ((e1).u, (e2).u);  \
+  EXPECT_FLOAT_EQ((e1).v, (e2).v)
+
+#define EXPECT_YUV_NEAR(e1, e2)                     \
+  EXPECT_NEAR((e1).y, (e2).y, ComparisonEpsilon()); \
+  EXPECT_NEAR((e1).u, (e2).u, ComparisonEpsilon()); \
+  EXPECT_NEAR((e1).v, (e2).v, ComparisonEpsilon())
+
+#define EXPECT_YUV_BETWEEN(e, min, max)                                           \
+  EXPECT_THAT((e).y, testing::AllOf(testing::Ge((min).y), testing::Le((max).y))); \
+  EXPECT_THAT((e).u, testing::AllOf(testing::Ge((min).u), testing::Le((max).u))); \
+  EXPECT_THAT((e).v, testing::AllOf(testing::Ge((min).v), testing::Le((max).v)))
+
+// TODO: a bunch of these tests can be parameterized.
+
+TEST_F(RecoveryMapMathTest, ColorConstruct) {
+  Color e1 = {{{ 0.1f, 0.2f, 0.3f }}};
+
+  EXPECT_FLOAT_EQ(e1.r, 0.1f);
+  EXPECT_FLOAT_EQ(e1.g, 0.2f);
+  EXPECT_FLOAT_EQ(e1.b, 0.3f);
+
+  EXPECT_FLOAT_EQ(e1.y, 0.1f);
+  EXPECT_FLOAT_EQ(e1.u, 0.2f);
+  EXPECT_FLOAT_EQ(e1.v, 0.3f);
+}
+
+TEST_F(RecoveryMapMathTest, ColorAddColor) {
+  Color e1 = {{{ 0.1f, 0.2f, 0.3f }}};
+
+  Color e2 = e1 + e1;
+  EXPECT_FLOAT_EQ(e2.r, e1.r * 2.0f);
+  EXPECT_FLOAT_EQ(e2.g, e1.g * 2.0f);
+  EXPECT_FLOAT_EQ(e2.b, e1.b * 2.0f);
+
+  e2 += e1;
+  EXPECT_FLOAT_EQ(e2.r, e1.r * 3.0f);
+  EXPECT_FLOAT_EQ(e2.g, e1.g * 3.0f);
+  EXPECT_FLOAT_EQ(e2.b, e1.b * 3.0f);
+}
+
+TEST_F(RecoveryMapMathTest, ColorAddFloat) {
+  Color e1 = {{{ 0.1f, 0.2f, 0.3f }}};
+
+  Color e2 = e1 + 0.1f;
+  EXPECT_FLOAT_EQ(e2.r, e1.r + 0.1f);
+  EXPECT_FLOAT_EQ(e2.g, e1.g + 0.1f);
+  EXPECT_FLOAT_EQ(e2.b, e1.b + 0.1f);
+
+  e2 += 0.1f;
+  EXPECT_FLOAT_EQ(e2.r, e1.r + 0.2f);
+  EXPECT_FLOAT_EQ(e2.g, e1.g + 0.2f);
+  EXPECT_FLOAT_EQ(e2.b, e1.b + 0.2f);
+}
+
+TEST_F(RecoveryMapMathTest, ColorSubtractColor) {
+  Color e1 = {{{ 0.1f, 0.2f, 0.3f }}};
+
+  Color e2 = e1 - e1;
+  EXPECT_FLOAT_EQ(e2.r, 0.0f);
+  EXPECT_FLOAT_EQ(e2.g, 0.0f);
+  EXPECT_FLOAT_EQ(e2.b, 0.0f);
+
+  e2 -= e1;
+  EXPECT_FLOAT_EQ(e2.r, -e1.r);
+  EXPECT_FLOAT_EQ(e2.g, -e1.g);
+  EXPECT_FLOAT_EQ(e2.b, -e1.b);
+}
+
+TEST_F(RecoveryMapMathTest, ColorSubtractFloat) {
+  Color e1 = {{{ 0.1f, 0.2f, 0.3f }}};
+
+  Color e2 = e1 - 0.1f;
+  EXPECT_FLOAT_EQ(e2.r, e1.r - 0.1f);
+  EXPECT_FLOAT_EQ(e2.g, e1.g - 0.1f);
+  EXPECT_FLOAT_EQ(e2.b, e1.b - 0.1f);
+
+  e2 -= 0.1f;
+  EXPECT_FLOAT_EQ(e2.r, e1.r - 0.2f);
+  EXPECT_FLOAT_EQ(e2.g, e1.g - 0.2f);
+  EXPECT_FLOAT_EQ(e2.b, e1.b - 0.2f);
+}
+
+TEST_F(RecoveryMapMathTest, ColorMultiplyFloat) {
+  Color e1 = {{{ 0.1f, 0.2f, 0.3f }}};
+
+  Color e2 = e1 * 2.0f;
+  EXPECT_FLOAT_EQ(e2.r, e1.r * 2.0f);
+  EXPECT_FLOAT_EQ(e2.g, e1.g * 2.0f);
+  EXPECT_FLOAT_EQ(e2.b, e1.b * 2.0f);
+
+  e2 *= 2.0f;
+  EXPECT_FLOAT_EQ(e2.r, e1.r * 4.0f);
+  EXPECT_FLOAT_EQ(e2.g, e1.g * 4.0f);
+  EXPECT_FLOAT_EQ(e2.b, e1.b * 4.0f);
+}
+
+TEST_F(RecoveryMapMathTest, ColorDivideFloat) {
+  Color e1 = {{{ 0.1f, 0.2f, 0.3f }}};
+
+  Color e2 = e1 / 2.0f;
+  EXPECT_FLOAT_EQ(e2.r, e1.r / 2.0f);
+  EXPECT_FLOAT_EQ(e2.g, e1.g / 2.0f);
+  EXPECT_FLOAT_EQ(e2.b, e1.b / 2.0f);
+
+  e2 /= 2.0f;
+  EXPECT_FLOAT_EQ(e2.r, e1.r / 4.0f);
+  EXPECT_FLOAT_EQ(e2.g, e1.g / 4.0f);
+  EXPECT_FLOAT_EQ(e2.b, e1.b / 4.0f);
+}
+
+TEST_F(RecoveryMapMathTest, SrgbLuminance) {
+  EXPECT_FLOAT_EQ(srgbLuminance(RgbBlack()), 0.0f);
+  EXPECT_FLOAT_EQ(srgbLuminance(RgbWhite()), 1.0f);
+  EXPECT_FLOAT_EQ(srgbLuminance(RgbRed()), 0.2126f);
+  EXPECT_FLOAT_EQ(srgbLuminance(RgbGreen()), 0.7152f);
+  EXPECT_FLOAT_EQ(srgbLuminance(RgbBlue()), 0.0722f);
+}
+
+TEST_F(RecoveryMapMathTest, SrgbYuvToRgb) {
+  Color rgb_black = srgbYuvToRgb(YuvBlack());
+  EXPECT_RGB_NEAR(rgb_black, RgbBlack());
+
+  Color rgb_white = srgbYuvToRgb(YuvWhite());
+  EXPECT_RGB_NEAR(rgb_white, RgbWhite());
+
+  Color rgb_r = srgbYuvToRgb(SrgbYuvRed());
+  EXPECT_RGB_NEAR(rgb_r, RgbRed());
+
+  Color rgb_g = srgbYuvToRgb(SrgbYuvGreen());
+  EXPECT_RGB_NEAR(rgb_g, RgbGreen());
+
+  Color rgb_b = srgbYuvToRgb(SrgbYuvBlue());
+  EXPECT_RGB_NEAR(rgb_b, RgbBlue());
+}
+
+TEST_F(RecoveryMapMathTest, SrgbRgbToYuv) {
+  Color yuv_black = srgbRgbToYuv(RgbBlack());
+  EXPECT_YUV_NEAR(yuv_black, YuvBlack());
+
+  Color yuv_white = srgbRgbToYuv(RgbWhite());
+  EXPECT_YUV_NEAR(yuv_white, YuvWhite());
+
+  Color yuv_r = srgbRgbToYuv(RgbRed());
+  EXPECT_YUV_NEAR(yuv_r, SrgbYuvRed());
+
+  Color yuv_g = srgbRgbToYuv(RgbGreen());
+  EXPECT_YUV_NEAR(yuv_g, SrgbYuvGreen());
+
+  Color yuv_b = srgbRgbToYuv(RgbBlue());
+  EXPECT_YUV_NEAR(yuv_b, SrgbYuvBlue());
+}
+
+TEST_F(RecoveryMapMathTest, SrgbRgbYuvRoundtrip) {
+  Color rgb_black = srgbYuvToRgb(srgbRgbToYuv(RgbBlack()));
+  EXPECT_RGB_NEAR(rgb_black, RgbBlack());
+
+  Color rgb_white = srgbYuvToRgb(srgbRgbToYuv(RgbWhite()));
+  EXPECT_RGB_NEAR(rgb_white, RgbWhite());
+
+  Color rgb_r = srgbYuvToRgb(srgbRgbToYuv(RgbRed()));
+  EXPECT_RGB_NEAR(rgb_r, RgbRed());
+
+  Color rgb_g = srgbYuvToRgb(srgbRgbToYuv(RgbGreen()));
+  EXPECT_RGB_NEAR(rgb_g, RgbGreen());
+
+  Color rgb_b = srgbYuvToRgb(srgbRgbToYuv(RgbBlue()));
+  EXPECT_RGB_NEAR(rgb_b, RgbBlue());
+}
+
+TEST_F(RecoveryMapMathTest, SrgbTransferFunction) {
+  EXPECT_FLOAT_EQ(srgbInvOetf(0.0f), 0.0f);
+  EXPECT_NEAR(srgbInvOetf(0.02f), 0.00154f, ComparisonEpsilon());
+  EXPECT_NEAR(srgbInvOetf(0.04045f), 0.00313f, ComparisonEpsilon());
+  EXPECT_NEAR(srgbInvOetf(0.5f), 0.21404f, ComparisonEpsilon());
+  EXPECT_FLOAT_EQ(srgbInvOetf(1.0f), 1.0f);
+}
+
+TEST_F(RecoveryMapMathTest, P3Luminance) {
+  EXPECT_FLOAT_EQ(p3Luminance(RgbBlack()), 0.0f);
+  EXPECT_FLOAT_EQ(p3Luminance(RgbWhite()), 1.0f);
+  EXPECT_FLOAT_EQ(p3Luminance(RgbRed()), 0.20949f);
+  EXPECT_FLOAT_EQ(p3Luminance(RgbGreen()), 0.72160f);
+  EXPECT_FLOAT_EQ(p3Luminance(RgbBlue()), 0.06891f);
+}
+
+TEST_F(RecoveryMapMathTest, Bt2100Luminance) {
+  EXPECT_FLOAT_EQ(bt2100Luminance(RgbBlack()), 0.0f);
+  EXPECT_FLOAT_EQ(bt2100Luminance(RgbWhite()), 1.0f);
+  EXPECT_FLOAT_EQ(bt2100Luminance(RgbRed()), 0.2627f);
+  EXPECT_FLOAT_EQ(bt2100Luminance(RgbGreen()), 0.6780f);
+  EXPECT_FLOAT_EQ(bt2100Luminance(RgbBlue()), 0.0593f);
+}
+
+TEST_F(RecoveryMapMathTest, Bt2100YuvToRgb) {
+  Color rgb_black = bt2100YuvToRgb(YuvBlack());
+  EXPECT_RGB_NEAR(rgb_black, RgbBlack());
+
+  Color rgb_white = bt2100YuvToRgb(YuvWhite());
+  EXPECT_RGB_NEAR(rgb_white, RgbWhite());
+
+  Color rgb_r = bt2100YuvToRgb(Bt2100YuvRed());
+  EXPECT_RGB_NEAR(rgb_r, RgbRed());
+
+  Color rgb_g = bt2100YuvToRgb(Bt2100YuvGreen());
+  EXPECT_RGB_NEAR(rgb_g, RgbGreen());
+
+  Color rgb_b = bt2100YuvToRgb(Bt2100YuvBlue());
+  EXPECT_RGB_NEAR(rgb_b, RgbBlue());
+}
+
+TEST_F(RecoveryMapMathTest, Bt2100RgbToYuv) {
+  Color yuv_black = bt2100RgbToYuv(RgbBlack());
+  EXPECT_YUV_NEAR(yuv_black, YuvBlack());
+
+  Color yuv_white = bt2100RgbToYuv(RgbWhite());
+  EXPECT_YUV_NEAR(yuv_white, YuvWhite());
+
+  Color yuv_r = bt2100RgbToYuv(RgbRed());
+  EXPECT_YUV_NEAR(yuv_r, Bt2100YuvRed());
+
+  Color yuv_g = bt2100RgbToYuv(RgbGreen());
+  EXPECT_YUV_NEAR(yuv_g, Bt2100YuvGreen());
+
+  Color yuv_b = bt2100RgbToYuv(RgbBlue());
+  EXPECT_YUV_NEAR(yuv_b, Bt2100YuvBlue());
+}
+
+TEST_F(RecoveryMapMathTest, Bt2100RgbYuvRoundtrip) {
+  Color rgb_black = bt2100YuvToRgb(bt2100RgbToYuv(RgbBlack()));
+  EXPECT_RGB_NEAR(rgb_black, RgbBlack());
+
+  Color rgb_white = bt2100YuvToRgb(bt2100RgbToYuv(RgbWhite()));
+  EXPECT_RGB_NEAR(rgb_white, RgbWhite());
+
+  Color rgb_r = bt2100YuvToRgb(bt2100RgbToYuv(RgbRed()));
+  EXPECT_RGB_NEAR(rgb_r, RgbRed());
+
+  Color rgb_g = bt2100YuvToRgb(bt2100RgbToYuv(RgbGreen()));
+  EXPECT_RGB_NEAR(rgb_g, RgbGreen());
+
+  Color rgb_b = bt2100YuvToRgb(bt2100RgbToYuv(RgbBlue()));
+  EXPECT_RGB_NEAR(rgb_b, RgbBlue());
+}
+
+TEST_F(RecoveryMapMathTest, HlgOetf) {
+  EXPECT_FLOAT_EQ(hlgOetf(0.0f), 0.0f);
+  EXPECT_NEAR(hlgOetf(0.04167f), 0.35357f, ComparisonEpsilon());
+  EXPECT_NEAR(hlgOetf(0.08333f), 0.5f, ComparisonEpsilon());
+  EXPECT_NEAR(hlgOetf(0.5f), 0.87164f, ComparisonEpsilon());
+  EXPECT_FLOAT_EQ(hlgOetf(1.0f), 1.0f);
+
+  Color e = {{{ 0.04167f, 0.08333f, 0.5f }}};
+  Color e_gamma = {{{ 0.35357f, 0.5f, 0.87164f }}};
+  EXPECT_RGB_NEAR(hlgOetf(e), e_gamma);
+}
+
+TEST_F(RecoveryMapMathTest, HlgInvOetf) {
+  EXPECT_FLOAT_EQ(hlgInvOetf(0.0f), 0.0f);
+  EXPECT_NEAR(hlgInvOetf(0.25f), 0.02083f, ComparisonEpsilon());
+  EXPECT_NEAR(hlgInvOetf(0.5f), 0.08333f, ComparisonEpsilon());
+  EXPECT_NEAR(hlgInvOetf(0.75f), 0.26496f, ComparisonEpsilon());
+  EXPECT_FLOAT_EQ(hlgInvOetf(1.0f), 1.0f);
+
+  Color e_gamma = {{{ 0.25f, 0.5f, 0.75f }}};
+  Color e = {{{ 0.02083f, 0.08333f, 0.26496f }}};
+  EXPECT_RGB_NEAR(hlgInvOetf(e_gamma), e);
+}
+
+TEST_F(RecoveryMapMathTest, HlgTransferFunctionRoundtrip) {
+  EXPECT_FLOAT_EQ(hlgInvOetf(hlgOetf(0.0f)), 0.0f);
+  EXPECT_NEAR(hlgInvOetf(hlgOetf(0.04167f)), 0.04167f, ComparisonEpsilon());
+  EXPECT_NEAR(hlgInvOetf(hlgOetf(0.08333f)), 0.08333f, ComparisonEpsilon());
+  EXPECT_NEAR(hlgInvOetf(hlgOetf(0.5f)), 0.5f, ComparisonEpsilon());
+  EXPECT_FLOAT_EQ(hlgInvOetf(hlgOetf(1.0f)), 1.0f);
+}
+
+TEST_F(RecoveryMapMathTest, PqOetf) {
+  EXPECT_FLOAT_EQ(pqOetf(0.0f), 0.0f);
+  EXPECT_NEAR(pqOetf(0.01f), 0.50808f, ComparisonEpsilon());
+  EXPECT_NEAR(pqOetf(0.5f), 0.92655f, ComparisonEpsilon());
+  EXPECT_NEAR(pqOetf(0.99f), 0.99895f, ComparisonEpsilon());
+  EXPECT_FLOAT_EQ(pqOetf(1.0f), 1.0f);
+
+  Color e = {{{ 0.01f, 0.5f, 0.99f }}};
+  Color e_gamma = {{{ 0.50808f, 0.92655f, 0.99895f }}};
+  EXPECT_RGB_NEAR(pqOetf(e), e_gamma);
+}
+
+TEST_F(RecoveryMapMathTest, PqInvOetf) {
+  EXPECT_FLOAT_EQ(pqInvOetf(0.0f), 0.0f);
+  EXPECT_NEAR(pqInvOetf(0.01f), 2.31017e-7f, ComparisonEpsilon());
+  EXPECT_NEAR(pqInvOetf(0.5f), 0.00922f, ComparisonEpsilon());
+  EXPECT_NEAR(pqInvOetf(0.99f), 0.90903f, ComparisonEpsilon());
+  EXPECT_FLOAT_EQ(pqInvOetf(1.0f), 1.0f);
+
+  Color e_gamma = {{{ 0.01f, 0.5f, 0.99f }}};
+  Color e = {{{ 2.31017e-7f, 0.00922f, 0.90903f }}};
+  EXPECT_RGB_NEAR(pqInvOetf(e_gamma), e);
+}
+
+TEST_F(RecoveryMapMathTest, PqTransferFunctionRoundtrip) {
+  EXPECT_FLOAT_EQ(pqInvOetf(pqOetf(0.0f)), 0.0f);
+  EXPECT_NEAR(pqInvOetf(pqOetf(0.01f)), 0.01f, ComparisonEpsilon());
+  EXPECT_NEAR(pqInvOetf(pqOetf(0.5f)), 0.5f, ComparisonEpsilon());
+  EXPECT_NEAR(pqInvOetf(pqOetf(0.99f)), 0.99f, ComparisonEpsilon());
+  EXPECT_FLOAT_EQ(pqInvOetf(pqOetf(1.0f)), 1.0f);
+}
+
+TEST_F(RecoveryMapMathTest, ColorConversionLookup) {
+  EXPECT_EQ(getHdrConversionFn(JPEGR_COLORGAMUT_BT709, JPEGR_COLORGAMUT_UNSPECIFIED),
+            nullptr);
+  EXPECT_EQ(getHdrConversionFn(JPEGR_COLORGAMUT_BT709, JPEGR_COLORGAMUT_BT709),
+            identityConversion);
+  EXPECT_EQ(getHdrConversionFn(JPEGR_COLORGAMUT_BT709, JPEGR_COLORGAMUT_P3),
+            p3ToBt709);
+  EXPECT_EQ(getHdrConversionFn(JPEGR_COLORGAMUT_BT709, JPEGR_COLORGAMUT_BT2100),
+            bt2100ToBt709);
+
+  EXPECT_EQ(getHdrConversionFn(JPEGR_COLORGAMUT_P3, JPEGR_COLORGAMUT_UNSPECIFIED),
+            nullptr);
+  EXPECT_EQ(getHdrConversionFn(JPEGR_COLORGAMUT_P3, JPEGR_COLORGAMUT_BT709),
+            bt709ToP3);
+  EXPECT_EQ(getHdrConversionFn(JPEGR_COLORGAMUT_P3, JPEGR_COLORGAMUT_P3),
+            identityConversion);
+  EXPECT_EQ(getHdrConversionFn(JPEGR_COLORGAMUT_P3, JPEGR_COLORGAMUT_BT2100),
+            bt2100ToP3);
+
+  EXPECT_EQ(getHdrConversionFn(JPEGR_COLORGAMUT_BT2100, JPEGR_COLORGAMUT_UNSPECIFIED),
+            nullptr);
+  EXPECT_EQ(getHdrConversionFn(JPEGR_COLORGAMUT_BT2100, JPEGR_COLORGAMUT_BT709),
+            bt709ToBt2100);
+  EXPECT_EQ(getHdrConversionFn(JPEGR_COLORGAMUT_BT2100, JPEGR_COLORGAMUT_P3),
+            p3ToBt2100);
+  EXPECT_EQ(getHdrConversionFn(JPEGR_COLORGAMUT_BT2100, JPEGR_COLORGAMUT_BT2100),
+            identityConversion);
+
+  EXPECT_EQ(getHdrConversionFn(JPEGR_COLORGAMUT_UNSPECIFIED, JPEGR_COLORGAMUT_UNSPECIFIED),
+            nullptr);
+  EXPECT_EQ(getHdrConversionFn(JPEGR_COLORGAMUT_UNSPECIFIED, JPEGR_COLORGAMUT_BT709),
+            nullptr);
+  EXPECT_EQ(getHdrConversionFn(JPEGR_COLORGAMUT_UNSPECIFIED, JPEGR_COLORGAMUT_P3),
+            nullptr);
+  EXPECT_EQ(getHdrConversionFn(JPEGR_COLORGAMUT_UNSPECIFIED, JPEGR_COLORGAMUT_BT2100),
+            nullptr);
+}
+
+TEST_F(RecoveryMapMathTest, EncodeRecovery) {
+  EXPECT_EQ(encodeRecovery(0.0f, 0.0f, 4.0f), 127);
+  EXPECT_EQ(encodeRecovery(0.0f, 1.0f, 4.0f), 127);
+  EXPECT_EQ(encodeRecovery(1.0f, 0.0f, 4.0f), 0);
+  EXPECT_EQ(encodeRecovery(0.5f, 0.0f, 4.0f), 0);
+
+  EXPECT_EQ(encodeRecovery(1.0f, 1.0f, 4.0f), 127);
+  EXPECT_EQ(encodeRecovery(1.0f, 4.0f, 4.0f), 255);
+  EXPECT_EQ(encodeRecovery(1.0f, 5.0f, 4.0f), 255);
+  EXPECT_EQ(encodeRecovery(4.0f, 1.0f, 4.0f), 0);
+  EXPECT_EQ(encodeRecovery(4.0f, 0.5f, 4.0f), 0);
+  EXPECT_EQ(encodeRecovery(1.0f, 2.0f, 4.0f), 191);
+  EXPECT_EQ(encodeRecovery(2.0f, 1.0f, 4.0f), 63);
+
+  EXPECT_EQ(encodeRecovery(1.0f, 2.0f, 2.0f), 255);
+  EXPECT_EQ(encodeRecovery(2.0f, 1.0f, 2.0f), 0);
+  EXPECT_EQ(encodeRecovery(1.0f, 1.41421f, 2.0f), 191);
+  EXPECT_EQ(encodeRecovery(1.41421f, 1.0f, 2.0f), 63);
+
+  EXPECT_EQ(encodeRecovery(1.0f, 8.0f, 8.0f), 255);
+  EXPECT_EQ(encodeRecovery(8.0f, 1.0f, 8.0f), 0);
+  EXPECT_EQ(encodeRecovery(1.0f, 2.82843f, 8.0f), 191);
+  EXPECT_EQ(encodeRecovery(2.82843f, 1.0f, 8.0f), 63);
+}
+
+TEST_F(RecoveryMapMathTest, ApplyRecovery) {
+  EXPECT_RGB_NEAR(applyRecovery(RgbBlack(), -1.0f, 4.0f), RgbBlack());
+  EXPECT_RGB_NEAR(applyRecovery(RgbBlack(), 0.0f, 4.0f), RgbBlack());
+  EXPECT_RGB_NEAR(applyRecovery(RgbBlack(), 1.0f, 4.0f), RgbBlack());
+
+  EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), -1.0f, 4.0f), RgbWhite() / 4.0f);
+  EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), -0.5f, 4.0f), RgbWhite() / 2.0f);
+  EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.0f, 4.0f), RgbWhite());
+  EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.5f, 4.0f), RgbWhite() * 2.0f);
+  EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 1.0f, 4.0f), RgbWhite() * 4.0f);
+
+  EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), -1.0f, 2.0f), RgbWhite() / 2.0f);
+  EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), -0.5f, 2.0f), RgbWhite() / 1.41421f);
+  EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.0f, 2.0f), RgbWhite());
+  EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.5f, 2.0f), RgbWhite() * 1.41421f);
+  EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 1.0f, 2.0f), RgbWhite() * 2.0f);
+
+  EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), -1.0f, 8.0f), RgbWhite() / 8.0f);
+  EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), -0.5f, 8.0f), RgbWhite() / 2.82843f);
+  EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.0f, 8.0f), RgbWhite());
+  EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 0.5f, 8.0f), RgbWhite() * 2.82843f);
+  EXPECT_RGB_NEAR(applyRecovery(RgbWhite(), 1.0f, 8.0f), RgbWhite() * 8.0f);
+
+  Color e = {{{ 0.0f, 0.5f, 1.0f }}};
+
+  EXPECT_RGB_NEAR(applyRecovery(e, -1.0f, 4.0f), e / 4.0f);
+  EXPECT_RGB_NEAR(applyRecovery(e, -0.5f, 4.0f), e / 2.0f);
+  EXPECT_RGB_NEAR(applyRecovery(e, 0.0f, 4.0f), e);
+  EXPECT_RGB_NEAR(applyRecovery(e, 0.5f, 4.0f), e * 2.0f);
+  EXPECT_RGB_NEAR(applyRecovery(e, 1.0f, 4.0f), e * 4.0f);
+}
+
+TEST_F(RecoveryMapMathTest, GetYuv420Pixel) {
+  jpegr_uncompressed_struct image = Yuv420Image();
+  Color (*colors)[4] = Yuv420Colors();
+
+  for (size_t y = 0; y < 4; ++y) {
+    for (size_t x = 0; x < 4; ++x) {
+      EXPECT_YUV_NEAR(getYuv420Pixel(&image, x, y), colors[y][x]);
+    }
+  }
+}
+
+TEST_F(RecoveryMapMathTest, GetP010Pixel) {
+  jpegr_uncompressed_struct image = P010Image();
+  Color (*colors)[4] = P010Colors();
+
+  for (size_t y = 0; y < 4; ++y) {
+    for (size_t x = 0; x < 4; ++x) {
+      EXPECT_YUV_NEAR(getP010Pixel(&image, x, y), colors[y][x]);
+    }
+  }
+}
+
+TEST_F(RecoveryMapMathTest, SampleYuv420) {
+  jpegr_uncompressed_struct image = Yuv420Image();
+  Color (*colors)[4] = Yuv420Colors();
+
+  static const size_t kMapScaleFactor = 2;
+  for (size_t y = 0; y < 4 / kMapScaleFactor; ++y) {
+    for (size_t x = 0; x < 4 / kMapScaleFactor; ++x) {
+      Color min = {{{ 1.0f, 1.0f, 1.0f }}};
+      Color max = {{{ -1.0f, -1.0f, -1.0f }}};
+
+      for (size_t dy = 0; dy < kMapScaleFactor; ++dy) {
+        for (size_t dx = 0; dx < kMapScaleFactor; ++dx) {
+          Color e = colors[y * kMapScaleFactor + dy][x * kMapScaleFactor + dx];
+          min = ColorMin(min, e);
+          max = ColorMax(max, e);
+        }
+      }
+
+      // Instead of reimplementing the sampling algorithm, confirm that the
+      // sample output is within the range of the min and max of the nearest
+      // points.
+      EXPECT_YUV_BETWEEN(sampleYuv420(&image, kMapScaleFactor, x, y), min, max);
+    }
+  }
+}
+
+TEST_F(RecoveryMapMathTest, SampleP010) {
+  jpegr_uncompressed_struct image = P010Image();
+  Color (*colors)[4] = P010Colors();
+
+  static const size_t kMapScaleFactor = 2;
+  for (size_t y = 0; y < 4 / kMapScaleFactor; ++y) {
+    for (size_t x = 0; x < 4 / kMapScaleFactor; ++x) {
+      Color min = {{{ 1.0f, 1.0f, 1.0f }}};
+      Color max = {{{ -1.0f, -1.0f, -1.0f }}};
+
+      for (size_t dy = 0; dy < kMapScaleFactor; ++dy) {
+        for (size_t dx = 0; dx < kMapScaleFactor; ++dx) {
+          Color e = colors[y * kMapScaleFactor + dy][x * kMapScaleFactor + dx];
+          min = ColorMin(min, e);
+          max = ColorMax(max, e);
+        }
+      }
+
+      // Instead of reimplementing the sampling algorithm, confirm that the
+      // sample output is within the range of the min and max of the nearest
+      // points.
+      EXPECT_YUV_BETWEEN(sampleP010(&image, kMapScaleFactor, x, y), min, max);
+    }
+  }
+}
+
+TEST_F(RecoveryMapMathTest, SampleMap) {
+  jpegr_uncompressed_struct image = MapImage();
+  float (*values)[4] = MapValues();
+
+  static const size_t kMapScaleFactor = 2;
+  for (size_t y = 0; y < 4 * kMapScaleFactor; ++y) {
+    for (size_t x = 0; x < 4 * kMapScaleFactor; ++x) {
+      size_t x_base = x / kMapScaleFactor;
+      size_t y_base = y / kMapScaleFactor;
+
+      float min = 1.0f;
+      float max = -1.0f;
+
+      min = fmin(min, values[y_base][x_base]);
+      max = fmax(max, values[y_base][x_base]);
+      if (y_base + 1 < 4) {
+        min = fmin(min, values[y_base + 1][x_base]);
+        max = fmax(max, values[y_base + 1][x_base]);
+      }
+      if (x_base + 1 < 4) {
+        min = fmin(min, values[y_base][x_base + 1]);
+        max = fmax(max, values[y_base][x_base + 1]);
+      }
+      if (y_base + 1 < 4 && x_base + 1 < 4) {
+        min = fmin(min, values[y_base + 1][x_base + 1]);
+        max = fmax(max, values[y_base + 1][x_base + 1]);
+      }
+
+      // Instead of reimplementing the sampling algorithm, confirm that the
+      // sample output is within the range of the min and max of the nearest
+      // points.
+      EXPECT_THAT(sampleMap(&image, kMapScaleFactor, x, y),
+                  testing::AllOf(testing::Ge(min), testing::Le(max)));
+    }
+  }
+}
+
+TEST_F(RecoveryMapMathTest, ColorToRgba1010102) {
+  EXPECT_EQ(colorToRgba1010102(RgbBlack()), 0x3 << 30);
+  EXPECT_EQ(colorToRgba1010102(RgbWhite()), 0xFFFFFFFF);
+  EXPECT_EQ(colorToRgba1010102(RgbRed()), 0x3 << 30 | 0x3ff);
+  EXPECT_EQ(colorToRgba1010102(RgbGreen()), 0x3 << 30 | 0x3ff << 10);
+  EXPECT_EQ(colorToRgba1010102(RgbBlue()), 0x3 << 30 | 0x3ff << 20);
+
+  Color e_gamma = {{{ 0.1f, 0.2f, 0.3f }}};
+  EXPECT_EQ(colorToRgba1010102(e_gamma),
+            0x3 << 30
+          | static_cast<uint32_t>(0.1f * static_cast<float>(0x3ff))
+          | static_cast<uint32_t>(0.2f * static_cast<float>(0x3ff)) << 10
+          | static_cast<uint32_t>(0.3f * static_cast<float>(0x3ff)) << 20);
+}
+
+TEST_F(RecoveryMapMathTest, GenerateMapLuminanceSrgb) {
+  EXPECT_FLOAT_EQ(SrgbYuvToLuminance(YuvBlack(), srgbLuminance),
+                  0.0f);
+  EXPECT_FLOAT_EQ(SrgbYuvToLuminance(YuvWhite(), srgbLuminance),
+                  kSdrWhiteNits);
+  EXPECT_NEAR(SrgbYuvToLuminance(SrgbYuvRed(), srgbLuminance),
+              srgbLuminance(RgbRed()) * kSdrWhiteNits, LuminanceEpsilon());
+  EXPECT_NEAR(SrgbYuvToLuminance(SrgbYuvGreen(), srgbLuminance),
+              srgbLuminance(RgbGreen()) * kSdrWhiteNits, LuminanceEpsilon());
+  EXPECT_NEAR(SrgbYuvToLuminance(SrgbYuvBlue(), srgbLuminance),
+              srgbLuminance(RgbBlue()) * kSdrWhiteNits, LuminanceEpsilon());
+}
+
+TEST_F(RecoveryMapMathTest, GenerateMapLuminanceSrgbP3) {
+  EXPECT_FLOAT_EQ(SrgbYuvToLuminance(YuvBlack(), p3Luminance),
+                  0.0f);
+  EXPECT_FLOAT_EQ(SrgbYuvToLuminance(YuvWhite(), p3Luminance),
+                  kSdrWhiteNits);
+  EXPECT_NEAR(SrgbYuvToLuminance(SrgbYuvRed(), p3Luminance),
+              p3Luminance(RgbRed()) * kSdrWhiteNits, LuminanceEpsilon());
+  EXPECT_NEAR(SrgbYuvToLuminance(SrgbYuvGreen(), p3Luminance),
+              p3Luminance(RgbGreen()) * kSdrWhiteNits, LuminanceEpsilon());
+  EXPECT_NEAR(SrgbYuvToLuminance(SrgbYuvBlue(), p3Luminance),
+              p3Luminance(RgbBlue()) * kSdrWhiteNits, LuminanceEpsilon());
+}
+
+TEST_F(RecoveryMapMathTest, GenerateMapLuminanceSrgbBt2100) {
+  EXPECT_FLOAT_EQ(SrgbYuvToLuminance(YuvBlack(), bt2100Luminance),
+                  0.0f);
+  EXPECT_FLOAT_EQ(SrgbYuvToLuminance(YuvWhite(), bt2100Luminance),
+                  kSdrWhiteNits);
+  EXPECT_NEAR(SrgbYuvToLuminance(SrgbYuvRed(), bt2100Luminance),
+              bt2100Luminance(RgbRed()) * kSdrWhiteNits, LuminanceEpsilon());
+  EXPECT_NEAR(SrgbYuvToLuminance(SrgbYuvGreen(), bt2100Luminance),
+              bt2100Luminance(RgbGreen()) * kSdrWhiteNits, LuminanceEpsilon());
+  EXPECT_NEAR(SrgbYuvToLuminance(SrgbYuvBlue(), bt2100Luminance),
+              bt2100Luminance(RgbBlue()) * kSdrWhiteNits, LuminanceEpsilon());
+}
+
+TEST_F(RecoveryMapMathTest, GenerateMapLuminanceHlg) {
+  EXPECT_FLOAT_EQ(Bt2100YuvToLuminance(YuvBlack(), hlgInvOetf, identityConversion,
+                                       bt2100Luminance, kHlgMaxNits),
+                  0.0f);
+  EXPECT_FLOAT_EQ(Bt2100YuvToLuminance(YuvWhite(), hlgInvOetf, identityConversion,
+                                       bt2100Luminance, kHlgMaxNits),
+                  kHlgMaxNits);
+  EXPECT_NEAR(Bt2100YuvToLuminance(Bt2100YuvRed(), hlgInvOetf, identityConversion,
+                                   bt2100Luminance, kHlgMaxNits),
+              bt2100Luminance(RgbRed()) * kHlgMaxNits, LuminanceEpsilon());
+  EXPECT_NEAR(Bt2100YuvToLuminance(Bt2100YuvGreen(), hlgInvOetf, identityConversion,
+                                   bt2100Luminance, kHlgMaxNits),
+              bt2100Luminance(RgbGreen()) * kHlgMaxNits, LuminanceEpsilon());
+  EXPECT_NEAR(Bt2100YuvToLuminance(Bt2100YuvBlue(), hlgInvOetf, identityConversion,
+                                   bt2100Luminance, kHlgMaxNits),
+              bt2100Luminance(RgbBlue()) * kHlgMaxNits, LuminanceEpsilon());
+}
+
+TEST_F(RecoveryMapMathTest, GenerateMapLuminancePq) {
+  EXPECT_FLOAT_EQ(Bt2100YuvToLuminance(YuvBlack(), pqInvOetf, identityConversion,
+                                       bt2100Luminance, kPqMaxNits),
+                  0.0f);
+  EXPECT_FLOAT_EQ(Bt2100YuvToLuminance(YuvWhite(), pqInvOetf, identityConversion,
+                                       bt2100Luminance, kPqMaxNits),
+                  kPqMaxNits);
+  EXPECT_NEAR(Bt2100YuvToLuminance(Bt2100YuvRed(), pqInvOetf, identityConversion,
+                                       bt2100Luminance, kPqMaxNits),
+              bt2100Luminance(RgbRed()) * kPqMaxNits, LuminanceEpsilon());
+  EXPECT_NEAR(Bt2100YuvToLuminance(Bt2100YuvGreen(), pqInvOetf, identityConversion,
+                                       bt2100Luminance, kPqMaxNits),
+              bt2100Luminance(RgbGreen()) * kPqMaxNits, LuminanceEpsilon());
+  EXPECT_NEAR(Bt2100YuvToLuminance(Bt2100YuvBlue(), pqInvOetf, identityConversion,
+                                       bt2100Luminance, kPqMaxNits),
+              bt2100Luminance(RgbBlue()) * kPqMaxNits, LuminanceEpsilon());
+}
+
+//Color Recover(Color yuv_gamma, float recovery, float range_scaling_factor) {
+TEST_F(RecoveryMapMathTest, ApplyMap) {
+  EXPECT_RGB_EQ(Recover(YuvWhite(), 1.0f, 8.0f),
+                RgbWhite() * 8.0f);
+  EXPECT_RGB_EQ(Recover(YuvBlack(), 1.0f, 8.0f),
+                RgbBlack());
+  EXPECT_RGB_CLOSE(Recover(SrgbYuvRed(), 1.0f, 8.0f),
+                  RgbRed() * 8.0f);
+  EXPECT_RGB_CLOSE(Recover(SrgbYuvGreen(), 1.0f, 8.0f),
+                  RgbGreen() * 8.0f);
+  EXPECT_RGB_CLOSE(Recover(SrgbYuvBlue(), 1.0f, 8.0f),
+                  RgbBlue() * 8.0f);
+
+  EXPECT_RGB_EQ(Recover(YuvWhite(), 0.5f, 8.0f),
+                RgbWhite() * sqrt(8.0f));
+  EXPECT_RGB_EQ(Recover(YuvBlack(), 0.5f, 8.0f),
+                RgbBlack());
+  EXPECT_RGB_CLOSE(Recover(SrgbYuvRed(), 0.5f, 8.0f),
+                  RgbRed() * sqrt(8.0f));
+  EXPECT_RGB_CLOSE(Recover(SrgbYuvGreen(), 0.5f, 8.0f),
+                  RgbGreen() * sqrt(8.0f));
+  EXPECT_RGB_CLOSE(Recover(SrgbYuvBlue(), 0.5f, 8.0f),
+                  RgbBlue() * sqrt(8.0f));
+
+  EXPECT_RGB_EQ(Recover(YuvWhite(), 0.0f, 8.0f),
+                RgbWhite());
+  EXPECT_RGB_EQ(Recover(YuvBlack(), 0.0f, 8.0f),
+                RgbBlack());
+  EXPECT_RGB_CLOSE(Recover(SrgbYuvRed(), 0.0f, 8.0f),
+                  RgbRed());
+  EXPECT_RGB_CLOSE(Recover(SrgbYuvGreen(), 0.0f, 8.0f),
+                  RgbGreen());
+  EXPECT_RGB_CLOSE(Recover(SrgbYuvBlue(), 0.0f, 8.0f),
+                  RgbBlue());
+
+  EXPECT_RGB_EQ(Recover(YuvWhite(), -0.5f, 8.0f),
+                RgbWhite() / sqrt(8.0f));
+  EXPECT_RGB_EQ(Recover(YuvBlack(), -0.5f, 8.0f),
+                RgbBlack());
+  EXPECT_RGB_CLOSE(Recover(SrgbYuvRed(), -0.5f, 8.0f),
+                  RgbRed() / sqrt(8.0f));
+  EXPECT_RGB_CLOSE(Recover(SrgbYuvGreen(), -0.5f, 8.0f),
+                  RgbGreen() / sqrt(8.0f));
+  EXPECT_RGB_CLOSE(Recover(SrgbYuvBlue(), -0.5f, 8.0f),
+                  RgbBlue() / sqrt(8.0f));
+
+  EXPECT_RGB_EQ(Recover(YuvWhite(), -1.0f, 8.0f),
+                RgbWhite() / 8.0f);
+  EXPECT_RGB_EQ(Recover(YuvBlack(), -1.0f, 8.0f),
+                RgbBlack());
+  EXPECT_RGB_CLOSE(Recover(SrgbYuvRed(), -1.0f, 8.0f),
+                  RgbRed() / 8.0f);
+  EXPECT_RGB_CLOSE(Recover(SrgbYuvGreen(), -1.0f, 8.0f),
+                  RgbGreen() / 8.0f);
+  EXPECT_RGB_CLOSE(Recover(SrgbYuvBlue(), -1.0f, 8.0f),
+                  RgbBlue() / 8.0f);
+}
+
+} // namespace android::recoverymap
diff --git a/libs/nativedisplay/AChoreographer.cpp b/libs/nativedisplay/AChoreographer.cpp
index 539cbaa..e64165f 100644
--- a/libs/nativedisplay/AChoreographer.cpp
+++ b/libs/nativedisplay/AChoreographer.cpp
@@ -14,13 +14,10 @@
  * limitations under the License.
  */
 
-#define LOG_TAG "Choreographer"
-//#define LOG_NDEBUG 0
-
 #include <android-base/thread_annotations.h>
 #include <android/gui/ISurfaceComposer.h>
-#include <gui/DisplayEventDispatcher.h>
 #include <jni.h>
+#include <nativedisplay/Choreographer.h>
 #include <private/android/choreographer.h>
 #include <utils/Looper.h>
 #include <utils/Timers.h>
@@ -31,444 +28,9 @@
 #include <queue>
 #include <thread>
 
-namespace {
-struct {
-    // Global JVM that is provided by zygote
-    JavaVM* jvm = nullptr;
-    struct {
-        jclass clazz;
-        jmethodID getInstance;
-        jmethodID registerNativeChoreographerForRefreshRateCallbacks;
-        jmethodID unregisterNativeChoreographerForRefreshRateCallbacks;
-    } displayManagerGlobal;
-} gJni;
+#undef LOG_TAG
+#define LOG_TAG "AChoreographer"
 
-// Gets the JNIEnv* for this thread, and performs one-off initialization if we
-// have never retrieved a JNIEnv* pointer before.
-JNIEnv* getJniEnv() {
-    if (gJni.jvm == nullptr) {
-        ALOGW("AChoreographer: No JVM provided!");
-        return nullptr;
-    }
-
-    JNIEnv* env = nullptr;
-    if (gJni.jvm->GetEnv((void**)&env, JNI_VERSION_1_4) != JNI_OK) {
-        ALOGD("Attaching thread to JVM for AChoreographer");
-        JavaVMAttachArgs args = {JNI_VERSION_1_4, "AChoreographer_env", NULL};
-        jint attachResult = gJni.jvm->AttachCurrentThreadAsDaemon(&env, (void*)&args);
-        if (attachResult != JNI_OK) {
-            ALOGE("Unable to attach thread. Error: %d", attachResult);
-            return nullptr;
-        }
-    }
-    if (env == nullptr) {
-        ALOGW("AChoreographer: No JNI env available!");
-    }
-    return env;
-}
-
-inline const char* toString(bool value) {
-    return value ? "true" : "false";
-}
-} // namespace
-
-namespace android {
-using gui::VsyncEventData;
-
-struct FrameCallback {
-    AChoreographer_frameCallback callback;
-    AChoreographer_frameCallback64 callback64;
-    AChoreographer_vsyncCallback vsyncCallback;
-    void* data;
-    nsecs_t dueTime;
-
-    inline bool operator<(const FrameCallback& rhs) const {
-        // Note that this is intentionally flipped because we want callbacks due sooner to be at
-        // the head of the queue
-        return dueTime > rhs.dueTime;
-    }
-};
-
-struct RefreshRateCallback {
-    AChoreographer_refreshRateCallback callback;
-    void* data;
-    bool firstCallbackFired = false;
-};
-
-class Choreographer;
-
-/**
- * Implementation of AChoreographerFrameCallbackData.
- */
-struct ChoreographerFrameCallbackDataImpl {
-    int64_t frameTimeNanos{0};
-
-    VsyncEventData vsyncEventData;
-
-    const Choreographer* choreographer;
-};
-
-struct {
-    std::mutex lock;
-    std::vector<Choreographer*> ptrs GUARDED_BY(lock);
-    std::map<AVsyncId, int64_t> startTimes GUARDED_BY(lock);
-    bool registeredToDisplayManager GUARDED_BY(lock) = false;
-
-    std::atomic<nsecs_t> mLastKnownVsync = -1;
-} gChoreographers;
-
-class Choreographer : public DisplayEventDispatcher, public MessageHandler {
-public:
-    explicit Choreographer(const sp<Looper>& looper) EXCLUDES(gChoreographers.lock);
-    void postFrameCallbackDelayed(AChoreographer_frameCallback cb,
-                                  AChoreographer_frameCallback64 cb64,
-                                  AChoreographer_vsyncCallback vsyncCallback, void* data,
-                                  nsecs_t delay);
-    void registerRefreshRateCallback(AChoreographer_refreshRateCallback cb, void* data)
-            EXCLUDES(gChoreographers.lock);
-    void unregisterRefreshRateCallback(AChoreographer_refreshRateCallback cb, void* data);
-    // Drains the queue of pending vsync periods and dispatches refresh rate
-    // updates to callbacks.
-    // The assumption is that this method is only called on a single
-    // processing thread, either by looper or by AChoreographer_handleEvents
-    void handleRefreshRateUpdates();
-    void scheduleLatestConfigRequest();
-
-    enum {
-        MSG_SCHEDULE_CALLBACKS = 0,
-        MSG_SCHEDULE_VSYNC = 1,
-        MSG_HANDLE_REFRESH_RATE_UPDATES = 2,
-    };
-    virtual void handleMessage(const Message& message) override;
-
-    static Choreographer* getForThread();
-    virtual ~Choreographer() override EXCLUDES(gChoreographers.lock);
-    int64_t getFrameInterval() const;
-    bool inCallback() const;
-
-private:
-    Choreographer(const Choreographer&) = delete;
-
-    void dispatchVsync(nsecs_t timestamp, PhysicalDisplayId displayId, uint32_t count,
-                       VsyncEventData vsyncEventData) override;
-    void dispatchHotplug(nsecs_t timestamp, PhysicalDisplayId displayId, bool connected) override;
-    void dispatchModeChanged(nsecs_t timestamp, PhysicalDisplayId displayId, int32_t modeId,
-                             nsecs_t vsyncPeriod) override;
-    void dispatchNullEvent(nsecs_t, PhysicalDisplayId) override;
-    void dispatchFrameRateOverrides(nsecs_t timestamp, PhysicalDisplayId displayId,
-                                    std::vector<FrameRateOverride> overrides) override;
-
-    void scheduleCallbacks();
-
-    ChoreographerFrameCallbackDataImpl createFrameCallbackData(nsecs_t timestamp) const;
-    void registerStartTime() const;
-
-    std::mutex mLock;
-    // Protected by mLock
-    std::priority_queue<FrameCallback> mFrameCallbacks;
-    std::vector<RefreshRateCallback> mRefreshRateCallbacks;
-
-    nsecs_t mLatestVsyncPeriod = -1;
-    VsyncEventData mLastVsyncEventData;
-    bool mInCallback = false;
-
-    const sp<Looper> mLooper;
-    const std::thread::id mThreadId;
-
-    // Approximation of num_threads_using_choreographer * num_frames_of_history with leeway.
-    static constexpr size_t kMaxStartTimes = 250;
-};
-
-static thread_local Choreographer* gChoreographer;
-Choreographer* Choreographer::getForThread() {
-    if (gChoreographer == nullptr) {
-        sp<Looper> looper = Looper::getForThread();
-        if (!looper.get()) {
-            ALOGW("No looper prepared for thread");
-            return nullptr;
-        }
-        gChoreographer = new Choreographer(looper);
-        status_t result = gChoreographer->initialize();
-        if (result != OK) {
-            ALOGW("Failed to initialize");
-            return nullptr;
-        }
-    }
-    return gChoreographer;
-}
-
-Choreographer::Choreographer(const sp<Looper>& looper)
-      : DisplayEventDispatcher(looper, gui::ISurfaceComposer::VsyncSource::eVsyncSourceApp),
-        mLooper(looper),
-        mThreadId(std::this_thread::get_id()) {
-    std::lock_guard<std::mutex> _l(gChoreographers.lock);
-    gChoreographers.ptrs.push_back(this);
-}
-
-Choreographer::~Choreographer() {
-    std::lock_guard<std::mutex> _l(gChoreographers.lock);
-    gChoreographers.ptrs.erase(std::remove_if(gChoreographers.ptrs.begin(),
-                                              gChoreographers.ptrs.end(),
-                                              [=](Choreographer* c) { return c == this; }),
-                               gChoreographers.ptrs.end());
-    // Only poke DisplayManagerGlobal to unregister if we previously registered
-    // callbacks.
-    if (gChoreographers.ptrs.empty() && gChoreographers.registeredToDisplayManager) {
-        gChoreographers.registeredToDisplayManager = false;
-        JNIEnv* env = getJniEnv();
-        if (env == nullptr) {
-            ALOGW("JNI environment is unavailable, skipping choreographer cleanup");
-            return;
-        }
-        jobject dmg = env->CallStaticObjectMethod(gJni.displayManagerGlobal.clazz,
-                                                  gJni.displayManagerGlobal.getInstance);
-        if (dmg == nullptr) {
-            ALOGW("DMS is not initialized yet, skipping choreographer cleanup");
-        } else {
-            env->CallVoidMethod(dmg,
-                                gJni.displayManagerGlobal
-                                        .unregisterNativeChoreographerForRefreshRateCallbacks);
-            env->DeleteLocalRef(dmg);
-        }
-    }
-}
-
-void Choreographer::postFrameCallbackDelayed(AChoreographer_frameCallback cb,
-                                             AChoreographer_frameCallback64 cb64,
-                                             AChoreographer_vsyncCallback vsyncCallback, void* data,
-                                             nsecs_t delay) {
-    nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
-    FrameCallback callback{cb, cb64, vsyncCallback, data, now + delay};
-    {
-        std::lock_guard<std::mutex> _l{mLock};
-        mFrameCallbacks.push(callback);
-    }
-    if (callback.dueTime <= now) {
-        if (std::this_thread::get_id() != mThreadId) {
-            if (mLooper != nullptr) {
-                Message m{MSG_SCHEDULE_VSYNC};
-                mLooper->sendMessage(this, m);
-            } else {
-                scheduleVsync();
-            }
-        } else {
-            scheduleVsync();
-        }
-    } else {
-        if (mLooper != nullptr) {
-            Message m{MSG_SCHEDULE_CALLBACKS};
-            mLooper->sendMessageDelayed(delay, this, m);
-        } else {
-            scheduleCallbacks();
-        }
-    }
-}
-
-void Choreographer::registerRefreshRateCallback(AChoreographer_refreshRateCallback cb, void* data) {
-    std::lock_guard<std::mutex> _l{mLock};
-    for (const auto& callback : mRefreshRateCallbacks) {
-        // Don't re-add callbacks.
-        if (cb == callback.callback && data == callback.data) {
-            return;
-        }
-    }
-    mRefreshRateCallbacks.emplace_back(
-            RefreshRateCallback{.callback = cb, .data = data, .firstCallbackFired = false});
-    bool needsRegistration = false;
-    {
-        std::lock_guard<std::mutex> _l2(gChoreographers.lock);
-        needsRegistration = !gChoreographers.registeredToDisplayManager;
-    }
-    if (needsRegistration) {
-        JNIEnv* env = getJniEnv();
-        if (env == nullptr) {
-            ALOGW("JNI environment is unavailable, skipping registration");
-            return;
-        }
-        jobject dmg = env->CallStaticObjectMethod(gJni.displayManagerGlobal.clazz,
-                                                  gJni.displayManagerGlobal.getInstance);
-        if (dmg == nullptr) {
-            ALOGW("DMS is not initialized yet: skipping registration");
-            return;
-        } else {
-            env->CallVoidMethod(dmg,
-                                gJni.displayManagerGlobal
-                                        .registerNativeChoreographerForRefreshRateCallbacks,
-                                reinterpret_cast<int64_t>(this));
-            env->DeleteLocalRef(dmg);
-            {
-                std::lock_guard<std::mutex> _l2(gChoreographers.lock);
-                gChoreographers.registeredToDisplayManager = true;
-            }
-        }
-    } else {
-        scheduleLatestConfigRequest();
-    }
-}
-
-void Choreographer::unregisterRefreshRateCallback(AChoreographer_refreshRateCallback cb,
-                                                  void* data) {
-    std::lock_guard<std::mutex> _l{mLock};
-    mRefreshRateCallbacks.erase(std::remove_if(mRefreshRateCallbacks.begin(),
-                                               mRefreshRateCallbacks.end(),
-                                               [&](const RefreshRateCallback& callback) {
-                                                   return cb == callback.callback &&
-                                                           data == callback.data;
-                                               }),
-                                mRefreshRateCallbacks.end());
-}
-
-void Choreographer::scheduleLatestConfigRequest() {
-    if (mLooper != nullptr) {
-        Message m{MSG_HANDLE_REFRESH_RATE_UPDATES};
-        mLooper->sendMessage(this, m);
-    } else {
-        // If the looper thread is detached from Choreographer, then refresh rate
-        // changes will be handled in AChoreographer_handlePendingEvents, so we
-        // need to wake up the looper thread by writing to the write-end of the
-        // socket the looper is listening on.
-        // Fortunately, these events are small so sending packets across the
-        // socket should be atomic across processes.
-        DisplayEventReceiver::Event event;
-        event.header =
-                DisplayEventReceiver::Event::Header{DisplayEventReceiver::DISPLAY_EVENT_NULL,
-                                                    PhysicalDisplayId::fromPort(0), systemTime()};
-        injectEvent(event);
-    }
-}
-
-void Choreographer::scheduleCallbacks() {
-    const nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
-    nsecs_t dueTime;
-    {
-        std::lock_guard<std::mutex> _l{mLock};
-        // If there are no pending callbacks then don't schedule a vsync
-        if (mFrameCallbacks.empty()) {
-            return;
-        }
-        dueTime = mFrameCallbacks.top().dueTime;
-    }
-
-    if (dueTime <= now) {
-        ALOGV("choreographer %p ~ scheduling vsync", this);
-        scheduleVsync();
-        return;
-    }
-}
-
-void Choreographer::handleRefreshRateUpdates() {
-    std::vector<RefreshRateCallback> callbacks{};
-    const nsecs_t pendingPeriod = gChoreographers.mLastKnownVsync.load();
-    const nsecs_t lastPeriod = mLatestVsyncPeriod;
-    if (pendingPeriod > 0) {
-        mLatestVsyncPeriod = pendingPeriod;
-    }
-    {
-        std::lock_guard<std::mutex> _l{mLock};
-        for (auto& cb : mRefreshRateCallbacks) {
-            callbacks.push_back(cb);
-            cb.firstCallbackFired = true;
-        }
-    }
-
-    for (auto& cb : callbacks) {
-        if (!cb.firstCallbackFired || (pendingPeriod > 0 && pendingPeriod != lastPeriod)) {
-            cb.callback(pendingPeriod, cb.data);
-        }
-    }
-}
-
-// TODO(b/74619554): The PhysicalDisplayId is ignored because SF only emits VSYNC events for the
-// internal display and DisplayEventReceiver::requestNextVsync only allows requesting VSYNC for
-// the internal display implicitly.
-void Choreographer::dispatchVsync(nsecs_t timestamp, PhysicalDisplayId, uint32_t,
-                                  VsyncEventData vsyncEventData) {
-    std::vector<FrameCallback> callbacks{};
-    {
-        std::lock_guard<std::mutex> _l{mLock};
-        nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
-        while (!mFrameCallbacks.empty() && mFrameCallbacks.top().dueTime < now) {
-            callbacks.push_back(mFrameCallbacks.top());
-            mFrameCallbacks.pop();
-        }
-    }
-    mLastVsyncEventData = vsyncEventData;
-    for (const auto& cb : callbacks) {
-        if (cb.vsyncCallback != nullptr) {
-            const ChoreographerFrameCallbackDataImpl frameCallbackData =
-                    createFrameCallbackData(timestamp);
-            registerStartTime();
-            mInCallback = true;
-            cb.vsyncCallback(reinterpret_cast<const AChoreographerFrameCallbackData*>(
-                                     &frameCallbackData),
-                             cb.data);
-            mInCallback = false;
-        } else if (cb.callback64 != nullptr) {
-            cb.callback64(timestamp, cb.data);
-        } else if (cb.callback != nullptr) {
-            cb.callback(timestamp, cb.data);
-        }
-    }
-}
-
-void Choreographer::dispatchHotplug(nsecs_t, PhysicalDisplayId displayId, bool connected) {
-    ALOGV("choreographer %p ~ received hotplug event (displayId=%s, connected=%s), ignoring.", this,
-          to_string(displayId).c_str(), toString(connected));
-}
-
-void Choreographer::dispatchModeChanged(nsecs_t, PhysicalDisplayId, int32_t, nsecs_t) {
-    LOG_ALWAYS_FATAL("dispatchModeChanged was called but was never registered");
-}
-
-void Choreographer::dispatchFrameRateOverrides(nsecs_t, PhysicalDisplayId,
-                                               std::vector<FrameRateOverride>) {
-    LOG_ALWAYS_FATAL("dispatchFrameRateOverrides was called but was never registered");
-}
-
-void Choreographer::dispatchNullEvent(nsecs_t, PhysicalDisplayId) {
-    ALOGV("choreographer %p ~ received null event.", this);
-    handleRefreshRateUpdates();
-}
-
-void Choreographer::handleMessage(const Message& message) {
-    switch (message.what) {
-        case MSG_SCHEDULE_CALLBACKS:
-            scheduleCallbacks();
-            break;
-        case MSG_SCHEDULE_VSYNC:
-            scheduleVsync();
-            break;
-        case MSG_HANDLE_REFRESH_RATE_UPDATES:
-            handleRefreshRateUpdates();
-            break;
-    }
-}
-
-int64_t Choreographer::getFrameInterval() const {
-    return mLastVsyncEventData.frameInterval;
-}
-
-bool Choreographer::inCallback() const {
-    return mInCallback;
-}
-
-ChoreographerFrameCallbackDataImpl Choreographer::createFrameCallbackData(nsecs_t timestamp) const {
-    return {.frameTimeNanos = timestamp,
-            .vsyncEventData = mLastVsyncEventData,
-            .choreographer = this};
-}
-
-void Choreographer::registerStartTime() const {
-    std::scoped_lock _l(gChoreographers.lock);
-    for (VsyncEventData::FrameTimeline frameTimeline : mLastVsyncEventData.frameTimelines) {
-        while (gChoreographers.startTimes.size() >= kMaxStartTimes) {
-            gChoreographers.startTimes.erase(gChoreographers.startTimes.begin());
-        }
-        gChoreographers.startTimes[frameTimeline.vsyncId] = systemTime(SYSTEM_TIME_MONOTONIC);
-    }
-}
-
-} // namespace android
 using namespace android;
 
 static inline Choreographer* AChoreographer_to_Choreographer(AChoreographer* choreographer) {
@@ -488,27 +50,12 @@
 
 // Glue for private C api
 namespace android {
-void AChoreographer_signalRefreshRateCallbacks(nsecs_t vsyncPeriod) EXCLUDES(gChoreographers.lock) {
-    std::lock_guard<std::mutex> _l(gChoreographers.lock);
-    gChoreographers.mLastKnownVsync.store(vsyncPeriod);
-    for (auto c : gChoreographers.ptrs) {
-        c->scheduleLatestConfigRequest();
-    }
+void AChoreographer_signalRefreshRateCallbacks(nsecs_t vsyncPeriod) {
+    Choreographer::signalRefreshRateCallbacks(vsyncPeriod);
 }
 
 void AChoreographer_initJVM(JNIEnv* env) {
-    env->GetJavaVM(&gJni.jvm);
-    // Now we need to find the java classes.
-    jclass dmgClass = env->FindClass("android/hardware/display/DisplayManagerGlobal");
-    gJni.displayManagerGlobal.clazz = static_cast<jclass>(env->NewGlobalRef(dmgClass));
-    gJni.displayManagerGlobal.getInstance =
-            env->GetStaticMethodID(dmgClass, "getInstance",
-                                   "()Landroid/hardware/display/DisplayManagerGlobal;");
-    gJni.displayManagerGlobal.registerNativeChoreographerForRefreshRateCallbacks =
-            env->GetMethodID(dmgClass, "registerNativeChoreographerForRefreshRateCallbacks", "()V");
-    gJni.displayManagerGlobal.unregisterNativeChoreographerForRefreshRateCallbacks =
-            env->GetMethodID(dmgClass, "unregisterNativeChoreographerForRefreshRateCallbacks",
-                             "()V");
+    Choreographer::initJVM(env);
 }
 
 AChoreographer* AChoreographer_routeGetInstance() {
@@ -583,13 +130,7 @@
 }
 
 int64_t AChoreographer_getStartTimeNanosForVsyncId(AVsyncId vsyncId) {
-    std::scoped_lock _l(gChoreographers.lock);
-    const auto iter = gChoreographers.startTimes.find(vsyncId);
-    if (iter == gChoreographers.startTimes.end()) {
-        ALOGW("Start time was not found for vsync id: %" PRId64, vsyncId);
-        return 0;
-    }
-    return iter->second;
+    return Choreographer::getStartTimeNanosForVsyncId(vsyncId);
 }
 
 } // namespace android
diff --git a/libs/nativedisplay/Android.bp b/libs/nativedisplay/Android.bp
index 8d8a2bc..70de33d 100644
--- a/libs/nativedisplay/Android.bp
+++ b/libs/nativedisplay/Android.bp
@@ -56,6 +56,7 @@
         ":libgui_frame_event_aidl",
         "AChoreographer.cpp",
         "ADisplay.cpp",
+        "Choreographer.cpp",
         "surfacetexture/surface_texture.cpp",
         "surfacetexture/SurfaceTexture.cpp",
         "surfacetexture/ImageConsumer.cpp",
diff --git a/libs/nativedisplay/Choreographer.cpp b/libs/nativedisplay/Choreographer.cpp
new file mode 100644
index 0000000..01e9f04
--- /dev/null
+++ b/libs/nativedisplay/Choreographer.cpp
@@ -0,0 +1,390 @@
+/*
+ * 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.
+ */
+
+// #define LOG_NDEBUG 0
+
+#include <jni.h>
+#include <nativedisplay/Choreographer.h>
+
+#undef LOG_TAG
+#define LOG_TAG "AChoreographer"
+
+namespace {
+struct {
+    // Global JVM that is provided by zygote
+    JavaVM* jvm = nullptr;
+    struct {
+        jclass clazz;
+        jmethodID getInstance;
+        jmethodID registerNativeChoreographerForRefreshRateCallbacks;
+        jmethodID unregisterNativeChoreographerForRefreshRateCallbacks;
+    } displayManagerGlobal;
+} gJni;
+
+// Gets the JNIEnv* for this thread, and performs one-off initialization if we
+// have never retrieved a JNIEnv* pointer before.
+JNIEnv* getJniEnv() {
+    if (gJni.jvm == nullptr) {
+        ALOGW("AChoreographer: No JVM provided!");
+        return nullptr;
+    }
+
+    JNIEnv* env = nullptr;
+    if (gJni.jvm->GetEnv((void**)&env, JNI_VERSION_1_4) != JNI_OK) {
+        ALOGD("Attaching thread to JVM for AChoreographer");
+        JavaVMAttachArgs args = {JNI_VERSION_1_4, "AChoreographer_env", NULL};
+        jint attachResult = gJni.jvm->AttachCurrentThreadAsDaemon(&env, (void*)&args);
+        if (attachResult != JNI_OK) {
+            ALOGE("Unable to attach thread. Error: %d", attachResult);
+            return nullptr;
+        }
+    }
+    if (env == nullptr) {
+        ALOGW("AChoreographer: No JNI env available!");
+    }
+    return env;
+}
+
+inline const char* toString(bool value) {
+    return value ? "true" : "false";
+}
+} // namespace
+
+namespace android {
+
+Choreographer::Context Choreographer::gChoreographers;
+
+static thread_local Choreographer* gChoreographer;
+
+void Choreographer::initJVM(JNIEnv* env) {
+    env->GetJavaVM(&gJni.jvm);
+    // Now we need to find the java classes.
+    jclass dmgClass = env->FindClass("android/hardware/display/DisplayManagerGlobal");
+    gJni.displayManagerGlobal.clazz = static_cast<jclass>(env->NewGlobalRef(dmgClass));
+    gJni.displayManagerGlobal.getInstance =
+            env->GetStaticMethodID(dmgClass, "getInstance",
+                                   "()Landroid/hardware/display/DisplayManagerGlobal;");
+    gJni.displayManagerGlobal.registerNativeChoreographerForRefreshRateCallbacks =
+            env->GetMethodID(dmgClass, "registerNativeChoreographerForRefreshRateCallbacks", "()V");
+    gJni.displayManagerGlobal.unregisterNativeChoreographerForRefreshRateCallbacks =
+            env->GetMethodID(dmgClass, "unregisterNativeChoreographerForRefreshRateCallbacks",
+                             "()V");
+}
+
+Choreographer* Choreographer::getForThread() {
+    if (gChoreographer == nullptr) {
+        sp<Looper> looper = Looper::getForThread();
+        if (!looper.get()) {
+            ALOGW("No looper prepared for thread");
+            return nullptr;
+        }
+        gChoreographer = new Choreographer(looper);
+        status_t result = gChoreographer->initialize();
+        if (result != OK) {
+            ALOGW("Failed to initialize");
+            return nullptr;
+        }
+    }
+    return gChoreographer;
+}
+
+Choreographer::Choreographer(const sp<Looper>& looper)
+      : DisplayEventDispatcher(looper, gui::ISurfaceComposer::VsyncSource::eVsyncSourceApp),
+        mLooper(looper),
+        mThreadId(std::this_thread::get_id()) {
+    std::lock_guard<std::mutex> _l(gChoreographers.lock);
+    gChoreographers.ptrs.push_back(this);
+}
+
+Choreographer::~Choreographer() {
+    std::lock_guard<std::mutex> _l(gChoreographers.lock);
+    gChoreographers.ptrs.erase(std::remove_if(gChoreographers.ptrs.begin(),
+                                              gChoreographers.ptrs.end(),
+                                              [=](Choreographer* c) { return c == this; }),
+                               gChoreographers.ptrs.end());
+    // Only poke DisplayManagerGlobal to unregister if we previously registered
+    // callbacks.
+    if (gChoreographers.ptrs.empty() && gChoreographers.registeredToDisplayManager) {
+        gChoreographers.registeredToDisplayManager = false;
+        JNIEnv* env = getJniEnv();
+        if (env == nullptr) {
+            ALOGW("JNI environment is unavailable, skipping choreographer cleanup");
+            return;
+        }
+        jobject dmg = env->CallStaticObjectMethod(gJni.displayManagerGlobal.clazz,
+                                                  gJni.displayManagerGlobal.getInstance);
+        if (dmg == nullptr) {
+            ALOGW("DMS is not initialized yet, skipping choreographer cleanup");
+        } else {
+            env->CallVoidMethod(dmg,
+                                gJni.displayManagerGlobal
+                                        .unregisterNativeChoreographerForRefreshRateCallbacks);
+            env->DeleteLocalRef(dmg);
+        }
+    }
+}
+
+void Choreographer::postFrameCallbackDelayed(AChoreographer_frameCallback cb,
+                                             AChoreographer_frameCallback64 cb64,
+                                             AChoreographer_vsyncCallback vsyncCallback, void* data,
+                                             nsecs_t delay) {
+    nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
+    FrameCallback callback{cb, cb64, vsyncCallback, data, now + delay};
+    {
+        std::lock_guard<std::mutex> _l{mLock};
+        mFrameCallbacks.push(callback);
+    }
+    if (callback.dueTime <= now) {
+        if (std::this_thread::get_id() != mThreadId) {
+            if (mLooper != nullptr) {
+                Message m{MSG_SCHEDULE_VSYNC};
+                mLooper->sendMessage(this, m);
+            } else {
+                scheduleVsync();
+            }
+        } else {
+            scheduleVsync();
+        }
+    } else {
+        if (mLooper != nullptr) {
+            Message m{MSG_SCHEDULE_CALLBACKS};
+            mLooper->sendMessageDelayed(delay, this, m);
+        } else {
+            scheduleCallbacks();
+        }
+    }
+}
+
+void Choreographer::registerRefreshRateCallback(AChoreographer_refreshRateCallback cb, void* data) {
+    std::lock_guard<std::mutex> _l{mLock};
+    for (const auto& callback : mRefreshRateCallbacks) {
+        // Don't re-add callbacks.
+        if (cb == callback.callback && data == callback.data) {
+            return;
+        }
+    }
+    mRefreshRateCallbacks.emplace_back(
+            RefreshRateCallback{.callback = cb, .data = data, .firstCallbackFired = false});
+    bool needsRegistration = false;
+    {
+        std::lock_guard<std::mutex> _l2(gChoreographers.lock);
+        needsRegistration = !gChoreographers.registeredToDisplayManager;
+    }
+    if (needsRegistration) {
+        JNIEnv* env = getJniEnv();
+        if (env == nullptr) {
+            ALOGW("JNI environment is unavailable, skipping registration");
+            return;
+        }
+        jobject dmg = env->CallStaticObjectMethod(gJni.displayManagerGlobal.clazz,
+                                                  gJni.displayManagerGlobal.getInstance);
+        if (dmg == nullptr) {
+            ALOGW("DMS is not initialized yet: skipping registration");
+            return;
+        } else {
+            env->CallVoidMethod(dmg,
+                                gJni.displayManagerGlobal
+                                        .registerNativeChoreographerForRefreshRateCallbacks,
+                                reinterpret_cast<int64_t>(this));
+            env->DeleteLocalRef(dmg);
+            {
+                std::lock_guard<std::mutex> _l2(gChoreographers.lock);
+                gChoreographers.registeredToDisplayManager = true;
+            }
+        }
+    } else {
+        scheduleLatestConfigRequest();
+    }
+}
+
+void Choreographer::unregisterRefreshRateCallback(AChoreographer_refreshRateCallback cb,
+                                                  void* data) {
+    std::lock_guard<std::mutex> _l{mLock};
+    mRefreshRateCallbacks.erase(std::remove_if(mRefreshRateCallbacks.begin(),
+                                               mRefreshRateCallbacks.end(),
+                                               [&](const RefreshRateCallback& callback) {
+                                                   return cb == callback.callback &&
+                                                           data == callback.data;
+                                               }),
+                                mRefreshRateCallbacks.end());
+}
+
+void Choreographer::scheduleLatestConfigRequest() {
+    if (mLooper != nullptr) {
+        Message m{MSG_HANDLE_REFRESH_RATE_UPDATES};
+        mLooper->sendMessage(this, m);
+    } else {
+        // If the looper thread is detached from Choreographer, then refresh rate
+        // changes will be handled in AChoreographer_handlePendingEvents, so we
+        // need to wake up the looper thread by writing to the write-end of the
+        // socket the looper is listening on.
+        // Fortunately, these events are small so sending packets across the
+        // socket should be atomic across processes.
+        DisplayEventReceiver::Event event;
+        event.header =
+                DisplayEventReceiver::Event::Header{DisplayEventReceiver::DISPLAY_EVENT_NULL,
+                                                    PhysicalDisplayId::fromPort(0), systemTime()};
+        injectEvent(event);
+    }
+}
+
+void Choreographer::scheduleCallbacks() {
+    const nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
+    nsecs_t dueTime;
+    {
+        std::lock_guard<std::mutex> _l{mLock};
+        // If there are no pending callbacks then don't schedule a vsync
+        if (mFrameCallbacks.empty()) {
+            return;
+        }
+        dueTime = mFrameCallbacks.top().dueTime;
+    }
+
+    if (dueTime <= now) {
+        ALOGV("choreographer %p ~ scheduling vsync", this);
+        scheduleVsync();
+        return;
+    }
+}
+
+void Choreographer::handleRefreshRateUpdates() {
+    std::vector<RefreshRateCallback> callbacks{};
+    const nsecs_t pendingPeriod = gChoreographers.mLastKnownVsync.load();
+    const nsecs_t lastPeriod = mLatestVsyncPeriod;
+    if (pendingPeriod > 0) {
+        mLatestVsyncPeriod = pendingPeriod;
+    }
+    {
+        std::lock_guard<std::mutex> _l{mLock};
+        for (auto& cb : mRefreshRateCallbacks) {
+            callbacks.push_back(cb);
+            cb.firstCallbackFired = true;
+        }
+    }
+
+    for (auto& cb : callbacks) {
+        if (!cb.firstCallbackFired || (pendingPeriod > 0 && pendingPeriod != lastPeriod)) {
+            cb.callback(pendingPeriod, cb.data);
+        }
+    }
+}
+
+void Choreographer::dispatchVsync(nsecs_t timestamp, PhysicalDisplayId, uint32_t,
+                                  VsyncEventData vsyncEventData) {
+    std::vector<FrameCallback> callbacks{};
+    {
+        std::lock_guard<std::mutex> _l{mLock};
+        nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
+        while (!mFrameCallbacks.empty() && mFrameCallbacks.top().dueTime < now) {
+            callbacks.push_back(mFrameCallbacks.top());
+            mFrameCallbacks.pop();
+        }
+    }
+    mLastVsyncEventData = vsyncEventData;
+    for (const auto& cb : callbacks) {
+        if (cb.vsyncCallback != nullptr) {
+            const ChoreographerFrameCallbackDataImpl frameCallbackData =
+                    createFrameCallbackData(timestamp);
+            registerStartTime();
+            mInCallback = true;
+            cb.vsyncCallback(reinterpret_cast<const AChoreographerFrameCallbackData*>(
+                                     &frameCallbackData),
+                             cb.data);
+            mInCallback = false;
+        } else if (cb.callback64 != nullptr) {
+            cb.callback64(timestamp, cb.data);
+        } else if (cb.callback != nullptr) {
+            cb.callback(timestamp, cb.data);
+        }
+    }
+}
+
+void Choreographer::dispatchHotplug(nsecs_t, PhysicalDisplayId displayId, bool connected) {
+    ALOGV("choreographer %p ~ received hotplug event (displayId=%s, connected=%s), ignoring.", this,
+          to_string(displayId).c_str(), toString(connected));
+}
+
+void Choreographer::dispatchModeChanged(nsecs_t, PhysicalDisplayId, int32_t, nsecs_t) {
+    LOG_ALWAYS_FATAL("dispatchModeChanged was called but was never registered");
+}
+
+void Choreographer::dispatchFrameRateOverrides(nsecs_t, PhysicalDisplayId,
+                                               std::vector<FrameRateOverride>) {
+    LOG_ALWAYS_FATAL("dispatchFrameRateOverrides was called but was never registered");
+}
+
+void Choreographer::dispatchNullEvent(nsecs_t, PhysicalDisplayId) {
+    ALOGV("choreographer %p ~ received null event.", this);
+    handleRefreshRateUpdates();
+}
+
+void Choreographer::handleMessage(const Message& message) {
+    switch (message.what) {
+        case MSG_SCHEDULE_CALLBACKS:
+            scheduleCallbacks();
+            break;
+        case MSG_SCHEDULE_VSYNC:
+            scheduleVsync();
+            break;
+        case MSG_HANDLE_REFRESH_RATE_UPDATES:
+            handleRefreshRateUpdates();
+            break;
+    }
+}
+
+int64_t Choreographer::getFrameInterval() const {
+    return mLastVsyncEventData.frameInterval;
+}
+
+bool Choreographer::inCallback() const {
+    return mInCallback;
+}
+
+ChoreographerFrameCallbackDataImpl Choreographer::createFrameCallbackData(nsecs_t timestamp) const {
+    return {.frameTimeNanos = timestamp,
+            .vsyncEventData = mLastVsyncEventData,
+            .choreographer = this};
+}
+
+void Choreographer::registerStartTime() const {
+    std::scoped_lock _l(gChoreographers.lock);
+    for (VsyncEventData::FrameTimeline frameTimeline : mLastVsyncEventData.frameTimelines) {
+        while (gChoreographers.startTimes.size() >= kMaxStartTimes) {
+            gChoreographers.startTimes.erase(gChoreographers.startTimes.begin());
+        }
+        gChoreographers.startTimes[frameTimeline.vsyncId] = systemTime(SYSTEM_TIME_MONOTONIC);
+    }
+}
+
+void Choreographer::signalRefreshRateCallbacks(nsecs_t vsyncPeriod) {
+    std::lock_guard<std::mutex> _l(gChoreographers.lock);
+    gChoreographers.mLastKnownVsync.store(vsyncPeriod);
+    for (auto c : gChoreographers.ptrs) {
+        c->scheduleLatestConfigRequest();
+    }
+}
+
+int64_t Choreographer::getStartTimeNanosForVsyncId(AVsyncId vsyncId) {
+    std::scoped_lock _l(gChoreographers.lock);
+    const auto iter = gChoreographers.startTimes.find(vsyncId);
+    if (iter == gChoreographers.startTimes.end()) {
+        ALOGW("Start time was not found for vsync id: %" PRId64, vsyncId);
+        return 0;
+    }
+    return iter->second;
+}
+
+} // namespace android
\ No newline at end of file
diff --git a/libs/nativedisplay/include/nativedisplay/Choreographer.h b/libs/nativedisplay/include/nativedisplay/Choreographer.h
new file mode 100644
index 0000000..bb63f29
--- /dev/null
+++ b/libs/nativedisplay/include/nativedisplay/Choreographer.h
@@ -0,0 +1,138 @@
+/*
+ * 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 <gui/DisplayEventDispatcher.h>
+#include <private/android/choreographer.h>
+#include <utils/Looper.h>
+
+#include <mutex>
+#include <queue>
+#include <thread>
+
+namespace android {
+using gui::VsyncEventData;
+
+struct FrameCallback {
+    AChoreographer_frameCallback callback;
+    AChoreographer_frameCallback64 callback64;
+    AChoreographer_vsyncCallback vsyncCallback;
+    void* data;
+    nsecs_t dueTime;
+
+    inline bool operator<(const FrameCallback& rhs) const {
+        // Note that this is intentionally flipped because we want callbacks due sooner to be at
+        // the head of the queue
+        return dueTime > rhs.dueTime;
+    }
+};
+
+struct RefreshRateCallback {
+    AChoreographer_refreshRateCallback callback;
+    void* data;
+    bool firstCallbackFired = false;
+};
+
+class Choreographer;
+
+/**
+ * Implementation of AChoreographerFrameCallbackData.
+ */
+struct ChoreographerFrameCallbackDataImpl {
+    int64_t frameTimeNanos{0};
+
+    VsyncEventData vsyncEventData;
+
+    const Choreographer* choreographer;
+};
+
+class Choreographer : public DisplayEventDispatcher, public MessageHandler {
+public:
+    struct Context {
+        std::mutex lock;
+        std::vector<Choreographer*> ptrs GUARDED_BY(lock);
+        std::map<AVsyncId, int64_t> startTimes GUARDED_BY(lock);
+        bool registeredToDisplayManager GUARDED_BY(lock) = false;
+
+        std::atomic<nsecs_t> mLastKnownVsync = -1;
+    };
+    static Context gChoreographers;
+
+    explicit Choreographer(const sp<Looper>& looper) EXCLUDES(gChoreographers.lock);
+    void postFrameCallbackDelayed(AChoreographer_frameCallback cb,
+                                  AChoreographer_frameCallback64 cb64,
+                                  AChoreographer_vsyncCallback vsyncCallback, void* data,
+                                  nsecs_t delay);
+    void registerRefreshRateCallback(AChoreographer_refreshRateCallback cb, void* data)
+            EXCLUDES(gChoreographers.lock);
+    void unregisterRefreshRateCallback(AChoreographer_refreshRateCallback cb, void* data);
+    // Drains the queue of pending vsync periods and dispatches refresh rate
+    // updates to callbacks.
+    // The assumption is that this method is only called on a single
+    // processing thread, either by looper or by AChoreographer_handleEvents
+    void handleRefreshRateUpdates();
+    void scheduleLatestConfigRequest();
+
+    enum {
+        MSG_SCHEDULE_CALLBACKS = 0,
+        MSG_SCHEDULE_VSYNC = 1,
+        MSG_HANDLE_REFRESH_RATE_UPDATES = 2,
+    };
+    virtual void handleMessage(const Message& message) override;
+
+    static void initJVM(JNIEnv* env);
+    static Choreographer* getForThread();
+    static void signalRefreshRateCallbacks(nsecs_t vsyncPeriod) EXCLUDES(gChoreographers.lock);
+    static int64_t getStartTimeNanosForVsyncId(AVsyncId vsyncId) EXCLUDES(gChoreographers.lock);
+    virtual ~Choreographer() override EXCLUDES(gChoreographers.lock);
+    int64_t getFrameInterval() const;
+    bool inCallback() const;
+
+private:
+    Choreographer(const Choreographer&) = delete;
+
+    void dispatchVsync(nsecs_t timestamp, PhysicalDisplayId displayId, uint32_t count,
+                       VsyncEventData vsyncEventData) override;
+    void dispatchHotplug(nsecs_t timestamp, PhysicalDisplayId displayId, bool connected) override;
+    void dispatchModeChanged(nsecs_t timestamp, PhysicalDisplayId displayId, int32_t modeId,
+                             nsecs_t vsyncPeriod) override;
+    void dispatchNullEvent(nsecs_t, PhysicalDisplayId) override;
+    void dispatchFrameRateOverrides(nsecs_t timestamp, PhysicalDisplayId displayId,
+                                    std::vector<FrameRateOverride> overrides) override;
+
+    void scheduleCallbacks();
+
+    ChoreographerFrameCallbackDataImpl createFrameCallbackData(nsecs_t timestamp) const;
+    void registerStartTime() const;
+
+    std::mutex mLock;
+    // Protected by mLock
+    std::priority_queue<FrameCallback> mFrameCallbacks;
+    std::vector<RefreshRateCallback> mRefreshRateCallbacks;
+
+    nsecs_t mLatestVsyncPeriod = -1;
+    VsyncEventData mLastVsyncEventData;
+    bool mInCallback = false;
+
+    const sp<Looper> mLooper;
+    const std::thread::id mThreadId;
+
+    // Approximation of num_threads_using_choreographer * num_frames_of_history with leeway.
+    static constexpr size_t kMaxStartTimes = 250;
+};
+
+} // namespace android
\ No newline at end of file
diff --git a/libs/nativewindow/AHardwareBuffer.cpp b/libs/nativewindow/AHardwareBuffer.cpp
index 180dce9..bbafbff 100644
--- a/libs/nativewindow/AHardwareBuffer.cpp
+++ b/libs/nativewindow/AHardwareBuffer.cpp
@@ -702,6 +702,14 @@
     return ahardwarebuffer_format;
 }
 
+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);
+}
+
 uint64_t AHardwareBuffer_convertToGrallocUsageBits(uint64_t usage) {
     using android::hardware::graphics::common::V1_1::BufferUsage;
     static_assert(AHARDWAREBUFFER_USAGE_CPU_READ_NEVER == (uint64_t)BufferUsage::CPU_READ_NEVER,
diff --git a/libs/nativewindow/ANativeWindow.cpp b/libs/nativewindow/ANativeWindow.cpp
index b075080..b7b2926 100644
--- a/libs/nativewindow/ANativeWindow.cpp
+++ b/libs/nativewindow/ANativeWindow.cpp
@@ -20,10 +20,15 @@
 // from nativewindow/includes/system/window.h
 // (not to be confused with the compatibility-only window.h from system/core/includes)
 #include <system/window.h>
+#include <android/native_window_aidl.h>
 
 #include <private/android/AHardwareBufferHelpers.h>
 
+#include <log/log.h>
 #include <ui/GraphicBuffer.h>
+#include <gui/Surface.h>
+#include <gui/view/Surface.h>
+#include <android/binder_libbinder.h>
 
 using namespace android;
 
@@ -59,6 +64,13 @@
             return false;
     }
 }
+static sp<IGraphicBufferProducer> IGraphicBufferProducer_from_ANativeWindow(ANativeWindow* window) {
+    return Surface::getIGraphicBufferProducer(window);
+}
+
+static sp<IBinder> SurfaceControlHandle_from_ANativeWindow(ANativeWindow* window) {
+    return Surface::getSurfaceControlHandle(window);
+}
 
 /**************************************************************************************************
  * NDK
@@ -220,15 +232,6 @@
     return native_window_set_frame_rate(window, frameRate, compatibility, changeFrameRateStrategy);
 }
 
-int32_t ANativeWindow_clearFrameRate(ANativeWindow* window) {
-    if (!window || !query(window, NATIVE_WINDOW_IS_VALID)) {
-        return -EINVAL;
-    }
-    return native_window_set_frame_rate(window, 0,
-            ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT,
-            ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS);
-}
-
 /**************************************************************************************************
  * vndk-stable
  **************************************************************************************************/
@@ -350,6 +353,42 @@
     return native_window_set_auto_prerotation(window, autoPrerotation);
 }
 
+binder_status_t ANativeWindow_readFromParcel(
+        const AParcel* _Nonnull parcel, ANativeWindow* _Nullable* _Nonnull outWindow) {
+    const Parcel* nativeParcel = AParcel_viewPlatformParcel(parcel);
+
+    // Use a android::view::Surface to unparcel the window
+    std::shared_ptr<android::view::Surface> shimSurface = std::shared_ptr<android::view::Surface>();
+    status_t ret = shimSurface->readFromParcel(nativeParcel);
+    if (ret != OK) {
+        ALOGE("%s: Error: Failed to create android::view::Surface from AParcel", __FUNCTION__);
+        return STATUS_BAD_VALUE;
+    }
+    sp<Surface> surface = sp<Surface>::make(
+            shimSurface->graphicBufferProducer, false, shimSurface->surfaceControlHandle);
+    ANativeWindow* anw = surface.get();
+    ANativeWindow_acquire(anw);
+    *outWindow = anw;
+    return STATUS_OK;
+}
+
+binder_status_t ANativeWindow_writeToParcel(
+        ANativeWindow* _Nonnull window, AParcel* _Nonnull parcel) {
+    int value;
+    int err = (*window->query)(window, NATIVE_WINDOW_CONCRETE_TYPE, &value);
+    if (err != OK || value != NATIVE_WINDOW_SURFACE) {
+        ALOGE("Error: ANativeWindow is not backed by Surface");
+        return STATUS_BAD_VALUE;
+    }
+    // Use a android::view::Surface to parcelize the window
+    std::shared_ptr<android::view::Surface> shimSurface = std::shared_ptr<android::view::Surface>();
+    shimSurface->graphicBufferProducer = IGraphicBufferProducer_from_ANativeWindow(window);
+    shimSurface->surfaceControlHandle = SurfaceControlHandle_from_ANativeWindow(window);
+
+    Parcel* nativeParcel = AParcel_viewPlatformParcel(parcel);
+    return shimSurface->writeToParcel(nativeParcel);
+}
+
 /**************************************************************************************************
  * apex-stable
  **************************************************************************************************/
diff --git a/libs/nativewindow/Android.bp b/libs/nativewindow/Android.bp
index 3503a9e..bc0bfc5 100644
--- a/libs/nativewindow/Android.bp
+++ b/libs/nativewindow/Android.bp
@@ -74,6 +74,9 @@
         override_export_include_dirs: [
             "include",
         ],
+        export_llndk_headers: [
+            "libarect_headers",
+        ],
     },
     export_include_dirs: [
         "include",
@@ -107,19 +110,19 @@
     static_libs: [
         "libarect",
         "libgrallocusage",
+        "libgui_aidl_static",
     ],
 
     header_libs: [
+        "libgui_headers",
+        "libarect_headers",
         "libnativebase_headers",
         "libnativewindow_headers",
     ],
 
     // headers we include in our public headers
-    export_static_lib_headers: [
-        "libarect",
-    ],
-
     export_header_lib_headers: [
+        "libarect_headers",
         "libnativebase_headers",
     ],
 
diff --git a/libs/nativewindow/include-private/private/android/AHardwareBufferHelpers.h b/libs/nativewindow/include-private/private/android/AHardwareBufferHelpers.h
index ddfd1d1..6d3d295 100644
--- a/libs/nativewindow/include-private/private/android/AHardwareBufferHelpers.h
+++ b/libs/nativewindow/include-private/private/android/AHardwareBufferHelpers.h
@@ -52,6 +52,11 @@
 // 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/native_window.h b/libs/nativewindow/include/android/native_window.h
index 281ec52..be6623e 100644
--- a/libs/nativewindow/include/android/native_window.h
+++ b/libs/nativewindow/include/android/native_window.h
@@ -372,11 +372,15 @@
  *
  * \return 0 for success, -EINVAL if the window value is invalid.
  */
-int32_t ANativeWindow_clearFrameRate(ANativeWindow* window)
-        __INTRODUCED_IN(__ANDROID_API_U__);
+inline int32_t ANativeWindow_clearFrameRate(ANativeWindow* window)
+        __INTRODUCED_IN(__ANDROID_API_U__) {
+    return ANativeWindow_setFrameRateWithChangeStrategy(window, 0,
+            ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT,
+            ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS);
+}
 
 #ifdef __cplusplus
-};
+}
 #endif
 
 #endif // ANDROID_NATIVE_WINDOW_H
diff --git a/libs/nativewindow/include/android/native_window_aidl.h b/libs/nativewindow/include/android/native_window_aidl.h
new file mode 100644
index 0000000..a252245
--- /dev/null
+++ b/libs/nativewindow/include/android/native_window_aidl.h
@@ -0,0 +1,161 @@
+/*
+ * 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.
+ */
+
+/**
+ * @file native_window_aidl.h
+ * @brief NativeWindow NDK AIDL glue code
+ */
+
+/**
+ * @addtogroup ANativeWindow
+ *
+ * Parcelable support for ANativeWindow. Can be used with libbinder_ndk
+ *
+ * @{
+ */
+
+#ifndef ANDROID_NATIVE_WINDOW_AIDL_H
+#define ANDROID_NATIVE_WINDOW_AIDL_H
+
+#include <android/binder_parcel.h>
+#include <android/native_window.h>
+#include <sys/cdefs.h>
+
+__BEGIN_DECLS
+
+/**
+ * Read an ANativeWindow from a AParcel. The output buffer will have an
+ * initial reference acquired and will need to be released with
+ * ANativeWindow_release.
+ *
+ * Available since API level 34.
+ *
+ * \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 ANativeWindow type
+ *         STATUS_NO_MEMORY if an allocation fails
+ */
+binder_status_t ANativeWindow_readFromParcel(const AParcel* _Nonnull parcel,
+        ANativeWindow* _Nullable* _Nonnull outWindow) __INTRODUCED_IN(__ANDROID_API_U__);
+
+/**
+ * Write an ANativeWindow to an AParcel.
+ *
+ * Available since API level 34.
+ *
+ * \return STATUS_OK on success.
+ *         STATUS_BAD_VALUE if either buffer or parcel is null, or if the ANativeWindow*
+ *                          fails to serialize (eg, internally corrupted)
+ *         STATUS_NO_MEMORY if the parcel runs out of space to store the buffer & is
+ *                          unable to allocate more
+ *         STATUS_FDS_NOT_ALLOWED if the parcel does not allow storing FDs
+ */
+binder_status_t ANativeWindow_writeToParcel(ANativeWindow* _Nonnull window,
+        AParcel* _Nonnull parcel) __INTRODUCED_IN(__ANDROID_API_U__);
+
+__END_DECLS
+
+// Only enable the AIDL glue helper if this is C++
+#ifdef __cplusplus
+
+namespace aidl::android::hardware {
+
+/**
+ * Wrapper class that enables interop with AIDL NDK generation
+ * 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 {
+public:
+    NativeWindow() noexcept {}
+    explicit NativeWindow(ANativeWindow* _Nullable window) {
+        reset(window);
+    }
+
+    explicit NativeWindow(NativeWindow&& other) noexcept {
+        mWindow = other.release(); // steal ownership from r-value
+    }
+
+    ~NativeWindow() {
+        reset();
+    }
+
+    binder_status_t readFromParcel(const AParcel* _Nonnull parcel) {
+        reset();
+        return ANativeWindow_readFromParcel(parcel, &mWindow);
+    }
+
+    binder_status_t writeToParcel(AParcel* _Nonnull parcel) const {
+        if (!mWindow) {
+            return STATUS_BAD_VALUE;
+        }
+        return ANativeWindow_writeToParcel(mWindow, parcel);
+    }
+
+    /**
+     * Destroys any currently owned ANativeWindow* and takes ownership of the given
+     * ANativeWindow*
+     *
+     * @param buffer The buffer to take ownership of
+     */
+    void reset(ANativeWindow* _Nullable window = nullptr) noexcept {
+        if (mWindow) {
+            ANativeWindow_release(mWindow);
+            mWindow = nullptr;
+        }
+        if (window != nullptr) {
+            ANativeWindow_acquire(window);
+        }
+        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;
+    }
+
+    /**
+     * Stops managing any contained ANativeWindow*, returning it to the caller. Ownership
+     * is released.
+     * @return ANativeWindow* or null if this was empty
+     */
+    [[nodiscard]] ANativeWindow* _Nullable release() noexcept {
+        ANativeWindow* _Nullable ret = mWindow;
+        mWindow = nullptr;
+        return ret;
+    }
+private:
+    ANativeWindow* _Nullable mWindow = nullptr;
+    NativeWindow(const NativeWindow &other) = delete;
+    NativeWindow& operator=(const NativeWindow &other) = delete;
+};
+
+} // aidl::android::hardware
+  //
+namespace aidl::android::view {
+    using Surface = aidl::android::hardware::NativeWindow;
+}
+
+#endif // __cplusplus
+
+#endif // ANDROID_NATIVE_WINDOW_AIDL_H
+
+/** @} */
diff --git a/libs/nativewindow/libnativewindow.map.txt b/libs/nativewindow/libnativewindow.map.txt
index ce108b6..c2fd6ef 100644
--- a/libs/nativewindow/libnativewindow.map.txt
+++ b/libs/nativewindow/libnativewindow.map.txt
@@ -51,12 +51,13 @@
     ANativeWindow_setDequeueTimeout; # systemapi # introduced=30
     ANativeWindow_setFrameRate; # introduced=30
     ANativeWindow_setFrameRateWithChangeStrategy; # introduced=31
-    ANativeWindow_clearFrameRate; # introduced=UpsideDownCake
     ANativeWindow_setSharedBufferMode; # llndk
     ANativeWindow_setSwapInterval; # llndk
     ANativeWindow_setUsage; # llndk
     ANativeWindow_tryAllocateBuffers; # introduced=30
     ANativeWindow_unlockAndPost;
+    ANativeWindow_readFromParcel; # introduced=UpsideDownCake
+    ANativeWindow_writeToParcel; # introduced=UpsideDownCake
   local:
     *;
 };
@@ -69,6 +70,7 @@
       android::AHardwareBuffer_convertToPixelFormat*;
       android::AHardwareBuffer_convertFromGrallocUsageBits*;
       android::AHardwareBuffer_convertToGrallocUsageBits*;
+      android::AHardwareBuffer_getDataSpace*;
       android::AHardwareBuffer_to_GraphicBuffer*;
       android::AHardwareBuffer_to_ANativeWindowBuffer*;
       android::AHardwareBuffer_from_GraphicBuffer*;
diff --git a/libs/renderengine/Android.bp b/libs/renderengine/Android.bp
index 0540538..04e24ed 100644
--- a/libs/renderengine/Android.bp
+++ b/libs/renderengine/Android.bp
@@ -42,6 +42,7 @@
         "libsync",
         "libui",
         "libutils",
+        "libvulkan",
     ],
 
     static_libs: [
@@ -97,6 +98,7 @@
         "skia/ColorSpaces.cpp",
         "skia/SkiaRenderEngine.cpp",
         "skia/SkiaGLRenderEngine.cpp",
+        "skia/SkiaVkRenderEngine.cpp",
         "skia/debug/CaptureTimer.cpp",
         "skia/debug/CommonPool.cpp",
         "skia/debug/SkiaCapture.cpp",
diff --git a/libs/renderengine/RenderEngine.cpp b/libs/renderengine/RenderEngine.cpp
index 9d9cb6b..341c011 100644
--- a/libs/renderengine/RenderEngine.cpp
+++ b/libs/renderengine/RenderEngine.cpp
@@ -19,9 +19,11 @@
 #include <cutils/properties.h>
 #include <log/log.h>
 #include "gl/GLESRenderEngine.h"
+#include "renderengine/ExternalTexture.h"
 #include "threaded/RenderEngineThreaded.h"
 
 #include "skia/SkiaGLRenderEngine.h"
+#include "skia/SkiaVkRenderEngine.h"
 
 namespace android {
 namespace renderengine {
@@ -36,6 +38,13 @@
         case RenderEngineType::SKIA_GL:
             ALOGD("RenderEngine with SkiaGL Backend");
             return renderengine::skia::SkiaGLRenderEngine::create(args);
+        case RenderEngineType::SKIA_VK:
+#ifdef RE_SKIAVK
+            ALOGD("RenderEngine with SkiaVK Backend");
+            return renderengine::skia::SkiaVkRenderEngine::create(args);
+#else
+            LOG_ALWAYS_FATAL("Requested VK backend, but RE_SKIAVK is not defined!");
+#endif
         case RenderEngineType::SKIA_GL_THREADED: {
             ALOGD("Threaded RenderEngine with SkiaGL Backend");
             return renderengine::threaded::RenderEngineThreaded::create(
@@ -44,6 +53,17 @@
                     },
                     args.renderEngineType);
         }
+        case RenderEngineType::SKIA_VK_THREADED:
+#ifdef RE_SKIAVK
+            ALOGD("Threaded RenderEngine with SkiaVK Backend");
+            return renderengine::threaded::RenderEngineThreaded::create(
+                    [args]() {
+                        return android::renderengine::skia::SkiaVkRenderEngine::create(args);
+                    },
+                    args.renderEngineType);
+#else
+            LOG_ALWAYS_FATAL("Requested VK backend, but RE_SKIAVK is not defined!");
+#endif
         case RenderEngineType::GLES:
         default:
             ALOGD("RenderEngine with GLES Backend");
@@ -70,10 +90,22 @@
                                                   base::unique_fd&& bufferFence) {
     const auto resultPromise = std::make_shared<std::promise<FenceResult>>();
     std::future<FenceResult> resultFuture = resultPromise->get_future();
+    updateProtectedContext(layers, buffer);
     drawLayersInternal(std::move(resultPromise), display, layers, buffer, useFramebufferCache,
                        std::move(bufferFence));
     return resultFuture;
 }
 
+void RenderEngine::updateProtectedContext(const std::vector<LayerSettings>& layers,
+                                          const std::shared_ptr<ExternalTexture>& buffer) {
+    const bool needsProtectedContext =
+            (buffer && (buffer->getUsage() & GRALLOC_USAGE_PROTECTED)) ||
+            std::any_of(layers.begin(), layers.end(), [](const LayerSettings& layer) {
+                const std::shared_ptr<ExternalTexture>& buffer = layer.source.buffer.buffer;
+                return buffer && (buffer->getUsage() & GRALLOC_USAGE_PROTECTED);
+            });
+    useProtectedContext(needsProtectedContext);
+}
+
 } // namespace renderengine
 } // namespace android
diff --git a/libs/renderengine/benchmark/RenderEngineBench.cpp b/libs/renderengine/benchmark/RenderEngineBench.cpp
index d44eb46..bd7b617 100644
--- a/libs/renderengine/benchmark/RenderEngineBench.cpp
+++ b/libs/renderengine/benchmark/RenderEngineBench.cpp
@@ -39,6 +39,10 @@
             return "skiaglthreaded";
         case RenderEngine::RenderEngineType::SKIA_GL:
             return "skiagl";
+        case RenderEngine::RenderEngineType::SKIA_VK:
+            return "skiavk";
+        case RenderEngine::RenderEngineType::SKIA_VK_THREADED:
+            return "skiavkthreaded";
         case RenderEngine::RenderEngineType::GLES:
         case RenderEngine::RenderEngineType::THREADED:
             LOG_ALWAYS_FATAL("GLESRenderEngine is deprecated - why time it?");
diff --git a/libs/renderengine/gl/GLESRenderEngine.h b/libs/renderengine/gl/GLESRenderEngine.h
index 1ee5cba..1b34921 100644
--- a/libs/renderengine/gl/GLESRenderEngine.h
+++ b/libs/renderengine/gl/GLESRenderEngine.h
@@ -61,7 +61,7 @@
     std::future<void> primeCache() override;
     void genTextures(size_t count, uint32_t* names) override;
     void deleteTextures(size_t count, uint32_t const* names) override;
-    bool isProtected() const override { return mInProtectedContext; }
+    bool isProtected() const { return mInProtectedContext; }
     bool supportsProtectedContent() const override;
     void useProtectedContext(bool useProtectedContext) override;
     void cleanupPostRender() override;
diff --git a/libs/renderengine/gl/ProgramCache.cpp b/libs/renderengine/gl/ProgramCache.cpp
index 5ff9240..f7f2d54 100644
--- a/libs/renderengine/gl/ProgramCache.cpp
+++ b/libs/renderengine/gl/ProgramCache.cpp
@@ -601,7 +601,7 @@
     }
 
     if (needs.hasTextureCoords()) {
-        fs << "varying vec2 outTexCoords;";
+        fs << "varying highp vec2 outTexCoords;";
     }
 
     if (needs.hasRoundedCorners()) {
diff --git a/libs/renderengine/include/renderengine/DisplaySettings.h b/libs/renderengine/include/renderengine/DisplaySettings.h
index 25fe9f2..8d7c13c 100644
--- a/libs/renderengine/include/renderengine/DisplaySettings.h
+++ b/libs/renderengine/include/renderengine/DisplaySettings.h
@@ -23,17 +23,24 @@
 #include <math/mat4.h>
 #include <renderengine/PrintMatrix.h>
 #include <renderengine/BorderRenderInfo.h>
+#include <ui/DisplayId.h>
 #include <ui/GraphicTypes.h>
 #include <ui/Rect.h>
 #include <ui/Region.h>
 #include <ui/Transform.h>
 
+#include <optional>
+
 namespace android {
 namespace renderengine {
 
 // DisplaySettings contains the settings that are applicable when drawing all
 // layers for a given display.
 struct DisplaySettings {
+    // A string containing the name of the display, along with its id, if it has
+    // one.
+    std::string namePlusId;
+
     // Rectangle describing the physical display. We will project from the
     // logical clip onto this rectangle.
     Rect physicalDisplay = Rect::INVALID_RECT;
@@ -85,8 +92,8 @@
 };
 
 static inline bool operator==(const DisplaySettings& lhs, const DisplaySettings& rhs) {
-    return lhs.physicalDisplay == rhs.physicalDisplay && lhs.clip == rhs.clip &&
-            lhs.maxLuminance == rhs.maxLuminance &&
+    return lhs.namePlusId == rhs.namePlusId && lhs.physicalDisplay == rhs.physicalDisplay &&
+            lhs.clip == rhs.clip && lhs.maxLuminance == rhs.maxLuminance &&
             lhs.currentLuminanceNits == rhs.currentLuminanceNits &&
             lhs.outputDataspace == rhs.outputDataspace &&
             lhs.colorTransform == rhs.colorTransform &&
@@ -121,6 +128,7 @@
 
 static inline void PrintTo(const DisplaySettings& settings, ::std::ostream* os) {
     *os << "DisplaySettings {";
+    *os << "\n    .display = " << settings.namePlusId;
     *os << "\n    .physicalDisplay = ";
     PrintTo(settings.physicalDisplay, os);
     *os << "\n    .clip = ";
diff --git a/libs/renderengine/include/renderengine/RenderEngine.h b/libs/renderengine/include/renderengine/RenderEngine.h
index 199392c..39621cd 100644
--- a/libs/renderengine/include/renderengine/RenderEngine.h
+++ b/libs/renderengine/include/renderengine/RenderEngine.h
@@ -99,6 +99,8 @@
         THREADED = 2,
         SKIA_GL = 3,
         SKIA_GL_THREADED = 4,
+        SKIA_VK = 5,
+        SKIA_VK_THREADED = 6,
     };
 
     static std::unique_ptr<RenderEngine> create(const RenderEngineCreationArgs& args);
@@ -126,12 +128,8 @@
     // ----- BEGIN NEW INTERFACE -----
 
     // queries that are required to be thread safe
-    virtual bool isProtected() const = 0;
     virtual bool supportsProtectedContent() const = 0;
 
-    // Attempt to switch RenderEngine into and out of protectedContext mode
-    virtual void useProtectedContext(bool useProtectedContext) = 0;
-
     // Notify RenderEngine of changes to the dimensions of the active display
     // so that it can configure its internal caches accordingly.
     virtual void onActiveDisplaySizeChanged(ui::Size size) = 0;
@@ -174,9 +172,16 @@
     virtual void cleanupPostRender() = 0;
 
     virtual void cleanFramebufferCache() = 0;
-    // Returns the priority this context was actually created with. Note: this may not be
-    // the same as specified at context creation time, due to implementation limits on the
-    // number of contexts that can be created at a specific priority level in the system.
+
+    // Returns the priority this context was actually created with. Note: this
+    // may not be the same as specified at context creation time, due to
+    // implementation limits on the number of contexts that can be created at a
+    // specific priority level in the system.
+    //
+    // This should return a valid EGL context priority enum as described by
+    // https://registry.khronos.org/EGL/extensions/IMG/EGL_IMG_context_priority.txt
+    // or
+    // https://registry.khronos.org/EGL/extensions/NV/EGL_NV_context_priority_realtime.txt
     virtual int getContextPriority() = 0;
 
     // Returns true if blur was requested in the RenderEngineCreationArgs and the implementation
@@ -238,6 +243,13 @@
     friend class RenderEngineTest_cleanupPostRender_cleansUpOnce_Test;
     const RenderEngineType mRenderEngineType;
 
+    // Update protectedContext mode depending on whether or not any layer has a protected buffer.
+    void updateProtectedContext(const std::vector<LayerSettings>&,
+                                const std::shared_ptr<ExternalTexture>&);
+
+    // Attempt to switch RenderEngine into and out of protectedContext mode
+    virtual void useProtectedContext(bool useProtectedContext) = 0;
+
     virtual void drawLayersInternal(
             const std::shared_ptr<std::promise<FenceResult>>&& resultPromise,
             const DisplaySettings& display, const std::vector<LayerSettings>& layers,
diff --git a/libs/renderengine/skia/SkiaGLRenderEngine.cpp b/libs/renderengine/skia/SkiaGLRenderEngine.cpp
index 347b8b7..ff598e7 100644
--- a/libs/renderengine/skia/SkiaGLRenderEngine.cpp
+++ b/libs/renderengine/skia/SkiaGLRenderEngine.cpp
@@ -193,9 +193,9 @@
     }
 
     // initialize the renderer while GL is current
-    std::unique_ptr<SkiaGLRenderEngine> engine =
-            std::make_unique<SkiaGLRenderEngine>(args, display, ctxt, placeholder, protectedContext,
-                                                 protectedPlaceholder);
+    std::unique_ptr<SkiaGLRenderEngine> engine(new SkiaGLRenderEngine(args, display, ctxt,
+                                                                      placeholder, protectedContext,
+                                                                      protectedPlaceholder));
     engine->ensureGrContextsCreated();
 
     ALOGI("OpenGL ES informations:");
diff --git a/libs/renderengine/skia/SkiaGLRenderEngine.h b/libs/renderengine/skia/SkiaGLRenderEngine.h
index 4a37ffe..af33110 100644
--- a/libs/renderengine/skia/SkiaGLRenderEngine.h
+++ b/libs/renderengine/skia/SkiaGLRenderEngine.h
@@ -52,9 +52,6 @@
 class SkiaGLRenderEngine : public skia::SkiaRenderEngine {
 public:
     static std::unique_ptr<SkiaGLRenderEngine> create(const RenderEngineCreationArgs& args);
-    SkiaGLRenderEngine(const RenderEngineCreationArgs& args, EGLDisplay display, EGLContext ctxt,
-                       EGLSurface placeholder, EGLContext protectedContext,
-                       EGLSurface protectedPlaceholder);
     ~SkiaGLRenderEngine() override;
 
     int getContextPriority() override;
@@ -70,6 +67,9 @@
     void appendBackendSpecificInfoToDump(std::string& result) override;
 
 private:
+    SkiaGLRenderEngine(const RenderEngineCreationArgs& args, EGLDisplay display, EGLContext ctxt,
+                       EGLSurface placeholder, EGLContext protectedContext,
+                       EGLSurface protectedPlaceholder);
     bool waitGpuFence(base::borrowed_fd fenceFd);
     base::unique_fd flush();
     static EGLConfig chooseEglConfig(EGLDisplay display, int format, bool logConfig);
diff --git a/libs/renderengine/skia/SkiaRenderEngine.cpp b/libs/renderengine/skia/SkiaRenderEngine.cpp
index b9aa5ac..413811e 100644
--- a/libs/renderengine/skia/SkiaRenderEngine.cpp
+++ b/libs/renderengine/skia/SkiaRenderEngine.cpp
@@ -388,9 +388,11 @@
 
 void SkiaRenderEngine::mapExternalTextureBuffer(const sp<GraphicBuffer>& buffer,
                                                   bool isRenderable) {
-    // Only run this if RE is running on its own thread. This way the access to GL
-    // operations is guaranteed to be happening on the same thread.
-    if (mRenderEngineType != RenderEngineType::SKIA_GL_THREADED) {
+    // Only run this if RE is running on its own thread. This
+    // way the access to GL operations is guaranteed to be happening on the
+    // same thread.
+    if (mRenderEngineType != RenderEngineType::SKIA_GL_THREADED &&
+        mRenderEngineType != RenderEngineType::SKIA_VK_THREADED) {
         return;
     }
     // We currently don't attempt to map a buffer if the buffer contains protected content
@@ -636,7 +638,7 @@
         const DisplaySettings& display, const std::vector<LayerSettings>& layers,
         const std::shared_ptr<ExternalTexture>& buffer, const bool /*useFramebufferCache*/,
         base::unique_fd&& bufferFence) {
-    ATRACE_NAME("SkiaGL::drawLayersInternal");
+    ATRACE_FORMAT("%s for %s", __func__, display.namePlusId.c_str());
 
     std::lock_guard<std::mutex> lock(mRenderingMutex);
 
diff --git a/libs/renderengine/skia/SkiaRenderEngine.h b/libs/renderengine/skia/SkiaRenderEngine.h
index e7c5b8f..1973c7d 100644
--- a/libs/renderengine/skia/SkiaRenderEngine.h
+++ b/libs/renderengine/skia/SkiaRenderEngine.h
@@ -68,7 +68,6 @@
     std::future<void> primeCache() override final;
     void cleanupPostRender() override final;
     void cleanFramebufferCache() override final{ }
-    bool isProtected() const override final{ return mInProtectedContext; }
     bool supportsBackgroundBlur() override final {
         return mBlurFilter != nullptr;
     }
@@ -102,6 +101,8 @@
     size_t getMaxViewportDims() const override final;
     GrDirectContext* getActiveGrContext();
 
+    bool isProtected() const { return mInProtectedContext; }
+
     // Implements PersistentCache as a way to monitor what SkSL shaders Skia has
     // cached.
     class SkSLCacheMonitor : public GrContextOptions::PersistentCache {
diff --git a/libs/renderengine/skia/SkiaVkRenderEngine.cpp b/libs/renderengine/skia/SkiaVkRenderEngine.cpp
new file mode 100644
index 0000000..2b8495c
--- /dev/null
+++ b/libs/renderengine/skia/SkiaVkRenderEngine.cpp
@@ -0,0 +1,680 @@
+/*
+ * 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.
+ */
+
+// Allow the SkiaVkRenderEngine class to not be compiled, to save space
+// NOTE: In order to build this class, define `RE_SKIAVK` in a build file.
+#ifdef RE_SKIAVK
+
+// #define LOG_NDEBUG 0
+#undef LOG_TAG
+#define LOG_TAG "RenderEngine"
+#define ATRACE_TAG ATRACE_TAG_GRAPHICS
+
+#include "SkiaVkRenderEngine.h"
+
+#include <GrBackendSemaphore.h>
+#include <GrContextOptions.h>
+#include <vk/GrVkExtensions.h>
+#include <vk/GrVkTypes.h>
+
+#include <android-base/stringprintf.h>
+#include <gui/TraceUtils.h>
+#include <sync/sync.h>
+#include <utils/Trace.h>
+
+#include <cstdint>
+#include <memory>
+#include <vector>
+
+#include <vulkan/vulkan.h>
+#include "log/log_main.h"
+
+namespace android {
+namespace renderengine {
+
+struct VulkanFuncs {
+    PFN_vkCreateSemaphore vkCreateSemaphore = nullptr;
+    PFN_vkImportSemaphoreFdKHR vkImportSemaphoreFdKHR = nullptr;
+    PFN_vkGetSemaphoreFdKHR vkGetSemaphoreFdKHR = nullptr;
+    PFN_vkDestroySemaphore vkDestroySemaphore = nullptr;
+
+    PFN_vkDeviceWaitIdle vkDeviceWaitIdle = nullptr;
+    PFN_vkDestroyDevice vkDestroyDevice = nullptr;
+    PFN_vkDestroyInstance vkDestroyInstance = nullptr;
+};
+
+struct VulkanInterface {
+    bool initialized = false;
+    VkInstance instance;
+    VkPhysicalDevice physicalDevice;
+    VkDevice device;
+    VkQueue queue;
+    int queueIndex;
+    uint32_t apiVersion;
+    GrVkExtensions grExtensions;
+    VkPhysicalDeviceFeatures2* physicalDeviceFeatures2 = nullptr;
+    VkPhysicalDeviceSamplerYcbcrConversionFeatures* samplerYcbcrConversionFeatures = nullptr;
+    VkPhysicalDeviceProtectedMemoryProperties* protectedMemoryFeatures = nullptr;
+    GrVkGetProc grGetProc;
+    bool isProtected;
+    bool isRealtimePriority;
+
+    VulkanFuncs funcs;
+
+    std::vector<std::string> instanceExtensionNames;
+    std::vector<std::string> deviceExtensionNames;
+
+    GrVkBackendContext getBackendContext() {
+        GrVkBackendContext backendContext;
+        backendContext.fInstance = instance;
+        backendContext.fPhysicalDevice = physicalDevice;
+        backendContext.fDevice = device;
+        backendContext.fQueue = queue;
+        backendContext.fGraphicsQueueIndex = queueIndex;
+        backendContext.fMaxAPIVersion = apiVersion;
+        backendContext.fVkExtensions = &grExtensions;
+        backendContext.fDeviceFeatures2 = physicalDeviceFeatures2;
+        backendContext.fGetProc = grGetProc;
+        backendContext.fProtectedContext = isProtected ? GrProtected::kYes : GrProtected::kNo;
+        return backendContext;
+    };
+
+    VkSemaphore createExportableSemaphore() {
+        VkExportSemaphoreCreateInfo exportInfo;
+        exportInfo.sType = VK_STRUCTURE_TYPE_EXPORT_SEMAPHORE_CREATE_INFO;
+        exportInfo.pNext = nullptr;
+        exportInfo.handleTypes = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT;
+
+        VkSemaphoreCreateInfo semaphoreInfo;
+        semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
+        semaphoreInfo.pNext = &exportInfo;
+        semaphoreInfo.flags = 0;
+
+        VkSemaphore semaphore;
+        VkResult err = funcs.vkCreateSemaphore(device, &semaphoreInfo, nullptr, &semaphore);
+        if (VK_SUCCESS != err) {
+            ALOGE("%s: failed to create semaphore. err %d\n", __func__, err);
+            return VK_NULL_HANDLE;
+        }
+
+        return semaphore;
+    }
+
+    // syncFd cannot be <= 0
+    VkSemaphore importSemaphoreFromSyncFd(int syncFd) {
+        VkSemaphoreCreateInfo semaphoreInfo;
+        semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
+        semaphoreInfo.pNext = nullptr;
+        semaphoreInfo.flags = 0;
+
+        VkSemaphore semaphore;
+        VkResult err = funcs.vkCreateSemaphore(device, &semaphoreInfo, nullptr, &semaphore);
+        if (VK_SUCCESS != err) {
+            ALOGE("%s: failed to create import semaphore", __func__);
+            return VK_NULL_HANDLE;
+        }
+
+        VkImportSemaphoreFdInfoKHR importInfo;
+        importInfo.sType = VK_STRUCTURE_TYPE_IMPORT_SEMAPHORE_FD_INFO_KHR;
+        importInfo.pNext = nullptr;
+        importInfo.semaphore = semaphore;
+        importInfo.flags = VK_SEMAPHORE_IMPORT_TEMPORARY_BIT;
+        importInfo.handleType = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT;
+        importInfo.fd = syncFd;
+
+        err = funcs.vkImportSemaphoreFdKHR(device, &importInfo);
+        if (VK_SUCCESS != err) {
+            funcs.vkDestroySemaphore(device, semaphore, nullptr);
+            ALOGE("%s: failed to import semaphore", __func__);
+            return VK_NULL_HANDLE;
+        }
+
+        return semaphore;
+    }
+
+    int exportSemaphoreSyncFd(VkSemaphore semaphore) {
+        int res;
+
+        VkSemaphoreGetFdInfoKHR getFdInfo;
+        getFdInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_GET_FD_INFO_KHR;
+        getFdInfo.pNext = nullptr;
+        getFdInfo.semaphore = semaphore;
+        getFdInfo.handleType = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT;
+
+        VkResult err = funcs.vkGetSemaphoreFdKHR(device, &getFdInfo, &res);
+        if (VK_SUCCESS != err) {
+            ALOGE("%s: failed to export semaphore, err: %d", __func__, err);
+            return -1;
+        }
+        return res;
+    }
+
+    void destroySemaphore(VkSemaphore semaphore) {
+        funcs.vkDestroySemaphore(device, semaphore, nullptr);
+    }
+};
+
+static GrVkGetProc sGetProc = [](const char* proc_name, VkInstance instance, VkDevice device) {
+    if (device != VK_NULL_HANDLE) {
+        return vkGetDeviceProcAddr(device, proc_name);
+    }
+    return vkGetInstanceProcAddr(instance, proc_name);
+};
+
+#define BAIL(fmt, ...)                                          \
+    {                                                           \
+        ALOGE("%s: " fmt ", bailing", __func__, ##__VA_ARGS__); \
+        return interface;                                       \
+    }
+
+#define CHECK_NONNULL(expr)       \
+    if ((expr) == nullptr) {      \
+        BAIL("[%s] null", #expr); \
+    }
+
+#define VK_CHECK(expr)                              \
+    if ((expr) != VK_SUCCESS) {                     \
+        BAIL("[%s] failed. err = %d", #expr, expr); \
+        return interface;                           \
+    }
+
+#define VK_GET_PROC(F)                                                           \
+    PFN_vk##F vk##F = (PFN_vk##F)vkGetInstanceProcAddr(VK_NULL_HANDLE, "vk" #F); \
+    CHECK_NONNULL(vk##F)
+#define VK_GET_INST_PROC(instance, F)                                      \
+    PFN_vk##F vk##F = (PFN_vk##F)vkGetInstanceProcAddr(instance, "vk" #F); \
+    CHECK_NONNULL(vk##F)
+#define VK_GET_DEV_PROC(device, F)                                     \
+    PFN_vk##F vk##F = (PFN_vk##F)vkGetDeviceProcAddr(device, "vk" #F); \
+    CHECK_NONNULL(vk##F)
+
+VulkanInterface initVulkanInterface(bool protectedContent = false) {
+    VulkanInterface interface;
+
+    VK_GET_PROC(EnumerateInstanceVersion);
+    uint32_t instanceVersion;
+    VK_CHECK(vkEnumerateInstanceVersion(&instanceVersion));
+
+    if (instanceVersion < VK_MAKE_VERSION(1, 1, 0)) {
+        return interface;
+    }
+
+    const VkApplicationInfo appInfo = {
+            VK_STRUCTURE_TYPE_APPLICATION_INFO, nullptr, "surfaceflinger", 0, "android platform", 0,
+            VK_MAKE_VERSION(1, 1, 0),
+    };
+
+    VK_GET_PROC(EnumerateInstanceExtensionProperties);
+
+    uint32_t extensionCount = 0;
+    VK_CHECK(vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, nullptr));
+    std::vector<VkExtensionProperties> instanceExtensions(extensionCount);
+    VK_CHECK(vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount,
+                                                    instanceExtensions.data()));
+    std::vector<const char*> enabledInstanceExtensionNames;
+    enabledInstanceExtensionNames.reserve(instanceExtensions.size());
+    interface.instanceExtensionNames.reserve(instanceExtensions.size());
+    for (const auto& instExt : instanceExtensions) {
+        enabledInstanceExtensionNames.push_back(instExt.extensionName);
+        interface.instanceExtensionNames.push_back(instExt.extensionName);
+    }
+
+    const VkInstanceCreateInfo instanceCreateInfo = {
+            VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
+            nullptr,
+            0,
+            &appInfo,
+            0,
+            nullptr,
+            (uint32_t)enabledInstanceExtensionNames.size(),
+            enabledInstanceExtensionNames.data(),
+    };
+
+    VK_GET_PROC(CreateInstance);
+    VkInstance instance;
+    VK_CHECK(vkCreateInstance(&instanceCreateInfo, nullptr, &instance));
+
+    VK_GET_INST_PROC(instance, DestroyInstance);
+    interface.funcs.vkDestroyInstance = vkDestroyInstance;
+    VK_GET_INST_PROC(instance, EnumeratePhysicalDevices);
+    VK_GET_INST_PROC(instance, EnumerateDeviceExtensionProperties);
+    VK_GET_INST_PROC(instance, GetPhysicalDeviceProperties2);
+    VK_GET_INST_PROC(instance, GetPhysicalDeviceExternalSemaphoreProperties);
+    VK_GET_INST_PROC(instance, GetPhysicalDeviceQueueFamilyProperties);
+    VK_GET_INST_PROC(instance, GetPhysicalDeviceFeatures2);
+    VK_GET_INST_PROC(instance, CreateDevice);
+
+    uint32_t physdevCount;
+    VK_CHECK(vkEnumeratePhysicalDevices(instance, &physdevCount, nullptr));
+    if (physdevCount == 0) {
+        BAIL("Could not find any physical devices");
+    }
+
+    physdevCount = 1;
+    VkPhysicalDevice physicalDevice;
+    VkResult enumeratePhysDevsErr =
+            vkEnumeratePhysicalDevices(instance, &physdevCount, &physicalDevice);
+    if (enumeratePhysDevsErr != VK_SUCCESS && VK_INCOMPLETE != enumeratePhysDevsErr) {
+        BAIL("vkEnumeratePhysicalDevices failed with non-VK_INCOMPLETE error: %d",
+             enumeratePhysDevsErr);
+    }
+
+    VkPhysicalDeviceProperties2 physDevProps = {
+            VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2,
+            0,
+            {},
+    };
+    VkPhysicalDeviceProtectedMemoryProperties protMemProps = {
+            VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROTECTED_MEMORY_PROPERTIES,
+            0,
+            {},
+    };
+
+    if (protectedContent) {
+        physDevProps.pNext = &protMemProps;
+    }
+
+    vkGetPhysicalDeviceProperties2(physicalDevice, &physDevProps);
+    if (physDevProps.properties.apiVersion < VK_MAKE_VERSION(1, 1, 0)) {
+        BAIL("Could not find a Vulkan 1.1+ physical device");
+    }
+
+    // Check for syncfd support. Bail if we cannot both import and export them.
+    VkPhysicalDeviceExternalSemaphoreInfo semInfo = {
+            VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_SEMAPHORE_INFO,
+            nullptr,
+            VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT,
+    };
+    VkExternalSemaphoreProperties semProps = {
+            VK_STRUCTURE_TYPE_EXTERNAL_SEMAPHORE_PROPERTIES, nullptr, 0, 0, 0,
+    };
+    vkGetPhysicalDeviceExternalSemaphoreProperties(physicalDevice, &semInfo, &semProps);
+
+    bool sufficientSemaphoreSyncFdSupport = (semProps.exportFromImportedHandleTypes &
+                                             VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT) &&
+            (semProps.compatibleHandleTypes & VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT) &&
+            (semProps.externalSemaphoreFeatures & VK_EXTERNAL_SEMAPHORE_FEATURE_EXPORTABLE_BIT) &&
+            (semProps.externalSemaphoreFeatures & VK_EXTERNAL_SEMAPHORE_FEATURE_IMPORTABLE_BIT);
+
+    if (!sufficientSemaphoreSyncFdSupport) {
+        BAIL("Vulkan device does not support sufficient external semaphore sync fd features. "
+             "exportFromImportedHandleTypes 0x%x (needed 0x%x) "
+             "compatibleHandleTypes 0x%x (needed 0x%x) "
+             "externalSemaphoreFeatures 0x%x (needed 0x%x) ",
+             semProps.exportFromImportedHandleTypes, VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT,
+             semProps.compatibleHandleTypes, VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT,
+             semProps.externalSemaphoreFeatures,
+             VK_EXTERNAL_SEMAPHORE_FEATURE_EXPORTABLE_BIT |
+                     VK_EXTERNAL_SEMAPHORE_FEATURE_IMPORTABLE_BIT);
+    } else {
+        ALOGD("Vulkan device supports sufficient external semaphore sync fd features. "
+              "exportFromImportedHandleTypes 0x%x (needed 0x%x) "
+              "compatibleHandleTypes 0x%x (needed 0x%x) "
+              "externalSemaphoreFeatures 0x%x (needed 0x%x) ",
+              semProps.exportFromImportedHandleTypes, VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT,
+              semProps.compatibleHandleTypes, VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT,
+              semProps.externalSemaphoreFeatures,
+              VK_EXTERNAL_SEMAPHORE_FEATURE_EXPORTABLE_BIT |
+                      VK_EXTERNAL_SEMAPHORE_FEATURE_IMPORTABLE_BIT);
+    }
+
+    uint32_t queueCount;
+    vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &queueCount, nullptr);
+    if (queueCount == 0) {
+        BAIL("Could not find queues for physical device");
+    }
+
+    std::vector<VkQueueFamilyProperties> queueProps(queueCount);
+    vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &queueCount, queueProps.data());
+
+    int graphicsQueueIndex = -1;
+    for (uint32_t i = 0; i < queueCount; ++i) {
+        if (queueProps[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) {
+            graphicsQueueIndex = i;
+            break;
+        }
+    }
+
+    if (graphicsQueueIndex == -1) {
+        BAIL("Could not find a graphics queue family");
+    }
+
+    uint32_t deviceExtensionCount;
+    VK_CHECK(vkEnumerateDeviceExtensionProperties(physicalDevice, nullptr, &deviceExtensionCount,
+                                                  nullptr));
+    std::vector<VkExtensionProperties> deviceExtensions(deviceExtensionCount);
+    VK_CHECK(vkEnumerateDeviceExtensionProperties(physicalDevice, nullptr, &deviceExtensionCount,
+                                                  deviceExtensions.data()));
+
+    std::vector<const char*> enabledDeviceExtensionNames;
+    enabledDeviceExtensionNames.reserve(deviceExtensions.size());
+    interface.deviceExtensionNames.reserve(deviceExtensions.size());
+    for (const auto& devExt : deviceExtensions) {
+        enabledDeviceExtensionNames.push_back(devExt.extensionName);
+        interface.deviceExtensionNames.push_back(devExt.extensionName);
+    }
+
+    interface.grExtensions.init(sGetProc, instance, physicalDevice,
+                                enabledInstanceExtensionNames.size(),
+                                enabledInstanceExtensionNames.data(),
+                                enabledDeviceExtensionNames.size(),
+                                enabledDeviceExtensionNames.data());
+
+    if (!interface.grExtensions.hasExtension(VK_KHR_EXTERNAL_SEMAPHORE_FD_EXTENSION_NAME, 1)) {
+        BAIL("Vulkan driver doesn't support external semaphore fd");
+    }
+
+    interface.physicalDeviceFeatures2 = new VkPhysicalDeviceFeatures2;
+    interface.physicalDeviceFeatures2->sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2;
+    interface.physicalDeviceFeatures2->pNext = nullptr;
+
+    interface.samplerYcbcrConversionFeatures = new VkPhysicalDeviceSamplerYcbcrConversionFeatures;
+    interface.samplerYcbcrConversionFeatures->sType =
+            VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SAMPLER_YCBCR_CONVERSION_FEATURES;
+    interface.samplerYcbcrConversionFeatures->pNext = nullptr;
+
+    interface.physicalDeviceFeatures2->pNext = interface.samplerYcbcrConversionFeatures;
+    void** tailPnext = &interface.samplerYcbcrConversionFeatures->pNext;
+
+    if (protectedContent) {
+        interface.protectedMemoryFeatures = new VkPhysicalDeviceProtectedMemoryProperties;
+        interface.protectedMemoryFeatures->sType =
+                VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROTECTED_MEMORY_FEATURES;
+        interface.protectedMemoryFeatures->pNext = nullptr;
+        *tailPnext = interface.protectedMemoryFeatures;
+        tailPnext = &interface.protectedMemoryFeatures->pNext;
+    }
+
+    vkGetPhysicalDeviceFeatures2(physicalDevice, interface.physicalDeviceFeatures2);
+    // Looks like this would slow things down and we can't depend on it on all platforms
+    interface.physicalDeviceFeatures2->features.robustBufferAccess = VK_FALSE;
+
+    float queuePriorities[1] = {0.0f};
+    void* queueNextPtr = nullptr;
+
+    VkDeviceQueueGlobalPriorityCreateInfoEXT queuePriorityCreateInfo = {
+            VK_STRUCTURE_TYPE_DEVICE_QUEUE_GLOBAL_PRIORITY_CREATE_INFO_EXT,
+            nullptr,
+            // If queue priority is supported, RE should always have realtime priority.
+            VK_QUEUE_GLOBAL_PRIORITY_REALTIME_EXT,
+    };
+
+    if (interface.grExtensions.hasExtension(VK_EXT_GLOBAL_PRIORITY_EXTENSION_NAME, 2)) {
+        queueNextPtr = &queuePriorityCreateInfo;
+        interface.isRealtimePriority = true;
+    }
+
+    VkDeviceQueueCreateFlags deviceQueueCreateFlags =
+            (VkDeviceQueueCreateFlags)(protectedContent ? VK_DEVICE_QUEUE_CREATE_PROTECTED_BIT : 0);
+
+    const VkDeviceQueueCreateInfo queueInfo = {
+            VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
+            queueNextPtr,
+            deviceQueueCreateFlags,
+            (uint32_t)graphicsQueueIndex,
+            1,
+            queuePriorities,
+    };
+
+    const VkDeviceCreateInfo deviceInfo = {
+            VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
+            interface.physicalDeviceFeatures2,
+            0,
+            1,
+            &queueInfo,
+            0,
+            nullptr,
+            (uint32_t)enabledDeviceExtensionNames.size(),
+            enabledDeviceExtensionNames.data(),
+            nullptr,
+    };
+
+    ALOGD("Trying to create Vk device with protectedContent=%d", protectedContent);
+    VkDevice device;
+    VK_CHECK(vkCreateDevice(physicalDevice, &deviceInfo, nullptr, &device));
+    ALOGD("Trying to create Vk device with protectedContent=%d (success)", protectedContent);
+
+    VkQueue graphicsQueue;
+    VK_GET_DEV_PROC(device, GetDeviceQueue);
+    vkGetDeviceQueue(device, graphicsQueueIndex, 0, &graphicsQueue);
+
+    VK_GET_DEV_PROC(device, DeviceWaitIdle);
+    VK_GET_DEV_PROC(device, DestroyDevice);
+    interface.funcs.vkDeviceWaitIdle = vkDeviceWaitIdle;
+    interface.funcs.vkDestroyDevice = vkDestroyDevice;
+
+    VK_GET_DEV_PROC(device, CreateSemaphore);
+    VK_GET_DEV_PROC(device, ImportSemaphoreFdKHR);
+    VK_GET_DEV_PROC(device, GetSemaphoreFdKHR);
+    VK_GET_DEV_PROC(device, DestroySemaphore);
+    interface.funcs.vkCreateSemaphore = vkCreateSemaphore;
+    interface.funcs.vkImportSemaphoreFdKHR = vkImportSemaphoreFdKHR;
+    interface.funcs.vkGetSemaphoreFdKHR = vkGetSemaphoreFdKHR;
+    interface.funcs.vkDestroySemaphore = vkDestroySemaphore;
+
+    // At this point, everything's succeeded and we can continue
+    interface.initialized = true;
+    interface.instance = instance;
+    interface.physicalDevice = physicalDevice;
+    interface.device = device;
+    interface.queue = graphicsQueue;
+    interface.queueIndex = graphicsQueueIndex;
+    interface.apiVersion = physDevProps.properties.apiVersion;
+    // grExtensions already constructed
+    // feature pointers already constructed
+    interface.grGetProc = sGetProc;
+    interface.isProtected = protectedContent;
+    // funcs already initialized
+
+    ALOGD("%s: Success init Vulkan interface", __func__);
+    return interface;
+}
+
+void teardownVulkanInterface(VulkanInterface* interface) {
+    interface->initialized = false;
+
+    if (interface->device != VK_NULL_HANDLE) {
+        interface->funcs.vkDeviceWaitIdle(interface->device);
+        interface->funcs.vkDestroyDevice(interface->device, nullptr);
+        interface->device = VK_NULL_HANDLE;
+    }
+    if (interface->instance != VK_NULL_HANDLE) {
+        interface->funcs.vkDestroyInstance(interface->instance, nullptr);
+        interface->instance = VK_NULL_HANDLE;
+    }
+
+    if (interface->protectedMemoryFeatures) {
+        delete interface->protectedMemoryFeatures;
+    }
+
+    if (interface->samplerYcbcrConversionFeatures) {
+        delete interface->samplerYcbcrConversionFeatures;
+    }
+
+    if (interface->physicalDeviceFeatures2) {
+        delete interface->physicalDeviceFeatures2;
+    }
+
+    interface->samplerYcbcrConversionFeatures = nullptr;
+    interface->physicalDeviceFeatures2 = nullptr;
+    interface->protectedMemoryFeatures = nullptr;
+}
+
+static VulkanInterface sVulkanInterface;
+static VulkanInterface sProtectedContentVulkanInterface;
+
+static void sSetupVulkanInterface() {
+    if (!sVulkanInterface.initialized) {
+        sVulkanInterface = initVulkanInterface(false /* no protected content */);
+        // We will have to abort if non-protected VkDevice creation fails (then nothing works).
+        LOG_ALWAYS_FATAL_IF(!sVulkanInterface.initialized,
+                            "Could not initialize Vulkan RenderEngine!");
+    }
+    if (!sProtectedContentVulkanInterface.initialized) {
+        sProtectedContentVulkanInterface = initVulkanInterface(true /* protected content */);
+        if (!sProtectedContentVulkanInterface.initialized) {
+            ALOGE("Could not initialize protected content Vulkan RenderEngine.");
+        }
+    }
+}
+
+namespace skia {
+
+using base::StringAppendF;
+
+bool SkiaVkRenderEngine::canSupportSkiaVkRenderEngine() {
+    VulkanInterface temp = initVulkanInterface(false /* no protected content */);
+    ALOGD("SkiaVkRenderEngine::canSupportSkiaVkRenderEngine(): initialized == %s.",
+          temp.initialized ? "true" : "false");
+    return temp.initialized;
+}
+
+std::unique_ptr<SkiaVkRenderEngine> SkiaVkRenderEngine::create(
+        const RenderEngineCreationArgs& args) {
+    std::unique_ptr<SkiaVkRenderEngine> engine(new SkiaVkRenderEngine(args));
+    engine->ensureGrContextsCreated();
+
+    if (sVulkanInterface.initialized) {
+        ALOGD("SkiaVkRenderEngine::%s: successfully initialized SkiaVkRenderEngine", __func__);
+        return engine;
+    } else {
+        ALOGD("SkiaVkRenderEngine::%s: could not create SkiaVkRenderEngine. "
+              "Likely insufficient Vulkan support",
+              __func__);
+        return {};
+    }
+}
+
+SkiaVkRenderEngine::SkiaVkRenderEngine(const RenderEngineCreationArgs& args)
+      : SkiaRenderEngine(args.renderEngineType, static_cast<PixelFormat>(args.pixelFormat),
+                         args.useColorManagement, args.supportsBackgroundBlur) {}
+
+SkiaVkRenderEngine::~SkiaVkRenderEngine() {
+    finishRenderingAndAbandonContext();
+}
+
+SkiaRenderEngine::Contexts SkiaVkRenderEngine::createDirectContexts(
+        const GrContextOptions& options) {
+    sSetupVulkanInterface();
+
+    SkiaRenderEngine::Contexts contexts;
+    contexts.first = GrDirectContext::MakeVulkan(sVulkanInterface.getBackendContext(), options);
+    if (supportsProtectedContentImpl()) {
+        contexts.second =
+                GrDirectContext::MakeVulkan(sProtectedContentVulkanInterface.getBackendContext(),
+                                            options);
+    }
+
+    return contexts;
+}
+
+bool SkiaVkRenderEngine::supportsProtectedContentImpl() const {
+    return sProtectedContentVulkanInterface.initialized;
+}
+
+bool SkiaVkRenderEngine::useProtectedContextImpl(GrProtected) {
+    return true;
+}
+
+static void delete_semaphore(void* _semaphore) {
+    VkSemaphore semaphore = (VkSemaphore)_semaphore;
+    sVulkanInterface.destroySemaphore(semaphore);
+}
+
+static void delete_semaphore_protected(void* _semaphore) {
+    VkSemaphore semaphore = (VkSemaphore)_semaphore;
+    sProtectedContentVulkanInterface.destroySemaphore(semaphore);
+}
+
+static VulkanInterface& getVulkanInterface(bool protectedContext) {
+    if (protectedContext) {
+        return sProtectedContentVulkanInterface;
+    }
+    return sVulkanInterface;
+}
+
+void SkiaVkRenderEngine::waitFence(GrDirectContext* grContext, base::borrowed_fd fenceFd) {
+    if (fenceFd.get() < 0) return;
+
+    int dupedFd = dup(fenceFd.get());
+    if (dupedFd < 0) {
+        ALOGE("failed to create duplicate fence fd: %d", dupedFd);
+        sync_wait(fenceFd.get(), -1);
+        return;
+    }
+
+    base::unique_fd fenceDup(dupedFd);
+    VkSemaphore waitSemaphore =
+            getVulkanInterface(isProtected()).importSemaphoreFromSyncFd(fenceDup.release());
+    GrBackendSemaphore beSemaphore;
+    beSemaphore.initVulkan(waitSemaphore);
+    grContext->wait(1, &beSemaphore, true /* delete after wait */);
+}
+
+base::unique_fd SkiaVkRenderEngine::flushAndSubmit(GrDirectContext* grContext) {
+    VkSemaphore signalSemaphore = getVulkanInterface(isProtected()).createExportableSemaphore();
+    GrBackendSemaphore beSignalSemaphore;
+    beSignalSemaphore.initVulkan(signalSemaphore);
+    GrFlushInfo flushInfo;
+    flushInfo.fNumSemaphores = 1;
+    flushInfo.fSignalSemaphores = &beSignalSemaphore;
+    flushInfo.fFinishedProc = isProtected() ? delete_semaphore_protected : delete_semaphore;
+    flushInfo.fFinishedContext = (void*)signalSemaphore;
+    GrSemaphoresSubmitted submitted = grContext->flush(flushInfo);
+    grContext->submit(false /* no cpu sync */);
+    int drawFenceFd = -1;
+    if (GrSemaphoresSubmitted::kYes == submitted) {
+        drawFenceFd = getVulkanInterface(isProtected()).exportSemaphoreSyncFd(signalSemaphore);
+    }
+    base::unique_fd res(drawFenceFd);
+    return res;
+}
+
+int SkiaVkRenderEngine::getContextPriority() {
+    // EGL_CONTEXT_PRIORITY_REALTIME_NV
+    constexpr int kRealtimePriority = 0x3357;
+    if (getVulkanInterface(isProtected()).isRealtimePriority) {
+        return kRealtimePriority;
+    } else {
+        return 0;
+    }
+}
+
+void SkiaVkRenderEngine::appendBackendSpecificInfoToDump(std::string& result) {
+    StringAppendF(&result, "\n ------------RE Vulkan----------\n");
+    StringAppendF(&result, "\n Vulkan device initialized: %d\n", sVulkanInterface.initialized);
+    StringAppendF(&result, "\n Vulkan protected device initialized: %d\n",
+                  sProtectedContentVulkanInterface.initialized);
+
+    if (!sVulkanInterface.initialized) {
+        return;
+    }
+
+    StringAppendF(&result, "\n Instance extensions:\n");
+    for (const auto& name : sVulkanInterface.instanceExtensionNames) {
+        StringAppendF(&result, "\n %s\n", name.c_str());
+    }
+
+    StringAppendF(&result, "\n Device extensions:\n");
+    for (const auto& name : sVulkanInterface.deviceExtensionNames) {
+        StringAppendF(&result, "\n %s\n", name.c_str());
+    }
+}
+
+} // namespace skia
+} // namespace renderengine
+} // namespace android
+#endif // RE_SKIAVK
diff --git a/libs/renderengine/skia/SkiaVkRenderEngine.h b/libs/renderengine/skia/SkiaVkRenderEngine.h
new file mode 100644
index 0000000..1e42b80
--- /dev/null
+++ b/libs/renderengine/skia/SkiaVkRenderEngine.h
@@ -0,0 +1,63 @@
+/*
+ * 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.
+ */
+
+#ifndef SF_SKIAVKRENDERENGINE_H_
+#define SF_SKIAVKRENDERENGINE_H_
+
+// Allow the SkiaVkRenderEngine class to not be compiled, to save space
+// NOTE: In order to build this class, define `RE_SKIAVK` in a build file.
+#ifdef RE_SKIAVK
+
+#include <vk/GrVkBackendContext.h>
+
+#include "SkiaRenderEngine.h"
+
+namespace android {
+namespace renderengine {
+namespace skia {
+
+class SkiaVkRenderEngine : public SkiaRenderEngine {
+public:
+    // Returns false if Vulkan implementation can't support SkiaVkRenderEngine.
+    static bool canSupportSkiaVkRenderEngine();
+    static std::unique_ptr<SkiaVkRenderEngine> create(const RenderEngineCreationArgs& args);
+    ~SkiaVkRenderEngine() override;
+
+    int getContextPriority() override;
+
+protected:
+    // Implementations of abstract SkiaRenderEngine functions specific to
+    // rendering backend
+    virtual SkiaRenderEngine::Contexts createDirectContexts(const GrContextOptions& options);
+    bool supportsProtectedContentImpl() const override;
+    bool useProtectedContextImpl(GrProtected isProtected) override;
+    void waitFence(GrDirectContext* grContext, base::borrowed_fd fenceFd) override;
+    base::unique_fd flushAndSubmit(GrDirectContext* context) override;
+    void appendBackendSpecificInfoToDump(std::string& result) override;
+
+private:
+    SkiaVkRenderEngine(const RenderEngineCreationArgs& args);
+    base::unique_fd flush();
+
+    GrVkBackendContext mBackendContext;
+};
+
+} // namespace skia
+} // namespace renderengine
+} // namespace android
+
+#endif // RE_SKIAVK
+#endif // SF_SKIAVKRENDERENGINE_H_
diff --git a/libs/renderengine/tests/RenderEngineTest.cpp b/libs/renderengine/tests/RenderEngineTest.cpp
index 777d02f..7db95a7 100644
--- a/libs/renderengine/tests/RenderEngineTest.cpp
+++ b/libs/renderengine/tests/RenderEngineTest.cpp
@@ -38,6 +38,7 @@
 #include <fstream>
 
 #include "../skia/SkiaGLRenderEngine.h"
+#include "../skia/SkiaVkRenderEngine.h"
 #include "../threaded/RenderEngineThreaded.h"
 
 constexpr int DEFAULT_DISPLAY_WIDTH = 128;
@@ -107,9 +108,53 @@
     virtual std::string name() = 0;
     virtual renderengine::RenderEngine::RenderEngineType type() = 0;
     virtual std::unique_ptr<renderengine::RenderEngine> createRenderEngine() = 0;
+    virtual bool typeSupported() = 0;
     virtual bool useColorManagement() const = 0;
 };
 
+#ifdef RE_SKIAVK
+class SkiaVkRenderEngineFactory : public RenderEngineFactory {
+public:
+    std::string name() override { return "SkiaVkRenderEngineFactory"; }
+
+    renderengine::RenderEngine::RenderEngineType type() {
+        return renderengine::RenderEngine::RenderEngineType::SKIA_VK;
+    }
+
+    std::unique_ptr<renderengine::RenderEngine> createRenderEngine() override {
+        std::unique_ptr<renderengine::RenderEngine> re = createSkiaVkRenderEngine();
+        return re;
+    }
+
+    std::unique_ptr<renderengine::skia::SkiaVkRenderEngine> createSkiaVkRenderEngine() {
+        renderengine::RenderEngineCreationArgs reCreationArgs =
+                renderengine::RenderEngineCreationArgs::Builder()
+                        .setPixelFormat(static_cast<int>(ui::PixelFormat::RGBA_8888))
+                        .setImageCacheSize(1)
+                        .setUseColorManagerment(false)
+                        .setEnableProtectedContext(false)
+                        .setPrecacheToneMapperShaderOnly(false)
+                        .setSupportsBackgroundBlur(true)
+                        .setContextPriority(renderengine::RenderEngine::ContextPriority::MEDIUM)
+                        .setRenderEngineType(type())
+                        .setUseColorManagerment(useColorManagement())
+                        .build();
+        return renderengine::skia::SkiaVkRenderEngine::create(reCreationArgs);
+    }
+
+    bool typeSupported() override {
+        return skia::SkiaVkRenderEngine::canSupportSkiaVkRenderEngine();
+    }
+    bool useColorManagement() const override { return false; }
+    void skip() { GTEST_SKIP(); }
+};
+
+class SkiaVkCMRenderEngineFactory : public SkiaVkRenderEngineFactory {
+public:
+    bool useColorManagement() const override { return true; }
+};
+#endif // RE_SKIAVK
+
 class SkiaGLESRenderEngineFactory : public RenderEngineFactory {
 public:
     std::string name() override { return "SkiaGLRenderEngineFactory"; }
@@ -133,6 +178,7 @@
         return renderengine::skia::SkiaGLRenderEngine::create(reCreationArgs);
     }
 
+    bool typeSupported() override { return true; }
     bool useColorManagement() const override { return false; }
 };
 
@@ -159,6 +205,7 @@
         return renderengine::skia::SkiaGLRenderEngine::create(reCreationArgs);
     }
 
+    bool typeSupported() override { return true; }
     bool useColorManagement() const override { return true; }
 };
 
@@ -1513,16 +1560,30 @@
     expectBufferColor(Rect(kGreyLevels, 1), generator, 2);
 }
 
+#ifdef RE_SKIAVK
+INSTANTIATE_TEST_SUITE_P(PerRenderEngineType, RenderEngineTest,
+                         testing::Values(std::make_shared<SkiaGLESRenderEngineFactory>(),
+                                         std::make_shared<SkiaGLESCMRenderEngineFactory>(),
+                                         std::make_shared<SkiaVkRenderEngineFactory>(),
+                                         std::make_shared<SkiaVkCMRenderEngineFactory>()));
+#else  // RE_SKIAVK
 INSTANTIATE_TEST_SUITE_P(PerRenderEngineType, RenderEngineTest,
                          testing::Values(std::make_shared<SkiaGLESRenderEngineFactory>(),
                                          std::make_shared<SkiaGLESCMRenderEngineFactory>()));
+#endif // RE_SKIAVK
 
 TEST_P(RenderEngineTest, drawLayers_noLayersToDraw) {
+    if (!GetParam()->typeSupported()) {
+        GTEST_SKIP();
+    }
     initializeRenderEngine();
     drawEmptyLayers();
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillRedBufferAndEmptyBuffer) {
+    if (!GetParam()->typeSupported()) {
+        GTEST_SKIP();
+    }
     initializeRenderEngine();
     renderengine::DisplaySettings settings;
     settings.physicalDisplay = fullscreenRect();
@@ -1547,6 +1608,9 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_withoutBuffers_withColorTransform) {
+    if (!GetParam()->typeSupported()) {
+        GTEST_SKIP();
+    }
     initializeRenderEngine();
 
     renderengine::DisplaySettings settings;
@@ -1578,6 +1642,9 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_nullOutputBuffer) {
+    if (!GetParam()->typeSupported()) {
+        GTEST_SKIP();
+    }
     initializeRenderEngine();
 
     renderengine::DisplaySettings settings;
@@ -1597,56 +1664,89 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillRedBuffer_colorSource) {
+    if (!GetParam()->typeSupported()) {
+        GTEST_SKIP();
+    }
     initializeRenderEngine();
     fillRedBuffer<ColorSourceVariant>();
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillGreenBuffer_colorSource) {
+    if (!GetParam()->typeSupported()) {
+        GTEST_SKIP();
+    }
     initializeRenderEngine();
     fillGreenBuffer<ColorSourceVariant>();
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillBlueBuffer_colorSource) {
+    if (!GetParam()->typeSupported()) {
+        GTEST_SKIP();
+    }
     initializeRenderEngine();
     fillBlueBuffer<ColorSourceVariant>();
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillRedTransparentBuffer_colorSource) {
+    if (!GetParam()->typeSupported()) {
+        GTEST_SKIP();
+    }
     initializeRenderEngine();
     fillRedTransparentBuffer<ColorSourceVariant>();
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillBufferPhysicalOffset_colorSource) {
+    if (!GetParam()->typeSupported()) {
+        GTEST_SKIP();
+    }
     initializeRenderEngine();
     fillBufferPhysicalOffset<ColorSourceVariant>();
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillBufferCheckersRotate0_colorSource) {
+    if (!GetParam()->typeSupported()) {
+        GTEST_SKIP();
+    }
     initializeRenderEngine();
     fillBufferCheckersRotate0<ColorSourceVariant>();
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillBufferCheckersRotate90_colorSource) {
+    if (!GetParam()->typeSupported()) {
+        GTEST_SKIP();
+    }
     initializeRenderEngine();
     fillBufferCheckersRotate90<ColorSourceVariant>();
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillBufferCheckersRotate180_colorSource) {
+    if (!GetParam()->typeSupported()) {
+        GTEST_SKIP();
+    }
     initializeRenderEngine();
     fillBufferCheckersRotate180<ColorSourceVariant>();
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillBufferCheckersRotate270_colorSource) {
+    if (!GetParam()->typeSupported()) {
+        GTEST_SKIP();
+    }
     initializeRenderEngine();
     fillBufferCheckersRotate270<ColorSourceVariant>();
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillBufferLayerTransform_colorSource) {
+    if (!GetParam()->typeSupported()) {
+        GTEST_SKIP();
+    }
     initializeRenderEngine();
     fillBufferLayerTransform<ColorSourceVariant>();
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransform_colorSource) {
+    if (!GetParam()->typeSupported()) {
+        GTEST_SKIP();
+    }
     initializeRenderEngine();
     fillBufferColorTransform<ColorSourceVariant>();
 }
@@ -1654,7 +1754,7 @@
 TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransform_sourceDataspace) {
     const auto& renderEngineFactory = GetParam();
     // skip for non color management
-    if (!renderEngineFactory->useColorManagement()) {
+    if (!renderEngineFactory->typeSupported() || !renderEngineFactory->useColorManagement()) {
         GTEST_SKIP();
     }
 
@@ -1665,7 +1765,7 @@
 TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransform_outputDataspace) {
     const auto& renderEngineFactory = GetParam();
     // skip for non color management
-    if (!renderEngineFactory->useColorManagement()) {
+    if (!renderEngineFactory->typeSupported() || !renderEngineFactory->useColorManagement()) {
         GTEST_SKIP();
     }
 
@@ -1674,81 +1774,129 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillBufferRoundedCorners_colorSource) {
+    if (!GetParam()->typeSupported()) {
+        GTEST_SKIP();
+    }
     initializeRenderEngine();
     fillBufferWithRoundedCorners<ColorSourceVariant>();
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransformZeroLayerAlpha_colorSource) {
+    if (!GetParam()->typeSupported()) {
+        GTEST_SKIP();
+    }
     initializeRenderEngine();
     fillBufferColorTransformZeroLayerAlpha<ColorSourceVariant>();
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillBufferAndBlurBackground_colorSource) {
+    if (!GetParam()->typeSupported()) {
+        GTEST_SKIP();
+    }
     initializeRenderEngine();
     fillBufferAndBlurBackground<ColorSourceVariant>();
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillSmallLayerAndBlurBackground_colorSource) {
+    if (!GetParam()->typeSupported()) {
+        GTEST_SKIP();
+    }
     initializeRenderEngine();
     fillSmallLayerAndBlurBackground<ColorSourceVariant>();
 }
 
 TEST_P(RenderEngineTest, drawLayers_overlayCorners_colorSource) {
+    if (!GetParam()->typeSupported()) {
+        GTEST_SKIP();
+    }
     initializeRenderEngine();
     overlayCorners<ColorSourceVariant>();
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillRedBuffer_opaqueBufferSource) {
+    if (!GetParam()->typeSupported()) {
+        GTEST_SKIP();
+    }
     initializeRenderEngine();
     fillRedBuffer<BufferSourceVariant<ForceOpaqueBufferVariant>>();
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillGreenBuffer_opaqueBufferSource) {
+    if (!GetParam()->typeSupported()) {
+        GTEST_SKIP();
+    }
     initializeRenderEngine();
     fillGreenBuffer<BufferSourceVariant<ForceOpaqueBufferVariant>>();
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillBlueBuffer_opaqueBufferSource) {
+    if (!GetParam()->typeSupported()) {
+        GTEST_SKIP();
+    }
     initializeRenderEngine();
     fillBlueBuffer<BufferSourceVariant<ForceOpaqueBufferVariant>>();
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillRedTransparentBuffer_opaqueBufferSource) {
+    if (!GetParam()->typeSupported()) {
+        GTEST_SKIP();
+    }
     initializeRenderEngine();
     fillRedTransparentBuffer<BufferSourceVariant<ForceOpaqueBufferVariant>>();
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillBufferPhysicalOffset_opaqueBufferSource) {
+    if (!GetParam()->typeSupported()) {
+        GTEST_SKIP();
+    }
     initializeRenderEngine();
     fillBufferPhysicalOffset<BufferSourceVariant<ForceOpaqueBufferVariant>>();
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillBufferCheckersRotate0_opaqueBufferSource) {
+    if (!GetParam()->typeSupported()) {
+        GTEST_SKIP();
+    }
     initializeRenderEngine();
     fillBufferCheckersRotate0<BufferSourceVariant<ForceOpaqueBufferVariant>>();
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillBufferCheckersRotate90_opaqueBufferSource) {
+    if (!GetParam()->typeSupported()) {
+        GTEST_SKIP();
+    }
     initializeRenderEngine();
     fillBufferCheckersRotate90<BufferSourceVariant<ForceOpaqueBufferVariant>>();
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillBufferCheckersRotate180_opaqueBufferSource) {
+    if (!GetParam()->typeSupported()) {
+        GTEST_SKIP();
+    }
     initializeRenderEngine();
     fillBufferCheckersRotate180<BufferSourceVariant<ForceOpaqueBufferVariant>>();
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillBufferCheckersRotate270_opaqueBufferSource) {
+    if (!GetParam()->typeSupported()) {
+        GTEST_SKIP();
+    }
     initializeRenderEngine();
     fillBufferCheckersRotate270<BufferSourceVariant<ForceOpaqueBufferVariant>>();
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillBufferLayerTransform_opaqueBufferSource) {
+    if (!GetParam()->typeSupported()) {
+        GTEST_SKIP();
+    }
     initializeRenderEngine();
     fillBufferLayerTransform<BufferSourceVariant<ForceOpaqueBufferVariant>>();
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransform_opaqueBufferSource) {
+    if (!GetParam()->typeSupported()) {
+        GTEST_SKIP();
+    }
     initializeRenderEngine();
     fillBufferColorTransform<BufferSourceVariant<ForceOpaqueBufferVariant>>();
 }
@@ -1756,7 +1904,7 @@
 TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransformAndSourceDataspace_opaqueBufferSource) {
     const auto& renderEngineFactory = GetParam();
     // skip for non color management
-    if (!renderEngineFactory->useColorManagement()) {
+    if (!renderEngineFactory->typeSupported() || !renderEngineFactory->useColorManagement()) {
         GTEST_SKIP();
     }
 
@@ -1767,7 +1915,7 @@
 TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransformAndOutputDataspace_opaqueBufferSource) {
     const auto& renderEngineFactory = GetParam();
     // skip for non color management
-    if (!renderEngineFactory->useColorManagement()) {
+    if (!renderEngineFactory->typeSupported() || !renderEngineFactory->useColorManagement()) {
         GTEST_SKIP();
     }
 
@@ -1776,81 +1924,129 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillBufferRoundedCorners_opaqueBufferSource) {
+    if (!GetParam()->typeSupported()) {
+        GTEST_SKIP();
+    }
     initializeRenderEngine();
     fillBufferWithRoundedCorners<BufferSourceVariant<ForceOpaqueBufferVariant>>();
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransformZeroLayerAlpha_opaqueBufferSource) {
+    if (!GetParam()->typeSupported()) {
+        GTEST_SKIP();
+    }
     initializeRenderEngine();
     fillBufferColorTransformZeroLayerAlpha<BufferSourceVariant<ForceOpaqueBufferVariant>>();
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillBufferAndBlurBackground_opaqueBufferSource) {
+    if (!GetParam()->typeSupported()) {
+        GTEST_SKIP();
+    }
     initializeRenderEngine();
     fillBufferAndBlurBackground<BufferSourceVariant<ForceOpaqueBufferVariant>>();
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillSmallLayerAndBlurBackground_opaqueBufferSource) {
+    if (!GetParam()->typeSupported()) {
+        GTEST_SKIP();
+    }
     initializeRenderEngine();
     fillSmallLayerAndBlurBackground<BufferSourceVariant<ForceOpaqueBufferVariant>>();
 }
 
 TEST_P(RenderEngineTest, drawLayers_overlayCorners_opaqueBufferSource) {
+    if (!GetParam()->typeSupported()) {
+        GTEST_SKIP();
+    }
     initializeRenderEngine();
     overlayCorners<BufferSourceVariant<ForceOpaqueBufferVariant>>();
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillRedBuffer_bufferSource) {
+    if (!GetParam()->typeSupported()) {
+        GTEST_SKIP();
+    }
     initializeRenderEngine();
     fillRedBuffer<BufferSourceVariant<RelaxOpaqueBufferVariant>>();
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillGreenBuffer_bufferSource) {
+    if (!GetParam()->typeSupported()) {
+        GTEST_SKIP();
+    }
     initializeRenderEngine();
     fillGreenBuffer<BufferSourceVariant<RelaxOpaqueBufferVariant>>();
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillBlueBuffer_bufferSource) {
+    if (!GetParam()->typeSupported()) {
+        GTEST_SKIP();
+    }
     initializeRenderEngine();
     fillBlueBuffer<BufferSourceVariant<RelaxOpaqueBufferVariant>>();
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillRedTransparentBuffer_bufferSource) {
+    if (!GetParam()->typeSupported()) {
+        GTEST_SKIP();
+    }
     initializeRenderEngine();
     fillRedTransparentBuffer<BufferSourceVariant<RelaxOpaqueBufferVariant>>();
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillBufferPhysicalOffset_bufferSource) {
+    if (!GetParam()->typeSupported()) {
+        GTEST_SKIP();
+    }
     initializeRenderEngine();
     fillBufferPhysicalOffset<BufferSourceVariant<RelaxOpaqueBufferVariant>>();
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillBufferCheckersRotate0_bufferSource) {
+    if (!GetParam()->typeSupported()) {
+        GTEST_SKIP();
+    }
     initializeRenderEngine();
     fillBufferCheckersRotate0<BufferSourceVariant<RelaxOpaqueBufferVariant>>();
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillBufferCheckersRotate90_bufferSource) {
+    if (!GetParam()->typeSupported()) {
+        GTEST_SKIP();
+    }
     initializeRenderEngine();
     fillBufferCheckersRotate90<BufferSourceVariant<RelaxOpaqueBufferVariant>>();
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillBufferCheckersRotate180_bufferSource) {
+    if (!GetParam()->typeSupported()) {
+        GTEST_SKIP();
+    }
     initializeRenderEngine();
     fillBufferCheckersRotate180<BufferSourceVariant<RelaxOpaqueBufferVariant>>();
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillBufferCheckersRotate270_bufferSource) {
+    if (!GetParam()->typeSupported()) {
+        GTEST_SKIP();
+    }
     initializeRenderEngine();
     fillBufferCheckersRotate270<BufferSourceVariant<RelaxOpaqueBufferVariant>>();
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillBufferLayerTransform_bufferSource) {
+    if (!GetParam()->typeSupported()) {
+        GTEST_SKIP();
+    }
     initializeRenderEngine();
     fillBufferLayerTransform<BufferSourceVariant<RelaxOpaqueBufferVariant>>();
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransform_bufferSource) {
+    if (!GetParam()->typeSupported()) {
+        GTEST_SKIP();
+    }
     initializeRenderEngine();
     fillBufferColorTransform<BufferSourceVariant<RelaxOpaqueBufferVariant>>();
 }
@@ -1858,7 +2054,7 @@
 TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransformAndSourceDataspace_bufferSource) {
     const auto& renderEngineFactory = GetParam();
     // skip for non color management
-    if (!renderEngineFactory->useColorManagement()) {
+    if (!renderEngineFactory->typeSupported() || !renderEngineFactory->useColorManagement()) {
         GTEST_SKIP();
     }
 
@@ -1869,7 +2065,7 @@
 TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransformAndOutputDataspace_bufferSource) {
     const auto& renderEngineFactory = GetParam();
     // skip for non color management
-    if (!renderEngineFactory->useColorManagement()) {
+    if (!renderEngineFactory->typeSupported() || !renderEngineFactory->useColorManagement()) {
         GTEST_SKIP();
     }
 
@@ -1878,46 +2074,73 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillBufferRoundedCorners_bufferSource) {
+    if (!GetParam()->typeSupported()) {
+        GTEST_SKIP();
+    }
     initializeRenderEngine();
     fillBufferWithRoundedCorners<BufferSourceVariant<RelaxOpaqueBufferVariant>>();
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransformZeroLayerAlpha_bufferSource) {
+    if (!GetParam()->typeSupported()) {
+        GTEST_SKIP();
+    }
     initializeRenderEngine();
     fillBufferColorTransformZeroLayerAlpha<BufferSourceVariant<RelaxOpaqueBufferVariant>>();
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillBufferAndBlurBackground_bufferSource) {
+    if (!GetParam()->typeSupported()) {
+        GTEST_SKIP();
+    }
     initializeRenderEngine();
     fillBufferAndBlurBackground<BufferSourceVariant<RelaxOpaqueBufferVariant>>();
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillSmallLayerAndBlurBackground_bufferSource) {
+    if (!GetParam()->typeSupported()) {
+        GTEST_SKIP();
+    }
     initializeRenderEngine();
     fillSmallLayerAndBlurBackground<BufferSourceVariant<RelaxOpaqueBufferVariant>>();
 }
 
 TEST_P(RenderEngineTest, drawLayers_overlayCorners_bufferSource) {
+    if (!GetParam()->typeSupported()) {
+        GTEST_SKIP();
+    }
     initializeRenderEngine();
     overlayCorners<BufferSourceVariant<RelaxOpaqueBufferVariant>>();
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillBufferTextureTransform) {
+    if (!GetParam()->typeSupported()) {
+        GTEST_SKIP();
+    }
     initializeRenderEngine();
     fillBufferTextureTransform();
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillBuffer_premultipliesAlpha) {
+    if (!GetParam()->typeSupported()) {
+        GTEST_SKIP();
+    }
     initializeRenderEngine();
     fillBufferWithPremultiplyAlpha();
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillBuffer_withoutPremultiplyingAlpha) {
+    if (!GetParam()->typeSupported()) {
+        GTEST_SKIP();
+    }
     initializeRenderEngine();
     fillBufferWithoutPremultiplyAlpha();
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillShadow_castsWithoutCasterLayer) {
+    if (!GetParam()->typeSupported()) {
+        GTEST_SKIP();
+    }
     initializeRenderEngine();
 
     const ubyte4 backgroundColor(static_cast<uint8_t>(255), static_cast<uint8_t>(255),
@@ -1934,6 +2157,9 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillShadow_casterLayerMinSize) {
+    if (!GetParam()->typeSupported()) {
+        GTEST_SKIP();
+    }
     initializeRenderEngine();
 
     const ubyte4 casterColor(static_cast<uint8_t>(255), static_cast<uint8_t>(0),
@@ -1955,6 +2181,9 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillShadow_casterColorLayer) {
+    if (!GetParam()->typeSupported()) {
+        GTEST_SKIP();
+    }
     initializeRenderEngine();
 
     const ubyte4 casterColor(static_cast<uint8_t>(255), static_cast<uint8_t>(0),
@@ -1977,6 +2206,9 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillShadow_casterOpaqueBufferLayer) {
+    if (!GetParam()->typeSupported()) {
+        GTEST_SKIP();
+    }
     initializeRenderEngine();
 
     const ubyte4 casterColor(static_cast<uint8_t>(255), static_cast<uint8_t>(0),
@@ -2000,6 +2232,9 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillShadow_casterWithRoundedCorner) {
+    if (!GetParam()->typeSupported()) {
+        GTEST_SKIP();
+    }
     initializeRenderEngine();
 
     const ubyte4 casterColor(static_cast<uint8_t>(255), static_cast<uint8_t>(0),
@@ -2024,6 +2259,9 @@
 }
 
 TEST_P(RenderEngineTest, drawLayers_fillShadow_translucentCasterWithAlpha) {
+    if (!GetParam()->typeSupported()) {
+        GTEST_SKIP();
+    }
     initializeRenderEngine();
 
     const ubyte4 casterColor(255, 0, 0, 255);
@@ -2051,6 +2289,9 @@
 }
 
 TEST_P(RenderEngineTest, cleanupPostRender_cleansUpOnce) {
+    if (!GetParam()->typeSupported()) {
+        GTEST_SKIP();
+    }
     initializeRenderEngine();
 
     renderengine::DisplaySettings settings;
@@ -2081,12 +2322,20 @@
     fenceTwo->waitForever(LOG_TAG);
 
     // Only cleanup the first time.
-    EXPECT_FALSE(mRE->canSkipPostRenderCleanup());
-    mRE->cleanupPostRender();
-    EXPECT_TRUE(mRE->canSkipPostRenderCleanup());
+    if (mRE->canSkipPostRenderCleanup()) {
+        // Skia's Vk backend may keep the texture alive beyond drawLayersInternal, so
+        // it never gets added to the cleanup list. In those cases, we can skip.
+        EXPECT_TRUE(GetParam()->type() == renderengine::RenderEngine::RenderEngineType::SKIA_VK);
+    } else {
+        mRE->cleanupPostRender();
+        EXPECT_TRUE(mRE->canSkipPostRenderCleanup());
+    }
 }
 
 TEST_P(RenderEngineTest, testRoundedCornersCrop) {
+    if (!GetParam()->typeSupported()) {
+        GTEST_SKIP();
+    }
     initializeRenderEngine();
 
     renderengine::DisplaySettings settings;
@@ -2137,6 +2386,9 @@
 }
 
 TEST_P(RenderEngineTest, testRoundedCornersParentCrop) {
+    if (!GetParam()->typeSupported()) {
+        GTEST_SKIP();
+    }
     initializeRenderEngine();
 
     renderengine::DisplaySettings settings;
@@ -2182,6 +2434,9 @@
 }
 
 TEST_P(RenderEngineTest, testRoundedCornersParentCropSmallBounds) {
+    if (!GetParam()->typeSupported()) {
+        GTEST_SKIP();
+    }
     initializeRenderEngine();
 
     renderengine::DisplaySettings settings;
@@ -2259,6 +2514,9 @@
 }
 
 TEST_P(RenderEngineTest, testClear) {
+    if (!GetParam()->typeSupported()) {
+        GTEST_SKIP();
+    }
     initializeRenderEngine();
 
     const auto rect = fullscreenRect();
@@ -2288,6 +2546,9 @@
 }
 
 TEST_P(RenderEngineTest, testDisableBlendingBuffer) {
+    if (!GetParam()->typeSupported()) {
+        GTEST_SKIP();
+    }
     initializeRenderEngine();
 
     const auto rect = Rect(0, 0, 1, 1);
@@ -2385,6 +2646,9 @@
 }
 
 TEST_P(RenderEngineTest, testDimming) {
+    if (!GetParam()->typeSupported()) {
+        GTEST_SKIP();
+    }
     initializeRenderEngine();
 
     const ui::Dataspace dataspace = ui::Dataspace::V0_SRGB_LINEAR;
@@ -2457,6 +2721,9 @@
 }
 
 TEST_P(RenderEngineTest, testDimming_inGammaSpace) {
+    if (!GetParam()->typeSupported()) {
+        GTEST_SKIP();
+    }
     initializeRenderEngine();
 
     const ui::Dataspace dataspace = static_cast<ui::Dataspace>(ui::Dataspace::STANDARD_BT709 |
@@ -2532,6 +2799,9 @@
 }
 
 TEST_P(RenderEngineTest, testDimming_inGammaSpace_withDisplayColorTransform) {
+    if (!GetParam()->typeSupported()) {
+        GTEST_SKIP();
+    }
     initializeRenderEngine();
 
     const ui::Dataspace dataspace = static_cast<ui::Dataspace>(ui::Dataspace::STANDARD_BT709 |
@@ -2592,6 +2862,9 @@
 }
 
 TEST_P(RenderEngineTest, testDimming_inGammaSpace_withDisplayColorTransform_deviceHandles) {
+    if (!GetParam()->typeSupported()) {
+        GTEST_SKIP();
+    }
     initializeRenderEngine();
 
     const ui::Dataspace dataspace = static_cast<ui::Dataspace>(ui::Dataspace::STANDARD_BT709 |
@@ -2653,6 +2926,9 @@
 }
 
 TEST_P(RenderEngineTest, testDimming_withoutTargetLuminance) {
+    if (!GetParam()->typeSupported()) {
+        GTEST_SKIP();
+    }
     initializeRenderEngine();
 
     const auto displayRect = Rect(2, 1);
@@ -2704,6 +2980,9 @@
 }
 
 TEST_P(RenderEngineTest, test_isOpaque) {
+    if (!GetParam()->typeSupported()) {
+        GTEST_SKIP();
+    }
     initializeRenderEngine();
 
     const auto rect = Rect(0, 0, 1, 1);
@@ -2755,7 +3034,7 @@
 }
 
 TEST_P(RenderEngineTest, test_tonemapPQMatches) {
-    if (!GetParam()->useColorManagement()) {
+    if (!GetParam()->typeSupported() || !GetParam()->useColorManagement()) {
         GTEST_SKIP();
     }
 
@@ -2772,7 +3051,7 @@
 }
 
 TEST_P(RenderEngineTest, test_tonemapHLGMatches) {
-    if (!GetParam()->useColorManagement()) {
+    if (!GetParam()->typeSupported() || !GetParam()->useColorManagement()) {
         GTEST_SKIP();
     }
 
@@ -2789,6 +3068,9 @@
 }
 
 TEST_P(RenderEngineTest, r8_behaves_as_mask) {
+    if (!GetParam()->typeSupported()) {
+        GTEST_SKIP();
+    }
     initializeRenderEngine();
 
     const auto r8Buffer = allocateR8Buffer(2, 1);
@@ -2846,6 +3128,9 @@
 }
 
 TEST_P(RenderEngineTest, r8_respects_color_transform) {
+    if (!GetParam()->typeSupported()) {
+        GTEST_SKIP();
+    }
     initializeRenderEngine();
 
     const auto r8Buffer = allocateR8Buffer(2, 1);
@@ -2908,6 +3193,9 @@
 }
 
 TEST_P(RenderEngineTest, r8_respects_color_transform_when_device_handles) {
+    if (!GetParam()->typeSupported()) {
+        GTEST_SKIP();
+    }
     initializeRenderEngine();
 
     const auto r8Buffer = allocateR8Buffer(2, 1);
@@ -2973,6 +3261,9 @@
 }
 
 TEST_P(RenderEngineTest, primeShaderCache) {
+    if (!GetParam()->typeSupported()) {
+        GTEST_SKIP();
+    }
     initializeRenderEngine();
 
     auto fut = mRE->primeCache();
diff --git a/libs/renderengine/tests/RenderEngineThreadedTest.cpp b/libs/renderengine/tests/RenderEngineThreadedTest.cpp
index 1a96289..fe3a16d 100644
--- a/libs/renderengine/tests/RenderEngineThreadedTest.cpp
+++ b/libs/renderengine/tests/RenderEngineThreadedTest.cpp
@@ -17,8 +17,10 @@
 #include <cutils/properties.h>
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
+#include <hardware/gralloc.h>
 #include <renderengine/impl/ExternalTexture.h>
 #include <renderengine/mock/RenderEngine.h>
+#include <ui/PixelFormat.h>
 #include "../threaded/RenderEngineThreaded.h"
 
 namespace android {
@@ -95,18 +97,6 @@
     ASSERT_EQ(dims, result);
 }
 
-TEST_F(RenderEngineThreadedTest, isProtected_returnsFalse) {
-    EXPECT_CALL(*mRenderEngine, isProtected()).WillOnce(Return(false));
-    status_t result = mThreadedRE->isProtected();
-    ASSERT_EQ(false, result);
-}
-
-TEST_F(RenderEngineThreadedTest, isProtected_returnsTrue) {
-    EXPECT_CALL(*mRenderEngine, isProtected()).WillOnce(Return(true));
-    size_t result = mThreadedRE->isProtected();
-    ASSERT_EQ(true, result);
-}
-
 TEST_F(RenderEngineThreadedTest, supportsProtectedContent_returnsFalse) {
     EXPECT_CALL(*mRenderEngine, supportsProtectedContent()).WillOnce(Return(false));
     status_t result = mThreadedRE->supportsProtectedContent();
@@ -119,28 +109,6 @@
     ASSERT_EQ(true, result);
 }
 
-TEST_F(RenderEngineThreadedTest, useProtectedContext) {
-    EXPECT_CALL(*mRenderEngine, useProtectedContext(true));
-    auto& ipExpect = EXPECT_CALL(*mRenderEngine, isProtected()).WillOnce(Return(false));
-    EXPECT_CALL(*mRenderEngine, supportsProtectedContent()).WillOnce(Return(true));
-    EXPECT_CALL(*mRenderEngine, isProtected()).After(ipExpect).WillOnce(Return(true));
-
-    mThreadedRE->useProtectedContext(true);
-    ASSERT_EQ(true, mThreadedRE->isProtected());
-
-    // call ANY synchronous function to ensure that useProtectedContext has completed.
-    mThreadedRE->getContextPriority();
-    ASSERT_EQ(true, mThreadedRE->isProtected());
-}
-
-TEST_F(RenderEngineThreadedTest, useProtectedContext_quickReject) {
-    EXPECT_CALL(*mRenderEngine, useProtectedContext(false)).Times(0);
-    EXPECT_CALL(*mRenderEngine, isProtected()).WillOnce(Return(false));
-    mThreadedRE->useProtectedContext(false);
-    // call ANY synchronous function to ensure that useProtectedContext has completed.
-    mThreadedRE->getContextPriority();
-}
-
 TEST_F(RenderEngineThreadedTest, PostRenderCleanup_skipped) {
     EXPECT_CALL(*mRenderEngine, canSkipPostRenderCleanup()).WillOnce(Return(true));
     EXPECT_CALL(*mRenderEngine, cleanupPostRender()).Times(0);
@@ -182,6 +150,68 @@
 
     base::unique_fd bufferFence;
 
+    EXPECT_CALL(*mRenderEngine, useProtectedContext(false));
+    EXPECT_CALL(*mRenderEngine, drawLayersInternal)
+            .WillOnce([&](const std::shared_ptr<std::promise<FenceResult>>&& resultPromise,
+                          const renderengine::DisplaySettings&,
+                          const std::vector<renderengine::LayerSettings>&,
+                          const std::shared_ptr<renderengine::ExternalTexture>&, const bool,
+                          base::unique_fd&&) { resultPromise->set_value(Fence::NO_FENCE); });
+
+    ftl::Future<FenceResult> future =
+            mThreadedRE->drawLayers(settings, layers, buffer, false, std::move(bufferFence));
+    ASSERT_TRUE(future.valid());
+    auto result = future.get();
+    ASSERT_TRUE(result.ok());
+}
+
+TEST_F(RenderEngineThreadedTest, drawLayers_protectedLayer) {
+    renderengine::DisplaySettings settings;
+    auto layerBuffer = sp<GraphicBuffer>::make();
+    layerBuffer->usage |= GRALLOC_USAGE_PROTECTED;
+    renderengine::LayerSettings layer;
+    layer.source.buffer.buffer = std::make_shared<
+            renderengine::impl::ExternalTexture>(std::move(layerBuffer), *mRenderEngine,
+                                                 renderengine::impl::ExternalTexture::Usage::
+                                                         READABLE);
+    std::vector<renderengine::LayerSettings> layers = {std::move(layer)};
+    std::shared_ptr<renderengine::ExternalTexture> buffer = std::make_shared<
+            renderengine::impl::
+                    ExternalTexture>(sp<GraphicBuffer>::make(), *mRenderEngine,
+                                     renderengine::impl::ExternalTexture::Usage::READABLE |
+                                             renderengine::impl::ExternalTexture::Usage::WRITEABLE);
+
+    base::unique_fd bufferFence;
+
+    EXPECT_CALL(*mRenderEngine, useProtectedContext(true));
+    EXPECT_CALL(*mRenderEngine, drawLayersInternal)
+            .WillOnce([&](const std::shared_ptr<std::promise<FenceResult>>&& resultPromise,
+                          const renderengine::DisplaySettings&,
+                          const std::vector<renderengine::LayerSettings>&,
+                          const std::shared_ptr<renderengine::ExternalTexture>&, const bool,
+                          base::unique_fd&&) { resultPromise->set_value(Fence::NO_FENCE); });
+
+    ftl::Future<FenceResult> future =
+            mThreadedRE->drawLayers(settings, layers, buffer, false, std::move(bufferFence));
+    ASSERT_TRUE(future.valid());
+    auto result = future.get();
+    ASSERT_TRUE(result.ok());
+}
+
+TEST_F(RenderEngineThreadedTest, drawLayers_protectedOutputBuffer) {
+    renderengine::DisplaySettings settings;
+    std::vector<renderengine::LayerSettings> layers;
+    auto graphicBuffer = sp<GraphicBuffer>::make();
+    graphicBuffer->usage |= GRALLOC_USAGE_PROTECTED;
+    std::shared_ptr<renderengine::ExternalTexture> buffer = std::make_shared<
+            renderengine::impl::
+                    ExternalTexture>(std::move(graphicBuffer), *mRenderEngine,
+                                     renderengine::impl::ExternalTexture::Usage::READABLE |
+                                             renderengine::impl::ExternalTexture::Usage::WRITEABLE);
+
+    base::unique_fd bufferFence;
+
+    EXPECT_CALL(*mRenderEngine, useProtectedContext(true));
     EXPECT_CALL(*mRenderEngine, drawLayersInternal)
             .WillOnce([&](const std::shared_ptr<std::promise<FenceResult>>&& resultPromise,
                           const renderengine::DisplaySettings&,
diff --git a/libs/renderengine/threaded/RenderEngineThreaded.cpp b/libs/renderengine/threaded/RenderEngineThreaded.cpp
index b41e843..8aa41b3 100644
--- a/libs/renderengine/threaded/RenderEngineThreaded.cpp
+++ b/libs/renderengine/threaded/RenderEngineThreaded.cpp
@@ -90,7 +90,6 @@
     }
 
     mRenderEngine = factory();
-    mIsProtected = mRenderEngine->isProtected();
 
     pthread_setname_np(pthread_self(), mThreadName);
 
@@ -255,41 +254,11 @@
     return mRenderEngine->getMaxViewportDims();
 }
 
-bool RenderEngineThreaded::isProtected() const {
-    waitUntilInitialized();
-    std::lock_guard lock(mThreadMutex);
-    return mIsProtected;
-}
-
 bool RenderEngineThreaded::supportsProtectedContent() const {
     waitUntilInitialized();
     return mRenderEngine->supportsProtectedContent();
 }
 
-void RenderEngineThreaded::useProtectedContext(bool useProtectedContext) {
-    if (isProtected() == useProtectedContext ||
-        (useProtectedContext && !supportsProtectedContent())) {
-        return;
-    }
-
-    {
-        std::lock_guard lock(mThreadMutex);
-        mFunctionCalls.push([useProtectedContext, this](renderengine::RenderEngine& instance) {
-            ATRACE_NAME("REThreaded::useProtectedContext");
-            instance.useProtectedContext(useProtectedContext);
-            if (instance.isProtected() != useProtectedContext) {
-                ALOGE("Failed to switch RenderEngine context.");
-                // reset the cached mIsProtected value to a good state, but this does not
-                // prevent other callers of this method and isProtected from reading the
-                // invalid cached value.
-                mIsProtected = instance.isProtected();
-            }
-        });
-        mIsProtected = useProtectedContext;
-    }
-    mCondition.notify_one();
-}
-
 void RenderEngineThreaded::cleanupPostRender() {
     if (canSkipPostRenderCleanup()) {
         return;
@@ -334,6 +303,7 @@
         mFunctionCalls.push([resultPromise, display, layers, buffer, useFramebufferCache,
                              fd](renderengine::RenderEngine& instance) {
             ATRACE_NAME("REThreaded::drawLayers");
+            instance.updateProtectedContext(layers, buffer);
             instance.drawLayersInternal(std::move(resultPromise), display, layers, buffer,
                                         useFramebufferCache, base::unique_fd(fd));
         });
diff --git a/libs/renderengine/threaded/RenderEngineThreaded.h b/libs/renderengine/threaded/RenderEngineThreaded.h
index bf2ebea..168e2d2 100644
--- a/libs/renderengine/threaded/RenderEngineThreaded.h
+++ b/libs/renderengine/threaded/RenderEngineThreaded.h
@@ -51,9 +51,7 @@
     size_t getMaxTextureSize() const override;
     size_t getMaxViewportDims() const override;
 
-    bool isProtected() const override;
     bool supportsProtectedContent() const override;
-    void useProtectedContext(bool useProtectedContext) override;
     void cleanupPostRender() override;
 
     ftl::Future<FenceResult> drawLayers(const DisplaySettings& display,
@@ -84,6 +82,9 @@
     void waitUntilInitialized() const;
     static status_t setSchedFifo(bool enabled);
 
+    // No-op. This method is only called on leaf implementations of RenderEngine.
+    void useProtectedContext(bool) override {}
+
     /* ------------------------------------------------------------------------
      * Threading
      */
@@ -107,7 +108,6 @@
      * Render Engine
      */
     std::unique_ptr<renderengine::RenderEngine> mRenderEngine;
-    std::atomic<bool> mIsProtected = false;
 };
 } // namespace threaded
 } // namespace renderengine
diff --git a/libs/sensor/Android.bp b/libs/sensor/Android.bp
index 2b93c6e..b6b9cc4 100644
--- a/libs/sensor/Android.bp
+++ b/libs/sensor/Android.bp
@@ -21,9 +21,10 @@
     default_applicable_licenses: ["frameworks_native_license"],
 }
 
-cc_library_shared {
+cc_library {
     name: "libsensor",
 
+    host_supported: true,
     cflags: [
         "-Wall",
         "-Werror",
diff --git a/libs/sensor/ISensorServer.cpp b/libs/sensor/ISensorServer.cpp
index 78f692b..2278d39 100644
--- a/libs/sensor/ISensorServer.cpp
+++ b/libs/sensor/ISensorServer.cpp
@@ -42,6 +42,7 @@
     GET_DYNAMIC_SENSOR_LIST,
     CREATE_SENSOR_DIRECT_CONNECTION,
     SET_OPERATION_PARAMETER,
+    GET_RUNTIME_SENSOR_LIST,
 };
 
 class BpSensorServer : public BpInterface<ISensorServer>
@@ -90,6 +91,25 @@
         return v;
     }
 
+    virtual Vector<Sensor> getRuntimeSensorList(const String16& opPackageName, int deviceId)
+    {
+        Parcel data, reply;
+        data.writeInterfaceToken(ISensorServer::getInterfaceDescriptor());
+        data.writeString16(opPackageName);
+        data.writeInt32(deviceId);
+        remote()->transact(GET_RUNTIME_SENSOR_LIST, data, &reply);
+        Sensor s;
+        Vector<Sensor> v;
+        uint32_t n = reply.readUint32();
+        v.setCapacity(n);
+        while (n) {
+            n--;
+            reply.read(s);
+            v.add(s);
+        }
+        return v;
+    }
+
     virtual sp<ISensorEventConnection> createSensorEventConnection(const String8& packageName,
              int mode, const String16& opPackageName, const String16& attributionTag)
     {
@@ -194,6 +214,18 @@
             }
             return NO_ERROR;
         }
+        case GET_RUNTIME_SENSOR_LIST: {
+            CHECK_INTERFACE(ISensorServer, data, reply);
+            const String16& opPackageName = data.readString16();
+            const int deviceId = data.readInt32();
+            Vector<Sensor> v(getRuntimeSensorList(opPackageName, deviceId));
+            size_t n = v.size();
+            reply->writeUint32(static_cast<uint32_t>(n));
+            for (size_t i = 0; i < n; i++) {
+                reply->write(v[i]);
+            }
+            return NO_ERROR;
+        }
         case CREATE_SENSOR_DIRECT_CONNECTION: {
             CHECK_INTERFACE(ISensorServer, data, reply);
             const String16& opPackageName = data.readString16();
diff --git a/libs/sensor/SensorManager.cpp b/libs/sensor/SensorManager.cpp
index 0ba9704..2748276 100644
--- a/libs/sensor/SensorManager.cpp
+++ b/libs/sensor/SensorManager.cpp
@@ -201,6 +201,19 @@
     return static_cast<ssize_t>(count);
 }
 
+ssize_t SensorManager::getRuntimeSensorList(int deviceId, Vector<Sensor>& runtimeSensors) {
+    Mutex::Autolock _l(mLock);
+    status_t err = assertStateLocked();
+    if (err < 0) {
+        return static_cast<ssize_t>(err);
+    }
+
+    runtimeSensors = mSensorServer->getRuntimeSensorList(mOpPackageName, deviceId);
+    size_t count = runtimeSensors.size();
+
+    return static_cast<ssize_t>(count);
+}
+
 ssize_t SensorManager::getDynamicSensorList(Sensor const* const** list) {
     Mutex::Autolock _l(mLock);
     status_t err = assertStateLocked();
diff --git a/libs/sensor/include/sensor/ISensorServer.h b/libs/sensor/include/sensor/ISensorServer.h
index ce5c672..3295196 100644
--- a/libs/sensor/include/sensor/ISensorServer.h
+++ b/libs/sensor/include/sensor/ISensorServer.h
@@ -43,6 +43,7 @@
 
     virtual Vector<Sensor> getSensorList(const String16& opPackageName) = 0;
     virtual Vector<Sensor> getDynamicSensorList(const String16& opPackageName) = 0;
+    virtual Vector<Sensor> getRuntimeSensorList(const String16& opPackageName, int deviceId) = 0;
 
     virtual sp<ISensorEventConnection> createSensorEventConnection(const String8& packageName,
              int mode, const String16& opPackageName, const String16& attributionTag) = 0;
diff --git a/libs/sensor/include/sensor/SensorManager.h b/libs/sensor/include/sensor/SensorManager.h
index 8d0a8a4..0798da2 100644
--- a/libs/sensor/include/sensor/SensorManager.h
+++ b/libs/sensor/include/sensor/SensorManager.h
@@ -59,6 +59,7 @@
     ssize_t getSensorList(Sensor const* const** list);
     ssize_t getDynamicSensorList(Vector<Sensor>& list);
     ssize_t getDynamicSensorList(Sensor const* const** list);
+    ssize_t getRuntimeSensorList(int deviceId, Vector<Sensor>& list);
     Sensor const* getDefaultSensor(int type);
     sp<SensorEventQueue> createEventQueue(
         String8 packageName = String8(""), int mode = 0, String16 attributionTag = String16(""));
diff --git a/libs/shaders/include/shaders/shaders.h b/libs/shaders/include/shaders/shaders.h
index 2a4a370..42b0cc1 100644
--- a/libs/shaders/include/shaders/shaders.h
+++ b/libs/shaders/include/shaders/shaders.h
@@ -68,6 +68,9 @@
     // fakeInputDataspace is used to essentially masquerade the input dataspace to be the output
     // dataspace for correct conversion to linear colors.
     ui::Dataspace fakeInputDataspace = ui::Dataspace::UNKNOWN;
+
+    enum SkSLType { Shader, ColorFilter };
+    SkSLType type = Shader;
 };
 
 static inline bool operator==(const LinearEffect& lhs, const LinearEffect& rhs) {
@@ -96,6 +99,10 @@
 // 2. Apply color transform matrices in linear space
 std::string buildLinearEffectSkSL(const LinearEffect& linearEffect);
 
+// Generates a shader string that applies color transforms in linear space.
+// This is intended to be plugged into an SkColorFilter
+std::string buildLinearEffectSkSLForColorFilter(const LinearEffect& linearEffect);
+
 // Generates a list of uniforms to set on the LinearEffect shader above.
 std::vector<tonemap::ShaderUniform> buildLinearEffectUniforms(
         const LinearEffect& linearEffect, const mat4& colorTransform, float maxDisplayLuminance,
diff --git a/libs/shaders/shaders.cpp b/libs/shaders/shaders.cpp
index f80e93f..a3c403e 100644
--- a/libs/shaders/shaders.cpp
+++ b/libs/shaders/shaders.cpp
@@ -386,12 +386,23 @@
     }
 }
 
-void generateEffectiveOOTF(bool undoPremultipliedAlpha, std::string& shader) {
-    shader.append(R"(
-        uniform shader child;
-        half4 main(float2 xy) {
-            float4 c = float4(child.eval(xy));
-    )");
+void generateEffectiveOOTF(bool undoPremultipliedAlpha, LinearEffect::SkSLType type,
+                           std::string& shader) {
+    switch (type) {
+        case LinearEffect::SkSLType::ColorFilter:
+            shader.append(R"(
+                half4 main(half4 inputColor) {
+                    float4 c = float4(inputColor);
+            )");
+            break;
+        case LinearEffect::SkSLType::Shader:
+            shader.append(R"(
+                uniform shader child;
+                half4 main(float2 xy) {
+                    float4 c = float4(child.eval(xy));
+            )");
+            break;
+    }
     if (undoPremultipliedAlpha) {
         shader.append(R"(
             c.rgb = c.rgb / (c.a + 0.0019);
@@ -459,7 +470,7 @@
     generateXYZTransforms(shaderString);
     generateOOTF(linearEffect.inputDataspace, linearEffect.outputDataspace, shaderString);
     generateOETF(linearEffect.outputDataspace, shaderString);
-    generateEffectiveOOTF(linearEffect.undoPremultipliedAlpha, shaderString);
+    generateEffectiveOOTF(linearEffect.undoPremultipliedAlpha, linearEffect.type, shaderString);
     return shaderString;
 }
 
diff --git a/libs/ui/include/ui/DisplayId.h b/libs/ui/include/ui/DisplayId.h
index d0c03fe..3a31fa0 100644
--- a/libs/ui/include/ui/DisplayId.h
+++ b/libs/ui/include/ui/DisplayId.h
@@ -17,6 +17,7 @@
 #pragma once
 
 #include <cstdint>
+#include <ostream>
 #include <string>
 
 #include <ftl/optional.h>
@@ -67,6 +68,11 @@
     return std::to_string(displayId.value);
 }
 
+// For tests.
+inline std::ostream& operator<<(std::ostream& stream, DisplayId displayId) {
+    return stream << "DisplayId{" << displayId.value << '}';
+}
+
 // DisplayId of a physical display, such as the internal display or externally connected display.
 struct PhysicalDisplayId : DisplayId {
     static constexpr ftl::Optional<PhysicalDisplayId> tryCast(DisplayId id) {
diff --git a/libs/ui/include/ui/DisplayMode.h b/libs/ui/include/ui/DisplayMode.h
index a2791a6..65a8769 100644
--- a/libs/ui/include/ui/DisplayMode.h
+++ b/libs/ui/include/ui/DisplayMode.h
@@ -19,6 +19,7 @@
 #include <cstdint>
 #include <type_traits>
 
+#include <ui/GraphicTypes.h>
 #include <ui/Size.h>
 #include <utils/Flattenable.h>
 #include <utils/Timers.h>
@@ -34,6 +35,7 @@
     ui::Size resolution;
     float xDpi = 0;
     float yDpi = 0;
+    std::vector<ui::Hdr> supportedHdrTypes;
 
     float refreshRate = 0;
     nsecs_t appVsyncOffset = 0;
diff --git a/libs/ui/include/ui/DynamicDisplayInfo.h b/libs/ui/include/ui/DynamicDisplayInfo.h
index 8c9fe4c..0b77754 100644
--- a/libs/ui/include/ui/DynamicDisplayInfo.h
+++ b/libs/ui/include/ui/DynamicDisplayInfo.h
@@ -35,6 +35,7 @@
     // we can't use size_t because it may have different width
     // in the client process.
     ui::DisplayModeId activeDisplayModeId;
+    float renderFrameRate;
 
     std::vector<ui::ColorMode> supportedColorModes;
     ui::ColorMode activeColorMode;
diff --git a/libs/ui/include/ui/GraphicTypes.h b/libs/ui/include/ui/GraphicTypes.h
index 8661c36..1775d39 100644
--- a/libs/ui/include/ui/GraphicTypes.h
+++ b/libs/ui/include/ui/GraphicTypes.h
@@ -20,6 +20,7 @@
 #include <aidl/android/hardware/graphics/common/ChromaSiting.h>
 #include <aidl/android/hardware/graphics/common/Compression.h>
 #include <aidl/android/hardware/graphics/common/Cta861_3.h>
+#include <aidl/android/hardware/graphics/common/Hdr.h>
 #include <aidl/android/hardware/graphics/common/Interlaced.h>
 #include <aidl/android/hardware/graphics/common/PlaneLayout.h>
 #include <aidl/android/hardware/graphics/common/Smpte2086.h>
@@ -42,7 +43,6 @@
 using android::hardware::graphics::common::V1_1::RenderIntent;
 using android::hardware::graphics::common::V1_2::ColorMode;
 using android::hardware::graphics::common::V1_2::Dataspace;
-using android::hardware::graphics::common::V1_2::Hdr;
 using android::hardware::graphics::common::V1_2::PixelFormat;
 
 /**
@@ -50,6 +50,7 @@
  */
 using aidl::android::hardware::graphics::common::BlendMode;
 using aidl::android::hardware::graphics::common::Cta861_3;
+using aidl::android::hardware::graphics::common::Hdr;
 using aidl::android::hardware::graphics::common::PlaneLayout;
 using aidl::android::hardware::graphics::common::Smpte2086;
 
diff --git a/libs/ui/include/ui/Rotation.h b/libs/ui/include/ui/Rotation.h
index 83d431d..c1d60f4 100644
--- a/libs/ui/include/ui/Rotation.h
+++ b/libs/ui/include/ui/Rotation.h
@@ -20,7 +20,14 @@
 
 namespace android::ui {
 
-enum class Rotation { Rotation0 = 0, Rotation90 = 1, Rotation180 = 2, Rotation270 = 3 };
+enum class Rotation {
+    Rotation0 = 0,
+    Rotation90 = 1,
+    Rotation180 = 2,
+    Rotation270 = 3,
+
+    ftl_last = Rotation270
+};
 
 // Equivalent to Surface.java constants.
 constexpr auto ROTATION_0 = Rotation::Rotation0;
diff --git a/libs/vibrator/Android.bp b/libs/vibrator/Android.bp
index 83c250a..2af51a7 100644
--- a/libs/vibrator/Android.bp
+++ b/libs/vibrator/Android.bp
@@ -21,31 +21,8 @@
     default_applicable_licenses: ["frameworks_native_license"],
 }
 
-cc_library {
-    name: "libvibrator",
-    vendor_available: true,
-    double_loadable: true,
-
-    shared_libs: [
-        "libbinder",
-        "liblog",
-        "libutils",
-    ],
-
-    header_libs: [
-        "libaudio_system_headers",
-    ],
-
-    aidl: {
-        include_dirs: ["frameworks/base/core/java"],
-        local_include_dirs: ["include/"],
-        export_aidl_headers: true,
-    },
-
-    srcs: [
-        ":libvibrator_aidl",
-        "*.cpp",
-    ],
+cc_defaults {
+    name: "libvibrator_defaults",
 
     cflags: [
         "-Wall",
@@ -64,3 +41,54 @@
         },
     },
 }
+
+cc_library {
+    name: "libvibrator",
+    defaults: ["libvibrator_defaults"],
+
+    shared_libs: [
+        "libbinder",
+        "liblog",
+        "libutils",
+    ],
+
+    whole_static_libs: [
+        "libvibratorutils",
+    ],
+
+    header_libs: [
+        "libaudio_system_headers",
+    ],
+
+    aidl: {
+        include_dirs: ["frameworks/base/core/java"],
+        local_include_dirs: ["include/"],
+        export_aidl_headers: true,
+    },
+
+    srcs: [
+        ":libvibrator_aidl",
+        "ExternalVibration.cpp",
+    ],
+}
+
+cc_library {
+    name: "libvibratorutils",
+    defaults: ["libvibrator_defaults"],
+
+    vendor_available: true,
+    double_loadable: true,
+
+    shared_libs: [
+        "libutils",
+    ],
+
+    srcs: [
+        "ExternalVibrationUtils.cpp",
+    ],
+
+    visibility: [
+        "//frameworks/native/libs/vibrator",
+        "//frameworks/av/media/libeffects/hapticgenerator",
+    ],
+}
diff --git a/libs/vibrator/ExternalVibration.cpp b/libs/vibrator/ExternalVibration.cpp
index f6fc19e..80e911c 100644
--- a/libs/vibrator/ExternalVibration.cpp
+++ b/libs/vibrator/ExternalVibration.cpp
@@ -15,7 +15,9 @@
  */
 
 #include <vibrator/ExternalVibration.h>
+#include <vibrator/ExternalVibrationUtils.h>
 
+#include <android/os/IExternalVibratorService.h>
 #include <binder/Parcel.h>
 #include <log/log.h>
 #include <utils/Errors.h>
@@ -63,5 +65,25 @@
     return mToken == rhs.mToken;
 }
 
+os::HapticScale ExternalVibration::externalVibrationScaleToHapticScale(int externalVibrationScale) {
+    switch (externalVibrationScale) {
+        case IExternalVibratorService::SCALE_MUTE:
+            return os::HapticScale::MUTE;
+        case IExternalVibratorService::SCALE_VERY_LOW:
+            return os::HapticScale::VERY_LOW;
+        case IExternalVibratorService::SCALE_LOW:
+            return os::HapticScale::LOW;
+        case IExternalVibratorService::SCALE_NONE:
+            return os::HapticScale::NONE;
+        case IExternalVibratorService::SCALE_HIGH:
+            return os::HapticScale::HIGH;
+        case IExternalVibratorService::SCALE_VERY_HIGH:
+            return os::HapticScale::VERY_HIGH;
+        default:
+          ALOGE("Unknown ExternalVibrationScale %d, not applying scaling", externalVibrationScale);
+          return os::HapticScale::NONE;
+      }
+}
+
 } // namespace os
 } // namespace android
diff --git a/libs/vibrator/include/vibrator/ExternalVibration.h b/libs/vibrator/include/vibrator/ExternalVibration.h
index 760dbce..00cd3cd 100644
--- a/libs/vibrator/include/vibrator/ExternalVibration.h
+++ b/libs/vibrator/include/vibrator/ExternalVibration.h
@@ -23,6 +23,7 @@
 #include <binder/Parcelable.h>
 #include <system/audio.h>
 #include <utils/RefBase.h>
+#include <vibrator/ExternalVibrationUtils.h>
 
 namespace android {
 namespace os {
@@ -44,6 +45,10 @@
     audio_attributes_t getAudioAttributes() const { return mAttrs; }
     sp<IExternalVibrationController> getController() { return mController; }
 
+    /* Converts the scale from non-public ExternalVibrationService into the HapticScale
+     * used by the utils.
+     */
+    static os::HapticScale externalVibrationScaleToHapticScale(int externalVibrationScale);
 
 private:
     int32_t mUid;
@@ -53,7 +58,7 @@
     sp<IBinder> mToken = new BBinder();
 };
 
-} // namespace android
 } // namespace os
+} // namespace android
 
 #endif // ANDROID_EXTERNAL_VIBRATION_H
diff --git a/libs/vibrator/include/vibrator/ExternalVibrationUtils.h b/libs/vibrator/include/vibrator/ExternalVibrationUtils.h
index 84357fc..ca219d3 100644
--- a/libs/vibrator/include/vibrator/ExternalVibrationUtils.h
+++ b/libs/vibrator/include/vibrator/ExternalVibrationUtils.h
@@ -17,17 +17,15 @@
 #ifndef ANDROID_EXTERNAL_VIBRATION_UTILS_H
 #define ANDROID_EXTERNAL_VIBRATION_UTILS_H
 
-#include <android/os/IExternalVibratorService.h>
-
 namespace android::os {
 
 enum class HapticScale {
-    MUTE = IExternalVibratorService::SCALE_MUTE,
-    VERY_LOW = IExternalVibratorService::SCALE_VERY_LOW,
-    LOW = IExternalVibratorService::SCALE_LOW,
-    NONE = IExternalVibratorService::SCALE_NONE,
-    HIGH = IExternalVibratorService::SCALE_HIGH,
-    VERY_HIGH = IExternalVibratorService::SCALE_VERY_HIGH,
+    MUTE = -100,
+    VERY_LOW = -2,
+    LOW = -1,
+    NONE = 0,
+    HIGH = 1,
+    VERY_HIGH = 2,
 };
 
 bool isValidHapticScale(HapticScale scale);
diff --git a/libs/vr/libbufferhubqueue/Android.bp b/libs/vr/libbufferhubqueue/Android.bp
deleted file mode 100644
index 0bda798..0000000
--- a/libs/vr/libbufferhubqueue/Android.bp
+++ /dev/null
@@ -1,71 +0,0 @@
-// Copyright (C) 2016 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"],
-}
-
-sourceFiles = [
-    "buffer_hub_queue_client.cpp",
-    "buffer_hub_queue_parcelable.cpp",
-]
-
-includeFiles = [
-    "include",
-]
-
-staticLibraries = [
-    "libbufferhub",
-]
-
-sharedLibraries = [
-    "libbinder",
-    "libcutils",
-    "liblog",
-    "libui",
-    "libutils",
-    "libpdx_default_transport",
-]
-
-headerLibraries = [
-    "libdvr_headers",
-    "libnativebase_headers",
-]
-
-cc_library_shared {
-    name: "libbufferhubqueue",
-    cflags: [
-        "-DLOG_TAG=\"libbufferhubqueue\"",
-        "-DTRACE=0",
-        "-DATRACE_TAG=ATRACE_TAG_GRAPHICS",
-        "-Wall",
-        "-Werror",
-        "-Wno-format",
-        "-Wno-unused-parameter",
-        "-Wno-unused-variable",
-    ],
-    srcs: sourceFiles,
-    export_include_dirs: includeFiles,
-    export_static_lib_headers: staticLibraries,
-    static_libs: staticLibraries,
-    shared_libs: sharedLibraries,
-    header_libs: headerLibraries,
-}
-
-subdirs = ["benchmarks", "tests"]
diff --git a/libs/vr/libbufferhubqueue/benchmarks/Android.bp b/libs/vr/libbufferhubqueue/benchmarks/Android.bp
deleted file mode 100644
index e33e03b..0000000
--- a/libs/vr/libbufferhubqueue/benchmarks/Android.bp
+++ /dev/null
@@ -1,35 +0,0 @@
-
-package {
-    // See: http://go/android-license-faq
-    // A large-scale-change added 'default_applicable_licenses' to import
-    // all of the 'license_kinds' from "frameworks_native_license"
-    // to get the below license kinds:
-    //   SPDX-license-identifier-Apache-2.0
-    default_applicable_licenses: ["frameworks_native_license"],
-}
-
-cc_benchmark {
-    srcs: ["buffer_transport_benchmark.cpp"],
-    shared_libs: [
-        "libbase",
-        "libbinder",
-        "libcutils",
-        "libdvr.google",
-        "libgui",
-        "liblog",
-        "libhardware",
-        "libui",
-        "libutils",
-        "libnativewindow",
-        "libbufferhubqueue",
-        "libpdx_default_transport",
-    ],
-    cflags: [
-        "-DLOG_TAG=\"buffer_transport_benchmark\"",
-        "-DTRACE=0",
-        "-O2",
-        "-Wall",
-        "-Werror",
-    ],
-    name: "buffer_transport_benchmark",
-}
diff --git a/libs/vr/libbufferhubqueue/benchmarks/buffer_transport_benchmark.cpp b/libs/vr/libbufferhubqueue/benchmarks/buffer_transport_benchmark.cpp
deleted file mode 100644
index b6813eb..0000000
--- a/libs/vr/libbufferhubqueue/benchmarks/buffer_transport_benchmark.cpp
+++ /dev/null
@@ -1,589 +0,0 @@
-#include <android-base/logging.h>
-#include <android/native_window.h>
-#include <benchmark/benchmark.h>
-#include <binder/IPCThreadState.h>
-#include <binder/IServiceManager.h>
-#include <dvr/dvr_api.h>
-#include <gui/BufferItem.h>
-#include <gui/BufferItemConsumer.h>
-#include <gui/Surface.h>
-#include <private/dvr/epoll_file_descriptor.h>
-#include <utils/Trace.h>
-
-#include <chrono>
-#include <functional>
-#include <iostream>
-#include <thread>
-#include <vector>
-
-#include <dlfcn.h>
-#include <poll.h>
-#include <sys/epoll.h>
-#include <sys/wait.h>
-
-// Use ALWAYS at the tag level. Control is performed manually during command
-// line processing.
-#ifdef ATRACE_TAG
-#undef ATRACE_TAG
-#endif
-#define ATRACE_TAG ATRACE_TAG_ALWAYS
-
-using namespace android;
-using ::benchmark::State;
-
-static const String16 kBinderService = String16("bufferTransport");
-static const uint32_t kBufferWidth = 100;
-static const uint32_t kBufferHeight = 1;
-static const uint32_t kBufferFormat = HAL_PIXEL_FORMAT_BLOB;
-static const uint64_t kBufferUsage =
-    GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN;
-static const uint32_t kBufferLayer = 1;
-static const int kMaxAcquiredImages = 1;
-static const int kQueueDepth = 2;  // We are double buffering for this test.
-static const size_t kMaxQueueCounts = 128;
-static const int kInvalidFence = -1;
-
-enum BufferTransportServiceCode {
-  CREATE_BUFFER_QUEUE = IBinder::FIRST_CALL_TRANSACTION,
-};
-
-// A binder services that minics a compositor that consumes buffers. It provides
-// one Binder interface to create a new Surface for buffer producer to write
-// into; while itself will carry out no-op buffer consuming by acquiring then
-// releasing the buffer immediately.
-class BufferTransportService : public BBinder {
- public:
-  BufferTransportService() = default;
-  ~BufferTransportService() = default;
-
-  virtual status_t onTransact(uint32_t code, const Parcel& data, Parcel* reply,
-                              uint32_t flags = 0) {
-    (void)flags;
-    (void)data;
-    switch (code) {
-      case CREATE_BUFFER_QUEUE: {
-        auto new_queue = std::make_shared<BufferQueueHolder>(this);
-        reply->writeStrongBinder(
-            IGraphicBufferProducer::asBinder(new_queue->producer));
-        buffer_queues_.push_back(new_queue);
-        return OK;
-      }
-      default:
-        return UNKNOWN_TRANSACTION;
-    };
-  }
-
- private:
-  struct FrameListener : public ConsumerBase::FrameAvailableListener {
-   public:
-    FrameListener(BufferTransportService* /*service*/,
-                  sp<BufferItemConsumer> buffer_item_consumer)
-        : buffer_item_consumer_(buffer_item_consumer) {}
-
-    void onFrameAvailable(const BufferItem& /*item*/) override {
-      BufferItem buffer;
-      status_t ret = 0;
-      {
-        ATRACE_NAME("AcquireBuffer");
-        ret = buffer_item_consumer_->acquireBuffer(&buffer, /*presentWhen=*/0,
-                                                   /*waitForFence=*/false);
-      }
-
-      if (ret != OK) {
-        LOG(ERROR) << "Failed to acquire next buffer.";
-        return;
-      }
-
-      {
-        ATRACE_NAME("ReleaseBuffer");
-        ret = buffer_item_consumer_->releaseBuffer(buffer);
-      }
-
-      if (ret != OK) {
-        LOG(ERROR) << "Failed to release buffer.";
-        return;
-      }
-    }
-
-   private:
-    sp<BufferItemConsumer> buffer_item_consumer_;
-  };
-
-  struct BufferQueueHolder {
-    explicit BufferQueueHolder(BufferTransportService* service) {
-      BufferQueue::createBufferQueue(&producer, &consumer);
-
-      sp<BufferItemConsumer> buffer_item_consumer =
-          new BufferItemConsumer(consumer, kBufferUsage, kMaxAcquiredImages,
-                                 /*controlledByApp=*/true);
-      buffer_item_consumer->setName(String8("BinderBufferTransport"));
-      frame_listener_ = new FrameListener(service, buffer_item_consumer);
-      buffer_item_consumer->setFrameAvailableListener(frame_listener_);
-    }
-
-    sp<IGraphicBufferProducer> producer;
-    sp<IGraphicBufferConsumer> consumer;
-
-   private:
-    sp<FrameListener> frame_listener_;
-  };
-
-  std::vector<std::shared_ptr<BufferQueueHolder>> buffer_queues_;
-};
-
-// A virtual interfaces that abstracts the common BufferQueue operations, so
-// that the test suite can use the same test case to drive different types of
-// transport backends.
-class BufferTransport {
- public:
-  virtual ~BufferTransport() {}
-
-  virtual int Start() = 0;
-  virtual sp<Surface> CreateSurface() = 0;
-};
-
-// Binder-based buffer transport backend.
-//
-// On Start() a new process will be swapned to run a Binder server that
-// actually consumes the buffer.
-// On CreateSurface() a new Binder BufferQueue will be created, which the
-// service holds the concrete binder node of the IGraphicBufferProducer while
-// sending the binder proxy to the client. In another word, the producer side
-// operations are carried out process while the consumer side operations are
-// carried out within the BufferTransportService's own process.
-class BinderBufferTransport : public BufferTransport {
- public:
-  BinderBufferTransport() {}
-
-  int Start() override {
-    sp<IServiceManager> sm = defaultServiceManager();
-    service_ = sm->getService(kBinderService);
-    if (service_ == nullptr) {
-      LOG(ERROR) << "Failed to get the benchmark service.";
-      return -EIO;
-    }
-
-    LOG(INFO) << "Binder server is ready for client.";
-    return 0;
-  }
-
-  sp<Surface> CreateSurface() override {
-    Parcel data;
-    Parcel reply;
-    int error = service_->transact(CREATE_BUFFER_QUEUE, data, &reply);
-    if (error != OK) {
-      LOG(ERROR) << "Failed to get buffer queue over binder.";
-      return nullptr;
-    }
-
-    sp<IBinder> binder;
-    error = reply.readNullableStrongBinder(&binder);
-    if (error != OK) {
-      LOG(ERROR) << "Failed to get IGraphicBufferProducer over binder.";
-      return nullptr;
-    }
-
-    auto producer = interface_cast<IGraphicBufferProducer>(binder);
-    if (producer == nullptr) {
-      LOG(ERROR) << "Failed to get IGraphicBufferProducer over binder.";
-      return nullptr;
-    }
-
-    sp<Surface> surface = new Surface(producer, /*controlledByApp=*/true);
-
-    // Set buffer dimension.
-    ANativeWindow* window = static_cast<ANativeWindow*>(surface.get());
-    ANativeWindow_setBuffersGeometry(window, kBufferWidth, kBufferHeight,
-                                     kBufferFormat);
-
-    return surface;
-  }
-
- private:
-  sp<IBinder> service_;
-};
-
-class DvrApi {
- public:
-  DvrApi() {
-    handle_ = dlopen("libdvr.google.so", RTLD_NOW | RTLD_LOCAL);
-    CHECK(handle_);
-
-    auto dvr_get_api =
-        reinterpret_cast<decltype(&dvrGetApi)>(dlsym(handle_, "dvrGetApi"));
-    int ret = dvr_get_api(&api_, sizeof(api_), /*version=*/1);
-
-    CHECK(ret == 0);
-  }
-
-  ~DvrApi() { dlclose(handle_); }
-
-  const DvrApi_v1& Api() { return api_; }
-
- private:
-  void* handle_ = nullptr;
-  DvrApi_v1 api_;
-};
-
-// BufferHub/PDX-based buffer transport.
-//
-// On Start() a new thread will be swapned to run an epoll polling thread which
-// minics the behavior of a compositor. Similar to Binder-based backend, the
-// buffer available handler is also a no-op: Buffer gets acquired and released
-// immediately.
-// On CreateSurface() a pair of dvr::ProducerQueue and dvr::ConsumerQueue will
-// be created. The epoll thread holds on the consumer queue and dequeues buffer
-// from it; while the producer queue will be wrapped in a Surface and returned
-// to test suite.
-class BufferHubTransport : public BufferTransport {
- public:
-  virtual ~BufferHubTransport() {
-    stopped_.store(true);
-    if (reader_thread_.joinable()) {
-      reader_thread_.join();
-    }
-  }
-
-  int Start() override {
-    int ret = epoll_fd_.Create();
-    if (ret < 0) {
-      LOG(ERROR) << "Failed to create epoll fd: %s", strerror(-ret);
-      return -1;
-    }
-
-    // Create the reader thread.
-    reader_thread_ = std::thread([this]() {
-      int ret = dvr_.Api().PerformanceSetSchedulerPolicy(0, "graphics");
-      if (ret < 0) {
-        LOG(ERROR) << "Failed to set scheduler policy, ret=" << ret;
-        return;
-      }
-
-      stopped_.store(false);
-      LOG(INFO) << "Reader Thread Running...";
-
-      while (!stopped_.load()) {
-        std::array<epoll_event, kMaxQueueCounts> events;
-
-        // Don't sleep forever so that we will have a chance to wake up.
-        const int ret = epoll_fd_.Wait(events.data(), events.size(),
-                                       /*timeout=*/100);
-        if (ret < 0) {
-          LOG(ERROR) << "Error polling consumer queues.";
-          continue;
-        }
-        if (ret == 0) {
-          continue;
-        }
-
-        const int num_events = ret;
-        for (int i = 0; i < num_events; i++) {
-          uint32_t index = events[i].data.u32;
-          dvr_.Api().ReadBufferQueueHandleEvents(
-              buffer_queues_[index]->GetReadQueue());
-        }
-      }
-
-      LOG(INFO) << "Reader Thread Exiting...";
-    });
-
-    return 0;
-  }
-
-  sp<Surface> CreateSurface() override {
-    auto new_queue = std::make_shared<BufferQueueHolder>();
-    if (!new_queue->IsReady()) {
-      LOG(ERROR) << "Failed to create BufferHub-based BufferQueue.";
-      return nullptr;
-    }
-
-    // Set buffer dimension.
-    ANativeWindow_setBuffersGeometry(new_queue->GetSurface(), kBufferWidth,
-                                     kBufferHeight, kBufferFormat);
-
-    // Use the next position as buffer_queue index.
-    uint32_t index = buffer_queues_.size();
-    epoll_event event = {.events = EPOLLIN | EPOLLET, .data = {.u32 = index}};
-    int queue_fd =
-        dvr_.Api().ReadBufferQueueGetEventFd(new_queue->GetReadQueue());
-    const int ret = epoll_fd_.Control(EPOLL_CTL_ADD, queue_fd, &event);
-    if (ret < 0) {
-      LOG(ERROR) << "Failed to track consumer queue: " << strerror(-ret)
-                 << ", consumer queue fd: " << queue_fd;
-      return nullptr;
-    }
-
-    buffer_queues_.push_back(new_queue);
-    ANativeWindow_acquire(new_queue->GetSurface());
-    return static_cast<Surface*>(new_queue->GetSurface());
-  }
-
- private:
-  struct BufferQueueHolder {
-    BufferQueueHolder() {
-      int ret = 0;
-      ret = dvr_.Api().WriteBufferQueueCreate(
-          kBufferWidth, kBufferHeight, kBufferFormat, kBufferLayer,
-          kBufferUsage, 0, sizeof(DvrNativeBufferMetadata), &write_queue_);
-      if (ret < 0) {
-        LOG(ERROR) << "Failed to create write buffer queue, ret=" << ret;
-        return;
-      }
-
-      ret = dvr_.Api().WriteBufferQueueCreateReadQueue(write_queue_,
-                                                       &read_queue_);
-      if (ret < 0) {
-        LOG(ERROR) << "Failed to create read buffer queue, ret=" << ret;
-        return;
-      }
-
-      ret = dvr_.Api().ReadBufferQueueSetBufferAvailableCallback(
-          read_queue_, BufferAvailableCallback, this);
-      if (ret < 0) {
-        LOG(ERROR) << "Failed to create buffer available callback, ret=" << ret;
-        return;
-      }
-
-      ret =
-          dvr_.Api().WriteBufferQueueGetANativeWindow(write_queue_, &surface_);
-      if (ret < 0) {
-        LOG(ERROR) << "Failed to create surface, ret=" << ret;
-        return;
-      }
-    }
-
-    static void BufferAvailableCallback(void* context) {
-      BufferQueueHolder* thiz = static_cast<BufferQueueHolder*>(context);
-      thiz->HandleBufferAvailable();
-    }
-
-    DvrReadBufferQueue* GetReadQueue() { return read_queue_; }
-
-    ANativeWindow* GetSurface() { return surface_; }
-
-    bool IsReady() {
-      return write_queue_ != nullptr && read_queue_ != nullptr &&
-             surface_ != nullptr;
-    }
-
-    void HandleBufferAvailable() {
-      int ret = 0;
-      DvrNativeBufferMetadata meta;
-      DvrReadBuffer* buffer = nullptr;
-      DvrNativeBufferMetadata metadata;
-      int acquire_fence = kInvalidFence;
-
-      {
-        ATRACE_NAME("AcquireBuffer");
-        ret = dvr_.Api().ReadBufferQueueAcquireBuffer(
-            read_queue_, 0, &buffer, &metadata, &acquire_fence);
-      }
-      if (ret < 0) {
-        LOG(ERROR) << "Failed to acquire consumer buffer, error: " << ret;
-        return;
-      }
-
-      if (buffer != nullptr) {
-        ATRACE_NAME("ReleaseBuffer");
-        ret = dvr_.Api().ReadBufferQueueReleaseBuffer(read_queue_, buffer,
-                                                      &meta, kInvalidFence);
-      }
-      if (ret < 0) {
-        LOG(ERROR) << "Failed to release consumer buffer, error: " << ret;
-      }
-    }
-
-   private:
-    DvrWriteBufferQueue* write_queue_ = nullptr;
-    DvrReadBufferQueue* read_queue_ = nullptr;
-    ANativeWindow* surface_ = nullptr;
-  };
-
-  static DvrApi dvr_;
-  std::atomic<bool> stopped_;
-  std::thread reader_thread_;
-
-  dvr::EpollFileDescriptor epoll_fd_;
-  std::vector<std::shared_ptr<BufferQueueHolder>> buffer_queues_;
-};
-
-DvrApi BufferHubTransport::dvr_ = {};
-
-enum TransportType {
-  kBinderBufferTransport,
-  kBufferHubTransport,
-};
-
-// Main test suite, which supports two transport backend: 1) BinderBufferQueue,
-// 2) BufferHubQueue. The test case drives the producer end of both transport
-// backend by queuing buffers into the buffer queue by using ANativeWindow API.
-class BufferTransportBenchmark : public ::benchmark::Fixture {
- public:
-  void SetUp(State& state) override {
-    if (state.thread_index == 0) {
-      const int transport = state.range(0);
-      switch (transport) {
-        case kBinderBufferTransport:
-          transport_.reset(new BinderBufferTransport);
-          break;
-        case kBufferHubTransport:
-          transport_.reset(new BufferHubTransport);
-          break;
-        default:
-          CHECK(false) << "Unknown test case.";
-          break;
-      }
-
-      CHECK(transport_);
-      const int ret = transport_->Start();
-      CHECK_EQ(ret, 0);
-
-      LOG(INFO) << "Transport backend running, transport=" << transport << ".";
-
-      // Create surfaces for each thread.
-      surfaces_.resize(state.threads);
-      for (int i = 0; i < state.threads; i++) {
-        // Common setup every thread needs.
-        surfaces_[i] = transport_->CreateSurface();
-        CHECK(surfaces_[i]);
-
-        LOG(INFO) << "Surface initialized on thread " << i << ".";
-      }
-    }
-  }
-
-  void TearDown(State& state) override {
-    if (state.thread_index == 0) {
-      surfaces_.clear();
-      transport_.reset();
-      LOG(INFO) << "Tear down benchmark.";
-    }
-  }
-
- protected:
-  std::unique_ptr<BufferTransport> transport_;
-  std::vector<sp<Surface>> surfaces_;
-};
-
-BENCHMARK_DEFINE_F(BufferTransportBenchmark, Producers)(State& state) {
-  ANativeWindow* window = nullptr;
-  ANativeWindow_Buffer buffer;
-  int32_t error = 0;
-  double total_gain_buffer_us = 0;
-  double total_post_buffer_us = 0;
-  int iterations = 0;
-
-  while (state.KeepRunning()) {
-    if (window == nullptr) {
-      CHECK(surfaces_[state.thread_index]);
-      window = static_cast<ANativeWindow*>(surfaces_[state.thread_index].get());
-
-      // Lock buffers a couple time from the queue, so that we have the buffer
-      // allocated.
-      for (int i = 0; i < kQueueDepth; i++) {
-        error = ANativeWindow_lock(window, &buffer,
-                                   /*inOutDirtyBounds=*/nullptr);
-        CHECK_EQ(error, 0);
-        error = ANativeWindow_unlockAndPost(window);
-        CHECK_EQ(error, 0);
-      }
-    }
-
-    {
-      ATRACE_NAME("GainBuffer");
-      auto t1 = std::chrono::high_resolution_clock::now();
-      error = ANativeWindow_lock(window, &buffer,
-                                 /*inOutDirtyBounds=*/nullptr);
-      auto t2 = std::chrono::high_resolution_clock::now();
-      std::chrono::duration<double, std::micro> delta_us = t2 - t1;
-      total_gain_buffer_us += delta_us.count();
-    }
-    CHECK_EQ(error, 0);
-
-    {
-      ATRACE_NAME("PostBuffer");
-      auto t1 = std::chrono::high_resolution_clock::now();
-      error = ANativeWindow_unlockAndPost(window);
-      auto t2 = std::chrono::high_resolution_clock::now();
-      std::chrono::duration<double, std::micro> delta_us = t2 - t1;
-      total_post_buffer_us += delta_us.count();
-    }
-    CHECK_EQ(error, 0);
-
-    iterations++;
-  }
-
-  state.counters["gain_buffer_us"] = ::benchmark::Counter(
-      total_gain_buffer_us / iterations, ::benchmark::Counter::kAvgThreads);
-  state.counters["post_buffer_us"] = ::benchmark::Counter(
-      total_post_buffer_us / iterations, ::benchmark::Counter::kAvgThreads);
-  state.counters["producer_us"] = ::benchmark::Counter(
-      (total_gain_buffer_us + total_post_buffer_us) / iterations,
-      ::benchmark::Counter::kAvgThreads);
-}
-
-BENCHMARK_REGISTER_F(BufferTransportBenchmark, Producers)
-    ->Unit(::benchmark::kMicrosecond)
-    ->Ranges({{kBinderBufferTransport, kBufferHubTransport}})
-    ->ThreadRange(1, 32);
-
-static void runBinderServer() {
-  ProcessState::self()->setThreadPoolMaxThreadCount(0);
-  ProcessState::self()->startThreadPool();
-
-  sp<IServiceManager> sm = defaultServiceManager();
-  sp<BufferTransportService> service = new BufferTransportService;
-  sm->addService(kBinderService, service, false);
-
-  LOG(INFO) << "Binder server running...";
-
-  while (true) {
-    int stat, retval;
-    retval = wait(&stat);
-    if (retval == -1 && errno == ECHILD) {
-      break;
-    }
-  }
-
-  LOG(INFO) << "Service Exiting...";
-}
-
-// To run binder-based benchmark, use:
-// adb shell buffer_transport_benchmark \
-//   --benchmark_filter="BufferTransportBenchmark/ContinuousLoad/0/"
-//
-// To run bufferhub-based benchmark, use:
-// adb shell buffer_transport_benchmark \
-//   --benchmark_filter="BufferTransportBenchmark/ContinuousLoad/1/"
-int main(int argc, char** argv) {
-  bool tracing_enabled = false;
-
-  // Parse arguments in addition to "--benchmark_filter" paramters.
-  for (int i = 1; i < argc; i++) {
-    if (std::string(argv[i]) == "--help") {
-      std::cout << "Usage: binderThroughputTest [OPTIONS]" << std::endl;
-      std::cout << "\t--trace: Enable systrace logging." << std::endl;
-      return 0;
-    }
-    if (std::string(argv[i]) == "--trace") {
-      tracing_enabled = true;
-      continue;
-    }
-  }
-
-  // Setup ATRACE/systrace based on command line.
-  atrace_setup();
-  atrace_set_tracing_enabled(tracing_enabled);
-
-  pid_t pid = fork();
-  if (pid == 0) {
-    // Child, i.e. the client side.
-    ProcessState::self()->startThreadPool();
-
-    ::benchmark::Initialize(&argc, argv);
-    ::benchmark::RunSpecifiedBenchmarks();
-  } else {
-    LOG(INFO) << "Benchmark process pid: " << pid;
-    runBinderServer();
-  }
-}
diff --git a/libs/vr/libbufferhubqueue/buffer_hub_queue_client.cpp b/libs/vr/libbufferhubqueue/buffer_hub_queue_client.cpp
deleted file mode 100644
index 2d3fa4a..0000000
--- a/libs/vr/libbufferhubqueue/buffer_hub_queue_client.cpp
+++ /dev/null
@@ -1,823 +0,0 @@
-#include "include/private/dvr/buffer_hub_queue_client.h"
-
-#include <inttypes.h>
-#include <log/log.h>
-#include <poll.h>
-#include <sys/epoll.h>
-
-#include <array>
-
-#include <pdx/default_transport/client_channel.h>
-#include <pdx/default_transport/client_channel_factory.h>
-#include <pdx/file_handle.h>
-#include <pdx/trace.h>
-
-#define RETRY_EINTR(fnc_call)                 \
-  ([&]() -> decltype(fnc_call) {              \
-    decltype(fnc_call) result;                \
-    do {                                      \
-      result = (fnc_call);                    \
-    } while (result == -1 && errno == EINTR); \
-    return result;                            \
-  })()
-
-using android::pdx::ErrorStatus;
-using android::pdx::LocalChannelHandle;
-using android::pdx::LocalHandle;
-using android::pdx::Status;
-
-namespace android {
-namespace dvr {
-
-namespace {
-
-std::pair<int32_t, int32_t> Unstuff(uint64_t value) {
-  return {static_cast<int32_t>(value >> 32),
-          static_cast<int32_t>(value & ((1ull << 32) - 1))};
-}
-
-uint64_t Stuff(int32_t a, int32_t b) {
-  const uint32_t ua = static_cast<uint32_t>(a);
-  const uint32_t ub = static_cast<uint32_t>(b);
-  return (static_cast<uint64_t>(ua) << 32) | static_cast<uint64_t>(ub);
-}
-
-}  // anonymous namespace
-
-BufferHubQueue::BufferHubQueue(LocalChannelHandle channel_handle)
-    : Client{pdx::default_transport::ClientChannel::Create(
-          std::move(channel_handle))} {
-  Initialize();
-}
-
-BufferHubQueue::BufferHubQueue(const std::string& endpoint_path)
-    : Client{
-          pdx::default_transport::ClientChannelFactory::Create(endpoint_path)} {
-  Initialize();
-}
-
-void BufferHubQueue::Initialize() {
-  int ret = epoll_fd_.Create();
-  if (ret < 0) {
-    ALOGE("BufferHubQueue::BufferHubQueue: Failed to create epoll fd: %s",
-          strerror(-ret));
-    return;
-  }
-
-  epoll_event event = {
-      .events = EPOLLIN | EPOLLET,
-      .data = {.u64 = Stuff(-1, BufferHubQueue::kEpollQueueEventIndex)}};
-  ret = epoll_fd_.Control(EPOLL_CTL_ADD, event_fd(), &event);
-  if (ret < 0) {
-    ALOGE("%s: Failed to add event fd to epoll set: %s", __FUNCTION__,
-          strerror(-ret));
-  }
-}
-
-Status<void> BufferHubQueue::ImportQueue() {
-  auto status = InvokeRemoteMethod<BufferHubRPC::GetQueueInfo>();
-  if (!status) {
-    ALOGE("%s: Failed to import queue: %s", __FUNCTION__,
-          status.GetErrorMessage().c_str());
-    return ErrorStatus(status.error());
-  } else {
-    SetupQueue(status.get());
-    return {};
-  }
-}
-
-void BufferHubQueue::SetupQueue(const QueueInfo& queue_info) {
-  is_async_ = queue_info.producer_config.is_async;
-  default_width_ = queue_info.producer_config.default_width;
-  default_height_ = queue_info.producer_config.default_height;
-  default_format_ = queue_info.producer_config.default_format;
-  user_metadata_size_ = queue_info.producer_config.user_metadata_size;
-  id_ = queue_info.id;
-}
-
-std::unique_ptr<ConsumerQueue> BufferHubQueue::CreateConsumerQueue() {
-  if (auto status = CreateConsumerQueueHandle(/*silent*/ false))
-    return std::unique_ptr<ConsumerQueue>(new ConsumerQueue(status.take()));
-  else
-    return nullptr;
-}
-
-std::unique_ptr<ConsumerQueue> BufferHubQueue::CreateSilentConsumerQueue() {
-  if (auto status = CreateConsumerQueueHandle(/*silent*/ true))
-    return std::unique_ptr<ConsumerQueue>(new ConsumerQueue(status.take()));
-  else
-    return nullptr;
-}
-
-Status<LocalChannelHandle> BufferHubQueue::CreateConsumerQueueHandle(
-    bool silent) {
-  auto status = InvokeRemoteMethod<BufferHubRPC::CreateConsumerQueue>(silent);
-  if (!status) {
-    ALOGE(
-        "BufferHubQueue::CreateConsumerQueue: Failed to create consumer queue: "
-        "%s",
-        status.GetErrorMessage().c_str());
-    return ErrorStatus(status.error());
-  }
-
-  return status;
-}
-
-pdx::Status<ConsumerQueueParcelable>
-BufferHubQueue::CreateConsumerQueueParcelable(bool silent) {
-  auto status = CreateConsumerQueueHandle(silent);
-  if (!status)
-    return status.error_status();
-
-  // A temporary consumer queue client to pull its channel parcelable.
-  auto consumer_queue =
-      std::unique_ptr<ConsumerQueue>(new ConsumerQueue(status.take()));
-  ConsumerQueueParcelable queue_parcelable(
-      consumer_queue->GetChannel()->TakeChannelParcelable());
-
-  if (!queue_parcelable.IsValid()) {
-    ALOGE("%s: Failed to create consumer queue parcelable.", __FUNCTION__);
-    return ErrorStatus(EINVAL);
-  }
-
-  return {std::move(queue_parcelable)};
-}
-
-bool BufferHubQueue::WaitForBuffers(int timeout) {
-  ATRACE_NAME("BufferHubQueue::WaitForBuffers");
-  std::array<epoll_event, kMaxEvents> events;
-
-  // Loop at least once to check for hangups.
-  do {
-    ALOGD_IF(
-        TRACE,
-        "BufferHubQueue::WaitForBuffers: queue_id=%d count=%zu capacity=%zu",
-        id(), count(), capacity());
-
-    // If there is already a buffer then just check for hangup without waiting.
-    const int ret = epoll_fd_.Wait(events.data(), events.size(),
-                                   count() == 0 ? timeout : 0);
-
-    if (ret == 0) {
-      ALOGI_IF(TRACE,
-               "BufferHubQueue::WaitForBuffers: No events before timeout: "
-               "queue_id=%d",
-               id());
-      return count() != 0;
-    }
-
-    if (ret < 0 && ret != -EINTR) {
-      ALOGE("%s: Failed to wait for buffers: %s", __FUNCTION__, strerror(-ret));
-      return false;
-    }
-
-    const int num_events = ret;
-
-    // A BufferQueue's epoll fd tracks N+1 events, where there are N events,
-    // one for each buffer in the queue, and one extra event for the queue
-    // client itself.
-    for (int i = 0; i < num_events; i++) {
-      int32_t event_fd;
-      int32_t index;
-      std::tie(event_fd, index) = Unstuff(events[i].data.u64);
-
-      PDX_TRACE_FORMAT(
-          "epoll_event|queue_id=%d;num_events=%d;event_index=%d;event_fd=%d;"
-          "slot=%d|",
-          id(), num_events, i, event_fd, index);
-
-      ALOGD_IF(TRACE,
-               "BufferHubQueue::WaitForBuffers: event %d: event_fd=%d index=%d",
-               i, event_fd, index);
-
-      if (is_buffer_event_index(index)) {
-        HandleBufferEvent(static_cast<size_t>(index), event_fd,
-                          events[i].events);
-      } else if (is_queue_event_index(index)) {
-        HandleQueueEvent(events[i].events);
-      } else {
-        ALOGW(
-            "BufferHubQueue::WaitForBuffers: Unknown event type event_fd=%d "
-            "index=%d",
-            event_fd, index);
-      }
-    }
-  } while (count() == 0 && capacity() > 0 && !hung_up());
-
-  return count() != 0;
-}
-
-Status<void> BufferHubQueue::HandleBufferEvent(size_t slot, int event_fd,
-                                               int poll_events) {
-  ATRACE_NAME("BufferHubQueue::HandleBufferEvent");
-  if (!buffers_[slot]) {
-    ALOGW("BufferHubQueue::HandleBufferEvent: Invalid buffer slot: %zu", slot);
-    return ErrorStatus(ENOENT);
-  }
-
-  auto status = buffers_[slot]->GetEventMask(poll_events);
-  if (!status) {
-    ALOGW("BufferHubQueue::HandleBufferEvent: Failed to get event mask: %s",
-          status.GetErrorMessage().c_str());
-    return status.error_status();
-  }
-
-  const int events = status.get();
-  PDX_TRACE_FORMAT(
-      "buffer|queue_id=%d;buffer_id=%d;slot=%zu;event_fd=%d;poll_events=%x;"
-      "events=%d|",
-      id(), buffers_[slot]->id(), slot, event_fd, poll_events, events);
-
-  if (events & EPOLLIN) {
-    return Enqueue({buffers_[slot], slot, buffers_[slot]->GetQueueIndex()});
-  } else if (events & EPOLLHUP) {
-    ALOGW(
-        "BufferHubQueue::HandleBufferEvent: Received EPOLLHUP event: slot=%zu "
-        "event_fd=%d buffer_id=%d",
-        slot, buffers_[slot]->event_fd(), buffers_[slot]->id());
-    return RemoveBuffer(slot);
-  } else {
-    ALOGW(
-        "BufferHubQueue::HandleBufferEvent: Unknown event, slot=%zu, epoll "
-        "events=%d",
-        slot, events);
-  }
-
-  return {};
-}
-
-Status<void> BufferHubQueue::HandleQueueEvent(int poll_event) {
-  ATRACE_NAME("BufferHubQueue::HandleQueueEvent");
-  auto status = GetEventMask(poll_event);
-  if (!status) {
-    ALOGW("BufferHubQueue::HandleQueueEvent: Failed to get event mask: %s",
-          status.GetErrorMessage().c_str());
-    return status.error_status();
-  }
-
-  const int events = status.get();
-  if (events & EPOLLIN) {
-    // Note that after buffer imports, if |count()| still returns 0, epoll
-    // wait will be tried again to acquire the newly imported buffer.
-    auto buffer_status = OnBufferAllocated();
-    if (!buffer_status) {
-      ALOGE("%s: Failed to import buffer: %s", __FUNCTION__,
-            buffer_status.GetErrorMessage().c_str());
-    }
-  } else if (events & EPOLLHUP) {
-    ALOGD_IF(TRACE, "%s: hang up event!", __FUNCTION__);
-    hung_up_ = true;
-  } else {
-    ALOGW("%s: Unknown epoll events=%x", __FUNCTION__, events);
-  }
-
-  return {};
-}
-
-Status<void> BufferHubQueue::AddBuffer(
-    const std::shared_ptr<BufferHubBase>& buffer, size_t slot) {
-  ALOGD_IF(TRACE, "%s: buffer_id=%d slot=%zu", __FUNCTION__, buffer->id(),
-           slot);
-
-  if (is_full()) {
-    ALOGE("%s: queue is at maximum capacity: %zu", __FUNCTION__, capacity_);
-    return ErrorStatus(E2BIG);
-  }
-
-  if (buffers_[slot]) {
-    // Replace the buffer if the slot is occupied. This could happen when the
-    // producer side replaced the slot with a newly allocated buffer. Remove the
-    // buffer before setting up with the new one.
-    auto remove_status = RemoveBuffer(slot);
-    if (!remove_status)
-      return remove_status.error_status();
-  }
-
-  for (const auto& event_source : buffer->GetEventSources()) {
-    epoll_event event = {.events = event_source.event_mask | EPOLLET,
-                         .data = {.u64 = Stuff(buffer->event_fd(), slot)}};
-    const int ret =
-        epoll_fd_.Control(EPOLL_CTL_ADD, event_source.event_fd, &event);
-    if (ret < 0) {
-      ALOGE("%s: Failed to add buffer to epoll set: %s", __FUNCTION__,
-            strerror(-ret));
-      return ErrorStatus(-ret);
-    }
-  }
-
-  buffers_[slot] = buffer;
-  capacity_++;
-  return {};
-}
-
-Status<void> BufferHubQueue::RemoveBuffer(size_t slot) {
-  ALOGD_IF(TRACE, "%s: slot=%zu", __FUNCTION__, slot);
-
-  if (buffers_[slot]) {
-    for (const auto& event_source : buffers_[slot]->GetEventSources()) {
-      const int ret =
-          epoll_fd_.Control(EPOLL_CTL_DEL, event_source.event_fd, nullptr);
-      if (ret < 0) {
-        ALOGE("%s: Failed to remove buffer from epoll set: %s", __FUNCTION__,
-              strerror(-ret));
-        return ErrorStatus(-ret);
-      }
-    }
-
-    // Trigger OnBufferRemoved callback if registered.
-    if (on_buffer_removed_)
-      on_buffer_removed_(buffers_[slot]);
-
-    buffers_[slot] = nullptr;
-    capacity_--;
-  }
-
-  return {};
-}
-
-Status<void> BufferHubQueue::Enqueue(Entry entry) {
-  if (!is_full()) {
-    // Find and remove the enqueued buffer from unavailable_buffers_slot if
-    // exist.
-    auto enqueued_buffer_iter = std::find_if(
-        unavailable_buffers_slot_.begin(), unavailable_buffers_slot_.end(),
-        [&entry](size_t slot) -> bool { return slot == entry.slot; });
-    if (enqueued_buffer_iter != unavailable_buffers_slot_.end()) {
-      unavailable_buffers_slot_.erase(enqueued_buffer_iter);
-    }
-
-    available_buffers_.push(std::move(entry));
-
-    // Trigger OnBufferAvailable callback if registered.
-    if (on_buffer_available_)
-      on_buffer_available_();
-
-    return {};
-  } else {
-    ALOGE("%s: Buffer queue is full!", __FUNCTION__);
-    return ErrorStatus(E2BIG);
-  }
-}
-
-Status<std::shared_ptr<BufferHubBase>> BufferHubQueue::Dequeue(int timeout,
-                                                               size_t* slot) {
-  ALOGD_IF(TRACE, "%s: count=%zu, timeout=%d", __FUNCTION__, count(), timeout);
-
-  PDX_TRACE_FORMAT("%s|count=%zu|", __FUNCTION__, count());
-
-  if (count() == 0) {
-    if (!WaitForBuffers(timeout))
-      return ErrorStatus(ETIMEDOUT);
-  }
-
-  auto& entry = available_buffers_.top();
-  PDX_TRACE_FORMAT("buffer|buffer_id=%d;slot=%zu|", entry.buffer->id(),
-                   entry.slot);
-
-  std::shared_ptr<BufferHubBase> buffer = std::move(entry.buffer);
-  *slot = entry.slot;
-
-  available_buffers_.pop();
-  unavailable_buffers_slot_.push_back(*slot);
-
-  return {std::move(buffer)};
-}
-
-void BufferHubQueue::SetBufferAvailableCallback(
-    BufferAvailableCallback callback) {
-  on_buffer_available_ = callback;
-}
-
-void BufferHubQueue::SetBufferRemovedCallback(BufferRemovedCallback callback) {
-  on_buffer_removed_ = callback;
-}
-
-pdx::Status<void> BufferHubQueue::FreeAllBuffers() {
-  // Clear all available buffers.
-  while (!available_buffers_.empty())
-    available_buffers_.pop();
-
-  pdx::Status<void> last_error;  // No error.
-  // Clear all buffers this producer queue is tracking.
-  for (size_t slot = 0; slot < BufferHubQueue::kMaxQueueCapacity; slot++) {
-    if (buffers_[slot] != nullptr) {
-      auto status = RemoveBuffer(slot);
-      if (!status) {
-        ALOGE(
-            "ProducerQueue::FreeAllBuffers: Failed to remove buffer at "
-            "slot=%zu.",
-            slot);
-        last_error = status.error_status();
-      }
-    }
-  }
-
-  return last_error;
-}
-
-ProducerQueue::ProducerQueue(LocalChannelHandle handle)
-    : BASE(std::move(handle)) {
-  auto status = ImportQueue();
-  if (!status) {
-    ALOGE("ProducerQueue::ProducerQueue: Failed to import queue: %s",
-          status.GetErrorMessage().c_str());
-    Close(-status.error());
-  }
-}
-
-ProducerQueue::ProducerQueue(const ProducerQueueConfig& config,
-                             const UsagePolicy& usage)
-    : BASE(BufferHubRPC::kClientPath) {
-  auto status =
-      InvokeRemoteMethod<BufferHubRPC::CreateProducerQueue>(config, usage);
-  if (!status) {
-    ALOGE("ProducerQueue::ProducerQueue: Failed to create producer queue: %s",
-          status.GetErrorMessage().c_str());
-    Close(-status.error());
-    return;
-  }
-
-  SetupQueue(status.get());
-}
-
-Status<std::vector<size_t>> ProducerQueue::AllocateBuffers(
-    uint32_t width, uint32_t height, uint32_t layer_count, uint32_t format,
-    uint64_t usage, size_t buffer_count) {
-  if (buffer_count == 0) {
-    return {std::vector<size_t>()};
-  }
-
-  if (capacity() + buffer_count > kMaxQueueCapacity) {
-    ALOGE(
-        "ProducerQueue::AllocateBuffers: queue is at capacity: %zu, cannot "
-        "allocate %zu more buffer(s).",
-        capacity(), buffer_count);
-    return ErrorStatus(E2BIG);
-  }
-
-  Status<std::vector<std::pair<LocalChannelHandle, size_t>>> status =
-      InvokeRemoteMethod<BufferHubRPC::ProducerQueueAllocateBuffers>(
-          width, height, layer_count, format, usage, buffer_count);
-  if (!status) {
-    ALOGE("ProducerQueue::AllocateBuffers: failed to allocate buffers: %s",
-          status.GetErrorMessage().c_str());
-    return status.error_status();
-  }
-
-  auto buffer_handle_slots = status.take();
-  LOG_ALWAYS_FATAL_IF(buffer_handle_slots.size() != buffer_count,
-                      "BufferHubRPC::ProducerQueueAllocateBuffers should "
-                      "return %zu buffer handle(s), but returned %zu instead.",
-                      buffer_count, buffer_handle_slots.size());
-
-  std::vector<size_t> buffer_slots;
-  buffer_slots.reserve(buffer_count);
-
-  // Bookkeeping for each buffer.
-  for (auto& hs : buffer_handle_slots) {
-    auto& buffer_handle = hs.first;
-    size_t buffer_slot = hs.second;
-
-    // Note that import might (though very unlikely) fail. If so, buffer_handle
-    // will be closed and included in returned buffer_slots.
-    if (AddBuffer(ProducerBuffer::Import(std::move(buffer_handle)),
-                  buffer_slot)) {
-      ALOGD_IF(TRACE, "ProducerQueue::AllocateBuffers: new buffer at slot: %zu",
-               buffer_slot);
-      buffer_slots.push_back(buffer_slot);
-    }
-  }
-
-  if (buffer_slots.size() != buffer_count) {
-    // Error out if the count of imported buffer(s) is not correct.
-    ALOGE(
-        "ProducerQueue::AllocateBuffers: requested to import %zu "
-        "buffers, but actually imported %zu buffers.",
-        buffer_count, buffer_slots.size());
-    return ErrorStatus(ENOMEM);
-  }
-
-  return {std::move(buffer_slots)};
-}
-
-Status<size_t> ProducerQueue::AllocateBuffer(uint32_t width, uint32_t height,
-                                             uint32_t layer_count,
-                                             uint32_t format, uint64_t usage) {
-  // We only allocate one buffer at a time.
-  constexpr size_t buffer_count = 1;
-  auto status =
-      AllocateBuffers(width, height, layer_count, format, usage, buffer_count);
-  if (!status) {
-    ALOGE("ProducerQueue::AllocateBuffer: Failed to allocate buffer: %s",
-          status.GetErrorMessage().c_str());
-    return status.error_status();
-  }
-
-  return {status.get()[0]};
-}
-
-Status<void> ProducerQueue::AddBuffer(
-    const std::shared_ptr<ProducerBuffer>& buffer, size_t slot) {
-  ALOGD_IF(TRACE, "ProducerQueue::AddBuffer: queue_id=%d buffer_id=%d slot=%zu",
-           id(), buffer->id(), slot);
-  // For producer buffer, we need to enqueue the newly added buffer
-  // immediately. Producer queue starts with all buffers in available state.
-  auto status = BufferHubQueue::AddBuffer(buffer, slot);
-  if (!status)
-    return status;
-
-  return BufferHubQueue::Enqueue({buffer, slot, 0ULL});
-}
-
-Status<size_t> ProducerQueue::InsertBuffer(
-    const std::shared_ptr<ProducerBuffer>& buffer) {
-  if (buffer == nullptr ||
-      !BufferHubDefs::isClientGained(buffer->buffer_state(),
-                                     buffer->client_state_mask())) {
-    ALOGE(
-        "ProducerQueue::InsertBuffer: Can only insert a buffer when it's in "
-        "gained state.");
-    return ErrorStatus(EINVAL);
-  }
-
-  auto status_or_slot =
-      InvokeRemoteMethod<BufferHubRPC::ProducerQueueInsertBuffer>(
-          buffer->cid());
-  if (!status_or_slot) {
-    ALOGE(
-        "ProducerQueue::InsertBuffer: Failed to insert producer buffer: "
-        "buffer_cid=%d, error: %s.",
-        buffer->cid(), status_or_slot.GetErrorMessage().c_str());
-    return status_or_slot.error_status();
-  }
-
-  size_t slot = status_or_slot.get();
-
-  // Note that we are calling AddBuffer() from the base class to explicitly
-  // avoid Enqueue() the ProducerBuffer.
-  auto status = BufferHubQueue::AddBuffer(buffer, slot);
-  if (!status) {
-    ALOGE("ProducerQueue::InsertBuffer: Failed to add buffer: %s.",
-          status.GetErrorMessage().c_str());
-    return status.error_status();
-  }
-  return {slot};
-}
-
-Status<void> ProducerQueue::RemoveBuffer(size_t slot) {
-  auto status =
-      InvokeRemoteMethod<BufferHubRPC::ProducerQueueRemoveBuffer>(slot);
-  if (!status) {
-    ALOGE("%s: Failed to remove producer buffer: %s", __FUNCTION__,
-          status.GetErrorMessage().c_str());
-    return status.error_status();
-  }
-
-  return BufferHubQueue::RemoveBuffer(slot);
-}
-
-Status<std::shared_ptr<ProducerBuffer>> ProducerQueue::Dequeue(
-    int timeout, size_t* slot, LocalHandle* release_fence) {
-  DvrNativeBufferMetadata canonical_meta;
-  return Dequeue(timeout, slot, &canonical_meta, release_fence);
-}
-
-pdx::Status<std::shared_ptr<ProducerBuffer>> ProducerQueue::Dequeue(
-    int timeout, size_t* slot, DvrNativeBufferMetadata* out_meta,
-    pdx::LocalHandle* release_fence, bool gain_posted_buffer) {
-  ATRACE_NAME("ProducerQueue::Dequeue");
-  if (slot == nullptr || out_meta == nullptr || release_fence == nullptr) {
-    ALOGE("%s: Invalid parameter.", __FUNCTION__);
-    return ErrorStatus(EINVAL);
-  }
-
-  std::shared_ptr<ProducerBuffer> buffer;
-  Status<std::shared_ptr<BufferHubBase>> dequeue_status =
-      BufferHubQueue::Dequeue(timeout, slot);
-  if (dequeue_status.ok()) {
-    buffer = std::static_pointer_cast<ProducerBuffer>(dequeue_status.take());
-  } else {
-    if (gain_posted_buffer) {
-      Status<std::shared_ptr<ProducerBuffer>> dequeue_unacquired_status =
-          ProducerQueue::DequeueUnacquiredBuffer(slot);
-      if (!dequeue_unacquired_status.ok()) {
-        ALOGE("%s: DequeueUnacquiredBuffer returned error: %d", __FUNCTION__,
-              dequeue_unacquired_status.error());
-        return dequeue_unacquired_status.error_status();
-      }
-      buffer = dequeue_unacquired_status.take();
-    } else {
-      return dequeue_status.error_status();
-    }
-  }
-  const int ret =
-      buffer->GainAsync(out_meta, release_fence, gain_posted_buffer);
-  if (ret < 0 && ret != -EALREADY)
-    return ErrorStatus(-ret);
-
-  return {std::move(buffer)};
-}
-
-Status<std::shared_ptr<ProducerBuffer>> ProducerQueue::DequeueUnacquiredBuffer(
-    size_t* slot) {
-  if (unavailable_buffers_slot_.size() < 1) {
-    ALOGE(
-        "%s: Failed to dequeue un-acquired buffer. All buffer(s) are in "
-        "acquired state if exist.",
-        __FUNCTION__);
-    return ErrorStatus(ENOMEM);
-  }
-
-  // Find the first buffer that is not in acquired state from
-  // unavailable_buffers_slot_.
-  for (auto iter = unavailable_buffers_slot_.begin();
-       iter != unavailable_buffers_slot_.end(); iter++) {
-    std::shared_ptr<ProducerBuffer> buffer = ProducerQueue::GetBuffer(*iter);
-    if (buffer == nullptr) {
-      ALOGE("%s failed. Buffer slot %d is  null.", __FUNCTION__,
-            static_cast<int>(*slot));
-      return ErrorStatus(EIO);
-    }
-    if (!BufferHubDefs::isAnyClientAcquired(buffer->buffer_state())) {
-      *slot = *iter;
-      unavailable_buffers_slot_.erase(iter);
-      unavailable_buffers_slot_.push_back(*slot);
-      ALOGD("%s: Producer queue dequeue unacquired buffer in slot %d",
-            __FUNCTION__, static_cast<int>(*slot));
-      return {std::move(buffer)};
-    }
-  }
-  ALOGE(
-      "%s: Failed to dequeue un-acquired buffer. No un-acquired buffer exist.",
-      __FUNCTION__);
-  return ErrorStatus(EBUSY);
-}
-
-pdx::Status<ProducerQueueParcelable> ProducerQueue::TakeAsParcelable() {
-  if (capacity() != 0) {
-    ALOGE(
-        "%s: producer queue can only be taken out as a parcelable when empty. "
-        "Current queue capacity: %zu",
-        __FUNCTION__, capacity());
-    return ErrorStatus(EINVAL);
-  }
-
-  std::unique_ptr<pdx::ClientChannel> channel = TakeChannel();
-  ProducerQueueParcelable queue_parcelable(channel->TakeChannelParcelable());
-
-  // Here the queue parcelable is returned and holds the underlying system
-  // resources backing the queue; while the original client channel of this
-  // producer queue is destroyed in place so that this client can no longer
-  // provide producer operations.
-  return {std::move(queue_parcelable)};
-}
-
-/*static */
-std::unique_ptr<ConsumerQueue> ConsumerQueue::Import(
-    LocalChannelHandle handle) {
-  return std::unique_ptr<ConsumerQueue>(new ConsumerQueue(std::move(handle)));
-}
-
-ConsumerQueue::ConsumerQueue(LocalChannelHandle handle)
-    : BufferHubQueue(std::move(handle)) {
-  auto status = ImportQueue();
-  if (!status) {
-    ALOGE("%s: Failed to import queue: %s", __FUNCTION__,
-          status.GetErrorMessage().c_str());
-    Close(-status.error());
-  }
-
-  auto import_status = ImportBuffers();
-  if (import_status) {
-    ALOGI("%s: Imported %zu buffers.", __FUNCTION__, import_status.get());
-  } else {
-    ALOGE("%s: Failed to import buffers: %s", __FUNCTION__,
-          import_status.GetErrorMessage().c_str());
-  }
-}
-
-Status<size_t> ConsumerQueue::ImportBuffers() {
-  auto status = InvokeRemoteMethod<BufferHubRPC::ConsumerQueueImportBuffers>();
-  if (!status) {
-    if (status.error() == EBADR) {
-      ALOGI("%s: Queue is silent, no buffers imported.", __FUNCTION__);
-      return {0};
-    } else {
-      ALOGE("%s: Failed to import consumer buffer: %s", __FUNCTION__,
-            status.GetErrorMessage().c_str());
-      return status.error_status();
-    }
-  }
-
-  int ret;
-  Status<void> last_error;
-  size_t imported_buffers_count = 0;
-
-  auto buffer_handle_slots = status.take();
-  for (auto& buffer_handle_slot : buffer_handle_slots) {
-    ALOGD_IF(TRACE, ": buffer_handle=%d", __FUNCTION__,
-             buffer_handle_slot.first.value());
-
-    std::unique_ptr<ConsumerBuffer> consumer_buffer =
-        ConsumerBuffer::Import(std::move(buffer_handle_slot.first));
-    if (!consumer_buffer) {
-      ALOGE("%s: Failed to import buffer: slot=%zu", __FUNCTION__,
-            buffer_handle_slot.second);
-      last_error = ErrorStatus(EPIPE);
-      continue;
-    }
-
-    auto add_status =
-        AddBuffer(std::move(consumer_buffer), buffer_handle_slot.second);
-    if (!add_status) {
-      ALOGE("%s: Failed to add buffer: %s", __FUNCTION__,
-            add_status.GetErrorMessage().c_str());
-      last_error = add_status;
-    } else {
-      imported_buffers_count++;
-    }
-  }
-
-  if (imported_buffers_count > 0)
-    return {imported_buffers_count};
-  else
-    return last_error.error_status();
-}
-
-Status<void> ConsumerQueue::AddBuffer(
-    const std::shared_ptr<ConsumerBuffer>& buffer, size_t slot) {
-  ALOGD_IF(TRACE, "%s: queue_id=%d buffer_id=%d slot=%zu", __FUNCTION__, id(),
-           buffer->id(), slot);
-  return BufferHubQueue::AddBuffer(buffer, slot);
-}
-
-Status<std::shared_ptr<ConsumerBuffer>> ConsumerQueue::Dequeue(
-    int timeout, size_t* slot, void* meta, size_t user_metadata_size,
-    LocalHandle* acquire_fence) {
-  if (user_metadata_size != user_metadata_size_) {
-    ALOGE(
-        "%s: Metadata size (%zu) for the dequeuing buffer does not match "
-        "metadata size (%zu) for the queue.",
-        __FUNCTION__, user_metadata_size, user_metadata_size_);
-    return ErrorStatus(EINVAL);
-  }
-
-  DvrNativeBufferMetadata canonical_meta;
-  auto status = Dequeue(timeout, slot, &canonical_meta, acquire_fence);
-  if (!status)
-    return status.error_status();
-
-  if (meta && user_metadata_size) {
-    void* metadata_src =
-        reinterpret_cast<void*>(canonical_meta.user_metadata_ptr);
-    if (metadata_src) {
-      memcpy(meta, metadata_src, user_metadata_size);
-    } else {
-      ALOGW("%s: no user-defined metadata.", __FUNCTION__);
-    }
-  }
-
-  return status;
-}
-
-Status<std::shared_ptr<ConsumerBuffer>> ConsumerQueue::Dequeue(
-    int timeout, size_t* slot, DvrNativeBufferMetadata* out_meta,
-    pdx::LocalHandle* acquire_fence) {
-  ATRACE_NAME("ConsumerQueue::Dequeue");
-  if (slot == nullptr || out_meta == nullptr || acquire_fence == nullptr) {
-    ALOGE("%s: Invalid parameter.", __FUNCTION__);
-    return ErrorStatus(EINVAL);
-  }
-
-  auto status = BufferHubQueue::Dequeue(timeout, slot);
-  if (!status)
-    return status.error_status();
-
-  auto buffer = std::static_pointer_cast<ConsumerBuffer>(status.take());
-  const int ret = buffer->AcquireAsync(out_meta, acquire_fence);
-  if (ret < 0)
-    return ErrorStatus(-ret);
-
-  return {std::move(buffer)};
-}
-
-Status<void> ConsumerQueue::OnBufferAllocated() {
-  ALOGD_IF(TRACE, "%s: queue_id=%d", __FUNCTION__, id());
-
-  auto status = ImportBuffers();
-  if (!status) {
-    ALOGE("%s: Failed to import buffers: %s", __FUNCTION__,
-          status.GetErrorMessage().c_str());
-    return ErrorStatus(status.error());
-  } else if (status.get() == 0) {
-    ALOGW("%s: No new buffers allocated!", __FUNCTION__);
-    return ErrorStatus(ENOBUFS);
-  } else {
-    ALOGD_IF(TRACE, "%s: Imported %zu consumer buffers.", __FUNCTION__,
-             status.get());
-    return {};
-  }
-}
-
-}  // namespace dvr
-}  // namespace android
diff --git a/libs/vr/libbufferhubqueue/buffer_hub_queue_parcelable.cpp b/libs/vr/libbufferhubqueue/buffer_hub_queue_parcelable.cpp
deleted file mode 100644
index f705749..0000000
--- a/libs/vr/libbufferhubqueue/buffer_hub_queue_parcelable.cpp
+++ /dev/null
@@ -1,82 +0,0 @@
-#include "include/private/dvr/buffer_hub_queue_parcelable.h"
-
-#include <binder/Parcel.h>
-#include <pdx/default_transport/channel_parcelable.h>
-
-namespace android {
-namespace dvr {
-
-template <BufferHubQueueParcelableMagic Magic>
-bool BufferHubQueueParcelable<Magic>::IsValid() const {
-  return !!channel_parcelable_ && channel_parcelable_->IsValid();
-}
-
-template <BufferHubQueueParcelableMagic Magic>
-pdx::LocalChannelHandle BufferHubQueueParcelable<Magic>::TakeChannelHandle() {
-  if (!IsValid()) {
-    ALOGE(
-        "BufferHubQueueParcelable::TakeChannelHandle: Invalid channel parcel.");
-    return {};  // Returns an empty channel handle.
-  }
-
-  // Take channel handle out of the parcelable and reset the parcelable.
-  pdx::LocalChannelHandle handle = channel_parcelable_->TakeChannelHandle();
-  // Now channel_parcelable_ should already be invalid, but reset it to release
-  // the invalid parcelable object from unique_ptr.
-  channel_parcelable_ = nullptr;
-  return handle;
-}
-
-template <BufferHubQueueParcelableMagic Magic>
-status_t BufferHubQueueParcelable<Magic>::writeToParcel(Parcel* parcel) const {
-  if (!IsValid()) {
-    ALOGE("BufferHubQueueParcelable::writeToParcel: Invalid channel.");
-    return -EINVAL;
-  }
-
-  status_t res = parcel->writeUint32(Magic);
-  if (res != OK) {
-    ALOGE("BufferHubQueueParcelable::writeToParcel: Cannot write magic.");
-    return res;
-  }
-
-  return channel_parcelable_->writeToParcel(parcel);
-}
-
-template <BufferHubQueueParcelableMagic Magic>
-status_t BufferHubQueueParcelable<Magic>::readFromParcel(const Parcel* parcel) {
-  if (IsValid()) {
-    ALOGE(
-        "BufferHubQueueParcelable::readFromParcel: This parcelable object has "
-        "been initialized already.");
-    return -EINVAL;
-  }
-
-  uint32_t out_magic = 0;
-  status_t res = OK;
-
-  res = parcel->readUint32(&out_magic);
-  if (res != OK)
-    return res;
-
-  if (out_magic != Magic) {
-    ALOGE(
-        "BufferHubQueueParcelable::readFromParcel: Unexpected magic: 0x%x, "
-        "epxected: 0x%x",
-        out_magic, Magic);
-    return -EINVAL;
-  }
-
-  // (Re)Alocate channel parcelable object.
-  channel_parcelable_ =
-      std::make_unique<pdx::default_transport::ChannelParcelable>();
-  return channel_parcelable_->readFromParcel(parcel);
-}
-
-template class BufferHubQueueParcelable<
-    BufferHubQueueParcelableMagic::Producer>;
-template class BufferHubQueueParcelable<
-    BufferHubQueueParcelableMagic::Consumer>;
-
-}  // namespace dvr
-}  // namespace android
diff --git a/libs/vr/libbufferhubqueue/include/private/dvr/buffer_hub_queue_client.h b/libs/vr/libbufferhubqueue/include/private/dvr/buffer_hub_queue_client.h
deleted file mode 100644
index 74b4b3d..0000000
--- a/libs/vr/libbufferhubqueue/include/private/dvr/buffer_hub_queue_client.h
+++ /dev/null
@@ -1,476 +0,0 @@
-#ifndef ANDROID_DVR_BUFFER_HUB_QUEUE_CLIENT_H_
-#define ANDROID_DVR_BUFFER_HUB_QUEUE_CLIENT_H_
-
-#include <ui/BufferQueueDefs.h>
-
-#if defined(__clang__)
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Weverything"
-#endif
-
-// The following headers are included without checking every warning.
-// TODO(b/72172820): Remove the workaround once we have enforced -Weverything
-// in these headers and their dependencies.
-#include <pdx/client.h>
-#include <pdx/status.h>
-#include <private/dvr/buffer_hub_queue_parcelable.h>
-#include <private/dvr/bufferhub_rpc.h>
-#include <private/dvr/consumer_buffer.h>
-#include <private/dvr/epoll_file_descriptor.h>
-#include <private/dvr/producer_buffer.h>
-
-#if defined(__clang__)
-#pragma clang diagnostic pop
-#endif
-
-#include <memory>
-#include <queue>
-#include <vector>
-
-namespace android {
-namespace dvr {
-
-class ConsumerQueue;
-
-// |BufferHubQueue| manages a queue of |BufferHubBase|s. Buffers are
-// automatically re-requeued when released by the remote side.
-class BufferHubQueue : public pdx::Client {
- public:
-  using BufferAvailableCallback = std::function<void()>;
-  using BufferRemovedCallback =
-      std::function<void(const std::shared_ptr<BufferHubBase>&)>;
-
-  virtual ~BufferHubQueue() {}
-
-  // Creates a new consumer queue that is attached to the producer. Returns
-  // a new consumer queue client or nullptr on failure.
-  std::unique_ptr<ConsumerQueue> CreateConsumerQueue();
-
-  // Creates a new consumer queue that is attached to the producer. This queue
-  // sets each of its imported consumer buffers to the ignored state to avoid
-  // participation in lifecycle events.
-  std::unique_ptr<ConsumerQueue> CreateSilentConsumerQueue();
-
-  // Returns whether the buffer queue is in async mode.
-  bool is_async() const { return is_async_; }
-
-  // Returns the default buffer width of this buffer queue.
-  uint32_t default_width() const { return default_width_; }
-
-  // Returns the default buffer height of this buffer queue.
-  uint32_t default_height() const { return default_height_; }
-
-  // Returns the default buffer format of this buffer queue.
-  uint32_t default_format() const { return default_format_; }
-
-  // Creates a new consumer in handle form for immediate transport over RPC.
-  pdx::Status<pdx::LocalChannelHandle> CreateConsumerQueueHandle(
-      bool silent = false);
-
-  // Creates a new consumer in parcelable form for immediate transport over
-  // Binder.
-  pdx::Status<ConsumerQueueParcelable> CreateConsumerQueueParcelable(
-      bool silent = false);
-
-  // Returns the number of buffers avaiable for dequeue.
-  size_t count() const { return available_buffers_.size(); }
-
-  // Returns the total number of buffers that the queue is tracking.
-  size_t capacity() const { return capacity_; }
-
-  // Returns the size of metadata structure associated with this queue.
-  size_t metadata_size() const { return user_metadata_size_; }
-
-  // Returns whether the buffer queue is full.
-  bool is_full() const {
-    return available_buffers_.size() >= kMaxQueueCapacity;
-  }
-
-  // Returns whether the buffer queue is connected to bufferhubd.
-  bool is_connected() const { return !!GetChannel(); }
-
-  int GetBufferId(size_t slot) const {
-    return (slot < buffers_.size() && buffers_[slot]) ? buffers_[slot]->id()
-                                                      : -1;
-  }
-
-  std::shared_ptr<BufferHubBase> GetBuffer(size_t slot) const {
-    return buffers_[slot];
-  }
-
-  pdx::Status<int> GetEventMask(int events) {
-    if (auto* client_channel = GetChannel()) {
-      return client_channel->GetEventMask(events);
-    } else {
-      return pdx::ErrorStatus(EINVAL);
-    }
-  }
-
-  // Returns an fd that signals pending queue events using
-  // EPOLLIN/POLLIN/readible. Either HandleQueueEvents or WaitForBuffers may be
-  // called to handle pending queue events.
-  int queue_fd() const { return epoll_fd_.Get(); }
-
-  // Handles any pending events, returning available buffers to the queue and
-  // reaping disconnected buffers. Returns true if successful, false if an error
-  // occurred.
-  bool HandleQueueEvents() { return WaitForBuffers(0); }
-
-  // Set buffer event callbacks, which are std::function wrappers. The caller is
-  // responsible for ensuring the validity of these callbacks' callable targets.
-  void SetBufferAvailableCallback(BufferAvailableCallback callback);
-  void SetBufferRemovedCallback(BufferRemovedCallback callback);
-
-  // The queue tracks at most this many buffers.
-  static constexpr size_t kMaxQueueCapacity =
-      android::BufferQueueDefs::NUM_BUFFER_SLOTS;
-
-  static constexpr int kNoTimeOut = -1;
-
-  int id() const { return id_; }
-  bool hung_up() const { return hung_up_; }
-
- protected:
-  explicit BufferHubQueue(pdx::LocalChannelHandle channel);
-  explicit BufferHubQueue(const std::string& endpoint_path);
-
-  // Imports the queue parameters by querying BufferHub for the parameters for
-  // this channel.
-  pdx::Status<void> ImportQueue();
-
-  // Sets up the queue with the given parameters.
-  void SetupQueue(const QueueInfo& queue_info);
-
-  // Register a buffer for management by the queue. Used by subclasses to add a
-  // buffer to internal bookkeeping.
-  pdx::Status<void> AddBuffer(const std::shared_ptr<BufferHubBase>& buffer,
-                              size_t slot);
-
-  // Called by ProducerQueue::RemoveBuffer and ConsumerQueue::RemoveBuffer only
-  // to deregister a buffer for epoll and internal bookkeeping.
-  virtual pdx::Status<void> RemoveBuffer(size_t slot);
-
-  // Free all buffers that belongs to this queue. Can only be called from
-  // producer side.
-  virtual pdx::Status<void> FreeAllBuffers();
-
-  // Dequeue a buffer from the free queue, blocking until one is available. The
-  // timeout argument specifies the number of milliseconds that |Dequeue()| will
-  // block. Specifying a timeout of -1 causes Dequeue() to block indefinitely,
-  // while specifying a timeout equal to zero cause Dequeue() to return
-  // immediately, even if no buffers are available.
-  pdx::Status<std::shared_ptr<BufferHubBase>> Dequeue(int timeout,
-                                                      size_t* slot);
-
-  // Waits for buffers to become available and adds them to the available queue.
-  bool WaitForBuffers(int timeout);
-
-  pdx::Status<void> HandleBufferEvent(size_t slot, int event_fd,
-                                      int poll_events);
-  pdx::Status<void> HandleQueueEvent(int poll_events);
-
-  // Entry in the priority queue of available buffers that stores related
-  // per-buffer data.
-  struct Entry {
-    Entry() : slot(0) {}
-    Entry(const std::shared_ptr<BufferHubBase>& in_buffer, size_t in_slot,
-          uint64_t in_index)
-        : buffer(in_buffer), slot(in_slot), index(in_index) {}
-    Entry(const std::shared_ptr<BufferHubBase>& in_buffer,
-          std::unique_ptr<uint8_t[]> in_metadata, pdx::LocalHandle in_fence,
-          size_t in_slot)
-        : buffer(in_buffer),
-          metadata(std::move(in_metadata)),
-          fence(std::move(in_fence)),
-          slot(in_slot) {}
-    Entry(Entry&&) = default;
-    Entry& operator=(Entry&&) = default;
-
-    std::shared_ptr<BufferHubBase> buffer;
-    std::unique_ptr<uint8_t[]> metadata;
-    pdx::LocalHandle fence;
-    size_t slot;
-    uint64_t index;
-  };
-
-  struct EntryComparator {
-    bool operator()(const Entry& lhs, const Entry& rhs) {
-      return lhs.index > rhs.index;
-    }
-  };
-
-  // Enqueues a buffer to the available list (Gained for producer or Acquireed
-  // for consumer).
-  pdx::Status<void> Enqueue(Entry entry);
-
-  // Called when a buffer is allocated remotely.
-  virtual pdx::Status<void> OnBufferAllocated() { return {}; }
-
-  // Size of the metadata that buffers in this queue cary.
-  size_t user_metadata_size_{0};
-
-  // Buffers and related data that are available for dequeue.
-  std::priority_queue<Entry, std::vector<Entry>, EntryComparator>
-      available_buffers_;
-
-  // Slot of the buffers that are not available for normal dequeue. For example,
-  // the slot of posted or acquired buffers in the perspective of a producer.
-  std::vector<size_t> unavailable_buffers_slot_;
-
- private:
-  void Initialize();
-
-  // Special epoll data field indicating that the epoll event refers to the
-  // queue.
-  static constexpr int64_t kEpollQueueEventIndex = -1;
-
-  static constexpr size_t kMaxEvents = 128;
-
-  // The u64 data field of an epoll event is interpreted as int64_t:
-  // When |index| >= 0 and |index| < kMaxQueueCapacity it refers to a specific
-  // element of |buffers_| as a direct index;
-  static bool is_buffer_event_index(int64_t index) {
-    return index >= 0 &&
-           index < static_cast<int64_t>(BufferHubQueue::kMaxQueueCapacity);
-  }
-
-  // When |index| == kEpollQueueEventIndex it refers to the queue itself.
-  static bool is_queue_event_index(int64_t index) {
-    return index == BufferHubQueue::kEpollQueueEventIndex;
-  }
-
-  // Whether the buffer queue is operating in Async mode.
-  // From GVR's perspective of view, this means a buffer can be acquired
-  // asynchronously by the compositor.
-  // From Android Surface's perspective of view, this is equivalent to
-  // IGraphicBufferProducer's async mode. When in async mode, a producer
-  // will never block even if consumer is running slow.
-  bool is_async_{false};
-
-  // Default buffer width that is set during ProducerQueue's creation.
-  uint32_t default_width_{1};
-
-  // Default buffer height that is set during ProducerQueue's creation.
-  uint32_t default_height_{1};
-
-  // Default buffer format that is set during ProducerQueue's creation.
-  uint32_t default_format_{1};  // PIXEL_FORMAT_RGBA_8888
-
-  // Tracks the buffers belonging to this queue. Buffers are stored according to
-  // "slot" in this vector. Each slot is a logical id of the buffer within this
-  // queue regardless of its queue position or presence in the ring buffer.
-  std::array<std::shared_ptr<BufferHubBase>, kMaxQueueCapacity> buffers_;
-
-  // Keeps track with how many buffers have been added into the queue.
-  size_t capacity_{0};
-
-  // Epoll fd used to manage buffer events.
-  EpollFileDescriptor epoll_fd_;
-
-  // Flag indicating that the other side hung up. For ProducerQueues this
-  // triggers when BufferHub dies or explicitly closes the queue channel. For
-  // ConsumerQueues this can either mean the same or that the ProducerQueue on
-  // the other end hung up.
-  bool hung_up_{false};
-
-  // Global id for the queue that is consistent across processes.
-  int id_{-1};
-
-  // Buffer event callbacks
-  BufferAvailableCallback on_buffer_available_;
-  BufferRemovedCallback on_buffer_removed_;
-
-  BufferHubQueue(const BufferHubQueue&) = delete;
-  void operator=(BufferHubQueue&) = delete;
-};
-
-class ProducerQueue : public pdx::ClientBase<ProducerQueue, BufferHubQueue> {
- public:
-  // Usage bits in |usage_set_mask| will be automatically masked on. Usage bits
-  // in |usage_clear_mask| will be automatically masked off. Note that
-  // |usage_set_mask| and |usage_clear_mask| may conflict with each other, but
-  // |usage_set_mask| takes precedence over |usage_clear_mask|. All buffer
-  // allocation through this producer queue shall not have any of the usage bits
-  // in |usage_deny_set_mask| set. Allocation calls violating this will be
-  // rejected. All buffer allocation through this producer queue must have all
-  // the usage bits in |usage_deny_clear_mask| set. Allocation calls violating
-  // this will be rejected. Note that |usage_deny_set_mask| and
-  // |usage_deny_clear_mask| shall not conflict with each other. Such
-  // configuration will be treated as invalid input on creation.
-  static std::unique_ptr<ProducerQueue> Create(
-      const ProducerQueueConfig& config, const UsagePolicy& usage) {
-    return BASE::Create(config, usage);
-  }
-
-  // Import a ProducerQueue from a channel handle.
-  static std::unique_ptr<ProducerQueue> Import(pdx::LocalChannelHandle handle) {
-    return BASE::Create(std::move(handle));
-  }
-
-  // Get a producer buffer. Note that the method doesn't check whether the
-  // buffer slot has a valid buffer that has been allocated already. When no
-  // buffer has been imported before it returns nullptr; otherwise it returns
-  // a shared pointer to a ProducerBuffer.
-  std::shared_ptr<ProducerBuffer> GetBuffer(size_t slot) const {
-    return std::static_pointer_cast<ProducerBuffer>(
-        BufferHubQueue::GetBuffer(slot));
-  }
-
-  // Batch allocate buffers. Once allocated, producer buffers are automatically
-  // enqueue'd into the ProducerQueue and available to use (i.e. in GAINED
-  // state). Upon success, returns a list of slots for each buffer allocated.
-  pdx::Status<std::vector<size_t>> AllocateBuffers(
-      uint32_t width, uint32_t height, uint32_t layer_count, uint32_t format,
-      uint64_t usage, size_t buffer_count);
-
-  // Allocate producer buffer to populate the queue. Once allocated, a producer
-  // buffer is automatically enqueue'd into the ProducerQueue and available to
-  // use (i.e. in GAINED state). Upon success, returns the slot number for the
-  // buffer allocated.
-  pdx::Status<size_t> AllocateBuffer(uint32_t width, uint32_t height,
-                                     uint32_t layer_count, uint32_t format,
-                                     uint64_t usage);
-
-  // Add a producer buffer to populate the queue. Once added, a producer buffer
-  // is available to use (i.e. in GAINED state).
-  pdx::Status<void> AddBuffer(const std::shared_ptr<ProducerBuffer>& buffer,
-                              size_t slot);
-
-  // Inserts a ProducerBuffer into the queue. On success, the method returns the
-  // |slot| number where the new buffer gets inserted. Note that the buffer
-  // being inserted should be in Gain'ed state prior to the call and it's
-  // considered as already Dequeued when the function returns.
-  pdx::Status<size_t> InsertBuffer(
-      const std::shared_ptr<ProducerBuffer>& buffer);
-
-  // Remove producer buffer from the queue.
-  pdx::Status<void> RemoveBuffer(size_t slot) override;
-
-  // Free all buffers on this producer queue.
-  pdx::Status<void> FreeAllBuffers() override {
-    return BufferHubQueue::FreeAllBuffers();
-  }
-
-  // Dequeue a producer buffer to write. The returned buffer in |Gain|'ed mode,
-  // and caller should call Post() once it's done writing to release the buffer
-  // to the consumer side.
-  // @return a buffer in gained state, which was originally in released state.
-  pdx::Status<std::shared_ptr<ProducerBuffer>> Dequeue(
-      int timeout, size_t* slot, pdx::LocalHandle* release_fence);
-
-  // Dequeue a producer buffer to write. The returned buffer in |Gain|'ed mode,
-  // and caller should call Post() once it's done writing to release the buffer
-  // to the consumer side.
-  //
-  // @param timeout to dequeue a buffer.
-  // @param slot is the slot of the output ProducerBuffer.
-  // @param release_fence for gaining a buffer.
-  // @param out_meta metadata of the output buffer.
-  // @param gain_posted_buffer whether to gain posted buffer if no released
-  //     buffer is available to gain.
-  // @return a buffer in gained state, which was originally in released state if
-  //     gain_posted_buffer is false, or in posted/released state if
-  //     gain_posted_buffer is true.
-  // TODO(b/112007999): gain_posted_buffer true is only used to prevent
-  // libdvrtracking from starving when there are non-responding clients. This
-  // gain_posted_buffer param can be removed once libdvrtracking start to use
-  // the new AHardwareBuffer API.
-  pdx::Status<std::shared_ptr<ProducerBuffer>> Dequeue(
-      int timeout, size_t* slot, DvrNativeBufferMetadata* out_meta,
-      pdx::LocalHandle* release_fence, bool gain_posted_buffer = false);
-
-  // Enqueues a producer buffer in the queue.
-  pdx::Status<void> Enqueue(const std::shared_ptr<ProducerBuffer>& buffer,
-                            size_t slot, uint64_t index) {
-    return BufferHubQueue::Enqueue({buffer, slot, index});
-  }
-
-  // Takes out the current producer queue as a binder parcelable object. Note
-  // that the queue must be empty to be exportable. After successful export, the
-  // producer queue client should no longer be used.
-  pdx::Status<ProducerQueueParcelable> TakeAsParcelable();
-
- private:
-  friend BASE;
-
-  // Constructors are automatically exposed through ProducerQueue::Create(...)
-  // static template methods inherited from ClientBase, which take the same
-  // arguments as the constructors.
-  explicit ProducerQueue(pdx::LocalChannelHandle handle);
-  ProducerQueue(const ProducerQueueConfig& config, const UsagePolicy& usage);
-
-  // Dequeue a producer buffer to write. The returned buffer in |Gain|'ed mode,
-  // and caller should call Post() once it's done writing to release the buffer
-  // to the consumer side.
-  //
-  // @param slot the slot of the returned buffer.
-  // @return a buffer in gained state, which was originally in posted state or
-  //     released state.
-  pdx::Status<std::shared_ptr<ProducerBuffer>> DequeueUnacquiredBuffer(
-      size_t* slot);
-};
-
-class ConsumerQueue : public BufferHubQueue {
- public:
-  // Get a consumer buffer. Note that the method doesn't check whether the
-  // buffer slot has a valid buffer that has been imported already. When no
-  // buffer has been imported before it returns nullptr; otherwise returns a
-  // shared pointer to a ConsumerBuffer.
-  std::shared_ptr<ConsumerBuffer> GetBuffer(size_t slot) const {
-    return std::static_pointer_cast<ConsumerBuffer>(
-        BufferHubQueue::GetBuffer(slot));
-  }
-
-  // Import a ConsumerQueue from a channel handle. |ignore_on_import| controls
-  // whether or not buffers are set to be ignored when imported. This may be
-  // used to avoid participation in the buffer lifecycle by a consumer queue
-  // that is only used to spawn other consumer queues, such as in an
-  // intermediate service.
-  static std::unique_ptr<ConsumerQueue> Import(pdx::LocalChannelHandle handle);
-
-  // Import newly created buffers from the service side.
-  // Returns number of buffers successfully imported or an error.
-  pdx::Status<size_t> ImportBuffers();
-
-  // Dequeue a consumer buffer to read. The returned buffer in |Acquired|'ed
-  // mode, and caller should call Releasse() once it's done writing to release
-  // the buffer to the producer side. |meta| is passed along from BufferHub,
-  // The user of ProducerBuffer is responsible with making sure that the
-  // Dequeue() is done with the corect metadata type and size with those used
-  // when the buffer is orignally created.
-  template <typename Meta>
-  pdx::Status<std::shared_ptr<ConsumerBuffer>> Dequeue(
-      int timeout, size_t* slot, Meta* meta, pdx::LocalHandle* acquire_fence) {
-    return Dequeue(timeout, slot, meta, sizeof(*meta), acquire_fence);
-  }
-  pdx::Status<std::shared_ptr<ConsumerBuffer>> Dequeue(
-      int timeout, size_t* slot, pdx::LocalHandle* acquire_fence) {
-    return Dequeue(timeout, slot, nullptr, 0, acquire_fence);
-  }
-
-  pdx::Status<std::shared_ptr<ConsumerBuffer>> Dequeue(
-      int timeout, size_t* slot, void* meta, size_t user_metadata_size,
-      pdx::LocalHandle* acquire_fence);
-  pdx::Status<std::shared_ptr<ConsumerBuffer>> Dequeue(
-      int timeout, size_t* slot, DvrNativeBufferMetadata* out_meta,
-      pdx::LocalHandle* acquire_fence);
-
- private:
-  friend BufferHubQueue;
-
-  explicit ConsumerQueue(pdx::LocalChannelHandle handle);
-
-  // Add a consumer buffer to populate the queue. Once added, a consumer buffer
-  // is NOT available to use until the producer side |Post| it. |WaitForBuffers|
-  // will catch the |Post| and |Acquire| the buffer to make it available for
-  // consumer.
-  pdx::Status<void> AddBuffer(const std::shared_ptr<ConsumerBuffer>& buffer,
-                              size_t slot);
-
-  pdx::Status<void> OnBufferAllocated() override;
-};
-
-}  // namespace dvr
-}  // namespace android
-
-#endif  // ANDROID_DVR_BUFFER_HUB_QUEUE_CLIENT_H_
diff --git a/libs/vr/libbufferhubqueue/include/private/dvr/buffer_hub_queue_parcelable.h b/libs/vr/libbufferhubqueue/include/private/dvr/buffer_hub_queue_parcelable.h
deleted file mode 100644
index 36ab5f6..0000000
--- a/libs/vr/libbufferhubqueue/include/private/dvr/buffer_hub_queue_parcelable.h
+++ /dev/null
@@ -1,74 +0,0 @@
-#ifndef ANDROID_DVR_BUFFER_HUB_QUEUE_PARCELABLE_H_
-#define ANDROID_DVR_BUFFER_HUB_QUEUE_PARCELABLE_H_
-
-#if defined(__clang__)
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Weverything"
-#endif
-
-// The following headers are included without checking every warning.
-// TODO(b/72172820): Remove the workaround once we have enforced -Weverything
-// in these headers and their dependencies.
-#include <pdx/channel_parcelable.h>
-
-#if defined(__clang__)
-#pragma clang diagnostic pop
-#endif
-
-namespace android {
-namespace dvr {
-
-enum BufferHubQueueParcelableMagic : uint32_t {
-  Producer = 0x62687170,  // 'bhqp'
-  Consumer = 0x62687163,  // 'bhqc'
-};
-
-template <BufferHubQueueParcelableMagic Magic>
-class BufferHubQueueParcelable : public Parcelable {
- public:
-  BufferHubQueueParcelable() = default;
-
-  BufferHubQueueParcelable(BufferHubQueueParcelable&& other) noexcept = default;
-  BufferHubQueueParcelable& operator=(BufferHubQueueParcelable&& other) noexcept {
-    channel_parcelable_ = std::move(other.channel_parcelable_);
-    return *this;
-  }
-
-  // Constructs an parcelable contains the channel parcelable.
-  explicit BufferHubQueueParcelable(
-      std::unique_ptr<pdx::ChannelParcelable> channel_parcelable)
-      : channel_parcelable_(std::move(channel_parcelable)) {}
-
-  BufferHubQueueParcelable(const BufferHubQueueParcelable&) = delete;
-  void operator=(const BufferHubQueueParcelable&) = delete;
-
-  bool IsValid() const;
-
-  // Returns a channel handle constructed from this parcelable object and takes
-  // the ownership of all resources from the parcelable object.
-  pdx::LocalChannelHandle TakeChannelHandle();
-
-  // Serializes the queue parcelable into the given parcel. Note that no system
-  // resources are getting duplicated, nor did the parcel takes ownership of the
-  // queue parcelable. Thus, the parcelable object must remain valid for the
-  // lifetime of the parcel.
-  status_t writeToParcel(Parcel* parcel) const override;
-
-  // Deserialize the queue parcelable from the given parcel. Note that system
-  // resources are duplicated from the parcel into the queue parcelable. Returns
-  // error if the targeting parcelable object is already valid.
-  status_t readFromParcel(const Parcel* parcel) override;
-
- private:
-  std::unique_ptr<pdx::ChannelParcelable> channel_parcelable_;
-};
-
-using ProducerQueueParcelable =
-    BufferHubQueueParcelable<BufferHubQueueParcelableMagic::Producer>;
-using ConsumerQueueParcelable =
-    BufferHubQueueParcelable<BufferHubQueueParcelableMagic::Consumer>;
-
-}  // namespace dvr
-}  // namespace android
-
-#endif  // ANDROID_DVR_BUFFER_HUB_QUEUE_PARCELABLE_H_
diff --git a/libs/vr/libbufferhubqueue/include/private/dvr/epoll_file_descriptor.h b/libs/vr/libbufferhubqueue/include/private/dvr/epoll_file_descriptor.h
deleted file mode 100644
index 2f14f7c..0000000
--- a/libs/vr/libbufferhubqueue/include/private/dvr/epoll_file_descriptor.h
+++ /dev/null
@@ -1,64 +0,0 @@
-#ifndef ANDROID_DVR_EPOLL_FILE_DESCRIPTOR_H_
-#define ANDROID_DVR_EPOLL_FILE_DESCRIPTOR_H_
-
-#include <android-base/unique_fd.h>
-#include <log/log.h>
-#include <sys/epoll.h>
-
-namespace android {
-namespace dvr {
-
-class EpollFileDescriptor {
- public:
-  static const int CTL_ADD = EPOLL_CTL_ADD;
-  static const int CTL_MOD = EPOLL_CTL_MOD;
-  static const int CTL_DEL = EPOLL_CTL_DEL;
-
-  EpollFileDescriptor() : fd_(-1) {}
-
-  // Constructs an EpollFileDescriptor from an integer file descriptor and
-  // takes ownership.
-  explicit EpollFileDescriptor(int fd) : fd_(fd) {}
-
-  bool IsValid() const { return fd_.get() >= 0; }
-
-  int Create() {
-    if (IsValid()) {
-      ALOGW("epoll fd has already been created.");
-      return -EALREADY;
-    }
-
-    fd_.reset(epoll_create1(EPOLL_CLOEXEC));
-
-    if (fd_.get() < 0)
-      return -errno;
-    else
-      return 0;
-  }
-
-  int Control(int op, int target_fd, epoll_event* ev) {
-    if (epoll_ctl(fd_.get(), op, target_fd, ev) < 0)
-      return -errno;
-    else
-      return 0;
-  }
-
-  int Wait(epoll_event* events, int maxevents, int timeout) {
-    int ret = epoll_wait(fd_.get(), events, maxevents, timeout);
-
-    if (ret < 0)
-      return -errno;
-    else
-      return ret;
-  }
-
-  int Get() const { return fd_.get(); }
-
- private:
-  base::unique_fd fd_;
-};
-
-}  // namespace dvr
-}  // namespace android
-
-#endif  // ANDROID_DVR_EPOLL_FILE_DESCRIPTOR_H_
diff --git a/libs/vr/libbufferhubqueue/tests/Android.bp b/libs/vr/libbufferhubqueue/tests/Android.bp
deleted file mode 100644
index 33a0d75..0000000
--- a/libs/vr/libbufferhubqueue/tests/Android.bp
+++ /dev/null
@@ -1,66 +0,0 @@
-
-package {
-    // See: http://go/android-license-faq
-    // A large-scale-change added 'default_applicable_licenses' to import
-    // all of the 'license_kinds' from "frameworks_native_license"
-    // to get the below license kinds:
-    //   SPDX-license-identifier-Apache-2.0
-    default_applicable_licenses: ["frameworks_native_license"],
-}
-
-header_libraries = [
-    "libdvr_headers",
-]
-
-shared_libraries = [
-    "libbase",
-    "libbinder",
-    "libbufferhubqueue",
-    "libcutils",
-    "libgui",
-    "liblog",
-    "libhardware",
-    "libui",
-    "libutils",
-    "libnativewindow",
-    "libpdx_default_transport",
-]
-
-static_libraries = [
-    "libchrome",
-    "libdvrcommon",
-    "libperformance",
-]
-
-cc_test {
-    srcs: ["buffer_hub_queue-test.cpp"],
-    header_libs: header_libraries,
-    static_libs: static_libraries,
-    shared_libs: shared_libraries,
-    cflags: [
-        "-DLOG_TAG=\"buffer_hub_queue-test\"",
-        "-DTRACE=0",
-        "-O0",
-        "-g",
-        "-Wall",
-        "-Werror",
-        "-Wno-error=sign-compare", // to fix later
-    ],
-    name: "buffer_hub_queue-test",
-}
-
-cc_test {
-    srcs: ["buffer_hub_queue_producer-test.cpp"],
-    header_libs: header_libraries,
-    static_libs: static_libraries,
-    shared_libs: shared_libraries,
-    cflags: [
-        "-DLOG_TAG=\"buffer_hub_queue_producer-test\"",
-        "-DTRACE=0",
-        "-O0",
-        "-g",
-        "-Wall",
-        "-Werror",
-    ],
-    name: "buffer_hub_queue_producer-test",
-}
diff --git a/libs/vr/libbufferhubqueue/tests/buffer_hub_queue-test.cpp b/libs/vr/libbufferhubqueue/tests/buffer_hub_queue-test.cpp
deleted file mode 100644
index 6ae603b..0000000
--- a/libs/vr/libbufferhubqueue/tests/buffer_hub_queue-test.cpp
+++ /dev/null
@@ -1,1083 +0,0 @@
-#include <base/logging.h>
-#include <binder/Parcel.h>
-#include <dvr/dvr_api.h>
-#include <private/dvr/buffer_hub_queue_client.h>
-#include <private/dvr/consumer_buffer.h>
-#include <private/dvr/producer_buffer.h>
-
-#include <gtest/gtest.h>
-#include <poll.h>
-#include <sys/eventfd.h>
-
-#include <vector>
-
-// Enable/disable debug logging.
-#define TRACE 0
-
-namespace android {
-namespace dvr {
-
-using pdx::LocalChannelHandle;
-using pdx::LocalHandle;
-
-namespace {
-
-constexpr uint32_t kBufferWidth = 100;
-constexpr uint32_t kBufferHeight = 1;
-constexpr uint32_t kBufferLayerCount = 1;
-constexpr uint32_t kBufferFormat = HAL_PIXEL_FORMAT_BLOB;
-constexpr uint64_t kBufferUsage = GRALLOC_USAGE_SW_READ_RARELY;
-constexpr int kTimeoutMs = 100;
-constexpr int kNoTimeout = 0;
-
-class BufferHubQueueTest : public ::testing::Test {
- public:
-  bool CreateProducerQueue(const ProducerQueueConfig& config,
-                           const UsagePolicy& usage) {
-    producer_queue_ = ProducerQueue::Create(config, usage);
-    return producer_queue_ != nullptr;
-  }
-
-  bool CreateConsumerQueue() {
-    if (producer_queue_) {
-      consumer_queue_ = producer_queue_->CreateConsumerQueue();
-      return consumer_queue_ != nullptr;
-    } else {
-      return false;
-    }
-  }
-
-  bool CreateQueues(const ProducerQueueConfig& config,
-                    const UsagePolicy& usage) {
-    return CreateProducerQueue(config, usage) && CreateConsumerQueue();
-  }
-
-  void AllocateBuffer(size_t* slot_out = nullptr) {
-    // Create producer buffer.
-    auto status = producer_queue_->AllocateBuffer(kBufferWidth, kBufferHeight,
-                                                  kBufferLayerCount,
-                                                  kBufferFormat, kBufferUsage);
-
-    ASSERT_TRUE(status.ok());
-    size_t slot = status.take();
-    if (slot_out)
-      *slot_out = slot;
-  }
-
-  bool WaitAndHandleOnce(BufferHubQueue* queue, int timeout_ms) {
-    pollfd pfd{queue->queue_fd(), POLLIN, 0};
-    int ret;
-    do {
-      ret = poll(&pfd, 1, timeout_ms);
-    } while (ret == -1 && errno == EINTR);
-
-    if (ret < 0) {
-      ALOGW("Failed to poll queue %d's event fd, error: %s.", queue->id(),
-            strerror(errno));
-      return false;
-    } else if (ret == 0) {
-      return false;
-    }
-    return queue->HandleQueueEvents();
-  }
-
- protected:
-  ProducerQueueConfigBuilder config_builder_;
-  std::unique_ptr<ProducerQueue> producer_queue_;
-  std::unique_ptr<ConsumerQueue> consumer_queue_;
-};
-
-TEST_F(BufferHubQueueTest, TestDequeue) {
-  const int64_t nb_dequeue_times = 16;
-
-  ASSERT_TRUE(CreateQueues(config_builder_.Build(), UsagePolicy{}));
-
-  // Allocate only one buffer.
-  AllocateBuffer();
-
-  // But dequeue multiple times.
-  for (int64_t i = 0; i < nb_dequeue_times; i++) {
-    size_t slot;
-    LocalHandle fence;
-    DvrNativeBufferMetadata mi, mo;
-
-    // Producer gains a buffer.
-    auto p1_status = producer_queue_->Dequeue(kTimeoutMs, &slot, &mo, &fence);
-    EXPECT_TRUE(p1_status.ok());
-    auto p1 = p1_status.take();
-    ASSERT_NE(p1, nullptr);
-
-    // Producer posts the buffer.
-    mi.index = i;
-    EXPECT_EQ(p1->PostAsync(&mi, LocalHandle()), 0);
-
-    // Consumer acquires a buffer.
-    auto c1_status = consumer_queue_->Dequeue(kTimeoutMs, &slot, &mo, &fence);
-    EXPECT_TRUE(c1_status.ok()) << c1_status.GetErrorMessage();
-    auto c1 = c1_status.take();
-    ASSERT_NE(c1, nullptr);
-    EXPECT_EQ(mi.index, i);
-    EXPECT_EQ(mo.index, i);
-
-    // Consumer releases the buffer.
-    EXPECT_EQ(c1->ReleaseAsync(&mi, LocalHandle()), 0);
-  }
-}
-
-TEST_F(BufferHubQueueTest,
-       TestDequeuePostedBufferIfNoAvailableReleasedBuffer_withConsumerBuffer) {
-  ASSERT_TRUE(CreateQueues(config_builder_.Build(), UsagePolicy{}));
-
-  // Allocate 3 buffers to use.
-  const size_t test_queue_capacity = 3;
-  for (int64_t i = 0; i < test_queue_capacity; i++) {
-    AllocateBuffer();
-  }
-  EXPECT_EQ(producer_queue_->capacity(), test_queue_capacity);
-
-  size_t producer_slot, consumer_slot;
-  LocalHandle fence;
-  DvrNativeBufferMetadata mi, mo;
-
-  // Producer posts 2 buffers and remember their posted sequence.
-  std::deque<size_t> posted_slots;
-  for (int64_t i = 0; i < 2; i++) {
-    auto p1_status =
-        producer_queue_->Dequeue(kTimeoutMs, &producer_slot, &mo, &fence, true);
-    EXPECT_TRUE(p1_status.ok());
-    auto p1 = p1_status.take();
-    ASSERT_NE(p1, nullptr);
-
-    // Producer should not be gaining posted buffer when there are still
-    // available buffers to gain.
-    auto found_iter =
-        std::find(posted_slots.begin(), posted_slots.end(), producer_slot);
-    EXPECT_EQ(found_iter, posted_slots.end());
-    posted_slots.push_back(producer_slot);
-
-    // Producer posts the buffer.
-    mi.index = i;
-    EXPECT_EQ(0, p1->PostAsync(&mi, LocalHandle()));
-  }
-
-  // Consumer acquires one buffer.
-  auto c1_status =
-      consumer_queue_->Dequeue(kTimeoutMs, &consumer_slot, &mo, &fence);
-  EXPECT_TRUE(c1_status.ok());
-  auto c1 = c1_status.take();
-  ASSERT_NE(c1, nullptr);
-  // Consumer should get the oldest posted buffer. No checks here.
-  // posted_slots[0] should be in acquired state now.
-  EXPECT_EQ(mo.index, 0);
-  // Consumer releases the buffer.
-  EXPECT_EQ(c1->ReleaseAsync(&mi, LocalHandle()), 0);
-  // posted_slots[0] should be in released state now.
-
-  // Producer gain and post 2 buffers.
-  for (int64_t i = 0; i < 2; i++) {
-    auto p1_status =
-        producer_queue_->Dequeue(kTimeoutMs, &producer_slot, &mo, &fence, true);
-    EXPECT_TRUE(p1_status.ok());
-    auto p1 = p1_status.take();
-    ASSERT_NE(p1, nullptr);
-
-    // The gained buffer should be the one in released state or the one haven't
-    // been use.
-    EXPECT_NE(posted_slots[1], producer_slot);
-
-    mi.index = i + 2;
-    EXPECT_EQ(0, p1->PostAsync(&mi, LocalHandle()));
-  }
-
-  // Producer gains a buffer.
-  auto p1_status =
-      producer_queue_->Dequeue(kTimeoutMs, &producer_slot, &mo, &fence, true);
-  EXPECT_TRUE(p1_status.ok());
-  auto p1 = p1_status.take();
-  ASSERT_NE(p1, nullptr);
-
-  // The gained buffer should be the oldest posted buffer.
-  EXPECT_EQ(posted_slots[1], producer_slot);
-
-  // Producer posts the buffer.
-  mi.index = 4;
-  EXPECT_EQ(0, p1->PostAsync(&mi, LocalHandle()));
-}
-
-TEST_F(BufferHubQueueTest,
-       TestDequeuePostedBufferIfNoAvailableReleasedBuffer_noConsumerBuffer) {
-  ASSERT_TRUE(CreateQueues(config_builder_.Build(), UsagePolicy{}));
-
-  // Allocate 4 buffers to use.
-  const size_t test_queue_capacity = 4;
-  for (int64_t i = 0; i < test_queue_capacity; i++) {
-    AllocateBuffer();
-  }
-  EXPECT_EQ(producer_queue_->capacity(), test_queue_capacity);
-
-  // Post all allowed buffers and remember their posted sequence.
-  std::deque<size_t> posted_slots;
-  for (int64_t i = 0; i < test_queue_capacity; i++) {
-    size_t slot;
-    LocalHandle fence;
-    DvrNativeBufferMetadata mi, mo;
-
-    // Producer gains a buffer.
-    auto p1_status =
-        producer_queue_->Dequeue(kTimeoutMs, &slot, &mo, &fence, true);
-    EXPECT_TRUE(p1_status.ok());
-    auto p1 = p1_status.take();
-    ASSERT_NE(p1, nullptr);
-
-    // Producer should not be gaining posted buffer when there are still
-    // available buffers to gain.
-    auto found_iter = std::find(posted_slots.begin(), posted_slots.end(), slot);
-    EXPECT_EQ(found_iter, posted_slots.end());
-    posted_slots.push_back(slot);
-
-    // Producer posts the buffer.
-    mi.index = i;
-    EXPECT_EQ(p1->PostAsync(&mi, LocalHandle()), 0);
-  }
-
-  // Gain posted buffers in sequence.
-  const int64_t nb_dequeue_all_times = 2;
-  for (int j = 0; j < nb_dequeue_all_times; ++j) {
-    for (int i = 0; i < test_queue_capacity; ++i) {
-      size_t slot;
-      LocalHandle fence;
-      DvrNativeBufferMetadata mi, mo;
-
-      // Producer gains a buffer.
-      auto p1_status =
-          producer_queue_->Dequeue(kTimeoutMs, &slot, &mo, &fence, true);
-      EXPECT_TRUE(p1_status.ok());
-      auto p1 = p1_status.take();
-      ASSERT_NE(p1, nullptr);
-
-      // The gained buffer should be the oldest posted buffer.
-      EXPECT_EQ(posted_slots[i], slot);
-
-      // Producer posts the buffer.
-      mi.index = i + test_queue_capacity * (j + 1);
-      EXPECT_EQ(p1->PostAsync(&mi, LocalHandle()), 0);
-    }
-  }
-}
-
-TEST_F(BufferHubQueueTest, TestProducerConsumer) {
-  const size_t kBufferCount = 16;
-  size_t slot;
-  DvrNativeBufferMetadata mi, mo;
-  LocalHandle fence;
-
-  ASSERT_TRUE(CreateQueues(config_builder_.Build(), UsagePolicy{}));
-
-  for (size_t i = 0; i < kBufferCount; i++) {
-    AllocateBuffer();
-
-    // Producer queue has all the available buffers on initialize.
-    ASSERT_EQ(producer_queue_->count(), i + 1);
-    ASSERT_EQ(producer_queue_->capacity(), i + 1);
-
-    // Consumer queue has no avaiable buffer on initialize.
-    ASSERT_EQ(consumer_queue_->count(), 0U);
-    // Consumer queue does not import buffers until a dequeue is issued.
-    ASSERT_EQ(consumer_queue_->capacity(), i);
-    // Dequeue returns timeout since no buffer is ready to consumer, but
-    // this implicitly triggers buffer import and bump up |capacity|.
-    auto status = consumer_queue_->Dequeue(kNoTimeout, &slot, &mo, &fence);
-    ASSERT_FALSE(status.ok());
-    ASSERT_EQ(ETIMEDOUT, status.error());
-    ASSERT_EQ(consumer_queue_->capacity(), i + 1);
-  }
-
-  // Use eventfd as a stand-in for a fence.
-  LocalHandle post_fence(eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK));
-
-  for (size_t i = 0; i < kBufferCount; i++) {
-    // First time there is no buffer available to dequeue.
-    auto consumer_status =
-        consumer_queue_->Dequeue(kNoTimeout, &slot, &mo, &fence);
-    ASSERT_FALSE(consumer_status.ok());
-    ASSERT_EQ(consumer_status.error(), ETIMEDOUT);
-
-    // Make sure Producer buffer is POSTED so that it's ready to Accquire
-    // in the consumer's Dequeue() function.
-    auto producer_status =
-        producer_queue_->Dequeue(kTimeoutMs, &slot, &mo, &fence);
-    ASSERT_TRUE(producer_status.ok());
-    auto producer = producer_status.take();
-    ASSERT_NE(nullptr, producer);
-
-    mi.index = static_cast<int64_t>(i);
-    ASSERT_EQ(producer->PostAsync(&mi, post_fence), 0);
-
-    // Second time the just the POSTED buffer should be dequeued.
-    consumer_status = consumer_queue_->Dequeue(kTimeoutMs, &slot, &mo, &fence);
-    ASSERT_TRUE(consumer_status.ok());
-    EXPECT_TRUE(fence.IsValid());
-
-    auto consumer = consumer_status.take();
-    ASSERT_NE(nullptr, consumer);
-    ASSERT_EQ(mi.index, mo.index);
-  }
-}
-
-TEST_F(BufferHubQueueTest, TestInsertBuffer) {
-  ASSERT_TRUE(CreateProducerQueue(config_builder_.Build(), UsagePolicy{}));
-
-  consumer_queue_ = producer_queue_->CreateConsumerQueue();
-  ASSERT_TRUE(consumer_queue_ != nullptr);
-  EXPECT_EQ(producer_queue_->capacity(), 0);
-  EXPECT_EQ(consumer_queue_->capacity(), 0);
-
-  std::shared_ptr<ProducerBuffer> p1 = ProducerBuffer::Create(
-      kBufferWidth, kBufferHeight, kBufferFormat, kBufferUsage, 0);
-  ASSERT_TRUE(p1 != nullptr);
-  ASSERT_EQ(p1->GainAsync(), 0);
-
-  // Inserting a posted buffer will fail.
-  DvrNativeBufferMetadata meta;
-  EXPECT_EQ(p1->PostAsync(&meta, LocalHandle()), 0);
-  auto status_or_slot = producer_queue_->InsertBuffer(p1);
-  EXPECT_FALSE(status_or_slot.ok());
-  EXPECT_EQ(status_or_slot.error(), EINVAL);
-
-  // Inserting a gained buffer will succeed.
-  std::shared_ptr<ProducerBuffer> p2 = ProducerBuffer::Create(
-      kBufferWidth, kBufferHeight, kBufferFormat, kBufferUsage);
-  ASSERT_EQ(p2->GainAsync(), 0);
-  ASSERT_TRUE(p2 != nullptr);
-  status_or_slot = producer_queue_->InsertBuffer(p2);
-  EXPECT_TRUE(status_or_slot.ok()) << status_or_slot.GetErrorMessage();
-  // This is the first buffer inserted, should take slot 0.
-  size_t slot = status_or_slot.get();
-  EXPECT_EQ(slot, 0);
-
-  // Wait and expect the consumer to kick up the newly inserted buffer.
-  WaitAndHandleOnce(consumer_queue_.get(), kTimeoutMs);
-  EXPECT_EQ(consumer_queue_->capacity(), 1ULL);
-}
-
-TEST_F(BufferHubQueueTest, TestRemoveBuffer) {
-  ASSERT_TRUE(CreateProducerQueue(config_builder_.Build(), UsagePolicy{}));
-  DvrNativeBufferMetadata mo;
-
-  // Allocate buffers.
-  const size_t kBufferCount = 4u;
-  for (size_t i = 0; i < kBufferCount; i++) {
-    AllocateBuffer();
-  }
-  ASSERT_EQ(kBufferCount, producer_queue_->count());
-  ASSERT_EQ(kBufferCount, producer_queue_->capacity());
-
-  consumer_queue_ = producer_queue_->CreateConsumerQueue();
-  ASSERT_NE(nullptr, consumer_queue_);
-
-  // Check that buffers are correctly imported on construction.
-  EXPECT_EQ(kBufferCount, consumer_queue_->capacity());
-  EXPECT_EQ(0u, consumer_queue_->count());
-
-  // Dequeue all the buffers and keep track of them in an array. This prevents
-  // the producer queue ring buffer ref counts from interfering with the tests.
-  struct Entry {
-    std::shared_ptr<ProducerBuffer> buffer;
-    LocalHandle fence;
-    size_t slot;
-  };
-  std::array<Entry, kBufferCount> buffers;
-
-  for (size_t i = 0; i < kBufferCount; i++) {
-    Entry* entry = &buffers[i];
-    auto producer_status =
-        producer_queue_->Dequeue(kTimeoutMs, &entry->slot, &mo, &entry->fence);
-    ASSERT_TRUE(producer_status.ok());
-    entry->buffer = producer_status.take();
-    ASSERT_NE(nullptr, entry->buffer);
-  }
-
-  // Remove a buffer and make sure both queues reflect the change.
-  ASSERT_TRUE(producer_queue_->RemoveBuffer(buffers[0].slot));
-  EXPECT_EQ(kBufferCount - 1, producer_queue_->capacity());
-
-  // As long as the removed buffer is still alive the consumer queue won't know
-  // its gone.
-  EXPECT_EQ(kBufferCount, consumer_queue_->capacity());
-  EXPECT_FALSE(consumer_queue_->HandleQueueEvents());
-  EXPECT_EQ(kBufferCount, consumer_queue_->capacity());
-
-  // Release the removed buffer.
-  buffers[0].buffer = nullptr;
-
-  // Now the consumer queue should know it's gone.
-  EXPECT_FALSE(WaitAndHandleOnce(consumer_queue_.get(), kTimeoutMs));
-  ASSERT_EQ(kBufferCount - 1, consumer_queue_->capacity());
-
-  // Allocate a new buffer. This should take the first empty slot.
-  size_t slot;
-  AllocateBuffer(&slot);
-  ALOGE_IF(TRACE, "ALLOCATE %zu", slot);
-  EXPECT_EQ(buffers[0].slot, slot);
-  EXPECT_EQ(kBufferCount, producer_queue_->capacity());
-
-  // The consumer queue should pick up the new buffer.
-  EXPECT_EQ(kBufferCount - 1, consumer_queue_->capacity());
-  EXPECT_FALSE(consumer_queue_->HandleQueueEvents());
-  EXPECT_EQ(kBufferCount, consumer_queue_->capacity());
-
-  // Remove and allocate a buffer.
-  ASSERT_TRUE(producer_queue_->RemoveBuffer(buffers[1].slot));
-  EXPECT_EQ(kBufferCount - 1, producer_queue_->capacity());
-  buffers[1].buffer = nullptr;
-
-  AllocateBuffer(&slot);
-  ALOGE_IF(TRACE, "ALLOCATE %zu", slot);
-  EXPECT_EQ(buffers[1].slot, slot);
-  EXPECT_EQ(kBufferCount, producer_queue_->capacity());
-
-  // The consumer queue should pick up the new buffer but the count shouldn't
-  // change.
-  EXPECT_EQ(kBufferCount, consumer_queue_->capacity());
-  EXPECT_FALSE(consumer_queue_->HandleQueueEvents());
-  EXPECT_EQ(kBufferCount, consumer_queue_->capacity());
-
-  // Remove and allocate a buffer, but don't free the buffer right away.
-  ASSERT_TRUE(producer_queue_->RemoveBuffer(buffers[2].slot));
-  EXPECT_EQ(kBufferCount - 1, producer_queue_->capacity());
-
-  AllocateBuffer(&slot);
-  ALOGE_IF(TRACE, "ALLOCATE %zu", slot);
-  EXPECT_EQ(buffers[2].slot, slot);
-  EXPECT_EQ(kBufferCount, producer_queue_->capacity());
-
-  EXPECT_EQ(kBufferCount, consumer_queue_->capacity());
-  EXPECT_FALSE(consumer_queue_->HandleQueueEvents());
-  EXPECT_EQ(kBufferCount, consumer_queue_->capacity());
-
-  // Release the producer buffer to trigger a POLLHUP event for an already
-  // removed buffer.
-  buffers[2].buffer = nullptr;
-  EXPECT_EQ(kBufferCount, consumer_queue_->capacity());
-  EXPECT_FALSE(consumer_queue_->HandleQueueEvents());
-  EXPECT_EQ(kBufferCount, consumer_queue_->capacity());
-}
-
-TEST_F(BufferHubQueueTest, TestMultipleConsumers) {
-  // ProducerConfigureBuilder doesn't set Metadata{size}, which means there
-  // is no metadata associated with this BufferQueue's buffer.
-  ASSERT_TRUE(CreateProducerQueue(config_builder_.Build(), UsagePolicy{}));
-
-  // Allocate buffers.
-  const size_t kBufferCount = 4u;
-  for (size_t i = 0; i < kBufferCount; i++) {
-    AllocateBuffer();
-  }
-  ASSERT_EQ(kBufferCount, producer_queue_->count());
-
-  // Build a silent consumer queue to test multi-consumer queue features.
-  auto silent_queue = producer_queue_->CreateSilentConsumerQueue();
-  ASSERT_NE(nullptr, silent_queue);
-
-  // Check that silent queue doesn't import buffers on creation.
-  EXPECT_EQ(silent_queue->capacity(), 0U);
-
-  // Dequeue and post a buffer.
-  size_t slot;
-  LocalHandle fence;
-  DvrNativeBufferMetadata mi, mo;
-  auto producer_status =
-      producer_queue_->Dequeue(kTimeoutMs, &slot, &mo, &fence);
-  EXPECT_TRUE(producer_status.ok());
-  auto producer_buffer = producer_status.take();
-  ASSERT_NE(producer_buffer, nullptr);
-  EXPECT_EQ(producer_buffer->PostAsync(&mi, {}), 0);
-  // After post, check the number of remaining available buffers.
-  EXPECT_EQ(producer_queue_->count(), kBufferCount - 1);
-
-  // Currently we expect no buffer to be available prior to calling
-  // WaitForBuffers/HandleQueueEvents.
-  // TODO(eieio): Note this behavior may change in the future.
-  EXPECT_EQ(silent_queue->count(), 0U);
-  EXPECT_FALSE(silent_queue->HandleQueueEvents());
-  EXPECT_EQ(silent_queue->count(), 0U);
-
-  // Build a new consumer queue to test multi-consumer queue features.
-  consumer_queue_ = silent_queue->CreateConsumerQueue();
-  ASSERT_NE(consumer_queue_, nullptr);
-
-  // Check that buffers are correctly imported on construction.
-  EXPECT_EQ(consumer_queue_->capacity(), kBufferCount);
-  // Buffers are only imported, but their availability is not checked until
-  // first call to Dequeue().
-  EXPECT_EQ(consumer_queue_->count(), 0U);
-
-  // Reclaim released/ignored buffers.
-  EXPECT_EQ(producer_queue_->count(), kBufferCount - 1);
-
-  usleep(10000);
-  WaitAndHandleOnce(producer_queue_.get(), kTimeoutMs);
-  EXPECT_EQ(producer_queue_->count(), kBufferCount - 1);
-
-  // Post another buffer.
-  producer_status = producer_queue_->Dequeue(kTimeoutMs, &slot, &mo, &fence);
-  EXPECT_TRUE(producer_status.ok());
-  producer_buffer = producer_status.take();
-  ASSERT_NE(producer_buffer, nullptr);
-  EXPECT_EQ(producer_buffer->PostAsync(&mi, {}), 0);
-
-  // Verify that the consumer queue receives it.
-  size_t consumer_queue_count = consumer_queue_->count();
-  WaitAndHandleOnce(consumer_queue_.get(), kTimeoutMs);
-  EXPECT_GT(consumer_queue_->count(), consumer_queue_count);
-
-  // Save the current consumer queue buffer count to compare after the dequeue.
-  consumer_queue_count = consumer_queue_->count();
-
-  // Dequeue and acquire/release (discard) buffers on the consumer end.
-  auto consumer_status =
-      consumer_queue_->Dequeue(kTimeoutMs, &slot, &mo, &fence);
-  EXPECT_TRUE(consumer_status.ok());
-  auto consumer_buffer = consumer_status.take();
-  ASSERT_NE(consumer_buffer, nullptr);
-  consumer_buffer->Discard();
-
-  // Buffer should be returned to the producer queue without being handled by
-  // the silent consumer queue.
-  EXPECT_LT(consumer_queue_->count(), consumer_queue_count);
-  EXPECT_EQ(producer_queue_->count(), kBufferCount - 2);
-
-  WaitAndHandleOnce(producer_queue_.get(), kTimeoutMs);
-  EXPECT_EQ(producer_queue_->count(), kBufferCount - 1);
-}
-
-struct TestUserMetadata {
-  char a;
-  int32_t b;
-  int64_t c;
-};
-
-constexpr uint64_t kUserMetadataSize =
-    static_cast<uint64_t>(sizeof(TestUserMetadata));
-
-TEST_F(BufferHubQueueTest, TestUserMetadata) {
-  ASSERT_TRUE(CreateQueues(
-      config_builder_.SetMetadata<TestUserMetadata>().Build(), UsagePolicy{}));
-
-  AllocateBuffer();
-
-  std::vector<TestUserMetadata> user_metadata_list = {
-      {'0', 0, 0}, {'1', 10, 3333}, {'@', 123, 1000000000}};
-
-  for (auto user_metadata : user_metadata_list) {
-    size_t slot;
-    LocalHandle fence;
-    DvrNativeBufferMetadata mi, mo;
-
-    auto p1_status = producer_queue_->Dequeue(kTimeoutMs, &slot, &mo, &fence);
-    EXPECT_TRUE(p1_status.ok());
-    auto p1 = p1_status.take();
-    ASSERT_NE(p1, nullptr);
-
-    // TODO(b/69469185): Test against metadata from consumer once we implement
-    // release metadata properly.
-    // EXPECT_EQ(mo.user_metadata_ptr, 0U);
-    // EXPECT_EQ(mo.user_metadata_size, 0U);
-
-    mi.user_metadata_size = kUserMetadataSize;
-    mi.user_metadata_ptr = reinterpret_cast<uint64_t>(&user_metadata);
-    EXPECT_EQ(p1->PostAsync(&mi, {}), 0);
-    auto c1_status = consumer_queue_->Dequeue(kTimeoutMs, &slot, &mo, &fence);
-    EXPECT_TRUE(c1_status.ok()) << c1_status.GetErrorMessage();
-    auto c1 = c1_status.take();
-    ASSERT_NE(c1, nullptr);
-
-    EXPECT_EQ(mo.user_metadata_size, kUserMetadataSize);
-    auto out_user_metadata =
-        reinterpret_cast<TestUserMetadata*>(mo.user_metadata_ptr);
-    EXPECT_EQ(user_metadata.a, out_user_metadata->a);
-    EXPECT_EQ(user_metadata.b, out_user_metadata->b);
-    EXPECT_EQ(user_metadata.c, out_user_metadata->c);
-
-    // When release, empty metadata is also legit.
-    mi.user_metadata_size = 0U;
-    mi.user_metadata_ptr = 0U;
-    c1->ReleaseAsync(&mi, {});
-  }
-}
-
-TEST_F(BufferHubQueueTest, TestUserMetadataMismatch) {
-  ASSERT_TRUE(CreateQueues(
-      config_builder_.SetMetadata<TestUserMetadata>().Build(), UsagePolicy{}));
-
-  AllocateBuffer();
-
-  TestUserMetadata user_metadata;
-  size_t slot;
-  LocalHandle fence;
-  DvrNativeBufferMetadata mi, mo;
-  auto p1_status = producer_queue_->Dequeue(kTimeoutMs, &slot, &mo, &fence);
-  EXPECT_TRUE(p1_status.ok());
-  auto p1 = p1_status.take();
-  ASSERT_NE(p1, nullptr);
-
-  // Post with mismatched user metadata size will fail. But the producer buffer
-  // itself should stay untouched.
-  mi.user_metadata_ptr = reinterpret_cast<uint64_t>(&user_metadata);
-  mi.user_metadata_size = kUserMetadataSize + 1;
-  EXPECT_EQ(p1->PostAsync(&mi, {}), -E2BIG);
-  // Post with the exact same user metdata size can success.
-  mi.user_metadata_ptr = reinterpret_cast<uint64_t>(&user_metadata);
-  mi.user_metadata_size = kUserMetadataSize;
-  EXPECT_EQ(p1->PostAsync(&mi, {}), 0);
-}
-
-TEST_F(BufferHubQueueTest, TestEnqueue) {
-  ASSERT_TRUE(CreateQueues(config_builder_.SetMetadata<int64_t>().Build(),
-                           UsagePolicy{}));
-  AllocateBuffer();
-
-  size_t slot;
-  LocalHandle fence;
-  DvrNativeBufferMetadata mo;
-  auto p1_status = producer_queue_->Dequeue(kTimeoutMs, &slot, &mo, &fence);
-  ASSERT_TRUE(p1_status.ok());
-  auto p1 = p1_status.take();
-  ASSERT_NE(nullptr, p1);
-
-  producer_queue_->Enqueue(p1, slot, 0ULL);
-  auto c1_status = consumer_queue_->Dequeue(kTimeoutMs, &slot, &mo, &fence);
-  ASSERT_FALSE(c1_status.ok());
-}
-
-TEST_F(BufferHubQueueTest, TestAllocateBuffer) {
-  ASSERT_TRUE(CreateQueues(config_builder_.Build(), UsagePolicy{}));
-
-  size_t ps1;
-  AllocateBuffer();
-  LocalHandle fence;
-  DvrNativeBufferMetadata mi, mo;
-  auto p1_status = producer_queue_->Dequeue(kTimeoutMs, &ps1, &mo, &fence);
-  ASSERT_TRUE(p1_status.ok());
-  auto p1 = p1_status.take();
-  ASSERT_NE(p1, nullptr);
-
-  // producer queue is exhausted
-  size_t ps2;
-  auto p2_status = producer_queue_->Dequeue(kTimeoutMs, &ps2, &mo, &fence);
-  ASSERT_FALSE(p2_status.ok());
-  ASSERT_EQ(ETIMEDOUT, p2_status.error());
-
-  // dynamically add buffer.
-  AllocateBuffer();
-  ASSERT_EQ(producer_queue_->count(), 1U);
-  ASSERT_EQ(producer_queue_->capacity(), 2U);
-
-  // now we can dequeue again
-  p2_status = producer_queue_->Dequeue(kTimeoutMs, &ps2, &mo, &fence);
-  ASSERT_TRUE(p2_status.ok());
-  auto p2 = p2_status.take();
-  ASSERT_NE(p2, nullptr);
-  ASSERT_EQ(producer_queue_->count(), 0U);
-  // p1 and p2 should have different slot number
-  ASSERT_NE(ps1, ps2);
-
-  // Consumer queue does not import buffers until |Dequeue| or |ImportBuffers|
-  // are called. So far consumer_queue_ should be empty.
-  ASSERT_EQ(consumer_queue_->count(), 0U);
-
-  int64_t seq = 1;
-  mi.index = seq;
-  ASSERT_EQ(p1->PostAsync(&mi, {}), 0);
-
-  size_t cs1, cs2;
-  auto c1_status = consumer_queue_->Dequeue(kTimeoutMs, &cs1, &mo, &fence);
-  ASSERT_TRUE(c1_status.ok()) << c1_status.GetErrorMessage();
-  auto c1 = c1_status.take();
-  ASSERT_NE(c1, nullptr);
-  ASSERT_EQ(consumer_queue_->count(), 0U);
-  ASSERT_EQ(consumer_queue_->capacity(), 2U);
-  ASSERT_EQ(cs1, ps1);
-
-  ASSERT_EQ(p2->PostAsync(&mi, {}), 0);
-  auto c2_status = consumer_queue_->Dequeue(kTimeoutMs, &cs2, &mo, &fence);
-  ASSERT_TRUE(c2_status.ok());
-  auto c2 = c2_status.take();
-  ASSERT_NE(c2, nullptr);
-  ASSERT_EQ(cs2, ps2);
-}
-
-TEST_F(BufferHubQueueTest, TestAllocateTwoBuffers) {
-  ASSERT_TRUE(CreateQueues(config_builder_.Build(), UsagePolicy{}));
-  ASSERT_EQ(producer_queue_->capacity(), 0);
-  auto status = producer_queue_->AllocateBuffers(
-      kBufferWidth, kBufferHeight, kBufferLayerCount, kBufferFormat,
-      kBufferUsage, /*buffer_count=*/2);
-  ASSERT_TRUE(status.ok());
-  std::vector<size_t> buffer_slots = status.take();
-  ASSERT_EQ(buffer_slots.size(), 2);
-  ASSERT_EQ(producer_queue_->capacity(), 2);
-}
-
-TEST_F(BufferHubQueueTest, TestAllocateZeroBuffers) {
-  ASSERT_TRUE(CreateQueues(config_builder_.Build(), UsagePolicy{}));
-  ASSERT_EQ(producer_queue_->capacity(), 0);
-  auto status = producer_queue_->AllocateBuffers(
-      kBufferWidth, kBufferHeight, kBufferLayerCount, kBufferFormat,
-      kBufferUsage, /*buffer_count=*/0);
-  ASSERT_TRUE(status.ok());
-  std::vector<size_t> buffer_slots = status.take();
-  ASSERT_EQ(buffer_slots.size(), 0);
-  ASSERT_EQ(producer_queue_->capacity(), 0);
-}
-
-TEST_F(BufferHubQueueTest, TestUsageSetMask) {
-  const uint32_t set_mask = GRALLOC_USAGE_SW_WRITE_OFTEN;
-  ASSERT_TRUE(
-      CreateQueues(config_builder_.Build(), UsagePolicy{set_mask, 0, 0, 0}));
-
-  // When allocation, leave out |set_mask| from usage bits on purpose.
-  auto status = producer_queue_->AllocateBuffer(
-      kBufferWidth, kBufferHeight, kBufferLayerCount, kBufferFormat,
-      kBufferUsage & ~set_mask);
-  ASSERT_TRUE(status.ok());
-
-  LocalHandle fence;
-  size_t slot;
-  DvrNativeBufferMetadata mo;
-  auto p1_status = producer_queue_->Dequeue(kTimeoutMs, &slot, &mo, &fence);
-  ASSERT_TRUE(p1_status.ok());
-  auto p1 = p1_status.take();
-  ASSERT_EQ(p1->usage() & set_mask, set_mask);
-}
-
-TEST_F(BufferHubQueueTest, TestUsageClearMask) {
-  const uint32_t clear_mask = GRALLOC_USAGE_SW_WRITE_OFTEN;
-  ASSERT_TRUE(
-      CreateQueues(config_builder_.Build(), UsagePolicy{0, clear_mask, 0, 0}));
-
-  // When allocation, add |clear_mask| into usage bits on purpose.
-  auto status = producer_queue_->AllocateBuffer(
-      kBufferWidth, kBufferHeight, kBufferLayerCount, kBufferFormat,
-      kBufferUsage | clear_mask);
-  ASSERT_TRUE(status.ok());
-
-  LocalHandle fence;
-  size_t slot;
-  DvrNativeBufferMetadata mo;
-  auto p1_status = producer_queue_->Dequeue(kTimeoutMs, &slot, &mo, &fence);
-  ASSERT_TRUE(p1_status.ok());
-  auto p1 = p1_status.take();
-  ASSERT_EQ(p1->usage() & clear_mask, 0U);
-}
-
-TEST_F(BufferHubQueueTest, TestUsageDenySetMask) {
-  const uint32_t deny_set_mask = GRALLOC_USAGE_SW_WRITE_OFTEN;
-  ASSERT_TRUE(CreateQueues(config_builder_.SetMetadata<int64_t>().Build(),
-                           UsagePolicy{0, 0, deny_set_mask, 0}));
-
-  // Now that |deny_set_mask| is illegal, allocation without those bits should
-  // be able to succeed.
-  auto status = producer_queue_->AllocateBuffer(
-      kBufferWidth, kBufferHeight, kBufferLayerCount, kBufferFormat,
-      kBufferUsage & ~deny_set_mask);
-  ASSERT_TRUE(status.ok());
-
-  // While allocation with those bits should fail.
-  status = producer_queue_->AllocateBuffer(kBufferWidth, kBufferHeight,
-                                           kBufferLayerCount, kBufferFormat,
-                                           kBufferUsage | deny_set_mask);
-  ASSERT_FALSE(status.ok());
-  ASSERT_EQ(EINVAL, status.error());
-}
-
-TEST_F(BufferHubQueueTest, TestUsageDenyClearMask) {
-  const uint32_t deny_clear_mask = GRALLOC_USAGE_SW_WRITE_OFTEN;
-  ASSERT_TRUE(CreateQueues(config_builder_.SetMetadata<int64_t>().Build(),
-                           UsagePolicy{0, 0, 0, deny_clear_mask}));
-
-  // Now that clearing |deny_clear_mask| is illegal (i.e. setting these bits are
-  // mandatory), allocation with those bits should be able to succeed.
-  auto status = producer_queue_->AllocateBuffer(
-      kBufferWidth, kBufferHeight, kBufferLayerCount, kBufferFormat,
-      kBufferUsage | deny_clear_mask);
-  ASSERT_TRUE(status.ok());
-
-  // While allocation without those bits should fail.
-  status = producer_queue_->AllocateBuffer(kBufferWidth, kBufferHeight,
-                                           kBufferLayerCount, kBufferFormat,
-                                           kBufferUsage & ~deny_clear_mask);
-  ASSERT_FALSE(status.ok());
-  ASSERT_EQ(EINVAL, status.error());
-}
-
-TEST_F(BufferHubQueueTest, TestQueueInfo) {
-  static const bool kIsAsync = true;
-  ASSERT_TRUE(CreateQueues(config_builder_.SetIsAsync(kIsAsync)
-                               .SetDefaultWidth(kBufferWidth)
-                               .SetDefaultHeight(kBufferHeight)
-                               .SetDefaultFormat(kBufferFormat)
-                               .Build(),
-                           UsagePolicy{}));
-
-  EXPECT_EQ(producer_queue_->default_width(), kBufferWidth);
-  EXPECT_EQ(producer_queue_->default_height(), kBufferHeight);
-  EXPECT_EQ(producer_queue_->default_format(), kBufferFormat);
-  EXPECT_EQ(producer_queue_->is_async(), kIsAsync);
-
-  EXPECT_EQ(consumer_queue_->default_width(), kBufferWidth);
-  EXPECT_EQ(consumer_queue_->default_height(), kBufferHeight);
-  EXPECT_EQ(consumer_queue_->default_format(), kBufferFormat);
-  EXPECT_EQ(consumer_queue_->is_async(), kIsAsync);
-}
-
-TEST_F(BufferHubQueueTest, TestFreeAllBuffers) {
-  constexpr size_t kBufferCount = 2;
-
-#define CHECK_NO_BUFFER_THEN_ALLOCATE(num_buffers)  \
-  EXPECT_EQ(consumer_queue_->count(), 0U);          \
-  EXPECT_EQ(consumer_queue_->capacity(), 0U);       \
-  EXPECT_EQ(producer_queue_->count(), 0U);          \
-  EXPECT_EQ(producer_queue_->capacity(), 0U);       \
-  for (size_t i = 0; i < num_buffers; i++) {        \
-    AllocateBuffer();                               \
-  }                                                 \
-  EXPECT_EQ(producer_queue_->count(), num_buffers); \
-  EXPECT_EQ(producer_queue_->capacity(), num_buffers);
-
-  size_t slot;
-  LocalHandle fence;
-  pdx::Status<void> status;
-  pdx::Status<std::shared_ptr<ConsumerBuffer>> consumer_status;
-  pdx::Status<std::shared_ptr<ProducerBuffer>> producer_status;
-  std::shared_ptr<ConsumerBuffer> consumer_buffer;
-  std::shared_ptr<ProducerBuffer> producer_buffer;
-  DvrNativeBufferMetadata mi, mo;
-
-  ASSERT_TRUE(CreateQueues(config_builder_.Build(), UsagePolicy{}));
-
-  // Free all buffers when buffers are avaible for dequeue.
-  CHECK_NO_BUFFER_THEN_ALLOCATE(kBufferCount);
-  status = producer_queue_->FreeAllBuffers();
-  EXPECT_TRUE(status.ok());
-
-  // Free all buffers when one buffer is dequeued.
-  CHECK_NO_BUFFER_THEN_ALLOCATE(kBufferCount);
-  producer_status = producer_queue_->Dequeue(kTimeoutMs, &slot, &mo, &fence);
-  ASSERT_TRUE(producer_status.ok());
-  status = producer_queue_->FreeAllBuffers();
-  EXPECT_TRUE(status.ok());
-
-  // Free all buffers when all buffers are dequeued.
-  CHECK_NO_BUFFER_THEN_ALLOCATE(kBufferCount);
-  for (size_t i = 0; i < kBufferCount; i++) {
-    producer_status = producer_queue_->Dequeue(kTimeoutMs, &slot, &mo, &fence);
-    ASSERT_TRUE(producer_status.ok());
-  }
-  status = producer_queue_->FreeAllBuffers();
-  EXPECT_TRUE(status.ok());
-
-  // Free all buffers when one buffer is posted.
-  CHECK_NO_BUFFER_THEN_ALLOCATE(kBufferCount);
-  producer_status = producer_queue_->Dequeue(kTimeoutMs, &slot, &mo, &fence);
-  ASSERT_TRUE(producer_status.ok());
-  producer_buffer = producer_status.take();
-  ASSERT_NE(nullptr, producer_buffer);
-  ASSERT_EQ(0, producer_buffer->PostAsync(&mi, fence));
-  status = producer_queue_->FreeAllBuffers();
-  EXPECT_TRUE(status.ok());
-
-  // Free all buffers when all buffers are posted.
-  CHECK_NO_BUFFER_THEN_ALLOCATE(kBufferCount);
-  for (size_t i = 0; i < kBufferCount; i++) {
-    producer_status = producer_queue_->Dequeue(kTimeoutMs, &slot, &mo, &fence);
-    ASSERT_TRUE(producer_status.ok());
-    producer_buffer = producer_status.take();
-    ASSERT_NE(producer_buffer, nullptr);
-    ASSERT_EQ(producer_buffer->PostAsync(&mi, fence), 0);
-  }
-  status = producer_queue_->FreeAllBuffers();
-  EXPECT_TRUE(status.ok());
-
-  // Free all buffers when all buffers are acquired.
-  CHECK_NO_BUFFER_THEN_ALLOCATE(kBufferCount);
-  for (size_t i = 0; i < kBufferCount; i++) {
-    producer_status = producer_queue_->Dequeue(kTimeoutMs, &slot, &mo, &fence);
-    ASSERT_TRUE(producer_status.ok());
-    producer_buffer = producer_status.take();
-    ASSERT_NE(producer_buffer, nullptr);
-    ASSERT_EQ(producer_buffer->PostAsync(&mi, fence), 0);
-    consumer_status = consumer_queue_->Dequeue(kTimeoutMs, &slot, &mo, &fence);
-    ASSERT_TRUE(consumer_status.ok()) << consumer_status.GetErrorMessage();
-  }
-
-  status = producer_queue_->FreeAllBuffers();
-  EXPECT_TRUE(status.ok());
-
-  // In addition to FreeAllBuffers() from the queue, it is also required to
-  // delete all references to the ProducerBuffer (i.e. the PDX client).
-  producer_buffer = nullptr;
-
-  // Crank consumer queue events to pickup EPOLLHUP events on the queue.
-  consumer_queue_->HandleQueueEvents();
-
-  // One last check.
-  CHECK_NO_BUFFER_THEN_ALLOCATE(kBufferCount);
-
-#undef CHECK_NO_BUFFER_THEN_ALLOCATE
-}
-
-TEST_F(BufferHubQueueTest, TestProducerToParcelableNotEmpty) {
-  ASSERT_TRUE(CreateQueues(config_builder_.SetMetadata<uint64_t>().Build(),
-                           UsagePolicy{}));
-
-  // Allocate only one buffer.
-  AllocateBuffer();
-
-  // Export should fail as the queue is not empty.
-  auto status = producer_queue_->TakeAsParcelable();
-  EXPECT_FALSE(status.ok());
-}
-
-TEST_F(BufferHubQueueTest, TestProducerExportToParcelable) {
-  ASSERT_TRUE(CreateQueues(config_builder_.Build(), UsagePolicy{}));
-
-  auto s1 = producer_queue_->TakeAsParcelable();
-  EXPECT_TRUE(s1.ok());
-
-  ProducerQueueParcelable output_parcelable = s1.take();
-  EXPECT_TRUE(output_parcelable.IsValid());
-
-  Parcel parcel;
-  status_t res;
-  res = output_parcelable.writeToParcel(&parcel);
-  EXPECT_EQ(res, OK);
-
-  // After written into parcelable, the output_parcelable is still valid has
-  // keeps the producer channel alive.
-  EXPECT_TRUE(output_parcelable.IsValid());
-
-  // Creating producer buffer should fail.
-  auto s2 = producer_queue_->AllocateBuffer(kBufferWidth, kBufferHeight,
-                                            kBufferLayerCount, kBufferFormat,
-                                            kBufferUsage);
-  ASSERT_FALSE(s2.ok());
-
-  // Reset the data position so that we can read back from the same parcel
-  // without doing actually Binder IPC.
-  parcel.setDataPosition(0);
-  producer_queue_ = nullptr;
-
-  // Recreate the producer queue from the parcel.
-  ProducerQueueParcelable input_parcelable;
-  EXPECT_FALSE(input_parcelable.IsValid());
-
-  res = input_parcelable.readFromParcel(&parcel);
-  EXPECT_EQ(res, OK);
-  EXPECT_TRUE(input_parcelable.IsValid());
-
-  EXPECT_EQ(producer_queue_, nullptr);
-  producer_queue_ = ProducerQueue::Import(input_parcelable.TakeChannelHandle());
-  EXPECT_FALSE(input_parcelable.IsValid());
-  ASSERT_NE(producer_queue_, nullptr);
-
-  // Newly created queue from the parcel can allocate buffer, post buffer to
-  // consumer.
-  EXPECT_NO_FATAL_FAILURE(AllocateBuffer());
-  EXPECT_EQ(producer_queue_->count(), 1U);
-  EXPECT_EQ(producer_queue_->capacity(), 1U);
-
-  size_t slot;
-  DvrNativeBufferMetadata producer_meta;
-  DvrNativeBufferMetadata consumer_meta;
-  LocalHandle fence;
-  auto s3 = producer_queue_->Dequeue(0, &slot, &producer_meta, &fence);
-  EXPECT_TRUE(s3.ok());
-
-  std::shared_ptr<ProducerBuffer> p1 = s3.take();
-  ASSERT_NE(p1, nullptr);
-
-  producer_meta.timestamp = 42;
-  EXPECT_EQ(p1->PostAsync(&producer_meta, LocalHandle()), 0);
-
-  // Make sure the buffer can be dequeued from consumer side.
-  auto s4 = consumer_queue_->Dequeue(kTimeoutMs, &slot, &consumer_meta, &fence);
-  EXPECT_TRUE(s4.ok()) << s4.GetErrorMessage();
-  EXPECT_EQ(consumer_queue_->capacity(), 1U);
-
-  auto consumer = s4.take();
-  ASSERT_NE(consumer, nullptr);
-  EXPECT_EQ(producer_meta.timestamp, consumer_meta.timestamp);
-}
-
-TEST_F(BufferHubQueueTest, TestCreateConsumerParcelable) {
-  ASSERT_TRUE(CreateProducerQueue(config_builder_.Build(), UsagePolicy{}));
-
-  auto s1 = producer_queue_->CreateConsumerQueueParcelable();
-  EXPECT_TRUE(s1.ok());
-  ConsumerQueueParcelable output_parcelable = s1.take();
-  EXPECT_TRUE(output_parcelable.IsValid());
-
-  // Write to a Parcel new object.
-  Parcel parcel;
-  status_t res;
-  res = output_parcelable.writeToParcel(&parcel);
-
-  // Reset the data position so that we can read back from the same parcel
-  // without doing actually Binder IPC.
-  parcel.setDataPosition(0);
-
-  // No consumer queue created yet.
-  EXPECT_EQ(consumer_queue_, nullptr);
-
-  // If the parcel contains a consumer queue, read into a
-  // ProducerQueueParcelable should fail.
-  ProducerQueueParcelable wrongly_typed_parcelable;
-  EXPECT_FALSE(wrongly_typed_parcelable.IsValid());
-  res = wrongly_typed_parcelable.readFromParcel(&parcel);
-  EXPECT_EQ(res, -EINVAL);
-  parcel.setDataPosition(0);
-
-  // Create the consumer queue from the parcel.
-  ConsumerQueueParcelable input_parcelable;
-  EXPECT_FALSE(input_parcelable.IsValid());
-
-  res = input_parcelable.readFromParcel(&parcel);
-  EXPECT_EQ(res, OK);
-  EXPECT_TRUE(input_parcelable.IsValid());
-
-  consumer_queue_ = ConsumerQueue::Import(input_parcelable.TakeChannelHandle());
-  EXPECT_FALSE(input_parcelable.IsValid());
-  ASSERT_NE(consumer_queue_, nullptr);
-
-  EXPECT_NO_FATAL_FAILURE(AllocateBuffer());
-  EXPECT_EQ(producer_queue_->count(), 1U);
-  EXPECT_EQ(producer_queue_->capacity(), 1U);
-
-  size_t slot;
-  DvrNativeBufferMetadata producer_meta;
-  DvrNativeBufferMetadata consumer_meta;
-  LocalHandle fence;
-  auto s2 = producer_queue_->Dequeue(0, &slot, &producer_meta, &fence);
-  EXPECT_TRUE(s2.ok());
-
-  std::shared_ptr<ProducerBuffer> p1 = s2.take();
-  ASSERT_NE(p1, nullptr);
-
-  producer_meta.timestamp = 42;
-  EXPECT_EQ(p1->PostAsync(&producer_meta, LocalHandle()), 0);
-
-  // Make sure the buffer can be dequeued from consumer side.
-  auto s3 = consumer_queue_->Dequeue(kTimeoutMs, &slot, &consumer_meta, &fence);
-  EXPECT_TRUE(s3.ok()) << s3.GetErrorMessage();
-  EXPECT_EQ(consumer_queue_->capacity(), 1U);
-
-  auto consumer = s3.take();
-  ASSERT_NE(consumer, nullptr);
-  EXPECT_EQ(producer_meta.timestamp, consumer_meta.timestamp);
-}
-
-}  // namespace
-
-}  // namespace dvr
-}  // namespace android
diff --git a/libs/vr/libbufferhubqueue/tests/buffer_hub_queue_producer-test.cpp b/libs/vr/libbufferhubqueue/tests/buffer_hub_queue_producer-test.cpp
deleted file mode 100644
index fab1097..0000000
--- a/libs/vr/libbufferhubqueue/tests/buffer_hub_queue_producer-test.cpp
+++ /dev/null
@@ -1,603 +0,0 @@
-#include <base/logging.h>
-#include <gui/BufferHubProducer.h>
-#include <gui/IProducerListener.h>
-#include <gui/Surface.h>
-#include <pdx/default_transport/channel_parcelable.h>
-
-#include <gtest/gtest.h>
-
-namespace android {
-namespace dvr {
-
-using pdx::LocalHandle;
-
-namespace {
-
-// Default dimensions before setDefaultBufferSize is called by the consumer.
-constexpr uint32_t kDefaultWidth = 1;
-constexpr uint32_t kDefaultHeight = 1;
-
-// Default format before setDefaultBufferFormat is called by the consumer.
-constexpr PixelFormat kDefaultFormat = HAL_PIXEL_FORMAT_RGBA_8888;
-constexpr int kDefaultConsumerUsageBits = 0;
-
-// Default transform hint before setTransformHint is called by the consumer.
-constexpr uint32_t kDefaultTransformHint = 0;
-
-constexpr int kTestApi = NATIVE_WINDOW_API_CPU;
-constexpr int kTestApiOther = NATIVE_WINDOW_API_EGL;
-constexpr int kTestApiInvalid = 0xDEADBEEF;
-constexpr int kTestProducerUsageBits = 0;
-constexpr bool kTestControlledByApp = true;
-
-// Builder pattern to slightly vary *almost* correct input
-// -- avoids copying and pasting
-struct QueueBufferInputBuilder {
-  IGraphicBufferProducer::QueueBufferInput build() {
-    return IGraphicBufferProducer::QueueBufferInput(
-        mTimestamp, mIsAutoTimestamp, mDataSpace, mCrop, mScalingMode,
-        mTransform, mFence);
-  }
-
-  QueueBufferInputBuilder& setTimestamp(int64_t timestamp) {
-    this->mTimestamp = timestamp;
-    return *this;
-  }
-
-  QueueBufferInputBuilder& setIsAutoTimestamp(bool isAutoTimestamp) {
-    this->mIsAutoTimestamp = isAutoTimestamp;
-    return *this;
-  }
-
-  QueueBufferInputBuilder& setDataSpace(android_dataspace dataSpace) {
-    this->mDataSpace = dataSpace;
-    return *this;
-  }
-
-  QueueBufferInputBuilder& setCrop(Rect crop) {
-    this->mCrop = crop;
-    return *this;
-  }
-
-  QueueBufferInputBuilder& setScalingMode(int scalingMode) {
-    this->mScalingMode = scalingMode;
-    return *this;
-  }
-
-  QueueBufferInputBuilder& setTransform(uint32_t transform) {
-    this->mTransform = transform;
-    return *this;
-  }
-
-  QueueBufferInputBuilder& setFence(sp<Fence> fence) {
-    this->mFence = fence;
-    return *this;
-  }
-
- private:
-  int64_t mTimestamp{1384888611};
-  bool mIsAutoTimestamp{false};
-  android_dataspace mDataSpace{HAL_DATASPACE_UNKNOWN};
-  Rect mCrop{Rect(kDefaultWidth, kDefaultHeight)};
-  int mScalingMode{0};
-  uint32_t mTransform{0};
-  sp<Fence> mFence{Fence::NO_FENCE};
-};
-
-// This is a test that covers our implementation of bufferhubqueue-based
-// IGraphicBufferProducer.
-class BufferHubQueueProducerTest : public ::testing::Test {
- protected:
-  virtual void SetUp() {
-    const ::testing::TestInfo* const testInfo =
-        ::testing::UnitTest::GetInstance()->current_test_info();
-    ALOGD_IF(TRACE, "Begin test: %s.%s", testInfo->test_case_name(),
-             testInfo->name());
-
-    auto config = ProducerQueueConfigBuilder().Build();
-    auto queue = ProducerQueue::Create(config, UsagePolicy{});
-    ASSERT_TRUE(queue != nullptr);
-
-    mProducer = BufferHubProducer::Create(std::move(queue));
-    ASSERT_TRUE(mProducer != nullptr);
-    mSurface = new Surface(mProducer, true);
-    ASSERT_TRUE(mSurface != nullptr);
-  }
-
-  // Connect to a producer in a 'correct' fashion.
-  void ConnectProducer() {
-    IGraphicBufferProducer::QueueBufferOutput output;
-    // Can connect the first time.
-    ASSERT_EQ(OK, mProducer->connect(kStubListener, kTestApi,
-                                     kTestControlledByApp, &output));
-  }
-
-  // Dequeue a buffer in a 'correct' fashion.
-  //   Precondition: Producer is connected.
-  void DequeueBuffer(int* outSlot) {
-    sp<Fence> fence;
-    ASSERT_NO_FATAL_FAILURE(DequeueBuffer(outSlot, &fence));
-  }
-
-  void DequeueBuffer(int* outSlot, sp<Fence>* outFence) {
-    ASSERT_NE(nullptr, outSlot);
-    ASSERT_NE(nullptr, outFence);
-
-    int ret = mProducer->dequeueBuffer(
-        outSlot, outFence, kDefaultWidth, kDefaultHeight, kDefaultFormat,
-        kTestProducerUsageBits, nullptr, nullptr);
-    // BUFFER_NEEDS_REALLOCATION can be either on or off.
-    ASSERT_EQ(0, ~IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION & ret);
-
-    // Slot number should be in boundary.
-    ASSERT_LE(0, *outSlot);
-    ASSERT_GT(BufferQueueDefs::NUM_BUFFER_SLOTS, *outSlot);
-  }
-
-  // Create a generic "valid" input for queueBuffer
-  // -- uses the default buffer format, width, etc.
-  static IGraphicBufferProducer::QueueBufferInput CreateBufferInput() {
-    return QueueBufferInputBuilder().build();
-  }
-
-  const sp<IProducerListener> kStubListener{new StubProducerListener};
-
-  sp<BufferHubProducer> mProducer;
-  sp<Surface> mSurface;
-};
-
-TEST_F(BufferHubQueueProducerTest, ConnectFirst_ReturnsError) {
-  IGraphicBufferProducer::QueueBufferOutput output;
-
-  // NULL output returns BAD_VALUE
-  EXPECT_EQ(BAD_VALUE, mProducer->connect(kStubListener, kTestApi,
-                                          kTestControlledByApp, nullptr));
-
-  // Invalid API returns bad value
-  EXPECT_EQ(BAD_VALUE, mProducer->connect(kStubListener, kTestApiInvalid,
-                                          kTestControlledByApp, &output));
-}
-
-TEST_F(BufferHubQueueProducerTest, ConnectAgain_ReturnsError) {
-  ASSERT_NO_FATAL_FAILURE(ConnectProducer());
-
-  // Can't connect when there is already a producer connected.
-  IGraphicBufferProducer::QueueBufferOutput output;
-  EXPECT_EQ(BAD_VALUE, mProducer->connect(kStubListener, kTestApi,
-                                          kTestControlledByApp, &output));
-}
-
-TEST_F(BufferHubQueueProducerTest, Disconnect_Succeeds) {
-  ASSERT_NO_FATAL_FAILURE(ConnectProducer());
-
-  ASSERT_EQ(OK, mProducer->disconnect(kTestApi));
-}
-
-TEST_F(BufferHubQueueProducerTest, Disconnect_ReturnsError) {
-  ASSERT_NO_FATAL_FAILURE(ConnectProducer());
-
-  // Must disconnect with same API number
-  EXPECT_EQ(BAD_VALUE, mProducer->disconnect(kTestApiOther));
-  // API must not be out of range
-  EXPECT_EQ(BAD_VALUE, mProducer->disconnect(kTestApiInvalid));
-}
-
-TEST_F(BufferHubQueueProducerTest, Query_Succeeds) {
-  ASSERT_NO_FATAL_FAILURE(ConnectProducer());
-
-  int32_t value = -1;
-  EXPECT_EQ(OK, mProducer->query(NATIVE_WINDOW_WIDTH, &value));
-  EXPECT_EQ(kDefaultWidth, static_cast<uint32_t>(value));
-
-  EXPECT_EQ(OK, mProducer->query(NATIVE_WINDOW_HEIGHT, &value));
-  EXPECT_EQ(kDefaultHeight, static_cast<uint32_t>(value));
-
-  EXPECT_EQ(OK, mProducer->query(NATIVE_WINDOW_FORMAT, &value));
-  EXPECT_EQ(kDefaultFormat, value);
-
-  EXPECT_EQ(OK, mProducer->query(NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS, &value));
-  EXPECT_LE(0, value);
-  EXPECT_GE(BufferQueueDefs::NUM_BUFFER_SLOTS, value);
-
-  EXPECT_EQ(OK,
-            mProducer->query(NATIVE_WINDOW_CONSUMER_RUNNING_BEHIND, &value));
-  EXPECT_FALSE(value);  // Can't run behind when we haven't touched the queue
-
-  EXPECT_EQ(OK, mProducer->query(NATIVE_WINDOW_CONSUMER_USAGE_BITS, &value));
-  EXPECT_EQ(kDefaultConsumerUsageBits, value);
-}
-
-TEST_F(BufferHubQueueProducerTest, Query_ReturnsError) {
-  ASSERT_NO_FATAL_FAILURE(ConnectProducer());
-
-  // One past the end of the last 'query' enum value. Update this if we add more
-  // enums.
-  const int NATIVE_WINDOW_QUERY_LAST_OFF_BY_ONE = NATIVE_WINDOW_BUFFER_AGE + 1;
-
-  int value;
-  // What was out of range
-  EXPECT_EQ(BAD_VALUE, mProducer->query(/*what*/ -1, &value));
-  EXPECT_EQ(BAD_VALUE, mProducer->query(/*what*/ 0xDEADBEEF, &value));
-  EXPECT_EQ(BAD_VALUE,
-            mProducer->query(NATIVE_WINDOW_QUERY_LAST_OFF_BY_ONE, &value));
-
-  // Some enums from window.h are 'invalid'
-  EXPECT_EQ(BAD_VALUE,
-            mProducer->query(NATIVE_WINDOW_QUEUES_TO_WINDOW_COMPOSER, &value));
-  EXPECT_EQ(BAD_VALUE, mProducer->query(NATIVE_WINDOW_CONCRETE_TYPE, &value));
-  EXPECT_EQ(BAD_VALUE, mProducer->query(NATIVE_WINDOW_DEFAULT_WIDTH, &value));
-  EXPECT_EQ(BAD_VALUE, mProducer->query(NATIVE_WINDOW_DEFAULT_HEIGHT, &value));
-  EXPECT_EQ(BAD_VALUE, mProducer->query(NATIVE_WINDOW_TRANSFORM_HINT, &value));
-
-  // Value was NULL
-  EXPECT_EQ(BAD_VALUE, mProducer->query(NATIVE_WINDOW_FORMAT, /*value*/ NULL));
-}
-
-TEST_F(BufferHubQueueProducerTest, Queue_Succeeds) {
-  int slot = -1;
-
-  ASSERT_NO_FATAL_FAILURE(ConnectProducer());
-  ASSERT_NO_FATAL_FAILURE(DequeueBuffer(&slot));
-
-  // Request the buffer (pre-requisite for queueing)
-  sp<GraphicBuffer> buffer;
-  ASSERT_EQ(OK, mProducer->requestBuffer(slot, &buffer));
-
-  // A generic "valid" input
-  IGraphicBufferProducer::QueueBufferInput input = CreateBufferInput();
-  IGraphicBufferProducer::QueueBufferOutput output;
-
-  // Queue the buffer back into the BQ
-  ASSERT_EQ(OK, mProducer->queueBuffer(slot, input, &output));
-
-  EXPECT_EQ(kDefaultWidth, output.width);
-  EXPECT_EQ(kDefaultHeight, output.height);
-  EXPECT_EQ(kDefaultTransformHint, output.transformHint);
-
-  // BufferHubQueue delivers buffers to consumer immediately.
-  EXPECT_EQ(0u, output.numPendingBuffers);
-
-  // Note that BufferHubQueue doesn't support nextFrameNumber as it seems to
-  // be a SurfaceFlinger specific optimization.
-  EXPECT_EQ(0u, output.nextFrameNumber);
-
-  // Buffer was not in the dequeued state
-  EXPECT_EQ(BAD_VALUE, mProducer->queueBuffer(slot, input, &output));
-}
-
-// Test invalid slot number
-TEST_F(BufferHubQueueProducerTest, QueueInvalidSlot_ReturnsError) {
-  ASSERT_NO_FATAL_FAILURE(ConnectProducer());
-
-  // A generic "valid" input
-  IGraphicBufferProducer::QueueBufferInput input = CreateBufferInput();
-  IGraphicBufferProducer::QueueBufferOutput output;
-
-  EXPECT_EQ(BAD_VALUE, mProducer->queueBuffer(/*slot*/ -1, input, &output));
-  EXPECT_EQ(BAD_VALUE,
-            mProducer->queueBuffer(/*slot*/ 0xDEADBEEF, input, &output));
-  EXPECT_EQ(BAD_VALUE, mProducer->queueBuffer(BufferQueueDefs::NUM_BUFFER_SLOTS,
-                                              input, &output));
-}
-
-// Slot was not in the dequeued state (all slots start out in Free state)
-TEST_F(BufferHubQueueProducerTest, QueueNotDequeued_ReturnsError) {
-  ASSERT_NO_FATAL_FAILURE(ConnectProducer());
-
-  IGraphicBufferProducer::QueueBufferInput input = CreateBufferInput();
-  IGraphicBufferProducer::QueueBufferOutput output;
-
-  EXPECT_EQ(BAD_VALUE, mProducer->queueBuffer(/*slot*/ 0, input, &output));
-}
-
-// Slot was enqueued without requesting a buffer
-TEST_F(BufferHubQueueProducerTest, QueueNotRequested_ReturnsError) {
-  int slot = -1;
-
-  ASSERT_NO_FATAL_FAILURE(ConnectProducer());
-  ASSERT_NO_FATAL_FAILURE(DequeueBuffer(&slot));
-
-  IGraphicBufferProducer::QueueBufferInput input = CreateBufferInput();
-  IGraphicBufferProducer::QueueBufferOutput output;
-
-  EXPECT_EQ(BAD_VALUE, mProducer->queueBuffer(slot, input, &output));
-}
-
-// Test when fence was NULL
-TEST_F(BufferHubQueueProducerTest, QueueNoFence_ReturnsError) {
-  int slot = -1;
-
-  ASSERT_NO_FATAL_FAILURE(ConnectProducer());
-  ASSERT_NO_FATAL_FAILURE(DequeueBuffer(&slot));
-
-  sp<GraphicBuffer> buffer;
-  ASSERT_EQ(OK, mProducer->requestBuffer(slot, &buffer));
-
-  sp<Fence> nullFence = NULL;
-
-  IGraphicBufferProducer::QueueBufferInput input =
-      QueueBufferInputBuilder().setFence(nullFence).build();
-  IGraphicBufferProducer::QueueBufferOutput output;
-
-  EXPECT_EQ(BAD_VALUE, mProducer->queueBuffer(slot, input, &output));
-}
-
-// Test scaling mode was invalid
-TEST_F(BufferHubQueueProducerTest, QueueTestInvalidScalingMode_ReturnsError) {
-  int slot = -1;
-
-  ASSERT_NO_FATAL_FAILURE(ConnectProducer());
-  ASSERT_NO_FATAL_FAILURE(DequeueBuffer(&slot));
-
-  sp<GraphicBuffer> buffer;
-  ASSERT_EQ(OK, mProducer->requestBuffer(slot, &buffer));
-
-  IGraphicBufferProducer::QueueBufferInput input =
-      QueueBufferInputBuilder().setScalingMode(-1).build();
-  IGraphicBufferProducer::QueueBufferOutput output;
-
-  EXPECT_EQ(BAD_VALUE, mProducer->queueBuffer(slot, input, &output));
-
-  input = QueueBufferInputBuilder().setScalingMode(0xDEADBEEF).build();
-
-  EXPECT_EQ(BAD_VALUE, mProducer->queueBuffer(slot, input, &output));
-}
-
-// Test crop rect is out of bounds of the buffer dimensions
-TEST_F(BufferHubQueueProducerTest, QueueCropOutOfBounds_ReturnsError) {
-  int slot = -1;
-
-  ASSERT_NO_FATAL_FAILURE(ConnectProducer());
-  ASSERT_NO_FATAL_FAILURE(DequeueBuffer(&slot));
-
-  sp<GraphicBuffer> buffer;
-  ASSERT_EQ(OK, mProducer->requestBuffer(slot, &buffer));
-
-  IGraphicBufferProducer::QueueBufferInput input =
-      QueueBufferInputBuilder()
-          .setCrop(Rect(kDefaultWidth + 1, kDefaultHeight + 1))
-          .build();
-  IGraphicBufferProducer::QueueBufferOutput output;
-
-  EXPECT_EQ(BAD_VALUE, mProducer->queueBuffer(slot, input, &output));
-}
-
-TEST_F(BufferHubQueueProducerTest, CancelBuffer_Succeeds) {
-  int slot = -1;
-  sp<Fence> fence;
-
-  ASSERT_NO_FATAL_FAILURE(ConnectProducer());
-  ASSERT_NO_FATAL_FAILURE(DequeueBuffer(&slot, &fence));
-
-  // Should be able to cancel buffer after a dequeue.
-  EXPECT_EQ(OK, mProducer->cancelBuffer(slot, fence));
-}
-
-TEST_F(BufferHubQueueProducerTest, SetMaxDequeuedBufferCount_Succeeds) {
-  return;
-  ASSERT_NO_FATAL_FAILURE(ConnectProducer());
-
-  int minUndequeuedBuffers;
-  ASSERT_EQ(OK, mProducer->query(NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS,
-                                 &minUndequeuedBuffers));
-
-  const int minBuffers = 1;
-  const int maxBuffers =
-      BufferQueueDefs::NUM_BUFFER_SLOTS - minUndequeuedBuffers;
-
-  ASSERT_EQ(OK, mProducer->setAsyncMode(false)) << "async mode: " << false;
-  ASSERT_EQ(OK, mProducer->setMaxDequeuedBufferCount(minBuffers))
-      << "bufferCount: " << minBuffers;
-
-  // Should now be able to dequeue up to minBuffers times
-  // Should now be able to dequeue up to maxBuffers times
-  int slot = -1;
-  for (int i = 0; i < minBuffers; ++i) {
-    ASSERT_NO_FATAL_FAILURE(DequeueBuffer(&slot));
-  }
-
-  ASSERT_EQ(OK, mProducer->setMaxDequeuedBufferCount(maxBuffers));
-
-  // queue the first buffer to enable max dequeued buffer count checking
-  IGraphicBufferProducer::QueueBufferInput input = CreateBufferInput();
-  IGraphicBufferProducer::QueueBufferOutput output;
-  sp<GraphicBuffer> buffer;
-  ASSERT_EQ(OK, mProducer->requestBuffer(slot, &buffer));
-  ASSERT_EQ(OK, mProducer->queueBuffer(slot, input, &output));
-
-  sp<Fence> fence;
-  for (int i = 0; i < maxBuffers; ++i) {
-    ASSERT_NO_FATAL_FAILURE(DequeueBuffer(&slot, &fence));
-  }
-
-  // Cancel a buffer, so we can decrease the buffer count
-  ASSERT_EQ(OK, mProducer->cancelBuffer(slot, fence));
-
-  // Should now be able to decrease the max dequeued count by 1
-  ASSERT_EQ(OK, mProducer->setMaxDequeuedBufferCount(maxBuffers - 1));
-}
-
-TEST_F(BufferHubQueueProducerTest, SetMaxDequeuedBufferCount_Fails) {
-  ASSERT_NO_FATAL_FAILURE(ConnectProducer());
-
-  int minUndequeuedBuffers;
-  ASSERT_EQ(OK, mProducer->query(NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS,
-                                 &minUndequeuedBuffers));
-
-  const int minBuffers = 1;
-  const int maxBuffers =
-      BufferQueueDefs::NUM_BUFFER_SLOTS - minUndequeuedBuffers;
-
-  ASSERT_EQ(OK, mProducer->setAsyncMode(false)) << "async mode: " << false;
-  // Buffer count was out of range
-  EXPECT_EQ(BAD_VALUE, mProducer->setMaxDequeuedBufferCount(0))
-      << "bufferCount: " << 0;
-  EXPECT_EQ(BAD_VALUE, mProducer->setMaxDequeuedBufferCount(maxBuffers + 1))
-      << "bufferCount: " << maxBuffers + 1;
-
-  // Set max dequeue count to 2
-  ASSERT_EQ(OK, mProducer->setMaxDequeuedBufferCount(2));
-  // Dequeue 2 buffers
-  int slot = -1;
-  sp<Fence> fence;
-  for (int i = 0; i < 2; i++) {
-    ASSERT_EQ(OK, ~IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION &
-                      (mProducer->dequeueBuffer(&slot, &fence, kDefaultWidth,
-                                                kDefaultHeight, kDefaultFormat,
-                                                kTestProducerUsageBits,
-                                                nullptr, nullptr)))
-        << "slot: " << slot;
-  }
-
-  // Client has too many buffers dequeued
-  EXPECT_EQ(BAD_VALUE, mProducer->setMaxDequeuedBufferCount(1))
-      << "bufferCount: " << minBuffers;
-}
-
-TEST_F(BufferHubQueueProducerTest,
-       DisconnectedProducerReturnsError_dequeueBuffer) {
-  int slot = -1;
-  sp<Fence> fence;
-
-  ASSERT_EQ(NO_INIT, mProducer->dequeueBuffer(&slot, &fence, kDefaultWidth,
-                                              kDefaultHeight, kDefaultFormat,
-                                              kTestProducerUsageBits,
-                                              nullptr, nullptr));
-}
-
-TEST_F(BufferHubQueueProducerTest,
-       DisconnectedProducerReturnsError_requestBuffer) {
-  int slot = -1;
-  sp<GraphicBuffer> buffer;
-
-  ASSERT_NO_FATAL_FAILURE(ConnectProducer());
-  ASSERT_NO_FATAL_FAILURE(DequeueBuffer(&slot));
-
-  // Shouldn't be able to request buffer after disconnect.
-  ASSERT_EQ(OK, mProducer->disconnect(kTestApi));
-  ASSERT_EQ(NO_INIT, mProducer->requestBuffer(slot, &buffer));
-}
-
-TEST_F(BufferHubQueueProducerTest,
-       DisconnectedProducerReturnsError_queueBuffer) {
-  int slot = -1;
-  sp<GraphicBuffer> buffer;
-
-  ASSERT_NO_FATAL_FAILURE(ConnectProducer());
-  ASSERT_NO_FATAL_FAILURE(DequeueBuffer(&slot));
-  ASSERT_EQ(OK, mProducer->requestBuffer(slot, &buffer));
-
-  // A generic "valid" input
-  IGraphicBufferProducer::QueueBufferInput input = CreateBufferInput();
-  IGraphicBufferProducer::QueueBufferOutput output;
-
-  // Shouldn't be able to queue buffer after disconnect.
-  ASSERT_EQ(OK, mProducer->disconnect(kTestApi));
-  ASSERT_EQ(NO_INIT, mProducer->queueBuffer(slot, input, &output));
-}
-
-TEST_F(BufferHubQueueProducerTest,
-       DisconnectedProducerReturnsError_cancelBuffer) {
-  int slot = -1;
-  sp<GraphicBuffer> buffer;
-
-  ASSERT_NO_FATAL_FAILURE(ConnectProducer());
-  ASSERT_NO_FATAL_FAILURE(DequeueBuffer(&slot));
-  ASSERT_EQ(OK, mProducer->requestBuffer(slot, &buffer));
-
-  // Shouldn't be able to cancel buffer after disconnect.
-  ASSERT_EQ(OK, mProducer->disconnect(kTestApi));
-  ASSERT_EQ(NO_INIT, mProducer->cancelBuffer(slot, Fence::NO_FENCE));
-}
-
-TEST_F(BufferHubQueueProducerTest, ConnectDisconnectReconnect) {
-  int slot = -1;
-  sp<GraphicBuffer> buffer;
-  IGraphicBufferProducer::QueueBufferInput input = CreateBufferInput();
-  IGraphicBufferProducer::QueueBufferOutput output;
-
-  EXPECT_NO_FATAL_FAILURE(ConnectProducer());
-
-  constexpr int maxDequeuedBuffers = 1;
-  int minUndequeuedBuffers;
-  EXPECT_EQ(OK, mProducer->query(NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS,
-                                 &minUndequeuedBuffers));
-  EXPECT_EQ(OK, mProducer->setAsyncMode(false));
-  EXPECT_EQ(OK, mProducer->setMaxDequeuedBufferCount(maxDequeuedBuffers));
-
-  int maxCapacity = maxDequeuedBuffers + minUndequeuedBuffers;
-
-  // Dequeue, request, and queue all buffers.
-  for (int i = 0; i < maxCapacity; i++) {
-    EXPECT_NO_FATAL_FAILURE(DequeueBuffer(&slot));
-    EXPECT_EQ(OK, mProducer->requestBuffer(slot, &buffer));
-    EXPECT_EQ(OK, mProducer->queueBuffer(slot, input, &output));
-  }
-
-  // Disconnect then reconnect.
-  EXPECT_EQ(OK, mProducer->disconnect(kTestApi));
-  EXPECT_NO_FATAL_FAILURE(ConnectProducer());
-
-  // Dequeue, request, and queue all buffers.
-  for (int i = 0; i < maxCapacity; i++) {
-    EXPECT_NO_FATAL_FAILURE(DequeueBuffer(&slot));
-    EXPECT_EQ(OK, mProducer->requestBuffer(slot, &buffer));
-    EXPECT_EQ(OK, mProducer->queueBuffer(slot, input, &output));
-  }
-
-  EXPECT_EQ(OK, mProducer->disconnect(kTestApi));
-}
-
-TEST_F(BufferHubQueueProducerTest, TakeAsParcelable) {
-  // Connected producer cannot be taken out as a parcelable.
-  EXPECT_NO_FATAL_FAILURE(ConnectProducer());
-  ProducerQueueParcelable producer_parcelable;
-  EXPECT_EQ(mProducer->TakeAsParcelable(&producer_parcelable), BAD_VALUE);
-
-  // Create a valid fake producer parcelable.
-  auto fake_channel_parcelable =
-      std::make_unique<pdx::default_transport::ChannelParcelable>(
-          LocalHandle(0), LocalHandle(0), LocalHandle(0));
-  EXPECT_TRUE(fake_channel_parcelable->IsValid());
-  ProducerQueueParcelable fake_producer_parcelable(
-      std::move(fake_channel_parcelable));
-  EXPECT_TRUE(fake_producer_parcelable.IsValid());
-
-  // Disconnect producer can be taken out, but only to an invalid parcelable.
-  ASSERT_EQ(mProducer->disconnect(kTestApi), OK);
-  EXPECT_EQ(mProducer->TakeAsParcelable(&fake_producer_parcelable), BAD_VALUE);
-  EXPECT_FALSE(producer_parcelable.IsValid());
-  EXPECT_EQ(mProducer->TakeAsParcelable(&producer_parcelable), OK);
-  EXPECT_TRUE(producer_parcelable.IsValid());
-
-  // Should still be able to query buffer dimension after disconnect.
-  int32_t value = -1;
-  EXPECT_EQ(OK, mProducer->query(NATIVE_WINDOW_WIDTH, &value));
-  EXPECT_EQ(static_cast<uint32_t>(value), kDefaultWidth);
-
-  EXPECT_EQ(mProducer->query(NATIVE_WINDOW_HEIGHT, &value), OK);
-  EXPECT_EQ(static_cast<uint32_t>(value), kDefaultHeight);
-
-  EXPECT_EQ(mProducer->query(NATIVE_WINDOW_FORMAT, &value), OK);
-  EXPECT_EQ(value, kDefaultFormat);
-
-  // But connect to API will fail.
-  IGraphicBufferProducer::QueueBufferOutput output;
-  EXPECT_EQ(mProducer->connect(kStubListener, kTestApi, kTestControlledByApp,
-                               &output),
-            BAD_VALUE);
-
-  // Create a new producer from the parcelable and connect to kTestApi should
-  // succeed.
-  sp<BufferHubProducer> new_producer =
-      BufferHubProducer::Create(std::move(producer_parcelable));
-  ASSERT_TRUE(new_producer != nullptr);
-  EXPECT_EQ(new_producer->connect(kStubListener, kTestApi, kTestControlledByApp,
-                                  &output),
-            OK);
-}
-
-}  // namespace
-
-}  // namespace dvr
-}  // namespace android
diff --git a/libs/vr/libdisplay/Android.bp b/libs/vr/libdisplay/Android.bp
deleted file mode 100644
index b0ed950..0000000
--- a/libs/vr/libdisplay/Android.bp
+++ /dev/null
@@ -1,78 +0,0 @@
-// Copyright (C) 2015 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"],
-}
-
-sourceFiles = [
-    "display_client.cpp",
-    "display_manager_client.cpp",
-    "display_protocol.cpp",
-    "shared_buffer_helpers.cpp",
-    "vsync_service.cpp",
-]
-
-localIncludeFiles = [
-    "include",
-]
-
-sharedLibraries = [
-    "libbase",
-    "libbinder",
-    "libbufferhubqueue",
-    "libcutils",
-    "liblog",
-    "libutils",
-    "libui",
-    "libgui",
-    "libhardware",
-    "libsync",
-    "libnativewindow",
-    "libpdx_default_transport",
-]
-
-staticLibraries = [
-    "libdvrcommon",
-    "libbroadcastring",
-]
-
-headerLibraries = [
-    "vulkan_headers",
-    "libdvr_headers",
-]
-
-cc_library {
-    srcs: sourceFiles,
-    cflags: ["-DLOG_TAG=\"libdisplay\"",
-        "-DTRACE=0",
-        "-DATRACE_TAG=ATRACE_TAG_GRAPHICS",
-        "-DGL_GLEXT_PROTOTYPES",
-        "-DEGL_EGLEXT_PROTOTYPES",
-        "-Wall",
-        "-Werror",
-    ],  // + [ "-UNDEBUG", "-DDEBUG", "-O0", "-g" ],
-    export_include_dirs: localIncludeFiles,
-    shared_libs: sharedLibraries,
-    static_libs: staticLibraries,
-    header_libs: headerLibraries,
-    export_header_lib_headers: headerLibraries,
-
-    name: "libdisplay",
-}
diff --git a/libs/vr/libdisplay/display_client.cpp b/libs/vr/libdisplay/display_client.cpp
deleted file mode 100644
index 62856df..0000000
--- a/libs/vr/libdisplay/display_client.cpp
+++ /dev/null
@@ -1,261 +0,0 @@
-#include "include/private/dvr/display_client.h"
-
-#include <cutils/native_handle.h>
-#include <log/log.h>
-#include <pdx/default_transport/client_channel.h>
-#include <pdx/default_transport/client_channel_factory.h>
-#include <pdx/status.h>
-
-#include <mutex>
-
-#include <private/dvr/display_protocol.h>
-
-using android::pdx::ErrorStatus;
-using android::pdx::LocalHandle;
-using android::pdx::LocalChannelHandle;
-using android::pdx::Status;
-using android::pdx::Transaction;
-using android::pdx::rpc::IfAnyOf;
-
-namespace android {
-namespace dvr {
-namespace display {
-
-Surface::Surface(LocalChannelHandle channel_handle, int* error)
-    : BASE{pdx::default_transport::ClientChannel::Create(
-          std::move(channel_handle))} {
-  auto status = InvokeRemoteMethod<DisplayProtocol::GetSurfaceInfo>();
-  if (!status) {
-    ALOGE("Surface::Surface: Failed to get surface info: %s",
-          status.GetErrorMessage().c_str());
-    Close(status.error());
-    if (error)
-      *error = status.error();
-  }
-
-  surface_id_ = status.get().surface_id;
-  z_order_ = status.get().z_order;
-  visible_ = status.get().visible;
-}
-
-Surface::Surface(const SurfaceAttributes& attributes, int* error)
-    : BASE{pdx::default_transport::ClientChannelFactory::Create(
-               DisplayProtocol::kClientPath),
-           kInfiniteTimeout} {
-  auto status = InvokeRemoteMethod<DisplayProtocol::CreateSurface>(attributes);
-  if (!status) {
-    ALOGE("Surface::Surface: Failed to create display surface: %s",
-          status.GetErrorMessage().c_str());
-    Close(status.error());
-    if (error)
-      *error = status.error();
-  }
-
-  surface_id_ = status.get().surface_id;
-  z_order_ = status.get().z_order;
-  visible_ = status.get().visible;
-}
-
-Status<void> Surface::SetVisible(bool visible) {
-  return SetAttributes(
-      {{SurfaceAttribute::Visible, SurfaceAttributeValue{visible}}});
-}
-
-Status<void> Surface::SetZOrder(int z_order) {
-  return SetAttributes(
-      {{SurfaceAttribute::ZOrder, SurfaceAttributeValue{z_order}}});
-}
-
-Status<void> Surface::SetAttributes(const SurfaceAttributes& attributes) {
-  auto status = InvokeRemoteMethod<DisplayProtocol::SetAttributes>(attributes);
-  if (!status) {
-    ALOGE(
-        "Surface::SetAttributes: Failed to set display surface "
-        "attributes: %s",
-        status.GetErrorMessage().c_str());
-    return status.error_status();
-  }
-
-  // Set the local cached copies of the attributes we care about from the full
-  // set of attributes sent to the display service.
-  for (const auto& attribute : attributes) {
-    const auto& key = attribute.first;
-    const auto* variant = &attribute.second;
-    bool invalid_value = false;
-    switch (key) {
-      case SurfaceAttribute::Visible:
-        invalid_value =
-            !IfAnyOf<int32_t, int64_t, bool>::Get(variant, &visible_);
-        break;
-      case SurfaceAttribute::ZOrder:
-        invalid_value = !IfAnyOf<int32_t>::Get(variant, &z_order_);
-        break;
-    }
-
-    if (invalid_value) {
-      ALOGW(
-          "Surface::SetAttributes: Failed to set display surface "
-          "attribute %d because of incompatible type: %d",
-          key, variant->index());
-    }
-  }
-
-  return {};
-}
-
-Status<std::unique_ptr<ProducerQueue>> Surface::CreateQueue(
-    uint32_t width, uint32_t height, uint32_t format, size_t metadata_size) {
-  ALOGD_IF(TRACE, "Surface::CreateQueue: Creating empty queue.");
-  auto status = InvokeRemoteMethod<DisplayProtocol::CreateQueue>(
-      ProducerQueueConfigBuilder()
-          .SetDefaultWidth(width)
-          .SetDefaultHeight(height)
-          .SetDefaultFormat(format)
-          .SetMetadataSize(metadata_size)
-          .Build());
-  if (!status) {
-    ALOGE("Surface::CreateQueue: Failed to create queue: %s",
-          status.GetErrorMessage().c_str());
-    return status.error_status();
-  }
-
-  auto producer_queue = ProducerQueue::Import(status.take());
-  if (!producer_queue) {
-    ALOGE("Surface::CreateQueue: Failed to import producer queue!");
-    return ErrorStatus(ENOMEM);
-  }
-
-  return {std::move(producer_queue)};
-}
-
-Status<std::unique_ptr<ProducerQueue>> Surface::CreateQueue(
-    uint32_t width, uint32_t height, uint32_t layer_count, uint32_t format,
-    uint64_t usage, size_t capacity, size_t metadata_size) {
-  ALOGD_IF(TRACE,
-           "Surface::CreateQueue: width=%u height=%u layer_count=%u format=%u "
-           "usage=%" PRIx64 " capacity=%zu",
-           width, height, layer_count, format, usage, capacity);
-  auto status = CreateQueue(width, height, format, metadata_size);
-  if (!status)
-    return status.error_status();
-
-  auto producer_queue = status.take();
-
-  ALOGD_IF(TRACE, "Surface::CreateQueue: Allocating %zu buffers...", capacity);
-  auto allocate_status = producer_queue->AllocateBuffers(
-      width, height, layer_count, format, usage, capacity);
-  if (!allocate_status) {
-    ALOGE("Surface::CreateQueue: Failed to allocate buffer on queue_id=%d: %s",
-          producer_queue->id(), allocate_status.GetErrorMessage().c_str());
-    return allocate_status.error_status();
-  }
-
-  return {std::move(producer_queue)};
-}
-
-DisplayClient::DisplayClient(int* error)
-    : BASE(pdx::default_transport::ClientChannelFactory::Create(
-               DisplayProtocol::kClientPath),
-           kInfiniteTimeout) {
-  if (error)
-    *error = Client::error();
-}
-
-Status<Metrics> DisplayClient::GetDisplayMetrics() {
-  return InvokeRemoteMethod<DisplayProtocol::GetMetrics>();
-}
-
-Status<std::string> DisplayClient::GetConfigurationData(
-    ConfigFileType config_type) {
-  auto status =
-      InvokeRemoteMethod<DisplayProtocol::GetConfigurationData>(config_type);
-  if (!status && status.error() != ENOENT) {
-    ALOGE(
-        "DisplayClient::GetConfigurationData: Unable to get"
-        "configuration data. Error: %s",
-        status.GetErrorMessage().c_str());
-  }
-  return status;
-}
-
-Status<uint8_t> DisplayClient::GetDisplayIdentificationPort() {
-  return InvokeRemoteMethod<DisplayProtocol::GetDisplayIdentificationPort>();
-}
-
-Status<std::unique_ptr<Surface>> DisplayClient::CreateSurface(
-    const SurfaceAttributes& attributes) {
-  int error;
-  if (auto client = Surface::Create(attributes, &error))
-    return {std::move(client)};
-  else
-    return ErrorStatus(error);
-}
-
-pdx::Status<std::unique_ptr<IonBuffer>> DisplayClient::SetupGlobalBuffer(
-    DvrGlobalBufferKey key, size_t size, uint64_t usage) {
-  auto status =
-      InvokeRemoteMethod<DisplayProtocol::SetupGlobalBuffer>(key, size, usage);
-  if (!status) {
-    ALOGE(
-        "DisplayClient::SetupGlobalBuffer: Failed to create the global buffer "
-        "%s",
-        status.GetErrorMessage().c_str());
-    return status.error_status();
-  }
-
-  auto ion_buffer = std::make_unique<IonBuffer>();
-  auto native_buffer_handle = status.take();
-  const int ret = native_buffer_handle.Import(ion_buffer.get());
-  if (ret < 0) {
-    ALOGE(
-        "DisplayClient::GetGlobalBuffer: Failed to import global buffer: "
-        "key=%d; error=%s",
-        key, strerror(-ret));
-    return ErrorStatus(-ret);
-  }
-
-  return {std::move(ion_buffer)};
-}
-
-pdx::Status<void> DisplayClient::DeleteGlobalBuffer(DvrGlobalBufferKey key) {
-  auto status = InvokeRemoteMethod<DisplayProtocol::DeleteGlobalBuffer>(key);
-  if (!status) {
-    ALOGE("DisplayClient::DeleteGlobalBuffer Failed: %s",
-          status.GetErrorMessage().c_str());
-  }
-
-  return status;
-}
-
-Status<std::unique_ptr<IonBuffer>> DisplayClient::GetGlobalBuffer(
-    DvrGlobalBufferKey key) {
-  auto status = InvokeRemoteMethod<DisplayProtocol::GetGlobalBuffer>(key);
-  if (!status) {
-    ALOGE(
-        "DisplayClient::GetGlobalBuffer: Failed to get named buffer: key=%d; "
-        "error=%s",
-        key, status.GetErrorMessage().c_str());
-    return status.error_status();
-  }
-
-  auto ion_buffer = std::make_unique<IonBuffer>();
-  auto native_buffer_handle = status.take();
-  const int ret = native_buffer_handle.Import(ion_buffer.get());
-  if (ret < 0) {
-    ALOGE(
-        "DisplayClient::GetGlobalBuffer: Failed to import global buffer: "
-        "key=%d; error=%s",
-        key, strerror(-ret));
-    return ErrorStatus(-ret);
-  }
-
-  return {std::move(ion_buffer)};
-}
-
-Status<bool> DisplayClient::IsVrAppRunning() {
-  return InvokeRemoteMethod<DisplayProtocol::IsVrAppRunning>();
-}
-
-}  // namespace display
-}  // namespace dvr
-}  // namespace android
diff --git a/libs/vr/libdisplay/display_manager_client.cpp b/libs/vr/libdisplay/display_manager_client.cpp
deleted file mode 100644
index fdeeb70..0000000
--- a/libs/vr/libdisplay/display_manager_client.cpp
+++ /dev/null
@@ -1,51 +0,0 @@
-#include "include/private/dvr/display_manager_client.h"
-
-#include <pdx/default_transport/client_channel_factory.h>
-#include <private/dvr/buffer_hub_queue_client.h>
-#include <private/dvr/display_protocol.h>
-#include <utils/Log.h>
-
-using android::pdx::ErrorStatus;
-using android::pdx::LocalChannelHandle;
-using android::pdx::Transaction;
-
-namespace android {
-namespace dvr {
-namespace display {
-
-DisplayManagerClient::DisplayManagerClient()
-    : BASE(pdx::default_transport::ClientChannelFactory::Create(
-          DisplayManagerProtocol::kClientPath)) {}
-
-DisplayManagerClient::~DisplayManagerClient() {}
-
-pdx::Status<std::vector<display::SurfaceState>>
-DisplayManagerClient::GetSurfaceState() {
-  auto status = InvokeRemoteMethod<DisplayManagerProtocol::GetSurfaceState>();
-  if (!status) {
-    ALOGE(
-        "DisplayManagerClient::GetSurfaceState: Failed to get surface info: %s",
-        status.GetErrorMessage().c_str());
-  }
-
-  return status;
-}
-
-pdx::Status<std::unique_ptr<ConsumerQueue>>
-DisplayManagerClient::GetSurfaceQueue(int surface_id, int queue_id) {
-  auto status = InvokeRemoteMethod<DisplayManagerProtocol::GetSurfaceQueue>(
-      surface_id, queue_id);
-  if (!status) {
-    ALOGE(
-        "DisplayManagerClient::GetSurfaceQueue: Failed to get queue for "
-        "surface_id=%d queue_id=%d: %s",
-        surface_id, queue_id, status.GetErrorMessage().c_str());
-    return status.error_status();
-  }
-
-  return {ConsumerQueue::Import(status.take())};
-}
-
-}  // namespace display
-}  // namespace dvr
-}  // namespace android
diff --git a/libs/vr/libdisplay/display_protocol.cpp b/libs/vr/libdisplay/display_protocol.cpp
deleted file mode 100644
index 773f9a5..0000000
--- a/libs/vr/libdisplay/display_protocol.cpp
+++ /dev/null
@@ -1,13 +0,0 @@
-#include "include/private/dvr/display_protocol.h"
-
-namespace android {
-namespace dvr {
-namespace display {
-
-constexpr char DisplayProtocol::kClientPath[];
-constexpr char DisplayManagerProtocol::kClientPath[];
-constexpr char VSyncProtocol::kClientPath[];
-
-}  // namespace display
-}  // namespace dvr
-}  // namespace android
diff --git a/libs/vr/libdisplay/include/CPPLINT.cfg b/libs/vr/libdisplay/include/CPPLINT.cfg
deleted file mode 100644
index 2f8a3c0..0000000
--- a/libs/vr/libdisplay/include/CPPLINT.cfg
+++ /dev/null
@@ -1 +0,0 @@
-filter=-build/header_guard
diff --git a/libs/vr/libdisplay/include/private/dvr/display_client.h b/libs/vr/libdisplay/include/private/dvr/display_client.h
deleted file mode 100644
index 81546ac..0000000
--- a/libs/vr/libdisplay/include/private/dvr/display_client.h
+++ /dev/null
@@ -1,100 +0,0 @@
-#ifndef ANDROID_DVR_DISPLAY_CLIENT_H_
-#define ANDROID_DVR_DISPLAY_CLIENT_H_
-
-#include <dvr/dvr_api.h>
-#include <hardware/hwcomposer.h>
-#include <pdx/client.h>
-#include <pdx/file_handle.h>
-#include <private/dvr/buffer_hub_queue_client.h>
-#include <private/dvr/display_protocol.h>
-
-namespace android {
-namespace dvr {
-namespace display {
-
-class Surface : public pdx::ClientBase<Surface> {
- public:
-  // Utility named constructor. This can be removed once ClientBase::Create is
-  // refactored to return Status<T> types.
-  static pdx::Status<std::unique_ptr<Surface>> CreateSurface(
-      const SurfaceAttributes& attributes) {
-    int error;
-    pdx::Status<std::unique_ptr<Surface>> status;
-    if (auto surface = Create(attributes, &error))
-      status.SetValue(std::move(surface));
-    else
-      status.SetError(error);
-    return status;
-  }
-
-  int surface_id() const { return surface_id_; }
-  int z_order() const { return z_order_; }
-  bool visible() const { return visible_; }
-
-  pdx::Status<void> SetVisible(bool visible);
-  pdx::Status<void> SetZOrder(int z_order);
-  pdx::Status<void> SetAttributes(const SurfaceAttributes& attributes);
-
-  // Creates an empty queue.
-  pdx::Status<std::unique_ptr<ProducerQueue>> CreateQueue(uint32_t width,
-                                                          uint32_t height,
-                                                          uint32_t format,
-                                                          size_t metadata_size);
-
-  // Creates a queue and populates it with |capacity| buffers of the specified
-  // parameters.
-  pdx::Status<std::unique_ptr<ProducerQueue>> CreateQueue(uint32_t width,
-                                                          uint32_t height,
-                                                          uint32_t layer_count,
-                                                          uint32_t format,
-                                                          uint64_t usage,
-                                                          size_t capacity,
-                                                          size_t metadata_size);
-
- private:
-  friend BASE;
-
-  int surface_id_ = -1;
-  int z_order_ = 0;
-  bool visible_ = false;
-
-  // TODO(eieio,avakulenko): Remove error param once pdx::ClientBase::Create()
-  // returns Status<T>.
-  explicit Surface(const SurfaceAttributes& attributes, int* error = nullptr);
-  explicit Surface(pdx::LocalChannelHandle channel_handle,
-                   int* error = nullptr);
-
-  Surface(const Surface&) = delete;
-  void operator=(const Surface&) = delete;
-};
-
-class DisplayClient : public pdx::ClientBase<DisplayClient> {
- public:
-  pdx::Status<Metrics> GetDisplayMetrics();
-  pdx::Status<std::string> GetConfigurationData(ConfigFileType config_type);
-  pdx::Status<uint8_t> GetDisplayIdentificationPort();
-  pdx::Status<std::unique_ptr<IonBuffer>> SetupGlobalBuffer(
-      DvrGlobalBufferKey key, size_t size, uint64_t usage);
-  pdx::Status<void> DeleteGlobalBuffer(DvrGlobalBufferKey key);
-  pdx::Status<std::unique_ptr<IonBuffer>> GetGlobalBuffer(
-      DvrGlobalBufferKey key);
-  pdx::Status<std::unique_ptr<Surface>> CreateSurface(
-      const SurfaceAttributes& attributes);
-
-  // Temporary query for current VR status. Will be removed later.
-  pdx::Status<bool> IsVrAppRunning();
-
- private:
-  friend BASE;
-
-  explicit DisplayClient(int* error = nullptr);
-
-  DisplayClient(const DisplayClient&) = delete;
-  void operator=(const DisplayClient&) = delete;
-};
-
-}  // namespace display
-}  // namespace dvr
-}  // namespace android
-
-#endif  // ANDROID_DVR_DISPLAY_CLIENT_H_
diff --git a/libs/vr/libdisplay/include/private/dvr/display_manager_client.h b/libs/vr/libdisplay/include/private/dvr/display_manager_client.h
deleted file mode 100644
index 45aef51..0000000
--- a/libs/vr/libdisplay/include/private/dvr/display_manager_client.h
+++ /dev/null
@@ -1,49 +0,0 @@
-#ifndef ANDROID_DVR_DISPLAY_MANAGER_CLIENT_H_
-#define ANDROID_DVR_DISPLAY_MANAGER_CLIENT_H_
-
-#include <string>
-#include <vector>
-
-#include <pdx/client.h>
-#include <pdx/status.h>
-#include <private/dvr/display_protocol.h>
-
-namespace android {
-namespace dvr {
-
-class IonBuffer;
-class ConsumerQueue;
-
-namespace display {
-
-class DisplayManagerClient : public pdx::ClientBase<DisplayManagerClient> {
- public:
-  ~DisplayManagerClient() override;
-
-  pdx::Status<std::vector<SurfaceState>> GetSurfaceState();
-  pdx::Status<std::unique_ptr<ConsumerQueue>> GetSurfaceQueue(int surface_id,
-                                                              int queue_id);
-
-  using Client::event_fd;
-
-  pdx::Status<int> GetEventMask(int events) {
-    if (auto* client_channel = GetChannel())
-      return client_channel->GetEventMask(events);
-    else
-      return pdx::ErrorStatus(EINVAL);
-  }
-
- private:
-  friend BASE;
-
-  DisplayManagerClient();
-
-  DisplayManagerClient(const DisplayManagerClient&) = delete;
-  void operator=(const DisplayManagerClient&) = delete;
-};
-
-}  // namespace display
-}  // namespace dvr
-}  // namespace android
-
-#endif  // ANDROID_DVR_DISPLAY_MANAGER_CLIENT_H_
diff --git a/libs/vr/libdisplay/include/private/dvr/display_protocol.h b/libs/vr/libdisplay/include/private/dvr/display_protocol.h
deleted file mode 100644
index 9f4cc4a..0000000
--- a/libs/vr/libdisplay/include/private/dvr/display_protocol.h
+++ /dev/null
@@ -1,304 +0,0 @@
-#ifndef ANDROID_DVR_DISPLAY_PROTOCOL_H_
-#define ANDROID_DVR_DISPLAY_PROTOCOL_H_
-
-#include <sys/types.h>
-
-#include <array>
-#include <map>
-
-#include <dvr/dvr_display_types.h>
-
-#include <dvr/dvr_api.h>
-#include <pdx/rpc/buffer_wrapper.h>
-#include <pdx/rpc/remote_method.h>
-#include <pdx/rpc/serializable.h>
-#include <pdx/rpc/variant.h>
-#include <private/dvr/bufferhub_rpc.h>
-
-// RPC protocol definitions for DVR display services (VrFlinger).
-
-namespace android {
-namespace dvr {
-namespace display {
-
-// Native display metrics.
-struct Metrics {
-  // Basic display properties.
-  uint32_t display_width;
-  uint32_t display_height;
-  uint32_t display_x_dpi;
-  uint32_t display_y_dpi;
-  uint32_t vsync_period_ns;
-
-  // HMD metrics.
-  // TODO(eieio): Determine how these fields should be populated. On phones
-  // these values are determined at runtime by VrCore based on which headset the
-  // phone is in. On dedicated hardware this needs to come from somewhere else.
-  // Perhaps these should be moved to a separate structure that is returned by a
-  // separate runtime call.
-  uint32_t distorted_width;
-  uint32_t distorted_height;
-  uint32_t hmd_ipd_mm;
-  float inter_lens_distance_m;
-  std::array<float, 4> left_fov_lrbt;
-  std::array<float, 4> right_fov_lrbt;
-
- private:
-  PDX_SERIALIZABLE_MEMBERS(Metrics, display_width, display_height,
-                           display_x_dpi, display_y_dpi, vsync_period_ns,
-                           distorted_width, distorted_height, hmd_ipd_mm,
-                           inter_lens_distance_m, left_fov_lrbt,
-                           right_fov_lrbt);
-};
-
-// Serializable base type for enum structs. Enum structs are easier to use than
-// enum classes, especially for bitmasks. This base type provides common
-// utilities for flags types.
-template <typename Integer>
-class Flags {
- public:
-  using Base = Flags<Integer>;
-  using Type = Integer;
-
-  // NOLINTNEXTLINE(google-explicit-constructor)
-  Flags(const Integer& value) : value_{value} {}
-  Flags(const Flags&) = default;
-  Flags& operator=(const Flags&) = default;
-
-  Integer value() const { return value_; }
-  // NOLINTNEXTLINE(google-explicit-constructor)
-  operator Integer() const { return value_; }
-
-  bool IsSet(Integer bits) const { return (value_ & bits) == bits; }
-  bool IsClear(Integer bits) const { return (value_ & bits) == 0; }
-
-  void Set(Integer bits) { value_ |= bits; }
-  void Clear(Integer bits) { value_ &= ~bits; }
-
-  Integer operator|(Integer bits) const { return value_ | bits; }
-  Integer operator&(Integer bits) const { return value_ & bits; }
-
-  Flags& operator|=(Integer bits) {
-    value_ |= bits;
-    return *this;
-  }
-  Flags& operator&=(Integer bits) {
-    value_ &= bits;
-    return *this;
-  }
-
- private:
-  Integer value_;
-
-  PDX_SERIALIZABLE_MEMBERS(Flags<Integer>, value_);
-};
-
-// Flags indicating what changed since last update.
-struct SurfaceUpdateFlags : public Flags<uint32_t> {
-  enum : Type {
-    None = DVR_SURFACE_UPDATE_FLAGS_NONE,
-    NewSurface = DVR_SURFACE_UPDATE_FLAGS_NEW_SURFACE,
-    BuffersChanged = DVR_SURFACE_UPDATE_FLAGS_BUFFERS_CHANGED,
-    VisibilityChanged = DVR_SURFACE_UPDATE_FLAGS_VISIBILITY_CHANGED,
-    AttributesChanged = DVR_SURFACE_UPDATE_FLAGS_ATTRIBUTES_CHANGED,
-  };
-
-  SurfaceUpdateFlags() : Base{None} {}
-  using Base::Base;
-};
-
-// Surface attribute key/value types.
-using SurfaceAttributeKey = int32_t;
-using SurfaceAttributeValue =
-    pdx::rpc::Variant<int32_t, int64_t, bool, float, std::array<float, 2>,
-                      std::array<float, 3>, std::array<float, 4>,
-                      std::array<float, 8>, std::array<float, 16>>;
-
-// Defined surface attribute keys.
-struct SurfaceAttribute : public Flags<SurfaceAttributeKey> {
-  enum : Type {
-    // Keys in the negative integer space are interpreted by VrFlinger for
-    // direct surfaces.
-    Direct = DVR_SURFACE_ATTRIBUTE_DIRECT,
-    ZOrder = DVR_SURFACE_ATTRIBUTE_Z_ORDER,
-    Visible = DVR_SURFACE_ATTRIBUTE_VISIBLE,
-
-    // Invalid key. May be used to terminate C style lists in public API code.
-    Invalid = DVR_SURFACE_ATTRIBUTE_INVALID,
-
-    // Positive keys are interpreted by the compositor only.
-    FirstUserKey = DVR_SURFACE_ATTRIBUTE_FIRST_USER_KEY,
-  };
-
-  SurfaceAttribute() : Base{Invalid} {}
-  using Base::Base;
-};
-
-// Collection of surface attribute key/value pairs.
-using SurfaceAttributes = std::map<SurfaceAttributeKey, SurfaceAttributeValue>;
-
-struct SurfaceState {
-  int32_t surface_id;
-  int32_t process_id;
-  int32_t user_id;
-
-  SurfaceAttributes surface_attributes;
-  SurfaceUpdateFlags update_flags;
-  std::vector<int32_t> queue_ids;
-
-  // Convenience accessors.
-  bool GetVisible() const {
-    bool bool_value = false;
-    GetAttribute(SurfaceAttribute::Visible, &bool_value,
-                 ValidTypes<int32_t, int64_t, bool, float>{});
-    return bool_value;
-  }
-
-  int GetZOrder() const {
-    int int_value = 0;
-    GetAttribute(SurfaceAttribute::ZOrder, &int_value,
-                 ValidTypes<int32_t, int64_t, float>{});
-    return int_value;
-  }
-
- private:
-  template <typename... Types>
-  struct ValidTypes {};
-
-  template <typename ReturnType, typename... Types>
-  bool GetAttribute(SurfaceAttributeKey key, ReturnType* out_value,
-                    ValidTypes<Types...>) const {
-    auto search = surface_attributes.find(key);
-    if (search != surface_attributes.end())
-      return pdx::rpc::IfAnyOf<Types...>::Get(&search->second, out_value);
-    else
-      return false;
-  }
-
-  PDX_SERIALIZABLE_MEMBERS(SurfaceState, surface_id, process_id,
-                           surface_attributes, update_flags, queue_ids);
-};
-
-struct SurfaceInfo {
-  int surface_id;
-  bool visible;
-  int z_order;
-
- private:
-  PDX_SERIALIZABLE_MEMBERS(SurfaceInfo, surface_id, visible, z_order);
-};
-
-enum class ConfigFileType : uint32_t {
-  kLensMetrics,
-  kDeviceMetrics,
-  kDeviceConfiguration,
-  kDeviceEdid
-};
-
-struct DisplayProtocol {
-  // Service path.
-  static constexpr char kClientPath[] = "system/vr/display/client";
-
-  // Op codes.
-  enum {
-    kOpGetMetrics = 0,
-    kOpGetConfigurationData,
-    kOpSetupGlobalBuffer,
-    kOpDeleteGlobalBuffer,
-    kOpGetGlobalBuffer,
-    kOpIsVrAppRunning,
-    kOpCreateSurface,
-    kOpGetSurfaceInfo,
-    kOpCreateQueue,
-    kOpSetAttributes,
-    kOpGetDisplayIdentificationPort,
-  };
-
-  // Aliases.
-  using LocalChannelHandle = pdx::LocalChannelHandle;
-  using Void = pdx::rpc::Void;
-
-  // Methods.
-  PDX_REMOTE_METHOD(GetMetrics, kOpGetMetrics, Metrics(Void));
-  PDX_REMOTE_METHOD(GetConfigurationData, kOpGetConfigurationData,
-                    std::string(ConfigFileType config_type));
-  PDX_REMOTE_METHOD(GetDisplayIdentificationPort,
-                    kOpGetDisplayIdentificationPort, uint8_t(Void));
-  PDX_REMOTE_METHOD(SetupGlobalBuffer, kOpSetupGlobalBuffer,
-                    LocalNativeBufferHandle(DvrGlobalBufferKey key, size_t size,
-                                            uint64_t usage));
-  PDX_REMOTE_METHOD(DeleteGlobalBuffer, kOpDeleteGlobalBuffer,
-                    void(DvrGlobalBufferKey key));
-  PDX_REMOTE_METHOD(GetGlobalBuffer, kOpGetGlobalBuffer,
-                    LocalNativeBufferHandle(DvrGlobalBufferKey key));
-  PDX_REMOTE_METHOD(IsVrAppRunning, kOpIsVrAppRunning, bool(Void));
-  PDX_REMOTE_METHOD(CreateSurface, kOpCreateSurface,
-                    SurfaceInfo(const SurfaceAttributes& attributes));
-  PDX_REMOTE_METHOD(GetSurfaceInfo, kOpGetSurfaceInfo, SurfaceInfo(Void));
-  PDX_REMOTE_METHOD(
-      CreateQueue, kOpCreateQueue,
-      LocalChannelHandle(const ProducerQueueConfig& producer_config));
-  PDX_REMOTE_METHOD(SetAttributes, kOpSetAttributes,
-                    void(const SurfaceAttributes& attributes));
-};
-
-struct DisplayManagerProtocol {
-  // Service path.
-  static constexpr char kClientPath[] = "system/vr/display/manager";
-
-  // Op codes.
-  enum {
-    kOpGetSurfaceState = 0,
-    kOpGetSurfaceQueue,
-  };
-
-  // Aliases.
-  using LocalChannelHandle = pdx::LocalChannelHandle;
-  using Void = pdx::rpc::Void;
-
-  // Methods.
-  PDX_REMOTE_METHOD(GetSurfaceState, kOpGetSurfaceState,
-                    std::vector<SurfaceState>(Void));
-  PDX_REMOTE_METHOD(GetSurfaceQueue, kOpGetSurfaceQueue,
-                    LocalChannelHandle(int surface_id, int queue_id));
-};
-
-struct VSyncSchedInfo {
-  int64_t vsync_period_ns;
-  int64_t timestamp_ns;
-  uint32_t next_vsync_count;
-
- private:
-  PDX_SERIALIZABLE_MEMBERS(VSyncSchedInfo, vsync_period_ns, timestamp_ns,
-                           next_vsync_count);
-};
-
-struct VSyncProtocol {
-  // Service path.
-  static constexpr char kClientPath[] = "system/vr/display/vsync";
-
-  // Op codes.
-  enum {
-    kOpWait = 0,
-    kOpAck,
-    kOpGetLastTimestamp,
-    kOpGetSchedInfo,
-    kOpAcknowledge,
-  };
-
-  // Aliases.
-  using Void = pdx::rpc::Void;
-  using Timestamp = int64_t;
-
-  // Methods.
-  PDX_REMOTE_METHOD(Wait, kOpWait, Timestamp(Void));
-  PDX_REMOTE_METHOD(GetLastTimestamp, kOpGetLastTimestamp, Timestamp(Void));
-  PDX_REMOTE_METHOD(GetSchedInfo, kOpGetSchedInfo, VSyncSchedInfo(Void));
-  PDX_REMOTE_METHOD(Acknowledge, kOpAcknowledge, void(Void));
-};
-
-}  // namespace display
-}  // namespace dvr
-}  // namespace android
-
-#endif  // ANDROID_DVR_DISPLAY_PROTOCOL_H_
diff --git a/libs/vr/libdisplay/include/private/dvr/shared_buffer_helpers.h b/libs/vr/libdisplay/include/private/dvr/shared_buffer_helpers.h
deleted file mode 100644
index 20541a6..0000000
--- a/libs/vr/libdisplay/include/private/dvr/shared_buffer_helpers.h
+++ /dev/null
@@ -1,146 +0,0 @@
-#ifndef ANDROID_DVR_SHARED_BUFFER_HELPERS_H_
-#define ANDROID_DVR_SHARED_BUFFER_HELPERS_H_
-
-#include <assert.h>
-#include <tuple>
-
-#include <libbroadcastring/broadcast_ring.h>
-#include <private/dvr/display_client.h>
-
-namespace android {
-namespace dvr {
-
-// The buffer usage type for mapped shared buffers.
-enum class CPUUsageMode { READ_OFTEN, READ_RARELY, WRITE_OFTEN, WRITE_RARELY };
-
-// Holds the memory for the mapped shared buffer. Unlocks and releases the
-// underlying IonBuffer in destructor.
-class CPUMappedBuffer {
- public:
-  // This constructor will create a display client and get the buffer from it.
-  CPUMappedBuffer(DvrGlobalBufferKey key, CPUUsageMode mode);
-
-  // If you already have the IonBuffer, use this. It will take ownership.
-  CPUMappedBuffer(std::unique_ptr<IonBuffer> buffer, CPUUsageMode mode);
-
-  // Use this if you do not want to take ownership.
-  CPUMappedBuffer(IonBuffer* buffer, CPUUsageMode mode);
-
-  ~CPUMappedBuffer();
-
-  // Getters.
-  size_t Size() const { return size_; }
-  void* Address() const { return address_; }
-  bool IsMapped() const { return Address() != nullptr; }
-
-  // Attempt mapping this buffer to the CPU addressable space.
-  // This will create a display client and see if the buffer exists.
-  // If the buffer has not been setup yet, you will need to try again later.
-  void TryMapping();
-
- protected:
-  // The memory area if we managed to map it.
-  size_t size_ = 0;
-  void* address_ = nullptr;
-
-  // If we are polling the display client, the buffer key here.
-  DvrGlobalBufferKey buffer_key_;
-
-  // If we just own the IonBuffer outright, it's here.
-  std::unique_ptr<IonBuffer> owned_buffer_ = nullptr;
-
-  // The last time we connected to the display service.
-  int64_t last_display_service_connection_ns_ = 0;
-
-  // If we do not own the IonBuffer, it's here
-  IonBuffer* buffer_ = nullptr;
-
-  // The usage mode.
-  CPUUsageMode usage_mode_ = CPUUsageMode::READ_OFTEN;
-};
-
-// Represents a broadcast ring inside a mapped shared memory buffer.
-// If has the same set of constructors as CPUMappedBuffer.
-// The template argument is the concrete BroadcastRing class that this buffer
-// holds.
-template <class RingType>
-class CPUMappedBroadcastRing : public CPUMappedBuffer {
- public:
-  CPUMappedBroadcastRing(DvrGlobalBufferKey key, CPUUsageMode mode)
-      : CPUMappedBuffer(key, mode) {}
-
-  CPUMappedBroadcastRing(std::unique_ptr<IonBuffer> buffer, CPUUsageMode mode)
-      : CPUMappedBuffer(std::move(buffer), mode) {}
-
-  CPUMappedBroadcastRing(IonBuffer* buffer, CPUUsageMode mode)
-      : CPUMappedBuffer(buffer, mode) {}
-
-  // Helper function for publishing records in the ring.
-  void Publish(const typename RingType::Record& record) {
-    assert((usage_mode_ == CPUUsageMode::WRITE_OFTEN) ||
-           (usage_mode_ == CPUUsageMode::WRITE_RARELY));
-
-    auto ring = Ring();
-    if (ring) {
-      ring->Put(record);
-    }
-  }
-
-  // Helper function for getting records from the ring.
-  // Returns true if we were able to retrieve the latest.
-  bool GetNewest(typename RingType::Record* record) {
-    assert((usage_mode_ == CPUUsageMode::READ_OFTEN) ||
-           (usage_mode_ == CPUUsageMode::READ_RARELY));
-
-    auto ring = Ring();
-    if (ring) {
-      return ring->GetNewest(&sequence_, record);
-    }
-
-    return false;
-  }
-
-  // Try obtaining the ring. If the named buffer has not been created yet, it
-  // will return nullptr.
-  RingType* Ring() {
-    // No ring created yet?
-    if (ring_ == nullptr) {
-      // Not mapped the memory yet?
-      if (IsMapped() == false) {
-        TryMapping();
-      }
-
-      // If have the memory mapped, allocate the ring.
-      if (IsMapped()) {
-        switch (usage_mode_) {
-          case CPUUsageMode::READ_OFTEN:
-          case CPUUsageMode::READ_RARELY: {
-            RingType ring;
-            bool import_ok;
-            std::tie(ring, import_ok) = RingType::Import(address_, size_);
-            if (import_ok) {
-              ring_ = std::make_unique<RingType>(ring);
-            }
-          } break;
-          case CPUUsageMode::WRITE_OFTEN:
-          case CPUUsageMode::WRITE_RARELY:
-            ring_ =
-                std::make_unique<RingType>(RingType::Create(address_, size_));
-            break;
-        }
-      }
-    }
-
-    return ring_.get();
-  }
-
- protected:
-  std::unique_ptr<RingType> ring_ = nullptr;
-
-  uint32_t sequence_ = 0;
-};
-
-}  // namespace dvr
-}  // namespace android
-
-#endif  // ANDROID_DVR_SHARED_BUFFER_HELPERS_H_
diff --git a/libs/vr/libdisplay/include/private/dvr/vsync_service.h b/libs/vr/libdisplay/include/private/dvr/vsync_service.h
deleted file mode 100644
index 152464a..0000000
--- a/libs/vr/libdisplay/include/private/dvr/vsync_service.h
+++ /dev/null
@@ -1,65 +0,0 @@
-#ifndef ANDROID_DVR_VSYNC_SERVICE_H_
-#define ANDROID_DVR_VSYNC_SERVICE_H_
-
-#include <binder/IInterface.h>
-
-namespace android {
-namespace dvr {
-
-class IVsyncCallback : public IInterface {
- public:
-  DECLARE_META_INTERFACE(VsyncCallback)
-
-  enum {
-    ON_VSYNC = IBinder::FIRST_CALL_TRANSACTION
-  };
-
-  virtual status_t onVsync(int64_t vsync_timestamp) = 0;
-};
-
-class BnVsyncCallback : public BnInterface<IVsyncCallback> {
- public:
-  virtual status_t onTransact(uint32_t code, const Parcel& data,
-                              Parcel* reply, uint32_t flags = 0);
-};
-
-// Register a callback with IVsyncService to be notified of vsync events and
-// timestamps. There's also a shared memory vsync buffer defined in
-// dvr_shared_buffers.h. IVsyncService has advantages over the vsync shared
-// memory buffer that make it preferable in certain situations:
-//
-// 1. The shared memory buffer lifetime is controlled by VrCore. IVsyncService
-// is always available as long as surface flinger is running.
-//
-// 2. IVsyncService will make a binder callback when a vsync event occurs. This
-// allows the client to not write code to implement periodic "get the latest
-// vsync" calls, which is necessary with the vsync shared memory buffer.
-//
-// 3. The IVsyncService provides the real vsync timestamp reported by hardware
-// composer, whereas the vsync shared memory buffer only has predicted vsync
-// times.
-class IVsyncService : public IInterface {
-public:
-  DECLARE_META_INTERFACE(VsyncService)
-
-  static const char* GetServiceName() { return "vrflinger_vsync"; }
-
-  enum {
-    REGISTER_CALLBACK = IBinder::FIRST_CALL_TRANSACTION,
-    UNREGISTER_CALLBACK
-  };
-
-  virtual status_t registerCallback(const sp<IVsyncCallback> callback) = 0;
-  virtual status_t unregisterCallback(const sp<IVsyncCallback> callback) = 0;
-};
-
-class BnVsyncService : public BnInterface<IVsyncService> {
- public:
-  virtual status_t onTransact(uint32_t code, const Parcel& data,
-                              Parcel* reply, uint32_t flags = 0);
-};
-
-}  // namespace dvr
-}  // namespace android
-
-#endif  // ANDROID_DVR_VSYNC_SERVICE_H_
diff --git a/libs/vr/libdisplay/shared_buffer_helpers.cpp b/libs/vr/libdisplay/shared_buffer_helpers.cpp
deleted file mode 100644
index 6ebf487..0000000
--- a/libs/vr/libdisplay/shared_buffer_helpers.cpp
+++ /dev/null
@@ -1,98 +0,0 @@
-#include <private/dvr/clock_ns.h>
-#include <private/dvr/shared_buffer_helpers.h>
-
-namespace android {
-namespace dvr {
-namespace {
-
-// We will not poll the display service for buffers more frequently than this.
-constexpr size_t kDisplayServiceTriesPerSecond = 2;
-}  // namespace
-
-CPUMappedBuffer::CPUMappedBuffer(DvrGlobalBufferKey key, CPUUsageMode mode)
-    : buffer_key_(key), usage_mode_(mode) {
-  TryMapping();
-}
-
-CPUMappedBuffer::CPUMappedBuffer(std::unique_ptr<IonBuffer> buffer,
-                                 CPUUsageMode mode)
-    : owned_buffer_(std::move(buffer)),
-      buffer_(owned_buffer_.get()),
-      usage_mode_(mode) {
-  TryMapping();
-}
-
-CPUMappedBuffer::CPUMappedBuffer(IonBuffer* buffer, CPUUsageMode mode)
-    : buffer_(buffer), usage_mode_(mode) {
-  TryMapping();
-}
-
-CPUMappedBuffer::~CPUMappedBuffer() {
-  if (IsMapped()) {
-    buffer_->Unlock();
-  }
-}
-
-void CPUMappedBuffer::TryMapping() {
-  // Do we have an IonBuffer for this shared memory object?
-  if (buffer_ == nullptr) {
-    // Has it been too long since we last connected to the display service?
-    const auto current_time_ns = GetSystemClockNs();
-    if ((current_time_ns - last_display_service_connection_ns_) <
-        (1e9 / kDisplayServiceTriesPerSecond)) {
-      // Early exit.
-      return;
-    }
-    last_display_service_connection_ns_ = current_time_ns;
-
-    // Create a display client and get the buffer.
-    auto display_client = display::DisplayClient::Create();
-    if (display_client) {
-      auto get_result = display_client->GetGlobalBuffer(buffer_key_);
-      if (get_result.ok()) {
-        owned_buffer_ = get_result.take();
-        buffer_ = owned_buffer_.get();
-      } else {
-        // The buffer has not been created yet. This is OK, we will keep
-        // retrying.
-      }
-    } else {
-      ALOGE("Unable to create display client for shared buffer access");
-    }
-  }
-
-  if (buffer_) {
-    auto usage = buffer_->usage() & ~GRALLOC_USAGE_SW_READ_MASK &
-                 ~GRALLOC_USAGE_SW_WRITE_MASK;
-
-    // Figure out the usage bits.
-    switch (usage_mode_) {
-      case CPUUsageMode::READ_OFTEN:
-        usage |= GRALLOC_USAGE_SW_READ_OFTEN;
-        break;
-      case CPUUsageMode::READ_RARELY:
-        usage |= GRALLOC_USAGE_SW_READ_RARELY;
-        break;
-      case CPUUsageMode::WRITE_OFTEN:
-        usage |= GRALLOC_USAGE_SW_WRITE_OFTEN;
-        break;
-      case CPUUsageMode::WRITE_RARELY:
-        usage |= GRALLOC_USAGE_SW_WRITE_RARELY;
-        break;
-    }
-
-    int width = static_cast<int>(buffer_->width());
-    int height = 1;
-    const auto ret = buffer_->Lock(usage, 0, 0, width, height, &address_);
-
-    if (ret < 0 || !address_) {
-      ALOGE("Pose failed to map ring buffer: ret:%d, addr:%p", ret, address_);
-      buffer_->Unlock();
-    } else {
-      size_ = width;
-    }
-  }
-}
-
-}  // namespace dvr
-}  // namespace android
diff --git a/libs/vr/libdisplay/system/CPPLINT.cfg b/libs/vr/libdisplay/system/CPPLINT.cfg
deleted file mode 100644
index 2f8a3c0..0000000
--- a/libs/vr/libdisplay/system/CPPLINT.cfg
+++ /dev/null
@@ -1 +0,0 @@
-filter=-build/header_guard
diff --git a/libs/vr/libdisplay/vsync_service.cpp b/libs/vr/libdisplay/vsync_service.cpp
deleted file mode 100644
index 04d4f30..0000000
--- a/libs/vr/libdisplay/vsync_service.cpp
+++ /dev/null
@@ -1,146 +0,0 @@
-#include "include/private/dvr/vsync_service.h"
-
-#include <binder/Parcel.h>
-#include <log/log.h>
-
-namespace android {
-namespace dvr {
-
-status_t BnVsyncCallback::onTransact(
-    uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) {
-  switch (code) {
-    case ON_VSYNC: {
-      CHECK_INTERFACE(IVsyncCallback, data, reply);
-      int64_t vsync_timestamp = 0;
-      status_t result = data.readInt64(&vsync_timestamp);
-      if (result != OK) {
-        ALOGE("onVsync failed to readInt64: %d", result);
-        return result;
-      }
-      onVsync(vsync_timestamp);
-      return OK;
-    }
-    default: {
-      return BBinder::onTransact(code, data, reply, flags);
-    }
-  }
-}
-
-class BpVsyncCallback : public BpInterface<IVsyncCallback> {
-public:
-  explicit BpVsyncCallback(const sp<IBinder>& impl)
-      : BpInterface<IVsyncCallback>(impl) {}
-  virtual ~BpVsyncCallback() {}
-
-  virtual status_t onVsync(int64_t vsync_timestamp) {
-    Parcel data, reply;
-    status_t result = data.writeInterfaceToken(
-        IVsyncCallback::getInterfaceDescriptor());
-    if (result != OK) {
-      ALOGE("onVsync failed to writeInterfaceToken: %d", result);
-      return result;
-    }
-    result = data.writeInt64(vsync_timestamp);
-    if (result != OK) {
-      ALOGE("onVsync failed to writeInt64: %d", result);
-      return result;
-    }
-    result = remote()->transact(BnVsyncCallback::ON_VSYNC, data, &reply,
-                                IBinder::FLAG_ONEWAY);
-    if (result != OK) {
-      ALOGE("onVsync failed to transact: %d", result);
-      return result;
-    }
-    return result;
-  }
-};
-
-IMPLEMENT_META_INTERFACE(VsyncCallback, "android.dvr.IVsyncCallback");
-
-
-status_t BnVsyncService::onTransact(
-    uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) {
-  switch (code) {
-    case REGISTER_CALLBACK: {
-      CHECK_INTERFACE(IVsyncService, data, reply);
-      sp<IBinder> callback;
-      status_t result = data.readStrongBinder(&callback);
-      if (result != OK) {
-        ALOGE("registerCallback failed to readStrongBinder: %d", result);
-        return result;
-      }
-      registerCallback(interface_cast<IVsyncCallback>(callback));
-      return OK;
-    }
-    case UNREGISTER_CALLBACK: {
-      CHECK_INTERFACE(IVsyncService, data, reply);
-      sp<IBinder> callback;
-      status_t result = data.readStrongBinder(&callback);
-      if (result != OK) {
-        ALOGE("unregisterCallback failed to readStrongBinder: %d", result);
-        return result;
-      }
-      unregisterCallback(interface_cast<IVsyncCallback>(callback));
-      return OK;
-    }
-    default: {
-      return BBinder::onTransact(code, data, reply, flags);
-    }
-  }
-}
-
-class BpVsyncService : public BpInterface<IVsyncService> {
-public:
-  explicit BpVsyncService(const sp<IBinder>& impl)
-      : BpInterface<IVsyncService>(impl) {}
-  virtual ~BpVsyncService() {}
-
-  virtual status_t registerCallback(const sp<IVsyncCallback> callback) {
-    Parcel data, reply;
-    status_t result = data.writeInterfaceToken(
-        IVsyncService::getInterfaceDescriptor());
-    if (result != OK) {
-      ALOGE("registerCallback failed to writeInterfaceToken: %d", result);
-      return result;
-    }
-    result = data.writeStrongBinder(IInterface::asBinder(callback));
-    if (result != OK) {
-      ALOGE("registerCallback failed to writeStrongBinder: %d", result);
-      return result;
-    }
-    result = remote()->transact(
-        BnVsyncService::REGISTER_CALLBACK, data, &reply);
-    if (result != OK) {
-      ALOGE("registerCallback failed to transact: %d", result);
-      return result;
-    }
-    return result;
-  }
-
-  virtual status_t unregisterCallback(const sp<IVsyncCallback> callback) {
-    Parcel data, reply;
-    status_t result = data.writeInterfaceToken(
-        IVsyncService::getInterfaceDescriptor());
-    if (result != OK) {
-      ALOGE("unregisterCallback failed to writeInterfaceToken: %d", result);
-      return result;
-    }
-    result = data.writeStrongBinder(IInterface::asBinder(callback));
-    if (result != OK) {
-      ALOGE("unregisterCallback failed to writeStrongBinder: %d", result);
-      return result;
-    }
-    result = remote()->transact(
-        BnVsyncService::UNREGISTER_CALLBACK, data, &reply);
-    if (result != OK) {
-      ALOGE("unregisterCallback failed to transact: %d", result);
-      return result;
-    }
-    return result;
-  }
-};
-
-IMPLEMENT_META_INTERFACE(VsyncService, "android.dvr.IVsyncService");
-
-}  // namespace dvr
-}  // namespace android
diff --git a/libs/vr/libdvr/Android.bp b/libs/vr/libdvr/Android.bp
index 96023dd..9dbeacb 100644
--- a/libs/vr/libdvr/Android.bp
+++ b/libs/vr/libdvr/Android.bp
@@ -33,87 +33,3 @@
     ],
     min_sdk_version: "29",
 }
-
-cc_library_headers {
-    name: "libdvr_private_headers",
-    export_include_dirs: ["."],
-    vendor_available: false,
-}
-
-cflags = [
-    "-DDVR_TRACKING_IMPLEMENTED=0",
-    "-DLOG_TAG=\"libdvr\"",
-    "-DTRACE=0",
-    "-Wall",
-    "-Werror",
-]
-
-srcs = [
-    "dvr_api.cpp",
-    "dvr_buffer.cpp",
-    "dvr_buffer_queue.cpp",
-    "dvr_configuration_data.cpp",
-    "dvr_display_manager.cpp",
-    "dvr_hardware_composer_client.cpp",
-    "dvr_performance.cpp",
-    "dvr_pose.cpp",
-    "dvr_surface.cpp",
-    "dvr_tracking.cpp",
-]
-
-static_libs = [
-    "libbroadcastring",
-    "libvrsensor",
-    "libdisplay",
-    "libvirtualtouchpadclient",
-    "libvr_hwc-impl",
-    "libvr_hwc-binder",
-    "libgrallocusage",
-    "libperformance",
-]
-
-shared_libs = [
-    "android.hardware.graphics.bufferqueue@1.0",
-    "android.hidl.token@1.0-utils",
-    "libbase",
-    "libbufferhubqueue",
-    "libbinder",
-    "liblog",
-    "libcutils",
-    "libutils",
-    "libnativewindow",
-    "libgui",
-    "libui",
-    "libpdx_default_transport",
-]
-
-cc_library_shared {
-    name: "libdvr.google",
-    system_ext_specific: true,
-    owner: "google",
-    cflags: cflags,
-    header_libs: ["libdvr_headers"],
-    export_header_lib_headers: ["libdvr_headers"],
-    srcs: srcs,
-    static_libs: static_libs,
-    shared_libs: shared_libs,
-    version_script: "exported_apis.lds",
-}
-
-// Also build a static libdvr for linking into tests. The linker script
-// restricting function access in the shared lib makes it inconvenient to use in
-// test code.
-cc_library_static {
-    name: "libdvr_static.google",
-    owner: "google",
-    cflags: cflags,
-    header_libs: ["libdvr_headers"],
-    export_header_lib_headers: ["libdvr_headers"],
-    srcs: srcs,
-    static_libs: static_libs,
-    shared_libs: shared_libs,
-}
-
-subdirs = [
-    "tests",
-]
diff --git a/libs/vr/libdvr/dvr_api.cpp b/libs/vr/libdvr/dvr_api.cpp
deleted file mode 100644
index e099f6a..0000000
--- a/libs/vr/libdvr/dvr_api.cpp
+++ /dev/null
@@ -1,66 +0,0 @@
-#include "include/dvr/dvr_api.h"
-
-#include <errno.h>
-#include <utils/Log.h>
-
-#include <algorithm>
-
-// Headers from libdvr
-#include <dvr/dvr_buffer.h>
-#include <dvr/dvr_buffer_queue.h>
-#include <dvr/dvr_configuration_data.h>
-#include <dvr/dvr_display_manager.h>
-#include <dvr/dvr_performance.h>
-#include <dvr/dvr_surface.h>
-#include <dvr/dvr_tracking.h>
-#include <dvr/dvr_vsync.h>
-
-// Headers not yet moved into libdvr.
-// TODO(jwcai) Move these once their callers are moved into Google3.
-#include <dvr/dvr_hardware_composer_client.h>
-#include <dvr/pose_client.h>
-#include <dvr/virtual_touchpad_client.h>
-
-extern "C" {
-
-int dvrGetApi(void* api, size_t struct_size, int version) {
-  ALOGI("dvrGetApi: api=%p struct_size=%zu version=%d", api, struct_size,
-        version);
-  if (version == 1) {
-    // New entry points are added at the end. If the caller's struct and
-    // this library have different sizes, we define the entry points in common.
-    // The caller is expected to handle unset entry points if necessary.
-    size_t clamped_struct_size = std::min(struct_size, sizeof(DvrApi_v1));
-    DvrApi_v1* dvr_api = static_cast<DvrApi_v1*>(api);
-
-// Defines an API entry for V1 (no version suffix).
-#define DVR_V1_API_ENTRY(name)                                 \
-  do {                                                         \
-    if ((offsetof(DvrApi_v1, name) + sizeof(dvr_api->name)) <= \
-        clamped_struct_size) {                                 \
-      dvr_api->name = dvr##name;                               \
-    }                                                          \
-  } while (0)
-
-#define DVR_V1_API_ENTRY_DEPRECATED(name)                      \
-  do {                                                         \
-    if ((offsetof(DvrApi_v1, name) + sizeof(dvr_api->name)) <= \
-        clamped_struct_size) {                                 \
-      dvr_api->name = nullptr;                                 \
-    }                                                          \
-  } while (0)
-
-#include "include/dvr/dvr_api_entries.h"
-
-// Undefine macro definitions to play nice with Google3 style rules.
-#undef DVR_V1_API_ENTRY
-#undef DVR_V1_API_ENTRY_DEPRECATED
-
-    return 0;
-  }
-
-  ALOGE("dvrGetApi: Unknown API version=%d", version);
-  return -EINVAL;
-}
-
-}  // extern "C"
diff --git a/libs/vr/libdvr/dvr_buffer.cpp b/libs/vr/libdvr/dvr_buffer.cpp
deleted file mode 100644
index c11706f..0000000
--- a/libs/vr/libdvr/dvr_buffer.cpp
+++ /dev/null
@@ -1,121 +0,0 @@
-#include "include/dvr/dvr_buffer.h"
-
-#include <android/hardware_buffer.h>
-#include <dvr/dvr_shared_buffers.h>
-#include <private/dvr/consumer_buffer.h>
-#include <private/dvr/producer_buffer.h>
-#include <ui/GraphicBuffer.h>
-
-#include "dvr_internal.h"
-
-using namespace android;
-
-namespace android {
-namespace dvr {
-
-DvrBuffer* CreateDvrBufferFromIonBuffer(
-    const std::shared_ptr<IonBuffer>& ion_buffer) {
-  if (!ion_buffer)
-    return nullptr;
-  return new DvrBuffer{std::move(ion_buffer)};
-}
-
-}  // namespace dvr
-}  // namespace android
-
-namespace {
-
-int ConvertToAHardwareBuffer(GraphicBuffer* graphic_buffer,
-                             AHardwareBuffer** hardware_buffer) {
-  if (!hardware_buffer || !graphic_buffer) {
-    return -EINVAL;
-  }
-  *hardware_buffer = reinterpret_cast<AHardwareBuffer*>(graphic_buffer);
-  AHardwareBuffer_acquire(*hardware_buffer);
-  return 0;
-}
-
-}  // anonymous namespace
-
-extern "C" {
-
-void dvrWriteBufferDestroy(DvrWriteBuffer* write_buffer) {
-  if (write_buffer != nullptr) {
-    ALOGW_IF(
-        write_buffer->slot != -1,
-        "dvrWriteBufferDestroy: Destroying a buffer associated with a valid "
-        "buffer queue slot. This may indicate possible leaks, buffer_id=%d.",
-        dvrWriteBufferGetId(write_buffer));
-    delete write_buffer;
-  }
-}
-
-int dvrWriteBufferIsValid(DvrWriteBuffer* write_buffer) {
-  return write_buffer && write_buffer->write_buffer;
-}
-
-int dvrWriteBufferGetId(DvrWriteBuffer* write_buffer) {
-  if (!write_buffer || !write_buffer->write_buffer)
-    return -EINVAL;
-
-  return write_buffer->write_buffer->id();
-}
-
-int dvrWriteBufferGetAHardwareBuffer(DvrWriteBuffer* write_buffer,
-                                     AHardwareBuffer** hardware_buffer) {
-  if (!write_buffer || !write_buffer->write_buffer)
-    return -EINVAL;
-
-  return ConvertToAHardwareBuffer(
-      write_buffer->write_buffer->buffer()->buffer().get(), hardware_buffer);
-}
-
-void dvrReadBufferDestroy(DvrReadBuffer* read_buffer) {
-  if (read_buffer != nullptr) {
-    ALOGW_IF(
-        read_buffer->slot != -1,
-        "dvrReadBufferDestroy: Destroying a buffer associated with a valid "
-        "buffer queue slot. This may indicate possible leaks, buffer_id=%d.",
-        dvrReadBufferGetId(read_buffer));
-    delete read_buffer;
-  }
-}
-
-int dvrReadBufferIsValid(DvrReadBuffer* read_buffer) {
-  return read_buffer && read_buffer->read_buffer;
-}
-
-int dvrReadBufferGetId(DvrReadBuffer* read_buffer) {
-  if (!read_buffer || !read_buffer->read_buffer)
-    return -EINVAL;
-
-  return read_buffer->read_buffer->id();
-}
-
-int dvrReadBufferGetAHardwareBuffer(DvrReadBuffer* read_buffer,
-                                    AHardwareBuffer** hardware_buffer) {
-  if (!read_buffer || !read_buffer->read_buffer)
-    return -EINVAL;
-
-  return ConvertToAHardwareBuffer(
-      read_buffer->read_buffer->buffer()->buffer().get(), hardware_buffer);
-}
-
-void dvrBufferDestroy(DvrBuffer* buffer) { delete buffer; }
-
-int dvrBufferGetAHardwareBuffer(DvrBuffer* buffer,
-                                AHardwareBuffer** hardware_buffer) {
-  if (!buffer || !buffer->buffer || !hardware_buffer) {
-    return -EINVAL;
-  }
-
-  return ConvertToAHardwareBuffer(buffer->buffer->buffer().get(),
-                                  hardware_buffer);
-}
-
-// Retrieve the shared buffer layout version defined in dvr_shared_buffers.h.
-int dvrBufferGlobalLayoutVersionGet() {
-  return android::dvr::kSharedBufferLayoutVersion;
-}
-
-}  // extern "C"
diff --git a/libs/vr/libdvr/dvr_buffer_queue.cpp b/libs/vr/libdvr/dvr_buffer_queue.cpp
deleted file mode 100644
index 1ca653c..0000000
--- a/libs/vr/libdvr/dvr_buffer_queue.cpp
+++ /dev/null
@@ -1,552 +0,0 @@
-#include "include/dvr/dvr_api.h"
-#include "include/dvr/dvr_buffer_queue.h"
-
-#include <android/native_window.h>
-#include <gui/BufferHubProducer.h>
-
-#include "dvr_internal.h"
-#include "dvr_buffer_queue_internal.h"
-
-using namespace android;
-using android::dvr::BufferHubBase;
-using android::dvr::ConsumerBuffer;
-using android::dvr::ConsumerQueue;
-using android::dvr::ProducerBuffer;
-using android::dvr::ProducerQueue;
-using android::dvr::ProducerQueueConfigBuilder;
-using android::dvr::UsagePolicy;
-
-extern "C" {
-
-DvrWriteBufferQueue::DvrWriteBufferQueue(
-    const std::shared_ptr<ProducerQueue>& producer_queue)
-    : producer_queue_(producer_queue),
-      width_(producer_queue->default_width()),
-      height_(producer_queue->default_height()),
-      format_(producer_queue->default_format()) {}
-
-int DvrWriteBufferQueue::GetNativeWindow(ANativeWindow** out_window) {
-  if (native_window_ == nullptr) {
-    // Lazy creation of |native_window|, as not everyone is using
-    // DvrWriteBufferQueue as an external surface.
-    sp<IGraphicBufferProducer> gbp = BufferHubProducer::Create(producer_queue_);
-    native_window_ = new Surface(gbp, true);
-  }
-
-  *out_window = static_cast<ANativeWindow*>(native_window_.get());
-  return 0;
-}
-
-int DvrWriteBufferQueue::CreateReadQueue(DvrReadBufferQueue** out_read_queue) {
-  std::unique_ptr<ConsumerQueue> consumer_queue =
-      producer_queue_->CreateConsumerQueue();
-  if (consumer_queue == nullptr) {
-    ALOGE(
-        "DvrWriteBufferQueue::CreateReadQueue: Failed to create consumer queue "
-        "from producer queue: queue_id=%d.", producer_queue_->id());
-    return -ENOMEM;
-  }
-
-  *out_read_queue = new DvrReadBufferQueue(std::move(consumer_queue));
-  return 0;
-}
-
-int DvrWriteBufferQueue::Dequeue(int timeout, DvrWriteBuffer* write_buffer,
-                                 int* out_fence_fd) {
-  DvrNativeBufferMetadata meta;
-  DvrWriteBuffer* buffer = nullptr;
-  int fence_fd = -1;
-  if (const int ret = GainBuffer(timeout, &buffer, &meta, &fence_fd))
-    return ret;
-  if (!buffer)
-    return -ENOMEM;
-
-  write_buffers_[buffer->slot].reset(buffer);
-  write_buffer->write_buffer = std::move(buffer->write_buffer);
-  *out_fence_fd = fence_fd;
-  return 0;
-}
-
-int DvrWriteBufferQueue::GainBuffer(int timeout,
-                                    DvrWriteBuffer** out_write_buffer,
-                                    DvrNativeBufferMetadata* out_meta,
-                                    int* out_fence_fd) {
-  size_t slot;
-  pdx::LocalHandle release_fence;
-
-  // Need to retry N+1 times, where N is total number of buffers in the queue.
-  // As in the worst case, we will dequeue all N buffers and reallocate them, on
-  // the {N+1}th dequeue, we are guaranteed to get a buffer with new dimension.
-  size_t max_retries = 1 + producer_queue_->capacity();
-  size_t retry = 0;
-
-  for (; retry < max_retries; retry++) {
-    auto buffer_status =
-        producer_queue_->Dequeue(timeout, &slot, out_meta, &release_fence);
-    if (!buffer_status) {
-      ALOGE_IF(buffer_status.error() != ETIMEDOUT,
-               "DvrWriteBufferQueue::GainBuffer: Failed to dequeue buffer: %s",
-               buffer_status.GetErrorMessage().c_str());
-      return -buffer_status.error();
-    }
-
-    if (write_buffers_[slot] == nullptr) {
-      // Lazy initialization of a write_buffers_ slot. Note that a slot will
-      // only be dynamically allocated once during the entire cycle life of a
-      // queue.
-      write_buffers_[slot] = std::make_unique<DvrWriteBuffer>();
-      write_buffers_[slot]->slot = slot;
-    }
-
-    LOG_ALWAYS_FATAL_IF(
-        write_buffers_[slot]->write_buffer,
-        "DvrWriteBufferQueue::GainBuffer: Buffer slot is not empty: %zu", slot);
-    write_buffers_[slot]->write_buffer = std::move(buffer_status.take());
-
-    const auto& producer_buffer = write_buffers_[slot]->write_buffer;
-    if (!producer_buffer)
-      return -ENOMEM;
-
-    if (width_ == producer_buffer->width() &&
-        height_ == producer_buffer->height() &&
-        format_ == producer_buffer->format()) {
-      // Producer queue returns a buffer matches the current request.
-      break;
-    }
-
-    // Needs reallocation. Note that if there are already multiple available
-    // buffers in the queue, the next one returned from |queue_->Dequeue| may
-    // still have the old buffer dimension or format. Retry up to N+1 times or
-    // until we dequeued a buffer with new configuration.
-    ALOGD_IF(TRACE,
-             "DvrWriteBufferQueue::Dequeue: requested buffer at slot: %zu "
-             "(w=%u, h=%u, fmt=%u) is different from the buffer returned "
-             "(w=%u, h=%u, fmt=%u). Need re-allocation.",
-             slot, width_, height_, format_, producer_buffer->width(),
-             producer_buffer->height(), producer_buffer->format());
-
-    // Currently, we are not storing |layer_count| and |usage| in queue
-    // configuration. Copy those setup from the last buffer dequeued before we
-    // remove it.
-    uint32_t old_layer_count = producer_buffer->layer_count();
-    uint64_t old_usage = producer_buffer->usage();
-
-    // Allocate a new producer buffer with new buffer configs. Note that if
-    // there are already multiple available buffers in the queue, the next one
-    // returned from |queue_->Dequeue| may still have the old buffer dimension
-    // or format. Retry up to BufferHubQueue::kMaxQueueCapacity times or until
-    // we dequeued a buffer with new configuration.
-    auto remove_status = producer_queue_->RemoveBuffer(slot);
-    if (!remove_status) {
-      ALOGE("DvrWriteBufferQueue::Dequeue: Failed to remove buffer: %s",
-            remove_status.GetErrorMessage().c_str());
-      return -remove_status.error();
-    }
-    // Make sure that the previously allocated buffer is dereferenced from
-    // write_buffers_ array.
-    write_buffers_[slot]->write_buffer = nullptr;
-
-    auto allocate_status = producer_queue_->AllocateBuffer(
-        width_, height_, old_layer_count, format_, old_usage);
-    if (!allocate_status) {
-      ALOGE("DvrWriteBufferQueue::Dequeue: Failed to allocate buffer: %s",
-            allocate_status.GetErrorMessage().c_str());
-      return -allocate_status.error();
-    }
-  }
-
-  if (retry >= max_retries) {
-    ALOGE(
-        "DvrWriteBufferQueue::Dequeue: Failed to re-allocate buffer after "
-        "resizing.");
-    return -ENOMEM;
-  }
-
-  *out_write_buffer = write_buffers_[slot].release();
-  *out_fence_fd = release_fence.Release();
-
-  return 0;
-}
-
-int DvrWriteBufferQueue::PostBuffer(DvrWriteBuffer* write_buffer,
-                                    const DvrNativeBufferMetadata* meta,
-                                    int ready_fence_fd) {
-  // Some basic sanity checks before we put the buffer back into a slot.
-  size_t slot = static_cast<size_t>(write_buffer->slot);
-  LOG_FATAL_IF(
-      (write_buffers->slot < 0 || write_buffers->slot >= write_buffers_.size()),
-      "DvrWriteBufferQueue::ReleaseBuffer: Invalid slot: %zu", slot);
-
-  if (write_buffers_[slot] != nullptr) {
-    ALOGE("DvrWriteBufferQueue::PostBuffer: Slot is not empty: %zu", slot);
-    return -EINVAL;
-  }
-  if (write_buffer->write_buffer == nullptr) {
-    ALOGE("DvrWriteBufferQueue::PostBuffer: Invalid write buffer.");
-    return -EINVAL;
-  }
-  if (write_buffer->write_buffer->id() != producer_queue_->GetBufferId(slot)) {
-    ALOGE(
-        "DvrWriteBufferQueue::PostBuffer: Buffer to be posted does not "
-        "belong to this buffer queue. Posting buffer: id=%d, buffer in "
-        "queue: id=%d",
-        write_buffer->write_buffer->id(), producer_queue_->GetBufferId(slot));
-    return -EINVAL;
-  }
-
-  write_buffer->write_buffer->SetQueueIndex(next_post_index_++);
-  pdx::LocalHandle fence(ready_fence_fd);
-  const int ret = write_buffer->write_buffer->PostAsync(meta, fence);
-  if (ret < 0) {
-    ALOGE("DvrWriteBufferQueue::PostBuffer: Failed to post buffer, ret=%d",
-          ret);
-    return ret;
-  }
-
-  // Put the DvrWriteBuffer pointer back into its slot for reuse.
-  write_buffers_[slot].reset(write_buffer);
-  // It's import to reset the write buffer client now. It should stay invalid
-  // until next GainBuffer on the same slot.
-  write_buffers_[slot]->write_buffer = nullptr;
-  return 0;
-}
-
-int DvrWriteBufferQueue::ResizeBuffer(uint32_t width, uint32_t height) {
-  if (width == 0 || height == 0) {
-    ALOGE(
-        "DvrWriteBufferQueue::ResizeBuffer: invalid buffer dimension: w=%u, "
-        "h=%u.",
-        width, height);
-    return -EINVAL;
-  }
-
-  width_ = width;
-  height_ = height;
-  return 0;
-}
-
-int dvrWriteBufferQueueCreate(uint32_t width, uint32_t height, uint32_t format,
-                              uint32_t layer_count, uint64_t usage,
-                              size_t capacity, size_t metadata_size,
-                              DvrWriteBufferQueue** out_write_queue) {
-  if (!out_write_queue)
-    return -EINVAL;
-
-  auto config_builder = ProducerQueueConfigBuilder()
-                            .SetDefaultWidth(width)
-                            .SetDefaultHeight(height)
-                            .SetDefaultFormat(format)
-                            .SetMetadataSize(metadata_size);
-  std::unique_ptr<ProducerQueue> producer_queue =
-      ProducerQueue::Create(config_builder.Build(), UsagePolicy{});
-  if (!producer_queue) {
-    ALOGE("dvrWriteBufferQueueCreate: Failed to create producer queue.");
-    return -ENOMEM;
-  }
-
-  auto status = producer_queue->AllocateBuffers(width, height, layer_count,
-                                                format, usage, capacity);
-  if (!status.ok()) {
-    ALOGE("dvrWriteBufferQueueCreate: Failed to allocate buffers.");
-    return -ENOMEM;
-  }
-
-  *out_write_queue = new DvrWriteBufferQueue(std::move(producer_queue));
-  return 0;
-}
-
-void dvrWriteBufferQueueDestroy(DvrWriteBufferQueue* write_queue) {
-  delete write_queue;
-}
-
-ssize_t dvrWriteBufferQueueGetCapacity(DvrWriteBufferQueue* write_queue) {
-  if (!write_queue)
-    return -EINVAL;
-
-  return write_queue->capacity();
-}
-
-int dvrWriteBufferQueueGetId(DvrWriteBufferQueue* write_queue) {
-  if (!write_queue)
-    return -EINVAL;
-
-  return write_queue->id();
-}
-
-int dvrWriteBufferQueueGetANativeWindow(DvrWriteBufferQueue* write_queue,
-                                        ANativeWindow** out_window) {
-  if (!write_queue || !out_window)
-    return -EINVAL;
-
-  return write_queue->GetNativeWindow(out_window);
-}
-
-int dvrWriteBufferQueueCreateReadQueue(DvrWriteBufferQueue* write_queue,
-                                       DvrReadBufferQueue** out_read_queue) {
-  if (!write_queue || !out_read_queue)
-    return -EINVAL;
-
-  return write_queue->CreateReadQueue(out_read_queue);
-}
-
-int dvrWriteBufferQueueGainBuffer(DvrWriteBufferQueue* write_queue, int timeout,
-                                  DvrWriteBuffer** out_write_buffer,
-                                  DvrNativeBufferMetadata* out_meta,
-                                  int* out_fence_fd) {
-  if (!write_queue || !out_write_buffer || !out_meta || !out_fence_fd)
-    return -EINVAL;
-
-  return write_queue->GainBuffer(timeout, out_write_buffer, out_meta,
-                                 out_fence_fd);
-}
-
-int dvrWriteBufferQueuePostBuffer(DvrWriteBufferQueue* write_queue,
-                                  DvrWriteBuffer* write_buffer,
-                                  const DvrNativeBufferMetadata* meta,
-                                  int ready_fence_fd) {
-  if (!write_queue || !write_buffer || !write_buffer->write_buffer || !meta)
-    return -EINVAL;
-
-  return write_queue->PostBuffer(write_buffer, meta, ready_fence_fd);
-}
-
-int dvrWriteBufferQueueResizeBuffer(DvrWriteBufferQueue* write_queue,
-                                    uint32_t width, uint32_t height) {
-  if (!write_queue)
-    return -EINVAL;
-
-  return write_queue->ResizeBuffer(width, height);
-}
-
-// ReadBufferQueue
-
-DvrReadBufferQueue::DvrReadBufferQueue(
-    const std::shared_ptr<ConsumerQueue>& consumer_queue)
-    : consumer_queue_(consumer_queue) {}
-
-int DvrReadBufferQueue::CreateReadQueue(DvrReadBufferQueue** out_read_queue) {
-  std::unique_ptr<ConsumerQueue> consumer_queue =
-      consumer_queue_->CreateConsumerQueue();
-  if (consumer_queue == nullptr) {
-    ALOGE(
-        "DvrReadBufferQueue::CreateReadQueue: Failed to create consumer queue "
-        "from producer queue: queue_id=%d.", consumer_queue_->id());
-    return -ENOMEM;
-  }
-
-  *out_read_queue = new DvrReadBufferQueue(std::move(consumer_queue));
-  return 0;
-}
-
-int DvrReadBufferQueue::AcquireBuffer(int timeout,
-                                      DvrReadBuffer** out_read_buffer,
-                                      DvrNativeBufferMetadata* out_meta,
-                                      int* out_fence_fd) {
-  size_t slot;
-  pdx::LocalHandle acquire_fence;
-  auto buffer_status =
-      consumer_queue_->Dequeue(timeout, &slot, out_meta, &acquire_fence);
-  if (!buffer_status) {
-    ALOGE_IF(buffer_status.error() != ETIMEDOUT,
-             "DvrReadBufferQueue::AcquireBuffer: Failed to dequeue buffer: %s",
-             buffer_status.GetErrorMessage().c_str());
-    return -buffer_status.error();
-  }
-
-  if (read_buffers_[slot] == nullptr) {
-    // Lazy initialization of a read_buffers_ slot. Note that a slot will only
-    // be dynamically allocated once during the entire cycle life of a queue.
-    read_buffers_[slot] = std::make_unique<DvrReadBuffer>();
-    read_buffers_[slot]->slot = slot;
-  }
-
-  LOG_FATAL_IF(
-      read_buffers_[slot]->read_buffer,
-      "DvrReadBufferQueue::AcquireBuffer: Buffer slot is not empty: %zu", slot);
-  read_buffers_[slot]->read_buffer = std::move(buffer_status.take());
-
-  *out_read_buffer = read_buffers_[slot].release();
-  *out_fence_fd = acquire_fence.Release();
-
-  return 0;
-}
-
-int DvrReadBufferQueue::ReleaseBuffer(DvrReadBuffer* read_buffer,
-                                      const DvrNativeBufferMetadata* meta,
-                                      int release_fence_fd) {
-  // Some basic sanity checks before we put the buffer back into a slot.
-  size_t slot = static_cast<size_t>(read_buffer->slot);
-  LOG_FATAL_IF(
-      (read_buffers->slot < 0 || read_buffers->slot >= read_buffers_size()),
-      "DvrReadBufferQueue::ReleaseBuffer: Invalid slot: %zu", slot);
-
-  if (read_buffers_[slot] != nullptr) {
-    ALOGE("DvrReadBufferQueue::ReleaseBuffer: Slot is not empty: %zu", slot);
-    return -EINVAL;
-  }
-  if (read_buffer->read_buffer == nullptr) {
-    ALOGE("DvrReadBufferQueue::ReleaseBuffer: Invalid read buffer.");
-    return -EINVAL;
-  }
-  if (read_buffer->read_buffer->id() != consumer_queue_->GetBufferId(slot)) {
-    if (consumer_queue_->GetBufferId(slot) > 0) {
-      ALOGE(
-          "DvrReadBufferQueue::ReleaseBuffer: Buffer to be released may not "
-          "belong to this queue (queue_id=%d): attempting to release buffer "
-          "(buffer_id=%d) at slot %d which holds a different buffer "
-          "(buffer_id=%d).",
-          consumer_queue_->id(), read_buffer->read_buffer->id(),
-          static_cast<int>(slot), consumer_queue_->GetBufferId(slot));
-    } else {
-      ALOGI(
-          "DvrReadBufferQueue::ReleaseBuffer: Buffer to be released may not "
-          "belong to this queue (queue_id=%d): attempting to release buffer "
-          "(buffer_id=%d) at slot %d which is empty.",
-          consumer_queue_->id(), read_buffer->read_buffer->id(),
-          static_cast<int>(slot));
-    }
-  }
-
-  pdx::LocalHandle fence(release_fence_fd);
-  int ret = read_buffer->read_buffer->ReleaseAsync(meta, fence);
-  if (ret < 0) {
-    ALOGE("DvrReadBufferQueue::ReleaseBuffer: Failed to release buffer, ret=%d",
-          ret);
-    return ret;
-  }
-
-  // Put the DvrReadBuffer pointer back into its slot for reuse.
-  read_buffers_[slot].reset(read_buffer);
-  // It's import to reset the read buffer client now. It should stay invalid
-  // until next AcquireBuffer on the same slot.
-  read_buffers_[slot]->read_buffer = nullptr;
-  return 0;
-}
-
-void DvrReadBufferQueue::SetBufferAvailableCallback(
-    DvrReadBufferQueueBufferAvailableCallback callback, void* context) {
-  if (callback == nullptr) {
-    consumer_queue_->SetBufferAvailableCallback(nullptr);
-  } else {
-    consumer_queue_->SetBufferAvailableCallback(
-        [callback, context]() { callback(context); });
-  }
-}
-
-void DvrReadBufferQueue::SetBufferRemovedCallback(
-    DvrReadBufferQueueBufferRemovedCallback callback, void* context) {
-  if (callback == nullptr) {
-    consumer_queue_->SetBufferRemovedCallback(nullptr);
-  } else {
-    consumer_queue_->SetBufferRemovedCallback(
-        [callback, context](const std::shared_ptr<BufferHubBase>& buffer) {
-          // When buffer is removed from the queue, the slot is already invalid.
-          auto read_buffer = std::make_unique<DvrReadBuffer>();
-          read_buffer->read_buffer =
-              std::static_pointer_cast<ConsumerBuffer>(buffer);
-          callback(read_buffer.release(), context);
-        });
-  }
-}
-
-int DvrReadBufferQueue::HandleEvents() {
-  // TODO(jwcai) Probably should change HandleQueueEvents to return Status.
-  consumer_queue_->HandleQueueEvents();
-  return 0;
-}
-
-void dvrReadBufferQueueDestroy(DvrReadBufferQueue* read_queue) {
-  delete read_queue;
-}
-
-ssize_t dvrReadBufferQueueGetCapacity(DvrReadBufferQueue* read_queue) {
-  if (!read_queue)
-    return -EINVAL;
-
-  return read_queue->capacity();
-}
-
-int dvrReadBufferQueueGetId(DvrReadBufferQueue* read_queue) {
-  if (!read_queue)
-    return -EINVAL;
-
-  return read_queue->id();
-}
-
-int dvrReadBufferQueueGetEventFd(DvrReadBufferQueue* read_queue) {
-  if (!read_queue)
-    return -EINVAL;
-
-  return read_queue->event_fd();
-}
-
-int dvrReadBufferQueueCreateReadQueue(DvrReadBufferQueue* read_queue,
-                                      DvrReadBufferQueue** out_read_queue) {
-  if (!read_queue || !out_read_queue)
-    return -EINVAL;
-
-  return read_queue->CreateReadQueue(out_read_queue);
-}
-
-int dvrReadBufferQueueDequeue(DvrReadBufferQueue* read_queue, int timeout,
-                              DvrReadBuffer* read_buffer, int* out_fence_fd,
-                              void* out_meta, size_t meta_size_bytes) {
-  if (!read_queue || !read_buffer || !out_fence_fd)
-    return -EINVAL;
-
-  if (meta_size_bytes != 0 && !out_meta)
-    return -EINVAL;
-
-  return read_queue->Dequeue(timeout, read_buffer, out_fence_fd, out_meta,
-                             meta_size_bytes);
-}
-
-int dvrReadBufferQueueAcquireBuffer(DvrReadBufferQueue* read_queue, int timeout,
-                                    DvrReadBuffer** out_read_buffer,
-                                    DvrNativeBufferMetadata* out_meta,
-                                    int* out_fence_fd) {
-  if (!read_queue || !out_read_buffer || !out_meta || !out_fence_fd)
-    return -EINVAL;
-
-  return read_queue->AcquireBuffer(timeout, out_read_buffer, out_meta,
-                                   out_fence_fd);
-}
-
-int dvrReadBufferQueueReleaseBuffer(DvrReadBufferQueue* read_queue,
-                                    DvrReadBuffer* read_buffer,
-                                    const DvrNativeBufferMetadata* meta,
-                                    int release_fence_fd) {
-  if (!read_queue || !read_buffer || !read_buffer->read_buffer || !meta)
-    return -EINVAL;
-
-  return read_queue->ReleaseBuffer(read_buffer, meta, release_fence_fd);
-}
-
-int dvrReadBufferQueueSetBufferAvailableCallback(
-    DvrReadBufferQueue* read_queue,
-    DvrReadBufferQueueBufferAvailableCallback callback, void* context) {
-  if (!read_queue)
-    return -EINVAL;
-
-  read_queue->SetBufferAvailableCallback(callback, context);
-  return 0;
-}
-
-int dvrReadBufferQueueSetBufferRemovedCallback(
-    DvrReadBufferQueue* read_queue,
-    DvrReadBufferQueueBufferRemovedCallback callback, void* context) {
-  if (!read_queue)
-    return -EINVAL;
-
-  read_queue->SetBufferRemovedCallback(callback, context);
-  return 0;
-}
-
-int dvrReadBufferQueueHandleEvents(DvrReadBufferQueue* read_queue) {
-  if (!read_queue)
-    return -EINVAL;
-
-  return read_queue->HandleEvents();
-}
-
-}  // extern "C"
diff --git a/libs/vr/libdvr/dvr_buffer_queue_internal.h b/libs/vr/libdvr/dvr_buffer_queue_internal.h
deleted file mode 100644
index e53a686..0000000
--- a/libs/vr/libdvr/dvr_buffer_queue_internal.h
+++ /dev/null
@@ -1,95 +0,0 @@
-#ifndef ANDROID_DVR_BUFFER_QUEUE_INTERNAL_H_
-#define ANDROID_DVR_BUFFER_QUEUE_INTERNAL_H_
-
-#include <gui/Surface.h>
-#include <private/dvr/buffer_hub_queue_client.h>
-#include <sys/cdefs.h>
-
-#include <array>
-#include <memory>
-
-#include "dvr_internal.h"
-
-struct ANativeWindow;
-
-typedef struct DvrNativeBufferMetadata DvrNativeBufferMetadata;
-typedef struct DvrReadBuffer DvrReadBuffer;
-typedef struct DvrReadBufferQueue DvrReadBufferQueue;
-typedef struct DvrWriteBuffer DvrWriteBuffer;
-typedef void (*DvrReadBufferQueueBufferAvailableCallback)(void* context);
-typedef void (*DvrReadBufferQueueBufferRemovedCallback)(DvrReadBuffer* buffer,
-                                                        void* context);
-
-struct DvrWriteBufferQueue {
-  using BufferHubQueue = android::dvr::BufferHubQueue;
-  using ProducerQueue = android::dvr::ProducerQueue;
-
-  // Create a concrete object for DvrWriteBufferQueue.
-  //
-  // @param producer_queue The BufferHub's ProducerQueue that is used to back
-  //     this DvrWriteBufferQueue, must not be NULL.
-  explicit DvrWriteBufferQueue(
-      const std::shared_ptr<ProducerQueue>& producer_queue);
-
-  int id() const { return producer_queue_->id(); }
-  uint32_t width() const { return width_; };
-  uint32_t height() const { return height_; };
-  uint32_t format() const { return format_; };
-  size_t capacity() const { return producer_queue_->capacity(); }
-  const std::shared_ptr<ProducerQueue>& producer_queue() const {
-    return producer_queue_;
-  }
-
-  int GetNativeWindow(ANativeWindow** out_window);
-  int CreateReadQueue(DvrReadBufferQueue** out_read_queue);
-  int Dequeue(int timeout, DvrWriteBuffer* write_buffer, int* out_fence_fd);
-  int GainBuffer(int timeout, DvrWriteBuffer** out_write_buffer,
-                 DvrNativeBufferMetadata* out_meta, int* out_fence_fd);
-  int PostBuffer(DvrWriteBuffer* write_buffer,
-                 const DvrNativeBufferMetadata* meta, int ready_fence_fd);
-  int ResizeBuffer(uint32_t width, uint32_t height);
-
- private:
-  std::shared_ptr<ProducerQueue> producer_queue_;
-  std::array<std::unique_ptr<DvrWriteBuffer>, BufferHubQueue::kMaxQueueCapacity>
-      write_buffers_;
-
-  int64_t next_post_index_ = 0;
-  uint32_t width_;
-  uint32_t height_;
-  uint32_t format_;
-
-  android::sp<android::Surface> native_window_;
-};
-
-struct DvrReadBufferQueue {
-  using BufferHubQueue = android::dvr::BufferHubQueue;
-  using ConsumerQueue = android::dvr::ConsumerQueue;
-
-  explicit DvrReadBufferQueue(
-      const std::shared_ptr<ConsumerQueue>& consumer_queue);
-
-  int id() const { return consumer_queue_->id(); }
-  int event_fd() const { return consumer_queue_->queue_fd(); }
-  size_t capacity() const { return consumer_queue_->capacity(); }
-
-  int CreateReadQueue(DvrReadBufferQueue** out_read_queue);
-  int Dequeue(int timeout, DvrReadBuffer* read_buffer, int* out_fence_fd,
-              void* out_meta, size_t user_metadata_size);
-  int AcquireBuffer(int timeout, DvrReadBuffer** out_read_buffer,
-                    DvrNativeBufferMetadata* out_meta, int* out_fence_fd);
-  int ReleaseBuffer(DvrReadBuffer* read_buffer,
-                    const DvrNativeBufferMetadata* meta, int release_fence_fd);
-  void SetBufferAvailableCallback(
-      DvrReadBufferQueueBufferAvailableCallback callback, void* context);
-  void SetBufferRemovedCallback(
-      DvrReadBufferQueueBufferRemovedCallback callback, void* context);
-  int HandleEvents();
-
- private:
-  std::shared_ptr<ConsumerQueue> consumer_queue_;
-  std::array<std::unique_ptr<DvrReadBuffer>, BufferHubQueue::kMaxQueueCapacity>
-      read_buffers_;
-};
-
-#endif  // ANDROID_DVR_BUFFER_QUEUE_INTERNAL_H_
diff --git a/libs/vr/libdvr/dvr_configuration_data.cpp b/libs/vr/libdvr/dvr_configuration_data.cpp
deleted file mode 100644
index df0d54e..0000000
--- a/libs/vr/libdvr/dvr_configuration_data.cpp
+++ /dev/null
@@ -1,40 +0,0 @@
-#include "include/dvr/dvr_configuration_data.h"
-
-#include <private/dvr/display_client.h>
-
-using android::dvr::display::ConfigFileType;
-using android::dvr::display::DisplayClient;
-
-extern "C" {
-
-int dvrConfigurationDataGet(int config_type, uint8_t** data,
-                            size_t* data_size) {
-  if (!data || !data_size) {
-    return -EINVAL;
-  }
-
-  auto client = DisplayClient::Create();
-  if (!client) {
-    ALOGE("dvrGetGlobalBuffer: Failed to create display client!");
-    return -ECOMM;
-  }
-
-  ConfigFileType config_file_type = static_cast<ConfigFileType>(config_type);
-  auto config_data_status =
-      client->GetConfigurationData(config_file_type);
-
-  if (!config_data_status) {
-    return -config_data_status.error();
-  }
-
-  *data_size = config_data_status.get().size();
-  *data = new uint8_t[*data_size];
-  std::copy_n(config_data_status.get().begin(), *data_size, *data);
-  return 0;
-}
-
-void dvrConfigurationDataDestroy(uint8_t* data) {
-  delete[] data;
-}
-
-}  // extern "C"
diff --git a/libs/vr/libdvr/dvr_display_manager.cpp b/libs/vr/libdvr/dvr_display_manager.cpp
deleted file mode 100644
index 7f631e3..0000000
--- a/libs/vr/libdvr/dvr_display_manager.cpp
+++ /dev/null
@@ -1,283 +0,0 @@
-#include "include/dvr/dvr_display_manager.h"
-
-#include <dvr/dvr_buffer.h>
-#include <pdx/rpc/variant.h>
-#include <private/dvr/buffer_hub_queue_client.h>
-#include <private/dvr/consumer_buffer.h>
-#include <private/dvr/display_client.h>
-#include <private/dvr/display_manager_client.h>
-
-#include "dvr_internal.h"
-#include "dvr_buffer_queue_internal.h"
-
-using android::dvr::ConsumerBuffer;
-using android::dvr::display::DisplayManagerClient;
-using android::dvr::display::SurfaceAttribute;
-using android::dvr::display::SurfaceAttributes;
-using android::dvr::display::SurfaceState;
-using android::pdx::rpc::EmptyVariant;
-
-namespace {
-
-// Extracts type and value from the attribute Variant and writes them into the
-// respective fields of DvrSurfaceAttribute.
-struct AttributeVisitor {
-  DvrSurfaceAttribute* attribute;
-
-  void operator()(int32_t value) {
-    attribute->value.int32_value = value;
-    attribute->value.type = DVR_SURFACE_ATTRIBUTE_TYPE_INT32;
-  }
-  void operator()(int64_t value) {
-    attribute->value.int64_value = value;
-    attribute->value.type = DVR_SURFACE_ATTRIBUTE_TYPE_INT64;
-  }
-  void operator()(bool value) {
-    attribute->value.bool_value = value;
-    attribute->value.type = DVR_SURFACE_ATTRIBUTE_TYPE_BOOL;
-  }
-  void operator()(float value) {
-    attribute->value.float_value = value;
-    attribute->value.type = DVR_SURFACE_ATTRIBUTE_TYPE_FLOAT;
-  }
-  void operator()(const std::array<float, 2>& value) {
-    std::copy(value.cbegin(), value.cend(), attribute->value.float2_value);
-    attribute->value.type = DVR_SURFACE_ATTRIBUTE_TYPE_FLOAT2;
-  }
-  void operator()(const std::array<float, 3>& value) {
-    std::copy(value.cbegin(), value.cend(), attribute->value.float3_value);
-    attribute->value.type = DVR_SURFACE_ATTRIBUTE_TYPE_FLOAT3;
-  }
-  void operator()(const std::array<float, 4>& value) {
-    std::copy(value.cbegin(), value.cend(), attribute->value.float4_value);
-    attribute->value.type = DVR_SURFACE_ATTRIBUTE_TYPE_FLOAT4;
-  }
-  void operator()(const std::array<float, 8>& value) {
-    std::copy(value.cbegin(), value.cend(), attribute->value.float8_value);
-    attribute->value.type = DVR_SURFACE_ATTRIBUTE_TYPE_FLOAT8;
-  }
-  void operator()(const std::array<float, 16>& value) {
-    std::copy(value.cbegin(), value.cend(), attribute->value.float16_value);
-    attribute->value.type = DVR_SURFACE_ATTRIBUTE_TYPE_FLOAT16;
-  }
-  void operator()(EmptyVariant) {
-    attribute->value.type = DVR_SURFACE_ATTRIBUTE_TYPE_NONE;
-  }
-};
-
-size_t ConvertSurfaceAttributes(const SurfaceAttributes& surface_attributes,
-                                DvrSurfaceAttribute* attributes,
-                                size_t max_count) {
-  size_t count = 0;
-  for (const auto& attribute : surface_attributes) {
-    if (count >= max_count)
-      break;
-
-    // Copy the key and extract the Variant value using a visitor.
-    attributes[count].key = attribute.first;
-    attribute.second.Visit(AttributeVisitor{&attributes[count]});
-    count++;
-  }
-
-  return count;
-}
-
-}  // anonymous namespace
-
-extern "C" {
-
-struct DvrDisplayManager {
-  std::unique_ptr<DisplayManagerClient> client;
-};
-
-struct DvrSurfaceState {
-  std::vector<SurfaceState> state;
-};
-
-int dvrDisplayManagerCreate(DvrDisplayManager** client_out) {
-  if (!client_out)
-    return -EINVAL;
-
-  auto client = DisplayManagerClient::Create();
-  if (!client) {
-    ALOGE("dvrDisplayManagerCreate: Failed to create display manager client!");
-    return -EIO;
-  }
-
-  *client_out = new DvrDisplayManager{std::move(client)};
-  return 0;
-}
-
-void dvrDisplayManagerDestroy(DvrDisplayManager* client) { delete client; }
-
-int dvrDisplayManagerGetEventFd(DvrDisplayManager* client) {
-  if (!client)
-    return -EINVAL;
-
-  return client->client->event_fd();
-}
-
-int dvrDisplayManagerTranslateEpollEventMask(DvrDisplayManager* client,
-                                             int in_events, int* out_events) {
-  if (!client || !out_events)
-    return -EINVAL;
-
-  auto status = client->client->GetEventMask(in_events);
-  if (!status)
-    return -status.error();
-
-  *out_events = status.get();
-  return 0;
-}
-
-int dvrDisplayManagerGetSurfaceState(DvrDisplayManager* client,
-                                     DvrSurfaceState* state) {
-  if (!client || !state)
-    return -EINVAL;
-
-  auto status = client->client->GetSurfaceState();
-  if (!status)
-    return -status.error();
-
-  state->state = status.take();
-  return 0;
-}
-
-int dvrDisplayManagerGetReadBufferQueue(DvrDisplayManager* client,
-                                        int surface_id, int queue_id,
-                                        DvrReadBufferQueue** queue_out) {
-  if (!client || !queue_out)
-    return -EINVAL;
-
-  auto status = client->client->GetSurfaceQueue(surface_id, queue_id);
-  if (!status) {
-    ALOGE("dvrDisplayManagerGetReadBufferQueue: Failed to get queue: %s",
-          status.GetErrorMessage().c_str());
-    return -status.error();
-  }
-
-  *queue_out = new DvrReadBufferQueue(status.take());
-  return 0;
-}
-
-int dvrSurfaceStateCreate(DvrSurfaceState** surface_state_out) {
-  if (!surface_state_out)
-    return -EINVAL;
-
-  *surface_state_out = new DvrSurfaceState{};
-  return 0;
-}
-
-void dvrSurfaceStateDestroy(DvrSurfaceState* surface_state) {
-  delete surface_state;
-}
-
-int dvrSurfaceStateGetSurfaceCount(DvrSurfaceState* surface_state,
-                                   size_t* count_out) {
-  if (!surface_state)
-    return -EINVAL;
-
-  *count_out = surface_state->state.size();
-  return 0;
-}
-
-int dvrSurfaceStateGetUpdateFlags(DvrSurfaceState* surface_state,
-                                  size_t surface_index,
-                                  DvrSurfaceUpdateFlags* flags_out) {
-  if (!surface_state || surface_index >= surface_state->state.size())
-    return -EINVAL;
-
-  *flags_out = surface_state->state[surface_index].update_flags;
-  return 0;
-}
-
-int dvrSurfaceStateGetSurfaceId(DvrSurfaceState* surface_state,
-                                size_t surface_index, int* surface_id_out) {
-  if (!surface_state || surface_index >= surface_state->state.size())
-    return -EINVAL;
-
-  *surface_id_out = surface_state->state[surface_index].surface_id;
-  return 0;
-}
-
-int dvrSurfaceStateGetProcessId(DvrSurfaceState* surface_state,
-                                size_t surface_index, int* process_id_out) {
-  if (!surface_state || surface_index >= surface_state->state.size())
-    return -EINVAL;
-
-  *process_id_out = surface_state->state[surface_index].process_id;
-  return 0;
-}
-
-int dvrSurfaceStateGetQueueCount(DvrSurfaceState* surface_state,
-                                 size_t surface_index, size_t* count_out) {
-  if (!surface_state || surface_index >= surface_state->state.size())
-    return -EINVAL;
-
-  *count_out = surface_state->state[surface_index].queue_ids.size();
-  return 0;
-}
-
-ssize_t dvrSurfaceStateGetQueueIds(DvrSurfaceState* surface_state,
-                                   size_t surface_index, int* queue_ids,
-                                   size_t max_count) {
-  if (!surface_state || surface_index >= surface_state->state.size())
-    return -EINVAL;
-
-  size_t i;
-  const auto& state = surface_state->state[surface_index];
-  for (i = 0; i < std::min(max_count, state.queue_ids.size()); i++) {
-    queue_ids[i] = state.queue_ids[i];
-  }
-
-  return i;
-}
-
-int dvrSurfaceStateGetZOrder(DvrSurfaceState* surface_state,
-                             size_t surface_index, int* z_order_out) {
-  if (!surface_state || surface_index >= surface_state->state.size() ||
-      !z_order_out) {
-    return -EINVAL;
-  }
-
-  *z_order_out = surface_state->state[surface_index].GetZOrder();
-  return 0;
-}
-
-int dvrSurfaceStateGetVisible(DvrSurfaceState* surface_state,
-                              size_t surface_index, bool* visible_out) {
-  if (!surface_state || surface_index >= surface_state->state.size() ||
-      !visible_out) {
-    return -EINVAL;
-  }
-
-  *visible_out = surface_state->state[surface_index].GetVisible();
-  return 0;
-}
-
-int dvrSurfaceStateGetAttributeCount(DvrSurfaceState* surface_state,
-                                     size_t surface_index, size_t* count_out) {
-  if (!surface_state || surface_index >= surface_state->state.size() ||
-      !count_out) {
-    return -EINVAL;
-  }
-
-  *count_out = surface_state->state[surface_index].surface_attributes.size();
-  return 0;
-}
-
-ssize_t dvrSurfaceStateGetAttributes(DvrSurfaceState* surface_state,
-                                     size_t surface_index,
-                                     DvrSurfaceAttribute* attributes,
-                                     size_t max_count) {
-  if (!surface_state || surface_index >= surface_state->state.size() ||
-      !attributes) {
-    return -EINVAL;
-  }
-
-  return ConvertSurfaceAttributes(
-      surface_state->state[surface_index].surface_attributes, attributes,
-      max_count);
-}
-
-}  // extern "C"
diff --git a/libs/vr/libdvr/dvr_hardware_composer_client.cpp b/libs/vr/libdvr/dvr_hardware_composer_client.cpp
deleted file mode 100644
index 4e87cf6..0000000
--- a/libs/vr/libdvr/dvr_hardware_composer_client.cpp
+++ /dev/null
@@ -1,267 +0,0 @@
-#include "include/dvr/dvr_hardware_composer_client.h"
-
-#include <android/dvr/IVrComposer.h>
-#include <android/dvr/BnVrComposerCallback.h>
-#include <android/hardware_buffer.h>
-#include <binder/IServiceManager.h>
-#include <private/android/AHardwareBufferHelpers.h>
-
-#include <functional>
-#include <memory>
-#include <mutex>
-
-struct DvrHwcFrame {
-  android::dvr::ComposerView::Frame frame;
-};
-
-namespace {
-
-class HwcCallback : public android::dvr::BnVrComposerCallback {
- public:
-  using CallbackFunction = std::function<int(DvrHwcFrame*)>;
-
-  explicit HwcCallback(const CallbackFunction& callback);
-  ~HwcCallback() override;
-
-  // Reset the callback. This needs to be done early to avoid use after free
-  // accesses from binder thread callbacks.
-  void Shutdown();
-
-  std::unique_ptr<DvrHwcFrame> DequeueFrame();
-
- private:
-  // android::dvr::BnVrComposerCallback:
-  android::binder::Status onNewFrame(
-      const android::dvr::ParcelableComposerFrame& frame,
-      android::dvr::ParcelableUniqueFd* fence) override;
-
-  // Protects the |callback_| from uses from multiple threads. During shutdown
-  // there may be in-flight frame update events. In those cases the callback
-  // access needs to be protected otherwise binder threads may access an invalid
-  // callback.
-  std::mutex mutex_;
-  CallbackFunction callback_;
-
-  HwcCallback(const HwcCallback&) = delete;
-  void operator=(const HwcCallback&) = delete;
-};
-
-HwcCallback::HwcCallback(const CallbackFunction& callback)
-    : callback_(callback) {}
-
-HwcCallback::~HwcCallback() {}
-
-void HwcCallback::Shutdown() {
-  std::lock_guard<std::mutex> guard(mutex_);
-  callback_ = nullptr;
-}
-
-android::binder::Status HwcCallback::onNewFrame(
-    const android::dvr::ParcelableComposerFrame& frame,
-    android::dvr::ParcelableUniqueFd* fence) {
-  std::lock_guard<std::mutex> guard(mutex_);
-
-  if (!callback_) {
-    fence->set_fence(android::base::unique_fd());
-    return android::binder::Status::ok();
-  }
-
-  std::unique_ptr<DvrHwcFrame> dvr_frame(new DvrHwcFrame());
-  dvr_frame->frame = frame.frame();
-
-  fence->set_fence(android::base::unique_fd(callback_(dvr_frame.release())));
-  return android::binder::Status::ok();
-}
-
-}  // namespace
-
-struct DvrHwcClient {
-  android::sp<android::dvr::IVrComposer> composer;
-  android::sp<HwcCallback> callback;
-};
-
-DvrHwcClient* dvrHwcClientCreate(DvrHwcOnFrameCallback callback, void* data) {
-  std::unique_ptr<DvrHwcClient> client(new DvrHwcClient());
-
-  android::sp<android::IServiceManager> sm(android::defaultServiceManager());
-  client->composer = android::interface_cast<android::dvr::IVrComposer>(
-      sm->getService(android::dvr::IVrComposer::SERVICE_NAME()));
-  if (!client->composer.get())
-    return nullptr;
-
-  client->callback = new HwcCallback(std::bind(callback, data,
-                                               std::placeholders::_1));
-  android::binder::Status status = client->composer->registerObserver(
-      client->callback);
-  if (!status.isOk())
-    return nullptr;
-
-  return client.release();
-}
-
-void dvrHwcClientDestroy(DvrHwcClient* client) {
-  client->composer->clearObserver();
-
-  // NOTE: Deleting DvrHwcClient* isn't enough since DvrHwcClient::callback is a
-  // shared pointer that could be referenced from a binder thread. But the
-  // client callback isn't valid past this calls so that needs to be reset.
-  client->callback->Shutdown();
-
-  delete client;
-}
-
-void dvrHwcFrameDestroy(DvrHwcFrame* frame) {
-  delete frame;
-}
-
-DvrHwcDisplay dvrHwcFrameGetDisplayId(DvrHwcFrame* frame) {
-  return frame->frame.display_id;
-}
-
-int32_t dvrHwcFrameGetDisplayWidth(DvrHwcFrame* frame) {
-  return frame->frame.display_width;
-}
-
-int32_t dvrHwcFrameGetDisplayHeight(DvrHwcFrame* frame) {
-  return frame->frame.display_height;
-}
-
-bool dvrHwcFrameGetDisplayRemoved(DvrHwcFrame* frame) {
-  return frame->frame.removed;
-}
-
-size_t dvrHwcFrameGetLayerCount(DvrHwcFrame* frame) {
-  return frame->frame.layers.size();
-}
-
-uint32_t dvrHwcFrameGetActiveConfig(DvrHwcFrame* frame) {
-  return static_cast<uint32_t>(frame->frame.active_config);
-}
-
-uint32_t dvrHwcFrameGetColorMode(DvrHwcFrame* frame) {
-  return static_cast<uint32_t>(frame->frame.color_mode);
-}
-
-void dvrHwcFrameGetColorTransform(DvrHwcFrame* frame, float* out_matrix,
-                                  int32_t* out_hint) {
-  *out_hint = frame->frame.color_transform_hint;
-  memcpy(out_matrix, frame->frame.color_transform,
-         sizeof(frame->frame.color_transform));
-}
-
-uint32_t dvrHwcFrameGetPowerMode(DvrHwcFrame* frame) {
-  return static_cast<uint32_t>(frame->frame.power_mode);
-}
-
-uint32_t dvrHwcFrameGetVsyncEnabled(DvrHwcFrame* frame) {
-  return static_cast<uint32_t>(frame->frame.vsync_enabled);
-}
-
-DvrHwcLayer dvrHwcFrameGetLayerId(DvrHwcFrame* frame, size_t layer_index) {
-  return frame->frame.layers[layer_index].id;
-}
-
-AHardwareBuffer* dvrHwcFrameGetLayerBuffer(DvrHwcFrame* frame,
-                                           size_t layer_index) {
-  AHardwareBuffer* buffer = android::AHardwareBuffer_from_GraphicBuffer(
-      frame->frame.layers[layer_index].buffer.get());
-  AHardwareBuffer_acquire(buffer);
-  return buffer;
-}
-
-int dvrHwcFrameGetLayerFence(DvrHwcFrame* frame, size_t layer_index) {
-  return frame->frame.layers[layer_index].fence->dup();
-}
-
-DvrHwcRecti dvrHwcFrameGetLayerDisplayFrame(DvrHwcFrame* frame,
-                                            size_t layer_index) {
-  return DvrHwcRecti{
-    frame->frame.layers[layer_index].display_frame.left,
-    frame->frame.layers[layer_index].display_frame.top,
-    frame->frame.layers[layer_index].display_frame.right,
-    frame->frame.layers[layer_index].display_frame.bottom,
-  };
-}
-
-DvrHwcRectf dvrHwcFrameGetLayerCrop(DvrHwcFrame* frame, size_t layer_index) {
-  return DvrHwcRectf{
-    frame->frame.layers[layer_index].crop.left,
-    frame->frame.layers[layer_index].crop.top,
-    frame->frame.layers[layer_index].crop.right,
-    frame->frame.layers[layer_index].crop.bottom,
-  };
-}
-
-DvrHwcBlendMode dvrHwcFrameGetLayerBlendMode(DvrHwcFrame* frame,
-                                             size_t layer_index) {
-  return static_cast<DvrHwcBlendMode>(
-      frame->frame.layers[layer_index].blend_mode);
-}
-
-float dvrHwcFrameGetLayerAlpha(DvrHwcFrame* frame, size_t layer_index) {
-  return frame->frame.layers[layer_index].alpha;
-}
-
-uint32_t dvrHwcFrameGetLayerType(DvrHwcFrame* frame, size_t layer_index) {
-  return frame->frame.layers[layer_index].type;
-}
-
-uint32_t dvrHwcFrameGetLayerApplicationId(DvrHwcFrame* frame,
-                                          size_t layer_index) {
-  return frame->frame.layers[layer_index].app_id;
-}
-
-uint32_t dvrHwcFrameGetLayerZOrder(DvrHwcFrame* frame, size_t layer_index) {
-  return frame->frame.layers[layer_index].z_order;
-}
-
-void dvrHwcFrameGetLayerCursor(DvrHwcFrame* frame, size_t layer_index,
-                               int32_t* out_x, int32_t* out_y) {
-  *out_x = frame->frame.layers[layer_index].cursor_x;
-  *out_y = frame->frame.layers[layer_index].cursor_y;
-}
-
-uint32_t dvrHwcFrameGetLayerTransform(DvrHwcFrame* frame, size_t layer_index) {
-  return frame->frame.layers[layer_index].transform;
-}
-
-uint32_t dvrHwcFrameGetLayerDataspace(DvrHwcFrame* frame, size_t layer_index) {
-  return frame->frame.layers[layer_index].dataspace;
-}
-
-uint32_t dvrHwcFrameGetLayerColor(DvrHwcFrame* frame, size_t layer_index) {
-  const auto& color = frame->frame.layers[layer_index].color;
-  return color.r | (static_cast<uint32_t>(color.g) << 8) |
-         (static_cast<uint32_t>(color.b) << 16) |
-         (static_cast<uint32_t>(color.a) << 24);
-}
-
-uint32_t dvrHwcFrameGetLayerNumVisibleRegions(DvrHwcFrame* frame,
-                                              size_t layer_index) {
-  return frame->frame.layers[layer_index].visible_regions.size();
-}
-
-DvrHwcRecti dvrHwcFrameGetLayerVisibleRegion(DvrHwcFrame* frame,
-                                             size_t layer_index, size_t index) {
-  return DvrHwcRecti{
-      frame->frame.layers[layer_index].visible_regions[index].left,
-      frame->frame.layers[layer_index].visible_regions[index].top,
-      frame->frame.layers[layer_index].visible_regions[index].right,
-      frame->frame.layers[layer_index].visible_regions[index].bottom,
-  };
-}
-
-uint32_t dvrHwcFrameGetLayerNumDamagedRegions(DvrHwcFrame* frame,
-                                              size_t layer_index) {
-  return frame->frame.layers[layer_index].damaged_regions.size();
-}
-
-DvrHwcRecti dvrHwcFrameGetLayerDamagedRegion(DvrHwcFrame* frame,
-                                             size_t layer_index, size_t index) {
-  return DvrHwcRecti{
-      frame->frame.layers[layer_index].damaged_regions[index].left,
-      frame->frame.layers[layer_index].damaged_regions[index].top,
-      frame->frame.layers[layer_index].damaged_regions[index].right,
-      frame->frame.layers[layer_index].damaged_regions[index].bottom,
-  };
-}
diff --git a/libs/vr/libdvr/dvr_internal.h b/libs/vr/libdvr/dvr_internal.h
deleted file mode 100644
index f845cd8..0000000
--- a/libs/vr/libdvr/dvr_internal.h
+++ /dev/null
@@ -1,53 +0,0 @@
-#ifndef ANDROID_DVR_INTERNAL_H_
-#define ANDROID_DVR_INTERNAL_H_
-
-#include <sys/cdefs.h>
-
-#include <memory>
-
-extern "C" {
-
-typedef struct DvrBuffer DvrBuffer;
-typedef struct DvrReadBuffer DvrReadBuffer;
-typedef struct DvrWriteBuffer DvrWriteBuffer;
-
-}  // extern "C"
-
-namespace android {
-namespace dvr {
-
-class IonBuffer;
-
-DvrBuffer* CreateDvrBufferFromIonBuffer(
-    const std::shared_ptr<IonBuffer>& ion_buffer);
-
-}  // namespace dvr
-}  // namespace android
-
-extern "C" {
-
-struct DvrWriteBuffer {
-  // The slot nubmer of the buffer, a valid slot number must be in the range of
-  // [0, android::BufferQueueDefs::NUM_BUFFER_SLOTS). This is only valid for
-  // DvrWriteBuffer acquired from a DvrWriteBufferQueue.
-  int32_t slot = -1;
-
-  std::shared_ptr<android::dvr::ProducerBuffer> write_buffer;
-};
-
-struct DvrReadBuffer {
-  // The slot nubmer of the buffer, a valid slot number must be in the range of
-  // [0, android::BufferQueueDefs::NUM_BUFFER_SLOTS). This is only valid for
-  // DvrReadBuffer acquired from a DvrReadBufferQueue.
-  int32_t slot = -1;
-
-  std::shared_ptr<android::dvr::ConsumerBuffer> read_buffer;
-};
-
-struct DvrBuffer {
-  std::shared_ptr<android::dvr::IonBuffer> buffer;
-};
-
-}  // extern "C"
-
-#endif  // ANDROID_DVR_INTERNAL_H_
diff --git a/libs/vr/libdvr/dvr_performance.cpp b/libs/vr/libdvr/dvr_performance.cpp
deleted file mode 100644
index 599101f..0000000
--- a/libs/vr/libdvr/dvr_performance.cpp
+++ /dev/null
@@ -1,18 +0,0 @@
-#include "include/dvr/dvr_performance.h"
-
-#include <private/dvr/performance_client.h>
-
-using android::dvr::PerformanceClient;
-
-extern "C" {
-
-int dvrPerformanceSetSchedulerPolicy(pid_t task_id,
-                                     const char* scheduler_policy) {
-  int error;
-  if (auto client = PerformanceClient::Create(&error))
-    return client->SetSchedulerPolicy(task_id, scheduler_policy);
-  else
-    return error;
-}
-
-}  // extern "C"
diff --git a/libs/vr/libdvr/dvr_pose.cpp b/libs/vr/libdvr/dvr_pose.cpp
deleted file mode 100644
index c379ef5..0000000
--- a/libs/vr/libdvr/dvr_pose.cpp
+++ /dev/null
@@ -1,29 +0,0 @@
-#include "include/dvr/dvr_pose.h"
-
-#include <memory>
-
-#include <private/dvr/buffer_hub_queue_client.h>
-#include <private/dvr/pose_client_internal.h>
-
-#include "dvr_buffer_queue_internal.h"
-
-using android::dvr::ConsumerQueue;
-
-int dvrPoseClientGetDataReader(DvrPoseClient* client, uint64_t data_type,
-                               DvrReadBufferQueue** queue_out) {
-  if (!client || !queue_out)
-    return -EINVAL;
-
-  ConsumerQueue* consumer_queue;
-  int status = android::dvr::dvrPoseClientGetDataReaderHandle(client,
-                                                              data_type,
-                                                              &consumer_queue);
-  if (status != 0) {
-    ALOGE("dvrPoseClientGetDataReader: Failed to get queue: %d", status);
-    return status;
-  }
-
-  std::shared_ptr<ConsumerQueue> consumer_queue_ptr{consumer_queue};
-  *queue_out = new DvrReadBufferQueue(consumer_queue_ptr);
-  return 0;
-}
diff --git a/libs/vr/libdvr/dvr_surface.cpp b/libs/vr/libdvr/dvr_surface.cpp
deleted file mode 100644
index 0c7ec01..0000000
--- a/libs/vr/libdvr/dvr_surface.cpp
+++ /dev/null
@@ -1,277 +0,0 @@
-#include "include/dvr/dvr_surface.h"
-
-#include <inttypes.h>
-
-#include <pdx/rpc/variant.h>
-#include <private/android/AHardwareBufferHelpers.h>
-#include <private/dvr/display_client.h>
-
-#include "dvr_buffer_queue_internal.h"
-#include "dvr_internal.h"
-
-using android::AHardwareBuffer_convertToGrallocUsageBits;
-using android::dvr::display::DisplayClient;
-using android::dvr::display::Surface;
-using android::dvr::display::SurfaceAttributes;
-using android::dvr::display::SurfaceAttributeValue;
-using android::pdx::rpc::EmptyVariant;
-
-namespace {
-
-// Sets the Variant |destination| to the target std::array type and copies the C
-// array into it. Unsupported std::array configurations will fail to compile.
-template <typename T, std::size_t N>
-void ArrayCopy(SurfaceAttributeValue* destination, const T (&source)[N]) {
-  using ArrayType = std::array<T, N>;
-  *destination = ArrayType{};
-  std::copy(std::begin(source), std::end(source),
-            std::get<ArrayType>(*destination).begin());
-}
-
-bool ConvertSurfaceAttributes(const DvrSurfaceAttribute* attributes,
-                              size_t attribute_count,
-                              SurfaceAttributes* surface_attributes,
-                              size_t* error_index) {
-  for (size_t i = 0; i < attribute_count; i++) {
-    SurfaceAttributeValue value;
-    switch (attributes[i].value.type) {
-      case DVR_SURFACE_ATTRIBUTE_TYPE_INT32:
-        value = attributes[i].value.int32_value;
-        break;
-      case DVR_SURFACE_ATTRIBUTE_TYPE_INT64:
-        value = attributes[i].value.int64_value;
-        break;
-      case DVR_SURFACE_ATTRIBUTE_TYPE_BOOL:
-        // bool_value is defined in an extern "C" block, which makes it look
-        // like an int to C++. Use a cast to assign the correct type to the
-        // Variant type SurfaceAttributeValue.
-        value = static_cast<bool>(attributes[i].value.bool_value);
-        break;
-      case DVR_SURFACE_ATTRIBUTE_TYPE_FLOAT:
-        value = attributes[i].value.float_value;
-        break;
-      case DVR_SURFACE_ATTRIBUTE_TYPE_FLOAT2:
-        ArrayCopy(&value, attributes[i].value.float2_value);
-        break;
-      case DVR_SURFACE_ATTRIBUTE_TYPE_FLOAT3:
-        ArrayCopy(&value, attributes[i].value.float3_value);
-        break;
-      case DVR_SURFACE_ATTRIBUTE_TYPE_FLOAT4:
-        ArrayCopy(&value, attributes[i].value.float4_value);
-        break;
-      case DVR_SURFACE_ATTRIBUTE_TYPE_FLOAT8:
-        ArrayCopy(&value, attributes[i].value.float8_value);
-        break;
-      case DVR_SURFACE_ATTRIBUTE_TYPE_FLOAT16:
-        ArrayCopy(&value, attributes[i].value.float16_value);
-        break;
-      case DVR_SURFACE_ATTRIBUTE_TYPE_NONE:
-        value = EmptyVariant{};
-        break;
-      default:
-        *error_index = i;
-        return false;
-    }
-
-    surface_attributes->emplace(attributes[i].key, value);
-  }
-
-  return true;
-}
-
-}  // anonymous namespace
-
-extern "C" {
-
-struct DvrSurface {
-  std::unique_ptr<Surface> surface;
-};
-
-int dvrSurfaceCreate(const DvrSurfaceAttribute* attributes,
-                     size_t attribute_count, DvrSurface** out_surface) {
-  if (out_surface == nullptr) {
-    ALOGE("dvrSurfaceCreate: Invalid inputs: out_surface=%p.", out_surface);
-    return -EINVAL;
-  }
-
-  size_t error_index;
-  SurfaceAttributes surface_attributes;
-  if (!ConvertSurfaceAttributes(attributes, attribute_count,
-                                &surface_attributes, &error_index)) {
-    ALOGE("dvrSurfaceCreate: Invalid surface attribute type: %" PRIu64,
-          attributes[error_index].value.type);
-    return -EINVAL;
-  }
-
-  auto status = Surface::CreateSurface(surface_attributes);
-  if (!status) {
-    ALOGE("dvrSurfaceCreate:: Failed to create display surface: %s",
-          status.GetErrorMessage().c_str());
-    return -status.error();
-  }
-
-  *out_surface = new DvrSurface{status.take()};
-  return 0;
-}
-
-void dvrSurfaceDestroy(DvrSurface* surface) { delete surface; }
-
-int dvrSurfaceGetId(DvrSurface* surface) {
-  return surface->surface->surface_id();
-}
-
-int dvrSurfaceSetAttributes(DvrSurface* surface,
-                            const DvrSurfaceAttribute* attributes,
-                            size_t attribute_count) {
-  if (surface == nullptr || attributes == nullptr) {
-    ALOGE(
-        "dvrSurfaceSetAttributes: Invalid inputs: surface=%p attributes=%p "
-        "attribute_count=%zu",
-        surface, attributes, attribute_count);
-    return -EINVAL;
-  }
-
-  size_t error_index;
-  SurfaceAttributes surface_attributes;
-  if (!ConvertSurfaceAttributes(attributes, attribute_count,
-                                &surface_attributes, &error_index)) {
-    ALOGE("dvrSurfaceSetAttributes: Invalid surface attribute type: %" PRIu64,
-          attributes[error_index].value.type);
-    return -EINVAL;
-  }
-
-  auto status = surface->surface->SetAttributes(surface_attributes);
-  if (!status) {
-    ALOGE("dvrSurfaceSetAttributes: Failed to set attributes: %s",
-          status.GetErrorMessage().c_str());
-    return -status.error();
-  }
-
-  return 0;
-}
-
-int dvrSurfaceCreateWriteBufferQueue(DvrSurface* surface, uint32_t width,
-                                     uint32_t height, uint32_t format,
-                                     uint32_t layer_count, uint64_t usage,
-                                     size_t capacity, size_t metadata_size,
-                                     DvrWriteBufferQueue** out_writer) {
-  if (surface == nullptr || out_writer == nullptr) {
-    ALOGE(
-        "dvrSurfaceCreateWriteBufferQueue: Invalid inputs: surface=%p, "
-        "out_writer=%p.",
-        surface, out_writer);
-    return -EINVAL;
-  }
-
-  auto status = surface->surface->CreateQueue(
-      width, height, layer_count, format, usage, capacity, metadata_size);
-  if (!status) {
-    ALOGE("dvrSurfaceCreateWriteBufferQueue: Failed to create queue: %s",
-          status.GetErrorMessage().c_str());
-    return -status.error();
-  }
-
-  *out_writer = new DvrWriteBufferQueue(status.take());
-  return 0;
-}
-
-int dvrSetupGlobalBuffer(DvrGlobalBufferKey key, size_t size, uint64_t usage,
-                         DvrBuffer** buffer_out) {
-  if (!buffer_out)
-    return -EINVAL;
-
-  int error;
-  auto client = DisplayClient::Create(&error);
-  if (!client) {
-    ALOGE("dvrSetupGlobalBuffer: Failed to create display client: %s",
-          strerror(-error));
-    return error;
-  }
-
-  uint64_t gralloc_usage = AHardwareBuffer_convertToGrallocUsageBits(usage);
-
-  auto buffer_status = client->SetupGlobalBuffer(key, size, gralloc_usage);
-  if (!buffer_status) {
-    ALOGE("dvrSetupGlobalBuffer: Failed to setup global buffer: %s",
-          buffer_status.GetErrorMessage().c_str());
-    return -buffer_status.error();
-  }
-
-  *buffer_out = CreateDvrBufferFromIonBuffer(buffer_status.take());
-  return 0;
-}
-
-int dvrDeleteGlobalBuffer(DvrGlobalBufferKey key) {
-  int error;
-  auto client = DisplayClient::Create(&error);
-  if (!client) {
-    ALOGE("dvrDeleteGlobalBuffer: Failed to create display client: %s",
-          strerror(-error));
-    return error;
-  }
-
-  auto buffer_status = client->DeleteGlobalBuffer(key);
-  if (!buffer_status) {
-    ALOGE("dvrDeleteGlobalBuffer: Failed to delete named buffer: %s",
-          buffer_status.GetErrorMessage().c_str());
-    return -buffer_status.error();
-  }
-
-  return 0;
-}
-
-int dvrGetGlobalBuffer(DvrGlobalBufferKey key, DvrBuffer** out_buffer) {
-  if (!out_buffer)
-    return -EINVAL;
-
-  int error;
-  auto client = DisplayClient::Create(&error);
-  if (!client) {
-    ALOGE("dvrGetGlobalBuffer: Failed to create display client: %s",
-          strerror(-error));
-    return error;
-  }
-
-  auto status = client->GetGlobalBuffer(key);
-  if (!status) {
-    return -status.error();
-  }
-  *out_buffer = CreateDvrBufferFromIonBuffer(status.take());
-  return 0;
-}
-
-int dvrGetNativeDisplayMetrics(size_t sizeof_metrics,
-                               DvrNativeDisplayMetrics* metrics) {
-  ALOGE_IF(sizeof_metrics != sizeof(DvrNativeDisplayMetrics),
-           "dvrGetNativeDisplayMetrics: metrics struct mismatch, your dvr api "
-           "header is out of date.");
-
-  auto client = DisplayClient::Create();
-  if (!client) {
-    ALOGE("dvrGetNativeDisplayMetrics: Failed to create display client!");
-    return -ECOMM;
-  }
-
-  if (metrics == nullptr) {
-    ALOGE("dvrGetNativeDisplayMetrics: output metrics buffer must be non-null");
-    return -EINVAL;
-  }
-
-  auto status = client->GetDisplayMetrics();
-
-  if (!status) {
-    return -status.error();
-  }
-
-  if (sizeof_metrics >= 20) {
-    metrics->display_width = status.get().display_width;
-    metrics->display_height = status.get().display_height;
-    metrics->display_x_dpi = status.get().display_x_dpi;
-    metrics->display_y_dpi = status.get().display_y_dpi;
-    metrics->vsync_period_ns = status.get().vsync_period_ns;
-  }
-
-  return 0;
-}
-
-}  // extern "C"
diff --git a/libs/vr/libdvr/dvr_tracking.cpp b/libs/vr/libdvr/dvr_tracking.cpp
deleted file mode 100644
index 73addc9..0000000
--- a/libs/vr/libdvr/dvr_tracking.cpp
+++ /dev/null
@@ -1,82 +0,0 @@
-#include "include/dvr/dvr_tracking.h"
-
-#include <utils/Errors.h>
-#include <utils/Log.h>
-
-#if !DVR_TRACKING_IMPLEMENTED
-
-extern "C" {
-
-// This file provides the stub implementation of dvrTrackingXXX APIs. On
-// platforms that implement these APIs, set -DDVR_TRACKING_IMPLEMENTED=1 in the
-// build file.
-int dvrTrackingCameraCreate(DvrTrackingCamera**) {
-  ALOGE("dvrTrackingCameraCreate is not implemented.");
-  return -ENOSYS;
-}
-
-void dvrTrackingCameraDestroy(DvrTrackingCamera*) {
-  ALOGE("dvrTrackingCameraDestroy is not implemented.");
-}
-
-int dvrTrackingCameraStart(DvrTrackingCamera*, DvrWriteBufferQueue*) {
-  ALOGE("dvrTrackingCameraCreate is not implemented.");
-  return -ENOSYS;
-}
-
-int dvrTrackingCameraStop(DvrTrackingCamera*) {
-  ALOGE("dvrTrackingCameraCreate is not implemented.");
-  return -ENOSYS;
-}
-
-int dvrTrackingFeatureExtractorCreate(DvrTrackingFeatureExtractor**) {
-  ALOGE("dvrTrackingFeatureExtractorCreate is not implemented.");
-  return -ENOSYS;
-}
-
-void dvrTrackingFeatureExtractorDestroy(DvrTrackingFeatureExtractor*) {
-  ALOGE("dvrTrackingFeatureExtractorDestroy is not implemented.");
-}
-
-int dvrTrackingFeatureExtractorStart(DvrTrackingFeatureExtractor*,
-                                     DvrTrackingFeatureCallback, void*) {
-  ALOGE("dvrTrackingFeatureExtractorCreate is not implemented.");
-  return -ENOSYS;
-}
-
-int dvrTrackingFeatureExtractorStop(DvrTrackingFeatureExtractor*) {
-  ALOGE("dvrTrackingFeatureExtractorCreate is not implemented.");
-  return -ENOSYS;
-}
-
-int dvrTrackingFeatureExtractorProcessBuffer(DvrTrackingFeatureExtractor*,
-                                             DvrReadBuffer*,
-                                             const DvrTrackingBufferMetadata*,
-                                             bool*) {
-  ALOGE("dvrTrackingFeatureExtractorProcessBuffer is not implemented.");
-  return -ENOSYS;
-}
-
-int dvrTrackingSensorsCreate(DvrTrackingSensors**, const char*) {
-  ALOGE("dvrTrackingSensorsCreate is not implemented.");
-  return -ENOSYS;
-}
-
-void dvrTrackingSensorsDestroy(DvrTrackingSensors*) {
-  ALOGE("dvrTrackingSensorsDestroy is not implemented.");
-}
-
-int dvrTrackingSensorsStart(DvrTrackingSensors*, DvrTrackingSensorEventCallback,
-                            void*) {
-  ALOGE("dvrTrackingStart is not implemented.");
-  return -ENOSYS;
-}
-
-int dvrTrackingSensorsStop(DvrTrackingSensors*) {
-  ALOGE("dvrTrackingStop is not implemented.");
-  return -ENOSYS;
-}
-
-}  // extern "C"
-
-#endif  // DVR_TRACKING_IMPLEMENTED
diff --git a/libs/vr/libdvr/exported_apis.lds b/libs/vr/libdvr/exported_apis.lds
deleted file mode 100644
index 5ecb498..0000000
--- a/libs/vr/libdvr/exported_apis.lds
+++ /dev/null
@@ -1,9 +0,0 @@
-{
-  global:
-    # Whitelist the function to load the dvr api.
-    dvrGetApi;
-
-  local:
-    # Hide everything else.
-    *;
-};
diff --git a/libs/vr/libdvr/tests/Android.bp b/libs/vr/libdvr/tests/Android.bp
deleted file mode 100644
index fe7feb8..0000000
--- a/libs/vr/libdvr/tests/Android.bp
+++ /dev/null
@@ -1,113 +0,0 @@
-// Copyright (C) 2017 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_test {
-    srcs: [
-        "dvr_display_manager-test.cpp",
-        "dvr_named_buffer-test.cpp",
-        "dvr_tracking-test.cpp",
-    ],
-
-    header_libs: ["libdvr_headers"],
-    static_libs: [
-        "libdvr_static.google",
-        "libchrome",
-        "libdvrcommon",
-        "libdisplay",
-        "libbroadcastring",
-    ],
-    shared_libs: [
-        "libbase",
-        "libbinder",
-        "libbufferhubqueue",
-        "libcutils",
-        "libgui",
-        "liblog",
-        "libhardware",
-        "libui",
-        "libutils",
-        "libnativewindow",
-        "libpdx_default_transport",
-    ],
-    cflags: [
-        "-DDVR_TRACKING_IMPLEMENTED=0",
-        "-DLOG_TAG=\"dvr_api-test\"",
-        "-DTRACE=0",
-        "-Wno-missing-field-initializers",
-        "-O0",
-        "-g",
-    ],
-    name: "dvr_api-test",
-}
-
-cc_test {
-    name: "dvr_buffer_queue-test",
-
-    // Includes the dvr_api.h header. Tests should only include "dvr_api.h",
-    // and shall only get access to |dvrGetApi|, as other symbols are hidden
-    // from the library.
-    include_dirs: ["frameworks/native/libs/vr/libdvr/include"],
-
-    srcs: ["dvr_buffer_queue-test.cpp"],
-
-    shared_libs: [
-        "libandroid",
-        "liblog",
-    ],
-
-    cflags: [
-        "-DTRACE=0",
-        "-O2",
-        "-g",
-    ],
-
-    // DTS Should only link to NDK libraries.
-    sdk_version: "26",
-    stl: "c++_static",
-}
-
-cc_test {
-    name: "dvr_display-test",
-
-    include_dirs: [
-        "frameworks/native/libs/vr/libdvr/include",
-        "frameworks/native/libs/nativewindow/include",
-    ],
-
-    srcs: ["dvr_display-test.cpp"],
-
-    shared_libs: [
-        "libandroid",
-        "liblog",
-    ],
-
-    cflags: [
-        "-DTRACE=0",
-        "-O2",
-        "-g",
-    ],
-
-    // DTS Should only link to NDK libraries.
-    sdk_version: "26",
-    stl: "c++_static",
-}
diff --git a/libs/vr/libdvr/tests/dvr_api_test.h b/libs/vr/libdvr/tests/dvr_api_test.h
deleted file mode 100644
index 5d2ec28..0000000
--- a/libs/vr/libdvr/tests/dvr_api_test.h
+++ /dev/null
@@ -1,36 +0,0 @@
-#include <dlfcn.h>
-#include <dvr/dvr_api.h>
-
-#include <gtest/gtest.h>
-
-/** DvrTestBase loads the libdvr.so at runtime and get the Dvr API version 1. */
-class DvrApiTest : public ::testing::Test {
- protected:
-  void SetUp() override {
-    int flags = RTLD_NOW | RTLD_LOCAL;
-
-    // Here we need to ensure that libdvr is loaded with RTLD_NODELETE flag set
-    // (so that calls to `dlclose` don't actually unload the library). This is a
-    // workaround for an Android NDK bug. See more detail:
-    // https://github.com/android-ndk/ndk/issues/360
-    flags |= RTLD_NODELETE;
-    platform_handle_ = dlopen("libdvr.google.so", flags);
-    ASSERT_NE(nullptr, platform_handle_) << "Dvr shared library missing.";
-
-    auto dvr_get_api = reinterpret_cast<decltype(&dvrGetApi)>(
-        dlsym(platform_handle_, "dvrGetApi"));
-    ASSERT_NE(nullptr, dvr_get_api) << "Platform library missing dvrGetApi.";
-
-    ASSERT_EQ(dvr_get_api(&api_, sizeof(api_), /*version=*/1), 0)
-        << "Unable to find compatible Dvr API.";
-  }
-
-  void TearDown() override {
-    if (platform_handle_ != nullptr) {
-      dlclose(platform_handle_);
-    }
-  }
-
-  void* platform_handle_ = nullptr;
-  DvrApi_v1 api_;
-};
diff --git a/libs/vr/libdvr/tests/dvr_buffer_queue-test.cpp b/libs/vr/libdvr/tests/dvr_buffer_queue-test.cpp
deleted file mode 100644
index df06097..0000000
--- a/libs/vr/libdvr/tests/dvr_buffer_queue-test.cpp
+++ /dev/null
@@ -1,579 +0,0 @@
-#include <android/log.h>
-#include <android/native_window.h>
-#include <dvr/dvr_api.h>
-#include <dvr/dvr_buffer_queue.h>
-
-#include <gtest/gtest.h>
-
-#include <array>
-#include <unordered_map>
-
-#include "dvr_api_test.h"
-
-#define LOG_TAG "dvr_buffer_queue-test"
-
-#ifndef ALOGD
-#define ALOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
-#endif
-
-#ifndef ALOGD_IF
-#define ALOGD_IF(cond, ...) \
-  ((__predict_false(cond)) ? ((void)ALOGD(__VA_ARGS__)) : (void)0)
-#endif
-
-namespace {
-
-static constexpr uint32_t kBufferWidth = 100;
-static constexpr uint32_t kBufferHeight = 1;
-static constexpr uint32_t kLayerCount = 1;
-static constexpr uint32_t kBufferFormat = AHARDWAREBUFFER_FORMAT_BLOB;
-static constexpr uint64_t kBufferUsage = AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN;
-static constexpr size_t kQueueCapacity = 3;
-
-class DvrBufferQueueTest : public DvrApiTest {
- public:
-  static void BufferAvailableCallback(void* context) {
-    DvrBufferQueueTest* thiz = static_cast<DvrBufferQueueTest*>(context);
-    thiz->HandleBufferAvailable();
-  }
-
-  static void BufferRemovedCallback(DvrReadBuffer* buffer, void* context) {
-    DvrBufferQueueTest* thiz = static_cast<DvrBufferQueueTest*>(context);
-    thiz->HandleBufferRemoved(buffer);
-  }
-
- protected:
-  void TearDown() override {
-    if (write_queue_ != nullptr) {
-      api_.WriteBufferQueueDestroy(write_queue_);
-      write_queue_ = nullptr;
-    }
-    DvrApiTest::TearDown();
-  }
-
-  void HandleBufferAvailable() {
-    buffer_available_count_ += 1;
-    ALOGD_IF(TRACE, "Buffer avaiable, count=%d", buffer_available_count_);
-  }
-
-  void HandleBufferRemoved(DvrReadBuffer* buffer) {
-    buffer_removed_count_ += 1;
-    ALOGD_IF(TRACE, "Buffer removed, buffer=%p, count=%d", buffer,
-             buffer_removed_count_);
-  }
-
-  DvrWriteBufferQueue* write_queue_ = nullptr;
-  int buffer_available_count_{0};
-  int buffer_removed_count_{0};
-};
-
-TEST_F(DvrBufferQueueTest, WriteQueueCreateDestroy) {
-  int ret = api_.WriteBufferQueueCreate(
-      kBufferWidth, kBufferHeight, kBufferFormat, kLayerCount, kBufferUsage,
-      /*capacity=*/0, sizeof(DvrNativeBufferMetadata), &write_queue_);
-  ASSERT_EQ(0, ret);
-
-  api_.WriteBufferQueueDestroy(write_queue_);
-  write_queue_ = nullptr;
-}
-
-TEST_F(DvrBufferQueueTest, WriteQueueGetCapacity) {
-  int ret = api_.WriteBufferQueueCreate(
-      kBufferWidth, kBufferHeight, kBufferFormat, kLayerCount, kBufferUsage,
-      kQueueCapacity, sizeof(DvrNativeBufferMetadata), &write_queue_);
-  ASSERT_EQ(0, ret);
-
-  size_t capacity = api_.WriteBufferQueueGetCapacity(write_queue_);
-
-  ALOGD_IF(TRACE, "TestWrite_QueueGetCapacity, capacity=%zu", capacity);
-  ASSERT_EQ(kQueueCapacity, capacity);
-}
-
-TEST_F(DvrBufferQueueTest, CreateReadQueueFromWriteQueue) {
-  int ret = api_.WriteBufferQueueCreate(
-      kBufferWidth, kBufferHeight, kBufferFormat, kLayerCount, kBufferUsage,
-      /*capacity=*/0, sizeof(DvrNativeBufferMetadata), &write_queue_);
-  ASSERT_EQ(0, ret);
-
-  DvrReadBufferQueue* read_queue = nullptr;
-  ret = api_.WriteBufferQueueCreateReadQueue(write_queue_, &read_queue);
-
-  ASSERT_EQ(0, ret);
-  ASSERT_NE(nullptr, read_queue);
-
-  api_.ReadBufferQueueDestroy(read_queue);
-}
-
-TEST_F(DvrBufferQueueTest, CreateReadQueueFromReadQueue) {
-  int ret = api_.WriteBufferQueueCreate(
-      kBufferWidth, kBufferHeight, kBufferFormat, kLayerCount, kBufferUsage,
-      /*capacity=*/0, sizeof(DvrNativeBufferMetadata), &write_queue_);
-  ASSERT_EQ(0, ret);
-
-  DvrReadBufferQueue* read_queue1 = nullptr;
-  DvrReadBufferQueue* read_queue2 = nullptr;
-  ret = api_.WriteBufferQueueCreateReadQueue(write_queue_, &read_queue1);
-
-  ASSERT_EQ(0, ret);
-  ASSERT_NE(nullptr, read_queue1);
-
-  ret = api_.ReadBufferQueueCreateReadQueue(read_queue1, &read_queue2);
-  ASSERT_EQ(0, ret);
-  ASSERT_NE(nullptr, read_queue2);
-  ASSERT_NE(read_queue1, read_queue2);
-
-  api_.ReadBufferQueueDestroy(read_queue1);
-  api_.ReadBufferQueueDestroy(read_queue2);
-}
-
-TEST_F(DvrBufferQueueTest, GainBuffer) {
-  int ret = api_.WriteBufferQueueCreate(
-      kBufferWidth, kBufferHeight, kBufferFormat, kLayerCount, kBufferUsage,
-      kQueueCapacity, sizeof(DvrNativeBufferMetadata), &write_queue_);
-  ASSERT_EQ(ret, 0);
-
-  DvrWriteBuffer* wb = nullptr;
-  EXPECT_FALSE(api_.WriteBufferIsValid(wb));
-
-  DvrNativeBufferMetadata meta;
-  int fence_fd = -1;
-  ret = api_.WriteBufferQueueGainBuffer(write_queue_, /*timeout=*/0, &wb, &meta,
-                                        &fence_fd);
-  ASSERT_EQ(ret, 0);
-  EXPECT_EQ(fence_fd, -1);
-  EXPECT_NE(wb, nullptr);
-  EXPECT_TRUE(api_.WriteBufferIsValid(wb));
-}
-
-TEST_F(DvrBufferQueueTest, AcquirePostGainRelease) {
-  int ret = api_.WriteBufferQueueCreate(
-      kBufferWidth, kBufferHeight, kBufferFormat, kLayerCount, kBufferUsage,
-      kQueueCapacity, sizeof(DvrNativeBufferMetadata), &write_queue_);
-  ASSERT_EQ(ret, 0);
-
-  DvrReadBufferQueue* read_queue = nullptr;
-  DvrReadBuffer* rb = nullptr;
-  DvrWriteBuffer* wb = nullptr;
-  DvrNativeBufferMetadata meta1;
-  DvrNativeBufferMetadata meta2;
-  int fence_fd = -1;
-
-  ret = api_.WriteBufferQueueCreateReadQueue(write_queue_, &read_queue);
-
-  ASSERT_EQ(ret, 0);
-  ASSERT_NE(read_queue, nullptr);
-
-  api_.ReadBufferQueueSetBufferAvailableCallback(
-      read_queue, &BufferAvailableCallback, this);
-
-  // Gain buffer for writing.
-  ret = api_.WriteBufferQueueGainBuffer(write_queue_, /*timeout=*/0, &wb,
-                                        &meta1, &fence_fd);
-  ASSERT_EQ(ret, 0);
-  ASSERT_NE(wb, nullptr);
-  ASSERT_TRUE(api_.WriteBufferIsValid(wb));
-  ALOGD_IF(TRACE, "TestDequeuePostDequeueRelease, gain buffer %p, fence_fd=%d",
-           wb, fence_fd);
-  close(fence_fd);
-
-  // Post buffer to the read_queue.
-  meta1.timestamp = 42;
-  ret = api_.WriteBufferQueuePostBuffer(write_queue_, wb, &meta1, /*fence=*/-1);
-  ASSERT_EQ(ret, 0);
-  ASSERT_FALSE(api_.WriteBufferIsValid(wb));
-  wb = nullptr;
-
-  // Acquire buffer for reading.
-  ret = api_.ReadBufferQueueAcquireBuffer(read_queue, /*timeout=*/10, &rb,
-                                          &meta2, &fence_fd);
-  ASSERT_EQ(ret, 0);
-  ASSERT_NE(rb, nullptr);
-
-  // Dequeue is successfully, BufferAvailableCallback should be fired once.
-  ASSERT_EQ(buffer_available_count_, 1);
-  ASSERT_TRUE(api_.ReadBufferIsValid(rb));
-
-  // Metadata should be passed along from producer to consumer properly.
-  ASSERT_EQ(meta1.timestamp, meta2.timestamp);
-
-  ALOGD_IF(TRACE,
-           "TestDequeuePostDequeueRelease, acquire buffer %p, fence_fd=%d", rb,
-           fence_fd);
-  close(fence_fd);
-
-  // Release buffer to the write_queue.
-  ret = api_.ReadBufferQueueReleaseBuffer(read_queue, rb, &meta2,
-                                          /*release_fence_fd=*/-1);
-  ASSERT_EQ(ret, 0);
-  ASSERT_FALSE(api_.ReadBufferIsValid(rb));
-  rb = nullptr;
-
-  // TODO(b/34387835) Currently buffer allocation has to happen after all queues
-  // are initialized.
-  size_t capacity = api_.ReadBufferQueueGetCapacity(read_queue);
-
-  ALOGD_IF(TRACE, "TestDequeuePostDequeueRelease, capacity=%zu", capacity);
-  ASSERT_EQ(kQueueCapacity, capacity);
-
-  api_.ReadBufferQueueDestroy(read_queue);
-}
-
-TEST_F(DvrBufferQueueTest, GetANativeWindow) {
-  int ret = api_.WriteBufferQueueCreate(
-      kBufferWidth, kBufferHeight, kBufferFormat, kLayerCount, kBufferUsage,
-      /*capacity=*/0, /*user_metadata_size=*/0, &write_queue_);
-  ASSERT_EQ(0, ret);
-  ASSERT_NE(nullptr, write_queue_);
-
-  ANativeWindow* window = nullptr;
-  ret = api_.WriteBufferQueueGetANativeWindow(write_queue_, &window);
-  ASSERT_EQ(0, ret);
-  ASSERT_NE(nullptr, window);
-
-  uint32_t width = ANativeWindow_getWidth(window);
-  uint32_t height = ANativeWindow_getHeight(window);
-  uint32_t format = ANativeWindow_getFormat(window);
-  ASSERT_EQ(kBufferWidth, width);
-  ASSERT_EQ(kBufferHeight, height);
-  ASSERT_EQ(kBufferFormat, format);
-}
-
-// Create buffer queue of three buffers and dequeue three buffers out of it.
-// Before each dequeue operation, we resize the buffer queue and expect the
-// queue always return buffer with desired dimension.
-TEST_F(DvrBufferQueueTest, ResizeBuffer) {
-  int ret = api_.WriteBufferQueueCreate(
-      kBufferWidth, kBufferHeight, kBufferFormat, kLayerCount, kBufferUsage,
-      kQueueCapacity, sizeof(DvrNativeBufferMetadata), &write_queue_);
-  ASSERT_EQ(0, ret);
-
-  int fence_fd = -1;
-
-  DvrNativeBufferMetadata meta;
-  DvrReadBufferQueue* read_queue = nullptr;
-  DvrWriteBuffer* wb1 = nullptr;
-  DvrWriteBuffer* wb2 = nullptr;
-  DvrWriteBuffer* wb3 = nullptr;
-  AHardwareBuffer* ahb1 = nullptr;
-  AHardwareBuffer* ahb2 = nullptr;
-  AHardwareBuffer* ahb3 = nullptr;
-  AHardwareBuffer_Desc buffer_desc;
-
-  ret = api_.WriteBufferQueueCreateReadQueue(write_queue_, &read_queue);
-
-  ASSERT_EQ(0, ret);
-  ASSERT_NE(nullptr, read_queue);
-
-  api_.ReadBufferQueueSetBufferRemovedCallback(read_queue,
-                                               &BufferRemovedCallback, this);
-
-  // Handle all pending events on the read queue.
-  ret = api_.ReadBufferQueueHandleEvents(read_queue);
-  ASSERT_EQ(0, ret);
-
-  size_t capacity = api_.ReadBufferQueueGetCapacity(read_queue);
-  ALOGD_IF(TRACE, "TestResizeBuffer, capacity=%zu", capacity);
-  ASSERT_EQ(kQueueCapacity, capacity);
-
-  // Resize before dequeuing.
-  constexpr uint32_t w1 = 10;
-  ret = api_.WriteBufferQueueResizeBuffer(write_queue_, w1, kBufferHeight);
-  ASSERT_EQ(0, ret);
-
-  // Gain first buffer for writing. All buffers will be resized.
-  ret = api_.WriteBufferQueueGainBuffer(write_queue_, /*timeout=*/0, &wb1,
-                                        &meta, &fence_fd);
-  ASSERT_EQ(0, ret);
-  ASSERT_TRUE(api_.WriteBufferIsValid(wb1));
-  ALOGD_IF(TRACE, "TestResizeBuffer, gain buffer %p", wb1);
-  close(fence_fd);
-
-  // Check the buffer dimension.
-  ret = api_.WriteBufferGetAHardwareBuffer(wb1, &ahb1);
-  ASSERT_EQ(0, ret);
-  AHardwareBuffer_describe(ahb1, &buffer_desc);
-  ASSERT_EQ(w1, buffer_desc.width);
-  ASSERT_EQ(kBufferHeight, buffer_desc.height);
-  AHardwareBuffer_release(ahb1);
-
-  // For the first resize, all buffers are reallocated.
-  int expected_buffer_removed_count = kQueueCapacity;
-  ret = api_.ReadBufferQueueHandleEvents(read_queue);
-  ASSERT_EQ(0, ret);
-  ASSERT_EQ(expected_buffer_removed_count, buffer_removed_count_);
-
-  // Resize the queue. We are testing with blob format, keep height to be 1.
-  constexpr uint32_t w2 = 20;
-  ret = api_.WriteBufferQueueResizeBuffer(write_queue_, w2, kBufferHeight);
-  ASSERT_EQ(0, ret);
-
-  // The next buffer we dequeued should have new width.
-  ret = api_.WriteBufferQueueGainBuffer(write_queue_, /*timeout=*/0, &wb2,
-                                        &meta, &fence_fd);
-  ASSERT_EQ(0, ret);
-  ASSERT_TRUE(api_.WriteBufferIsValid(wb2));
-  ALOGD_IF(TRACE, "TestResizeBuffer, gain buffer %p, fence_fd=%d", wb2,
-           fence_fd);
-  close(fence_fd);
-
-  // Check the buffer dimension, should be new width
-  ret = api_.WriteBufferGetAHardwareBuffer(wb2, &ahb2);
-  ASSERT_EQ(0, ret);
-  AHardwareBuffer_describe(ahb2, &buffer_desc);
-  ASSERT_EQ(w2, buffer_desc.width);
-  AHardwareBuffer_release(ahb2);
-
-  // For the second resize, all but one buffers are reallocated.
-  expected_buffer_removed_count += (kQueueCapacity - 1);
-  ret = api_.ReadBufferQueueHandleEvents(read_queue);
-  ASSERT_EQ(0, ret);
-  ASSERT_EQ(expected_buffer_removed_count, buffer_removed_count_);
-
-  // Resize the queue for the third time.
-  constexpr uint32_t w3 = 30;
-  ret = api_.WriteBufferQueueResizeBuffer(write_queue_, w3, kBufferHeight);
-  ASSERT_EQ(0, ret);
-
-  // The next buffer we dequeued should have new width.
-  ret = api_.WriteBufferQueueGainBuffer(write_queue_, /*timeout=*/0, &wb3,
-                                        &meta, &fence_fd);
-  ASSERT_EQ(0, ret);
-  ASSERT_TRUE(api_.WriteBufferIsValid(wb3));
-  ALOGD_IF(TRACE, "TestResizeBuffer, gain buffer %p, fence_fd=%d", wb3,
-           fence_fd);
-  close(fence_fd);
-
-  // Check the buffer dimension, should be new width
-  ret = api_.WriteBufferGetAHardwareBuffer(wb3, &ahb3);
-  ASSERT_EQ(0, ret);
-  AHardwareBuffer_describe(ahb3, &buffer_desc);
-  ASSERT_EQ(w3, buffer_desc.width);
-  AHardwareBuffer_release(ahb3);
-
-  // For the third resize, all but two buffers are reallocated.
-  expected_buffer_removed_count += (kQueueCapacity - 2);
-  ret = api_.ReadBufferQueueHandleEvents(read_queue);
-  ASSERT_EQ(0, ret);
-  ASSERT_EQ(expected_buffer_removed_count, buffer_removed_count_);
-
-  api_.ReadBufferQueueDestroy(read_queue);
-}
-
-TEST_F(DvrBufferQueueTest, ReadQueueEventFd) {
-  int ret = api_.WriteBufferQueueCreate(
-      kBufferWidth, kBufferHeight, kBufferFormat, kLayerCount, kBufferUsage,
-      kQueueCapacity, sizeof(DvrNativeBufferMetadata), &write_queue_);
-  ASSERT_EQ(0, ret);
-
-  DvrReadBufferQueue* read_queue = nullptr;
-  ret = api_.WriteBufferQueueCreateReadQueue(write_queue_, &read_queue);
-
-  ASSERT_EQ(0, ret);
-  ASSERT_NE(nullptr, read_queue);
-
-  int event_fd = api_.ReadBufferQueueGetEventFd(read_queue);
-  ASSERT_GT(event_fd, 0);
-}
-
-// Verifies a Dvr{Read,Write}BufferQueue contains the same set of
-// Dvr{Read,Write}Buffer(s) during their lifecycles. And for the same buffer_id,
-// the corresponding AHardwareBuffer handle stays the same.
-TEST_F(DvrBufferQueueTest, StableBufferIdAndHardwareBuffer) {
-  int ret = api_.WriteBufferQueueCreate(
-      kBufferWidth, kBufferHeight, kBufferFormat, kLayerCount, kBufferUsage,
-      kQueueCapacity, sizeof(DvrNativeBufferMetadata), &write_queue_);
-  ASSERT_EQ(0, ret);
-
-  int fence_fd = -1;
-  DvrReadBufferQueue* read_queue = nullptr;
-  EXPECT_EQ(0, api_.WriteBufferQueueCreateReadQueue(write_queue_, &read_queue));
-
-  // Read buffers.
-  std::array<DvrReadBuffer*, kQueueCapacity> rbs;
-  // Write buffers.
-  std::array<DvrWriteBuffer*, kQueueCapacity> wbs;
-  // Buffer metadata.
-  std::array<DvrNativeBufferMetadata, kQueueCapacity> metas;
-  // Hardware buffers for Read buffers.
-  std::unordered_map<int, AHardwareBuffer*> rhbs;
-  // Hardware buffers for Write buffers.
-  std::unordered_map<int, AHardwareBuffer*> whbs;
-
-  constexpr int kNumTests = 100;
-
-  // This test runs the following operations many many times. Thus we prefer to
-  // use ASSERT_XXX rather than EXPECT_XXX to avoid spamming the output.
-  std::function<void(size_t i)> Gain = [&](size_t i) {
-    int ret = api_.WriteBufferQueueGainBuffer(write_queue_, /*timeout=*/10,
-                                              &wbs[i], &metas[i], &fence_fd);
-    ASSERT_EQ(ret, 0);
-    ASSERT_LT(fence_fd, 0);  // expect invalid fence.
-    ASSERT_TRUE(api_.WriteBufferIsValid(wbs[i]));
-    int buffer_id = api_.WriteBufferGetId(wbs[i]);
-    ASSERT_GT(buffer_id, 0);
-
-    AHardwareBuffer* hb = nullptr;
-    ASSERT_EQ(0, api_.WriteBufferGetAHardwareBuffer(wbs[i], &hb));
-
-    auto whb_it = whbs.find(buffer_id);
-    if (whb_it == whbs.end()) {
-      // If this is a new buffer id, check that total number of unique
-      // hardware buffers won't exceed queue capacity.
-      ASSERT_LT(whbs.size(), kQueueCapacity);
-      whbs.emplace(buffer_id, hb);
-    } else {
-      // If this is a buffer id we have seen before, check that the
-      // buffer_id maps to the same AHardwareBuffer handle.
-      ASSERT_EQ(hb, whb_it->second);
-    }
-  };
-
-  std::function<void(size_t i)> Post = [&](size_t i) {
-    ASSERT_TRUE(api_.WriteBufferIsValid(wbs[i]));
-
-    metas[i].timestamp++;
-    int ret = api_.WriteBufferQueuePostBuffer(write_queue_, wbs[i], &metas[i],
-                                              /*fence=*/-1);
-    ASSERT_EQ(ret, 0);
-  };
-
-  std::function<void(size_t i)> Acquire = [&](size_t i) {
-    int ret = api_.ReadBufferQueueAcquireBuffer(read_queue, /*timeout=*/10,
-                                                &rbs[i], &metas[i], &fence_fd);
-    ASSERT_EQ(ret, 0);
-    ASSERT_LT(fence_fd, 0);  // expect invalid fence.
-    ASSERT_TRUE(api_.ReadBufferIsValid(rbs[i]));
-
-    int buffer_id = api_.ReadBufferGetId(rbs[i]);
-    ASSERT_GT(buffer_id, 0);
-
-    AHardwareBuffer* hb = nullptr;
-    ASSERT_EQ(0, api_.ReadBufferGetAHardwareBuffer(rbs[i], &hb));
-
-    auto rhb_it = rhbs.find(buffer_id);
-    if (rhb_it == rhbs.end()) {
-      // If this is a new buffer id, check that total number of unique hardware
-      // buffers won't exceed queue capacity.
-      ASSERT_LT(rhbs.size(), kQueueCapacity);
-      rhbs.emplace(buffer_id, hb);
-    } else {
-      // If this is a buffer id we have seen before, check that the buffer_id
-      // maps to the same AHardwareBuffer handle.
-      ASSERT_EQ(hb, rhb_it->second);
-    }
-  };
-
-  std::function<void(size_t i)> Release = [&](size_t i) {
-    ASSERT_TRUE(api_.ReadBufferIsValid(rbs[i]));
-
-    int ret = api_.ReadBufferQueueReleaseBuffer(read_queue, rbs[i], &metas[i],
-                                                /*release_fence_fd=*/-1);
-    ASSERT_EQ(ret, 0);
-  };
-
-  // Scenario one:
-  for (int i = 0; i < kNumTests; i++) {
-    // Gain all write buffers.
-    for (size_t i = 0; i < kQueueCapacity; i++) {
-      ASSERT_NO_FATAL_FAILURE(Gain(i));
-    }
-    // Post all write buffers.
-    for (size_t i = 0; i < kQueueCapacity; i++) {
-      ASSERT_NO_FATAL_FAILURE(Post(i));
-    }
-    // Acquire all read buffers.
-    for (size_t i = 0; i < kQueueCapacity; i++) {
-      ASSERT_NO_FATAL_FAILURE(Acquire(i));
-    }
-    // Release all read buffers.
-    for (size_t i = 0; i < kQueueCapacity; i++) {
-      ASSERT_NO_FATAL_FAILURE(Release(i));
-    }
-  }
-
-  // Scenario two:
-  for (int i = 0; i < kNumTests; i++) {
-    // Gain and post all write buffers.
-    for (size_t i = 0; i < kQueueCapacity; i++) {
-      ASSERT_NO_FATAL_FAILURE(Gain(i));
-      ASSERT_NO_FATAL_FAILURE(Post(i));
-    }
-    // Acquire and release all read buffers.
-    for (size_t i = 0; i < kQueueCapacity; i++) {
-      ASSERT_NO_FATAL_FAILURE(Acquire(i));
-      ASSERT_NO_FATAL_FAILURE(Release(i));
-    }
-  }
-
-  // Scenario three:
-  for (int i = 0; i < kNumTests; i++) {
-    // Gain all write buffers then post them in reversed order.
-    for (size_t i = 0; i < kQueueCapacity; i++) {
-      ASSERT_NO_FATAL_FAILURE(Gain(i));
-    }
-    for (size_t i = 0; i < kQueueCapacity; i++) {
-      ASSERT_NO_FATAL_FAILURE(Post(kQueueCapacity - 1 - i));
-    }
-
-    // Acquire all write buffers then release them in reversed order.
-    for (size_t i = 0; i < kQueueCapacity; i++) {
-      ASSERT_NO_FATAL_FAILURE(Acquire(i));
-    }
-    for (size_t i = 0; i < kQueueCapacity; i++) {
-      ASSERT_NO_FATAL_FAILURE(Release(kQueueCapacity - 1 - i));
-    }
-  }
-}
-
-TEST_F(DvrBufferQueueTest, ConsumerReleaseAfterProducerDestroy) {
-  int ret = api_.WriteBufferQueueCreate(
-      kBufferWidth, kBufferHeight, kBufferFormat, kLayerCount, kBufferUsage,
-      kQueueCapacity, sizeof(DvrNativeBufferMetadata), &write_queue_);
-  ASSERT_EQ(ret, 0);
-
-  DvrReadBufferQueue* read_queue = nullptr;
-  DvrReadBuffer* rb = nullptr;
-  DvrWriteBuffer* wb = nullptr;
-  DvrNativeBufferMetadata meta1;
-  DvrNativeBufferMetadata meta2;
-  int fence_fd = -1;
-
-  ret = api_.WriteBufferQueueCreateReadQueue(write_queue_, &read_queue);
-  ASSERT_EQ(ret, 0);
-
-  api_.ReadBufferQueueSetBufferAvailableCallback(
-      read_queue, &BufferAvailableCallback, this);
-
-  // Gain buffer for writing.
-  ret = api_.WriteBufferQueueGainBuffer(write_queue_, /*timeout=*/0, &wb,
-                                        &meta1, &fence_fd);
-  ASSERT_EQ(ret, 0);
-  close(fence_fd);
-
-  // Post buffer to the read_queue.
-  ret = api_.WriteBufferQueuePostBuffer(write_queue_, wb, &meta1, /*fence=*/-1);
-  ASSERT_EQ(ret, 0);
-  wb = nullptr;
-
-  // Acquire buffer for reading.
-  ret = api_.ReadBufferQueueAcquireBuffer(read_queue, /*timeout=*/10, &rb,
-                                          &meta2, &fence_fd);
-  ASSERT_EQ(ret, 0);
-  close(fence_fd);
-
-  // Destroy the write buffer queue and make sure the reader queue is picking
-  // these events up.
-  api_.WriteBufferQueueDestroy(write_queue_);
-  ret = api_.ReadBufferQueueHandleEvents(read_queue);
-  ASSERT_EQ(0, ret);
-
-  // Release buffer to the write_queue.
-  ret = api_.ReadBufferQueueReleaseBuffer(read_queue, rb, &meta2,
-                                          /*release_fence_fd=*/-1);
-  ASSERT_EQ(ret, 0);
-  rb = nullptr;
-
-  api_.ReadBufferQueueDestroy(read_queue);
-}
-
-}  // namespace
diff --git a/libs/vr/libdvr/tests/dvr_display-test.cpp b/libs/vr/libdvr/tests/dvr_display-test.cpp
deleted file mode 100644
index c72f940..0000000
--- a/libs/vr/libdvr/tests/dvr_display-test.cpp
+++ /dev/null
@@ -1,351 +0,0 @@
-#include <android/hardware_buffer.h>
-#include <android/log.h>
-#include <dvr/dvr_api.h>
-#include <dvr/dvr_display_types.h>
-#include <dvr/dvr_surface.h>
-
-#include <gtest/gtest.h>
-
-#include "dvr_api_test.h"
-
-#define LOG_TAG "dvr_display-test"
-
-#ifndef ALOGD
-#define ALOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
-#endif
-
-class DvrDisplayTest : public DvrApiTest {
- protected:
-  void SetUp() override {
-    DvrApiTest::SetUp();
-    int ret = api_.GetNativeDisplayMetrics(sizeof(display_metrics_),
-                                           &display_metrics_);
-    ASSERT_EQ(ret, 0) << "Failed to get display metrics.";
-    ALOGD(
-        "display_width: %d, display_height: %d, display_x_dpi: %d, "
-        "display_y_dpi: %d, vsync_period_ns: %d.",
-        display_metrics_.display_width, display_metrics_.display_height,
-        display_metrics_.display_x_dpi, display_metrics_.display_y_dpi,
-        display_metrics_.vsync_period_ns);
-  }
-
-  void TearDown() override {
-    if (write_queue_ != nullptr) {
-      api_.WriteBufferQueueDestroy(write_queue_);
-      write_queue_ = nullptr;
-    }
-    if (direct_surface_ != nullptr) {
-      api_.SurfaceDestroy(direct_surface_);
-      direct_surface_ = nullptr;
-    }
-    DvrApiTest::TearDown();
-  }
-
-  /* Convert a write buffer to an android hardware buffer and fill in
-   * color_textures evenly to the buffer.
-   * AssertionError if the width of the buffer is not equal to the input width,
-   * AssertionError if the height of the buffer is not equal to the input
-   * height.
-   */
-  void FillWriteBuffer(DvrWriteBuffer* write_buffer,
-                       const std::vector<uint32_t>& color_textures,
-                       uint32_t width, uint32_t height);
-
-  // Write buffer queue properties.
-  static constexpr uint64_t kUsage = AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE |
-                                     AHARDWAREBUFFER_USAGE_GPU_COLOR_OUTPUT |
-                                     AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN;
-  uint32_t kFormat = AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM;
-  static constexpr size_t kMetadataSize = 0;
-  static constexpr int kTimeoutMs = 1000;  // Time for getting buffer.
-  uint32_t kLayerCount = 1;
-  DvrWriteBufferQueue* write_queue_ = nullptr;
-  DvrSurface* direct_surface_ = nullptr;
-
-  // Device display properties.
-  DvrNativeDisplayMetrics display_metrics_;
-};
-
-TEST_F(DvrDisplayTest, DisplayWithOneBuffer) {
-  // Create a direct surface.
-  std::vector<DvrSurfaceAttribute> direct_surface_attributes = {
-      {.key = DVR_SURFACE_ATTRIBUTE_DIRECT,
-       .value.type = DVR_SURFACE_ATTRIBUTE_TYPE_BOOL,
-       .value.bool_value = true},
-      {.key = DVR_SURFACE_ATTRIBUTE_Z_ORDER,
-       .value.type = DVR_SURFACE_ATTRIBUTE_TYPE_INT32,
-       .value.int32_value = 10},
-      {.key = DVR_SURFACE_ATTRIBUTE_VISIBLE,
-       .value.type = DVR_SURFACE_ATTRIBUTE_TYPE_BOOL,
-       .value.bool_value = true},
-  };
-  int ret =
-      api_.SurfaceCreate(direct_surface_attributes.data(),
-                         direct_surface_attributes.size(), &direct_surface_);
-  ASSERT_EQ(ret, 0) << "Failed to create direct surface.";
-
-  // Create a buffer queue with the direct surface.
-  constexpr size_t kCapacity = 1;
-  uint32_t width = display_metrics_.display_width;
-  uint32_t height = display_metrics_.display_height;
-  ret = api_.SurfaceCreateWriteBufferQueue(
-      direct_surface_, width, height, kFormat, kLayerCount, kUsage, kCapacity,
-      kMetadataSize, &write_queue_);
-  EXPECT_EQ(0, ret) << "Failed to create buffer queue.";
-  ASSERT_NE(nullptr, write_queue_) << "Write buffer queue should not be null.";
-
-  // Get buffer from WriteBufferQueue.
-  DvrWriteBuffer* write_buffer = nullptr;
-  DvrNativeBufferMetadata out_meta;
-  int out_fence_fd = -1;
-  ret = api_.WriteBufferQueueGainBuffer(write_queue_, kTimeoutMs, &write_buffer,
-                                        &out_meta, &out_fence_fd);
-  EXPECT_EQ(0, ret) << "Failed to get the buffer.";
-  ASSERT_NE(nullptr, write_buffer) << "Gained buffer should not be null.";
-
-  // Color the write buffer.
-  FillWriteBuffer(write_buffer,
-                  {0xff000000, 0x00ff0000, 0x0000ff00, 0x000000ff, 0x00000000},
-                  width, height);
-
-  // Post buffer.
-  int ready_fence_fd = -1;
-  ret = api_.WriteBufferQueuePostBuffer(write_queue_, write_buffer, &out_meta,
-                                        ready_fence_fd);
-  EXPECT_EQ(0, ret) << "Failed to post the buffer.";
-
-  sleep(5);  // For visual check on the device under test.
-  // Should observe three primary colors on the screen center.
-}
-
-TEST_F(DvrDisplayTest, DisplayWithDoubleBuffering) {
-  // Create a direct surface.
-  std::vector<DvrSurfaceAttribute> direct_surface_attributes = {
-      {.key = DVR_SURFACE_ATTRIBUTE_DIRECT,
-       .value.type = DVR_SURFACE_ATTRIBUTE_TYPE_BOOL,
-       .value.bool_value = true},
-      {.key = DVR_SURFACE_ATTRIBUTE_Z_ORDER,
-       .value.type = DVR_SURFACE_ATTRIBUTE_TYPE_INT32,
-       .value.int32_value = 10},
-      {.key = DVR_SURFACE_ATTRIBUTE_VISIBLE,
-       .value.type = DVR_SURFACE_ATTRIBUTE_TYPE_BOOL,
-       .value.bool_value = true},
-  };
-  int ret =
-      api_.SurfaceCreate(direct_surface_attributes.data(),
-                         direct_surface_attributes.size(), &direct_surface_);
-  ASSERT_EQ(ret, 0) << "Failed to create direct surface.";
-
-  // Create a buffer queue with the direct surface.
-  constexpr size_t kCapacity = 2;
-  uint32_t width = display_metrics_.display_width;
-  uint32_t height = display_metrics_.display_height;
-  ret = api_.SurfaceCreateWriteBufferQueue(
-      direct_surface_, width, height, kFormat, kLayerCount, kUsage, kCapacity,
-      kMetadataSize, &write_queue_);
-  EXPECT_EQ(0, ret) << "Failed to create buffer queue.";
-  ASSERT_NE(nullptr, write_queue_) << "Write buffer queue should not be null.";
-
-  int num_display_cycles_in_5s = 5 / (display_metrics_.vsync_period_ns / 1e9);
-  ALOGD("The number of display cycles: %d", num_display_cycles_in_5s);
-  int bufferhub_id_prev_write_buffer = -1;
-  for (int i = 0; i < num_display_cycles_in_5s; ++i) {
-    // Get a buffer from the WriteBufferQueue.
-    DvrWriteBuffer* write_buffer = nullptr;
-    DvrNativeBufferMetadata out_meta;
-    int out_fence_fd = -1;
-    ret = api_.WriteBufferQueueGainBuffer(
-        write_queue_, kTimeoutMs, &write_buffer, &out_meta, &out_fence_fd);
-    EXPECT_EQ(0, ret) << "Failed to get the a write buffer.";
-    ASSERT_NE(nullptr, write_buffer) << "The gained buffer should not be null.";
-
-    int bufferhub_id = api_.WriteBufferGetId(write_buffer);
-    ALOGD("Display cycle: %d, bufferhub id of the write buffer: %d", i,
-          bufferhub_id);
-    EXPECT_NE(bufferhub_id_prev_write_buffer, bufferhub_id)
-        << "Double buffering should be using the two buffers in turns, not "
-           "reusing the same write buffer.";
-    bufferhub_id_prev_write_buffer = bufferhub_id;
-
-    // Color the write buffer.
-    if (i % 2) {
-      FillWriteBuffer(write_buffer, {0xffff0000, 0xff00ff00, 0xff0000ff}, width,
-                      height);
-    } else {
-      FillWriteBuffer(write_buffer, {0xff00ff00, 0xff0000ff, 0xffff0000}, width,
-                      height);
-    }
-
-    // Post the write buffer.
-    int ready_fence_fd = -1;
-    ret = api_.WriteBufferQueuePostBuffer(write_queue_, write_buffer, &out_meta,
-                                          ready_fence_fd);
-    EXPECT_EQ(0, ret) << "Failed to post the buffer.";
-  }
-  // Should observe blinking screen in secondary colors
-  // although it is actually displaying primary colors.
-}
-
-TEST_F(DvrDisplayTest, DisplayWithTwoHardwareLayers) {
-  // Create the direct_surface_0 of z order 10 and direct_surface_1 of z
-  // order 11.
-  DvrSurface* direct_surface_0 = nullptr;
-  std::vector<DvrSurfaceAttribute> direct_surface_0_attributes = {
-      {.key = DVR_SURFACE_ATTRIBUTE_DIRECT,
-       .value.type = DVR_SURFACE_ATTRIBUTE_TYPE_BOOL,
-       .value.bool_value = true},
-      {.key = DVR_SURFACE_ATTRIBUTE_Z_ORDER,
-       .value.type = DVR_SURFACE_ATTRIBUTE_TYPE_INT32,
-       .value.int32_value = 10},
-      {.key = DVR_SURFACE_ATTRIBUTE_VISIBLE,
-       .value.type = DVR_SURFACE_ATTRIBUTE_TYPE_BOOL,
-       .value.bool_value = true},
-  };
-  int ret =
-      api_.SurfaceCreate(direct_surface_0_attributes.data(),
-                         direct_surface_0_attributes.size(), &direct_surface_0);
-  EXPECT_EQ(ret, 0) << "Failed to create direct surface.";
-
-  DvrSurface* direct_surface_1 = nullptr;
-  std::vector<DvrSurfaceAttribute> direct_surface_1_attributes = {
-      {.key = DVR_SURFACE_ATTRIBUTE_DIRECT,
-       .value.type = DVR_SURFACE_ATTRIBUTE_TYPE_BOOL,
-       .value.bool_value = true},
-      {.key = DVR_SURFACE_ATTRIBUTE_Z_ORDER,
-       .value.type = DVR_SURFACE_ATTRIBUTE_TYPE_INT32,
-       .value.int32_value = 11},
-      {.key = DVR_SURFACE_ATTRIBUTE_VISIBLE,
-       .value.type = DVR_SURFACE_ATTRIBUTE_TYPE_BOOL,
-       .value.bool_value = true},
-  };
-  ret =
-      api_.SurfaceCreate(direct_surface_1_attributes.data(),
-                         direct_surface_1_attributes.size(), &direct_surface_1);
-  EXPECT_EQ(ret, 0) << "Failed to create direct surface.";
-
-  // Create a buffer queue for each of the direct surfaces.
-  constexpr size_t kCapacity = 1;
-  uint32_t width = display_metrics_.display_width;
-  uint32_t height = display_metrics_.display_height;
-
-  DvrWriteBufferQueue* write_queue_0 = nullptr;
-  ret = api_.SurfaceCreateWriteBufferQueue(
-      direct_surface_0, width, height, kFormat, kLayerCount, kUsage, kCapacity,
-      kMetadataSize, &write_queue_0);
-  EXPECT_EQ(0, ret) << "Failed to create buffer queue.";
-  EXPECT_NE(nullptr, write_queue_0) << "Write buffer queue should not be null.";
-
-  DvrWriteBufferQueue* write_queue_1 = nullptr;
-  ret = api_.SurfaceCreateWriteBufferQueue(
-      direct_surface_1, width, height, kFormat, kLayerCount, kUsage, kCapacity,
-      kMetadataSize, &write_queue_1);
-  EXPECT_EQ(0, ret) << "Failed to create buffer queue.";
-  EXPECT_NE(nullptr, write_queue_1) << "Write buffer queue should not be null.";
-
-  // Get a buffer from each of the write buffer queues.
-  DvrWriteBuffer* write_buffer_0 = nullptr;
-  DvrNativeBufferMetadata out_meta_0;
-  int out_fence_fd = -1;
-  ret = api_.WriteBufferQueueGainBuffer(
-      write_queue_0, kTimeoutMs, &write_buffer_0, &out_meta_0, &out_fence_fd);
-  EXPECT_EQ(0, ret) << "Failed to get the buffer.";
-  EXPECT_NE(nullptr, write_buffer_0) << "Gained buffer should not be null.";
-
-  DvrWriteBuffer* write_buffer_1 = nullptr;
-  DvrNativeBufferMetadata out_meta_1;
-  out_fence_fd = -1;
-  ret = api_.WriteBufferQueueGainBuffer(
-      write_queue_1, kTimeoutMs, &write_buffer_1, &out_meta_1, &out_fence_fd);
-  EXPECT_EQ(0, ret) << "Failed to get the buffer.";
-  EXPECT_NE(nullptr, write_buffer_1) << "Gained buffer should not be null.";
-
-  // Color the write buffers.
-  FillWriteBuffer(write_buffer_0, {0xffff0000, 0xff00ff00, 0xff0000ff}, width,
-                  height);
-  FillWriteBuffer(write_buffer_1, {0x7f00ff00, 0x7f0000ff, 0x7fff0000}, width,
-                  height);
-
-  // Post buffers.
-  int ready_fence_fd = -1;
-  ret = api_.WriteBufferQueuePostBuffer(write_queue_0, write_buffer_0,
-                                        &out_meta_0, ready_fence_fd);
-  EXPECT_EQ(0, ret) << "Failed to post the buffer.";
-
-  ready_fence_fd = -1;
-  ret = api_.WriteBufferQueuePostBuffer(write_queue_1, write_buffer_1,
-                                        &out_meta_1, ready_fence_fd);
-  EXPECT_EQ(0, ret) << "Failed to post the buffer.";
-
-  sleep(5);  // For visual check on the device under test.
-  // Should observe three secondary colors.
-
-  // Test finished. Clean up buffers and surfaces.
-  if (write_queue_0 != nullptr) {
-    api_.WriteBufferQueueDestroy(write_queue_0);
-    write_queue_0 = nullptr;
-  }
-  if (write_queue_1 != nullptr) {
-    api_.WriteBufferQueueDestroy(write_queue_1);
-    write_queue_1 = nullptr;
-  }
-  if (direct_surface_0 != nullptr) {
-    api_.SurfaceDestroy(direct_surface_0);
-  }
-  if (direct_surface_1 != nullptr) {
-    api_.SurfaceDestroy(direct_surface_1);
-  }
-}
-
-void DvrDisplayTest::FillWriteBuffer(
-    DvrWriteBuffer* write_buffer, const std::vector<uint32_t>& color_textures,
-    uint32_t width, uint32_t height) {
-  uint32_t num_colors = color_textures.size();
-  // Convert the first write buffer to an android hardware buffer.
-  AHardwareBuffer* ah_buffer = nullptr;
-  int ret = api_.WriteBufferGetAHardwareBuffer(write_buffer, &ah_buffer);
-  ASSERT_EQ(0, ret) << "Failed to get a hardware buffer from the write buffer.";
-  ASSERT_NE(nullptr, ah_buffer) << "AHardware buffer should not be null.";
-  AHardwareBuffer_Desc ah_buffer_describe;
-  AHardwareBuffer_describe(ah_buffer, &ah_buffer_describe);
-  ASSERT_EQ(ah_buffer_describe.format, kFormat)
-      << "The format of the android hardware buffer is wrong.";
-  ASSERT_EQ(ah_buffer_describe.layers, kLayerCount)
-      << "The obtained android hardware buffer should have 2 layers.";
-  ASSERT_EQ(ah_buffer_describe.width, width)
-      << "The obtained android hardware buffer width is wrong.";
-  ASSERT_EQ(ah_buffer_describe.height, height)
-      << "The obtained android hardware buffer height is wrong.";
-  // Change the content of the android hardware buffer.
-  void* buffer_data = nullptr;
-  int32_t fence = -1;
-  ret = AHardwareBuffer_lock(ah_buffer, AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN,
-                             fence, nullptr, &buffer_data);
-  ASSERT_EQ(0, ret) << "Failed to lock the hardware buffer.";
-  ASSERT_NE(nullptr, buffer_data) << "Buffer data should not be null.";
-
-  uint32_t num_pixels = width * height / num_colors;
-  for (uint32_t color_index = 0; color_index < num_colors - 1; ++color_index) {
-    uint32_t color_texture = color_textures[color_index];
-    for (uint32_t i = 0; i < num_pixels; ++i) {
-      memcpy(reinterpret_cast<void*>(reinterpret_cast<int64_t>(buffer_data) +
-                                     (i + num_pixels * color_index) *
-                                         sizeof(color_texture)),
-             &color_texture, sizeof(color_texture));
-    }
-  }
-  uint32_t color_texture = color_textures[num_colors - 1];
-  uint32_t num_colored_pixels = num_pixels * (num_colors - 1);
-  num_pixels = width * height - num_colored_pixels;
-  for (uint32_t i = 0; i < num_pixels; ++i) {
-    memcpy(reinterpret_cast<void*>(reinterpret_cast<int64_t>(buffer_data) +
-                                   (i + num_colored_pixels) *
-                                       sizeof(color_texture)),
-           &color_texture, sizeof(color_texture));
-  }
-  fence = -1;
-  ret = AHardwareBuffer_unlock(ah_buffer, &fence);
-  EXPECT_EQ(0, ret) << "Failed to unlock the hardware buffer.";
-
-  // Release the android hardware buffer.
-  AHardwareBuffer_release(ah_buffer);
-}
diff --git a/libs/vr/libdvr/tests/dvr_display_manager-test.cpp b/libs/vr/libdvr/tests/dvr_display_manager-test.cpp
deleted file mode 100644
index 07e2121..0000000
--- a/libs/vr/libdvr/tests/dvr_display_manager-test.cpp
+++ /dev/null
@@ -1,891 +0,0 @@
-#include <android-base/properties.h>
-#include <base/logging.h>
-#include <cutils/properties.h>
-#include <gtest/gtest.h>
-#include <log/log.h>
-#include <poll.h>
-
-#include <android/hardware_buffer.h>
-
-#include <algorithm>
-#include <array>
-#include <set>
-#include <thread>
-#include <vector>
-
-#include <dvr/dvr_configuration_data.h>
-#include <dvr/dvr_deleter.h>
-#include <dvr/dvr_display_manager.h>
-#include <dvr/dvr_surface.h>
-
-#include <pdx/status.h>
-
-using android::pdx::ErrorStatus;
-using android::pdx::Status;
-
-namespace android {
-namespace dvr {
-
-namespace {
-
-using ::testing::Test;
-
-DvrSurfaceAttribute MakeAttribute(DvrSurfaceAttributeKey key, nullptr_t) {
-  DvrSurfaceAttribute attribute;
-  attribute.key = key;
-  attribute.value.type = DVR_SURFACE_ATTRIBUTE_TYPE_NONE;
-  return attribute;
-}
-
-DvrSurfaceAttribute MakeAttribute(DvrSurfaceAttributeKey key, int32_t value) {
-  DvrSurfaceAttribute attribute;
-  attribute.key = key;
-  attribute.value.type = DVR_SURFACE_ATTRIBUTE_TYPE_INT32;
-  attribute.value.int32_value = value;
-  return attribute;
-}
-
-DvrSurfaceAttribute MakeAttribute(DvrSurfaceAttributeKey key, int64_t value) {
-  DvrSurfaceAttribute attribute;
-  attribute.key = key;
-  attribute.value.type = DVR_SURFACE_ATTRIBUTE_TYPE_INT64;
-  attribute.value.int64_value = value;
-  return attribute;
-}
-
-DvrSurfaceAttribute MakeAttribute(DvrSurfaceAttributeKey key, bool value) {
-  DvrSurfaceAttribute attribute;
-  attribute.key = key;
-  attribute.value.type = DVR_SURFACE_ATTRIBUTE_TYPE_BOOL;
-  attribute.value.bool_value = value;
-  return attribute;
-}
-
-DvrSurfaceAttribute MakeAttribute(DvrSurfaceAttributeKey key, float value) {
-  DvrSurfaceAttribute attribute;
-  attribute.key = key;
-  attribute.value.type = DVR_SURFACE_ATTRIBUTE_TYPE_FLOAT;
-  attribute.value.float_value = value;
-  return attribute;
-}
-
-DvrSurfaceAttribute MakeAttribute(DvrSurfaceAttributeKey key,
-                                  const std::array<float, 2>& value) {
-  DvrSurfaceAttribute attribute;
-  attribute.key = key;
-  attribute.value.type = DVR_SURFACE_ATTRIBUTE_TYPE_FLOAT2;
-  std::copy(value.begin(), value.end(), attribute.value.float2_value);
-  return attribute;
-}
-
-DvrSurfaceAttribute MakeAttribute(DvrSurfaceAttributeKey key,
-                                  const std::array<float, 3>& value) {
-  DvrSurfaceAttribute attribute;
-  attribute.key = key;
-  attribute.value.type = DVR_SURFACE_ATTRIBUTE_TYPE_FLOAT3;
-  std::copy(value.begin(), value.end(), attribute.value.float3_value);
-  return attribute;
-}
-
-DvrSurfaceAttribute MakeAttribute(DvrSurfaceAttributeKey key,
-                                  const std::array<float, 4>& value) {
-  DvrSurfaceAttribute attribute;
-  attribute.key = key;
-  attribute.value.type = DVR_SURFACE_ATTRIBUTE_TYPE_FLOAT4;
-  std::copy(value.begin(), value.end(), attribute.value.float4_value);
-  return attribute;
-}
-
-DvrSurfaceAttribute MakeAttribute(DvrSurfaceAttributeKey key,
-                                  const std::array<float, 8>& value) {
-  DvrSurfaceAttribute attribute;
-  attribute.key = key;
-  attribute.value.type = DVR_SURFACE_ATTRIBUTE_TYPE_FLOAT8;
-  std::copy(value.begin(), value.end(), attribute.value.float8_value);
-  return attribute;
-}
-
-DvrSurfaceAttribute MakeAttribute(DvrSurfaceAttributeKey key,
-                                  const std::array<float, 16>& value) {
-  DvrSurfaceAttribute attribute;
-  attribute.key = key;
-  attribute.value.type = DVR_SURFACE_ATTRIBUTE_TYPE_FLOAT16;
-  std::copy(value.begin(), value.end(), attribute.value.float16_value);
-  return attribute;
-}
-
-Status<UniqueDvrSurface> CreateApplicationSurface(bool visible = false,
-                                                  int32_t z_order = 0) {
-  DvrSurface* surface = nullptr;
-  DvrSurfaceAttribute attributes[] = {
-      MakeAttribute(DVR_SURFACE_ATTRIBUTE_Z_ORDER, z_order),
-      MakeAttribute(DVR_SURFACE_ATTRIBUTE_VISIBLE, visible)};
-
-  const int ret = dvrSurfaceCreate(
-      attributes, std::extent<decltype(attributes)>::value, &surface);
-  if (ret < 0)
-    return ErrorStatus(-ret);
-  else
-    return {UniqueDvrSurface(surface)};
-}
-
-Status<UniqueDvrWriteBufferQueue> CreateSurfaceQueue(
-    const UniqueDvrSurface& surface, uint32_t width, uint32_t height,
-    uint32_t format, uint32_t layer_count, uint64_t usage, size_t capacity,
-    size_t metadata_size) {
-  DvrWriteBufferQueue* queue;
-  const int ret = dvrSurfaceCreateWriteBufferQueue(
-      surface.get(), width, height, format, layer_count, usage, capacity,
-      metadata_size, &queue);
-  if (ret < 0)
-    return ErrorStatus(-ret);
-  else
-    return {UniqueDvrWriteBufferQueue(queue)};
-}
-
-Status<std::vector<uint8_t>> GetConfigData(int config_type) {
-  uint8_t* data = nullptr;
-  size_t data_size = 0;
-  int error = dvrConfigurationDataGet(config_type, &data, &data_size);
-  if (error < 0) {
-    return ErrorStatus(-error);
-  }
-
-  if (!data || data_size == 0) {
-    return ErrorStatus(EINVAL);
-  }
-  std::vector<uint8_t> data_result(data, data + data_size);
-  dvrConfigurationDataDestroy(data);
-  std::string s(data, data + data_size);
-  return {std::move(data_result)};
-}
-
-class TestDisplayManager {
- public:
-  TestDisplayManager(UniqueDvrDisplayManager display_manager,
-                     UniqueDvrSurfaceState surface_state)
-      : display_manager_(std::move(display_manager)),
-        surface_state_(std::move(surface_state)) {
-    const int fd = dvrDisplayManagerGetEventFd(display_manager_.get());
-    LOG_IF(INFO, fd < 0) << "Failed to get event fd: " << strerror(-fd);
-    display_manager_event_fd_ = fd;
-  }
-
-  Status<UniqueDvrReadBufferQueue> GetReadBufferQueue(int surface_id,
-                                                      int queue_id) {
-    DvrReadBufferQueue* queue;
-    const int ret = dvrDisplayManagerGetReadBufferQueue(
-        display_manager_.get(), surface_id, queue_id, &queue);
-    if (ret < 0)
-      return ErrorStatus(-ret);
-    else
-      return {UniqueDvrReadBufferQueue(queue)};
-  }
-
-  Status<void> UpdateSurfaceState() {
-    const int ret = dvrDisplayManagerGetSurfaceState(display_manager_.get(),
-                                                     surface_state_.get());
-    if (ret < 0)
-      return ErrorStatus(-ret);
-    else
-      return {};
-  }
-
-  enum : int { kTimeoutMs = 10000 };  // 10s
-
-  Status<void> WaitForUpdate(int timeout_ms = kTimeoutMs) {
-    if (display_manager_event_fd_ < 0)
-      return ErrorStatus(-display_manager_event_fd_);
-
-    pollfd pfd = {display_manager_event_fd_, POLLIN, 0};
-    const int count = poll(&pfd, 1, timeout_ms);
-    if (count < 0)
-      return ErrorStatus(errno);
-    else if (count == 0)
-      return ErrorStatus(ETIMEDOUT);
-
-    int events;
-    const int ret = dvrDisplayManagerTranslateEpollEventMask(
-        display_manager_.get(), pfd.revents, &events);
-    if (ret < 0)
-      return ErrorStatus(-ret);
-    else if (events & POLLIN)
-      return UpdateSurfaceState();
-    else
-      return ErrorStatus(EPROTO);
-  }
-
-  Status<size_t> GetSurfaceCount() {
-    size_t count = 0;
-    const int ret =
-        dvrSurfaceStateGetSurfaceCount(surface_state_.get(), &count);
-    if (ret < 0)
-      return ErrorStatus(-ret);
-    else
-      return {count};
-  }
-
-  Status<DvrSurfaceUpdateFlags> GetUpdateFlags(size_t surface_index) {
-    DvrSurfaceUpdateFlags update_flags;
-    const int ret = dvrSurfaceStateGetUpdateFlags(surface_state_.get(),
-                                                  surface_index, &update_flags);
-    if (ret < 0)
-      return ErrorStatus(-ret);
-    else
-      return {update_flags};
-  }
-
-  Status<int> GetSurfaceId(size_t surface_index) {
-    int surface_id;
-    const int ret = dvrSurfaceStateGetSurfaceId(surface_state_.get(),
-                                                surface_index, &surface_id);
-    if (ret < 0)
-      return ErrorStatus(-ret);
-    else
-      return {surface_id};
-  }
-
-  Status<int> GetProcessId(size_t surface_index) {
-    int process_id;
-    const int ret = dvrSurfaceStateGetProcessId(surface_state_.get(),
-                                                surface_index, &process_id);
-    if (ret < 0)
-      return ErrorStatus(-ret);
-    else
-      return {process_id};
-  }
-
-  Status<std::vector<DvrSurfaceAttribute>> GetAttributes(size_t surface_index) {
-    std::vector<DvrSurfaceAttribute> attributes;
-    size_t count = 0;
-    const int ret = dvrSurfaceStateGetAttributeCount(surface_state_.get(),
-                                                     surface_index, &count);
-    if (ret < 0)
-      return ErrorStatus(-ret);
-
-    attributes.resize(count);
-    const ssize_t return_count = dvrSurfaceStateGetAttributes(
-        surface_state_.get(), surface_index, attributes.data(), count);
-    if (return_count < 0)
-      return ErrorStatus(-return_count);
-
-    attributes.resize(return_count);
-    return {std::move(attributes)};
-  }
-
-  Status<std::vector<int>> GetQueueIds(size_t surface_index) {
-    std::vector<int> queue_ids;
-    size_t count = 0;
-    const int ret = dvrSurfaceStateGetQueueCount(surface_state_.get(),
-                                                 surface_index, &count);
-    if (ret < 0)
-      return ErrorStatus(-ret);
-
-    if (count > 0) {
-      queue_ids.resize(count);
-      const ssize_t return_count = dvrSurfaceStateGetQueueIds(
-          surface_state_.get(), surface_index, queue_ids.data(), count);
-      if (return_count < 0)
-        return ErrorStatus(-return_count);
-
-      queue_ids.resize(return_count);
-    }
-
-    return {std::move(queue_ids)};
-  }
-
- private:
-  UniqueDvrDisplayManager display_manager_;
-  UniqueDvrSurfaceState surface_state_;
-
-  // Owned by object in display_manager_, do not explicitly close.
-  int display_manager_event_fd_;
-
-  TestDisplayManager(const TestDisplayManager&) = delete;
-  void operator=(const TestDisplayManager&) = delete;
-};
-
-class DvrDisplayManagerTest : public Test {
- protected:
-  void SetUp() override {
-    int ret;
-    DvrDisplayManager* display_manager;
-    DvrSurfaceState* surface_state;
-
-    ret = dvrDisplayManagerCreate(&display_manager);
-    ASSERT_EQ(0, ret) << "Failed to create display manager client";
-    ASSERT_NE(nullptr, display_manager);
-
-    ret = dvrSurfaceStateCreate(&surface_state);
-    ASSERT_EQ(0, ret) << "Failed to create surface state object";
-    ASSERT_NE(nullptr, surface_state);
-
-    manager_.reset(
-        new TestDisplayManager(UniqueDvrDisplayManager(display_manager),
-                               UniqueDvrSurfaceState(surface_state)));
-  }
-  void TearDown() override {}
-
-  std::unique_ptr<TestDisplayManager> manager_;
-};
-
-// TODO(eieio): Consider moving these somewhere more central because they are
-// broadly useful.
-
-template <typename T>
-testing::AssertionResult StatusOk(const char* status_expression,
-                                  const Status<T>& status) {
-  if (!status.ok()) {
-    return testing::AssertionFailure()
-           << "(" << status_expression
-           << ") expected to indicate success but actually contains error ("
-           << status.error() << ")";
-  } else {
-    return testing::AssertionSuccess();
-  }
-}
-
-template <typename T>
-testing::AssertionResult StatusError(const char* status_expression,
-                                     const Status<T>& status) {
-  if (status.ok()) {
-    return testing::AssertionFailure()
-           << "(" << status_expression
-           << ") expected to indicate error but instead indicates success.";
-  } else {
-    return testing::AssertionSuccess();
-  }
-}
-
-template <typename T>
-testing::AssertionResult StatusHasError(const char* status_expression,
-                                        const char* /*error_code_expression*/,
-                                        const Status<T>& status,
-                                        int error_code) {
-  if (status.ok()) {
-    return StatusError(status_expression, status);
-  } else if (status.error() != error_code) {
-    return testing::AssertionFailure()
-           << "(" << status_expression << ") expected to indicate error ("
-           << error_code << ") but actually indicates error (" << status.error()
-           << ")";
-  } else {
-    return testing::AssertionSuccess();
-  }
-}
-
-template <typename T, typename U>
-testing::AssertionResult StatusHasValue(const char* status_expression,
-                                        const char* /*value_expression*/,
-                                        const Status<T>& status,
-                                        const U& value) {
-  if (!status.ok()) {
-    return StatusOk(status_expression, status);
-  } else if (status.get() != value) {
-    return testing::AssertionFailure()
-           << "(" << status_expression << ") expected to contain value ("
-           << testing::PrintToString(value) << ") but actually contains value ("
-           << testing::PrintToString(status.get()) << ")";
-  } else {
-    return testing::AssertionSuccess();
-  }
-}
-
-template <typename T, typename Op>
-testing::AssertionResult StatusPred(const char* status_expression,
-                                    const char* pred_expression,
-                                    const Status<T>& status, Op pred) {
-  if (!status.ok()) {
-    return StatusOk(status_expression, status);
-  } else if (!pred(status.get())) {
-    return testing::AssertionFailure()
-           << status_expression << " value ("
-           << testing::PrintToString(status.get())
-           << ") failed to pass predicate " << pred_expression;
-  } else {
-    return testing::AssertionSuccess();
-  }
-}
-
-#define ASSERT_STATUS_OK(status) ASSERT_PRED_FORMAT1(StatusOk, status)
-#define ASSERT_STATUS_ERROR(status) ASSERT_PRED_FORMAT1(StatusError, status)
-
-#define ASSERT_STATUS_ERROR_VALUE(value, status) \
-  ASSERT_PRED_FORMAT2(StatusHasError, status, value)
-
-#define ASSERT_STATUS_EQ(value, status) \
-  ASSERT_PRED_FORMAT2(StatusHasValue, status, value)
-
-#define EXPECT_STATUS_OK(status) EXPECT_PRED_FORMAT1(StatusOk, status)
-#define EXPECT_STATUS_ERROR(status) EXPECT_PRED_FORMAT1(StatusError, status)
-
-#define EXPECT_STATUS_ERROR_VALUE(value, status) \
-  EXPECT_PRED_FORMAT2(StatusHasError, status, value)
-
-#define EXPECT_STATUS_EQ(value, status) \
-  EXPECT_PRED_FORMAT2(StatusHasValue, status, value)
-
-#define EXPECT_STATUS_PRED(pred, status) \
-  EXPECT_PRED_FORMAT2(StatusPred, status, pred)
-
-#if 0
-// Verify utility predicate/macro functionality. This section is commented out
-// because it is designed to fail in some cases to validate the helpers.
-TEST_F(Test, ExpectVoid) {
-  Status<void> status_error{ErrorStatus{EINVAL}};
-  Status<void> status_ok{};
-
-  EXPECT_STATUS_ERROR(status_error);
-  EXPECT_STATUS_ERROR(status_ok);
-  EXPECT_STATUS_OK(status_error);
-  EXPECT_STATUS_OK(status_ok);
-
-  EXPECT_STATUS_ERROR_VALUE(EINVAL, status_error);
-  EXPECT_STATUS_ERROR_VALUE(ENOMEM, status_error);
-  EXPECT_STATUS_ERROR_VALUE(EINVAL, status_ok);
-  EXPECT_STATUS_ERROR_VALUE(ENOMEM, status_ok);
-}
-
-TEST_F(Test, ExpectInt) {
-  Status<int> status_error{ErrorStatus{EINVAL}};
-  Status<int> status_ok{10};
-
-  EXPECT_STATUS_ERROR(status_error);
-  EXPECT_STATUS_ERROR(status_ok);
-  EXPECT_STATUS_OK(status_error);
-  EXPECT_STATUS_OK(status_ok);
-
-  EXPECT_STATUS_ERROR_VALUE(EINVAL, status_error);
-  EXPECT_STATUS_ERROR_VALUE(ENOMEM, status_error);
-  EXPECT_STATUS_ERROR_VALUE(EINVAL, status_ok);
-  EXPECT_STATUS_ERROR_VALUE(ENOMEM, status_ok);
-
-  EXPECT_STATUS_EQ(10, status_error);
-  EXPECT_STATUS_EQ(20, status_error);
-  EXPECT_STATUS_EQ(10, status_ok);
-  EXPECT_STATUS_EQ(20, status_ok);
-
-  auto pred1 = [](const auto& value) { return value < 15; };
-  auto pred2 = [](const auto& value) { return value > 5; };
-  auto pred3 = [](const auto& value) { return value > 15; };
-  auto pred4 = [](const auto& value) { return value < 5; };
-
-  EXPECT_STATUS_PRED(pred1, status_error);
-  EXPECT_STATUS_PRED(pred2, status_error);
-  EXPECT_STATUS_PRED(pred3, status_error);
-  EXPECT_STATUS_PRED(pred4, status_error);
-  EXPECT_STATUS_PRED(pred1, status_ok);
-  EXPECT_STATUS_PRED(pred2, status_ok);
-  EXPECT_STATUS_PRED(pred3, status_ok);
-  EXPECT_STATUS_PRED(pred4, status_ok);
-}
-#endif
-
-TEST_F(DvrDisplayManagerTest, SurfaceCreateEvent) {
-  // Get surface state and verify there are no surfaces.
-  ASSERT_STATUS_OK(manager_->UpdateSurfaceState());
-  ASSERT_STATUS_EQ(0u, manager_->GetSurfaceCount());
-
-  // Get flags for invalid surface index.
-  EXPECT_STATUS_ERROR_VALUE(EINVAL, manager_->GetUpdateFlags(0));
-
-  // Create an application surface.
-  auto surface_status = CreateApplicationSurface();
-  ASSERT_STATUS_OK(surface_status);
-  UniqueDvrSurface surface = surface_status.take();
-  ASSERT_NE(nullptr, surface.get());
-
-  const int surface_id = dvrSurfaceGetId(surface.get());
-  ASSERT_GE(surface_id, 0);
-
-  // Now there should be one new surface.
-  ASSERT_STATUS_OK(manager_->WaitForUpdate());
-  EXPECT_STATUS_EQ(1u, manager_->GetSurfaceCount());
-
-  // Verify the new surface flag is set.
-  auto check_flags = [](const auto& value) {
-    return value & DVR_SURFACE_UPDATE_FLAGS_NEW_SURFACE;
-  };
-  EXPECT_STATUS_PRED(check_flags, manager_->GetUpdateFlags(0));
-
-  // Verify the surface id matches.
-  EXPECT_STATUS_EQ(surface_id, manager_->GetSurfaceId(0));
-
-  // Verify the owning process of the surface.
-  EXPECT_STATUS_EQ(getpid(), manager_->GetProcessId(0));
-
-  surface.reset();
-
-  ASSERT_STATUS_OK(manager_->WaitForUpdate());
-  EXPECT_STATUS_EQ(0u, manager_->GetSurfaceCount());
-}
-
-TEST_F(DvrDisplayManagerTest, SurfaceAttributeEvent) {
-  // Get surface state and verify there are no surfaces.
-  ASSERT_STATUS_OK(manager_->UpdateSurfaceState());
-  ASSERT_STATUS_EQ(0u, manager_->GetSurfaceCount());
-
-  // Get attributes for an invalid surface index.
-  EXPECT_STATUS_ERROR_VALUE(EINVAL, manager_->GetAttributes(0));
-
-  const bool kInitialVisibility = true;
-  const int32_t kInitialZOrder = 10;
-  auto surface_status =
-      CreateApplicationSurface(kInitialVisibility, kInitialZOrder);
-  ASSERT_STATUS_OK(surface_status);
-  auto surface = surface_status.take();
-  ASSERT_NE(nullptr, surface.get());
-
-  ASSERT_STATUS_OK(manager_->WaitForUpdate());
-  ASSERT_STATUS_EQ(1u, manager_->GetSurfaceCount());
-
-  // Check the initial attribute values.
-  auto attribute_status = manager_->GetAttributes(0);
-  ASSERT_STATUS_OK(attribute_status);
-  auto attributes = attribute_status.take();
-  EXPECT_GE(attributes.size(), 2u);
-
-  std::set<int32_t> actual_keys;
-  std::set<int32_t> expected_keys = {DVR_SURFACE_ATTRIBUTE_Z_ORDER,
-                                     DVR_SURFACE_ATTRIBUTE_VISIBLE};
-
-  // Collect all the keys in attributes that match the expected keys.
-  auto compare_keys = [](const auto& attributes, const auto& expected_keys) {
-    std::set<int32_t> keys;
-    for (const auto& attribute : attributes) {
-      if (expected_keys.find(attribute.key) != expected_keys.end())
-        keys.emplace(attribute.key);
-    }
-    return keys;
-  };
-
-  // If the sets match then attributes contained at least the expected keys,
-  // even if other keys were also present.
-  actual_keys = compare_keys(attributes, expected_keys);
-  EXPECT_EQ(expected_keys, actual_keys);
-
-  std::vector<DvrSurfaceAttribute> attributes_to_set = {
-      MakeAttribute(DVR_SURFACE_ATTRIBUTE_Z_ORDER, 0)};
-
-  // Test invalid args.
-  EXPECT_EQ(-EINVAL, dvrSurfaceSetAttributes(nullptr, attributes_to_set.data(),
-                                             attributes_to_set.size()));
-  EXPECT_EQ(-EINVAL, dvrSurfaceSetAttributes(surface.get(), nullptr,
-                                             attributes_to_set.size()));
-
-  // Test attribute change events.
-  ASSERT_EQ(0, dvrSurfaceSetAttributes(surface.get(), attributes_to_set.data(),
-                                       attributes_to_set.size()));
-  ASSERT_STATUS_OK(manager_->WaitForUpdate());
-
-  // Verify the attributes changed flag is set.
-  auto check_flags = [](const auto& value) {
-    return value & DVR_SURFACE_UPDATE_FLAGS_ATTRIBUTES_CHANGED;
-  };
-  EXPECT_STATUS_PRED(check_flags, manager_->GetUpdateFlags(0));
-
-  attribute_status = manager_->GetAttributes(0);
-  ASSERT_STATUS_OK(attribute_status);
-  attributes = attribute_status.take();
-  EXPECT_GE(attributes.size(), 2u);
-
-  expected_keys = {DVR_SURFACE_ATTRIBUTE_Z_ORDER,
-                   DVR_SURFACE_ATTRIBUTE_VISIBLE};
-
-  actual_keys.clear();
-  actual_keys = compare_keys(attributes, expected_keys);
-  EXPECT_EQ(expected_keys, actual_keys);
-
-  // Test setting and then deleting an attribute.
-  const DvrSurfaceAttributeKey kUserKey = 1;
-  attributes_to_set = {MakeAttribute(kUserKey, 1024)};
-
-  ASSERT_EQ(0, dvrSurfaceSetAttributes(surface.get(), attributes_to_set.data(),
-                                       attributes_to_set.size()));
-  ASSERT_STATUS_OK(manager_->WaitForUpdate());
-  EXPECT_STATUS_PRED(check_flags, manager_->GetUpdateFlags(0));
-
-  attribute_status = manager_->GetAttributes(0);
-  ASSERT_STATUS_OK(attribute_status);
-  attributes = attribute_status.take();
-  EXPECT_GE(attributes.size(), 2u);
-
-  expected_keys = {DVR_SURFACE_ATTRIBUTE_Z_ORDER, DVR_SURFACE_ATTRIBUTE_VISIBLE,
-                   kUserKey};
-
-  actual_keys.clear();
-  actual_keys = compare_keys(attributes, expected_keys);
-  EXPECT_EQ(expected_keys, actual_keys);
-
-  // Delete the attribute.
-  attributes_to_set = {MakeAttribute(kUserKey, nullptr)};
-
-  ASSERT_EQ(0, dvrSurfaceSetAttributes(surface.get(), attributes_to_set.data(),
-                                       attributes_to_set.size()));
-  ASSERT_STATUS_OK(manager_->WaitForUpdate());
-  EXPECT_STATUS_PRED(check_flags, manager_->GetUpdateFlags(0));
-
-  attribute_status = manager_->GetAttributes(0);
-  ASSERT_STATUS_OK(attribute_status);
-  attributes = attribute_status.take();
-  EXPECT_GE(attributes.size(), 2u);
-
-  expected_keys = {DVR_SURFACE_ATTRIBUTE_Z_ORDER, DVR_SURFACE_ATTRIBUTE_VISIBLE,
-                   kUserKey};
-
-  actual_keys.clear();
-  actual_keys = compare_keys(attributes, expected_keys);
-  EXPECT_NE(expected_keys, actual_keys);
-
-  // Test deleting a reserved attribute.
-  attributes_to_set = {MakeAttribute(DVR_SURFACE_ATTRIBUTE_VISIBLE, nullptr)};
-
-  EXPECT_EQ(0, dvrSurfaceSetAttributes(surface.get(), attributes_to_set.data(),
-                                       attributes_to_set.size()));
-
-  // Failed attribute operations should not trigger update events.
-  const int kTimeoutMs = 100;  // 0.1s
-  EXPECT_STATUS_ERROR_VALUE(ETIMEDOUT, manager_->WaitForUpdate(kTimeoutMs));
-
-  attribute_status = manager_->GetAttributes(0);
-  ASSERT_STATUS_OK(attribute_status);
-  attributes = attribute_status.take();
-  EXPECT_GE(attributes.size(), 2u);
-
-  expected_keys = {DVR_SURFACE_ATTRIBUTE_Z_ORDER,
-                   DVR_SURFACE_ATTRIBUTE_VISIBLE};
-
-  actual_keys.clear();
-  actual_keys = compare_keys(attributes, expected_keys);
-  EXPECT_EQ(expected_keys, actual_keys);
-}
-
-TEST_F(DvrDisplayManagerTest, SurfaceAttributeTypes) {
-  // Create an application surface.
-  auto surface_status = CreateApplicationSurface();
-  ASSERT_STATUS_OK(surface_status);
-  UniqueDvrSurface surface = surface_status.take();
-  ASSERT_NE(nullptr, surface.get());
-
-  enum : std::int32_t {
-    kInt32Key = 1,
-    kInt64Key,
-    kBoolKey,
-    kFloatKey,
-    kFloat2Key,
-    kFloat3Key,
-    kFloat4Key,
-    kFloat8Key,
-    kFloat16Key,
-  };
-
-  const std::vector<DvrSurfaceAttribute> attributes_to_set = {
-      MakeAttribute(kInt32Key, int32_t{0}),
-      MakeAttribute(kInt64Key, int64_t{0}),
-      MakeAttribute(kBoolKey, false),
-      MakeAttribute(kFloatKey, 0.0f),
-      MakeAttribute(kFloat2Key, std::array<float, 2>{{1.0f, 2.0f}}),
-      MakeAttribute(kFloat3Key, std::array<float, 3>{{3.0f, 4.0f, 5.0f}}),
-      MakeAttribute(kFloat4Key, std::array<float, 4>{{6.0f, 7.0f, 8.0f, 9.0f}}),
-      MakeAttribute(kFloat8Key,
-                    std::array<float, 8>{{10.0f, 11.0f, 12.0f, 13.0f, 14.0f,
-                                          15.0f, 16.0f, 17.0f}}),
-      MakeAttribute(kFloat16Key, std::array<float, 16>{
-                                     {18.0f, 19.0f, 20.0f, 21.0f, 22.0f, 23.0f,
-                                      24.0f, 25.0f, 26.0f, 27.0f, 28.0f, 29.0f,
-                                      30.0f, 31.0f, 32.0f, 33.0f}})};
-
-  EXPECT_EQ(0, dvrSurfaceSetAttributes(surface.get(), attributes_to_set.data(),
-                                       attributes_to_set.size()));
-
-  ASSERT_STATUS_OK(manager_->WaitForUpdate());
-  auto attribute_status = manager_->GetAttributes(0);
-  ASSERT_STATUS_OK(attribute_status);
-  auto attributes = attribute_status.take();
-  EXPECT_GE(attributes.size(), attributes_to_set.size());
-
-  auto HasAttribute = [](const auto& attributes,
-                         DvrSurfaceAttributeKey key) -> bool {
-    for (const auto& attribute : attributes) {
-      if (attribute.key == key)
-        return true;
-    }
-    return false;
-  };
-  auto AttributeType =
-      [](const auto& attributes,
-         DvrSurfaceAttributeKey key) -> DvrSurfaceAttributeType {
-    for (const auto& attribute : attributes) {
-      if (attribute.key == key)
-        return attribute.value.type;
-    }
-    return DVR_SURFACE_ATTRIBUTE_TYPE_NONE;
-  };
-
-  ASSERT_TRUE(HasAttribute(attributes, kInt32Key));
-  EXPECT_EQ(DVR_SURFACE_ATTRIBUTE_TYPE_INT32,
-            AttributeType(attributes, kInt32Key));
-
-  ASSERT_TRUE(HasAttribute(attributes, kInt64Key));
-  EXPECT_EQ(DVR_SURFACE_ATTRIBUTE_TYPE_INT64,
-            AttributeType(attributes, kInt64Key));
-
-  ASSERT_TRUE(HasAttribute(attributes, kBoolKey));
-  EXPECT_EQ(DVR_SURFACE_ATTRIBUTE_TYPE_BOOL,
-            AttributeType(attributes, kBoolKey));
-
-  ASSERT_TRUE(HasAttribute(attributes, kFloatKey));
-  EXPECT_EQ(DVR_SURFACE_ATTRIBUTE_TYPE_FLOAT,
-            AttributeType(attributes, kFloatKey));
-
-  ASSERT_TRUE(HasAttribute(attributes, kFloat2Key));
-  EXPECT_EQ(DVR_SURFACE_ATTRIBUTE_TYPE_FLOAT2,
-            AttributeType(attributes, kFloat2Key));
-
-  ASSERT_TRUE(HasAttribute(attributes, kFloat3Key));
-  EXPECT_EQ(DVR_SURFACE_ATTRIBUTE_TYPE_FLOAT3,
-            AttributeType(attributes, kFloat3Key));
-
-  ASSERT_TRUE(HasAttribute(attributes, kFloat4Key));
-  EXPECT_EQ(DVR_SURFACE_ATTRIBUTE_TYPE_FLOAT4,
-            AttributeType(attributes, kFloat4Key));
-
-  ASSERT_TRUE(HasAttribute(attributes, kFloat8Key));
-  EXPECT_EQ(DVR_SURFACE_ATTRIBUTE_TYPE_FLOAT8,
-            AttributeType(attributes, kFloat8Key));
-
-  ASSERT_TRUE(HasAttribute(attributes, kFloat16Key));
-  EXPECT_EQ(DVR_SURFACE_ATTRIBUTE_TYPE_FLOAT16,
-            AttributeType(attributes, kFloat16Key));
-}
-
-TEST_F(DvrDisplayManagerTest, SurfaceQueueEvent) {
-  // Create an application surface.
-  auto surface_status = CreateApplicationSurface();
-  ASSERT_STATUS_OK(surface_status);
-  UniqueDvrSurface surface = surface_status.take();
-  ASSERT_NE(nullptr, surface.get());
-
-  const int surface_id = dvrSurfaceGetId(surface.get());
-  ASSERT_GE(surface_id, 0);
-  // Get surface state and verify there is one surface.
-  ASSERT_STATUS_OK(manager_->WaitForUpdate());
-  ASSERT_STATUS_EQ(1u, manager_->GetSurfaceCount());
-
-  // Verify there are no queues for the surface recorded in the state
-  // snapshot.
-  EXPECT_STATUS_EQ(std::vector<int>{}, manager_->GetQueueIds(0));
-
-  // Create a new queue in the surface.
-  auto write_queue_status = CreateSurfaceQueue(
-      surface, 320, 240, AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM, 1,
-      AHARDWAREBUFFER_USAGE_CPU_READ_RARELY, 1, 0);
-  ASSERT_STATUS_OK(write_queue_status);
-  UniqueDvrWriteBufferQueue write_queue = write_queue_status.take();
-  ASSERT_NE(nullptr, write_queue.get());
-
-  const int queue_id = dvrWriteBufferQueueGetId(write_queue.get());
-  ASSERT_GE(queue_id, 0);
-
-  // Update surface state.
-  ASSERT_STATUS_OK(manager_->WaitForUpdate());
-  ASSERT_STATUS_EQ(1u, manager_->GetSurfaceCount());
-
-  // Verify the buffers changed flag is set.
-  auto check_flags = [](const auto& value) {
-    return value & DVR_SURFACE_UPDATE_FLAGS_BUFFERS_CHANGED;
-  };
-  EXPECT_STATUS_PRED(check_flags, manager_->GetUpdateFlags(0));
-
-  auto queue_ids_status = manager_->GetQueueIds(0);
-  ASSERT_STATUS_OK(queue_ids_status);
-
-  auto queue_ids = queue_ids_status.take();
-  ASSERT_EQ(1u, queue_ids.size());
-  EXPECT_EQ(queue_id, queue_ids[0]);
-
-  auto read_queue_status = manager_->GetReadBufferQueue(surface_id, queue_id);
-  ASSERT_STATUS_OK(read_queue_status);
-  UniqueDvrReadBufferQueue read_queue = read_queue_status.take();
-  ASSERT_NE(nullptr, read_queue.get());
-  EXPECT_EQ(queue_id, dvrReadBufferQueueGetId(read_queue.get()));
-
-  write_queue.reset();
-
-  // Verify that destroying the queue generates a surface update event.
-  ASSERT_STATUS_OK(manager_->WaitForUpdate());
-  ASSERT_STATUS_EQ(1u, manager_->GetSurfaceCount());
-
-  // Verify that the buffers changed flag is set.
-  EXPECT_STATUS_PRED(check_flags, manager_->GetUpdateFlags(0));
-
-  // Verify that the queue ids reflect the change.
-  queue_ids_status = manager_->GetQueueIds(0);
-  ASSERT_STATUS_OK(queue_ids_status);
-
-  queue_ids = queue_ids_status.take();
-  ASSERT_EQ(0u, queue_ids.size());
-}
-
-TEST_F(DvrDisplayManagerTest, MultiLayerBufferQueue) {
-  // Create an application surface.
-  auto surface_status = CreateApplicationSurface();
-  ASSERT_STATUS_OK(surface_status);
-  UniqueDvrSurface surface = surface_status.take();
-  ASSERT_NE(nullptr, surface.get());
-
-  // Get surface state and verify there is one surface.
-  ASSERT_STATUS_OK(manager_->WaitForUpdate());
-  ASSERT_STATUS_EQ(1u, manager_->GetSurfaceCount());
-
-  // Create a new queue in the surface.
-  const uint32_t kLayerCount = 3;
-  auto write_queue_status = CreateSurfaceQueue(
-      surface, 320, 240, AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM, kLayerCount,
-      AHARDWAREBUFFER_USAGE_CPU_READ_RARELY, 1, 0);
-  ASSERT_STATUS_OK(write_queue_status);
-  UniqueDvrWriteBufferQueue write_queue = write_queue_status.take();
-  ASSERT_NE(nullptr, write_queue.get());
-
-  DvrWriteBuffer* buffer = nullptr;
-  DvrNativeBufferMetadata metadata;
-  int fence_fd = -1;
-  int error = dvrWriteBufferQueueGainBuffer(write_queue.get(), /*timeout=*/1000,
-                                            &buffer, &metadata, &fence_fd);
-  ASSERT_EQ(0, error);
-
-  AHardwareBuffer* hardware_buffer = nullptr;
-  error = dvrWriteBufferGetAHardwareBuffer(buffer, &hardware_buffer);
-  ASSERT_EQ(0, error);
-
-  AHardwareBuffer_Desc desc = {};
-  AHardwareBuffer_describe(hardware_buffer, &desc);
-  ASSERT_EQ(kLayerCount, desc.layers);
-
-  AHardwareBuffer_release(hardware_buffer);
-  dvrWriteBufferDestroy(buffer);
-}
-
-TEST_F(Test, ConfigurationData) {
-  // TODO(hendrikw): Move this test and GetConfigData helper function out of the
-  // display manager tests.
-  auto data1 = GetConfigData(-1);
-  ASSERT_STATUS_ERROR(data1);
-
-  const char kDvrLensMetricsProperty[] = "ro.dvr.lens_metrics";
-
-  // This should be run on devices with and without built in metrics.
-  bool has_metric = !base::GetProperty(kDvrLensMetricsProperty, "").empty();
-  auto data2 = GetConfigData(DVR_CONFIGURATION_DATA_LENS_METRICS);
-  if (has_metric) {
-    ASSERT_STATUS_OK(data2);
-    ASSERT_NE(0u, data2.get().size());
-  } else {
-    ASSERT_STATUS_ERROR(data2);
-  }
-}
-
-}  // namespace
-
-}  // namespace dvr
-}  // namespace android
diff --git a/libs/vr/libdvr/tests/dvr_named_buffer-test.cpp b/libs/vr/libdvr/tests/dvr_named_buffer-test.cpp
deleted file mode 100644
index 5c837e7..0000000
--- a/libs/vr/libdvr/tests/dvr_named_buffer-test.cpp
+++ /dev/null
@@ -1,284 +0,0 @@
-#include <android/hardware_buffer.h>
-#include <dvr/dvr_buffer.h>
-#include <dvr/dvr_config.h>
-#include <dvr/dvr_shared_buffers.h>
-#include <dvr/dvr_surface.h>
-#include <system/graphics.h>
-
-#include <gtest/gtest.h>
-
-namespace android {
-namespace dvr {
-
-namespace {
-
-TEST(DvrGlobalBufferTest, TestGlobalBuffersSameName) {
-  const DvrGlobalBufferKey buffer_key = 101;
-  DvrBuffer* buffer1 = nullptr;
-  int ret1 = dvrSetupGlobalBuffer(buffer_key, 10, 0, &buffer1);
-  ASSERT_EQ(0, ret1);
-  ASSERT_NE(nullptr, buffer1);
-
-  DvrBuffer* buffer2 = nullptr;
-  int ret2 = dvrSetupGlobalBuffer(buffer_key, 10, 0, &buffer2);
-  ASSERT_EQ(0, ret2);
-  ASSERT_NE(nullptr, buffer2);
-
-  AHardwareBuffer* hardware_buffer1 = nullptr;
-  int e1 = dvrBufferGetAHardwareBuffer(buffer1, &hardware_buffer1);
-  ASSERT_EQ(0, e1);
-  ASSERT_NE(nullptr, hardware_buffer1);
-
-  AHardwareBuffer* hardware_buffer2 = nullptr;
-  int e2 = dvrBufferGetAHardwareBuffer(buffer2, &hardware_buffer2);
-  ASSERT_EQ(0, e2);
-  ASSERT_NE(nullptr, hardware_buffer2);
-
-  AHardwareBuffer_Desc desc1 = {};
-  AHardwareBuffer_describe(hardware_buffer1, &desc1);
-  AHardwareBuffer_Desc desc2 = {};
-  AHardwareBuffer_describe(hardware_buffer2, &desc2);
-  ASSERT_EQ(desc1.width, 10u);
-  ASSERT_EQ(desc1.height, 1u);
-  ASSERT_EQ(desc1.layers, 1u);
-  ASSERT_EQ(desc1.format, HAL_PIXEL_FORMAT_BLOB);
-  ASSERT_EQ(desc1.usage, 0u);
-  ASSERT_EQ(desc2.width, 10u);
-  ASSERT_EQ(desc2.height, 1u);
-  ASSERT_EQ(desc2.layers, 1u);
-  ASSERT_EQ(desc2.format, HAL_PIXEL_FORMAT_BLOB);
-  ASSERT_EQ(desc2.usage, 0u);
-
-  dvrBufferDestroy(buffer1);
-  dvrBufferDestroy(buffer2);
-
-  DvrBuffer* buffer3 = nullptr;
-  int e3 = dvrGetGlobalBuffer(buffer_key, &buffer3);
-  ASSERT_NE(nullptr, buffer3);
-  ASSERT_EQ(0, e3);
-
-  AHardwareBuffer* hardware_buffer3 = nullptr;
-  int e4 = dvrBufferGetAHardwareBuffer(buffer3, &hardware_buffer3);
-  ASSERT_EQ(0, e4);
-  ASSERT_NE(nullptr, hardware_buffer3);
-
-  AHardwareBuffer_Desc desc3 = {};
-  AHardwareBuffer_describe(hardware_buffer3, &desc3);
-  ASSERT_EQ(desc3.width, 10u);
-  ASSERT_EQ(desc3.height, 1u);
-  ASSERT_EQ(desc3.layers, 1u);
-  ASSERT_EQ(desc3.format, HAL_PIXEL_FORMAT_BLOB);
-  ASSERT_EQ(desc3.usage, 0u);
-
-  dvrBufferDestroy(buffer3);
-
-  AHardwareBuffer_release(hardware_buffer1);
-  AHardwareBuffer_release(hardware_buffer2);
-  AHardwareBuffer_release(hardware_buffer3);
-}
-
-TEST(DvrGlobalBufferTest, TestMultipleGlobalBuffers) {
-  const DvrGlobalBufferKey buffer_key1 = 102;
-  const DvrGlobalBufferKey buffer_key2 = 103;
-  DvrBuffer* setup_buffer1 = nullptr;
-  int ret1 = dvrSetupGlobalBuffer(buffer_key1, 10, 0, &setup_buffer1);
-  ASSERT_EQ(0, ret1);
-  ASSERT_NE(nullptr, setup_buffer1);
-  dvrBufferDestroy(setup_buffer1);
-
-  DvrBuffer* setup_buffer2 = nullptr;
-  int ret2 = dvrSetupGlobalBuffer(buffer_key2, 10, 0, &setup_buffer2);
-  ASSERT_EQ(0, ret2);
-  ASSERT_NE(nullptr, setup_buffer2);
-  dvrBufferDestroy(setup_buffer2);
-
-  DvrBuffer* buffer1 = nullptr;
-  int e1 = dvrGetGlobalBuffer(buffer_key1, &buffer1);
-  ASSERT_NE(nullptr, buffer1);
-  ASSERT_EQ(0, e1);
-  dvrBufferDestroy(buffer1);
-
-  DvrBuffer* buffer2 = nullptr;
-  int e2 = dvrGetGlobalBuffer(buffer_key2, &buffer2);
-  ASSERT_NE(nullptr, buffer2);
-  ASSERT_EQ(0, e2);
-  dvrBufferDestroy(buffer2);
-}
-
-TEST(DvrGlobalBufferTest, TestGlobalBufferUsage) {
-  const DvrGlobalBufferKey buffer_key = 100;
-
-  // Set usage to AHARDWAREBUFFER_USAGE_VIDEO_ENCODE. We use this because
-  // internally AHARDWAREBUFFER_USAGE_VIDEO_ENCODE is converted to
-  // GRALLOC1_CONSUMER_USAGE_VIDEO_ENCODER, and these two values are different.
-  // If all is good, when we get the AHardwareBuffer, it should be converted
-  // back to AHARDWAREBUFFER_USAGE_VIDEO_ENCODE.
-  const uint64_t usage = AHARDWAREBUFFER_USAGE_VIDEO_ENCODE;
-
-  DvrBuffer* setup_buffer = nullptr;
-  int e1 = dvrSetupGlobalBuffer(buffer_key, 10, usage, &setup_buffer);
-  ASSERT_NE(nullptr, setup_buffer);
-  ASSERT_EQ(0, e1);
-
-  AHardwareBuffer* hardware_buffer = nullptr;
-  int e2 = dvrBufferGetAHardwareBuffer(setup_buffer, &hardware_buffer);
-  ASSERT_EQ(0, e2);
-  ASSERT_NE(nullptr, hardware_buffer);
-
-  AHardwareBuffer_Desc desc = {};
-  AHardwareBuffer_describe(hardware_buffer, &desc);
-  ASSERT_EQ(usage, desc.usage);
-
-  dvrBufferDestroy(setup_buffer);
-  AHardwareBuffer_release(hardware_buffer);
-}
-
-TEST(DvrGlobalBufferTest, TestGlobalBufferCarriesData) {
-  const DvrGlobalBufferKey buffer_name = 110;
-
-  uint64_t usage = AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN |
-                   AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN;
-  constexpr size_t size = 1024 * sizeof(uint64_t);
-  constexpr uint64_t value = 0x123456787654321;
-
-  {
-    // Allocate some data and set it to something.
-    DvrBuffer* setup_buffer = nullptr;
-    int e1 = dvrSetupGlobalBuffer(buffer_name, size, usage, &setup_buffer);
-    ASSERT_NE(nullptr, setup_buffer);
-    ASSERT_EQ(0, e1);
-
-    AHardwareBuffer* hardware_buffer = nullptr;
-    int e2 = dvrBufferGetAHardwareBuffer(setup_buffer, &hardware_buffer);
-    ASSERT_EQ(0, e2);
-    ASSERT_NE(nullptr, hardware_buffer);
-
-    void* buffer;
-    int e3 = AHardwareBuffer_lock(hardware_buffer, usage, -1, nullptr, &buffer);
-    ASSERT_EQ(0, e3);
-    ASSERT_NE(nullptr, buffer);
-    // Verify that the buffer pointer is at least 16 byte aligned.
-    ASSERT_EQ(0U, reinterpret_cast<uintptr_t>(buffer) & (16 - 1));
-
-    uint64_t* data = static_cast<uint64_t*>(buffer);
-    constexpr size_t num_values = size / sizeof(uint64_t);
-    for (size_t i = 0; i < num_values; ++i) {
-      data[i] = value;
-    }
-
-    int32_t fence = -1;
-    int e4 = AHardwareBuffer_unlock(hardware_buffer, &fence);
-    ASSERT_EQ(0, e4);
-
-    dvrBufferDestroy(setup_buffer);
-    AHardwareBuffer_release(hardware_buffer);
-  }
-
-  {
-    // Get the buffer and check that all the data is still present.
-    DvrBuffer* setup_buffer = nullptr;
-    int e1 = dvrGetGlobalBuffer(buffer_name, &setup_buffer);
-    ASSERT_NE(nullptr, setup_buffer);
-    ASSERT_EQ(0, e1);
-
-    AHardwareBuffer* hardware_buffer = nullptr;
-    int e2 = dvrBufferGetAHardwareBuffer(setup_buffer, &hardware_buffer);
-    ASSERT_EQ(0, e2);
-    ASSERT_NE(nullptr, hardware_buffer);
-
-    void* buffer;
-    int e3 = AHardwareBuffer_lock(hardware_buffer, usage, -1, nullptr, &buffer);
-    ASSERT_EQ(0, e3);
-    ASSERT_NE(nullptr, buffer);
-    // Verify that the buffer pointer is at least 16 byte aligned.
-    ASSERT_EQ(0U, reinterpret_cast<uintptr_t>(buffer) & (16 - 1));
-
-    uint64_t* data = static_cast<uint64_t*>(buffer);
-    constexpr size_t num_values = size / sizeof(uint64_t);
-    bool is_equal = true;
-    for (size_t i = 0; i < num_values; ++i) {
-      is_equal &= (data[i] == value);
-    }
-    ASSERT_TRUE(is_equal);
-
-    int32_t fence = -1;
-    int e4 = AHardwareBuffer_unlock(hardware_buffer, &fence);
-    ASSERT_EQ(0, e4);
-
-    dvrBufferDestroy(setup_buffer);
-    AHardwareBuffer_release(hardware_buffer);
-  }
-}
-
-TEST(DvrGlobalBufferTest, TestGlobalBufferZeroed) {
-  const DvrGlobalBufferKey buffer_name = 120;
-
-  // Allocate 1MB and check that it is all zeros.
-  uint64_t usage = AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN |
-                   AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN;
-  constexpr size_t size = 1024 * 1024;
-  DvrBuffer* setup_buffer = nullptr;
-  int e1 = dvrSetupGlobalBuffer(buffer_name, size, usage, &setup_buffer);
-  ASSERT_NE(nullptr, setup_buffer);
-  ASSERT_EQ(0, e1);
-
-  AHardwareBuffer* hardware_buffer = nullptr;
-  int e2 = dvrBufferGetAHardwareBuffer(setup_buffer, &hardware_buffer);
-  ASSERT_EQ(0, e2);
-  ASSERT_NE(nullptr, hardware_buffer);
-
-  void* buffer;
-  int e3 = AHardwareBuffer_lock(hardware_buffer, usage, -1, nullptr, &buffer);
-  ASSERT_EQ(0, e3);
-  ASSERT_NE(nullptr, buffer);
-  // Verify that the buffer pointer is at least 16 byte aligned.
-  ASSERT_EQ(0U, reinterpret_cast<uintptr_t>(buffer) & (16 - 1));
-
-  uint64_t* data = static_cast<uint64_t*>(buffer);
-  constexpr size_t num_values = size / sizeof(uint64_t);
-  uint64_t zero = 0;
-  for (size_t i = 0; i < num_values; ++i) {
-    zero |= data[i];
-  }
-  ASSERT_EQ(0U, zero);
-
-  int32_t fence = -1;
-  int e4 = AHardwareBuffer_unlock(hardware_buffer, &fence);
-  ASSERT_EQ(0, e4);
-
-  dvrBufferDestroy(setup_buffer);
-  AHardwareBuffer_release(hardware_buffer);
-}
-
-TEST(DvrGlobalBufferTest, TestVrflingerConfigBuffer) {
-  const DvrGlobalBufferKey buffer_name =
-      DvrGlobalBuffers::kVrFlingerConfigBufferKey;
-
-  // First delete any existing buffer so we can test the failure case.
-  dvrDeleteGlobalBuffer(buffer_name);
-
-  const uint64_t usage = AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN |
-                         AHARDWAREBUFFER_USAGE_CPU_WRITE_RARELY;
-
-  size_t correct_size = DvrConfigRing::MemorySize();
-  size_t wrong_size = DvrConfigRing::MemorySize(0);
-
-  // Setup an invalid config buffer (too small) and assert that it fails.
-  DvrBuffer* setup_buffer = nullptr;
-  int e1 = dvrSetupGlobalBuffer(buffer_name, wrong_size, usage, &setup_buffer);
-  ASSERT_EQ(nullptr, setup_buffer);
-  ASSERT_GT(0, e1);
-
-  // Setup a correct config buffer.
-  int e2 =
-      dvrSetupGlobalBuffer(buffer_name, correct_size, usage, &setup_buffer);
-  ASSERT_NE(nullptr, setup_buffer);
-  ASSERT_EQ(0, e2);
-
-  dvrBufferDestroy(setup_buffer);
-}
-
-}  // namespace
-
-}  // namespace dvr
-}  // namespace android
diff --git a/libs/vr/libdvr/tests/dvr_tracking-test.cpp b/libs/vr/libdvr/tests/dvr_tracking-test.cpp
deleted file mode 100644
index 3b6d6e1..0000000
--- a/libs/vr/libdvr/tests/dvr_tracking-test.cpp
+++ /dev/null
@@ -1,103 +0,0 @@
-#include <android/log.h>
-#include <gtest/gtest.h>
-
-#include "dvr_api_test.h"
-
-namespace {
-
-class DvrTrackingTest : public DvrApiTest {};
-
-#if DVR_TRACKING_IMPLEMENTED
-
-TEST_F(DvrTrackingTest, Implemented) {
-  ASSERT_TRUE(api_.TrackingCameraCreate != nullptr);
-  ASSERT_TRUE(api_.TrackingCameraStart != nullptr);
-  ASSERT_TRUE(api_.TrackingCameraStop != nullptr);
-
-  ASSERT_TRUE(api_.TrackingFeatureExtractorCreate != nullptr);
-  ASSERT_TRUE(api_.TrackingFeatureExtractorDestroy != nullptr);
-  ASSERT_TRUE(api_.TrackingFeatureExtractorStart != nullptr);
-  ASSERT_TRUE(api_.TrackingFeatureExtractorStop != nullptr);
-  ASSERT_TRUE(api_.TrackingFeatureExtractorProcessBuffer != nullptr);
-}
-
-TEST_F(DvrTrackingTest, CameraCreateFailsForInvalidInput) {
-  int ret;
-  ret = api_.TrackingCameraCreate(nullptr);
-  EXPECT_EQ(ret, -EINVAL);
-
-  DvrTrackingCamera* camera = reinterpret_cast<DvrTrackingCamera*>(42);
-  ret = api_.TrackingCameraCreate(&camera);
-  EXPECT_EQ(ret, -EINVAL);
-}
-
-TEST_F(DvrTrackingTest, CameraCreateDestroy) {
-  DvrTrackingCamera* camera = nullptr;
-  int ret = api_.TrackingCameraCreate(&camera);
-
-  EXPECT_EQ(ret, 0);
-  ASSERT_TRUE(camera != nullptr);
-
-  api_.TrackingCameraDestroy(camera);
-}
-
-TEST_F(DvrTrackingTest, FeatureExtractorCreateFailsForInvalidInput) {
-  int ret;
-  ret = api_.TrackingFeatureExtractorCreate(nullptr);
-  EXPECT_EQ(ret, -EINVAL);
-
-  DvrTrackingFeatureExtractor* camera =
-      reinterpret_cast<DvrTrackingFeatureExtractor*>(42);
-  ret = api_.TrackingFeatureExtractorCreate(&camera);
-  EXPECT_EQ(ret, -EINVAL);
-}
-
-TEST_F(DvrTrackingTest, FeatureExtractorCreateDestroy) {
-  DvrTrackingFeatureExtractor* camera = nullptr;
-  int ret = api_.TrackingFeatureExtractorCreate(&camera);
-
-  EXPECT_EQ(ret, 0);
-  ASSERT_TRUE(camera != nullptr);
-
-  api_.TrackingFeatureExtractorDestroy(camera);
-}
-
-#else  // !DVR_TRACKING_IMPLEMENTED
-
-TEST_F(DvrTrackingTest, NotImplemented) {
-  ASSERT_TRUE(api_.TrackingCameraCreate != nullptr);
-  ASSERT_TRUE(api_.TrackingCameraDestroy != nullptr);
-  ASSERT_TRUE(api_.TrackingCameraStart != nullptr);
-  ASSERT_TRUE(api_.TrackingCameraStop != nullptr);
-
-  EXPECT_EQ(api_.TrackingCameraCreate(nullptr), -ENOSYS);
-  EXPECT_EQ(api_.TrackingCameraStart(nullptr, nullptr), -ENOSYS);
-  EXPECT_EQ(api_.TrackingCameraStop(nullptr), -ENOSYS);
-
-  ASSERT_TRUE(api_.TrackingFeatureExtractorCreate != nullptr);
-  ASSERT_TRUE(api_.TrackingFeatureExtractorDestroy != nullptr);
-  ASSERT_TRUE(api_.TrackingFeatureExtractorStart != nullptr);
-  ASSERT_TRUE(api_.TrackingFeatureExtractorStop != nullptr);
-  ASSERT_TRUE(api_.TrackingFeatureExtractorProcessBuffer != nullptr);
-
-  EXPECT_EQ(api_.TrackingFeatureExtractorCreate(nullptr), -ENOSYS);
-  EXPECT_EQ(api_.TrackingFeatureExtractorStart(nullptr, nullptr, nullptr),
-            -ENOSYS);
-  EXPECT_EQ(api_.TrackingFeatureExtractorStop(nullptr), -ENOSYS);
-  EXPECT_EQ(api_.TrackingFeatureExtractorProcessBuffer(nullptr, nullptr,
-                                                       nullptr, nullptr),
-            -ENOSYS);
-
-  ASSERT_TRUE(api_.TrackingSensorsCreate != nullptr);
-  ASSERT_TRUE(api_.TrackingSensorsDestroy != nullptr);
-  ASSERT_TRUE(api_.TrackingSensorsStart != nullptr);
-  ASSERT_TRUE(api_.TrackingSensorsStop != nullptr);
-
-  EXPECT_EQ(api_.TrackingSensorsCreate(nullptr, nullptr), -ENOSYS);
-  EXPECT_EQ(api_.TrackingSensorsStart(nullptr, nullptr, nullptr), -ENOSYS);
-  EXPECT_EQ(api_.TrackingSensorsStop(nullptr), -ENOSYS);
-}
-
-#endif  // DVR_TRACKING_IMPLEMENTED
-
-}  // namespace
diff --git a/libs/vr/libvrsensor/Android.bp b/libs/vr/libvrsensor/Android.bp
deleted file mode 100644
index 40a5099..0000000
--- a/libs/vr/libvrsensor/Android.bp
+++ /dev/null
@@ -1,63 +0,0 @@
-// Copyright (C) 2015 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"],
-}
-
-sourceFiles = [
-    "pose_client.cpp",
-    "latency_model.cpp",
-]
-
-includeFiles = [
-    "include",
-]
-
-staticLibraries = [
-    "libdisplay",
-    "libdvrcommon",
-    "libbroadcastring",
-]
-
-sharedLibraries = [
-    "libbase",
-    "libbinder",
-    "libbufferhubqueue",
-    "libcutils",
-    "libhardware",
-    "liblog",
-    "libutils",
-    "libui",
-    "libpdx_default_transport",
-]
-
-cc_library {
-    srcs: sourceFiles,
-    cflags: [
-        "-Wall",
-        "-Werror",
-        "-Wno-macro-redefined",
-    ],
-    export_include_dirs: includeFiles,
-    static_libs: staticLibraries,
-    shared_libs: sharedLibraries,
-    header_libs: ["libdvr_headers"],
-    name: "libvrsensor",
-}
diff --git a/libs/vr/libvrsensor/include/CPPLINT.cfg b/libs/vr/libvrsensor/include/CPPLINT.cfg
deleted file mode 100644
index 2f8a3c0..0000000
--- a/libs/vr/libvrsensor/include/CPPLINT.cfg
+++ /dev/null
@@ -1 +0,0 @@
-filter=-build/header_guard
diff --git a/libs/vr/libvrsensor/include/dvr/pose_client.h b/libs/vr/libvrsensor/include/dvr/pose_client.h
deleted file mode 100644
index b663a67..0000000
--- a/libs/vr/libvrsensor/include/dvr/pose_client.h
+++ /dev/null
@@ -1,176 +0,0 @@
-#ifndef ANDROID_DVR_POSE_CLIENT_H_
-#define ANDROID_DVR_POSE_CLIENT_H_
-
-#ifdef __ARM_NEON
-#include <arm_neon.h>
-#else
-#ifndef __FLOAT32X4T_86
-#define __FLOAT32X4T_86
-typedef float float32x4_t __attribute__ ((__vector_size__ (16)));
-typedef struct float32x4x4_t { float32x4_t val[4]; } float32x4x4_t;
-#endif
-#endif
-
-#include <stdbool.h>
-#include <stdint.h>
-
-#include <dvr/dvr_pose.h>
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-typedef struct DvrPoseClient DvrPoseClient;
-
-// Returned by the async pose ring buffer access API.
-typedef struct DvrPoseRingBufferInfo {
-  // Read-only pointer to the pose ring buffer. The current pose is in this
-  // buffer at element buffer[current_frame & (buffer_size - 1)]. The next
-  // frame's forecasted pose is at element
-  // ((current_frame + 1) & (buffer_size - 1)). And so on. The poses are
-  // predicted for when 50% of the corresponding frame's pixel data is visible
-  // to the user.
-  // The last value returned by dvrPresent is the count for the next frame,
-  // which is the earliest that the application could display something if they
-  // were to render promptly. (TODO(jbates) move this comment to dvrPresent).
-  volatile const DvrPoseAsync* buffer;
-  // Minimum number of accurate forecasted poses including the current frame's
-  // pose. This is the number of poses that are udpated by the pose service.
-  // If the application reads past this count, they will get a stale prediction
-  // from a previous frame. Guaranteed to be at least 2.
-  uint32_t min_future_count;
-  // Number of elements in buffer. At least 8 and greater than min_future_count.
-  // Guaranteed to be a power of two. The total size of the buffer in bytes is:
-  //   total_count * sizeof(DvrPoseAsync)
-  uint32_t total_count;
-} DvrPoseRingBufferInfo;
-
-typedef enum DvrPoseMode {
-  DVR_POSE_MODE_6DOF = 0,
-  DVR_POSE_MODE_3DOF,
-  DVR_POSE_MODE_MOCK_FROZEN,
-  DVR_POSE_MODE_MOCK_HEAD_TURN_SLOW,
-  DVR_POSE_MODE_MOCK_HEAD_TURN_FAST,
-  DVR_POSE_MODE_MOCK_ROTATE_SLOW,
-  DVR_POSE_MODE_MOCK_ROTATE_MEDIUM,
-  DVR_POSE_MODE_MOCK_ROTATE_FAST,
-  DVR_POSE_MODE_MOCK_CIRCLE_STRAFE,
-  DVR_POSE_MODE_FLOAT,
-  DVR_POSE_MODE_MOCK_MOTION_SICKNESS,
-
-  // Always last.
-  DVR_POSE_MODE_COUNT,
-} DvrPoseMode;
-
-typedef enum DvrControllerId {
-  DVR_CONTROLLER_0 = 0,
-  DVR_CONTROLLER_1 = 1,
-} DvrControllerId;
-
-// Creates a new pose client.
-//
-// @return Pointer to the created pose client, nullptr on failure.
-DvrPoseClient* dvrPoseClientCreate();
-
-// Destroys a pose client.
-//
-// @param client Pointer to the pose client to be destroyed.
-void dvrPoseClientDestroy(DvrPoseClient* client);
-
-// Gets the pose for the given vsync count.
-//
-// @param client Pointer to the pose client.
-// @param vsync_count Vsync that this pose should be forward-predicted to.
-//     Typically this is the count returned by dvrGetNextVsyncCount.
-// @param out_pose Struct to store pose state.
-// @return Zero on success, negative error code on failure.
-int dvrPoseClientGet(DvrPoseClient* client, uint32_t vsync_count,
-                     DvrPoseAsync* out_pose);
-
-// Gets the current vsync count.
-uint32_t dvrPoseClientGetVsyncCount(DvrPoseClient* client);
-
-// Gets the pose for the given controller at the given vsync count.
-//
-// @param client Pointer to the pose client.
-// @param controller_id The controller id.
-// @param vsync_count Vsync that this pose should be forward-predicted to.
-//     Typically this is the count returned by dvrGetNextVsyncCount.
-// @param out_pose Struct to store pose state.
-// @return Zero on success, negative error code on failure.
-int dvrPoseClientGetController(DvrPoseClient* client, int32_t controller_id,
-                               uint32_t vsync_count, DvrPoseAsync* out_pose);
-
-// Enables/disables logging for the controller fusion.
-//
-// @param client Pointer to the pose client.
-// @param enable True starts logging, False stops.
-// @return Zero on success, negative error code on failure.
-int dvrPoseClientLogController(DvrPoseClient* client, bool enable);
-
-// DEPRECATED
-// Polls current pose state.
-//
-// @param client Pointer to the pose client.
-// @param state Struct to store polled state.
-// @return Zero on success, negative error code on failure.
-int dvrPoseClientPoll(DvrPoseClient* client, DvrPose* state);
-
-// Freezes the pose to the provided state.
-//
-// Future poll operations will return this state until a different state is
-// frozen or dvrPoseClientModeSet() is called with a different mode. The timestamp is
-// not frozen.
-//
-// @param client Pointer to the pose client.
-// @param frozen_state State pose to be frozen to.
-// @return Zero on success, negative error code on failure.
-int dvrPoseClientFreeze(DvrPoseClient* client, const DvrPose* frozen_state);
-
-// Sets the pose service mode.
-//
-// @param mode The requested pose mode.
-// @return Zero on success, negative error code on failure.
-int dvrPoseClientModeSet(DvrPoseClient* client, DvrPoseMode mode);
-
-// Gets the pose service mode.
-//
-// @param mode Return value for the current pose mode.
-// @return Zero on success, negative error code on failure.
-int dvrPoseClientModeGet(DvrPoseClient* client, DvrPoseMode* mode);
-
-// Get access to the shared memory pose ring buffer.
-// A future pose at vsync <current> + <offset> is accessed at index:
-//   index = (<current> + <offset>) % out_buffer_size
-// Where <current> was the last value returned by dvrPresent and
-// <offset> is less than or equal to |out_min_future_count|.
-// |out_buffer| will be set to a pointer to the buffer.
-// |out_fd| will be set to the gralloc buffer file descriptor, which is
-//   required for binding this buffer for GPU use.
-// Returns 0 on success.
-int dvrPoseClientGetRingBuffer(DvrPoseClient* client,
-                               DvrPoseRingBufferInfo* out_info);
-
-// Sets enabled state for sensors pose processing.
-//
-// @param enabled Whether sensors are enabled or disabled.
-// @return Zero on success
-int dvrPoseClientSensorsEnable(DvrPoseClient* client, bool enabled);
-
-// Requests a burst of data samples from pose service. The data samples are
-// passed through a shared memory buffer obtained by calling
-// dvrPoseClientGetDataReader().
-//
-// @param DvrPoseDataCaptureRequest Parameters on how to capture data.
-// @return Zero on success.
-int dvrPoseClientDataCapture(DvrPoseClient* client,
-                             const DvrPoseDataCaptureRequest* request);
-
-// Destroys the write buffer queue for the given |data_type|.
-int dvrPoseClientDataReaderDestroy(DvrPoseClient* client, uint64_t data_type);
-
-#ifdef __cplusplus
-}  // extern "C"
-#endif
-
-#endif  // ANDROID_DVR_POSE_CLIENT_H_
diff --git a/libs/vr/libvrsensor/include/private/dvr/latency_model.h b/libs/vr/libvrsensor/include/private/dvr/latency_model.h
deleted file mode 100644
index bf0e687..0000000
--- a/libs/vr/libvrsensor/include/private/dvr/latency_model.h
+++ /dev/null
@@ -1,29 +0,0 @@
-#ifndef ANDROID_DVR_LATENCY_MODEL_H_
-#define ANDROID_DVR_LATENCY_MODEL_H_
-
-#include <vector>
-
-namespace android {
-namespace dvr {
-
-// This class models the latency from sensors. It will look at the first
-// window_size measurements and return their average after that.
-class LatencyModel {
- public:
-  explicit LatencyModel(size_t window_size);
-  ~LatencyModel() = default;
-
-  void AddLatency(int64_t latency_ns);
-  int64_t CurrentLatencyEstimate() const { return latency_; }
-
- private:
-  size_t window_size_;
-  int64_t latency_sum_ = 0;
-  size_t num_summed_ = 0;
-  int64_t latency_ = 0;
-};
-
-}  // namespace dvr
-}  // namespace android
-
-#endif  // ANDROID_DVR_LATENCY_MODEL_H_
diff --git a/libs/vr/libvrsensor/include/private/dvr/pose-ipc.h b/libs/vr/libvrsensor/include/private/dvr/pose-ipc.h
deleted file mode 100644
index 7bf1cd4..0000000
--- a/libs/vr/libvrsensor/include/private/dvr/pose-ipc.h
+++ /dev/null
@@ -1,29 +0,0 @@
-#ifndef ANDROID_DVR_POSE_IPC_H_
-#define ANDROID_DVR_POSE_IPC_H_
-
-#include <stdint.h>
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-#define DVR_POSE_SERVICE_BASE "system/vr/pose"
-#define DVR_POSE_SERVICE_CLIENT (DVR_POSE_SERVICE_BASE "/client")
-
-enum {
-  DVR_POSE_FREEZE = 0,
-  DVR_POSE_SET_MODE,
-  DVR_POSE_GET_MODE,
-  DVR_POSE_GET_CONTROLLER_RING_BUFFER,
-  DVR_POSE_LOG_CONTROLLER,
-  DVR_POSE_SENSORS_ENABLE,
-  DVR_POSE_GET_TANGO_READER,
-  DVR_POSE_DATA_CAPTURE,
-  DVR_POSE_TANGO_READER_DESTROY,
-};
-
-#ifdef __cplusplus
-}  // extern "C"
-#endif
-
-#endif  // ANDROID_DVR_POSE_IPC_H_
diff --git a/libs/vr/libvrsensor/include/private/dvr/pose_client_internal.h b/libs/vr/libvrsensor/include/private/dvr/pose_client_internal.h
deleted file mode 100644
index 39592bb..0000000
--- a/libs/vr/libvrsensor/include/private/dvr/pose_client_internal.h
+++ /dev/null
@@ -1,19 +0,0 @@
-#ifndef ANDROID_DVR_POSE_CLIENT_INTERNAL_H_
-#define ANDROID_DVR_POSE_CLIENT_INTERNAL_H_
-
-#include <private/dvr/buffer_hub_queue_client.h>
-
-using android::dvr::ConsumerQueue;
-
-typedef struct DvrPoseClient DvrPoseClient;
-
-namespace android {
-namespace dvr {
-
-int dvrPoseClientGetDataReaderHandle(DvrPoseClient *client, uint64_t data_type,
-                                     ConsumerQueue **queue_out);
-
-}  // namespace dvr
-}  // namespace android
-
-#endif  // ANDROID_DVR_POSE_CLIENT_INTERNAL_H_
diff --git a/libs/vr/libvrsensor/latency_model.cpp b/libs/vr/libvrsensor/latency_model.cpp
deleted file mode 100644
index d3a4521..0000000
--- a/libs/vr/libvrsensor/latency_model.cpp
+++ /dev/null
@@ -1,24 +0,0 @@
-#include <private/dvr/latency_model.h>
-
-#include <cmath>
-
-namespace android {
-namespace dvr {
-
-LatencyModel::LatencyModel(size_t window_size) : window_size_(window_size) {}
-
-void LatencyModel::AddLatency(int64_t latency_ns) {
-  // Not enough samples yet?
-  if (num_summed_ < window_size_) {
-    // Accumulate.
-    latency_sum_ += latency_ns;
-
-    // Have enough samples for latency estimate?
-    if (++num_summed_ == window_size_) {
-      latency_ = latency_sum_ / window_size_;
-    }
-  }
-}
-
-}  // namespace dvr
-}  // namespace android
diff --git a/libs/vr/libvrsensor/pose_client.cpp b/libs/vr/libvrsensor/pose_client.cpp
deleted file mode 100644
index 4ff6a09..0000000
--- a/libs/vr/libvrsensor/pose_client.cpp
+++ /dev/null
@@ -1,368 +0,0 @@
-#define LOG_TAG "PoseClient"
-#include <dvr/dvr_shared_buffers.h>
-#include <dvr/pose_client.h>
-
-#include <stdint.h>
-
-#include <log/log.h>
-#include <pdx/client.h>
-#include <pdx/default_transport/client_channel_factory.h>
-#include <pdx/file_handle.h>
-#include <private/dvr/buffer_hub_queue_client.h>
-#include <private/dvr/consumer_buffer.h>
-#include <private/dvr/display_client.h>
-#include <private/dvr/pose-ipc.h>
-#include <private/dvr/shared_buffer_helpers.h>
-
-using android::dvr::ConsumerQueue;
-using android::pdx::LocalHandle;
-using android::pdx::LocalChannelHandle;
-using android::pdx::Status;
-using android::pdx::Transaction;
-
-namespace android {
-namespace dvr {
-namespace {
-
-typedef CPUMappedBroadcastRing<DvrPoseRing> SensorPoseRing;
-
-constexpr static int32_t MAX_CONTROLLERS = 2;
-}  // namespace
-
-// PoseClient is a remote interface to the pose service in sensord.
-class PoseClient : public pdx::ClientBase<PoseClient> {
- public:
-  ~PoseClient() override {}
-
-  // Casts C handle into an instance of this class.
-  static PoseClient* FromC(DvrPoseClient* client) {
-    return reinterpret_cast<PoseClient*>(client);
-  }
-
-  // Polls the pose service for the current state and stores it in *state.
-  // Returns zero on success, a negative error code otherwise.
-  int Poll(DvrPose* state) {
-    // Allocate the helper class to access the sensor pose buffer.
-    if (sensor_pose_buffer_ == nullptr) {
-      sensor_pose_buffer_ = std::make_unique<SensorPoseRing>(
-          DvrGlobalBuffers::kSensorPoseBuffer, CPUUsageMode::READ_RARELY);
-    }
-
-    if (state) {
-      if (sensor_pose_buffer_->GetNewest(state)) {
-        return 0;
-      } else {
-        return -EAGAIN;
-      }
-    }
-
-    return -EINVAL;
-  }
-
-  int GetPose(uint32_t vsync_count, DvrPoseAsync* out_pose) {
-    const auto vsync_buffer = GetVsyncBuffer();
-    if (vsync_buffer) {
-      *out_pose =
-          vsync_buffer
-              ->vsync_poses[vsync_count & DvrVsyncPoseBuffer::kIndexMask];
-      return 0;
-    } else {
-      return -EAGAIN;
-    }
-  }
-
-  uint32_t GetVsyncCount() {
-    const auto vsync_buffer = GetVsyncBuffer();
-    if (vsync_buffer) {
-      return vsync_buffer->vsync_count;
-    }
-
-    return 0;
-  }
-
-  int GetControllerPose(int32_t controller_id, uint32_t vsync_count,
-                        DvrPoseAsync* out_pose) {
-    if (controller_id < 0 || controller_id >= MAX_CONTROLLERS) {
-      return -EINVAL;
-    }
-    if (!controllers_[controller_id].mapped_pose_buffer) {
-      int ret = GetControllerRingBuffer(controller_id);
-      if (ret < 0)
-        return ret;
-    }
-    *out_pose =
-        controllers_[controller_id]
-            .mapped_pose_buffer[vsync_count & DvrVsyncPoseBuffer::kIndexMask];
-    return 0;
-  }
-
-  int LogController(bool enable) {
-    Transaction trans{*this};
-    Status<int> status = trans.Send<int>(DVR_POSE_LOG_CONTROLLER, &enable,
-                                         sizeof(enable), nullptr, 0);
-    ALOGE_IF(!status, "Pose LogController() failed because: %s",
-             status.GetErrorMessage().c_str());
-    return ReturnStatusOrError(status);
-  }
-
-  // Freezes the pose to the provided state. Future poll operations will return
-  // this state until a different state is frozen or SetMode() is called with a
-  // different mode.
-  // Returns zero on success, a negative error code otherwise.
-  int Freeze(const DvrPose& frozen_state) {
-    Transaction trans{*this};
-    Status<int> status = trans.Send<int>(DVR_POSE_FREEZE, &frozen_state,
-                                         sizeof(frozen_state), nullptr, 0);
-    ALOGE_IF(!status, "Pose Freeze() failed because: %s\n",
-             status.GetErrorMessage().c_str());
-    return ReturnStatusOrError(status);
-  }
-
-  // Sets the data mode for the pose service.
-  int SetMode(DvrPoseMode mode) {
-    Transaction trans{*this};
-    Status<int> status =
-        trans.Send<int>(DVR_POSE_SET_MODE, &mode, sizeof(mode), nullptr, 0);
-    ALOGE_IF(!status, "Pose SetPoseMode() failed because: %s",
-             status.GetErrorMessage().c_str());
-    return ReturnStatusOrError(status);
-  }
-
-  // Gets the data mode for the pose service.
-  int GetMode(DvrPoseMode* out_mode) {
-    int mode;
-    Transaction trans{*this};
-    Status<int> status =
-        trans.Send<int>(DVR_POSE_GET_MODE, nullptr, 0, &mode, sizeof(mode));
-    ALOGE_IF(!status, "Pose GetPoseMode() failed because: %s",
-             status.GetErrorMessage().c_str());
-    if (status)
-      *out_mode = DvrPoseMode(mode);
-    return ReturnStatusOrError(status);
-  }
-
-  int GetTangoReaderHandle(uint64_t data_type, ConsumerQueue** queue_out) {
-    // Get buffer.
-    Transaction trans{*this};
-    Status<LocalChannelHandle> status = trans.Send<LocalChannelHandle>(
-        DVR_POSE_GET_TANGO_READER, &data_type, sizeof(data_type), nullptr, 0);
-
-    if (!status) {
-      ALOGE("PoseClient GetTangoReaderHandle() failed because: %s",
-            status.GetErrorMessage().c_str());
-      *queue_out = nullptr;
-      return -status.error();
-    }
-
-    std::unique_ptr<ConsumerQueue> consumer_queue =
-        ConsumerQueue::Import(status.take());
-    *queue_out = consumer_queue.release();
-    return 0;
-  }
-
-  int DataCapture(const DvrPoseDataCaptureRequest* request) {
-    Transaction trans{*this};
-    Status<int> status = trans.Send<int>(DVR_POSE_DATA_CAPTURE, request,
-                                         sizeof(*request), nullptr, 0);
-    ALOGE_IF(!status, "PoseClient DataCapture() failed because: %s\n",
-             status.GetErrorMessage().c_str());
-    return ReturnStatusOrError(status);
-  }
-
-  int DataReaderDestroy(uint64_t data_type) {
-    Transaction trans{*this};
-    Status<int> status = trans.Send<int>(DVR_POSE_TANGO_READER_DESTROY,
-                                         &data_type, sizeof(data_type), nullptr,
-                                         0);
-    ALOGE_IF(!status, "PoseClient DataReaderDestroy() failed because: %s\n",
-             status.GetErrorMessage().c_str());
-    return ReturnStatusOrError(status);
-  }
-
-  // Enables or disables all pose processing from sensors
-  int EnableSensors(bool enabled) {
-    Transaction trans{*this};
-    Status<int> status = trans.Send<int>(DVR_POSE_SENSORS_ENABLE, &enabled,
-                                         sizeof(enabled), nullptr, 0);
-    ALOGE_IF(!status, "Pose EnableSensors() failed because: %s\n",
-             status.GetErrorMessage().c_str());
-    return ReturnStatusOrError(status);
-  }
-
-  int GetRingBuffer(DvrPoseRingBufferInfo* out_info) {
-    // First time mapping the buffer?
-    const auto vsync_buffer = GetVsyncBuffer();
-    if (vsync_buffer) {
-      if (out_info) {
-        out_info->min_future_count = DvrVsyncPoseBuffer::kMinFutureCount;
-        out_info->total_count = DvrVsyncPoseBuffer::kSize;
-        out_info->buffer = vsync_buffer->vsync_poses;
-      }
-      return -EINVAL;
-    }
-
-    return -EAGAIN;
-  }
-
-  int GetControllerRingBuffer(int32_t controller_id) {
-    if (controller_id < 0 || controller_id >= MAX_CONTROLLERS) {
-      return -EINVAL;
-    }
-    ControllerClientState& client_state = controllers_[controller_id];
-    if (client_state.pose_buffer.get()) {
-      return 0;
-    }
-
-    Transaction trans{*this};
-    Status<LocalChannelHandle> status = trans.Send<LocalChannelHandle>(
-        DVR_POSE_GET_CONTROLLER_RING_BUFFER, &controller_id,
-        sizeof(controller_id), nullptr, 0);
-    if (!status) {
-      return -status.error();
-    }
-
-    auto buffer = ConsumerBuffer::Import(status.take());
-    if (!buffer) {
-      ALOGE("Pose failed to import ring buffer");
-      return -EIO;
-    }
-    constexpr size_t size = DvrVsyncPoseBuffer::kSize * sizeof(DvrPoseAsync);
-    void* addr = nullptr;
-    int ret = buffer->GetBlobReadWritePointer(size, &addr);
-    if (ret < 0 || !addr) {
-      ALOGE("Pose failed to map ring buffer: ret:%d, addr:%p", ret, addr);
-      return -EIO;
-    }
-    client_state.pose_buffer.swap(buffer);
-    client_state.mapped_pose_buffer = static_cast<const DvrPoseAsync*>(addr);
-    ALOGI(
-        "Mapped controller %d pose data translation %f,%f,%f quat %f,%f,%f,%f",
-        controller_id, client_state.mapped_pose_buffer[0].position[0],
-        client_state.mapped_pose_buffer[0].position[1],
-        client_state.mapped_pose_buffer[0].position[2],
-        client_state.mapped_pose_buffer[0].orientation[0],
-        client_state.mapped_pose_buffer[0].orientation[1],
-        client_state.mapped_pose_buffer[0].orientation[2],
-        client_state.mapped_pose_buffer[0].orientation[3]);
-    return 0;
-  }
-
- private:
-  friend BASE;
-
-  // Set up a channel to the pose service.
-  PoseClient()
-      : BASE(pdx::default_transport::ClientChannelFactory::Create(
-            DVR_POSE_SERVICE_CLIENT)) {
-    // TODO(eieio): Cache the pose and make timeout 0 so that the API doesn't
-    // block while waiting for the pose service to come back up.
-    EnableAutoReconnect(kInfiniteTimeout);
-  }
-
-  PoseClient(const PoseClient&) = delete;
-  PoseClient& operator=(const PoseClient&) = delete;
-
-  const DvrVsyncPoseBuffer* GetVsyncBuffer() {
-    if (mapped_vsync_pose_buffer_ == nullptr) {
-      if (vsync_pose_buffer_ == nullptr) {
-        // The constructor tries mapping it so we do not need TryMapping after.
-        vsync_pose_buffer_ = std::make_unique<CPUMappedBuffer>(
-            DvrGlobalBuffers::kVsyncPoseBuffer, CPUUsageMode::READ_OFTEN);
-      } else if (vsync_pose_buffer_->IsMapped() == false) {
-        vsync_pose_buffer_->TryMapping();
-      }
-
-      if (vsync_pose_buffer_->IsMapped()) {
-        mapped_vsync_pose_buffer_ =
-            static_cast<DvrVsyncPoseBuffer*>(vsync_pose_buffer_->Address());
-      }
-    }
-
-    return mapped_vsync_pose_buffer_;
-  }
-
-  // The vsync pose buffer if already mapped.
-  std::unique_ptr<CPUMappedBuffer> vsync_pose_buffer_;
-
-  // The direct sensor pose buffer.
-  std::unique_ptr<SensorPoseRing> sensor_pose_buffer_;
-
-  const DvrVsyncPoseBuffer* mapped_vsync_pose_buffer_ = nullptr;
-
-  struct ControllerClientState {
-    std::unique_ptr<ConsumerBuffer> pose_buffer;
-    const DvrPoseAsync* mapped_pose_buffer = nullptr;
-  };
-  ControllerClientState controllers_[MAX_CONTROLLERS];
-};
-
-int dvrPoseClientGetDataReaderHandle(DvrPoseClient* client, uint64_t type,
-                                     ConsumerQueue** queue_out) {
-  return PoseClient::FromC(client)->GetTangoReaderHandle(type, queue_out);
-}
-
-}  // namespace dvr
-}  // namespace android
-
-using android::dvr::PoseClient;
-
-extern "C" {
-
-DvrPoseClient* dvrPoseClientCreate() {
-  auto* client = PoseClient::Create().release();
-  return reinterpret_cast<DvrPoseClient*>(client);
-}
-
-void dvrPoseClientDestroy(DvrPoseClient* client) {
-  delete PoseClient::FromC(client);
-}
-
-int dvrPoseClientGet(DvrPoseClient* client, uint32_t vsync_count,
-                     DvrPoseAsync* out_pose) {
-  return PoseClient::FromC(client)->GetPose(vsync_count, out_pose);
-}
-
-uint32_t dvrPoseClientGetVsyncCount(DvrPoseClient* client) {
-  return PoseClient::FromC(client)->GetVsyncCount();
-}
-
-int dvrPoseClientGetController(DvrPoseClient* client, int32_t controller_id,
-                               uint32_t vsync_count, DvrPoseAsync* out_pose) {
-  return PoseClient::FromC(client)->GetControllerPose(controller_id,
-                                                      vsync_count, out_pose);
-}
-
-int dvrPoseClientLogController(DvrPoseClient* client, bool enable) {
-  return PoseClient::FromC(client)->LogController(enable);
-}
-
-int dvrPoseClientPoll(DvrPoseClient* client, DvrPose* state) {
-  return PoseClient::FromC(client)->Poll(state);
-}
-
-int dvrPoseClientFreeze(DvrPoseClient* client, const DvrPose* frozen_state) {
-  return PoseClient::FromC(client)->Freeze(*frozen_state);
-}
-
-int dvrPoseClientModeSet(DvrPoseClient* client, DvrPoseMode mode) {
-  return PoseClient::FromC(client)->SetMode(mode);
-}
-
-int dvrPoseClientModeGet(DvrPoseClient* client, DvrPoseMode* mode) {
-  return PoseClient::FromC(client)->GetMode(mode);
-}
-
-int dvrPoseClientSensorsEnable(DvrPoseClient* client, bool enabled) {
-  return PoseClient::FromC(client)->EnableSensors(enabled);
-}
-
-int dvrPoseClientDataCapture(DvrPoseClient* client,
-                             const DvrPoseDataCaptureRequest* request) {
-  return PoseClient::FromC(client)->DataCapture(request);
-}
-
-int dvrPoseClientDataReaderDestroy(DvrPoseClient* client, uint64_t data_type) {
-  return PoseClient::FromC(client)->DataReaderDestroy(data_type);
-}
-
-}  // extern "C"
diff --git a/opengl/libs/EGL/getProcAddress.cpp b/opengl/libs/EGL/getProcAddress.cpp
index b3d6f74..e0ba946 100644
--- a/opengl/libs/EGL/getProcAddress.cpp
+++ b/opengl/libs/EGL/getProcAddress.cpp
@@ -118,70 +118,27 @@
             : "rax", "cc"                                       \
             );
 
-#elif defined(__mips64)
+#elif defined(__riscv)
+    #define API_ENTRY(_api) __attribute__((noinline)) _api
 
-        #define API_ENTRY(_api) __attribute__((noinline)) _api
-
-        #define CALL_GL_EXTENSION_API(_api, ...)                    \
-            register unsigned int _t0 asm("$12");                   \
-            register unsigned int _fn asm("$25");                   \
-            register unsigned int _tls asm("$3");                   \
-            asm volatile(                                           \
-                ".set  push\n\t"                                    \
-                ".set  noreorder\n\t"                               \
-                "rdhwr %[tls], $29\n\t"                             \
-                "ld    %[t0], %[OPENGL_API](%[tls])\n\t"            \
-                "beqz  %[t0], 1f\n\t"                               \
-                " move %[fn], $ra\n\t"                              \
-                "ld    %[t0], %[API](%[t0])\n\t"                    \
-                "beqz  %[t0], 1f\n\t"                               \
-                " nop\n\t"                                          \
-                "move  %[fn], %[t0]\n\t"                            \
-                "1:\n\t"                                            \
-                "jalr  $0, %[fn]\n\t"                               \
-                " nop\n\t"                                          \
-                ".set  pop\n\t"                                     \
-                : [fn] "=c"(_fn),                                   \
-                  [tls] "=&r"(_tls),                                \
-                  [t0] "=&r"(_t0)                                   \
-                : [OPENGL_API] "I"(TLS_SLOT_OPENGL_API*4),          \
-                  [API] "I"(__builtin_offsetof(gl_hooks_t,          \
-                                          ext.extensions[_api]))    \
-                :                                                   \
-            );
-
-#elif defined(__mips__)
-
-        #define API_ENTRY(_api) __attribute__((noinline)) _api
-
-        #define CALL_GL_EXTENSION_API(_api, ...)                    \
-            register unsigned int _t0 asm("$8");                    \
-            register unsigned int _fn asm("$25");                    \
-            register unsigned int _tls asm("$3");                   \
-            asm volatile(                                           \
-                ".set  push\n\t"                                    \
-                ".set  noreorder\n\t"                               \
-                ".set  mips32r2\n\t"                                \
-                "rdhwr %[tls], $29\n\t"                             \
-                "lw    %[t0], %[OPENGL_API](%[tls])\n\t"            \
-                "beqz  %[t0], 1f\n\t"                               \
-                " move %[fn], $ra\n\t"                              \
-                "lw    %[t0], %[API](%[t0])\n\t"                    \
-                "beqz  %[t0], 1f\n\t"                               \
-                " nop\n\t"                                          \
-                "move  %[fn], %[t0]\n\t"                            \
-                "1:\n\t"                                            \
-                "jalr  $0, %[fn]\n\t"                               \
-                " nop\n\t"                                          \
-                ".set  pop\n\t"                                     \
-                : [fn] "=c"(_fn),                                   \
-                  [tls] "=&r"(_tls),                                \
-                  [t0] "=&r"(_t0)                                   \
-                : [OPENGL_API] "I"(TLS_SLOT_OPENGL_API*4),          \
-                  [API] "I"(__builtin_offsetof(gl_hooks_t,          \
-                                          ext.extensions[_api]))    \
-                :                                                   \
-            );
+    #define CALL_GL_EXTENSION_API(_api)                             \
+        asm volatile(                                               \
+            "mv t0, tp\n"                                           \
+            "li t1, %[tls]\n"                                       \
+            "add t0, t0, t1\n"                                      \
+            "ld t0, 0(t0)\n"                                        \
+            "beqz t0, 1f\n"                                         \
+            "li t1, %[api]\n"                                       \
+            "add t0, t0, t1\n"                                      \
+            "ld t0, 0(t0)\n"                                        \
+            "jalr x0, t0\n"                                         \
+            "1: ret\n"                                              \
+            :                                                       \
+            : [tls] "i" (TLS_SLOT_OPENGL_API * sizeof(void*)),      \
+              [api] "i" (__builtin_offsetof(gl_hooks_t,             \
+                                        ext.extensions[_api]))      \
+            : "t0", "t1"                                            \
+        );
 
 #endif
 
diff --git a/opengl/libs/GLES2/gl2.cpp b/opengl/libs/GLES2/gl2.cpp
index 65f50f5..5bd5c14 100644
--- a/opengl/libs/GLES2/gl2.cpp
+++ b/opengl/libs/GLES2/gl2.cpp
@@ -191,73 +191,44 @@
             :                              \
         );
 
-#elif defined(__mips64)
+#elif defined(__riscv)
 
     #define API_ENTRY(_api) __attribute__((naked,noinline)) _api
 
-    // t0:  $12
-    // fn:  $25
-    // tls: $3
-    // v0:  $2
-    #define CALL_GL_API_INTERNAL_CALL(_api, ...)                  \
-        asm volatile(                                             \
-            ".set  push\n\t"                                      \
-            ".set  noreorder\n\t"                                 \
-            "rdhwr $3, $29\n\t"                                   \
-            "ld    $12, %[OPENGL_API]($3)\n\t"                    \
-            "beqz  $12, 1f\n\t"                                   \
-            " move $25, $ra\n\t"                                  \
-            "ld    $12, %[API]($12)\n\t"                          \
-            "beqz  $12, 1f\n\t"                                   \
-            " nop\n\t"                                            \
-            "move  $25, $12\n\t"                                  \
-            "1:\n\t"                                              \
-            "jalr  $0, $25\n\t"                                   \
-            " move $2, $0\n\t"                                    \
-            ".set  pop\n\t"                                       \
-            :                                                     \
-            : [OPENGL_API] "I"(TLS_SLOT_OPENGL_API*sizeof(void*)),\
-              [API] "I"(__builtin_offsetof(gl_hooks_t, gl._api))  \
-            : "$2", "$3", "$4", "$5", "$6", "$7", "$8", "$9",     \
-              "$10", "$11", "$12", "$25"                          \
-        );
-
-    #define CALL_GL_API_INTERNAL_SET_RETURN_VALUE
-    #define CALL_GL_API_INTERNAL_DO_RETURN
-
-#elif defined(__mips__)
-
-    #define API_ENTRY(_api) __attribute__((naked,noinline)) _api
-
-    // t0:  $8
-    // fn:  $25
-    // tls: $3
-    // v0:  $2
     #define CALL_GL_API_INTERNAL_CALL(_api, ...)                 \
         asm volatile(                                            \
-            ".set  push\n\t"                                     \
-            ".set  noreorder\n\t"                                \
-            ".set  mips32r2\n\t"                                 \
-            "rdhwr $3, $29\n\t"                                  \
-            "lw    $3, %[OPENGL_API]($3)\n\t"                    \
-            "beqz  $3, 1f\n\t"                                   \
-            " move $25,$ra\n\t"                                  \
-            "lw    $3, %[API]($3)\n\t"                           \
-            "beqz  $3, 1f\n\t"                                   \
-            " nop\n\t"                                           \
-            "move  $25, $3\n\t"                                  \
-            "1:\n\t"                                             \
-            "jalr  $0, $25\n\t"                                  \
-            " move $2, $0\n\t"                                   \
-            ".set  pop\n\t"                                      \
+            "mv t0, tp\n"                                        \
+            "li t1, %[tls]\n"                                    \
+            "add t0, t0, t1\n"                                   \
+            "ld t0, 0(t0)\n"                                     \
+            "beqz t0, 1f\n"                                      \
+            "li t1, %[api]\n"                                    \
+            "add t0, t0, t1\n"                                   \
+            "ld t0, 0(t0)\n"                                     \
+            "jalr x0, t0\n"                                      \
+            "1:\n"                                               \
             :                                                    \
-            : [OPENGL_API] "I"(TLS_SLOT_OPENGL_API*4),           \
-              [API] "I"(__builtin_offsetof(gl_hooks_t, gl._api)) \
-            : "$2", "$3", "$4", "$5", "$6", "$7", "$8", "$25"    \
+            : [tls] "i"(TLS_SLOT_OPENGL_API*sizeof(void *)),     \
+              [api] "i"(__builtin_offsetof(gl_hooks_t, gl._api)) \
+            : "t0", "t1", "t2", "a0", "a1", "a2", "a3", "a4",    \
+              "a5", "t6", "t3", "t4", "t5", "t6"                 \
         );
 
-    #define CALL_GL_API_INTERNAL_SET_RETURN_VALUE
-    #define CALL_GL_API_INTERNAL_DO_RETURN
+    #define CALL_GL_API_INTERNAL_SET_RETURN_VALUE \
+        asm volatile(                             \
+            "li a0, 0\n"                          \
+            :                                     \
+            :                                     \
+            : "a0"                                \
+        );
+
+    #define CALL_GL_API_INTERNAL_DO_RETURN \
+        asm volatile(                      \
+            "ret\n"                        \
+            :                              \
+            :                              \
+            :                              \
+        );
 
 #endif
 
diff --git a/opengl/libs/GLES_CM/gl.cpp b/opengl/libs/GLES_CM/gl.cpp
index bacd4b4..64c0f97 100644
--- a/opengl/libs/GLES_CM/gl.cpp
+++ b/opengl/libs/GLES_CM/gl.cpp
@@ -247,73 +247,44 @@
             :                              \
         );
 
-#elif defined(__mips64)
+#elif defined(__riscv)
 
     #define API_ENTRY(_api) __attribute__((naked,noinline)) _api
 
-    // t0:  $12
-    // fn:  $25
-    // tls: $3
-    // v0:  $2
-    #define CALL_GL_API_INTERNAL_CALL(_api, ...)                  \
-        asm volatile(                                             \
-            ".set  push\n\t"                                      \
-            ".set  noreorder\n\t"                                 \
-            "rdhwr $3, $29\n\t"                                   \
-            "ld    $12, %[OPENGL_API]($3)\n\t"                    \
-            "beqz  $12, 1f\n\t"                                   \
-            " move $25, $ra\n\t"                                  \
-            "ld    $12, %[API]($12)\n\t"                          \
-            "beqz  $12, 1f\n\t"                                   \
-            " nop\n\t"                                            \
-            "move  $25, $12\n\t"                                  \
-            "1:\n\t"                                              \
-            "jalr  $0, $25\n\t"                                   \
-            " move $2, $0\n\t"                                    \
-            ".set  pop\n\t"                                       \
-            :                                                     \
-            : [OPENGL_API] "I"(TLS_SLOT_OPENGL_API*sizeof(void*)),\
-              [API] "I"(__builtin_offsetof(gl_hooks_t, gl._api))  \
-            : "$2", "$3", "$4", "$5", "$6", "$7", "$8", "$9",     \
-              "$10", "$11", "$12", "$25"                          \
-        );
-
-    #define CALL_GL_API_INTERNAL_SET_RETURN_VALUE
-    #define CALL_GL_API_INTERNAL_DO_RETURN
-
-#elif defined(__mips__)
-
-    #define API_ENTRY(_api) __attribute__((naked,noinline)) _api
-
-    // t0:  $8
-    // fn:  $25
-    // tls: $3
-    // v0:  $2
     #define CALL_GL_API_INTERNAL_CALL(_api, ...)                 \
         asm volatile(                                            \
-            ".set  push\n\t"                                     \
-            ".set  noreorder\n\t"                                \
-            ".set  mips32r2\n\t"                                 \
-            "rdhwr $3, $29\n\t"                                  \
-            "lw    $3, %[OPENGL_API]($3)\n\t"                    \
-            "beqz  $3, 1f\n\t"                                   \
-            " move $25,$ra\n\t"                                  \
-            "lw    $3, %[API]($3)\n\t"                           \
-            "beqz  $3, 1f\n\t"                                   \
-            " nop\n\t"                                           \
-            "move  $25, $3\n\t"                                  \
-            "1:\n\t"                                             \
-            "jalr  $0, $25\n\t"                                  \
-            " move $2, $0\n\t"                                   \
-            ".set  pop\n\t"                                      \
+            "mv t0, tp\n"                                        \
+            "li t1, %[tls]\n"                                    \
+            "add t0, t0, t1\n"                                   \
+            "ld t0, 0(t0)\n"                                     \
+            "beqz t0, 1f\n"                                      \
+            "li t1, %[api]\n"                                    \
+            "add t0, t0, t1\n"                                   \
+            "ld t0, 0(t0)\n"                                     \
+            "jalr x0, t0\n"                                      \
+            "1:\n"                                               \
             :                                                    \
-            : [OPENGL_API] "I"(TLS_SLOT_OPENGL_API*4),           \
-              [API] "I"(__builtin_offsetof(gl_hooks_t, gl._api)) \
-            : "$2", "$3", "$4", "$5", "$6", "$7", "$8", "$25"    \
+            : [tls] "i"(TLS_SLOT_OPENGL_API*sizeof(void *)),     \
+              [api] "i"(__builtin_offsetof(gl_hooks_t, gl._api)) \
+            : "t0", "t1", "t2", "a0", "a1", "a2", "a3", "a4",    \
+              "a5", "t6", "t3", "t4", "t5", "t6"                 \
         );
 
-    #define CALL_GL_API_INTERNAL_SET_RETURN_VALUE
-    #define CALL_GL_API_INTERNAL_DO_RETURN
+    #define CALL_GL_API_INTERNAL_SET_RETURN_VALUE \
+        asm volatile(                             \
+            "li a0, 0\n"                          \
+            :                                     \
+            :                                     \
+            : "a0"                                \
+        );
+
+    #define CALL_GL_API_INTERNAL_DO_RETURN \
+        asm volatile(                      \
+            "ret\n"                        \
+            :                              \
+            :                              \
+            :                              \
+        );
 
 #endif
 
diff --git a/services/batteryservice/Android.bp b/services/batteryservice/Android.bp
index 1e37991..9b78391 100644
--- a/services/batteryservice/Android.bp
+++ b/services/batteryservice/Android.bp
@@ -9,6 +9,7 @@
 
 cc_library_headers {
     name: "libbatteryservice_headers",
+    host_supported: true,
     vendor_available: true,
     recovery_available: true,
     export_include_dirs: ["include"],
diff --git a/services/inputflinger/Android.bp b/services/inputflinger/Android.bp
index 8714b03..973986f 100644
--- a/services/inputflinger/Android.bp
+++ b/services/inputflinger/Android.bp
@@ -41,7 +41,9 @@
         "-DANDROID_UTILS_REF_BASE_DISABLE_IMPLICIT_CONSTRUCTION",
     ],
     sanitize: {
-        misc_undefined: ["bounds"],
+        misc_undefined: [
+            "bounds",
+        ],
     },
     tidy: true,
     tidy_checks: [
@@ -57,11 +59,10 @@
 filegroup {
     name: "libinputflinger_sources",
     srcs: [
-        "InputProcessor.cpp",
         "InputCommonConverter.cpp",
+        "InputProcessor.cpp",
         "PreferStylusOverTouchBlocker.cpp",
         "UnwantedInteractionBlocker.cpp",
-        "InputManager.cpp",
     ],
 }
 
@@ -77,13 +78,10 @@
         "libcrypto",
         "libcutils",
         "libhidlbase",
-        "libinput",
         "libkll",
         "liblog",
         "libprotobuf-cpp-lite",
         "libstatslog",
-        "libstatspull",
-        "libstatssocket",
         "libutils",
         "server_configurable_flags",
     ],
@@ -92,6 +90,23 @@
         "libpalmrejection",
         "libui-types",
     ],
+    target: {
+        android: {
+            shared_libs: [
+                "libgui",
+                "libinput",
+                "libstatspull",
+                "libstatssocket",
+            ],
+        },
+        host: {
+            static_libs: [
+                "libinput",
+                "libstatspull",
+                "libstatssocket",
+            ],
+        },
+    },
 }
 
 cc_library_shared {
@@ -100,6 +115,10 @@
         "inputflinger_defaults",
         "libinputflinger_defaults",
     ],
+    srcs: [
+        "InputManager.cpp",
+        // other sources are added via "defaults"
+    ],
     cflags: [
         // TODO(b/23084678): Move inputflinger to its own process and mark it hidden
         //-fvisibility=hidden
@@ -108,9 +127,8 @@
         // This should consist only of dependencies from inputflinger. Other dependencies should be
         // in cc_defaults so that they are included in the tests.
         "libinputflinger_base",
-        "libinputreporter",
         "libinputreader",
-        "libgui",
+        "libinputreporter",
     ],
     static_libs: [
         "libinputdispatcher",
@@ -130,6 +148,7 @@
 
 cc_library_headers {
     name: "libinputflinger_headers",
+    host_supported: true,
     export_include_dirs: ["include"],
 }
 
@@ -151,17 +170,30 @@
         "libbase",
         "libbinder",
         "libcutils",
-        "libinput",
         "liblog",
         "libutils",
     ],
     header_libs: [
         "libinputflinger_headers",
     ],
+    target: {
+        android: {
+            shared_libs: [
+                "libinput",
+            ],
+        },
+        host: {
+            static_libs: [
+                "libinput",
+                "libui-types",
+            ],
+        },
+    },
 }
 
 cc_library_shared {
     name: "libinputflinger_base",
+    host_supported: true,
     defaults: [
         "inputflinger_defaults",
         "libinputflinger_base_defaults",
diff --git a/services/inputflinger/InputCommonConverter.cpp b/services/inputflinger/InputCommonConverter.cpp
index 09f1c0f..b292c09 100644
--- a/services/inputflinger/InputCommonConverter.cpp
+++ b/services/inputflinger/InputCommonConverter.cpp
@@ -307,8 +307,8 @@
         common::PointerCoords coords;
         // OK to copy bits because we have static_assert for pointerCoords axes
         coords.bits = args.pointerCoords[i].bits;
-        coords.values = std::vector<float>(args.pointerCoords[i].values,
-                                           args.pointerCoords[i].values +
+        coords.values = std::vector<float>(args.pointerCoords[i].values.cbegin(),
+                                           args.pointerCoords[i].values.cbegin() +
                                                    BitSet64::count(args.pointerCoords[i].bits));
         outPointerCoords.push_back(coords);
     }
diff --git a/services/inputflinger/InputThread.cpp b/services/inputflinger/InputThread.cpp
index e2e64f9..e74f258 100644
--- a/services/inputflinger/InputThread.cpp
+++ b/services/inputflinger/InputThread.cpp
@@ -54,7 +54,13 @@
 }
 
 bool InputThread::isCallingThread() {
+#if defined(__ANDROID__)
     return gettid() == mThread->getTid();
+#else
+    // Assume that the caller is doing everything correctly,
+    // since thread information is not available on host
+    return false;
+#endif
 }
 
 } // namespace android
\ No newline at end of file
diff --git a/services/inputflinger/PreferStylusOverTouchBlocker.cpp b/services/inputflinger/PreferStylusOverTouchBlocker.cpp
index beec2e1..ddd5146 100644
--- a/services/inputflinger/PreferStylusOverTouchBlocker.cpp
+++ b/services/inputflinger/PreferStylusOverTouchBlocker.cpp
@@ -25,8 +25,7 @@
     for (size_t i = 0; i < args.pointerCount; i++) {
         // Make sure we are canceling stylus pointers
         const int32_t toolType = args.pointerProperties[i].toolType;
-        if (toolType == AMOTION_EVENT_TOOL_TYPE_STYLUS ||
-            toolType == AMOTION_EVENT_TOOL_TYPE_ERASER) {
+        if (isStylusToolType(toolType)) {
             hasStylus = true;
         }
         if (toolType == AMOTION_EVENT_TOOL_TYPE_FINGER) {
diff --git a/services/inputflinger/TEST_MAPPING b/services/inputflinger/TEST_MAPPING
index 4a0f2ec..3d7242e 100644
--- a/services/inputflinger/TEST_MAPPING
+++ b/services/inputflinger/TEST_MAPPING
@@ -15,6 +15,9 @@
       "name": "inputflinger_tests"
     },
     {
+      "name": "libchrome-gestures_test"
+    },
+    {
       "name": "libpalmrejection_test"
     },
     {
@@ -41,6 +44,7 @@
           "include-filter": "android.view.cts.input",
           "include-filter": "android.view.cts.MotionEventTest",
           "include-filter": "android.view.cts.PointerCaptureTest",
+          "include-filter": "android.view.cts.TooltipTest",
           "include-filter": "android.view.cts.VerifyInputEventTest"
         }
       ]
@@ -128,6 +132,7 @@
         {
           "include-filter": "android.view.cts.MotionEventTest",
           "include-filter": "android.view.cts.PointerCaptureTest",
+          "include-filter": "android.view.cts.TooltipTest",
           "include-filter": "android.view.cts.VerifyInputEventTest"
         }
       ]
diff --git a/services/inputflinger/UnwantedInteractionBlocker.cpp b/services/inputflinger/UnwantedInteractionBlocker.cpp
index ec41025..c170b81 100644
--- a/services/inputflinger/UnwantedInteractionBlocker.cpp
+++ b/services/inputflinger/UnwantedInteractionBlocker.cpp
@@ -99,14 +99,17 @@
 }
 
 static int getLinuxToolCode(int toolType) {
-    if (toolType == AMOTION_EVENT_TOOL_TYPE_STYLUS) {
-        return BTN_TOOL_PEN;
+    switch (toolType) {
+        case AMOTION_EVENT_TOOL_TYPE_STYLUS:
+            return BTN_TOOL_PEN;
+        case AMOTION_EVENT_TOOL_TYPE_ERASER:
+            return BTN_TOOL_RUBBER;
+        case AMOTION_EVENT_TOOL_TYPE_FINGER:
+            return BTN_TOOL_FINGER;
+        default:
+            ALOGW("Got tool type %" PRId32 ", converting to BTN_TOOL_FINGER", toolType);
+            return BTN_TOOL_FINGER;
     }
-    if (toolType == AMOTION_EVENT_TOOL_TYPE_FINGER) {
-        return BTN_TOOL_FINGER;
-    }
-    ALOGW("Got tool type %" PRId32 ", converting to BTN_TOOL_FINGER", toolType);
-    return BTN_TOOL_FINGER;
 }
 
 static int32_t getActionUpForPointerId(const NotifyMotionArgs& args, int32_t pointerId) {
@@ -195,7 +198,7 @@
 static std::optional<NotifyMotionArgs> removeStylusPointerIds(const NotifyMotionArgs& args) {
     std::set<int32_t> stylusPointerIds;
     for (uint32_t i = 0; i < args.pointerCount; i++) {
-        if (args.pointerProperties[i].toolType == AMOTION_EVENT_TOOL_TYPE_STYLUS) {
+        if (isStylusToolType(args.pointerProperties[i].toolType)) {
             stylusPointerIds.insert(args.pointerProperties[i].id);
         }
     }
diff --git a/services/inputflinger/benchmarks/Android.bp b/services/inputflinger/benchmarks/Android.bp
index e5c19af..4e2a6fb 100644
--- a/services/inputflinger/benchmarks/Android.bp
+++ b/services/inputflinger/benchmarks/Android.bp
@@ -21,7 +21,6 @@
         "libbinder",
         "libcrypto",
         "libcutils",
-        "libinput",
         "libinputflinger_base",
         "libinputreporter",
         "liblog",
diff --git a/services/inputflinger/dispatcher/Android.bp b/services/inputflinger/dispatcher/Android.bp
index eb79b76..ab5c5ef 100644
--- a/services/inputflinger/dispatcher/Android.bp
+++ b/services/inputflinger/dispatcher/Android.bp
@@ -23,6 +23,7 @@
 
 cc_library_headers {
     name: "libinputdispatcher_headers",
+    host_supported: true,
     export_include_dirs: [
         "include",
     ],
@@ -33,6 +34,7 @@
     srcs: [
         "AnrTracker.cpp",
         "Connection.cpp",
+        "DragState.cpp",
         "Entry.cpp",
         "FocusResolver.cpp",
         "InjectionState.cpp",
@@ -44,8 +46,8 @@
         "LatencyAggregator.cpp",
         "LatencyTracker.cpp",
         "Monitor.cpp",
+        "TouchedWindow.cpp",
         "TouchState.cpp",
-        "DragState.cpp",
     ],
 }
 
@@ -56,20 +58,34 @@
         "libbase",
         "libcrypto",
         "libcutils",
-        "libinput",
         "libkll",
         "liblog",
         "libprotobuf-cpp-lite",
         "libstatslog",
-        "libstatspull",
-        "libstatssocket",
-        "libgui",
         "libutils",
         "server_configurable_flags",
     ],
     static_libs: [
         "libattestation",
+        "libgui_window_info_static",
     ],
+    target: {
+        android: {
+            shared_libs: [
+                "libgui",
+                "libinput",
+                "libstatspull",
+                "libstatssocket",
+            ],
+        },
+        host: {
+            static_libs: [
+                "libinput",
+                "libstatspull",
+                "libstatssocket",
+            ],
+        },
+    },
     header_libs: [
         "libinputdispatcher_headers",
     ],
@@ -84,8 +100,8 @@
     shared_libs: [
         // This should consist only of dependencies from inputflinger. Other dependencies should be
         // in cc_defaults so that they are included in the tests.
-        "libinputreporter",
         "libinputflinger_base",
+        "libinputreporter",
     ],
     export_header_lib_headers: [
         "libinputdispatcher_headers",
diff --git a/services/inputflinger/dispatcher/AnrTracker.cpp b/services/inputflinger/dispatcher/AnrTracker.cpp
index c3f611e..a18063f 100644
--- a/services/inputflinger/dispatcher/AnrTracker.cpp
+++ b/services/inputflinger/dispatcher/AnrTracker.cpp
@@ -54,7 +54,7 @@
 }
 
 // If empty() is false, return the time at which the next connection should cause an ANR
-// If empty() is true, return LONG_LONG_MAX
+// If empty() is true, return LLONG_MAX
 nsecs_t AnrTracker::firstTimeout() const {
     if (mAnrTimeouts.empty()) {
         return std::numeric_limits<nsecs_t>::max();
diff --git a/services/inputflinger/dispatcher/AnrTracker.h b/services/inputflinger/dispatcher/AnrTracker.h
index 5e5e0c4..cff5d00 100644
--- a/services/inputflinger/dispatcher/AnrTracker.h
+++ b/services/inputflinger/dispatcher/AnrTracker.h
@@ -35,7 +35,7 @@
 
     bool empty() const;
     // If empty() is false, return the time at which the next connection should cause an ANR
-    // If empty() is true, return LONG_LONG_MAX
+    // If empty() is true, return LLONG_MAX
     nsecs_t firstTimeout() const;
     // Return the token of the next connection that should cause an ANR.
     // Do not call this unless empty() is false, you will encounter undefined behaviour.
diff --git a/services/inputflinger/dispatcher/CancelationOptions.h b/services/inputflinger/dispatcher/CancelationOptions.h
index d210e9e..512cb6e 100644
--- a/services/inputflinger/dispatcher/CancelationOptions.h
+++ b/services/inputflinger/dispatcher/CancelationOptions.h
@@ -24,7 +24,7 @@
 
 /* Specifies which events are to be canceled and why. */
 struct CancelationOptions {
-    enum Mode {
+    enum class Mode {
         CANCEL_ALL_EVENTS = 0,
         CANCEL_POINTER_EVENTS = 1,
         CANCEL_NON_POINTER_EVENTS = 2,
diff --git a/services/inputflinger/dispatcher/Entry.cpp b/services/inputflinger/dispatcher/Entry.cpp
index 33e7e17..7bbfb95 100644
--- a/services/inputflinger/dispatcher/Entry.cpp
+++ b/services/inputflinger/dispatcher/Entry.cpp
@@ -166,7 +166,7 @@
         repeatCount(repeatCount),
         downTime(downTime),
         syntheticRepeat(false),
-        interceptKeyResult(KeyEntry::INTERCEPT_KEY_RESULT_UNKNOWN),
+        interceptKeyResult(KeyEntry::InterceptKeyResult::UNKNOWN),
         interceptKeyWakeupTime(0) {}
 
 KeyEntry::~KeyEntry() {}
@@ -189,7 +189,7 @@
 
     dispatchInProgress = false;
     syntheticRepeat = false;
-    interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_UNKNOWN;
+    interceptKeyResult = KeyEntry::InterceptKeyResult::UNKNOWN;
     interceptKeyWakeupTime = 0;
 }
 
@@ -308,7 +308,8 @@
 
 volatile int32_t DispatchEntry::sNextSeqAtomic;
 
-DispatchEntry::DispatchEntry(std::shared_ptr<EventEntry> eventEntry, int32_t targetFlags,
+DispatchEntry::DispatchEntry(std::shared_ptr<EventEntry> eventEntry,
+                             ftl::Flags<InputTarget::Flags> targetFlags,
                              const ui::Transform& transform, const ui::Transform& rawTransform,
                              float globalScaleFactor)
       : seq(nextSeq()),
diff --git a/services/inputflinger/dispatcher/Entry.h b/services/inputflinger/dispatcher/Entry.h
index 60f319a..3799814 100644
--- a/services/inputflinger/dispatcher/Entry.h
+++ b/services/inputflinger/dispatcher/Entry.h
@@ -140,11 +140,11 @@
 
     bool syntheticRepeat; // set to true for synthetic key repeats
 
-    enum InterceptKeyResult {
-        INTERCEPT_KEY_RESULT_UNKNOWN,
-        INTERCEPT_KEY_RESULT_SKIP,
-        INTERCEPT_KEY_RESULT_CONTINUE,
-        INTERCEPT_KEY_RESULT_TRY_AGAIN_LATER,
+    enum class InterceptKeyResult {
+        UNKNOWN,
+        SKIP,
+        CONTINUE,
+        TRY_AGAIN_LATER,
     };
     InterceptKeyResult interceptKeyResult; // set based on the interception result
     nsecs_t interceptKeyWakeupTime;        // used with INTERCEPT_KEY_RESULT_TRY_AGAIN_LATER
@@ -223,7 +223,7 @@
     const uint32_t seq; // unique sequence number, never 0
 
     std::shared_ptr<EventEntry> eventEntry; // the event to dispatch
-    int32_t targetFlags;
+    ftl::Flags<InputTarget::Flags> targetFlags;
     ui::Transform transform;
     ui::Transform rawTransform;
     float globalScaleFactor;
@@ -238,13 +238,15 @@
     int32_t resolvedAction;
     int32_t resolvedFlags;
 
-    DispatchEntry(std::shared_ptr<EventEntry> eventEntry, int32_t targetFlags,
-                  const ui::Transform& transform, const ui::Transform& rawTransform,
-                  float globalScaleFactor);
+    DispatchEntry(std::shared_ptr<EventEntry> eventEntry,
+                  ftl::Flags<InputTarget::Flags> targetFlags, const ui::Transform& transform,
+                  const ui::Transform& rawTransform, float globalScaleFactor);
 
-    inline bool hasForegroundTarget() const { return targetFlags & InputTarget::FLAG_FOREGROUND; }
+    inline bool hasForegroundTarget() const {
+        return targetFlags.test(InputTarget::Flags::FOREGROUND);
+    }
 
-    inline bool isSplit() const { return targetFlags & InputTarget::FLAG_SPLIT; }
+    inline bool isSplit() const { return targetFlags.test(InputTarget::Flags::SPLIT); }
 
 private:
     static volatile int32_t sNextSeqAtomic;
diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp
index a793f57..4d3e6de 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.cpp
+++ b/services/inputflinger/dispatcher/InputDispatcher.cpp
@@ -25,8 +25,11 @@
 #include <android/os/IInputConstants.h>
 #include <binder/Binder.h>
 #include <ftl/enum.h>
+#if defined(__ANDROID__)
 #include <gui/SurfaceComposerClient.h>
+#endif
 #include <input/InputDevice.h>
+#include <input/PrintTools.h>
 #include <powermanager/PowerManager.h>
 #include <unistd.h>
 #include <utils/Trace.h>
@@ -48,6 +51,7 @@
 #define INDENT3 "      "
 #define INDENT4 "        "
 
+using namespace android::ftl::flag_operators;
 using android::base::HwTimeoutMultiplier;
 using android::base::Result;
 using android::base::StringPrintf;
@@ -56,7 +60,6 @@
 using android::gui::TouchOcclusionMode;
 using android::gui::WindowInfo;
 using android::gui::WindowInfoHandle;
-using android::os::IInputConstants;
 using android::os::InputEventInjectionResult;
 using android::os::InputEventInjectionSync;
 
@@ -145,21 +148,23 @@
 }
 
 bool isValidMotionAction(int32_t action, int32_t actionButton, int32_t pointerCount) {
-    switch (action & AMOTION_EVENT_ACTION_MASK) {
+    switch (MotionEvent::getActionMasked(action)) {
         case AMOTION_EVENT_ACTION_DOWN:
         case AMOTION_EVENT_ACTION_UP:
-        case AMOTION_EVENT_ACTION_CANCEL:
+            return pointerCount == 1;
         case AMOTION_EVENT_ACTION_MOVE:
-        case AMOTION_EVENT_ACTION_OUTSIDE:
         case AMOTION_EVENT_ACTION_HOVER_ENTER:
         case AMOTION_EVENT_ACTION_HOVER_MOVE:
         case AMOTION_EVENT_ACTION_HOVER_EXIT:
+            return pointerCount >= 1;
+        case AMOTION_EVENT_ACTION_CANCEL:
+        case AMOTION_EVENT_ACTION_OUTSIDE:
         case AMOTION_EVENT_ACTION_SCROLL:
             return true;
         case AMOTION_EVENT_ACTION_POINTER_DOWN:
         case AMOTION_EVENT_ACTION_POINTER_UP: {
-            int32_t index = getMotionEventActionPointerIndex(action);
-            return index >= 0 && index < pointerCount;
+            const int32_t index = MotionEvent::getActionIndex(action);
+            return index >= 0 && index < pointerCount && pointerCount > 1;
         }
         case AMOTION_EVENT_ACTION_BUTTON_PRESS:
         case AMOTION_EVENT_ACTION_BUTTON_RELEASE:
@@ -239,9 +244,9 @@
         }
         dump.append(INDENT4);
         dump += entry.eventEntry->getDescription();
-        dump += StringPrintf(", seq=%" PRIu32
-                             ", targetFlags=0x%08x, resolvedAction=%d, age=%" PRId64 "ms",
-                             entry.seq, entry.targetFlags, entry.resolvedAction,
+        dump += StringPrintf(", seq=%" PRIu32 ", targetFlags=%s, resolvedAction=%d, age=%" PRId64
+                             "ms",
+                             entry.seq, entry.targetFlags.string().c_str(), entry.resolvedAction,
                              ns2ms(currentTime - entry.eventEntry->eventTime));
         if (entry.deliveryTime != 0) {
             // This entry was delivered, so add information on how long we've been waiting
@@ -286,9 +291,9 @@
             first->applicationInfo.token == second->applicationInfo.token;
 }
 
-std::unique_ptr<DispatchEntry> createDispatchEntry(const InputTarget& inputTarget,
-                                                   std::shared_ptr<EventEntry> eventEntry,
-                                                   int32_t inputTargetFlags) {
+std::unique_ptr<DispatchEntry> createDispatchEntry(
+        const InputTarget& inputTarget, std::shared_ptr<EventEntry> eventEntry,
+        ftl::Flags<InputTarget::Flags> inputTargetFlags) {
     if (inputTarget.useDefaultPointerTransform()) {
         const ui::Transform& transform = inputTarget.getDefaultPointerTransform();
         return std::make_unique<DispatchEntry>(eventEntry, inputTargetFlags, transform,
@@ -477,15 +482,14 @@
 
 bool isPointerFromStylus(const MotionEntry& entry, int32_t pointerIndex) {
     return isFromSource(entry.source, AINPUT_SOURCE_STYLUS) &&
-            (entry.pointerProperties[pointerIndex].toolType == AMOTION_EVENT_TOOL_TYPE_STYLUS ||
-             entry.pointerProperties[pointerIndex].toolType == AMOTION_EVENT_TOOL_TYPE_ERASER);
+            isStylusToolType(entry.pointerProperties[pointerIndex].toolType);
 }
 
-// Determines if the given window can be targeted as InputTarget::FLAG_FOREGROUND.
+// Determines if the given window can be targeted as InputTarget::Flags::FOREGROUND.
 // Foreground events are only sent to "foreground targetable" windows, but not all gestures sent to
 // such window are necessarily targeted with the flag. For example, an event with ACTION_OUTSIDE can
 // be sent to such a window, but it is not a foreground event and doesn't use
-// InputTarget::FLAG_FOREGROUND.
+// InputTarget::Flags::FOREGROUND.
 bool canReceiveForegroundTouches(const WindowInfo& info) {
     // A non-touchable window can still receive touch events (e.g. in the case of
     // STYLUS_INTERCEPTOR), so prevent such windows from receiving foreground events for touches.
@@ -539,6 +543,76 @@
                          entry.pointerCoords[pointerIndex].getAxisValue(AMOTION_EVENT_AXIS_Y)));
 }
 
+std::optional<nsecs_t> getDownTime(const EventEntry& eventEntry) {
+    if (eventEntry.type == EventEntry::Type::KEY) {
+        const KeyEntry& keyEntry = static_cast<const KeyEntry&>(eventEntry);
+        return keyEntry.downTime;
+    } else if (eventEntry.type == EventEntry::Type::MOTION) {
+        const MotionEntry& motionEntry = static_cast<const MotionEntry&>(eventEntry);
+        return motionEntry.downTime;
+    }
+    return std::nullopt;
+}
+
+/**
+ * Compare the old touch state to the new touch state, and generate the corresponding touched
+ * windows (== input targets).
+ * If a window had the hovering pointer, but now it doesn't, produce HOVER_EXIT for that window.
+ * If the pointer just entered the new window, produce HOVER_ENTER.
+ * For pointers remaining in the window, produce HOVER_MOVE.
+ */
+std::vector<TouchedWindow> getHoveringWindowsLocked(const TouchState* oldState,
+                                                    const TouchState& newTouchState,
+                                                    const MotionEntry& entry) {
+    std::vector<TouchedWindow> out;
+    const int32_t maskedAction = MotionEvent::getActionMasked(entry.action);
+    if (maskedAction != AMOTION_EVENT_ACTION_HOVER_ENTER &&
+        maskedAction != AMOTION_EVENT_ACTION_HOVER_MOVE &&
+        maskedAction != AMOTION_EVENT_ACTION_HOVER_EXIT) {
+        // Not a hover event - don't need to do anything
+        return out;
+    }
+
+    // We should consider all hovering pointers here. But for now, just use the first one
+    const int32_t pointerId = entry.pointerProperties[0].id;
+
+    std::set<sp<WindowInfoHandle>> oldWindows;
+    if (oldState != nullptr) {
+        oldWindows = oldState->getWindowsWithHoveringPointer(entry.deviceId, pointerId);
+    }
+
+    std::set<sp<WindowInfoHandle>> newWindows =
+            newTouchState.getWindowsWithHoveringPointer(entry.deviceId, pointerId);
+
+    // 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.pointerIds.markBit(pointerId);
+            out.push_back(touchedWindow);
+        }
+    }
+
+    for (const sp<WindowInfoHandle>& newWindow : newWindows) {
+        TouchedWindow touchedWindow;
+        touchedWindow.windowHandle = newWindow;
+        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;
+        } else {
+            // This pointer was already sent to the window. Use ACTION_HOVER_MOVE.
+            LOG_ALWAYS_FATAL_IF(maskedAction != AMOTION_EVENT_ACTION_HOVER_MOVE);
+            touchedWindow.targetFlags = InputTarget::Flags::DISPATCH_AS_IS;
+        }
+        touchedWindow.pointerIds.markBit(pointerId);
+        out.push_back(touchedWindow);
+    }
+    return out;
+}
+
 } // namespace
 
 // --- InputDispatcher ---
@@ -553,7 +627,7 @@
         mLastDropReason(DropReason::NOT_DROPPED),
         mIdGenerator(IdGenerator::Source::INPUT_DISPATCHER),
         mAppSwitchSawKeyDown(false),
-        mAppSwitchDueTime(LONG_LONG_MAX),
+        mAppSwitchDueTime(LLONG_MAX),
         mNextUnblockedEvent(nullptr),
         mMonitorDispatchingTimeout(DEFAULT_INPUT_DISPATCHING_TIMEOUT),
         mDispatchEnabled(false),
@@ -569,8 +643,9 @@
     mReporter = createInputReporter();
 
     mWindowInfoListener = sp<DispatcherWindowListener>::make(*this);
+#if defined(__ANDROID__)
     SurfaceComposerClient::getDefault()->addWindowInfosListener(mWindowInfoListener);
-
+#endif
     mKeyRepeatState.lastKeyEntry = nullptr;
     policy->getDispatcherConfiguration(&mConfig);
 }
@@ -609,7 +684,7 @@
 }
 
 void InputDispatcher::dispatchOnce() {
-    nsecs_t nextWakeupTime = LONG_LONG_MAX;
+    nsecs_t nextWakeupTime = LLONG_MAX;
     { // acquire lock
         std::scoped_lock _l(mLock);
         mDispatcherIsAlive.notify_all();
@@ -623,7 +698,7 @@
         // Run all pending commands if there are any.
         // If any commands were run then force the next poll to wake up immediately.
         if (runCommandsLockedInterruptable()) {
-            nextWakeupTime = LONG_LONG_MIN;
+            nextWakeupTime = LLONG_MIN;
         }
 
         // If we are still waiting for ack on some events,
@@ -633,7 +708,7 @@
 
         // We are about to enter an infinitely long sleep, because we have no commands or
         // pending or queued events
-        if (nextWakeupTime == LONG_LONG_MAX) {
+        if (nextWakeupTime == LLONG_MAX) {
             mDispatcherEnteredIdle.notify_all();
         }
     } // release lock
@@ -678,14 +753,14 @@
  */
 nsecs_t InputDispatcher::processAnrsLocked() {
     const nsecs_t currentTime = now();
-    nsecs_t nextAnrCheck = LONG_LONG_MAX;
+    nsecs_t nextAnrCheck = LLONG_MAX;
     // Check if we are waiting for a focused window to appear. Raise ANR if waited too long
     if (mNoFocusedWindowTimeoutTime.has_value() && mAwaitedFocusedApplication != nullptr) {
         if (currentTime >= *mNoFocusedWindowTimeoutTime) {
             processNoFocusedWindowAnrLocked();
             mAwaitedFocusedApplication.reset();
             mNoFocusedWindowTimeoutTime = std::nullopt;
-            return LONG_LONG_MIN;
+            return LLONG_MIN;
         } else {
             // Keep waiting. We will drop the event when mNoFocusedWindowTimeoutTime comes.
             nextAnrCheck = *mNoFocusedWindowTimeoutTime;
@@ -708,7 +783,7 @@
     // Stop waking up for this unresponsive connection
     mAnrTracker.eraseToken(connection->inputChannel->getConnectionToken());
     onAnrLocked(connection);
-    return LONG_LONG_MIN;
+    return LLONG_MIN;
 }
 
 std::chrono::nanoseconds InputDispatcher::getDispatchingTimeoutLocked(
@@ -915,7 +990,7 @@
         mLastDropReason = dropReason;
 
         releasePendingEventLocked();
-        *nextWakeupTime = LONG_LONG_MIN; // force next poll to wake up immediately
+        *nextWakeupTime = LLONG_MIN; // force next poll to wake up immediately
     }
 }
 
@@ -1018,8 +1093,8 @@
                 KeyEntry& pendingKey = static_cast<KeyEntry&>(*mPendingEvent);
                 if (pendingKey.keyCode == keyEntry.keyCode &&
                     pendingKey.interceptKeyResult ==
-                            KeyEntry::INTERCEPT_KEY_RESULT_TRY_AGAIN_LATER) {
-                    pendingKey.interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_UNKNOWN;
+                            KeyEntry::InterceptKeyResult::TRY_AGAIN_LATER) {
+                    pendingKey.interceptKeyResult = KeyEntry::InterceptKeyResult::UNKNOWN;
                     pendingKey.interceptKeyWakeupTime = 0;
                     needWake = true;
                 }
@@ -1068,7 +1143,7 @@
                                                                 int32_t y, TouchState* touchState,
                                                                 bool isStylus,
                                                                 bool addOutsideTargets,
-                                                                bool ignoreDragWindow) {
+                                                                bool ignoreDragWindow) const {
     if (addOutsideTargets && touchState == nullptr) {
         LOG_ALWAYS_FATAL("Must provide a valid touch state if adding outside targets");
     }
@@ -1086,7 +1161,7 @@
 
         if (addOutsideTargets &&
             info.inputConfig.test(WindowInfo::InputConfig::WATCH_OUTSIDE_TOUCH)) {
-            touchState->addOrUpdateWindow(windowHandle, InputTarget::FLAG_DISPATCH_AS_OUTSIDE,
+            touchState->addOrUpdateWindow(windowHandle, InputTarget::Flags::DISPATCH_AS_OUTSIDE,
                                           BitSet32(0));
         }
     }
@@ -1154,17 +1229,18 @@
 
     switch (entry.type) {
         case EventEntry::Type::KEY: {
-            CancelationOptions options(CancelationOptions::CANCEL_NON_POINTER_EVENTS, reason);
+            CancelationOptions options(CancelationOptions::Mode::CANCEL_NON_POINTER_EVENTS, reason);
             synthesizeCancelationEventsForAllConnectionsLocked(options);
             break;
         }
         case EventEntry::Type::MOTION: {
             const MotionEntry& motionEntry = static_cast<const MotionEntry&>(entry);
             if (motionEntry.source & AINPUT_SOURCE_CLASS_POINTER) {
-                CancelationOptions options(CancelationOptions::CANCEL_POINTER_EVENTS, reason);
+                CancelationOptions options(CancelationOptions::Mode::CANCEL_POINTER_EVENTS, reason);
                 synthesizeCancelationEventsForAllConnectionsLocked(options);
             } else {
-                CancelationOptions options(CancelationOptions::CANCEL_NON_POINTER_EVENTS, reason);
+                CancelationOptions options(CancelationOptions::Mode::CANCEL_NON_POINTER_EVENTS,
+                                           reason);
                 synthesizeCancelationEventsForAllConnectionsLocked(options);
             }
             break;
@@ -1198,11 +1274,11 @@
 }
 
 bool InputDispatcher::isAppSwitchPendingLocked() {
-    return mAppSwitchDueTime != LONG_LONG_MAX;
+    return mAppSwitchDueTime != LLONG_MAX;
 }
 
 void InputDispatcher::resetPendingAppSwitchLocked(bool handled) {
-    mAppSwitchDueTime = LONG_LONG_MAX;
+    mAppSwitchDueTime = LLONG_MAX;
 
     if (DEBUG_APP_SWITCH) {
         if (handled) {
@@ -1319,7 +1395,7 @@
         resetKeyRepeatLocked();
     }
 
-    CancelationOptions options(CancelationOptions::CANCEL_ALL_EVENTS, "device was reset");
+    CancelationOptions options(CancelationOptions::Mode::CANCEL_ALL_EVENTS, "device was reset");
     options.deviceId = entry.deviceId;
     synthesizeCancelationEventsForAllConnectionsLocked(options);
     return true;
@@ -1357,7 +1433,7 @@
     }
     InputTarget target;
     target.inputChannel = channel;
-    target.flags = InputTarget::FLAG_DISPATCH_AS_IS;
+    target.flags = InputTarget::Flags::DISPATCH_AS_IS;
     entry->dispatchInProgress = true;
     std::string message = std::string("Focus ") + (entry->hasFocus ? "entering " : "leaving ") +
             channel->getName();
@@ -1431,7 +1507,7 @@
     }
     InputTarget target;
     target.inputChannel = channel;
-    target.flags = InputTarget::FLAG_DISPATCH_AS_IS;
+    target.flags = InputTarget::Flags::DISPATCH_AS_IS;
     entry->dispatchInProgress = true;
     dispatchEventLocked(currentTime, entry, {target});
 
@@ -1468,7 +1544,7 @@
         }
         InputTarget target;
         target.inputChannel = channel;
-        target.flags = InputTarget::FLAG_DISPATCH_AS_IS;
+        target.flags = InputTarget::Flags::DISPATCH_AS_IS;
         inputTargets.push_back(target);
     }
     return inputTargets;
@@ -1495,7 +1571,7 @@
                 // stop the key repeat on current device.
                 entry->repeatCount = mKeyRepeatState.lastKeyEntry->repeatCount + 1;
                 resetKeyRepeatLocked();
-                mKeyRepeatState.nextRepeatTime = LONG_LONG_MAX; // don't generate repeats ourselves
+                mKeyRepeatState.nextRepeatTime = LLONG_MAX; // don't generate repeats ourselves
             } else {
                 // Not a repeat.  Save key down state in case we do see a repeat later.
                 resetKeyRepeatLocked();
@@ -1524,19 +1600,19 @@
     }
 
     // Handle case where the policy asked us to try again later last time.
-    if (entry->interceptKeyResult == KeyEntry::INTERCEPT_KEY_RESULT_TRY_AGAIN_LATER) {
+    if (entry->interceptKeyResult == KeyEntry::InterceptKeyResult::TRY_AGAIN_LATER) {
         if (currentTime < entry->interceptKeyWakeupTime) {
             if (entry->interceptKeyWakeupTime < *nextWakeupTime) {
                 *nextWakeupTime = entry->interceptKeyWakeupTime;
             }
             return false; // wait until next wakeup
         }
-        entry->interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_UNKNOWN;
+        entry->interceptKeyResult = KeyEntry::InterceptKeyResult::UNKNOWN;
         entry->interceptKeyWakeupTime = 0;
     }
 
     // Give the policy a chance to intercept the key.
-    if (entry->interceptKeyResult == KeyEntry::INTERCEPT_KEY_RESULT_UNKNOWN) {
+    if (entry->interceptKeyResult == KeyEntry::InterceptKeyResult::UNKNOWN) {
         if (entry->policyFlags & POLICY_FLAG_PASS_TO_USER) {
             sp<IBinder> focusedWindowToken =
                     mFocusResolver.getFocusedWindowToken(getTargetDisplayId(*entry));
@@ -1547,9 +1623,9 @@
             postCommandLocked(std::move(command));
             return false; // wait for the command to run
         } else {
-            entry->interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_CONTINUE;
+            entry->interceptKeyResult = KeyEntry::InterceptKeyResult::CONTINUE;
         }
-    } else if (entry->interceptKeyResult == KeyEntry::INTERCEPT_KEY_RESULT_SKIP) {
+    } else if (entry->interceptKeyResult == KeyEntry::InterceptKeyResult::SKIP) {
         if (*dropReason == DropReason::NOT_DROPPED) {
             *dropReason = DropReason::POLICY;
         }
@@ -1565,9 +1641,10 @@
     }
 
     // Identify targets.
-    std::vector<InputTarget> inputTargets;
-    InputEventInjectionResult injectionResult =
-            findFocusedWindowTargetsLocked(currentTime, *entry, inputTargets, nextWakeupTime);
+    InputEventInjectionResult injectionResult;
+    sp<WindowInfoHandle> focusedWindow =
+            findFocusedWindowTargetLocked(currentTime, *entry, nextWakeupTime,
+                                          /*byref*/ injectionResult);
     if (injectionResult == InputEventInjectionResult::PENDING) {
         return false;
     }
@@ -1576,6 +1653,12 @@
     if (injectionResult != InputEventInjectionResult::SUCCEEDED) {
         return true;
     }
+    LOG_ALWAYS_FATAL_IF(focusedWindow == nullptr);
+
+    std::vector<InputTarget> inputTargets;
+    addWindowTargetLocked(focusedWindow,
+                          InputTarget::Flags::FOREGROUND | InputTarget::Flags::DISPATCH_AS_IS,
+                          BitSet32(0), getDownTime(*entry), inputTargets);
 
     // Add monitor channels from event's or focused display.
     addGlobalMonitoringTargetsLocked(inputTargets, getTargetDisplayId(*entry));
@@ -1670,13 +1753,27 @@
             pilferPointersLocked(mDragState->dragWindow->getToken());
         }
 
-        injectionResult =
-                findTouchedWindowTargetsLocked(currentTime, *entry, inputTargets, nextWakeupTime,
-                                               &conflictingPointerActions);
+        std::vector<TouchedWindow> touchedWindows =
+                findTouchedWindowTargetsLocked(currentTime, *entry, &conflictingPointerActions,
+                                               /*byref*/ injectionResult);
+        for (const TouchedWindow& touchedWindow : touchedWindows) {
+            LOG_ALWAYS_FATAL_IF(injectionResult != InputEventInjectionResult::SUCCEEDED,
+                                "Shouldn't be adding window if the injection didn't succeed.");
+            addWindowTargetLocked(touchedWindow.windowHandle, touchedWindow.targetFlags,
+                                  touchedWindow.pointerIds, touchedWindow.firstDownTimeInTarget,
+                                  inputTargets);
+        }
     } else {
         // Non touch event.  (eg. trackball)
-        injectionResult =
-                findFocusedWindowTargetsLocked(currentTime, *entry, inputTargets, nextWakeupTime);
+        sp<WindowInfoHandle> focusedWindow =
+                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,
+                                  BitSet32(0), getDownTime(*entry), inputTargets);
+        }
     }
     if (injectionResult == InputEventInjectionResult::PENDING) {
         return false;
@@ -1687,9 +1784,9 @@
         return true;
     }
     if (injectionResult != InputEventInjectionResult::SUCCEEDED) {
-        CancelationOptions::Mode mode(isPointerEvent
-                                              ? CancelationOptions::CANCEL_POINTER_EVENTS
-                                              : CancelationOptions::CANCEL_NON_POINTER_EVENTS);
+        CancelationOptions::Mode mode(
+                isPointerEvent ? CancelationOptions::Mode::CANCEL_POINTER_EVENTS
+                               : CancelationOptions::Mode::CANCEL_NON_POINTER_EVENTS);
         CancelationOptions options(mode, "input event injection failed");
         synthesizeCancelationEventsForMonitorsLocked(options);
         return true;
@@ -1700,7 +1797,7 @@
 
     // Dispatch the motion.
     if (conflictingPointerActions) {
-        CancelationOptions options(CancelationOptions::CANCEL_POINTER_EVENTS,
+        CancelationOptions options(CancelationOptions::Mode::CANCEL_POINTER_EVENTS,
                                    "conflicting pointer actions");
         synthesizeCancelationEventsForAllConnectionsLocked(options);
     }
@@ -1726,22 +1823,23 @@
     }
     InputTarget target;
     target.inputChannel = channel;
-    target.flags = InputTarget::FLAG_DISPATCH_AS_IS;
+    target.flags = InputTarget::Flags::DISPATCH_AS_IS;
     entry->dispatchInProgress = true;
     dispatchEventLocked(currentTime, entry, {target});
 }
 
 void InputDispatcher::logOutboundMotionDetails(const char* prefix, const MotionEntry& entry) {
     if (DEBUG_OUTBOUND_EVENT_DETAILS) {
-        ALOGD("%seventTime=%" PRId64 ", deviceId=%d, source=0x%x, displayId=%" PRId32
+        ALOGD("%seventTime=%" PRId64 ", deviceId=%d, source=%s, displayId=%" PRId32
               ", policyFlags=0x%x, "
               "action=%s, actionButton=0x%x, flags=0x%x, "
               "metaState=0x%x, buttonState=0x%x,"
               "edgeFlags=0x%x, xPrecision=%f, yPrecision=%f, downTime=%" PRId64,
-              prefix, entry.eventTime, entry.deviceId, entry.source, entry.displayId,
-              entry.policyFlags, MotionEvent::actionToString(entry.action).c_str(),
-              entry.actionButton, entry.flags, entry.metaState, entry.buttonState, entry.edgeFlags,
-              entry.xPrecision, entry.yPrecision, entry.downTime);
+              prefix, entry.eventTime, entry.deviceId,
+              inputEventSourceToString(entry.source).c_str(), entry.displayId, entry.policyFlags,
+              MotionEvent::actionToString(entry.action).c_str(), entry.actionButton, entry.flags,
+              entry.metaState, entry.buttonState, entry.edgeFlags, entry.xPrecision,
+              entry.yPrecision, entry.downTime);
 
         for (uint32_t i = 0; i < entry.pointerCount; i++) {
             ALOGD("  Pointer %d: id=%d, toolType=%d, "
@@ -1800,7 +1898,7 @@
     ALOGW("Canceling events for %s because it is unresponsive",
           connection->inputChannel->getName().c_str());
     if (connection->status == Connection::Status::NORMAL) {
-        CancelationOptions options(CancelationOptions::CANCEL_ALL_EVENTS,
+        CancelationOptions options(CancelationOptions::Mode::CANCEL_ALL_EVENTS,
                                    "application not responding");
         synthesizeCancelationEventsForConnectionLocked(connection, options);
     }
@@ -1878,21 +1976,11 @@
     return false;
 }
 
-static std::optional<nsecs_t> getDownTime(const EventEntry& eventEntry) {
-    if (eventEntry.type == EventEntry::Type::KEY) {
-        const KeyEntry& keyEntry = static_cast<const KeyEntry&>(eventEntry);
-        return keyEntry.downTime;
-    } else if (eventEntry.type == EventEntry::Type::MOTION) {
-        const MotionEntry& motionEntry = static_cast<const MotionEntry&>(eventEntry);
-        return motionEntry.downTime;
-    }
-    return std::nullopt;
-}
-
-InputEventInjectionResult InputDispatcher::findFocusedWindowTargetsLocked(
-        nsecs_t currentTime, const EventEntry& entry, std::vector<InputTarget>& inputTargets,
-        nsecs_t* nextWakeupTime) {
+sp<WindowInfoHandle> InputDispatcher::findFocusedWindowTargetLocked(
+        nsecs_t currentTime, const EventEntry& entry, nsecs_t* nextWakeupTime,
+        InputEventInjectionResult& outInjectionResult) {
     std::string reason;
+    outInjectionResult = InputEventInjectionResult::FAILED; // Default result
 
     int32_t displayId = getTargetDisplayId(entry);
     sp<WindowInfoHandle> focusedWindowHandle = getFocusedWindowHandleLocked(displayId);
@@ -1905,12 +1993,12 @@
         ALOGI("Dropping %s event because there is no focused window or focused application in "
               "display %" PRId32 ".",
               ftl::enum_string(entry.type).c_str(), displayId);
-        return InputEventInjectionResult::FAILED;
+        return nullptr;
     }
 
     // Drop key events if requested by input feature
     if (focusedWindowHandle != nullptr && shouldDropInput(entry, focusedWindowHandle)) {
-        return InputEventInjectionResult::FAILED;
+        return nullptr;
     }
 
     // Compatibility behavior: raise ANR if there is a focused application, but no focused window.
@@ -1930,15 +2018,17 @@
                   "window when it finishes starting up. Will wait for %" PRId64 "ms",
                   mAwaitedFocusedApplication->getName().c_str(), millis(timeout));
             *nextWakeupTime = *mNoFocusedWindowTimeoutTime;
-            return InputEventInjectionResult::PENDING;
+            outInjectionResult = InputEventInjectionResult::PENDING;
+            return nullptr;
         } else if (currentTime > *mNoFocusedWindowTimeoutTime) {
             // Already raised ANR. Drop the event
             ALOGE("Dropping %s event because there is no focused window",
                   ftl::enum_string(entry.type).c_str());
-            return InputEventInjectionResult::FAILED;
+            return nullptr;
         } else {
             // Still waiting for the focused window
-            return InputEventInjectionResult::PENDING;
+            outInjectionResult = InputEventInjectionResult::PENDING;
+            return nullptr;
         }
     }
 
@@ -1948,13 +2038,15 @@
     // Verify targeted injection.
     if (const auto err = verifyTargetedInjection(focusedWindowHandle, entry); err) {
         ALOGW("Dropping injected event: %s", (*err).c_str());
-        return InputEventInjectionResult::TARGET_MISMATCH;
+        outInjectionResult = InputEventInjectionResult::TARGET_MISMATCH;
+        return nullptr;
     }
 
     if (focusedWindowHandle->getInfo()->inputConfig.test(
                 WindowInfo::InputConfig::PAUSE_DISPATCHING)) {
         ALOGI("Waiting because %s is paused", focusedWindowHandle->getName().c_str());
-        return InputEventInjectionResult::PENDING;
+        outInjectionResult = InputEventInjectionResult::PENDING;
+        return nullptr;
     }
 
     // If the event is a key event, then we must wait for all previous events to
@@ -1971,17 +2063,13 @@
     if (entry.type == EventEntry::Type::KEY) {
         if (shouldWaitToSendKeyLocked(currentTime, focusedWindowHandle->getName().c_str())) {
             *nextWakeupTime = *mKeyIsWaitingForEventsTimeout;
-            return InputEventInjectionResult::PENDING;
+            outInjectionResult = InputEventInjectionResult::PENDING;
+            return nullptr;
         }
     }
 
-    // Success!  Output targets.
-    addWindowTargetLocked(focusedWindowHandle,
-                          InputTarget::FLAG_FOREGROUND | InputTarget::FLAG_DISPATCH_AS_IS,
-                          BitSet32(0), getDownTime(entry), inputTargets);
-
-    // Done.
-    return InputEventInjectionResult::SUCCEEDED;
+    outInjectionResult = InputEventInjectionResult::SUCCEEDED;
+    return focusedWindowHandle;
 }
 
 /**
@@ -2010,11 +2098,42 @@
     return responsiveMonitors;
 }
 
-InputEventInjectionResult InputDispatcher::findTouchedWindowTargetsLocked(
-        nsecs_t currentTime, const MotionEntry& entry, std::vector<InputTarget>& inputTargets,
-        nsecs_t* nextWakeupTime, bool* outConflictingPointerActions) {
+/**
+ * In general, touch should be always split between windows. Some exceptions:
+ * 1. Don't split touch is if we have an active pointer down, and a new pointer is going down that's
+ * from the same device, *and* the window that's receiving the current pointer does not support
+ * split touch.
+ * 2. Don't split mouse events
+ */
+bool InputDispatcher::shouldSplitTouch(const TouchState& touchState,
+                                       const MotionEntry& entry) const {
+    if (isFromSource(entry.source, AINPUT_SOURCE_MOUSE)) {
+        // We should never split mouse events
+        return false;
+    }
+    for (const TouchedWindow& touchedWindow : touchState.windows) {
+        if (touchedWindow.windowHandle->getInfo()->isSpy()) {
+            // Spy windows should not affect whether or not touch is split.
+            continue;
+        }
+        if (touchedWindow.windowHandle->getInfo()->supportsSplitTouch()) {
+            continue;
+        }
+        // Eventually, touchedWindow will contain the deviceId of each pointer that's currently
+        // being sent there. For now, use deviceId from touch state.
+        if (entry.deviceId == touchState.deviceId && !touchedWindow.pointerIds.isEmpty()) {
+            return false;
+        }
+    }
+    return true;
+}
+
+std::vector<TouchedWindow> InputDispatcher::findTouchedWindowTargetsLocked(
+        nsecs_t currentTime, const MotionEntry& entry, bool* outConflictingPointerActions,
+        InputEventInjectionResult& outInjectionResult) {
     ATRACE_CALL();
 
+    std::vector<TouchedWindow> touchedWindows;
     // For security reasons, we defer updating the touch state until we are sure that
     // event injection will be allowed.
     const int32_t displayId = entry.displayId;
@@ -2022,9 +2141,7 @@
     const int32_t maskedAction = action & AMOTION_EVENT_ACTION_MASK;
 
     // Update the touch state as needed based on the properties of the touch event.
-    InputEventInjectionResult injectionResult = InputEventInjectionResult::PENDING;
-    sp<WindowInfoHandle> newHoverWindowHandle(mLastHoverWindowHandle);
-    sp<WindowInfoHandle> newTouchedWindowHandle;
+    outInjectionResult = InputEventInjectionResult::PENDING;
 
     // Copy current touch state into tempTouchState.
     // This state will be used to update mTouchStatesByDisplay at the end of this function.
@@ -2036,10 +2153,9 @@
         tempTouchState = *oldState;
     }
 
-    bool isSplit = tempTouchState.split;
-    bool switchedDevice = tempTouchState.deviceId >= 0 && tempTouchState.displayId >= 0 &&
-            (tempTouchState.deviceId != entry.deviceId || tempTouchState.source != entry.source ||
-             tempTouchState.displayId != displayId);
+    bool isSplit = shouldSplitTouch(tempTouchState, entry);
+    const bool switchedDevice = (oldState != nullptr) &&
+            (oldState->deviceId != entry.deviceId || oldState->source != entry.source);
 
     const bool isHoverAction = (maskedAction == AMOTION_EVENT_ACTION_HOVER_MOVE ||
                                 maskedAction == AMOTION_EVENT_ACTION_HOVER_ENTER ||
@@ -2047,34 +2163,34 @@
     const bool newGesture = (maskedAction == AMOTION_EVENT_ACTION_DOWN ||
                              maskedAction == AMOTION_EVENT_ACTION_SCROLL || isHoverAction);
     const bool isFromMouse = isFromSource(entry.source, AINPUT_SOURCE_MOUSE);
-    bool wrongDevice = false;
+
     if (newGesture) {
         bool down = maskedAction == AMOTION_EVENT_ACTION_DOWN;
-        if (switchedDevice && tempTouchState.down && !down && !isHoverAction) {
+        if (switchedDevice && tempTouchState.isDown() && !down && !isHoverAction) {
             ALOGI("Dropping event because a pointer for a different device is already down "
                   "in display %" PRId32,
                   displayId);
             // TODO: test multiple simultaneous input streams.
-            injectionResult = InputEventInjectionResult::FAILED;
-            switchedDevice = false;
-            wrongDevice = true;
-            goto Failed;
+            outInjectionResult = InputEventInjectionResult::FAILED;
+            return touchedWindows; // wrong device
         }
-        tempTouchState.reset();
-        tempTouchState.down = down;
+        tempTouchState.clearWindowsWithoutPointers();
         tempTouchState.deviceId = entry.deviceId;
         tempTouchState.source = entry.source;
-        tempTouchState.displayId = displayId;
         isSplit = false;
     } else if (switchedDevice && maskedAction == AMOTION_EVENT_ACTION_MOVE) {
         ALOGI("Dropping move event because a pointer for a different device is already active "
               "in display %" PRId32,
               displayId);
         // TODO: test multiple simultaneous input streams.
-        injectionResult = InputEventInjectionResult::FAILED;
-        switchedDevice = false;
-        wrongDevice = true;
-        goto Failed;
+        outInjectionResult = InputEventInjectionResult::FAILED;
+        return touchedWindows; // wrong device
+    }
+
+    if (isHoverAction) {
+        // For hover actions, we will treat 'tempTouchState' as a new state, so let's erase
+        // all of the existing hovering pointers and recompute.
+        tempTouchState.clearHoveringPointers();
     }
 
     if (newGesture || (isSplit && maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN)) {
@@ -2083,8 +2199,9 @@
         const int32_t pointerIndex = getMotionEventActionPointerIndex(action);
         const bool isDown = maskedAction == AMOTION_EVENT_ACTION_DOWN;
         const bool isStylus = isPointerFromStylus(entry, pointerIndex);
-        newTouchedWindowHandle = findTouchedWindowAtLocked(displayId, x, y, &tempTouchState,
-                                                           isStylus, isDown /*addOutsideTargets*/);
+        sp<WindowInfoHandle> newTouchedWindowHandle =
+                findTouchedWindowAtLocked(displayId, x, y, &tempTouchState, isStylus,
+                                          isDown /*addOutsideTargets*/);
 
         // Handle the case where we did not find a window.
         if (newTouchedWindowHandle == nullptr) {
@@ -2097,7 +2214,7 @@
         // Verify targeted injection.
         if (const auto err = verifyTargetedInjection(newTouchedWindowHandle, entry); err) {
             ALOGW("Dropping injected touch event: %s", (*err).c_str());
-            injectionResult = os::InputEventInjectionResult::TARGET_MISMATCH;
+            outInjectionResult = os::InputEventInjectionResult::TARGET_MISMATCH;
             newTouchedWindowHandle = nullptr;
             goto Failed;
         }
@@ -2116,16 +2233,7 @@
             // No window is touched, so set split to true. This will allow the next pointer down to
             // be delivered to a new window which supports split touch. Pointers from a mouse device
             // should never be split.
-            tempTouchState.split = isSplit = !isFromMouse;
-        }
-
-        // Update hover state.
-        if (newTouchedWindowHandle != nullptr) {
-            if (maskedAction == AMOTION_EVENT_ACTION_HOVER_EXIT) {
-                newHoverWindowHandle = nullptr;
-            } else if (isHoverAction) {
-                newHoverWindowHandle = newTouchedWindowHandle;
-            }
+            isSplit = !isFromMouse;
         }
 
         std::vector<sp<WindowInfoHandle>> newTouchedWindows =
@@ -2138,7 +2246,7 @@
         if (newTouchedWindows.empty()) {
             ALOGI("Dropping event because there is no touchable window at (%d, %d) on display %d.",
                   x, y, displayId);
-            injectionResult = InputEventInjectionResult::FAILED;
+            outInjectionResult = InputEventInjectionResult::FAILED;
             goto Failed;
         }
 
@@ -2147,26 +2255,40 @@
                 continue;
             }
 
+            if (isHoverAction) {
+                const int32_t pointerId = entry.pointerProperties[0].id;
+                if (maskedAction == AMOTION_EVENT_ACTION_HOVER_EXIT) {
+                    // Pointer left. Remove it
+                    tempTouchState.removeHoveringPointer(entry.deviceId, pointerId);
+                } else {
+                    // The "windowHandle" is the target of this hovering pointer.
+                    tempTouchState.addHoveringPointerToWindow(windowHandle, entry.deviceId,
+                                                              pointerId);
+                }
+            }
+
             // Set target flags.
-            int32_t targetFlags = InputTarget::FLAG_DISPATCH_AS_IS;
+            ftl::Flags<InputTarget::Flags> targetFlags = InputTarget::Flags::DISPATCH_AS_IS;
 
             if (canReceiveForegroundTouches(*windowHandle->getInfo())) {
                 // There should only be one touched window that can be "foreground" for the pointer.
-                targetFlags |= InputTarget::FLAG_FOREGROUND;
+                targetFlags |= InputTarget::Flags::FOREGROUND;
             }
 
             if (isSplit) {
-                targetFlags |= InputTarget::FLAG_SPLIT;
+                targetFlags |= InputTarget::Flags::SPLIT;
             }
             if (isWindowObscuredAtPointLocked(windowHandle, x, y)) {
-                targetFlags |= InputTarget::FLAG_WINDOW_IS_OBSCURED;
+                targetFlags |= InputTarget::Flags::WINDOW_IS_OBSCURED;
             } else if (isWindowObscuredLocked(windowHandle)) {
-                targetFlags |= InputTarget::FLAG_WINDOW_IS_PARTIALLY_OBSCURED;
+                targetFlags |= InputTarget::Flags::WINDOW_IS_PARTIALLY_OBSCURED;
             }
 
             // Update the temporary touch state.
             BitSet32 pointerIds;
-            pointerIds.markBit(entry.pointerProperties[pointerIndex].id);
+            if (!isHoverAction) {
+                pointerIds.markBit(entry.pointerProperties[pointerIndex].id);
+            }
 
             tempTouchState.addOrUpdateWindow(windowHandle, targetFlags, pointerIds,
                                              entry.eventTime);
@@ -2184,13 +2306,12 @@
         /* Case 2: Pointer move, up, cancel or non-splittable pointer down. */
 
         // If the pointer is not currently down, then ignore the event.
-        if (!tempTouchState.down) {
-            if (DEBUG_FOCUS) {
-                ALOGD("Dropping event because the pointer is not down or we previously "
-                      "dropped the pointer down event in display %" PRId32,
-                      displayId);
-            }
-            injectionResult = InputEventInjectionResult::FAILED;
+        if (!tempTouchState.isDown()) {
+            ALOGD_IF(DEBUG_FOCUS,
+                     "Dropping event because the pointer is not down or we previously "
+                     "dropped the pointer down event in display %" PRId32 ": %s",
+                     displayId, entry.getDescription().c_str());
+            outInjectionResult = InputEventInjectionResult::FAILED;
             goto Failed;
         }
 
@@ -2203,13 +2324,13 @@
             const bool isStylus = isPointerFromStylus(entry, 0 /*pointerIndex*/);
             sp<WindowInfoHandle> oldTouchedWindowHandle =
                     tempTouchState.getFirstForegroundWindowHandle();
-            newTouchedWindowHandle =
+            sp<WindowInfoHandle> newTouchedWindowHandle =
                     findTouchedWindowAtLocked(displayId, x, y, &tempTouchState, isStylus);
 
             // Verify targeted injection.
             if (const auto err = verifyTargetedInjection(newTouchedWindowHandle, entry); err) {
                 ALOGW("Dropping injected event: %s", (*err).c_str());
-                injectionResult = os::InputEventInjectionResult::TARGET_MISMATCH;
+                outInjectionResult = os::InputEventInjectionResult::TARGET_MISMATCH;
                 newTouchedWindowHandle = nullptr;
                 goto Failed;
             }
@@ -2229,7 +2350,7 @@
                 }
                 // Make a slippery exit from the old window.
                 tempTouchState.addOrUpdateWindow(oldTouchedWindowHandle,
-                                                 InputTarget::FLAG_DISPATCH_AS_SLIPPERY_EXIT,
+                                                 InputTarget::Flags::DISPATCH_AS_SLIPPERY_EXIT,
                                                  BitSet32(0));
 
                 // Make a slippery entrance into the new window.
@@ -2237,17 +2358,18 @@
                     isSplit = !isFromMouse;
                 }
 
-                int32_t targetFlags = InputTarget::FLAG_DISPATCH_AS_SLIPPERY_ENTER;
+                ftl::Flags<InputTarget::Flags> targetFlags =
+                        InputTarget::Flags::DISPATCH_AS_SLIPPERY_ENTER;
                 if (canReceiveForegroundTouches(*newTouchedWindowHandle->getInfo())) {
-                    targetFlags |= InputTarget::FLAG_FOREGROUND;
+                    targetFlags |= InputTarget::Flags::FOREGROUND;
                 }
                 if (isSplit) {
-                    targetFlags |= InputTarget::FLAG_SPLIT;
+                    targetFlags |= InputTarget::Flags::SPLIT;
                 }
                 if (isWindowObscuredAtPointLocked(newTouchedWindowHandle, x, y)) {
-                    targetFlags |= InputTarget::FLAG_WINDOW_IS_OBSCURED;
+                    targetFlags |= InputTarget::Flags::WINDOW_IS_OBSCURED;
                 } else if (isWindowObscuredLocked(newTouchedWindowHandle)) {
-                    targetFlags |= InputTarget::FLAG_WINDOW_IS_PARTIALLY_OBSCURED;
+                    targetFlags |= InputTarget::Flags::WINDOW_IS_PARTIALLY_OBSCURED;
                 }
 
                 BitSet32 pointerIds;
@@ -2256,38 +2378,28 @@
                                                  entry.eventTime);
             }
         }
+
+        // Update the pointerIds for non-splittable when it received pointer down.
+        if (!isSplit && maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN) {
+            // If no split, we suppose all touched windows should receive pointer down.
+            const int32_t pointerIndex = getMotionEventActionPointerIndex(action);
+            for (size_t i = 0; i < tempTouchState.windows.size(); i++) {
+                TouchedWindow& touchedWindow = tempTouchState.windows[i];
+                // Ignore drag window for it should just track one pointer.
+                if (mDragState && mDragState->dragWindow == touchedWindow.windowHandle) {
+                    continue;
+                }
+                touchedWindow.pointerIds.markBit(entry.pointerProperties[pointerIndex].id);
+            }
+        }
     }
 
     // Update dispatching for hover enter and exit.
-    if (newHoverWindowHandle != mLastHoverWindowHandle) {
-        // Let the previous window know that the hover sequence is over, unless we already did
-        // it when dispatching it as is to newTouchedWindowHandle.
-        if (mLastHoverWindowHandle != nullptr &&
-            (maskedAction != AMOTION_EVENT_ACTION_HOVER_EXIT ||
-             mLastHoverWindowHandle != newTouchedWindowHandle)) {
-            if (DEBUG_HOVER) {
-                ALOGD("Sending hover exit event to window %s.",
-                      mLastHoverWindowHandle->getName().c_str());
-            }
-            tempTouchState.addOrUpdateWindow(mLastHoverWindowHandle,
-                                             InputTarget::FLAG_DISPATCH_AS_HOVER_EXIT, BitSet32(0));
-        }
-
-        // Let the new window know that the hover sequence is starting, unless we already did it
-        // when dispatching it as is to newTouchedWindowHandle.
-        if (newHoverWindowHandle != nullptr &&
-            (maskedAction != AMOTION_EVENT_ACTION_HOVER_ENTER ||
-             newHoverWindowHandle != newTouchedWindowHandle)) {
-            if (DEBUG_HOVER) {
-                ALOGD("Sending hover enter event to window %s.",
-                      newHoverWindowHandle->getName().c_str());
-            }
-            tempTouchState.addOrUpdateWindow(newHoverWindowHandle,
-                                             InputTarget::FLAG_DISPATCH_AS_HOVER_ENTER,
-                                             BitSet32(0));
-        }
+    {
+        std::vector<TouchedWindow> hoveringWindows =
+                getHoveringWindowsLocked(oldState, tempTouchState, entry);
+        touchedWindows.insert(touchedWindows.end(), hoveringWindows.begin(), hoveringWindows.end());
     }
-
     // Ensure that we have at least one foreground window or at least one window that cannot be a
     // foreground target. If we only have windows that are not receiving foreground touches (e.g. we
     // only have windows getting ACTION_OUTSIDE), then drop the event, because there is no window
@@ -2296,11 +2408,11 @@
                      [](const TouchedWindow& touchedWindow) {
                          return !canReceiveForegroundTouches(
                                         *touchedWindow.windowHandle->getInfo()) ||
-                                 (touchedWindow.targetFlags & InputTarget::FLAG_FOREGROUND) != 0;
+                                 touchedWindow.targetFlags.test(InputTarget::Flags::FOREGROUND);
                      })) {
         ALOGI("Dropping event because there is no touched window on display %d to receive it: %s",
               displayId, entry.getDescription().c_str());
-        injectionResult = InputEventInjectionResult::FAILED;
+        outInjectionResult = InputEventInjectionResult::FAILED;
         goto Failed;
     }
 
@@ -2308,7 +2420,7 @@
     if (entry.injectionState != nullptr) {
         std::string errs;
         for (const TouchedWindow& touchedWindow : tempTouchState.windows) {
-            if (touchedWindow.targetFlags & InputTarget::FLAG_DISPATCH_AS_OUTSIDE) {
+            if (touchedWindow.targetFlags.test(InputTarget::Flags::DISPATCH_AS_OUTSIDE)) {
                 // Allow ACTION_OUTSIDE events generated by targeted injection to be
                 // dispatched to any uid, since the coords will be zeroed out later.
                 continue;
@@ -2320,7 +2432,7 @@
             ALOGW("Dropping targeted injection: At least one touched window is not owned by uid "
                   "%d:%s",
                   *entry.injectionState->targetUid, errs.c_str());
-            injectionResult = InputEventInjectionResult::TARGET_MISMATCH;
+            outInjectionResult = InputEventInjectionResult::TARGET_MISMATCH;
             goto Failed;
         }
     }
@@ -2333,11 +2445,11 @@
         if (foregroundWindowHandle) {
             const int32_t foregroundWindowUid = foregroundWindowHandle->getInfo()->ownerUid;
             for (const TouchedWindow& touchedWindow : tempTouchState.windows) {
-                if (touchedWindow.targetFlags & InputTarget::FLAG_DISPATCH_AS_OUTSIDE) {
+                if (touchedWindow.targetFlags.test(InputTarget::Flags::DISPATCH_AS_OUTSIDE)) {
                     sp<WindowInfoHandle> windowInfoHandle = touchedWindow.windowHandle;
                     if (windowInfoHandle->getInfo()->ownerUid != foregroundWindowUid) {
                         tempTouchState.addOrUpdateWindow(windowInfoHandle,
-                                                         InputTarget::FLAG_ZERO_COORDS,
+                                                         InputTarget::Flags::ZERO_COORDS,
                                                          BitSet32(0));
                     }
                 }
@@ -2364,111 +2476,95 @@
                 if (info->displayId == displayId &&
                     windowHandle->getInfo()->inputConfig.test(
                             WindowInfo::InputConfig::IS_WALLPAPER)) {
-                    tempTouchState
-                            .addOrUpdateWindow(windowHandle,
-                                               InputTarget::FLAG_WINDOW_IS_OBSCURED |
-                                                       InputTarget::
-                                                               FLAG_WINDOW_IS_PARTIALLY_OBSCURED |
-                                                       InputTarget::FLAG_DISPATCH_AS_IS,
-                                               BitSet32(0), entry.eventTime);
+                    BitSet32 pointerIds;
+                    pointerIds.markBit(entry.pointerProperties[0].id);
+                    tempTouchState.addOrUpdateWindow(windowHandle,
+                                                     InputTarget::Flags::WINDOW_IS_OBSCURED |
+                                                             InputTarget::Flags::
+                                                                     WINDOW_IS_PARTIALLY_OBSCURED |
+                                                             InputTarget::Flags::DISPATCH_AS_IS,
+                                                     pointerIds, entry.eventTime);
                 }
             }
         }
     }
 
-    // Success!  Output targets.
-    injectionResult = InputEventInjectionResult::SUCCEEDED;
-
-    for (const TouchedWindow& touchedWindow : tempTouchState.windows) {
-        addWindowTargetLocked(touchedWindow.windowHandle, touchedWindow.targetFlags,
-                              touchedWindow.pointerIds, touchedWindow.firstDownTimeInTarget,
-                              inputTargets);
+    // Success!  Output targets for everything except hovers.
+    if (!isHoverAction) {
+        touchedWindows.insert(touchedWindows.end(), tempTouchState.windows.begin(),
+                              tempTouchState.windows.end());
     }
 
+    outInjectionResult = InputEventInjectionResult::SUCCEEDED;
     // Drop the outside or hover touch windows since we will not care about them
     // in the next iteration.
     tempTouchState.filterNonAsIsTouchWindows();
 
 Failed:
     // Update final pieces of touch state if the injector had permission.
-    if (!wrongDevice) {
-        if (switchedDevice) {
-            if (DEBUG_FOCUS) {
-                ALOGD("Conflicting pointer actions: Switched to a different device.");
-            }
-            *outConflictingPointerActions = true;
+    if (switchedDevice) {
+        if (DEBUG_FOCUS) {
+            ALOGD("Conflicting pointer actions: Switched to a different device.");
         }
-
-        if (isHoverAction) {
-            // Started hovering, therefore no longer down.
-            if (oldState && oldState->down) {
-                if (DEBUG_FOCUS) {
-                    ALOGD("Conflicting pointer actions: Hover received while pointer was "
-                          "down.");
-                }
-                *outConflictingPointerActions = true;
-            }
-            tempTouchState.reset();
-            if (maskedAction == AMOTION_EVENT_ACTION_HOVER_ENTER ||
-                maskedAction == AMOTION_EVENT_ACTION_HOVER_MOVE) {
-                tempTouchState.deviceId = entry.deviceId;
-                tempTouchState.source = entry.source;
-                tempTouchState.displayId = displayId;
-            }
-        } else if (maskedAction == AMOTION_EVENT_ACTION_UP ||
-                   maskedAction == AMOTION_EVENT_ACTION_CANCEL) {
-            // All pointers up or canceled.
-            tempTouchState.reset();
-        } else if (maskedAction == AMOTION_EVENT_ACTION_DOWN) {
-            // First pointer went down.
-            if (oldState && oldState->down) {
-                if (DEBUG_FOCUS) {
-                    ALOGD("Conflicting pointer actions: Down received while already down.");
-                }
-                *outConflictingPointerActions = true;
-            }
-        } else if (maskedAction == AMOTION_EVENT_ACTION_POINTER_UP) {
-            // One pointer went up.
-            int32_t pointerIndex = getMotionEventActionPointerIndex(action);
-            uint32_t pointerId = entry.pointerProperties[pointerIndex].id;
-
-            for (size_t i = 0; i < tempTouchState.windows.size();) {
-                TouchedWindow& touchedWindow = tempTouchState.windows[i];
-                touchedWindow.pointerIds.clearBit(pointerId);
-                if (touchedWindow.pointerIds.isEmpty()) {
-                    tempTouchState.windows.erase(tempTouchState.windows.begin() + i);
-                    continue;
-                }
-                i += 1;
-            }
-        } else if (!isSplit && maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN) {
-            // If no split, we suppose all touched windows should receive pointer down.
-            const int32_t pointerIndex = getMotionEventActionPointerIndex(action);
-            for (size_t i = 0; i < tempTouchState.windows.size(); i++) {
-                TouchedWindow& touchedWindow = tempTouchState.windows[i];
-                // Ignore drag window for it should just track one pointer.
-                if (mDragState && mDragState->dragWindow == touchedWindow.windowHandle) {
-                    continue;
-                }
-                touchedWindow.pointerIds.markBit(entry.pointerProperties[pointerIndex].id);
-            }
-        }
-
-        // Save changes unless the action was scroll in which case the temporary touch
-        // state was only valid for this one action.
-        if (maskedAction != AMOTION_EVENT_ACTION_SCROLL) {
-            if (tempTouchState.displayId >= 0) {
-                mTouchStatesByDisplay[displayId] = tempTouchState;
-            } else {
-                mTouchStatesByDisplay.erase(displayId);
-            }
-        }
-
-        // Update hover state.
-        mLastHoverWindowHandle = newHoverWindowHandle;
+        *outConflictingPointerActions = true;
     }
 
-    return injectionResult;
+    if (isHoverAction) {
+        // Started hovering, therefore no longer down.
+        if (oldState && oldState->isDown()) {
+            ALOGD_IF(DEBUG_FOCUS,
+                     "Conflicting pointer actions: Hover received while pointer was down.");
+            *outConflictingPointerActions = true;
+        }
+        if (maskedAction == AMOTION_EVENT_ACTION_HOVER_ENTER ||
+            maskedAction == AMOTION_EVENT_ACTION_HOVER_MOVE) {
+            tempTouchState.deviceId = entry.deviceId;
+            tempTouchState.source = entry.source;
+        }
+    } else if (maskedAction == AMOTION_EVENT_ACTION_UP) {
+        // Pointer went up.
+        tempTouchState.removeTouchedPointer(entry.pointerProperties[0].id);
+        tempTouchState.clearWindowsWithoutPointers();
+    } else if (maskedAction == AMOTION_EVENT_ACTION_CANCEL) {
+        // All pointers up or canceled.
+        tempTouchState.reset();
+    } else if (maskedAction == AMOTION_EVENT_ACTION_DOWN) {
+        // First pointer went down.
+        if (oldState && oldState->isDown()) {
+            ALOGD("Conflicting pointer actions: Down received while already down.");
+            *outConflictingPointerActions = true;
+        }
+    } else if (maskedAction == AMOTION_EVENT_ACTION_POINTER_UP) {
+        // One pointer went up.
+        int32_t pointerIndex = getMotionEventActionPointerIndex(action);
+        uint32_t pointerId = entry.pointerProperties[pointerIndex].id;
+
+        for (size_t i = 0; i < tempTouchState.windows.size();) {
+            TouchedWindow& touchedWindow = tempTouchState.windows[i];
+            touchedWindow.pointerIds.clearBit(pointerId);
+            if (touchedWindow.pointerIds.isEmpty()) {
+                tempTouchState.windows.erase(tempTouchState.windows.begin() + i);
+                continue;
+            }
+            i += 1;
+        }
+    }
+
+    // Save changes unless the action was scroll in which case the temporary touch
+    // state was only valid for this one action.
+    if (maskedAction != AMOTION_EVENT_ACTION_SCROLL) {
+        if (displayId >= 0) {
+            mTouchStatesByDisplay[displayId] = tempTouchState;
+        } else {
+            mTouchStatesByDisplay.erase(displayId);
+        }
+    }
+
+    if (tempTouchState.windows.empty()) {
+        mTouchStatesByDisplay.erase(displayId);
+    }
+
+    return touchedWindows;
 }
 
 void InputDispatcher::finishDragAndDrop(int32_t displayId, float x, float y) {
@@ -2573,9 +2669,10 @@
 }
 
 void InputDispatcher::addWindowTargetLocked(const sp<WindowInfoHandle>& windowHandle,
-                                            int32_t targetFlags, BitSet32 pointerIds,
+                                            ftl::Flags<InputTarget::Flags> targetFlags,
+                                            BitSet32 pointerIds,
                                             std::optional<nsecs_t> firstDownTimeInTarget,
-                                            std::vector<InputTarget>& inputTargets) {
+                                            std::vector<InputTarget>& inputTargets) const {
     std::vector<InputTarget>::iterator it =
             std::find_if(inputTargets.begin(), inputTargets.end(),
                          [&windowHandle](const InputTarget& inputTarget) {
@@ -2621,7 +2718,7 @@
     for (const Monitor& monitor : selectResponsiveMonitorsLocked(monitorsIt->second)) {
         InputTarget target;
         target.inputChannel = monitor.inputChannel;
-        target.flags = InputTarget::FLAG_DISPATCH_AS_IS;
+        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()) {
@@ -2653,7 +2750,7 @@
         // We do want to potentially flag touchable windows even if they have 0
         // opacity, since they can consume touches and alter the effects of the
         // user interaction (eg. apps that rely on
-        // FLAG_WINDOW_IS_PARTIALLY_OBSCURED should still be told about those
+        // Flags::WINDOW_IS_PARTIALLY_OBSCURED should still be told about those
         // windows), hence we also check for FLAG_NOT_TOUCHABLE.
         return false;
     } else if (info->ownerUid == otherInfo->ownerUid) {
@@ -2882,9 +2979,9 @@
         ATRACE_NAME(message.c_str());
     }
     if (DEBUG_DISPATCH_CYCLE) {
-        ALOGD("channel '%s' ~ prepareDispatchCycle - flags=0x%08x, "
+        ALOGD("channel '%s' ~ prepareDispatchCycle - flags=%s, "
               "globalScaleFactor=%f, pointerIds=0x%x %s",
-              connection->getInputChannelName().c_str(), inputTarget.flags,
+              connection->getInputChannelName().c_str(), inputTarget.flags.string().c_str(),
               inputTarget.globalScaleFactor, inputTarget.pointerIds.value,
               inputTarget.getPointerInfoString().c_str());
     }
@@ -2901,9 +2998,9 @@
     }
 
     // Split a motion event if needed.
-    if (inputTarget.flags & InputTarget::FLAG_SPLIT) {
+    if (inputTarget.flags.test(InputTarget::Flags::SPLIT)) {
         LOG_ALWAYS_FATAL_IF(eventEntry->type != EventEntry::Type::MOTION,
-                            "Entry type %s should not have FLAG_SPLIT",
+                            "Entry type %s should not have Flags::SPLIT",
                             ftl::enum_string(eventEntry->type).c_str());
 
         const MotionEntry& originalMotionEntry = static_cast<const MotionEntry&>(*eventEntry);
@@ -2947,22 +3044,24 @@
                              connection->getInputChannelName().c_str(), eventEntry->id);
         ATRACE_NAME(message.c_str());
     }
+    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::FLAG_DISPATCH_AS_HOVER_EXIT);
+                               InputTarget::Flags::DISPATCH_AS_HOVER_EXIT);
     enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,
-                               InputTarget::FLAG_DISPATCH_AS_OUTSIDE);
+                               InputTarget::Flags::DISPATCH_AS_OUTSIDE);
     enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,
-                               InputTarget::FLAG_DISPATCH_AS_HOVER_ENTER);
+                               InputTarget::Flags::DISPATCH_AS_HOVER_ENTER);
     enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,
-                               InputTarget::FLAG_DISPATCH_AS_IS);
+                               InputTarget::Flags::DISPATCH_AS_IS);
     enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,
-                               InputTarget::FLAG_DISPATCH_AS_SLIPPERY_EXIT);
+                               InputTarget::Flags::DISPATCH_AS_SLIPPERY_EXIT);
     enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,
-                               InputTarget::FLAG_DISPATCH_AS_SLIPPERY_ENTER);
+                               InputTarget::Flags::DISPATCH_AS_SLIPPERY_ENTER);
 
     // If the outbound queue was previously empty, start the dispatch cycle going.
     if (wasEmpty && !connection->outboundQueue.empty()) {
@@ -2973,18 +3072,20 @@
 void InputDispatcher::enqueueDispatchEntryLocked(const sp<Connection>& connection,
                                                  std::shared_ptr<EventEntry> eventEntry,
                                                  const InputTarget& inputTarget,
-                                                 int32_t dispatchMode) {
+                                                 ftl::Flags<InputTarget::Flags> dispatchMode) {
     if (ATRACE_ENABLED()) {
         std::string message = StringPrintf("enqueueDispatchEntry(inputChannel=%s, dispatchMode=%s)",
                                            connection->getInputChannelName().c_str(),
-                                           dispatchModeToString(dispatchMode).c_str());
+                                           dispatchMode.string().c_str());
         ATRACE_NAME(message.c_str());
     }
-    int32_t inputTargetFlags = inputTarget.flags;
-    if (!(inputTargetFlags & dispatchMode)) {
+    ftl::Flags<InputTarget::Flags> inputTargetFlags = inputTarget.flags;
+    if (!inputTargetFlags.any(dispatchMode)) {
         return;
     }
-    inputTargetFlags = (inputTargetFlags & ~InputTarget::FLAG_DISPATCH_MASK) | dispatchMode;
+
+    inputTargetFlags.clear(InputTarget::DISPATCH_MASK);
+    inputTargetFlags |= dispatchMode;
 
     // This is a new event.
     // Enqueue a new dispatch entry onto the outbound queue for this connection.
@@ -3021,15 +3122,15 @@
             constexpr int32_t DEFAULT_RESOLVED_EVENT_ID =
                     static_cast<int32_t>(IdGenerator::Source::OTHER);
             dispatchEntry->resolvedEventId = DEFAULT_RESOLVED_EVENT_ID;
-            if (dispatchMode & InputTarget::FLAG_DISPATCH_AS_OUTSIDE) {
+            if (dispatchMode.test(InputTarget::Flags::DISPATCH_AS_OUTSIDE)) {
                 dispatchEntry->resolvedAction = AMOTION_EVENT_ACTION_OUTSIDE;
-            } else if (dispatchMode & InputTarget::FLAG_DISPATCH_AS_HOVER_EXIT) {
+            } else if (dispatchMode.test(InputTarget::Flags::DISPATCH_AS_HOVER_EXIT)) {
                 dispatchEntry->resolvedAction = AMOTION_EVENT_ACTION_HOVER_EXIT;
-            } else if (dispatchMode & InputTarget::FLAG_DISPATCH_AS_HOVER_ENTER) {
+            } else if (dispatchMode.test(InputTarget::Flags::DISPATCH_AS_HOVER_ENTER)) {
                 dispatchEntry->resolvedAction = AMOTION_EVENT_ACTION_HOVER_ENTER;
-            } else if (dispatchMode & InputTarget::FLAG_DISPATCH_AS_SLIPPERY_EXIT) {
+            } else if (dispatchMode.test(InputTarget::Flags::DISPATCH_AS_SLIPPERY_EXIT)) {
                 dispatchEntry->resolvedAction = AMOTION_EVENT_ACTION_CANCEL;
-            } else if (dispatchMode & InputTarget::FLAG_DISPATCH_AS_SLIPPERY_ENTER) {
+            } else if (dispatchMode.test(InputTarget::Flags::DISPATCH_AS_SLIPPERY_ENTER)) {
                 dispatchEntry->resolvedAction = AMOTION_EVENT_ACTION_DOWN;
             } else {
                 dispatchEntry->resolvedAction = motionEntry.action;
@@ -3049,10 +3150,10 @@
             }
 
             dispatchEntry->resolvedFlags = motionEntry.flags;
-            if (dispatchEntry->targetFlags & InputTarget::FLAG_WINDOW_IS_OBSCURED) {
+            if (dispatchEntry->targetFlags.test(InputTarget::Flags::WINDOW_IS_OBSCURED)) {
                 dispatchEntry->resolvedFlags |= AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED;
             }
-            if (dispatchEntry->targetFlags & InputTarget::FLAG_WINDOW_IS_PARTIALLY_OBSCURED) {
+            if (dispatchEntry->targetFlags.test(InputTarget::Flags::WINDOW_IS_PARTIALLY_OBSCURED)) {
                 dispatchEntry->resolvedFlags |= AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED;
             }
 
@@ -3152,8 +3253,7 @@
     std::unordered_set<sp<IBinder>, StrongPointerHash<IBinder>> newConnectionTokens;
     std::vector<sp<Connection>> newConnections;
     for (const InputTarget& target : targets) {
-        if ((target.flags & InputTarget::FLAG_DISPATCH_AS_OUTSIDE) ==
-            InputTarget::FLAG_DISPATCH_AS_OUTSIDE) {
+        if (target.flags.test(InputTarget::Flags::DISPATCH_AS_OUTSIDE)) {
             continue; // Skip windows that receive ACTION_OUTSIDE
         }
 
@@ -3202,6 +3302,55 @@
     postCommandLocked(std::move(command));
 }
 
+status_t InputDispatcher::publishMotionEvent(Connection& connection,
+                                             DispatchEntry& dispatchEntry) const {
+    const EventEntry& eventEntry = *(dispatchEntry.eventEntry);
+    const MotionEntry& motionEntry = static_cast<const MotionEntry&>(eventEntry);
+
+    PointerCoords scaledCoords[MAX_POINTERS];
+    const PointerCoords* usingCoords = motionEntry.pointerCoords;
+
+    // Set the X and Y offset and X and Y scale depending on the input source.
+    if ((motionEntry.source & AINPUT_SOURCE_CLASS_POINTER) &&
+        !(dispatchEntry.targetFlags.test(InputTarget::Flags::ZERO_COORDS))) {
+        float globalScaleFactor = dispatchEntry.globalScaleFactor;
+        if (globalScaleFactor != 1.0f) {
+            for (uint32_t i = 0; i < motionEntry.pointerCount; i++) {
+                scaledCoords[i] = motionEntry.pointerCoords[i];
+                // Don't apply window scale here since we don't want scale to affect raw
+                // coordinates. The scale will be sent back to the client and applied
+                // later when requesting relative coordinates.
+                scaledCoords[i].scale(globalScaleFactor, 1 /* windowXScale */,
+                                      1 /* windowYScale */);
+            }
+            usingCoords = scaledCoords;
+        }
+    } else if (dispatchEntry.targetFlags.test(InputTarget::Flags::ZERO_COORDS)) {
+        // We don't want the dispatch target to know the coordinates
+        for (uint32_t i = 0; i < motionEntry.pointerCount; i++) {
+            scaledCoords[i].clear();
+        }
+        usingCoords = scaledCoords;
+    }
+
+    std::array<uint8_t, 32> hmac = getSignature(motionEntry, dispatchEntry);
+
+    // 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.pointerCount, motionEntry.pointerProperties,
+                                usingCoords);
+}
+
 void InputDispatcher::startDispatchCycleLocked(nsecs_t currentTime,
                                                const sp<Connection>& connection) {
     if (ATRACE_ENABLED()) {
@@ -3241,58 +3390,7 @@
             }
 
             case EventEntry::Type::MOTION: {
-                const MotionEntry& motionEntry = static_cast<const MotionEntry&>(eventEntry);
-
-                PointerCoords scaledCoords[MAX_POINTERS];
-                const PointerCoords* usingCoords = motionEntry.pointerCoords;
-
-                // Set the X and Y offset and X and Y scale depending on the input source.
-                if ((motionEntry.source & AINPUT_SOURCE_CLASS_POINTER) &&
-                    !(dispatchEntry->targetFlags & InputTarget::FLAG_ZERO_COORDS)) {
-                    float globalScaleFactor = dispatchEntry->globalScaleFactor;
-                    if (globalScaleFactor != 1.0f) {
-                        for (uint32_t i = 0; i < motionEntry.pointerCount; i++) {
-                            scaledCoords[i] = motionEntry.pointerCoords[i];
-                            // Don't apply window scale here since we don't want scale to affect raw
-                            // coordinates. The scale will be sent back to the client and applied
-                            // later when requesting relative coordinates.
-                            scaledCoords[i].scale(globalScaleFactor, 1 /* windowXScale */,
-                                                  1 /* windowYScale */);
-                        }
-                        usingCoords = scaledCoords;
-                    }
-                } else {
-                    // We don't want the dispatch target to know.
-                    if (dispatchEntry->targetFlags & InputTarget::FLAG_ZERO_COORDS) {
-                        for (uint32_t i = 0; i < motionEntry.pointerCount; i++) {
-                            scaledCoords[i].clear();
-                        }
-                        usingCoords = scaledCoords;
-                    }
-                }
-
-                std::array<uint8_t, 32> hmac = getSignature(motionEntry, *dispatchEntry);
-
-                // Publish the motion event.
-                status = 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.pointerCount,
-                                                     motionEntry.pointerProperties, usingCoords);
+                status = publishMotionEvent(*connection, *dispatchEntry);
                 break;
             }
 
@@ -3629,7 +3727,7 @@
         target.globalScaleFactor = windowInfo->globalScaleFactor;
     }
     target.inputChannel = connection->inputChannel;
-    target.flags = InputTarget::FLAG_DISPATCH_AS_IS;
+    target.flags = InputTarget::Flags::DISPATCH_AS_IS;
 
     const bool wasEmpty = connection->outboundQueue.empty();
 
@@ -3664,7 +3762,7 @@
         }
 
         enqueueDispatchEntryLocked(connection, std::move(cancelationEventEntry), target,
-                                   InputTarget::FLAG_DISPATCH_AS_IS);
+                                   InputTarget::Flags::DISPATCH_AS_IS);
     }
 
     // If the outbound queue was previously empty, start the dispatch cycle going.
@@ -3700,7 +3798,7 @@
         target.globalScaleFactor = windowInfo->globalScaleFactor;
     }
     target.inputChannel = connection->inputChannel;
-    target.flags = InputTarget::FLAG_DISPATCH_AS_IS;
+    target.flags = InputTarget::Flags::DISPATCH_AS_IS;
 
     const bool wasEmpty = connection->outboundQueue.empty();
     for (std::unique_ptr<EventEntry>& downEventEntry : downEvents) {
@@ -3726,7 +3824,7 @@
         }
 
         enqueueDispatchEntryLocked(connection, std::move(downEventEntry), target,
-                                   InputTarget::FLAG_DISPATCH_AS_IS);
+                                   InputTarget::Flags::DISPATCH_AS_IS);
     }
 
     // If the outbound queue was previously empty, start the dispatch cycle going.
@@ -3981,13 +4079,14 @@
     if (DEBUG_INBOUND_EVENT_DETAILS) {
         ALOGD("notifyMotion - id=%" PRIx32 " eventTime=%" PRId64 ", deviceId=%d, source=0x%x, "
               "displayId=%" PRId32 ", policyFlags=0x%x, "
-              "action=0x%x, actionButton=0x%x, flags=0x%x, metaState=0x%x, buttonState=0x%x, "
+              "action=%s, actionButton=0x%x, flags=0x%x, metaState=0x%x, buttonState=0x%x, "
               "edgeFlags=0x%x, xPrecision=%f, yPrecision=%f, xCursorPosition=%f, "
               "yCursorPosition=%f, downTime=%" PRId64,
               args->id, args->eventTime, args->deviceId, args->source, args->displayId,
-              args->policyFlags, args->action, args->actionButton, args->flags, args->metaState,
-              args->buttonState, args->edgeFlags, args->xPrecision, args->yPrecision,
-              args->xCursorPosition, args->yCursorPosition, args->downTime);
+              args->policyFlags, MotionEvent::actionToString(args->action).c_str(),
+              args->actionButton, args->flags, args->metaState, args->buttonState, args->edgeFlags,
+              args->xPrecision, args->yPrecision, args->xCursorPosition, args->yCursorPosition,
+              args->downTime);
         for (uint32_t i = 0; i < args->pointerCount; i++) {
             ALOGD("  Pointer %d: id=%d, toolType=%d, "
                   "x=%f, y=%f, pressure=%f, size=%f, "
@@ -4005,10 +4104,9 @@
                   args->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_ORIENTATION));
         }
     }
-    if (!validateMotionEvent(args->action, args->actionButton, args->pointerCount,
-                             args->pointerProperties)) {
-        return;
-    }
+    LOG_ALWAYS_FATAL_IF(!validateMotionEvent(args->action, args->actionButton, args->pointerCount,
+                                             args->pointerProperties),
+                        "Invalid event: %s", args->dump().c_str());
 
     uint32_t policyFlags = args->policyFlags;
     policyFlags |= POLICY_FLAG_TRUSTED;
@@ -4757,11 +4855,6 @@
     updateWindowHandlesForDisplayLocked(windowInfoHandles, displayId);
 
     const std::vector<sp<WindowInfoHandle>>& windowHandles = getWindowHandlesLocked(displayId);
-    if (mLastHoverWindowHandle &&
-        std::find(windowHandles.begin(), windowHandles.end(), mLastHoverWindowHandle) ==
-                windowHandles.end()) {
-        mLastHoverWindowHandle = nullptr;
-    }
 
     std::optional<FocusResolver::FocusChanges> changes =
             mFocusResolver.setInputWindows(displayId, windowHandles);
@@ -4783,12 +4876,12 @@
                 std::shared_ptr<InputChannel> touchedInputChannel =
                         getInputChannelLocked(touchedWindow.windowHandle->getToken());
                 if (touchedInputChannel != nullptr) {
-                    CancelationOptions options(CancelationOptions::CANCEL_POINTER_EVENTS,
+                    CancelationOptions options(CancelationOptions::Mode::CANCEL_POINTER_EVENTS,
                                                "touched window was removed");
                     synthesizeCancelationEventsForInputChannelLocked(touchedInputChannel, options);
                     // Since we are about to drop the touch, cancel the events for the wallpaper as
                     // well.
-                    if (touchedWindow.targetFlags & InputTarget::FLAG_FOREGROUND &&
+                    if (touchedWindow.targetFlags.test(InputTarget::Flags::FOREGROUND) &&
                         touchedWindow.windowHandle->getInfo()->inputConfig.test(
                                 gui::WindowInfo::InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER)) {
                         sp<WindowInfoHandle> wallpaper = state.getWallpaperWindow();
@@ -4829,7 +4922,7 @@
             std::shared_ptr<InputChannel> inputChannel =
                     getInputChannelLocked(newWindowHandle->getToken());
             if (inputChannel != nullptr) {
-                CancelationOptions options(CancelationOptions::CANCEL_POINTER_EVENTS,
+                CancelationOptions options(CancelationOptions::Mode::CANCEL_POINTER_EVENTS,
                                            "touched window's orientation changed");
                 synthesizeCancelationEventsForInputChannelLocked(inputChannel, options);
             }
@@ -4910,7 +5003,7 @@
                         getInputChannelLocked(oldFocusedWindowToken);
                 if (inputChannel != nullptr) {
                     CancelationOptions
-                            options(CancelationOptions::CANCEL_NON_POINTER_EVENTS,
+                            options(CancelationOptions::Mode::CANCEL_NON_POINTER_EVENTS,
                                     "The display which contains this window no longer has focus.");
                     options.displayId = ADISPLAY_ID_NONE;
                     synthesizeCancelationEventsForInputChannelLocked(inputChannel, options);
@@ -4930,10 +5023,6 @@
                 }
             }
         }
-
-        if (DEBUG_FOCUS) {
-            logDispatchStateLocked();
-        }
     } // release lock
 
     // Wake up poll loop since it may need to make new input dispatching choices.
@@ -4964,10 +5053,6 @@
         } else {
             changed = false;
         }
-
-        if (DEBUG_FOCUS) {
-            logDispatchStateLocked();
-        }
     } // release lock
 
     if (changed) {
@@ -5009,8 +5094,6 @@
                          ? "not set"
                          : std::to_string(mTouchModePerDisplay[displayId]).c_str());
 
-        // TODO(b/198499018): Ensure that WM can guarantee that touch mode is properly set when
-        // display is created.
         auto touchModeIt = mTouchModePerDisplay.find(displayId);
         if (touchModeIt != mTouchModePerDisplay.end() && touchModeIt->second == inTouchMode) {
             return false;
@@ -5064,16 +5147,16 @@
     mMaximumObscuringOpacityForTouch = opacity;
 }
 
-std::pair<TouchState*, TouchedWindow*> InputDispatcher::findTouchStateAndWindowLocked(
-        const sp<IBinder>& token) {
+std::tuple<TouchState*, TouchedWindow*, int32_t /*displayId*/>
+InputDispatcher::findTouchStateWindowAndDisplayLocked(const sp<IBinder>& token) {
     for (auto& [displayId, state] : mTouchStatesByDisplay) {
         for (TouchedWindow& w : state.windows) {
             if (w.windowHandle->getToken() == token) {
-                return std::make_pair(&state, &w);
+                return std::make_tuple(&state, &w, displayId);
             }
         }
     }
-    return std::make_pair(nullptr, nullptr);
+    return std::make_tuple(nullptr, nullptr, ADISPLAY_ID_DEFAULT);
 }
 
 bool InputDispatcher::transferTouchFocus(const sp<IBinder>& fromToken, const sp<IBinder>& toToken,
@@ -5089,13 +5172,12 @@
         std::scoped_lock _l(mLock);
 
         // Find the target touch state and touched window by fromToken.
-        auto [state, touchedWindow] = findTouchStateAndWindowLocked(fromToken);
+        auto [state, touchedWindow, displayId] = findTouchStateWindowAndDisplayLocked(fromToken);
         if (state == nullptr || touchedWindow == nullptr) {
             ALOGD("Focus transfer failed because from window is not being touched.");
             return false;
         }
 
-        const int32_t displayId = state->displayId;
         sp<WindowInfoHandle> toWindowHandle = getWindowHandleLocked(toToken, displayId);
         if (toWindowHandle == nullptr) {
             ALOGW("Cannot transfer focus because to window not found.");
@@ -5109,16 +5191,16 @@
         }
 
         // Erase old window.
-        int32_t oldTargetFlags = touchedWindow->targetFlags;
+        ftl::Flags<InputTarget::Flags> oldTargetFlags = touchedWindow->targetFlags;
         BitSet32 pointerIds = touchedWindow->pointerIds;
         state->removeWindowByToken(fromToken);
 
         // Add new window.
         nsecs_t downTimeInTarget = now();
-        int32_t newTargetFlags =
-                oldTargetFlags & (InputTarget::FLAG_SPLIT | InputTarget::FLAG_DISPATCH_AS_IS);
+        ftl::Flags<InputTarget::Flags> newTargetFlags =
+                oldTargetFlags & (InputTarget::Flags::SPLIT | InputTarget::Flags::DISPATCH_AS_IS);
         if (canReceiveForegroundTouches(*toWindowHandle->getInfo())) {
-            newTargetFlags |= InputTarget::FLAG_FOREGROUND;
+            newTargetFlags |= InputTarget::Flags::FOREGROUND;
         }
         state->addOrUpdateWindow(toWindowHandle, newTargetFlags, pointerIds, downTimeInTarget);
 
@@ -5140,15 +5222,11 @@
         if (fromConnection != nullptr && toConnection != nullptr) {
             fromConnection->inputState.mergePointerStateTo(toConnection->inputState);
             CancelationOptions
-                    options(CancelationOptions::CANCEL_POINTER_EVENTS,
+                    options(CancelationOptions::Mode::CANCEL_POINTER_EVENTS,
                             "transferring touch focus from this window to another window");
             synthesizeCancelationEventsForConnectionLocked(fromConnection, options);
             synthesizePointerDownEventsForConnectionLocked(downTimeInTarget, toConnection);
         }
-
-        if (DEBUG_FOCUS) {
-            logDispatchStateLocked();
-        }
     } // release lock
 
     // Wake up poll loop since it may need to make new input dispatching choices.
@@ -5172,7 +5250,7 @@
     sp<WindowInfoHandle> touchedForegroundWindow;
     // If multiple foreground windows are touched, return nullptr
     for (const TouchedWindow& window : state.windows) {
-        if (window.targetFlags & InputTarget::FLAG_FOREGROUND) {
+        if (window.targetFlags.test(InputTarget::Flags::FOREGROUND)) {
             if (touchedForegroundWindow != nullptr) {
                 ALOGI("Two or more foreground windows: %s and %s",
                       touchedForegroundWindow->getName().c_str(),
@@ -5214,7 +5292,7 @@
         ALOGD("Resetting and dropping all events (%s).", reason);
     }
 
-    CancelationOptions options(CancelationOptions::CANCEL_ALL_EVENTS, reason);
+    CancelationOptions options(CancelationOptions::Mode::CANCEL_ALL_EVENTS, reason);
     synthesizeCancelationEventsForAllConnectionsLocked(options);
 
     resetKeyRepeatLocked();
@@ -5224,7 +5302,6 @@
 
     mAnrTracker.clear();
     mTouchStatesByDisplay.clear();
-    mLastHoverWindowHandle.clear();
     mReplacedKeys.clear();
 }
 
@@ -5284,25 +5361,9 @@
 
     if (!mTouchStatesByDisplay.empty()) {
         dump += StringPrintf(INDENT "TouchStatesByDisplay:\n");
-        for (const std::pair<int32_t, TouchState>& pair : mTouchStatesByDisplay) {
-            const TouchState& state = pair.second;
-            dump += StringPrintf(INDENT2 "%d: down=%s, split=%s, deviceId=%d, source=0x%08x\n",
-                                 state.displayId, toString(state.down), toString(state.split),
-                                 state.deviceId, state.source);
-            if (!state.windows.empty()) {
-                dump += INDENT3 "Windows:\n";
-                for (size_t i = 0; i < state.windows.size(); i++) {
-                    const TouchedWindow& touchedWindow = state.windows[i];
-                    dump += StringPrintf(INDENT4 "%zu: name='%s', pointerIds=0x%0x, "
-                                                 "targetFlags=0x%x, firstDownTimeInTarget=%" PRId64
-                                                 "ms\n",
-                                         i, touchedWindow.windowHandle->getName().c_str(),
-                                         touchedWindow.pointerIds.value, touchedWindow.targetFlags,
-                                         ns2ms(touchedWindow.firstDownTimeInTarget.value_or(0)));
-                }
-            } else {
-                dump += INDENT3 "Windows: <none>\n";
-            }
+        for (const auto& [displayId, state] : mTouchStatesByDisplay) {
+            std::string touchStateDump = addLinePrefix(state.dump(), INDENT2);
+            dump += INDENT2 + std::to_string(displayId) + " : " + touchStateDump;
         }
     } else {
         dump += INDENT "TouchStates: <no displays touched>\n";
@@ -5411,9 +5472,7 @@
 
     if (!mReplacedKeys.empty()) {
         dump += INDENT "ReplacedKeys:\n";
-        for (const std::pair<KeyReplacement, int32_t>& pair : mReplacedKeys) {
-            const KeyReplacement& replacement = pair.first;
-            int32_t newKeyCode = pair.second;
+        for (const auto& [replacement, newKeyCode] : mReplacedKeys) {
             dump += StringPrintf(INDENT2 "originalKeyCode=%d, deviceId=%d -> newKeyCode=%d\n",
                                  replacement.keyCode, replacement.deviceId, newKeyCode);
         }
@@ -5647,8 +5706,8 @@
         return BAD_VALUE;
     }
 
-    auto [statePtr, windowPtr] = findTouchStateAndWindowLocked(token);
-    if (statePtr == nullptr || windowPtr == nullptr || !statePtr->down) {
+    auto [statePtr, windowPtr, displayId] = findTouchStateWindowAndDisplayLocked(token);
+    if (statePtr == nullptr || windowPtr == nullptr || windowPtr->pointerIds.isEmpty()) {
         ALOGW("Attempted to pilfer points from a channel without any on-going pointer streams."
               " Ignoring.");
         return BAD_VALUE;
@@ -5657,14 +5716,11 @@
     TouchState& state = *statePtr;
     TouchedWindow& window = *windowPtr;
     // Send cancel events to all the input channels we're stealing from.
-    CancelationOptions options(CancelationOptions::CANCEL_POINTER_EVENTS,
+    CancelationOptions options(CancelationOptions::Mode::CANCEL_POINTER_EVENTS,
                                "input channel stole pointer stream");
     options.deviceId = state.deviceId;
-    options.displayId = state.displayId;
-    if (state.split) {
-        // If split pointers then selectively cancel pointers otherwise cancel all pointers
-        options.pointerIds = window.pointerIds;
-    }
+    options.displayId = displayId;
+    options.pointerIds = window.pointerIds;
     std::string canceledWindows;
     for (const TouchedWindow& w : state.windows) {
         const std::shared_ptr<InputChannel> channel =
@@ -5683,11 +5739,7 @@
     // This only blocks relevant pointers to be sent to other windows
     window.isPilferingPointers = true;
 
-    if (state.split) {
-        state.cancelPointersForWindowsExcept(window.pointerIds, token);
-    } else {
-        state.filterWindowsExcept(token);
-    }
+    state.cancelPointersForWindowsExcept(window.pointerIds, token);
     return OK;
 }
 
@@ -5950,11 +6002,11 @@
     } // acquire lock
 
     if (delay < 0) {
-        entry.interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_SKIP;
+        entry.interceptKeyResult = KeyEntry::InterceptKeyResult::SKIP;
     } else if (delay == 0) {
-        entry.interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_CONTINUE;
+        entry.interceptKeyResult = KeyEntry::InterceptKeyResult::CONTINUE;
     } else {
-        entry.interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_TRY_AGAIN_LATER;
+        entry.interceptKeyResult = KeyEntry::InterceptKeyResult::TRY_AGAIN_LATER;
         entry.interceptKeyWakeupTime = now() + delay;
     }
 }
@@ -6064,7 +6116,7 @@
 
             // Cancel the fallback key.
             if (fallbackKeyCode != AKEYCODE_UNKNOWN) {
-                CancelationOptions options(CancelationOptions::CANCEL_FALLBACK_EVENTS,
+                CancelationOptions options(CancelationOptions::Mode::CANCEL_FALLBACK_EVENTS,
                                            "application handled the original non-fallback key "
                                            "or is no longer a foreground target, "
                                            "canceling previously dispatched fallback key");
@@ -6141,7 +6193,7 @@
                 }
             }
 
-            CancelationOptions options(CancelationOptions::CANCEL_FALLBACK_EVENTS,
+            CancelationOptions options(CancelationOptions::Mode::CANCEL_FALLBACK_EVENTS,
                                        "canceling fallback, policy no longer desires it");
             options.keyCode = fallbackKeyCode;
             synthesizeCancelationEventsForConnectionLocked(connection, options);
@@ -6294,7 +6346,7 @@
     if (changes.oldFocus) {
         std::shared_ptr<InputChannel> focusedInputChannel = getInputChannelLocked(changes.oldFocus);
         if (focusedInputChannel) {
-            CancelationOptions options(CancelationOptions::CANCEL_NON_POINTER_EVENTS,
+            CancelationOptions options(CancelationOptions::Mode::CANCEL_NON_POINTER_EVENTS,
                                        "focus left window");
             synthesizeCancelationEventsForInputChannelLocked(focusedInputChannel, options);
             enqueueFocusEventLocked(changes.oldFocus, false /*hasFocus*/, changes.reason);
@@ -6434,12 +6486,11 @@
     {
         std::scoped_lock _l(mLock);
         ALOGD("Canceling all ongoing pointer gestures on all displays.");
-        CancelationOptions options(CancelationOptions::CANCEL_POINTER_EVENTS,
+        CancelationOptions options(CancelationOptions::Mode::CANCEL_POINTER_EVENTS,
                                    "cancel current touch");
         synthesizeCancelationEventsForAllConnectionsLocked(options);
 
         mTouchStatesByDisplay.clear();
-        mLastHoverWindowHandle.clear();
     }
     // Wake up poll loop since there might be work to do.
     mLooper->wake();
diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h
index 14b046a..3c7ddfa 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.h
+++ b/services/inputflinger/dispatcher/InputDispatcher.h
@@ -239,7 +239,7 @@
 
     sp<android::gui::WindowInfoHandle> findTouchedWindowAtLocked(
             int32_t displayId, int32_t x, int32_t y, TouchState* touchState, bool isStylus = false,
-            bool addOutsideTargets = false, bool ignoreDragWindow = false) REQUIRES(mLock);
+            bool addOutsideTargets = false, bool ignoreDragWindow = false) const REQUIRES(mLock);
 
     std::vector<sp<android::gui::WindowInfoHandle>> findTouchedSpyWindowsAtLocked(
             int32_t displayId, int32_t x, int32_t y, bool isStylus) const REQUIRES(mLock);
@@ -530,9 +530,6 @@
     // prevent unneeded wakeups.
     AnrTracker mAnrTracker GUARDED_BY(mLock);
 
-    // Contains the last window which received a hover event.
-    sp<android::gui::WindowInfoHandle> mLastHoverWindowHandle GUARDED_BY(mLock);
-
     void cancelEventsForAnrLocked(const sp<Connection>& connection) REQUIRES(mLock);
     // If a focused application changes, we should stop counting down the "no focused window" time,
     // because we will have no way of knowing when the previous application actually added a window.
@@ -541,20 +538,21 @@
     // shade is pulled down while we are counting down the timeout).
     void resetNoFocusedWindowTimeoutLocked() REQUIRES(mLock);
 
+    bool shouldSplitTouch(const TouchState& touchState, const MotionEntry& entry) const;
     int32_t getTargetDisplayId(const EventEntry& entry);
-    android::os::InputEventInjectionResult findFocusedWindowTargetsLocked(
-            nsecs_t currentTime, const EventEntry& entry, std::vector<InputTarget>& inputTargets,
-            nsecs_t* nextWakeupTime) REQUIRES(mLock);
-    android::os::InputEventInjectionResult findTouchedWindowTargetsLocked(
-            nsecs_t currentTime, const MotionEntry& entry, std::vector<InputTarget>& inputTargets,
-            nsecs_t* nextWakeupTime, bool* outConflictingPointerActions) REQUIRES(mLock);
+    sp<android::gui::WindowInfoHandle> findFocusedWindowTargetLocked(
+            nsecs_t currentTime, const EventEntry& entry, nsecs_t* nextWakeupTime,
+            android::os::InputEventInjectionResult& outInjectionResult) REQUIRES(mLock);
+    std::vector<TouchedWindow> findTouchedWindowTargetsLocked(
+            nsecs_t currentTime, const MotionEntry& entry, bool* outConflictingPointerActions,
+            android::os::InputEventInjectionResult& outInjectionResult) REQUIRES(mLock);
     std::vector<Monitor> selectResponsiveMonitorsLocked(
             const std::vector<Monitor>& gestureMonitors) const REQUIRES(mLock);
 
     void addWindowTargetLocked(const sp<android::gui::WindowInfoHandle>& windowHandle,
-                               int32_t targetFlags, BitSet32 pointerIds,
+                               ftl::Flags<InputTarget::Flags> targetFlags, BitSet32 pointerIds,
                                std::optional<nsecs_t> firstDownTimeInTarget,
-                               std::vector<InputTarget>& inputTargets) REQUIRES(mLock);
+                               std::vector<InputTarget>& inputTargets) const REQUIRES(mLock);
     void addGlobalMonitoringTargetsLocked(std::vector<InputTarget>& inputTargets, int32_t displayId)
             REQUIRES(mLock);
     void pokeUserActivityLocked(const EventEntry& eventEntry) REQUIRES(mLock);
@@ -599,8 +597,9 @@
                                       std::shared_ptr<EventEntry>, const InputTarget& inputTarget)
             REQUIRES(mLock);
     void enqueueDispatchEntryLocked(const sp<Connection>& connection, std::shared_ptr<EventEntry>,
-                                    const InputTarget& inputTarget, int32_t dispatchMode)
-            REQUIRES(mLock);
+                                    const InputTarget& inputTarget,
+                                    ftl::Flags<InputTarget::Flags> dispatchMode) REQUIRES(mLock);
+    status_t publishMotionEvent(Connection& connection, DispatchEntry& dispatchEntry) const;
     void startDispatchCycleLocked(nsecs_t currentTime, const sp<Connection>& connection)
             REQUIRES(mLock);
     void finishDispatchCycleLocked(nsecs_t currentTime, const sp<Connection>& connection,
@@ -674,8 +673,8 @@
                                              bool handled) REQUIRES(mLock);
 
     // Find touched state and touched window by token.
-    std::pair<TouchState*, TouchedWindow*> findTouchStateAndWindowLocked(const sp<IBinder>& token)
-            REQUIRES(mLock);
+    std::tuple<TouchState*, TouchedWindow*, int32_t /*displayId*/>
+    findTouchStateWindowAndDisplayLocked(const sp<IBinder>& token) REQUIRES(mLock);
 
     // Statistics gathering.
     LatencyAggregator mLatencyAggregator GUARDED_BY(mLock);
diff --git a/services/inputflinger/dispatcher/InputState.cpp b/services/inputflinger/dispatcher/InputState.cpp
index 047c628..563868d 100644
--- a/services/inputflinger/dispatcher/InputState.cpp
+++ b/services/inputflinger/dispatcher/InputState.cpp
@@ -500,10 +500,10 @@
     }
 
     switch (options.mode) {
-        case CancelationOptions::CANCEL_ALL_EVENTS:
-        case CancelationOptions::CANCEL_NON_POINTER_EVENTS:
+        case CancelationOptions::Mode::CANCEL_ALL_EVENTS:
+        case CancelationOptions::Mode::CANCEL_NON_POINTER_EVENTS:
             return true;
-        case CancelationOptions::CANCEL_FALLBACK_EVENTS:
+        case CancelationOptions::Mode::CANCEL_FALLBACK_EVENTS:
             return memento.flags & AKEY_EVENT_FLAG_FALLBACK;
         default:
             return false;
@@ -521,11 +521,11 @@
     }
 
     switch (options.mode) {
-        case CancelationOptions::CANCEL_ALL_EVENTS:
+        case CancelationOptions::Mode::CANCEL_ALL_EVENTS:
             return true;
-        case CancelationOptions::CANCEL_POINTER_EVENTS:
+        case CancelationOptions::Mode::CANCEL_POINTER_EVENTS:
             return memento.source & AINPUT_SOURCE_CLASS_POINTER;
-        case CancelationOptions::CANCEL_NON_POINTER_EVENTS:
+        case CancelationOptions::Mode::CANCEL_NON_POINTER_EVENTS:
             return !(memento.source & AINPUT_SOURCE_CLASS_POINTER);
         default:
             return false;
diff --git a/services/inputflinger/dispatcher/InputTarget.cpp b/services/inputflinger/dispatcher/InputTarget.cpp
index 2df97d9..2f39480 100644
--- a/services/inputflinger/dispatcher/InputTarget.cpp
+++ b/services/inputflinger/dispatcher/InputTarget.cpp
@@ -24,24 +24,6 @@
 
 namespace android::inputdispatcher {
 
-std::string dispatchModeToString(int32_t dispatchMode) {
-    switch (dispatchMode) {
-        case InputTarget::FLAG_DISPATCH_AS_IS:
-            return "DISPATCH_AS_IS";
-        case InputTarget::FLAG_DISPATCH_AS_OUTSIDE:
-            return "DISPATCH_AS_OUTSIDE";
-        case InputTarget::FLAG_DISPATCH_AS_HOVER_ENTER:
-            return "DISPATCH_AS_HOVER_ENTER";
-        case InputTarget::FLAG_DISPATCH_AS_HOVER_EXIT:
-            return "DISPATCH_AS_HOVER_EXIT";
-        case InputTarget::FLAG_DISPATCH_AS_SLIPPERY_EXIT:
-            return "DISPATCH_AS_SLIPPERY_EXIT";
-        case InputTarget::FLAG_DISPATCH_AS_SLIPPERY_ENTER:
-            return "DISPATCH_AS_SLIPPERY_ENTER";
-    }
-    return StringPrintf("%" PRId32, dispatchMode);
-}
-
 void InputTarget::addPointers(BitSet32 newPointerIds, const ui::Transform& transform) {
     // The pointerIds can be empty, but still a valid InputTarget. This can happen when there is no
     // valid pointer property from the input event.
diff --git a/services/inputflinger/dispatcher/InputTarget.h b/services/inputflinger/dispatcher/InputTarget.h
index b2966f6..61b07fe 100644
--- a/services/inputflinger/dispatcher/InputTarget.h
+++ b/services/inputflinger/dispatcher/InputTarget.h
@@ -16,6 +16,7 @@
 
 #pragma once
 
+#include <ftl/flags.h>
 #include <gui/constants.h>
 #include <input/InputTransport.h>
 #include <ui/Transform.h>
@@ -30,70 +31,70 @@
  * window area.
  */
 struct InputTarget {
-    enum {
+    enum class Flags : uint32_t {
         /* This flag indicates that the event is being delivered to a foreground application. */
-        FLAG_FOREGROUND = 1 << 0,
+        FOREGROUND = 1 << 0,
 
         /* This flag indicates that the MotionEvent falls within the area of the target
          * obscured by another visible window above it.  The motion event should be
          * delivered with flag AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED. */
-        FLAG_WINDOW_IS_OBSCURED = 1 << 1,
+        WINDOW_IS_OBSCURED = 1 << 1,
 
         /* This flag indicates that a motion event is being split across multiple windows. */
-        FLAG_SPLIT = 1 << 2,
+        SPLIT = 1 << 2,
 
         /* This flag indicates that the pointer coordinates dispatched to the application
          * will be zeroed out to avoid revealing information to an application. This is
          * used in conjunction with FLAG_DISPATCH_AS_OUTSIDE to prevent apps not sharing
          * the same UID from watching all touches. */
-        FLAG_ZERO_COORDS = 1 << 3,
+        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. */
-        FLAG_DISPATCH_AS_IS = 1 << 8,
+        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. */
-        FLAG_DISPATCH_AS_OUTSIDE = 1 << 9,
+        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. */
-        FLAG_DISPATCH_AS_HOVER_ENTER = 1 << 10,
+        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. */
-        FLAG_DISPATCH_AS_HOVER_EXIT = 1 << 11,
+        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. */
-        FLAG_DISPATCH_AS_SLIPPERY_EXIT = 1 << 12,
+        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. */
-        FLAG_DISPATCH_AS_SLIPPERY_ENTER = 1 << 13,
-
-        /* Mask for all dispatch modes. */
-        FLAG_DISPATCH_MASK = FLAG_DISPATCH_AS_IS | FLAG_DISPATCH_AS_OUTSIDE |
-                FLAG_DISPATCH_AS_HOVER_ENTER | FLAG_DISPATCH_AS_HOVER_EXIT |
-                FLAG_DISPATCH_AS_SLIPPERY_EXIT | FLAG_DISPATCH_AS_SLIPPERY_ENTER,
+        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. */
-        FLAG_WINDOW_IS_PARTIALLY_OBSCURED = 1 << 14,
-
+        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;
+
     // The input channel to be targeted.
     std::shared_ptr<InputChannel> inputChannel;
 
     // Flags for the input target.
-    int32_t flags = 0;
+    ftl::Flags<Flags> flags;
 
     // Scaling factor to apply to MotionEvent as it is delivered.
     // (ignored for KeyEvents)
diff --git a/services/inputflinger/dispatcher/TouchState.cpp b/services/inputflinger/dispatcher/TouchState.cpp
index cf0c38a..f120fc9 100644
--- a/services/inputflinger/dispatcher/TouchState.cpp
+++ b/services/inputflinger/dispatcher/TouchState.cpp
@@ -14,12 +14,14 @@
  * limitations under the License.
  */
 
+#include <android-base/stringprintf.h>
 #include <gui/WindowInfo.h>
 
 #include "InputTarget.h"
-
 #include "TouchState.h"
 
+using namespace android::ftl::flag_operators;
+using android::base::StringPrintf;
 using android::gui::WindowInfo;
 using android::gui::WindowInfoHandle;
 
@@ -29,18 +31,34 @@
     *this = TouchState();
 }
 
-void TouchState::addOrUpdateWindow(const sp<WindowInfoHandle>& windowHandle, int32_t targetFlags,
-                                   BitSet32 pointerIds, std::optional<nsecs_t> eventTime) {
-    if (targetFlags & InputTarget::FLAG_SPLIT) {
-        split = true;
+void TouchState::removeTouchedPointer(int32_t pointerId) {
+    for (TouchedWindow& touchedWindow : windows) {
+        touchedWindow.pointerIds.clearBit(pointerId);
     }
+}
 
-    for (size_t i = 0; i < windows.size(); i++) {
-        TouchedWindow& touchedWindow = windows[i];
+void TouchState::clearHoveringPointers() {
+    for (TouchedWindow& touchedWindow : windows) {
+        touchedWindow.clearHoveringPointers();
+    }
+}
+
+void TouchState::clearWindowsWithoutPointers() {
+    std::erase_if(windows, [](const TouchedWindow& w) {
+        return w.pointerIds.isEmpty() && !w.hasHoveringPointers();
+    });
+}
+
+void TouchState::addOrUpdateWindow(const sp<WindowInfoHandle>& windowHandle,
+                                   ftl::Flags<InputTarget::Flags> targetFlags, BitSet32 pointerIds,
+                                   std::optional<nsecs_t> eventTime) {
+    for (TouchedWindow& touchedWindow : windows) {
+        // We do not compare windows by token here because two windows that share the same token
+        // may have a different transform
         if (touchedWindow.windowHandle == windowHandle) {
             touchedWindow.targetFlags |= targetFlags;
-            if (targetFlags & InputTarget::FLAG_DISPATCH_AS_SLIPPERY_EXIT) {
-                touchedWindow.targetFlags &= ~InputTarget::FLAG_DISPATCH_AS_IS;
+            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 an pointer is down for
@@ -61,6 +79,21 @@
     windows.push_back(touchedWindow);
 }
 
+void TouchState::addHoveringPointerToWindow(const sp<WindowInfoHandle>& windowHandle,
+                                            int32_t hoveringDeviceId, int32_t hoveringPointerId) {
+    for (TouchedWindow& touchedWindow : windows) {
+        if (touchedWindow.windowHandle == windowHandle) {
+            touchedWindow.addHoveringPointer(hoveringDeviceId, hoveringPointerId);
+            return;
+        }
+    }
+
+    TouchedWindow touchedWindow;
+    touchedWindow.windowHandle = windowHandle;
+    touchedWindow.addHoveringPointer(hoveringDeviceId, hoveringPointerId);
+    windows.push_back(touchedWindow);
+}
+
 void TouchState::removeWindowByToken(const sp<IBinder>& token) {
     for (size_t i = 0; i < windows.size(); i++) {
         if (windows[i].windowHandle->getToken() == token) {
@@ -73,10 +106,10 @@
 void TouchState::filterNonAsIsTouchWindows() {
     for (size_t i = 0; i < windows.size();) {
         TouchedWindow& window = windows[i];
-        if (window.targetFlags &
-            (InputTarget::FLAG_DISPATCH_AS_IS | InputTarget::FLAG_DISPATCH_AS_SLIPPERY_ENTER)) {
-            window.targetFlags &= ~InputTarget::FLAG_DISPATCH_MASK;
-            window.targetFlags |= InputTarget::FLAG_DISPATCH_AS_IS;
+        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);
@@ -105,15 +138,10 @@
     std::erase_if(windows, [](const TouchedWindow& w) { return w.pointerIds.isEmpty(); });
 }
 
-void TouchState::filterWindowsExcept(const sp<IBinder>& token) {
-    std::erase_if(windows,
-                  [&token](const TouchedWindow& w) { return w.windowHandle->getToken() != token; });
-}
-
 sp<WindowInfoHandle> TouchState::getFirstForegroundWindowHandle() const {
     for (size_t i = 0; i < windows.size(); i++) {
         const TouchedWindow& window = windows[i];
-        if (window.targetFlags & InputTarget::FLAG_FOREGROUND) {
+        if (window.targetFlags.test(InputTarget::Flags::FOREGROUND)) {
             return window.windowHandle;
         }
     }
@@ -124,7 +152,7 @@
     // Must have exactly one foreground window.
     bool haveSlipperyForegroundWindow = false;
     for (const TouchedWindow& window : windows) {
-        if (window.targetFlags & InputTarget::FLAG_FOREGROUND) {
+        if (window.targetFlags.test(InputTarget::Flags::FOREGROUND)) {
             if (haveSlipperyForegroundWindow ||
                 !window.windowHandle->getInfo()->inputConfig.test(
                         WindowInfo::InputConfig::SLIPPERY)) {
@@ -147,14 +175,45 @@
     return nullptr;
 }
 
-sp<WindowInfoHandle> TouchState::getWindow(const sp<IBinder>& token) const {
-    for (const TouchedWindow& touchedWindow : windows) {
-        const auto& windowHandle = touchedWindow.windowHandle;
-        if (windowHandle->getToken() == token) {
-            return windowHandle;
+bool TouchState::isDown() const {
+    return std::any_of(windows.begin(), windows.end(),
+                       [](const TouchedWindow& window) { return !window.pointerIds.isEmpty(); });
+}
+
+std::set<sp<WindowInfoHandle>> TouchState::getWindowsWithHoveringPointer(int32_t hoveringDeviceId,
+                                                                         int32_t pointerId) const {
+    std::set<sp<WindowInfoHandle>> out;
+    for (const TouchedWindow& window : windows) {
+        if (window.hasHoveringPointer(hoveringDeviceId, pointerId)) {
+            out.insert(window.windowHandle);
         }
     }
-    return nullptr;
+    return out;
+}
+
+void TouchState::removeHoveringPointer(int32_t hoveringDeviceId, int32_t hoveringPointerId) {
+    for (TouchedWindow& window : windows) {
+        window.removeHoveringPointer(hoveringDeviceId, hoveringPointerId);
+    }
+    std::erase_if(windows, [](const TouchedWindow& w) {
+        return w.pointerIds.isEmpty() && !w.hasHoveringPointers();
+    });
+}
+
+std::string TouchState::dump() const {
+    std::string out;
+    out += StringPrintf("deviceId=%d, source=%s\n", deviceId,
+                        inputEventSourceToString(source).c_str());
+    if (!windows.empty()) {
+        out += "  Windows:\n";
+        for (size_t i = 0; i < windows.size(); i++) {
+            const TouchedWindow& touchedWindow = windows[i];
+            out += StringPrintf("    %zu : ", i) + touchedWindow.dump();
+        }
+    } else {
+        out += "  Windows: <none>\n";
+    }
+    return out;
 }
 
 } // namespace android::inputdispatcher
diff --git a/services/inputflinger/dispatcher/TouchState.h b/services/inputflinger/dispatcher/TouchState.h
index cf5f1e5..b75e6ef 100644
--- a/services/inputflinger/dispatcher/TouchState.h
+++ b/services/inputflinger/dispatcher/TouchState.h
@@ -16,7 +16,7 @@
 
 #pragma once
 
-#include "Monitor.h"
+#include <set>
 #include "TouchedWindow.h"
 
 namespace android {
@@ -28,15 +28,10 @@
 namespace inputdispatcher {
 
 struct TouchState {
-    bool down = false;
-    bool split = false;
-
     // id of the device that is currently down, others are rejected
     int32_t deviceId = -1;
     // source of the device that is current down, others are rejected
     uint32_t source = 0;
-    // id to the display that currently has a touch, others are rejected
-    int32_t displayId = ADISPLAY_ID_NONE;
 
     std::vector<TouchedWindow> windows;
 
@@ -45,12 +40,18 @@
     TouchState& operator=(const TouchState&) = default;
 
     void reset();
+    void clearWindowsWithoutPointers();
+
+    void removeTouchedPointer(int32_t pointerId);
     void addOrUpdateWindow(const sp<android::gui::WindowInfoHandle>& windowHandle,
-                           int32_t targetFlags, BitSet32 pointerIds,
+                           ftl::Flags<InputTarget::Flags> targetFlags, BitSet32 pointerIds,
                            std::optional<nsecs_t> eventTime = std::nullopt);
+    void addHoveringPointerToWindow(const sp<android::gui::WindowInfoHandle>& windowHandle,
+                                    int32_t deviceId, int32_t hoveringPointerId);
+    void removeHoveringPointer(int32_t deviceId, int32_t hoveringPointerId);
+    void clearHoveringPointers();
     void removeWindowByToken(const sp<IBinder>& token);
     void filterNonAsIsTouchWindows();
-    void filterWindowsExcept(const sp<IBinder>& token);
 
     // Cancel pointers for current set of windows except the window with particular binder token.
     void cancelPointersForWindowsExcept(const BitSet32 pointerIds, const sp<IBinder>& token);
@@ -61,7 +62,12 @@
     sp<android::gui::WindowInfoHandle> getFirstForegroundWindowHandle() const;
     bool isSlippery() const;
     sp<android::gui::WindowInfoHandle> getWallpaperWindow() const;
-    sp<android::gui::WindowInfoHandle> getWindow(const sp<IBinder>&) const;
+    // Whether any of the windows are currently being touched
+    bool isDown() const;
+
+    std::set<sp<android::gui::WindowInfoHandle>> getWindowsWithHoveringPointer(
+            int32_t deviceId, int32_t pointerId) const;
+    std::string dump() const;
 };
 
 } // namespace inputdispatcher
diff --git a/services/inputflinger/dispatcher/TouchedWindow.cpp b/services/inputflinger/dispatcher/TouchedWindow.cpp
new file mode 100644
index 0000000..3704edd
--- /dev/null
+++ b/services/inputflinger/dispatcher/TouchedWindow.cpp
@@ -0,0 +1,74 @@
+/*
+ * 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 "TouchedWindow.h"
+
+#include <android-base/stringprintf.h>
+#include <input/PrintTools.h>
+
+using android::base::StringPrintf;
+
+namespace android {
+
+namespace inputdispatcher {
+
+bool TouchedWindow::hasHoveringPointers() const {
+    return !mHoveringPointerIdsByDevice.empty();
+}
+
+void TouchedWindow::clearHoveringPointers() {
+    mHoveringPointerIdsByDevice.clear();
+}
+
+bool TouchedWindow::hasHoveringPointer(int32_t deviceId, int32_t pointerId) const {
+    auto it = mHoveringPointerIdsByDevice.find(deviceId);
+    if (it == mHoveringPointerIdsByDevice.end()) {
+        return false;
+    }
+    return it->second.test(pointerId);
+}
+
+void TouchedWindow::addHoveringPointer(int32_t deviceId, int32_t pointerId) {
+    const auto [it, _] = mHoveringPointerIdsByDevice.insert({deviceId, {}});
+    it->second.set(pointerId);
+}
+
+void TouchedWindow::removeHoveringPointer(int32_t deviceId, int32_t pointerId) {
+    const auto it = mHoveringPointerIdsByDevice.find(deviceId);
+    if (it == mHoveringPointerIdsByDevice.end()) {
+        return;
+    }
+    it->second.set(pointerId, false);
+
+    if (it->second.none()) {
+        mHoveringPointerIdsByDevice.erase(deviceId);
+    }
+}
+
+std::string TouchedWindow::dump() const {
+    std::string out;
+    std::string hoveringPointers =
+            dumpMap(mHoveringPointerIdsByDevice, constToString, bitsetToString);
+    out += StringPrintf("name='%s', pointerIds=0x%0x, targetFlags=%s, firstDownTimeInTarget=%s, "
+                        "mHoveringPointerIdsByDevice=%s\n",
+                        windowHandle->getName().c_str(), pointerIds.value,
+                        targetFlags.string().c_str(), toString(firstDownTimeInTarget).c_str(),
+                        hoveringPointers.c_str());
+    return out;
+}
+
+} // namespace inputdispatcher
+} // namespace android
diff --git a/services/inputflinger/dispatcher/TouchedWindow.h b/services/inputflinger/dispatcher/TouchedWindow.h
index a6c505b..add6b61 100644
--- a/services/inputflinger/dispatcher/TouchedWindow.h
+++ b/services/inputflinger/dispatcher/TouchedWindow.h
@@ -16,23 +16,36 @@
 
 #pragma once
 
-namespace android {
+#include <gui/WindowInfo.h>
+#include <input/Input.h>
+#include <utils/BitSet.h>
+#include <bitset>
+#include "InputTarget.h"
 
-namespace gui {
-class WindowInfoHandle;
-}
+namespace android {
 
 namespace inputdispatcher {
 
 // Focus tracking for touch.
 struct TouchedWindow {
     sp<gui::WindowInfoHandle> windowHandle;
-    int32_t targetFlags;
+    ftl::Flags<InputTarget::Flags> targetFlags;
     BitSet32 pointerIds;
     bool isPilferingPointers = false;
     // Time at which the first action down occurred on this window.
     // NOTE: This is not initialized in case of HOVER entry/exit and DISPATCH_AS_OUTSIDE scenario.
     std::optional<nsecs_t> firstDownTimeInTarget;
+
+    bool hasHoveringPointers() const;
+
+    bool hasHoveringPointer(int32_t deviceId, int32_t pointerId) const;
+    void addHoveringPointer(int32_t deviceId, int32_t pointerId);
+    void removeHoveringPointer(int32_t deviceId, int32_t pointerId);
+    void clearHoveringPointers();
+    std::string dump() const;
+
+private:
+    std::map<int32_t /*deviceId*/, std::bitset<MAX_POINTERS>> mHoveringPointerIdsByDevice;
 };
 
 } // namespace inputdispatcher
diff --git a/services/inputflinger/dispatcher/include/InputDispatcherInterface.h b/services/inputflinger/dispatcher/include/InputDispatcherInterface.h
index 484b0d3..76dce63 100644
--- a/services/inputflinger/dispatcher/include/InputDispatcherInterface.h
+++ b/services/inputflinger/dispatcher/include/InputDispatcherInterface.h
@@ -125,9 +125,12 @@
 
     /**
      * Set the touch mode state.
-     * Touch mode is a global state that apps may enter / exit based on specific
-     * user interactions with input devices.
-     * If true, the device is in touch mode.
+     * Touch mode is a per display state that apps may enter / exit based on specific user
+     * interactions with input devices. If <code>inTouchMode</code> is set to true, the display
+     * identified by <code>displayId</code> will be changed to touch mode. Performs a permission
+     * check if hasPermission is set to false.
+     *
+     * This method also enqueues a a TouchModeEntry message for dispatching.
      *
      * Returns true when changing touch mode state.
      */
diff --git a/services/inputflinger/include/InputReaderBase.h b/services/inputflinger/include/InputReaderBase.h
index cacb63c..6d6cefb 100644
--- a/services/inputflinger/include/InputReaderBase.h
+++ b/services/inputflinger/include/InputReaderBase.h
@@ -23,6 +23,7 @@
 #include <input/VelocityControl.h>
 #include <input/VelocityTracker.h>
 #include <stddef.h>
+#include <ui/Rotation.h>
 #include <unistd.h>
 #include <utils/Errors.h>
 #include <utils/RefBase.h>
@@ -142,6 +143,9 @@
     virtual std::optional<int32_t> getLightColor(int32_t deviceId, int32_t lightId) = 0;
     /* Get light player ID */
     virtual std::optional<int32_t> getLightPlayerId(int32_t deviceId, int32_t lightId) = 0;
+
+    /* Get the Bluetooth address of an input device, if known. */
+    virtual std::optional<std::string> getBluetoothAddress(int32_t deviceId) const = 0;
 };
 
 // --- InputReaderConfiguration ---
@@ -392,7 +396,9 @@
 
     /* Gets the affine calibration associated with the specified device. */
     virtual TouchAffineTransformation getTouchAffineTransformation(
-            const std::string& inputDeviceDescriptor, int32_t surfaceRotation) = 0;
+            const std::string& inputDeviceDescriptor, ui::Rotation surfaceRotation) = 0;
+    /* Notifies the input reader policy that a stylus gesture has started. */
+    virtual void notifyStylusGestureStarted(int32_t deviceId, nsecs_t eventTime) = 0;
 };
 
 } // namespace android
diff --git a/services/inputflinger/include/NotifyArgs.h b/services/inputflinger/include/NotifyArgs.h
index f28dbf3..c46f905 100644
--- a/services/inputflinger/include/NotifyArgs.h
+++ b/services/inputflinger/include/NotifyArgs.h
@@ -116,6 +116,8 @@
 
     NotifyMotionArgs(const NotifyMotionArgs& other);
 
+    NotifyMotionArgs& operator=(const android::NotifyMotionArgs&) = default;
+
     bool operator==(const NotifyMotionArgs& rhs) const;
 
     std::string dump() const;
diff --git a/services/inputflinger/include/PointerControllerInterface.h b/services/inputflinger/include/PointerControllerInterface.h
index 647e10c..7e0c1c7 100644
--- a/services/inputflinger/include/PointerControllerInterface.h
+++ b/services/inputflinger/include/PointerControllerInterface.h
@@ -79,6 +79,8 @@
         POINTER,
         // Show spots and a spot anchor in place of the mouse pointer.
         SPOT,
+
+        ftl_last = SPOT,
     };
 
     /* Sets the mode of the pointer controller. */
diff --git a/services/inputflinger/reader/Android.bp b/services/inputflinger/reader/Android.bp
index 0f87201..c93443a 100644
--- a/services/inputflinger/reader/Android.bp
+++ b/services/inputflinger/reader/Android.bp
@@ -23,6 +23,7 @@
 
 cc_library_headers {
     name: "libinputreader_headers",
+    host_supported: true,
     export_include_dirs: [
         "controller",
         "include",
@@ -36,11 +37,9 @@
     srcs: [
         "EventHub.cpp",
         "InputDevice.cpp",
+        "InputReader.cpp",
+        "TouchVideoDevice.cpp",
         "controller/PeripheralController.cpp",
-        "mapper/accumulator/CursorButtonAccumulator.cpp",
-        "mapper/accumulator/CursorScrollAccumulator.cpp",
-        "mapper/accumulator/SingleTouchMotionAccumulator.cpp",
-        "mapper/accumulator/TouchButtonAccumulator.cpp",
         "mapper/CursorInputMapper.cpp",
         "mapper/ExternalStylusInputMapper.cpp",
         "mapper/InputMapper.cpp",
@@ -52,9 +51,15 @@
         "mapper/SingleTouchInputMapper.cpp",
         "mapper/SwitchInputMapper.cpp",
         "mapper/TouchInputMapper.cpp",
+        "mapper/TouchpadInputMapper.cpp",
         "mapper/VibratorInputMapper.cpp",
-        "InputReader.cpp",
-        "TouchVideoDevice.cpp",
+        "mapper/accumulator/CursorButtonAccumulator.cpp",
+        "mapper/accumulator/CursorScrollAccumulator.cpp",
+        "mapper/accumulator/HidUsageAccumulator.cpp",
+        "mapper/accumulator/MultiTouchMotionAccumulator.cpp",
+        "mapper/accumulator/SingleTouchMotionAccumulator.cpp",
+        "mapper/accumulator/TouchButtonAccumulator.cpp",
+        "mapper/gestures/GesturesLogging.cpp",
     ],
 }
 
@@ -66,24 +71,59 @@
         "libcap",
         "libcrypto",
         "libcutils",
-        "libinput",
+        "libjsoncpp",
         "liblog",
         "libstatslog",
         "libutils",
-        "libPlatformProperties",
     ],
     static_libs: [
         "libc++fs",
+        "libchrome-gestures",
         "libui-types",
     ],
     header_libs: [
         "libbatteryservice_headers",
+        "libchrome-gestures_headers",
         "libinputreader_headers",
     ],
+    target: {
+        android: {
+            shared_libs: [
+                "libPlatformProperties",
+                "libinput",
+            ],
+        },
+        host: {
+            static_libs: [
+                "libinput",
+                "libbinder",
+            ],
+        },
+    },
+}
+
+cc_library_static {
+    name: "libinputreader_static",
+    defaults: [
+        "inputflinger_defaults",
+        "libinputreader_defaults",
+    ],
+    shared_libs: [
+        "libinputflinger_base",
+    ],
+    export_header_lib_headers: [
+        "libbatteryservice_headers",
+        "libchrome-gestures_headers",
+        "libinputreader_headers",
+    ],
+    whole_static_libs: [
+        "libchrome-gestures",
+    ],
 }
 
 cc_library_shared {
     name: "libinputreader",
+    host_supported: true,
     defaults: [
         "inputflinger_defaults",
         "libinputreader_defaults",
@@ -95,11 +135,21 @@
         // This should consist only of dependencies from inputflinger. Other dependencies should be
         // in cc_defaults so that they are included in the tests.
         "libinputflinger_base",
+        "libjsoncpp",
     ],
     export_header_lib_headers: [
         "libinputreader_headers",
     ],
+    target: {
+        host: {
+            include_dirs: [
+                "bionic/libc/kernel/android/uapi/",
+                "bionic/libc/kernel/uapi",
+            ],
+        },
+    },
     static_libs: [
         "libc++fs",
+        "libchrome-gestures",
     ],
 }
diff --git a/services/inputflinger/reader/EventHub.cpp b/services/inputflinger/reader/EventHub.cpp
index ca7e426..f2ea90c 100644
--- a/services/inputflinger/reader/EventHub.cpp
+++ b/services/inputflinger/reader/EventHub.cpp
@@ -19,6 +19,7 @@
 #include <errno.h>
 #include <fcntl.h>
 #include <inttypes.h>
+#include <linux/ioctl.h>
 #include <memory.h>
 #include <stdint.h>
 #include <stdio.h>
@@ -28,7 +29,6 @@
 #include <sys/epoll.h>
 #include <sys/inotify.h>
 #include <sys/ioctl.h>
-#include <sys/limits.h>
 #include <sys/stat.h>
 #include <sys/sysmacros.h>
 #include <unistd.h>
@@ -43,6 +43,7 @@
 #include <ftl/enum.h>
 #include <input/KeyCharacterMap.h>
 #include <input/KeyLayoutMap.h>
+#include <input/PrintTools.h>
 #include <input/VirtualKeyMap.h>
 #include <openssl/sha.h>
 #include <statslog.h>
@@ -134,10 +135,6 @@
                                                                   {"green", LightColor::GREEN},
                                                                   {"blue", LightColor::BLUE}};
 
-static inline const char* toString(bool value) {
-    return value ? "true" : "false";
-}
-
 static std::string sha1(const std::string& in) {
     SHA_CTX ctx;
     SHA1_Init(&ctx);
@@ -152,6 +149,14 @@
     return out;
 }
 
+/* The set of all Android key codes that correspond to buttons (bit-switches) on a stylus. */
+static constexpr std::array<int32_t, 4> STYLUS_BUTTON_KEYCODES = {
+        AKEYCODE_STYLUS_BUTTON_PRIMARY,
+        AKEYCODE_STYLUS_BUTTON_SECONDARY,
+        AKEYCODE_STYLUS_BUTTON_TERTIARY,
+        AKEYCODE_STYLUS_BUTTON_TAIL,
+};
+
 /**
  * Return true if name matches "v4l-touch*"
  */
@@ -192,8 +197,8 @@
     // calls clock_gettime(CLOCK_MONOTONIC) which is implemented as a
     // system call that also queries ktime_get_ts().
 
-    const nsecs_t inputEventTime = seconds_to_nanoseconds(event.time.tv_sec) +
-            microseconds_to_nanoseconds(event.time.tv_usec);
+    const nsecs_t inputEventTime = seconds_to_nanoseconds(event.input_event_sec) +
+            microseconds_to_nanoseconds(event.input_event_usec);
     return inputEventTime;
 }
 
@@ -633,8 +638,8 @@
     int32_t sc;
     if (hasValidFd() && mapLed(led, &sc) != NAME_NOT_FOUND) {
         struct input_event ev;
-        ev.time.tv_sec = 0;
-        ev.time.tv_usec = 0;
+        ev.input_event_sec = 0;
+        ev.input_event_usec = 0;
         ev.type = EV_LED;
         ev.code = sc;
         ev.value = on ? 1 : 0;
@@ -1463,8 +1468,8 @@
         device->ffEffectId = effect.id;
 
         struct input_event ev;
-        ev.time.tv_sec = 0;
-        ev.time.tv_usec = 0;
+        ev.input_event_sec = 0;
+        ev.input_event_usec = 0;
         ev.type = EV_FF;
         ev.code = device->ffEffectId;
         ev.value = 1;
@@ -1485,8 +1490,8 @@
             device->ffEffectPlaying = false;
 
             struct input_event ev;
-            ev.time.tv_sec = 0;
-            ev.time.tv_usec = 0;
+            ev.input_event_sec = 0;
+            ev.input_event_usec = 0;
             ev.type = EV_FF;
             ev.code = device->ffEffectId;
             ev.value = 0;
@@ -2128,6 +2133,17 @@
         identifier.uniqueId = buffer;
     }
 
+    // Attempt to get the bluetooth address of an input device from the uniqueId.
+    if (identifier.bus == BUS_BLUETOOTH &&
+        std::regex_match(identifier.uniqueId,
+                         std::regex("^[A-Fa-f0-9]{2}(?::[A-Fa-f0-9]{2}){5}$"))) {
+        identifier.bluetoothAddress = identifier.uniqueId;
+        // The Bluetooth stack requires alphabetic characters to be uppercase in a valid address.
+        for (auto& c : *identifier.bluetoothAddress) {
+            c = ::toupper(c);
+        }
+    }
+
     // Fill in the descriptor.
     assignDescriptorLocked(identifier);
 
@@ -2163,13 +2179,15 @@
     device->readDeviceBitMask(EVIOCGBIT(EV_MSC, 0), device->mscBitmask);
     device->readDeviceBitMask(EVIOCGPROP(0), device->propBitmask);
 
-    // See if this is a keyboard.  Ignore everything in the button range except for
-    // joystick and gamepad buttons which are handled like keyboards for the most part.
+    // See if this is a device with keys. This could be full keyboard, or other devices like
+    // gamepads, joysticks, and styluses with buttons that should generate key presses.
     bool haveKeyboardKeys =
             device->keyBitmask.any(0, BTN_MISC) || device->keyBitmask.any(BTN_WHEEL, KEY_MAX + 1);
     bool haveGamepadButtons = device->keyBitmask.any(BTN_MISC, BTN_MOUSE) ||
             device->keyBitmask.any(BTN_JOYSTICK, BTN_DIGI);
-    if (haveKeyboardKeys || haveGamepadButtons) {
+    bool haveStylusButtons = device->keyBitmask.test(BTN_STYLUS) ||
+            device->keyBitmask.test(BTN_STYLUS2) || device->keyBitmask.test(BTN_STYLUS3);
+    if (haveKeyboardKeys || haveGamepadButtons || haveStylusButtons) {
         device->classes |= InputDeviceClass::KEYBOARD;
     }
 
@@ -2179,11 +2197,13 @@
         device->classes |= InputDeviceClass::CURSOR;
     }
 
-    // See if this is a rotary encoder type device.
+    // See if the device is specially configured to be of a certain type.
     std::string deviceType;
     if (device->configuration && device->configuration->tryGetProperty("device.type", deviceType)) {
         if (deviceType == "rotaryEncoder") {
             device->classes |= InputDeviceClass::ROTARY_ENCODER;
+        } else if (deviceType == "externalStylus") {
+            device->classes |= InputDeviceClass::EXTERNAL_STYLUS;
         }
     }
 
@@ -2195,19 +2215,19 @@
         // a touch screen.
         if (device->keyBitmask.test(BTN_TOUCH) || !haveGamepadButtons) {
             device->classes |= (InputDeviceClass::TOUCH | InputDeviceClass::TOUCH_MT);
+            if (device->propBitmask.test(INPUT_PROP_POINTER) &&
+                !device->keyBitmask.any(BTN_TOOL_PEN, BTN_TOOL_FINGER) && !haveStylusButtons) {
+                device->classes |= InputDeviceClass::TOUCHPAD;
+            }
         }
         // Is this an old style single-touch driver?
     } else if (device->keyBitmask.test(BTN_TOUCH) && device->absBitmask.test(ABS_X) &&
                device->absBitmask.test(ABS_Y)) {
         device->classes |= InputDeviceClass::TOUCH;
-        // Is this a BT stylus?
+        // Is this a stylus that reports contact/pressure independently of touch coordinates?
     } else if ((device->absBitmask.test(ABS_PRESSURE) || device->keyBitmask.test(BTN_TOUCH)) &&
                !device->absBitmask.test(ABS_X) && !device->absBitmask.test(ABS_Y)) {
         device->classes |= InputDeviceClass::EXTERNAL_STYLUS;
-        // Keyboard will try to claim some of the buttons but we really want to reserve those so we
-        // can fuse it with the touch screen data, so just take them back. Note this means an
-        // external stylus cannot also be a keyboard device.
-        device->classes &= ~InputDeviceClass::KEYBOARD;
     }
 
     // See if this device is a joystick.
@@ -2292,6 +2312,16 @@
                 break;
             }
         }
+
+        // See if this device has any stylus buttons that we would want to fuse with touch data.
+        if (!device->classes.any(InputDeviceClass::TOUCH | InputDeviceClass::TOUCH_MT)) {
+            for (int32_t keycode : STYLUS_BUTTON_KEYCODES) {
+                if (device->hasKeycodeLocked(keycode)) {
+                    device->classes |= InputDeviceClass::EXTERNAL_STYLUS;
+                    break;
+                }
+            }
+        }
     }
 
     // If the device isn't recognized as something we handle, don't monitor it.
@@ -2625,9 +2655,10 @@
             dump += StringPrintf(INDENT3 "ControllerNumber: %d\n", device->controllerNumber);
             dump += StringPrintf(INDENT3 "UniqueId: %s\n", device->identifier.uniqueId.c_str());
             dump += StringPrintf(INDENT3 "Identifier: bus=0x%04x, vendor=0x%04x, "
-                                         "product=0x%04x, version=0x%04x\n",
+                                         "product=0x%04x, version=0x%04x, bluetoothAddress=%s\n",
                                  device->identifier.bus, device->identifier.vendor,
-                                 device->identifier.product, device->identifier.version);
+                                 device->identifier.product, device->identifier.version,
+                                 toString(device->identifier.bluetoothAddress).c_str());
             dump += StringPrintf(INDENT3 "KeyLayoutFile: %s\n",
                                  device->keyMap.keyLayoutFile.c_str());
             dump += StringPrintf(INDENT3 "KeyCharacterMapFile: %s\n",
diff --git a/services/inputflinger/reader/InputDevice.cpp b/services/inputflinger/reader/InputDevice.cpp
index 5291776..8d5377f 100644
--- a/services/inputflinger/reader/InputDevice.cpp
+++ b/services/inputflinger/reader/InputDevice.cpp
@@ -20,6 +20,9 @@
 
 #include <algorithm>
 
+#if defined(__ANDROID__)
+#include <android/sysprop/InputProperties.sysprop.h>
+#endif
 #include <ftl/flags.h>
 
 #include "CursorInputMapper.h"
@@ -33,6 +36,7 @@
 #include "SensorInputMapper.h"
 #include "SingleTouchInputMapper.h"
 #include "SwitchInputMapper.h"
+#include "TouchpadInputMapper.h"
 #include "VibratorInputMapper.h"
 
 using android::hardware::input::InputDeviceCountryCode;
@@ -208,7 +212,16 @@
     }
 
     // Touchscreens and touchpad devices.
-    if (classes.test(InputDeviceClass::TOUCH_MT)) {
+    static const bool ENABLE_TOUCHPAD_GESTURES_LIBRARY =
+#if defined(__ANDROID__)
+            sysprop::InputProperties::enable_touchpad_gestures_library().value_or(false);
+#else
+            false;
+#endif
+    if (ENABLE_TOUCHPAD_GESTURES_LIBRARY && classes.test(InputDeviceClass::TOUCHPAD) &&
+        classes.test(InputDeviceClass::TOUCH_MT)) {
+        mappers.push_back(std::make_unique<TouchpadInputMapper>(*contextPtr));
+    } else if (classes.test(InputDeviceClass::TOUCH_MT)) {
         mappers.push_back(std::make_unique<MultiTouchInputMapper>(*contextPtr));
     } else if (classes.test(InputDeviceClass::TOUCH)) {
         mappers.push_back(std::make_unique<SingleTouchInputMapper>(*contextPtr));
@@ -313,7 +326,10 @@
             }
         }
 
-        if (!changes || (changes & InputReaderConfiguration::CHANGE_ENABLED_STATE)) {
+        if (changes & InputReaderConfiguration::CHANGE_ENABLED_STATE) {
+            // Do not execute this code on the first configure, because 'setEnabled' would call
+            // InputMapper::reset, and you can't reset a mapper before it has been configured.
+            // The mappers are configured for the first time at the bottom of this function.
             auto it = config->disabledDevices.find(mId);
             bool enabled = it == config->disabledDevices.end();
             out += setEnabled(enabled, when);
diff --git a/services/inputflinger/reader/InputReader.cpp b/services/inputflinger/reader/InputReader.cpp
index 428e999..f04a646 100644
--- a/services/inputflinger/reader/InputReader.cpp
+++ b/services/inputflinger/reader/InputReader.cpp
@@ -58,6 +58,17 @@
             identifier1.location == identifier2.location);
 }
 
+static bool isStylusPointerGestureStart(const NotifyMotionArgs& motionArgs) {
+    const auto actionMasked = MotionEvent::getActionMasked(motionArgs.action);
+    if (actionMasked != AMOTION_EVENT_ACTION_HOVER_ENTER &&
+        actionMasked != AMOTION_EVENT_ACTION_DOWN &&
+        actionMasked != AMOTION_EVENT_ACTION_POINTER_DOWN) {
+        return false;
+    }
+    const auto actionIndex = MotionEvent::getActionIndex(motionArgs.action);
+    return isStylusToolType(motionArgs.pointerProperties[actionIndex].toolType);
+}
+
 // --- InputReader ---
 
 InputReader::InputReader(std::shared_ptr<EventHubInterface> eventHub,
@@ -101,8 +112,10 @@
 void InputReader::loopOnce() {
     int32_t oldGeneration;
     int32_t timeoutMillis;
+    // Copy some state so that we can access it outside the lock later.
     bool inputDevicesChanged = false;
     std::vector<InputDeviceInfo> inputDevices;
+    std::list<NotifyArgs> notifyArgs;
     { // acquire lock
         std::scoped_lock _l(mLock);
 
@@ -127,7 +140,7 @@
         mReaderIsAliveCondition.notify_all();
 
         if (!events.empty()) {
-            notifyAll(processEventsLocked(events.data(), events.size()));
+            notifyArgs += processEventsLocked(events.data(), events.size());
         }
 
         if (mNextTimeout != LLONG_MAX) {
@@ -137,7 +150,7 @@
                     ALOGD("Timeout expired, latency=%0.3fms", (now - mNextTimeout) * 0.000001f);
                 }
                 mNextTimeout = LLONG_MAX;
-                notifyAll(timeoutExpiredLocked(now));
+                notifyArgs += timeoutExpiredLocked(now);
             }
         }
 
@@ -152,6 +165,16 @@
         mPolicy->notifyInputDevicesChanged(inputDevices);
     }
 
+    // Notify the policy of the start of every new stylus gesture outside the lock.
+    for (const auto& args : notifyArgs) {
+        const auto* motionArgs = std::get_if<NotifyMotionArgs>(&args);
+        if (motionArgs != nullptr && isStylusPointerGestureStart(*motionArgs)) {
+            mPolicy->notifyStylusGestureStarted(motionArgs->deviceId, motionArgs->eventTime);
+        }
+    }
+
+    notifyAll(std::move(notifyArgs));
+
     // Flush queued events out to the listener.
     // This must happen outside of the lock because the listener could potentially call
     // back into the InputReader's methods, such as getScanCodeState, or become blocked
@@ -851,6 +874,16 @@
     return std::nullopt;
 }
 
+std::optional<std::string> InputReader::getBluetoothAddress(int32_t deviceId) const {
+    std::scoped_lock _l(mLock);
+
+    InputDevice* device = findInputDeviceLocked(deviceId);
+    if (device) {
+        return device->getBluetoothAddress();
+    }
+    return std::nullopt;
+}
+
 bool InputReader::isInputDeviceEnabled(int32_t deviceId) {
     std::scoped_lock _l(mLock);
 
diff --git a/services/inputflinger/reader/TouchVideoDevice.cpp b/services/inputflinger/reader/TouchVideoDevice.cpp
index 2f8138b..627dcba 100644
--- a/services/inputflinger/reader/TouchVideoDevice.cpp
+++ b/services/inputflinger/reader/TouchVideoDevice.cpp
@@ -198,8 +198,9 @@
     if ((buf.flags & V4L2_BUF_FLAG_TIMESTAMP_MASK) != V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC) {
         // We use CLOCK_MONOTONIC for input events, so if the clocks don't match,
         // we can't compare timestamps. Just log a warning, since this is a driver issue
-        ALOGW("The timestamp %ld.%ld was not acquired using CLOCK_MONOTONIC", buf.timestamp.tv_sec,
-              buf.timestamp.tv_usec);
+        ALOGW("The timestamp %lld.%lld was not acquired using CLOCK_MONOTONIC",
+              static_cast<long long>(buf.timestamp.tv_sec),
+              static_cast<long long>(buf.timestamp.tv_usec));
     }
     std::vector<int16_t> data(mHeight * mWidth);
     const int16_t* readFrom = mReadLocations[buf.index];
diff --git a/services/inputflinger/reader/include/EventHub.h b/services/inputflinger/reader/include/EventHub.h
index 6933ec7..42ca482 100644
--- a/services/inputflinger/reader/include/EventHub.h
+++ b/services/inputflinger/reader/include/EventHub.h
@@ -67,22 +67,15 @@
 
 /* Describes an absolute axis. */
 struct RawAbsoluteAxisInfo {
-    bool valid; // true if the information is valid, false otherwise
+    bool valid{false}; // true if the information is valid, false otherwise
 
-    int32_t minValue;   // minimum value
-    int32_t maxValue;   // maximum value
-    int32_t flat;       // center flat position, eg. flat == 8 means center is between -8 and 8
-    int32_t fuzz;       // error tolerance, eg. fuzz == 4 means value is +/- 4 due to noise
-    int32_t resolution; // resolution in units per mm or radians per mm
+    int32_t minValue{};   // minimum value
+    int32_t maxValue{};   // maximum value
+    int32_t flat{};       // center flat position, eg. flat == 8 means center is between -8 and 8
+    int32_t fuzz{};       // error tolerance, eg. fuzz == 4 means value is +/- 4 due to noise
+    int32_t resolution{}; // resolution in units per mm or radians per mm
 
-    inline void clear() {
-        valid = false;
-        minValue = 0;
-        maxValue = 0;
-        flat = 0;
-        fuzz = 0;
-        resolution = 0;
-    }
+    inline void clear() { *this = RawAbsoluteAxisInfo(); }
 };
 
 /*
@@ -101,7 +94,7 @@
     /* The input device is a cursor device such as a trackball or mouse. */
     CURSOR = 0x00000008,
 
-    /* The input device is a multi-touch touchscreen. */
+    /* The input device is a multi-touch touchscreen or touchpad. */
     TOUCH_MT = 0x00000010,
 
     /* The input device is a directional pad (implies keyboard, has DPAD keys). */
@@ -137,6 +130,9 @@
     /* The input device has sysfs controllable lights */
     LIGHT = 0x00008000,
 
+    /* The input device is a touchpad, requiring an on-screen cursor. */
+    TOUCHPAD = 0x00010000,
+
     /* The input device is virtual (not a real device, not part of UI configuration). */
     VIRTUAL = 0x40000000,
 
diff --git a/services/inputflinger/reader/include/InputDevice.h b/services/inputflinger/reader/include/InputDevice.h
index afb1bed..439123b 100644
--- a/services/inputflinger/reader/include/InputDevice.h
+++ b/services/inputflinger/reader/include/InputDevice.h
@@ -51,6 +51,9 @@
     inline int32_t getGeneration() const { return mGeneration; }
     inline const std::string getName() const { return mIdentifier.name; }
     inline const std::string getDescriptor() { return mIdentifier.descriptor; }
+    inline std::optional<std::string> getBluetoothAddress() const {
+        return mIdentifier.bluetoothAddress;
+    }
     inline ftl::Flags<InputDeviceClass> getClasses() const { return mClasses; }
     inline uint32_t getSources() const { return mSources; }
     inline bool hasEventHubDevices() const { return !mDevices.empty(); }
@@ -375,8 +378,11 @@
         mEventHub->getAbsoluteAxisInfo(mId, code, &info);
         return info.valid;
     }
-    inline bool isKeyPressed(int32_t code) const {
-        return mEventHub->getScanCodeState(mId, code) == AKEY_STATE_DOWN;
+    inline bool isKeyPressed(int32_t scanCode) const {
+        return mEventHub->getScanCodeState(mId, scanCode) == AKEY_STATE_DOWN;
+    }
+    inline bool isKeyCodePressed(int32_t keyCode) const {
+        return mEventHub->getKeyCodeState(mId, keyCode) == AKEY_STATE_DOWN;
     }
     inline int32_t getAbsoluteAxisValue(int32_t code) const {
         int32_t value;
diff --git a/services/inputflinger/reader/include/InputReader.h b/services/inputflinger/reader/include/InputReader.h
index de268cf..4f2503a 100644
--- a/services/inputflinger/reader/include/InputReader.h
+++ b/services/inputflinger/reader/include/InputReader.h
@@ -113,6 +113,8 @@
 
     std::optional<int32_t> getLightPlayerId(int32_t deviceId, int32_t lightId) override;
 
+    std::optional<std::string> getBluetoothAddress(int32_t deviceId) const override;
+
 protected:
     // These members are protected so they can be instrumented by test cases.
     virtual std::shared_ptr<InputDevice> createDeviceLocked(int32_t deviceId,
diff --git a/services/inputflinger/reader/include/StylusState.h b/services/inputflinger/reader/include/StylusState.h
index 8d14d3c..ff15e0c 100644
--- a/services/inputflinger/reader/include/StylusState.h
+++ b/services/inputflinger/reader/include/StylusState.h
@@ -24,27 +24,19 @@
 
 struct StylusState {
     /* Time the stylus event was received. */
-    nsecs_t when;
-    /* Pressure as reported by the stylus, normalized to the range [0, 1.0]. */
-    float pressure;
+    nsecs_t when{};
+    /*
+     * Pressure as reported by the stylus if supported, normalized to the range [0, 1.0].
+     * The presence of a pressure value indicates that the stylus is able to tell whether it is
+     * touching the display.
+     */
+    std::optional<float> pressure{};
     /* The state of the stylus buttons as a bitfield (e.g. AMOTION_EVENT_BUTTON_SECONDARY). */
-    uint32_t buttons;
+    uint32_t buttons{};
     /* Which tool type the stylus is currently using (e.g. AMOTION_EVENT_TOOL_TYPE_ERASER). */
-    int32_t toolType;
+    int32_t toolType{AMOTION_EVENT_TOOL_TYPE_UNKNOWN};
 
-    void copyFrom(const StylusState& other) {
-        when = other.when;
-        pressure = other.pressure;
-        buttons = other.buttons;
-        toolType = other.toolType;
-    }
-
-    void clear() {
-        when = LLONG_MAX;
-        pressure = 0.f;
-        buttons = 0;
-        toolType = AMOTION_EVENT_TOOL_TYPE_UNKNOWN;
-    }
+    void clear() { *this = StylusState{}; }
 };
 
 } // namespace android
diff --git a/services/inputflinger/reader/mapper/CursorInputMapper.cpp b/services/inputflinger/reader/mapper/CursorInputMapper.cpp
index c691ca9..13e4d0c 100644
--- a/services/inputflinger/reader/mapper/CursorInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/CursorInputMapper.cpp
@@ -67,7 +67,7 @@
 // --- CursorInputMapper ---
 
 CursorInputMapper::CursorInputMapper(InputDeviceContext& deviceContext)
-      : InputMapper(deviceContext) {}
+      : InputMapper(deviceContext), mLastEventTime(std::numeric_limits<nsecs_t>::min()) {}
 
 CursorInputMapper::~CursorInputMapper() {
     if (mPointerController != nullptr) {
@@ -117,6 +117,10 @@
                          toString(mCursorScrollAccumulator.haveRelativeVWheel()));
     dump += StringPrintf(INDENT3 "HaveHWheel: %s\n",
                          toString(mCursorScrollAccumulator.haveRelativeHWheel()));
+    dump += StringPrintf(INDENT3 "WheelYVelocityControlParameters: %s",
+                         mWheelYVelocityControl.getParameters().dump().c_str());
+    dump += StringPrintf(INDENT3 "WheelXVelocityControlParameters: %s",
+                         mWheelXVelocityControl.getParameters().dump().c_str());
     dump += StringPrintf(INDENT3 "VWheelScale: %0.3f\n", mVWheelScale);
     dump += StringPrintf(INDENT3 "HWheelScale: %0.3f\n", mHWheelScale);
     dump += StringPrintf(INDENT3 "DisplayId: %s\n", toString(mDisplayId).c_str());
@@ -223,7 +227,7 @@
             mDisplayId = mPointerController->getDisplayId();
         }
 
-        mOrientation = DISPLAY_ORIENTATION_0;
+        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
@@ -276,6 +280,7 @@
 std::list<NotifyArgs> CursorInputMapper::reset(nsecs_t when) {
     mButtonState = 0;
     mDownTime = 0;
+    mLastEventTime = std::numeric_limits<nsecs_t>::min();
 
     mPointerVelocityControl.reset();
     mWheelXVelocityControl.reset();
@@ -295,7 +300,12 @@
     mCursorScrollAccumulator.process(rawEvent);
 
     if (rawEvent->type == EV_SYN && rawEvent->code == SYN_REPORT) {
-        out += sync(rawEvent->when, rawEvent->readTime);
+        const auto [eventTime, readTime] =
+                applyBluetoothTimestampSmoothening(getDeviceContext().getDeviceIdentifier(),
+                                                   rawEvent->when, rawEvent->readTime,
+                                                   mLastEventTime);
+        out += sync(eventTime, readTime);
+        mLastEventTime = eventTime;
     }
     return out;
 }
diff --git a/services/inputflinger/reader/mapper/CursorInputMapper.h b/services/inputflinger/reader/mapper/CursorInputMapper.h
index 6a4275e..939cceb 100644
--- a/services/inputflinger/reader/mapper/CursorInputMapper.h
+++ b/services/inputflinger/reader/mapper/CursorInputMapper.h
@@ -22,6 +22,7 @@
 
 #include <PointerControllerInterface.h>
 #include <input/VelocityControl.h>
+#include <ui/Rotation.h>
 
 namespace android {
 
@@ -115,12 +116,13 @@
     // 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;
-    int32_t mOrientation;
+    ui::Rotation mOrientation;
 
     std::shared_ptr<PointerControllerInterface> mPointerController;
 
     int32_t mButtonState;
     nsecs_t mDownTime;
+    nsecs_t mLastEventTime;
 
     void configureParameters();
     void dumpParameters(std::string& dump);
diff --git a/services/inputflinger/reader/mapper/ExternalStylusInputMapper.cpp b/services/inputflinger/reader/mapper/ExternalStylusInputMapper.cpp
index 0404c9a..2809939 100644
--- a/services/inputflinger/reader/mapper/ExternalStylusInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/ExternalStylusInputMapper.cpp
@@ -24,7 +24,7 @@
 namespace android {
 
 ExternalStylusInputMapper::ExternalStylusInputMapper(InputDeviceContext& deviceContext)
-      : InputMapper(deviceContext) {}
+      : InputMapper(deviceContext), mTouchButtonAccumulator(deviceContext) {}
 
 uint32_t ExternalStylusInputMapper::getSources() const {
     return AINPUT_SOURCE_STYLUS;
@@ -32,8 +32,10 @@
 
 void ExternalStylusInputMapper::populateDeviceInfo(InputDeviceInfo* info) {
     InputMapper::populateDeviceInfo(info);
-    info->addMotionRange(AMOTION_EVENT_AXIS_PRESSURE, AINPUT_SOURCE_STYLUS, 0.0f, 1.0f, 0.0f, 0.0f,
-                         0.0f);
+    if (mRawPressureAxis.valid) {
+        info->addMotionRange(AMOTION_EVENT_AXIS_PRESSURE, AINPUT_SOURCE_STYLUS, 0.0f, 1.0f, 0.0f,
+                             0.0f, 0.0f);
+    }
 }
 
 void ExternalStylusInputMapper::dump(std::string& dump) {
@@ -48,13 +50,13 @@
                                                            const InputReaderConfiguration* config,
                                                            uint32_t changes) {
     getAbsoluteAxisInfo(ABS_PRESSURE, &mRawPressureAxis);
-    mTouchButtonAccumulator.configure(getDeviceContext());
+    mTouchButtonAccumulator.configure();
     return {};
 }
 
 std::list<NotifyArgs> ExternalStylusInputMapper::reset(nsecs_t when) {
     mSingleTouchMotionAccumulator.reset(getDeviceContext());
-    mTouchButtonAccumulator.reset(getDeviceContext());
+    mTouchButtonAccumulator.reset();
     return InputMapper::reset(when);
 }
 
@@ -79,13 +81,12 @@
         mStylusState.toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS;
     }
 
-    int32_t pressure = mSingleTouchMotionAccumulator.getAbsolutePressure();
     if (mRawPressureAxis.valid) {
-        mStylusState.pressure = float(pressure) / mRawPressureAxis.maxValue;
-    } else if (mTouchButtonAccumulator.isToolActive()) {
-        mStylusState.pressure = 1.0f;
-    } else {
-        mStylusState.pressure = 0.0f;
+        auto rawPressure = static_cast<float>(mSingleTouchMotionAccumulator.getAbsolutePressure());
+        mStylusState.pressure = (rawPressure - mRawPressureAxis.minValue) /
+                static_cast<float>(mRawPressureAxis.maxValue - mRawPressureAxis.minValue);
+    } else if (mTouchButtonAccumulator.hasButtonTouch()) {
+        mStylusState.pressure = mTouchButtonAccumulator.isHovering() ? 0.0f : 1.0f;
     }
 
     mStylusState.buttons = mTouchButtonAccumulator.getButtonState();
diff --git a/services/inputflinger/reader/mapper/InputMapper.cpp b/services/inputflinger/reader/mapper/InputMapper.cpp
index 844afe0..8e3539c 100644
--- a/services/inputflinger/reader/mapper/InputMapper.cpp
+++ b/services/inputflinger/reader/mapper/InputMapper.cpp
@@ -19,6 +19,7 @@
 #include "InputMapper.h"
 
 #include "InputDevice.h"
+#include "input/PrintTools.h"
 
 namespace android {
 
@@ -129,7 +130,7 @@
 
 void InputMapper::dumpStylusState(std::string& dump, const StylusState& state) {
     dump += StringPrintf(INDENT4 "When: %" PRId64 "\n", state.when);
-    dump += StringPrintf(INDENT4 "Pressure: %f\n", state.pressure);
+    dump += StringPrintf(INDENT4 "Pressure: %s\n", toString(state.pressure).c_str());
     dump += StringPrintf(INDENT4 "Button State: 0x%08x\n", state.buttons);
     dump += StringPrintf(INDENT4 "Tool Type: %" PRId32 "\n", state.toolType);
 }
diff --git a/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp b/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp
index 8704d1b..44f0dfe 100644
--- a/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp
@@ -20,71 +20,75 @@
 
 #include "KeyboardInputMapper.h"
 
+#include <ui/Rotation.h>
+
 namespace android {
 
 // --- Static Definitions ---
 
-static int32_t rotateValueUsingRotationMap(int32_t value, int32_t orientation,
-                                           const int32_t map[][4], size_t mapSize) {
-    if (orientation != DISPLAY_ORIENTATION_0) {
-        for (size_t i = 0; i < mapSize; i++) {
-            if (value == map[i][0]) {
-                return map[i][orientation];
+static int32_t rotateKeyCode(int32_t keyCode, ui::Rotation orientation) {
+    static constexpr int32_t KEYCODE_ROTATION_MAP[][4] = {
+            // key codes enumerated counter-clockwise with the original (unrotated) key first
+            // no rotation,        90 degree rotation,  180 degree rotation, 270 degree rotation
+            {AKEYCODE_DPAD_DOWN, AKEYCODE_DPAD_RIGHT, AKEYCODE_DPAD_UP, AKEYCODE_DPAD_LEFT},
+            {AKEYCODE_DPAD_RIGHT, AKEYCODE_DPAD_UP, AKEYCODE_DPAD_LEFT, AKEYCODE_DPAD_DOWN},
+            {AKEYCODE_DPAD_UP, AKEYCODE_DPAD_LEFT, AKEYCODE_DPAD_DOWN, AKEYCODE_DPAD_RIGHT},
+            {AKEYCODE_DPAD_LEFT, AKEYCODE_DPAD_DOWN, AKEYCODE_DPAD_RIGHT, AKEYCODE_DPAD_UP},
+            {AKEYCODE_SYSTEM_NAVIGATION_DOWN, AKEYCODE_SYSTEM_NAVIGATION_RIGHT,
+             AKEYCODE_SYSTEM_NAVIGATION_UP, AKEYCODE_SYSTEM_NAVIGATION_LEFT},
+            {AKEYCODE_SYSTEM_NAVIGATION_RIGHT, AKEYCODE_SYSTEM_NAVIGATION_UP,
+             AKEYCODE_SYSTEM_NAVIGATION_LEFT, AKEYCODE_SYSTEM_NAVIGATION_DOWN},
+            {AKEYCODE_SYSTEM_NAVIGATION_UP, AKEYCODE_SYSTEM_NAVIGATION_LEFT,
+             AKEYCODE_SYSTEM_NAVIGATION_DOWN, AKEYCODE_SYSTEM_NAVIGATION_RIGHT},
+            {AKEYCODE_SYSTEM_NAVIGATION_LEFT, AKEYCODE_SYSTEM_NAVIGATION_DOWN,
+             AKEYCODE_SYSTEM_NAVIGATION_RIGHT, AKEYCODE_SYSTEM_NAVIGATION_UP},
+    };
+
+    if (orientation != ui::ROTATION_0) {
+        for (const auto& rotation : KEYCODE_ROTATION_MAP) {
+            if (rotation[static_cast<size_t>(ui::ROTATION_0)] == keyCode) {
+                return rotation[static_cast<size_t>(orientation)];
             }
         }
     }
-    return value;
+    return keyCode;
 }
 
-static const int32_t keyCodeRotationMap[][4] = {
-        // key codes enumerated counter-clockwise with the original (unrotated) key first
-        // no rotation,        90 degree rotation,  180 degree rotation, 270 degree rotation
-        {AKEYCODE_DPAD_DOWN, AKEYCODE_DPAD_RIGHT, AKEYCODE_DPAD_UP, AKEYCODE_DPAD_LEFT},
-        {AKEYCODE_DPAD_RIGHT, AKEYCODE_DPAD_UP, AKEYCODE_DPAD_LEFT, AKEYCODE_DPAD_DOWN},
-        {AKEYCODE_DPAD_UP, AKEYCODE_DPAD_LEFT, AKEYCODE_DPAD_DOWN, AKEYCODE_DPAD_RIGHT},
-        {AKEYCODE_DPAD_LEFT, AKEYCODE_DPAD_DOWN, AKEYCODE_DPAD_RIGHT, AKEYCODE_DPAD_UP},
-        {AKEYCODE_SYSTEM_NAVIGATION_DOWN, AKEYCODE_SYSTEM_NAVIGATION_RIGHT,
-         AKEYCODE_SYSTEM_NAVIGATION_UP, AKEYCODE_SYSTEM_NAVIGATION_LEFT},
-        {AKEYCODE_SYSTEM_NAVIGATION_RIGHT, AKEYCODE_SYSTEM_NAVIGATION_UP,
-         AKEYCODE_SYSTEM_NAVIGATION_LEFT, AKEYCODE_SYSTEM_NAVIGATION_DOWN},
-        {AKEYCODE_SYSTEM_NAVIGATION_UP, AKEYCODE_SYSTEM_NAVIGATION_LEFT,
-         AKEYCODE_SYSTEM_NAVIGATION_DOWN, AKEYCODE_SYSTEM_NAVIGATION_RIGHT},
-        {AKEYCODE_SYSTEM_NAVIGATION_LEFT, AKEYCODE_SYSTEM_NAVIGATION_DOWN,
-         AKEYCODE_SYSTEM_NAVIGATION_RIGHT, AKEYCODE_SYSTEM_NAVIGATION_UP},
-};
+static bool isSupportedScanCode(int32_t scanCode) {
+    // KeyboardInputMapper handles keys from keyboards, gamepads, and styluses.
+    return scanCode < BTN_MOUSE || (scanCode >= BTN_JOYSTICK && scanCode < BTN_DIGI) ||
+            scanCode == BTN_STYLUS || scanCode == BTN_STYLUS2 || scanCode == BTN_STYLUS3 ||
+            scanCode >= BTN_WHEEL;
+}
 
-static const size_t keyCodeRotationMapSize =
-        sizeof(keyCodeRotationMap) / sizeof(keyCodeRotationMap[0]);
-
-static int32_t rotateStemKey(int32_t value, int32_t orientation, const int32_t map[][2],
-                             size_t mapSize) {
-    if (orientation == DISPLAY_ORIENTATION_180) {
-        for (size_t i = 0; i < mapSize; i++) {
-            if (value == map[i][0]) {
-                return map[i][1];
-            }
-        }
+static bool isMediaKey(int32_t keyCode) {
+    switch (keyCode) {
+        case AKEYCODE_MEDIA_PLAY:
+        case AKEYCODE_MEDIA_PAUSE:
+        case AKEYCODE_MEDIA_PLAY_PAUSE:
+        case AKEYCODE_MUTE:
+        case AKEYCODE_HEADSETHOOK:
+        case AKEYCODE_MEDIA_STOP:
+        case AKEYCODE_MEDIA_NEXT:
+        case AKEYCODE_MEDIA_PREVIOUS:
+        case AKEYCODE_MEDIA_REWIND:
+        case AKEYCODE_MEDIA_RECORD:
+        case AKEYCODE_MEDIA_FAST_FORWARD:
+        case AKEYCODE_MEDIA_SKIP_FORWARD:
+        case AKEYCODE_MEDIA_SKIP_BACKWARD:
+        case AKEYCODE_MEDIA_STEP_FORWARD:
+        case AKEYCODE_MEDIA_STEP_BACKWARD:
+        case AKEYCODE_MEDIA_AUDIO_TRACK:
+        case AKEYCODE_VOLUME_UP:
+        case AKEYCODE_VOLUME_DOWN:
+        case AKEYCODE_VOLUME_MUTE:
+        case AKEYCODE_TV_AUDIO_DESCRIPTION:
+        case AKEYCODE_TV_AUDIO_DESCRIPTION_MIX_UP:
+        case AKEYCODE_TV_AUDIO_DESCRIPTION_MIX_DOWN:
+            return true;
+        default:
+            return false;
     }
-    return value;
-}
-
-// The mapping can be defined using input device configuration properties keyboard.rotated.stem_X
-static int32_t stemKeyRotationMap[][2] = {
-        // key codes enumerated with the original (unrotated) key first
-        // no rotation,           180 degree rotation
-        {AKEYCODE_STEM_PRIMARY, AKEYCODE_STEM_PRIMARY},
-        {AKEYCODE_STEM_1, AKEYCODE_STEM_1},
-        {AKEYCODE_STEM_2, AKEYCODE_STEM_2},
-        {AKEYCODE_STEM_3, AKEYCODE_STEM_3},
-};
-
-static const size_t stemKeyRotationMapSize =
-        sizeof(stemKeyRotationMap) / sizeof(stemKeyRotationMap[0]);
-
-static int32_t rotateKeyCode(int32_t keyCode, int32_t orientation) {
-    keyCode = rotateStemKey(keyCode, orientation, stemKeyRotationMap, stemKeyRotationMapSize);
-    return rotateValueUsingRotationMap(keyCode, orientation, keyCodeRotationMap,
-                                       keyCodeRotationMapSize);
 }
 
 // --- KeyboardInputMapper ---
@@ -93,17 +97,15 @@
                                          int32_t keyboardType)
       : InputMapper(deviceContext), mSource(source), mKeyboardType(keyboardType) {}
 
-KeyboardInputMapper::~KeyboardInputMapper() {}
-
 uint32_t KeyboardInputMapper::getSources() const {
     return mSource;
 }
 
-int32_t KeyboardInputMapper::getOrientation() {
+ui::Rotation KeyboardInputMapper::getOrientation() {
     if (mViewport) {
         return mViewport->orientation;
     }
-    return DISPLAY_ORIENTATION_0;
+    return ui::ROTATION_0;
 }
 
 int32_t KeyboardInputMapper::getDisplayId() {
@@ -130,7 +132,7 @@
 }
 
 std::optional<DisplayViewport> KeyboardInputMapper::findViewport(
-        nsecs_t when, const InputReaderConfiguration* config) {
+        const InputReaderConfiguration* config) {
     if (getDeviceContext().getAssociatedViewport()) {
         return getDeviceContext().getAssociatedViewport();
     }
@@ -154,35 +156,16 @@
     }
 
     if (!changes || (changes & InputReaderConfiguration::CHANGE_DISPLAY_INFO)) {
-        mViewport = findViewport(when, config);
+        mViewport = findViewport(config);
     }
     return out;
 }
 
-static void mapStemKey(int32_t keyCode, const PropertyMap& config, char const* property) {
-    int32_t mapped = 0;
-    if (config.tryGetProperty(property, mapped) && mapped > 0) {
-        for (size_t i = 0; i < stemKeyRotationMapSize; i++) {
-            if (stemKeyRotationMap[i][0] == keyCode) {
-                stemKeyRotationMap[i][1] = mapped;
-                return;
-            }
-        }
-    }
-}
-
 void KeyboardInputMapper::configureParameters() {
     mParameters.orientationAware = false;
     const PropertyMap& config = getDeviceContext().getConfiguration();
     config.tryGetProperty("keyboard.orientationAware", mParameters.orientationAware);
 
-    if (mParameters.orientationAware) {
-        mapStemKey(AKEYCODE_STEM_PRIMARY, config, "keyboard.rotated.stem_primary");
-        mapStemKey(AKEYCODE_STEM_1, config, "keyboard.rotated.stem_1");
-        mapStemKey(AKEYCODE_STEM_2, config, "keyboard.rotated.stem_2");
-        mapStemKey(AKEYCODE_STEM_3, config, "keyboard.rotated.stem_3");
-    }
-
     mParameters.handlesKeyRepeat = false;
     config.tryGetProperty("keyboard.handlesKeyRepeat", mParameters.handlesKeyRepeat);
 
@@ -190,7 +173,7 @@
     config.tryGetProperty("keyboard.doNotWakeByDefault", mParameters.doNotWakeByDefault);
 }
 
-void KeyboardInputMapper::dumpParameters(std::string& dump) {
+void KeyboardInputMapper::dumpParameters(std::string& dump) const {
     dump += INDENT3 "Parameters:\n";
     dump += StringPrintf(INDENT4 "OrientationAware: %s\n", toString(mParameters.orientationAware));
     dump += StringPrintf(INDENT4 "HandlesKeyRepeat: %s\n", toString(mParameters.handlesKeyRepeat));
@@ -198,7 +181,7 @@
 
 std::list<NotifyArgs> KeyboardInputMapper::reset(nsecs_t when) {
     std::list<NotifyArgs> out = cancelAllDownKeys(when);
-    mCurrentHidUsage = 0;
+    mHidUsageAccumulator.reset();
 
     resetLedState();
 
@@ -208,68 +191,21 @@
 
 std::list<NotifyArgs> KeyboardInputMapper::process(const RawEvent* rawEvent) {
     std::list<NotifyArgs> out;
+    mHidUsageAccumulator.process(*rawEvent);
     switch (rawEvent->type) {
         case EV_KEY: {
             int32_t scanCode = rawEvent->code;
-            int32_t usageCode = mCurrentHidUsage;
-            mCurrentHidUsage = 0;
 
-            if (isKeyboardOrGamepadKey(scanCode)) {
+            if (isSupportedScanCode(scanCode)) {
                 out += processKey(rawEvent->when, rawEvent->readTime, rawEvent->value != 0,
-                                  scanCode, usageCode);
+                                  scanCode, mHidUsageAccumulator.consumeCurrentHidUsage());
             }
             break;
         }
-        case EV_MSC: {
-            if (rawEvent->code == MSC_SCAN) {
-                mCurrentHidUsage = rawEvent->value;
-            }
-            break;
-        }
-        case EV_SYN: {
-            if (rawEvent->code == SYN_REPORT) {
-                mCurrentHidUsage = 0;
-            }
-        }
     }
     return out;
 }
 
-bool KeyboardInputMapper::isKeyboardOrGamepadKey(int32_t scanCode) {
-    return scanCode < BTN_MOUSE || scanCode >= BTN_WHEEL ||
-            (scanCode >= BTN_MISC && scanCode < BTN_MOUSE) ||
-            (scanCode >= BTN_JOYSTICK && scanCode < BTN_DIGI);
-}
-
-bool KeyboardInputMapper::isMediaKey(int32_t keyCode) {
-    switch (keyCode) {
-        case AKEYCODE_MEDIA_PLAY:
-        case AKEYCODE_MEDIA_PAUSE:
-        case AKEYCODE_MEDIA_PLAY_PAUSE:
-        case AKEYCODE_MUTE:
-        case AKEYCODE_HEADSETHOOK:
-        case AKEYCODE_MEDIA_STOP:
-        case AKEYCODE_MEDIA_NEXT:
-        case AKEYCODE_MEDIA_PREVIOUS:
-        case AKEYCODE_MEDIA_REWIND:
-        case AKEYCODE_MEDIA_RECORD:
-        case AKEYCODE_MEDIA_FAST_FORWARD:
-        case AKEYCODE_MEDIA_SKIP_FORWARD:
-        case AKEYCODE_MEDIA_SKIP_BACKWARD:
-        case AKEYCODE_MEDIA_STEP_FORWARD:
-        case AKEYCODE_MEDIA_STEP_BACKWARD:
-        case AKEYCODE_MEDIA_AUDIO_TRACK:
-        case AKEYCODE_VOLUME_UP:
-        case AKEYCODE_VOLUME_DOWN:
-        case AKEYCODE_VOLUME_MUTE:
-        case AKEYCODE_TV_AUDIO_DESCRIPTION:
-        case AKEYCODE_TV_AUDIO_DESCRIPTION_MIX_UP:
-        case AKEYCODE_TV_AUDIO_DESCRIPTION_MIX_DOWN:
-            return true;
-    }
-    return false;
-}
-
 std::list<NotifyArgs> KeyboardInputMapper::processKey(nsecs_t when, nsecs_t readTime, bool down,
                                                       int32_t scanCode, int32_t usageCode) {
     std::list<NotifyArgs> out;
@@ -285,6 +221,7 @@
     }
 
     nsecs_t downTime = when;
+    std::optional<size_t> keyDownIndex = findKeyDownIndex(scanCode);
     if (down) {
         // Rotate key codes according to orientation if needed.
         if (mParameters.orientationAware) {
@@ -292,11 +229,10 @@
         }
 
         // Add key down.
-        ssize_t keyDownIndex = findKeyDown(scanCode);
-        if (keyDownIndex >= 0) {
+        if (keyDownIndex) {
             // key repeat, be sure to use same keycode as before in case of rotation
-            keyCode = mKeyDowns[keyDownIndex].keyCode;
-            downTime = mKeyDowns[keyDownIndex].downTime;
+            keyCode = mKeyDowns[*keyDownIndex].keyCode;
+            downTime = mKeyDowns[*keyDownIndex].downTime;
         } else {
             // key down
             if ((policyFlags & POLICY_FLAG_VIRTUAL) &&
@@ -315,12 +251,11 @@
         }
     } else {
         // Remove key down.
-        ssize_t keyDownIndex = findKeyDown(scanCode);
-        if (keyDownIndex >= 0) {
+        if (keyDownIndex) {
             // key up, be sure to use same keycode as before in case of rotation
-            keyCode = mKeyDowns[keyDownIndex].keyCode;
-            downTime = mKeyDowns[keyDownIndex].downTime;
-            mKeyDowns.erase(mKeyDowns.begin() + (size_t)keyDownIndex);
+            keyCode = mKeyDowns[*keyDownIndex].keyCode;
+            downTime = mKeyDowns[*keyDownIndex].downTime;
+            mKeyDowns.erase(mKeyDowns.begin() + *keyDownIndex);
         } else {
             // key was not actually down
             ALOGI("Dropping key up from device %s because the key was not down.  "
@@ -353,22 +288,22 @@
         policyFlags |= POLICY_FLAG_DISABLE_KEY_REPEAT;
     }
 
-    out.push_back(NotifyKeyArgs(getContext()->getNextId(), when, readTime, getDeviceId(), mSource,
-                                getDisplayId(), policyFlags,
-                                down ? AKEY_EVENT_ACTION_DOWN : AKEY_EVENT_ACTION_UP,
-                                AKEY_EVENT_FLAG_FROM_SYSTEM, keyCode, scanCode, keyMetaState,
-                                downTime));
+    out.emplace_back(NotifyKeyArgs(getContext()->getNextId(), when, readTime, getDeviceId(),
+                                   mSource, getDisplayId(), policyFlags,
+                                   down ? AKEY_EVENT_ACTION_DOWN : AKEY_EVENT_ACTION_UP,
+                                   AKEY_EVENT_FLAG_FROM_SYSTEM, keyCode, scanCode, keyMetaState,
+                                   downTime));
     return out;
 }
 
-ssize_t KeyboardInputMapper::findKeyDown(int32_t scanCode) {
+std::optional<size_t> KeyboardInputMapper::findKeyDownIndex(int32_t scanCode) {
     size_t n = mKeyDowns.size();
     for (size_t i = 0; i < n; i++) {
         if (mKeyDowns[i].scanCode == scanCode) {
             return i;
         }
     }
-    return -1;
+    return {};
 }
 
 int32_t KeyboardInputMapper::getKeyCodeState(uint32_t sourceMask, int32_t keyCode) {
@@ -481,12 +416,12 @@
     std::list<NotifyArgs> out;
     size_t n = mKeyDowns.size();
     for (size_t i = 0; i < n; i++) {
-        out.push_back(NotifyKeyArgs(getContext()->getNextId(), when,
-                                    systemTime(SYSTEM_TIME_MONOTONIC), getDeviceId(), mSource,
-                                    getDisplayId(), 0 /*policyFlags*/, AKEY_EVENT_ACTION_UP,
-                                    AKEY_EVENT_FLAG_FROM_SYSTEM | AKEY_EVENT_FLAG_CANCELED,
-                                    mKeyDowns[i].keyCode, mKeyDowns[i].scanCode, AMETA_NONE,
-                                    mKeyDowns[i].downTime));
+        out.emplace_back(NotifyKeyArgs(getContext()->getNextId(), when,
+                                       systemTime(SYSTEM_TIME_MONOTONIC), getDeviceId(), mSource,
+                                       getDisplayId(), 0 /*policyFlags*/, AKEY_EVENT_ACTION_UP,
+                                       AKEY_EVENT_FLAG_FROM_SYSTEM | AKEY_EVENT_FLAG_CANCELED,
+                                       mKeyDowns[i].keyCode, mKeyDowns[i].scanCode, AMETA_NONE,
+                                       mKeyDowns[i].downTime));
     }
     mKeyDowns.clear();
     mMetaState = AMETA_NONE;
diff --git a/services/inputflinger/reader/mapper/KeyboardInputMapper.h b/services/inputflinger/reader/mapper/KeyboardInputMapper.h
index 8d72ee9..0526fd8 100644
--- a/services/inputflinger/reader/mapper/KeyboardInputMapper.h
+++ b/services/inputflinger/reader/mapper/KeyboardInputMapper.h
@@ -16,6 +16,7 @@
 
 #pragma once
 
+#include "HidUsageAccumulator.h"
 #include "InputMapper.h"
 
 namespace android {
@@ -23,7 +24,7 @@
 class KeyboardInputMapper : public InputMapper {
 public:
     KeyboardInputMapper(InputDeviceContext& deviceContext, uint32_t source, int32_t keyboardType);
-    virtual ~KeyboardInputMapper();
+    ~KeyboardInputMapper() override = default;
 
     uint32_t getSources() const override;
     void populateDeviceInfo(InputDeviceInfo* deviceInfo) override;
@@ -47,58 +48,54 @@
 
 private:
     // The current viewport.
-    std::optional<DisplayViewport> mViewport;
+    std::optional<DisplayViewport> mViewport{};
 
     struct KeyDown {
-        nsecs_t downTime;
-        int32_t keyCode;
-        int32_t scanCode;
+        nsecs_t downTime{};
+        int32_t keyCode{};
+        int32_t scanCode{};
     };
 
-    uint32_t mSource;
-    int32_t mKeyboardType;
+    uint32_t mSource{};
+    int32_t mKeyboardType{};
 
-    std::vector<KeyDown> mKeyDowns; // keys that are down
-    int32_t mMetaState;
+    std::vector<KeyDown> mKeyDowns{}; // keys that are down
+    int32_t mMetaState{};
 
-    int32_t mCurrentHidUsage; // most recent HID usage seen this packet, or 0 if none
+    HidUsageAccumulator mHidUsageAccumulator;
 
     struct LedState {
-        bool avail; // led is available
-        bool on;    // we think the led is currently on
+        bool avail{}; // led is available
+        bool on{};    // we think the led is currently on
     };
-    LedState mCapsLockLedState;
-    LedState mNumLockLedState;
-    LedState mScrollLockLedState;
+    LedState mCapsLockLedState{};
+    LedState mNumLockLedState{};
+    LedState mScrollLockLedState{};
 
     // Immutable configuration parameters.
     struct Parameters {
-        bool orientationAware;
-        bool handlesKeyRepeat;
-        bool doNotWakeByDefault;
-    } mParameters;
+        bool orientationAware{};
+        bool handlesKeyRepeat{};
+        bool doNotWakeByDefault{};
+    } mParameters{};
 
     void configureParameters();
-    void dumpParameters(std::string& dump);
+    void dumpParameters(std::string& dump) const;
 
-    int32_t getOrientation();
+    ui::Rotation getOrientation();
     int32_t getDisplayId();
 
-    bool isKeyboardOrGamepadKey(int32_t scanCode);
-    bool isMediaKey(int32_t keyCode);
-
     [[nodiscard]] std::list<NotifyArgs> processKey(nsecs_t when, nsecs_t readTime, bool down,
                                                    int32_t scanCode, int32_t usageCode);
 
     bool updateMetaStateIfNeeded(int32_t keyCode, bool down);
 
-    ssize_t findKeyDown(int32_t scanCode);
+    std::optional<size_t> findKeyDownIndex(int32_t scanCode);
 
     void resetLedState();
     void initializeLedState(LedState& ledState, int32_t led);
     void updateLedStateForModifier(LedState& ledState, int32_t led, int32_t modifier, bool reset);
-    std::optional<DisplayViewport> findViewport(nsecs_t when,
-                                                const InputReaderConfiguration* config);
+    std::optional<DisplayViewport> findViewport(const InputReaderConfiguration* config);
     [[nodiscard]] std::list<NotifyArgs> cancelAllDownKeys(nsecs_t when);
 };
 
diff --git a/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp b/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp
index 1d53eab..b193dff 100644
--- a/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp
@@ -17,8 +17,9 @@
 #include "../Macros.h"
 
 #include "MultiTouchInputMapper.h"
-
+#if defined(__ANDROID__)
 #include <android/sysprop/InputProperties.sysprop.h>
+#endif
 
 namespace android {
 
@@ -27,163 +28,6 @@
 // Maximum number of slots supported when using the slot-based Multitouch Protocol B.
 static constexpr size_t MAX_SLOTS = 32;
 
-// --- MultiTouchMotionAccumulator ---
-
-MultiTouchMotionAccumulator::MultiTouchMotionAccumulator()
-      : mCurrentSlot(-1),
-        mUsingSlotsProtocol(false),
-        mHaveStylus(false) {}
-
-void MultiTouchMotionAccumulator::configure(InputDeviceContext& deviceContext, size_t slotCount,
-                                            bool usingSlotsProtocol) {
-    mUsingSlotsProtocol = usingSlotsProtocol;
-    mHaveStylus = deviceContext.hasAbsoluteAxis(ABS_MT_TOOL_TYPE);
-    mSlots = std::vector<Slot>(slotCount);
-
-    mCurrentSlot = -1;
-    if (mUsingSlotsProtocol) {
-        // Query the driver for the current slot index and use it as the initial slot
-        // before we start reading events from the device.  It is possible that the
-        // current slot index will not be the same as it was when the first event was
-        // written into the evdev buffer, which means the input mapper could start
-        // out of sync with the initial state of the events in the evdev buffer.
-        // In the extremely unlikely case that this happens, the data from
-        // two slots will be confused until the next ABS_MT_SLOT event is received.
-        // This can cause the touch point to "jump", but at least there will be
-        // no stuck touches.
-        int32_t initialSlot;
-        if (const auto status = deviceContext.getAbsoluteAxisValue(ABS_MT_SLOT, &initialSlot);
-            status == OK) {
-            mCurrentSlot = initialSlot;
-        } else {
-            ALOGD("Could not retrieve current multi-touch slot index. status=%d", status);
-        }
-    }
-}
-
-void MultiTouchMotionAccumulator::resetSlots() {
-    for (Slot& slot : mSlots) {
-        slot.clear();
-    }
-    mCurrentSlot = -1;
-}
-
-void MultiTouchMotionAccumulator::process(const RawEvent* rawEvent) {
-    if (rawEvent->type == EV_ABS) {
-        bool newSlot = false;
-        if (mUsingSlotsProtocol) {
-            if (rawEvent->code == ABS_MT_SLOT) {
-                mCurrentSlot = rawEvent->value;
-                newSlot = true;
-            }
-        } else if (mCurrentSlot < 0) {
-            mCurrentSlot = 0;
-        }
-
-        if (mCurrentSlot < 0 || size_t(mCurrentSlot) >= mSlots.size()) {
-            if (DEBUG_POINTERS) {
-                if (newSlot) {
-                    ALOGW("MultiTouch device emitted invalid slot index %d but it "
-                          "should be between 0 and %zd; ignoring this slot.",
-                          mCurrentSlot, mSlots.size() - 1);
-                }
-            }
-        } else {
-            Slot& slot = mSlots[mCurrentSlot];
-            // If mUsingSlotsProtocol is true, it means the raw pointer has axis info of
-            // ABS_MT_TRACKING_ID and ABS_MT_SLOT, so driver should send a valid trackingId while
-            // updating the slot.
-            if (!mUsingSlotsProtocol) {
-                slot.mInUse = true;
-            }
-
-            switch (rawEvent->code) {
-                case ABS_MT_POSITION_X:
-                    slot.mAbsMTPositionX = rawEvent->value;
-                    warnIfNotInUse(*rawEvent, slot);
-                    break;
-                case ABS_MT_POSITION_Y:
-                    slot.mAbsMTPositionY = rawEvent->value;
-                    warnIfNotInUse(*rawEvent, slot);
-                    break;
-                case ABS_MT_TOUCH_MAJOR:
-                    slot.mAbsMTTouchMajor = rawEvent->value;
-                    break;
-                case ABS_MT_TOUCH_MINOR:
-                    slot.mAbsMTTouchMinor = rawEvent->value;
-                    slot.mHaveAbsMTTouchMinor = true;
-                    break;
-                case ABS_MT_WIDTH_MAJOR:
-                    slot.mAbsMTWidthMajor = rawEvent->value;
-                    break;
-                case ABS_MT_WIDTH_MINOR:
-                    slot.mAbsMTWidthMinor = rawEvent->value;
-                    slot.mHaveAbsMTWidthMinor = true;
-                    break;
-                case ABS_MT_ORIENTATION:
-                    slot.mAbsMTOrientation = rawEvent->value;
-                    break;
-                case ABS_MT_TRACKING_ID:
-                    if (mUsingSlotsProtocol && rawEvent->value < 0) {
-                        // The slot is no longer in use but it retains its previous contents,
-                        // which may be reused for subsequent touches.
-                        slot.mInUse = false;
-                    } else {
-                        slot.mInUse = true;
-                        slot.mAbsMTTrackingId = rawEvent->value;
-                    }
-                    break;
-                case ABS_MT_PRESSURE:
-                    slot.mAbsMTPressure = rawEvent->value;
-                    break;
-                case ABS_MT_DISTANCE:
-                    slot.mAbsMTDistance = rawEvent->value;
-                    break;
-                case ABS_MT_TOOL_TYPE:
-                    slot.mAbsMTToolType = rawEvent->value;
-                    slot.mHaveAbsMTToolType = true;
-                    break;
-            }
-        }
-    } else if (rawEvent->type == EV_SYN && rawEvent->code == SYN_MT_REPORT) {
-        // MultiTouch Sync: The driver has returned all data for *one* of the pointers.
-        mCurrentSlot += 1;
-    }
-}
-
-void MultiTouchMotionAccumulator::finishSync() {
-    if (!mUsingSlotsProtocol) {
-        resetSlots();
-    }
-}
-
-bool MultiTouchMotionAccumulator::hasStylus() const {
-    return mHaveStylus;
-}
-
-void MultiTouchMotionAccumulator::warnIfNotInUse(const RawEvent& event, const Slot& slot) {
-    if (!slot.mInUse) {
-        ALOGW("Received unexpected event (0x%0x, 0x%0x) for slot %i with tracking id %i",
-              event.code, event.value, mCurrentSlot, slot.mAbsMTTrackingId);
-    }
-}
-
-// --- MultiTouchMotionAccumulator::Slot ---
-
-int32_t MultiTouchMotionAccumulator::Slot::getToolType() const {
-    if (mHaveAbsMTToolType) {
-        switch (mAbsMTToolType) {
-            case MT_TOOL_FINGER:
-                return AMOTION_EVENT_TOOL_TYPE_FINGER;
-            case MT_TOOL_PEN:
-                return AMOTION_EVENT_TOOL_TYPE_STYLUS;
-            case MT_TOOL_PALM:
-                return AMOTION_EVENT_TOOL_TYPE_PALM;
-        }
-    }
-    return AMOTION_EVENT_TOOL_TYPE_UNKNOWN;
-}
-
 // --- MultiTouchInputMapper ---
 
 MultiTouchInputMapper::MultiTouchInputMapper(InputDeviceContext& deviceContext)
@@ -275,6 +119,18 @@
             if (outPointer.toolType == AMOTION_EVENT_TOOL_TYPE_UNKNOWN) {
                 outPointer.toolType = AMOTION_EVENT_TOOL_TYPE_FINGER;
             }
+        } else if (outPointer.toolType == AMOTION_EVENT_TOOL_TYPE_STYLUS && !mStylusMtToolSeen) {
+            mStylusMtToolSeen = true;
+            // The multi-touch device produced a stylus event with MT_TOOL_PEN. Dynamically
+            // re-configure this input device so that we add SOURCE_STYLUS if we haven't already.
+            // This is to cover the case where we cannot reliably detect whether a multi-touch
+            // device will ever produce stylus events when it is initially being configured.
+            if (!isFromSource(mSource, AINPUT_SOURCE_STYLUS)) {
+                // Add the stylus source immediately so that it is included in any events generated
+                // before we have a chance to re-configure the device.
+                mSource |= AINPUT_SOURCE_STYLUS;
+                bumpGeneration();
+            }
         }
         if (shouldSimulateStylusWithTouch() &&
             outPointer.toolType == AMOTION_EVENT_TOOL_TYPE_FINGER) {
@@ -356,13 +212,18 @@
 }
 
 bool MultiTouchInputMapper::hasStylus() const {
-    return mMultiTouchMotionAccumulator.hasStylus() || mTouchButtonAccumulator.hasStylus() ||
+    return mStylusMtToolSeen || mTouchButtonAccumulator.hasStylus() ||
             shouldSimulateStylusWithTouch();
 }
 
 bool MultiTouchInputMapper::shouldSimulateStylusWithTouch() const {
     static const bool SIMULATE_STYLUS_WITH_TOUCH =
+#if defined(__ANDROID__)
             sysprop::InputProperties::simulate_stylus_with_touch().value_or(false);
+#else
+            // Disable this developer feature where sysproperties are not available
+            false;
+#endif
     return SIMULATE_STYLUS_WITH_TOUCH &&
             mParameters.deviceType == Parameters::DeviceType::TOUCH_SCREEN;
 }
diff --git a/services/inputflinger/reader/mapper/MultiTouchInputMapper.h b/services/inputflinger/reader/mapper/MultiTouchInputMapper.h
index 047e62d..5f8bccf 100644
--- a/services/inputflinger/reader/mapper/MultiTouchInputMapper.h
+++ b/services/inputflinger/reader/mapper/MultiTouchInputMapper.h
@@ -17,77 +17,10 @@
 #pragma once
 
 #include "TouchInputMapper.h"
+#include "accumulator/MultiTouchMotionAccumulator.h"
 
 namespace android {
 
-/* Keeps track of the state of multi-touch protocol. */
-class MultiTouchMotionAccumulator {
-public:
-    class Slot {
-    public:
-        inline bool isInUse() const { return mInUse; }
-        inline int32_t getX() const { return mAbsMTPositionX; }
-        inline int32_t getY() const { return mAbsMTPositionY; }
-        inline int32_t getTouchMajor() const { return mAbsMTTouchMajor; }
-        inline int32_t getTouchMinor() const {
-            return mHaveAbsMTTouchMinor ? mAbsMTTouchMinor : mAbsMTTouchMajor;
-        }
-        inline int32_t getToolMajor() const { return mAbsMTWidthMajor; }
-        inline int32_t getToolMinor() const {
-            return mHaveAbsMTWidthMinor ? mAbsMTWidthMinor : mAbsMTWidthMajor;
-        }
-        inline int32_t getOrientation() const { return mAbsMTOrientation; }
-        inline int32_t getTrackingId() const { return mAbsMTTrackingId; }
-        inline int32_t getPressure() const { return mAbsMTPressure; }
-        inline int32_t getDistance() const { return mAbsMTDistance; }
-        inline int32_t getToolType() const;
-
-    private:
-        friend class MultiTouchMotionAccumulator;
-
-        bool mInUse = false;
-        bool mHaveAbsMTTouchMinor = false;
-        bool mHaveAbsMTWidthMinor = false;
-        bool mHaveAbsMTToolType = false;
-
-        int32_t mAbsMTPositionX = 0;
-        int32_t mAbsMTPositionY = 0;
-        int32_t mAbsMTTouchMajor = 0;
-        int32_t mAbsMTTouchMinor = 0;
-        int32_t mAbsMTWidthMajor = 0;
-        int32_t mAbsMTWidthMinor = 0;
-        int32_t mAbsMTOrientation = 0;
-        int32_t mAbsMTTrackingId = -1;
-        int32_t mAbsMTPressure = 0;
-        int32_t mAbsMTDistance = 0;
-        int32_t mAbsMTToolType = 0;
-
-        void clear() { *this = Slot(); }
-    };
-
-    MultiTouchMotionAccumulator();
-
-    void configure(InputDeviceContext& deviceContext, size_t slotCount, bool usingSlotsProtocol);
-    void process(const RawEvent* rawEvent);
-    void finishSync();
-    bool hasStylus() 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);
-        return mSlots[index];
-    }
-
-private:
-    int32_t mCurrentSlot;
-    std::vector<Slot> mSlots;
-    bool mUsingSlotsProtocol;
-    bool mHaveStylus;
-
-    void resetSlots();
-    void warnIfNotInUse(const RawEvent& event, const Slot& slot);
-};
-
 class MultiTouchInputMapper : public TouchInputMapper {
 public:
     explicit MultiTouchInputMapper(InputDeviceContext& deviceContext);
@@ -117,6 +50,8 @@
     // Specifies the pointer id bits that are in use, and their associated tracking id.
     BitSet32 mPointerIdBits;
     int32_t mPointerTrackingIdMap[MAX_POINTER_ID + 1];
+
+    bool mStylusMtToolSeen{false};
 };
 
 } // namespace android
diff --git a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp
index 29a1bda..19a79d7 100644
--- a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp
@@ -25,7 +25,7 @@
 namespace android {
 
 RotaryEncoderInputMapper::RotaryEncoderInputMapper(InputDeviceContext& deviceContext)
-      : InputMapper(deviceContext), mOrientation(DISPLAY_ORIENTATION_0) {
+      : InputMapper(deviceContext), mOrientation(ui::ROTATION_0) {
     mSource = AINPUT_SOURCE_ROTARY_ENCODER;
 }
 
@@ -73,7 +73,7 @@
         if (internalViewport) {
             mOrientation = internalViewport->orientation;
         } else {
-            mOrientation = DISPLAY_ORIENTATION_0;
+            mOrientation = ui::ROTATION_0;
         }
     }
     return out;
@@ -97,35 +97,34 @@
 
 std::list<NotifyArgs> RotaryEncoderInputMapper::sync(nsecs_t when, nsecs_t readTime) {
     std::list<NotifyArgs> out;
-    PointerCoords pointerCoords;
-    pointerCoords.clear();
-
-    PointerProperties pointerProperties;
-    pointerProperties.clear();
-    pointerProperties.id = 0;
-    pointerProperties.toolType = AMOTION_EVENT_TOOL_TYPE_UNKNOWN;
 
     float scroll = mRotaryEncoderScrollAccumulator.getRelativeVWheel();
     bool scrolled = scroll != 0;
 
-    // This is not a pointer, so it's not associated with a display.
-    int32_t displayId = ADISPLAY_ID_NONE;
-
-    // Moving the rotary encoder should wake the device (if specified).
-    uint32_t policyFlags = 0;
-    if (scrolled && getDeviceContext().isExternal()) {
-        policyFlags |= POLICY_FLAG_WAKE;
-    }
-
-    if (mOrientation == DISPLAY_ORIENTATION_180) {
-        scroll = -scroll;
-    }
-
     // Send motion event.
     if (scrolled) {
         int32_t metaState = getContext()->getGlobalMetaState();
+        // This is not a pointer, so it's not associated with a display.
+        int32_t displayId = ADISPLAY_ID_NONE;
+
+        if (mOrientation == ui::ROTATION_180) {
+            scroll = -scroll;
+        }
+
+        PointerCoords pointerCoords;
+        pointerCoords.clear();
         pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_SCROLL, scroll * mScalingFactor);
 
+        PointerProperties pointerProperties;
+        pointerProperties.clear();
+        pointerProperties.id = 0;
+        pointerProperties.toolType = AMOTION_EVENT_TOOL_TYPE_UNKNOWN;
+
+        uint32_t policyFlags = 0;
+        if (getDeviceContext().isExternal()) {
+            policyFlags |= POLICY_FLAG_WAKE;
+        }
+
         out.push_back(
                 NotifyMotionArgs(getContext()->getNextId(), when, readTime, getDeviceId(), mSource,
                                  displayId, policyFlags, AMOTION_EVENT_ACTION_SCROLL, 0, 0,
diff --git a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.h b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.h
index f4352e7..cb5fd88 100644
--- a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.h
+++ b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.h
@@ -16,6 +16,8 @@
 
 #pragma once
 
+#include <ui/Rotation.h>
+
 #include "CursorScrollAccumulator.h"
 #include "InputMapper.h"
 
@@ -40,7 +42,7 @@
 
     int32_t mSource;
     float mScalingFactor;
-    int32_t mOrientation;
+    ui::Rotation mOrientation;
 
     [[nodiscard]] std::list<NotifyArgs> sync(nsecs_t when, nsecs_t readTime);
 };
diff --git a/services/inputflinger/reader/mapper/TouchCursorInputMapperCommon.h b/services/inputflinger/reader/mapper/TouchCursorInputMapperCommon.h
index 5a7ba9a..1c3ca97 100644
--- a/services/inputflinger/reader/mapper/TouchCursorInputMapperCommon.h
+++ b/services/inputflinger/reader/mapper/TouchCursorInputMapperCommon.h
@@ -18,6 +18,7 @@
 
 #include <input/DisplayViewport.h>
 #include <stdint.h>
+#include <ui/Rotation.h>
 
 #include "EventHub.h"
 #include "InputListener.h"
@@ -27,32 +28,32 @@
 
 // --- Static Definitions ---
 
-static int32_t getInverseRotation(int32_t orientation) {
+static ui::Rotation getInverseRotation(ui::Rotation orientation) {
     switch (orientation) {
-        case DISPLAY_ORIENTATION_90:
-            return DISPLAY_ORIENTATION_270;
-        case DISPLAY_ORIENTATION_270:
-            return DISPLAY_ORIENTATION_90;
+        case ui::ROTATION_90:
+            return ui::ROTATION_270;
+        case ui::ROTATION_270:
+            return ui::ROTATION_90;
         default:
             return orientation;
     }
 }
 
-static void rotateDelta(int32_t orientation, float* deltaX, float* deltaY) {
+static void rotateDelta(ui::Rotation orientation, float* deltaX, float* deltaY) {
     float temp;
     switch (orientation) {
-        case DISPLAY_ORIENTATION_90:
+        case ui::ROTATION_90:
             temp = *deltaX;
             *deltaX = *deltaY;
             *deltaY = -temp;
             break;
 
-        case DISPLAY_ORIENTATION_180:
+        case ui::ROTATION_180:
             *deltaX = -*deltaX;
             *deltaY = -*deltaY;
             break;
 
-        case DISPLAY_ORIENTATION_270:
+        case ui::ROTATION_270:
             temp = *deltaX;
             *deltaX = -*deltaY;
             *deltaY = temp;
@@ -101,4 +102,37 @@
     return out;
 }
 
+// For devices connected over Bluetooth, although they may produce events at a consistent rate,
+// the events might end up reaching Android in a "batched" manner through the Bluetooth
+// stack, where a few events may be clumped together and processed around the same time.
+// In this case, if the input device or its driver does not send or process the actual event
+// generation timestamps, the event time will set to whenever the kernel received the event.
+// When the timestamp deltas are minuscule for these batched events, any changes in x or y
+// coordinates result in extremely large instantaneous velocities, which can negatively impact
+// user experience. To avoid this, we augment the timestamps so that subsequent event timestamps
+// differ by at least a minimum delta value.
+static std::tuple<nsecs_t /*eventTime*/, nsecs_t /*readTime*/> applyBluetoothTimestampSmoothening(
+        const InputDeviceIdentifier& identifier, nsecs_t currentEventTime, nsecs_t readTime,
+        nsecs_t lastEventTime) {
+    if (identifier.bus != BUS_BLUETOOTH) {
+        return {currentEventTime, readTime};
+    }
+
+    // Assume the fastest rate at which a Bluetooth touch device can report input events is one
+    // every 4 milliseconds, or 250 Hz. Timestamps for successive events from a Bluetooth device
+    // will be separated by at least this amount.
+    constexpr static nsecs_t MIN_BLUETOOTH_TIMESTAMP_DELTA = ms2ns(4);
+    // We define a 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);
+    const nsecs_t smoothenedEventTime =
+            std::min(std::max(currentEventTime, lastEventTime + MIN_BLUETOOTH_TIMESTAMP_DELTA),
+                     currentEventTime + MAX_BLUETOOTH_SMOOTHING_DELTA);
+    // If we are modifying the event time, treat this event as a synthetically generated event for
+    // latency tracking purposes and use the event time as the read time (zero read latency).
+    const nsecs_t smoothenedReadTime =
+            smoothenedEventTime != currentEventTime ? currentEventTime : readTime;
+    return {smoothenedEventTime, smoothenedReadTime};
+}
+
 } // namespace android
diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
index 7f6785e..cefc44e 100644
--- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
@@ -21,23 +21,18 @@
 #include "TouchInputMapper.h"
 
 #include <ftl/enum.h>
+#include <input/PrintTools.h>
 
 #include "CursorButtonAccumulator.h"
 #include "CursorScrollAccumulator.h"
 #include "TouchButtonAccumulator.h"
 #include "TouchCursorInputMapperCommon.h"
+#include "ui/Rotation.h"
 
 namespace android {
 
 // --- Constants ---
 
-// Maximum amount of latency to add to touch events while waiting for data from an
-// external stylus.
-static constexpr nsecs_t EXTERNAL_STYLUS_DATA_TIMEOUT = ms2ns(72);
-
-// Maximum amount of time to wait on touch data before pushing out new pressure data.
-static constexpr nsecs_t TOUCH_DATA_TIMEOUT = ms2ns(20);
-
 // Artificial latency on synthetic events created from stylus data without corresponding touch
 // data.
 static constexpr nsecs_t STYLUS_DATA_LATENCY = ms2ns(10);
@@ -48,6 +43,19 @@
 
 static const DisplayViewport kUninitializedViewport;
 
+static std::string toString(const Rect& rect) {
+    return base::StringPrintf("Rect{%d, %d, %d, %d}", rect.left, rect.top, rect.right, rect.bottom);
+}
+
+static std::string toString(const ui::Size& size) {
+    return base::StringPrintf("%dx%d", size.width, size.height);
+}
+
+static bool isPointInRect(const Rect& rect, int32_t x, int32_t y) {
+    // Consider all four sides as "inclusive".
+    return x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom;
+}
+
 template <typename T>
 inline static void swap(T& a, T& b) {
     T temp = a;
@@ -73,53 +81,33 @@
     return value >= 8 ? value - 16 : value;
 }
 
-// --- RawPointerAxes ---
+static std::tuple<ui::Size /*displayBounds*/, Rect /*physicalFrame*/> getNaturalDisplayInfo(
+        const DisplayViewport& viewport, ui::Rotation naturalOrientation) {
+    ui::Size rotatedDisplaySize{viewport.deviceWidth, viewport.deviceHeight};
+    if (naturalOrientation == ui::ROTATION_90 || naturalOrientation == ui::ROTATION_270) {
+        std::swap(rotatedDisplaySize.width, rotatedDisplaySize.height);
+    }
 
-RawPointerAxes::RawPointerAxes() {
-    clear();
-}
+    ui::Transform rotate(ui::Transform::toRotationFlags(naturalOrientation),
+                         rotatedDisplaySize.width, rotatedDisplaySize.height);
 
-void RawPointerAxes::clear() {
-    x.clear();
-    y.clear();
-    pressure.clear();
-    touchMajor.clear();
-    touchMinor.clear();
-    toolMajor.clear();
-    toolMinor.clear();
-    orientation.clear();
-    distance.clear();
-    tiltX.clear();
-    tiltY.clear();
-    trackingId.clear();
-    slot.clear();
+    Rect physicalFrame{viewport.physicalLeft, viewport.physicalTop, viewport.physicalRight,
+                       viewport.physicalBottom};
+    physicalFrame = rotate.transform(physicalFrame);
+
+    LOG_ALWAYS_FATAL_IF(!physicalFrame.isValid());
+    if (physicalFrame.isEmpty()) {
+        ALOGE("Viewport is not set properly: %s", viewport.toString().c_str());
+        physicalFrame.right =
+                physicalFrame.left + (physicalFrame.width() == 0 ? 1 : physicalFrame.width());
+        physicalFrame.bottom =
+                physicalFrame.top + (physicalFrame.height() == 0 ? 1 : physicalFrame.height());
+    }
+    return {rotatedDisplaySize, physicalFrame};
 }
 
 // --- RawPointerData ---
 
-RawPointerData::RawPointerData() {
-    clear();
-}
-
-void RawPointerData::clear() {
-    pointerCount = 0;
-    clearIdBits();
-}
-
-void RawPointerData::copyFrom(const RawPointerData& other) {
-    pointerCount = other.pointerCount;
-    hoveringIdBits = other.hoveringIdBits;
-    touchingIdBits = other.touchingIdBits;
-    canceledIdBits = other.canceledIdBits;
-
-    for (uint32_t i = 0; i < pointerCount; i++) {
-        pointers[i] = other.pointers[i];
-
-        int id = pointers[i].id;
-        idToIndex[id] = other.idToIndex[id];
-    }
-}
-
 void RawPointerData::getCentroidOfTouchingPointers(float* outX, float* outY) const {
     float x = 0, y = 0;
     uint32_t count = touchingIdBits.count();
@@ -137,48 +125,14 @@
     *outY = y;
 }
 
-// --- CookedPointerData ---
-
-CookedPointerData::CookedPointerData() {
-    clear();
-}
-
-void CookedPointerData::clear() {
-    pointerCount = 0;
-    hoveringIdBits.clear();
-    touchingIdBits.clear();
-    canceledIdBits.clear();
-    validIdBits.clear();
-}
-
-void CookedPointerData::copyFrom(const CookedPointerData& other) {
-    pointerCount = other.pointerCount;
-    hoveringIdBits = other.hoveringIdBits;
-    touchingIdBits = other.touchingIdBits;
-    validIdBits = other.validIdBits;
-
-    for (uint32_t i = 0; i < pointerCount; i++) {
-        pointerProperties[i].copyFrom(other.pointerProperties[i]);
-        pointerCoords[i].copyFrom(other.pointerCoords[i]);
-
-        int id = pointerProperties[i].id;
-        idToIndex[id] = other.idToIndex[id];
-    }
-}
-
 // --- TouchInputMapper ---
 
 TouchInputMapper::TouchInputMapper(InputDeviceContext& deviceContext)
       : InputMapper(deviceContext),
+        mTouchButtonAccumulator(deviceContext),
         mSource(0),
         mDeviceMode(DeviceMode::DISABLED),
-        mDisplayWidth(-1),
-        mDisplayHeight(-1),
-        mPhysicalWidth(-1),
-        mPhysicalHeight(-1),
-        mPhysicalLeft(0),
-        mPhysicalTop(0),
-        mInputDeviceOrientation(DISPLAY_ORIENTATION_0) {}
+        mInputDeviceOrientation(ui::ROTATION_0) {}
 
 TouchInputMapper::~TouchInputMapper() {}
 
@@ -189,73 +143,74 @@
 void TouchInputMapper::populateDeviceInfo(InputDeviceInfo* info) {
     InputMapper::populateDeviceInfo(info);
 
-    if (mDeviceMode != DeviceMode::DISABLED) {
-        info->addMotionRange(mOrientedRanges.x);
-        info->addMotionRange(mOrientedRanges.y);
-        info->addMotionRange(mOrientedRanges.pressure);
-
-        if (mDeviceMode == DeviceMode::UNSCALED && mSource == AINPUT_SOURCE_TOUCHPAD) {
-            // Populate RELATIVE_X and RELATIVE_Y motion ranges for touchpad capture mode.
-            //
-            // RELATIVE_X and RELATIVE_Y motion ranges should be the largest possible relative
-            // motion, i.e. the hardware dimensions, as the finger could move completely across the
-            // touchpad in one sample cycle.
-            const InputDeviceInfo::MotionRange& x = mOrientedRanges.x;
-            const InputDeviceInfo::MotionRange& y = mOrientedRanges.y;
-            info->addMotionRange(AMOTION_EVENT_AXIS_RELATIVE_X, mSource, -x.max, x.max, x.flat,
-                                 x.fuzz, x.resolution);
-            info->addMotionRange(AMOTION_EVENT_AXIS_RELATIVE_Y, mSource, -y.max, y.max, y.flat,
-                                 y.fuzz, y.resolution);
-        }
-
-        if (mOrientedRanges.size) {
-            info->addMotionRange(*mOrientedRanges.size);
-        }
-
-        if (mOrientedRanges.touchMajor) {
-            info->addMotionRange(*mOrientedRanges.touchMajor);
-            info->addMotionRange(*mOrientedRanges.touchMinor);
-        }
-
-        if (mOrientedRanges.toolMajor) {
-            info->addMotionRange(*mOrientedRanges.toolMajor);
-            info->addMotionRange(*mOrientedRanges.toolMinor);
-        }
-
-        if (mOrientedRanges.orientation) {
-            info->addMotionRange(*mOrientedRanges.orientation);
-        }
-
-        if (mOrientedRanges.distance) {
-            info->addMotionRange(*mOrientedRanges.distance);
-        }
-
-        if (mOrientedRanges.tilt) {
-            info->addMotionRange(*mOrientedRanges.tilt);
-        }
-
-        if (mCursorScrollAccumulator.haveRelativeVWheel()) {
-            info->addMotionRange(AMOTION_EVENT_AXIS_VSCROLL, mSource, -1.0f, 1.0f, 0.0f, 0.0f,
-                                 0.0f);
-        }
-        if (mCursorScrollAccumulator.haveRelativeHWheel()) {
-            info->addMotionRange(AMOTION_EVENT_AXIS_HSCROLL, mSource, -1.0f, 1.0f, 0.0f, 0.0f,
-                                 0.0f);
-        }
-        if (mCalibration.coverageCalibration == Calibration::CoverageCalibration::BOX) {
-            const InputDeviceInfo::MotionRange& x = mOrientedRanges.x;
-            const InputDeviceInfo::MotionRange& y = mOrientedRanges.y;
-            info->addMotionRange(AMOTION_EVENT_AXIS_GENERIC_1, mSource, x.min, x.max, x.flat,
-                                 x.fuzz, x.resolution);
-            info->addMotionRange(AMOTION_EVENT_AXIS_GENERIC_2, mSource, y.min, y.max, y.flat,
-                                 y.fuzz, y.resolution);
-            info->addMotionRange(AMOTION_EVENT_AXIS_GENERIC_3, mSource, x.min, x.max, x.flat,
-                                 x.fuzz, x.resolution);
-            info->addMotionRange(AMOTION_EVENT_AXIS_GENERIC_4, mSource, y.min, y.max, y.flat,
-                                 y.fuzz, y.resolution);
-        }
-        info->setButtonUnderPad(mParameters.hasButtonUnderPad);
+    if (mDeviceMode == DeviceMode::DISABLED) {
+        return;
     }
+
+    info->addMotionRange(mOrientedRanges.x);
+    info->addMotionRange(mOrientedRanges.y);
+    info->addMotionRange(mOrientedRanges.pressure);
+
+    if (mDeviceMode == DeviceMode::UNSCALED && mSource == AINPUT_SOURCE_TOUCHPAD) {
+        // Populate RELATIVE_X and RELATIVE_Y motion ranges for touchpad capture mode.
+        //
+        // RELATIVE_X and RELATIVE_Y motion ranges should be the largest possible relative
+        // motion, i.e. the hardware dimensions, as the finger could move completely across the
+        // touchpad in one sample cycle.
+        const InputDeviceInfo::MotionRange& x = mOrientedRanges.x;
+        const InputDeviceInfo::MotionRange& y = mOrientedRanges.y;
+        info->addMotionRange(AMOTION_EVENT_AXIS_RELATIVE_X, mSource, -x.max, x.max, x.flat, x.fuzz,
+                             x.resolution);
+        info->addMotionRange(AMOTION_EVENT_AXIS_RELATIVE_Y, mSource, -y.max, y.max, y.flat, y.fuzz,
+                             y.resolution);
+    }
+
+    if (mOrientedRanges.size) {
+        info->addMotionRange(*mOrientedRanges.size);
+    }
+
+    if (mOrientedRanges.touchMajor) {
+        info->addMotionRange(*mOrientedRanges.touchMajor);
+        info->addMotionRange(*mOrientedRanges.touchMinor);
+    }
+
+    if (mOrientedRanges.toolMajor) {
+        info->addMotionRange(*mOrientedRanges.toolMajor);
+        info->addMotionRange(*mOrientedRanges.toolMinor);
+    }
+
+    if (mOrientedRanges.orientation) {
+        info->addMotionRange(*mOrientedRanges.orientation);
+    }
+
+    if (mOrientedRanges.distance) {
+        info->addMotionRange(*mOrientedRanges.distance);
+    }
+
+    if (mOrientedRanges.tilt) {
+        info->addMotionRange(*mOrientedRanges.tilt);
+    }
+
+    if (mCursorScrollAccumulator.haveRelativeVWheel()) {
+        info->addMotionRange(AMOTION_EVENT_AXIS_VSCROLL, mSource, -1.0f, 1.0f, 0.0f, 0.0f, 0.0f);
+    }
+    if (mCursorScrollAccumulator.haveRelativeHWheel()) {
+        info->addMotionRange(AMOTION_EVENT_AXIS_HSCROLL, mSource, -1.0f, 1.0f, 0.0f, 0.0f, 0.0f);
+    }
+    if (mCalibration.coverageCalibration == Calibration::CoverageCalibration::BOX) {
+        const InputDeviceInfo::MotionRange& x = mOrientedRanges.x;
+        const InputDeviceInfo::MotionRange& y = mOrientedRanges.y;
+        info->addMotionRange(AMOTION_EVENT_AXIS_GENERIC_1, mSource, x.min, x.max, x.flat, x.fuzz,
+                             x.resolution);
+        info->addMotionRange(AMOTION_EVENT_AXIS_GENERIC_2, mSource, y.min, y.max, y.flat, y.fuzz,
+                             y.resolution);
+        info->addMotionRange(AMOTION_EVENT_AXIS_GENERIC_3, mSource, x.min, x.max, x.flat, x.fuzz,
+                             x.resolution);
+        info->addMotionRange(AMOTION_EVENT_AXIS_GENERIC_4, mSource, y.min, y.max, y.flat, y.fuzz,
+                             y.resolution);
+    }
+    info->setButtonUnderPad(mParameters.hasButtonUnderPad);
+    info->setSupportsUsi(mParameters.supportsUsi);
 }
 
 void TouchInputMapper::dump(std::string& dump) {
@@ -330,9 +285,12 @@
     dump += INDENT3 "Stylus Fusion:\n";
     dump += StringPrintf(INDENT4 "ExternalStylusConnected: %s\n",
                          toString(mExternalStylusConnected));
-    dump += StringPrintf(INDENT4 "External Stylus ID: %" PRId64 "\n", mExternalStylusId);
+    dump += StringPrintf(INDENT4 "Fused External Stylus Pointer ID: %s\n",
+                         toString(mFusedStylusPointerId).c_str());
     dump += StringPrintf(INDENT4 "External Stylus Data Timeout: %" PRId64 "\n",
                          mExternalStylusFusionTimeout);
+    dump += StringPrintf(INDENT4 " External Stylus Buttons Applied: 0x%08x",
+                         mExternalStylusButtonsApplied);
     dump += INDENT3 "External Stylus State:\n";
     dumpStylusState(dump, mExternalStylusState);
 
@@ -359,7 +317,7 @@
 
         // Configure common accumulators.
         mCursorScrollAccumulator.configure(getDeviceContext());
-        mTouchButtonAccumulator.configure(getDeviceContext());
+        mTouchButtonAccumulator.configure();
 
         // Configure absolute axis information.
         configureRawPointerAxes();
@@ -395,13 +353,11 @@
     }
 
     if (changes && resetNeeded) {
-        // If the device needs to be reset, cancel any ongoing gestures and reset the state.
-        out += cancelTouch(when, when);
         out += reset(when);
 
         // Send reset, unless this is the first time the device has been configured,
         // in which case the reader will call reset itself after all mappers are ready.
-        out.push_back(NotifyDeviceResetArgs(getContext()->getNextId(), when, getDeviceId()));
+        out.emplace_back(NotifyDeviceResetArgs(getContext()->getNextId(), when, getDeviceId()));
     }
     return out;
 }
@@ -467,18 +423,18 @@
     getDeviceContext().getConfiguration().tryGetProperty("touch.orientationAware",
                                                          mParameters.orientationAware);
 
-    mParameters.orientation = Parameters::Orientation::ORIENTATION_0;
+    mParameters.orientation = ui::ROTATION_0;
     std::string orientationString;
     if (getDeviceContext().getConfiguration().tryGetProperty("touch.orientation",
                                                              orientationString)) {
         if (mParameters.deviceType != Parameters::DeviceType::TOUCH_SCREEN) {
             ALOGW("The configuration 'touch.orientation' is only supported for touchscreens.");
         } else if (orientationString == "ORIENTATION_90") {
-            mParameters.orientation = Parameters::Orientation::ORIENTATION_90;
+            mParameters.orientation = ui::ROTATION_90;
         } else if (orientationString == "ORIENTATION_180") {
-            mParameters.orientation = Parameters::Orientation::ORIENTATION_180;
+            mParameters.orientation = ui::ROTATION_180;
         } else if (orientationString == "ORIENTATION_270") {
-            mParameters.orientation = Parameters::Orientation::ORIENTATION_270;
+            mParameters.orientation = ui::ROTATION_270;
         } else if (orientationString != "ORIENTATION_0") {
             ALOGW("Invalid value for touch.orientation: '%s'", orientationString.c_str());
         }
@@ -507,6 +463,14 @@
     // up in your pocket but you can enable it using the input device configuration.
     mParameters.wake = getDeviceContext().isExternal();
     getDeviceContext().getConfiguration().tryGetProperty("touch.wake", mParameters.wake);
+
+    mParameters.supportsUsi = false;
+    getDeviceContext().getConfiguration().tryGetProperty("touch.supportsUsi",
+                                                         mParameters.supportsUsi);
+
+    mParameters.enableForInactiveViewport = false;
+    getDeviceContext().getConfiguration().tryGetProperty("touch.enableForInactiveViewport",
+                                                         mParameters.enableForInactiveViewport);
 }
 
 void TouchInputMapper::dumpParameters(std::string& dump) {
@@ -523,6 +487,9 @@
                          mParameters.uniqueDisplayId.c_str());
     dump += StringPrintf(INDENT4 "OrientationAware: %s\n", toString(mParameters.orientationAware));
     dump += INDENT4 "Orientation: " + ftl::enum_string(mParameters.orientation) + "\n";
+    dump += StringPrintf(INDENT4 "SupportsUsi: %s\n", toString(mParameters.supportsUsi));
+    dump += StringPrintf(INDENT4 "EnableForInactiveViewport: %s\n",
+                         toString(mParameters.enableForInactiveViewport));
 }
 
 void TouchInputMapper::configureRawPointerAxes() {
@@ -630,7 +597,7 @@
     }
 
     // Size of diagonal axis.
-    const float diagonalSize = hypotf(mDisplayWidth, mDisplayHeight);
+    const float diagonalSize = hypotf(mDisplayBounds.width, mDisplayBounds.height);
 
     // Size factors.
     if (mRawPointerAxes.touchMajor.valid && mRawPointerAxes.touchMajor.maxValue != 0) {
@@ -713,8 +680,8 @@
 
 void TouchInputMapper::initializeOrientedRanges() {
     // Configure X and Y factors.
-    mXScale = float(mDisplayWidth) / mRawPointerAxes.getRawWidth();
-    mYScale = float(mDisplayHeight) / mRawPointerAxes.getRawHeight();
+    mXScale = float(mDisplayBounds.width) / mRawPointerAxes.getRawWidth();
+    mYScale = float(mDisplayBounds.height) / mRawPointerAxes.getRawHeight();
     mXPrecision = 1.0f / mXScale;
     mYPrecision = 1.0f / mYScale;
 
@@ -844,19 +811,19 @@
     // Note that the maximum value reported is an inclusive maximum value so it is one
     // unit less than the total width or height of the display.
     switch (mInputDeviceOrientation) {
-        case DISPLAY_ORIENTATION_90:
-        case DISPLAY_ORIENTATION_270:
+        case ui::ROTATION_90:
+        case ui::ROTATION_270:
             mOrientedXPrecision = mYPrecision;
             mOrientedYPrecision = mXPrecision;
 
             mOrientedRanges.x.min = 0;
-            mOrientedRanges.x.max = mDisplayHeight - 1;
+            mOrientedRanges.x.max = mDisplayBounds.height - 1;
             mOrientedRanges.x.flat = 0;
             mOrientedRanges.x.fuzz = 0;
             mOrientedRanges.x.resolution = mRawPointerAxes.y.resolution * mYScale;
 
             mOrientedRanges.y.min = 0;
-            mOrientedRanges.y.max = mDisplayWidth - 1;
+            mOrientedRanges.y.max = mDisplayBounds.width - 1;
             mOrientedRanges.y.flat = 0;
             mOrientedRanges.y.fuzz = 0;
             mOrientedRanges.y.resolution = mRawPointerAxes.x.resolution * mXScale;
@@ -867,13 +834,13 @@
             mOrientedYPrecision = mYPrecision;
 
             mOrientedRanges.x.min = 0;
-            mOrientedRanges.x.max = mDisplayWidth - 1;
+            mOrientedRanges.x.max = mDisplayBounds.width - 1;
             mOrientedRanges.x.flat = 0;
             mOrientedRanges.x.fuzz = 0;
             mOrientedRanges.x.resolution = mRawPointerAxes.x.resolution * mXScale;
 
             mOrientedRanges.y.min = 0;
-            mOrientedRanges.y.max = mDisplayHeight - 1;
+            mOrientedRanges.y.max = mDisplayBounds.height - 1;
             mOrientedRanges.y.flat = 0;
             mOrientedRanges.y.fuzz = 0;
             mOrientedRanges.y.resolution = mRawPointerAxes.y.resolution * mYScale;
@@ -893,6 +860,8 @@
         mDeviceMode = DeviceMode::POINTER;
         if (hasStylus()) {
             mSource |= AINPUT_SOURCE_STYLUS;
+        } else {
+            mSource |= AINPUT_SOURCE_TOUCHPAD;
         }
     } else if (isTouchScreen()) {
         mSource = AINPUT_SOURCE_TOUCHSCREEN;
@@ -925,15 +894,14 @@
               "becomes available.",
               getDeviceName().c_str());
         mDeviceMode = DeviceMode::DISABLED;
-    } else if (!newViewportOpt->isActive) {
+    } else if (!mParameters.enableForInactiveViewport && !newViewportOpt->isActive) {
         ALOGI("Disabling %s (device %i) because the associated viewport is not active",
               getDeviceName().c_str(), getDeviceId());
         mDeviceMode = DeviceMode::DISABLED;
     }
 
     // Raw width and height in the natural orientation.
-    const int32_t rawWidth = mRawPointerAxes.getRawWidth();
-    const int32_t rawHeight = mRawPointerAxes.getRawHeight();
+    const ui::Size rawSize{mRawPointerAxes.getRawWidth(), mRawPointerAxes.getRawHeight()};
     const int32_t rawXResolution = mRawPointerAxes.x.resolution;
     const int32_t rawYResolution = mRawPointerAxes.y.resolution;
     // Calculate the mean resolution when both x and y resolution are set, otherwise set it to 0.
@@ -949,67 +917,16 @@
         mViewport = newViewport;
 
         if (mDeviceMode == DeviceMode::DIRECT || mDeviceMode == DeviceMode::POINTER) {
-            // Convert rotated viewport to the natural orientation.
-            int32_t naturalPhysicalWidth, naturalPhysicalHeight;
-            int32_t naturalPhysicalLeft, naturalPhysicalTop;
-            int32_t naturalDeviceWidth, naturalDeviceHeight;
+            const auto oldDisplayBounds = mDisplayBounds;
 
             // Apply the inverse of the input device orientation so that the input device is
             // configured in the same orientation as the viewport. The input device orientation will
             // be re-applied by mInputDeviceOrientation.
-            const int32_t naturalDeviceOrientation =
-                    (mViewport.orientation - static_cast<int32_t>(mParameters.orientation) + 4) % 4;
-            switch (naturalDeviceOrientation) {
-                case DISPLAY_ORIENTATION_90:
-                    naturalPhysicalWidth = mViewport.physicalBottom - mViewport.physicalTop;
-                    naturalPhysicalHeight = mViewport.physicalRight - mViewport.physicalLeft;
-                    naturalPhysicalLeft = mViewport.deviceHeight - mViewport.physicalBottom;
-                    naturalPhysicalTop = mViewport.physicalLeft;
-                    naturalDeviceWidth = mViewport.deviceHeight;
-                    naturalDeviceHeight = mViewport.deviceWidth;
-                    break;
-                case DISPLAY_ORIENTATION_180:
-                    naturalPhysicalWidth = mViewport.physicalRight - mViewport.physicalLeft;
-                    naturalPhysicalHeight = mViewport.physicalBottom - mViewport.physicalTop;
-                    naturalPhysicalLeft = mViewport.deviceWidth - mViewport.physicalRight;
-                    naturalPhysicalTop = mViewport.deviceHeight - mViewport.physicalBottom;
-                    naturalDeviceWidth = mViewport.deviceWidth;
-                    naturalDeviceHeight = mViewport.deviceHeight;
-                    break;
-                case DISPLAY_ORIENTATION_270:
-                    naturalPhysicalWidth = mViewport.physicalBottom - mViewport.physicalTop;
-                    naturalPhysicalHeight = mViewport.physicalRight - mViewport.physicalLeft;
-                    naturalPhysicalLeft = mViewport.physicalTop;
-                    naturalPhysicalTop = mViewport.deviceWidth - mViewport.physicalRight;
-                    naturalDeviceWidth = mViewport.deviceHeight;
-                    naturalDeviceHeight = mViewport.deviceWidth;
-                    break;
-                case DISPLAY_ORIENTATION_0:
-                default:
-                    naturalPhysicalWidth = mViewport.physicalRight - mViewport.physicalLeft;
-                    naturalPhysicalHeight = mViewport.physicalBottom - mViewport.physicalTop;
-                    naturalPhysicalLeft = mViewport.physicalLeft;
-                    naturalPhysicalTop = mViewport.physicalTop;
-                    naturalDeviceWidth = mViewport.deviceWidth;
-                    naturalDeviceHeight = mViewport.deviceHeight;
-                    break;
-            }
+            const ui::Rotation naturalDeviceOrientation =
+                    mViewport.orientation - mParameters.orientation;
 
-            if (naturalPhysicalHeight == 0 || naturalPhysicalWidth == 0) {
-                ALOGE("Viewport is not set properly: %s", mViewport.toString().c_str());
-                naturalPhysicalHeight = naturalPhysicalHeight == 0 ? 1 : naturalPhysicalHeight;
-                naturalPhysicalWidth = naturalPhysicalWidth == 0 ? 1 : naturalPhysicalWidth;
-            }
-
-            mPhysicalWidth = naturalPhysicalWidth;
-            mPhysicalHeight = naturalPhysicalHeight;
-            mPhysicalLeft = naturalPhysicalLeft;
-            mPhysicalTop = naturalPhysicalTop;
-
-            const int32_t oldDisplayWidth = mDisplayWidth;
-            const int32_t oldDisplayHeight = mDisplayHeight;
-            mDisplayWidth = naturalDeviceWidth;
-            mDisplayHeight = naturalDeviceHeight;
+            std::tie(mDisplayBounds, mPhysicalFrameInDisplay) =
+                    getNaturalDisplayInfo(mViewport, naturalDeviceOrientation);
 
             // 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
@@ -1017,26 +934,19 @@
             // when the display rotation is applied later as a part of the per-window transform, we
             // get the expected screen coordinates.
             mInputDeviceOrientation = mParameters.orientationAware
-                    ? DISPLAY_ORIENTATION_0
+                    ? ui::ROTATION_0
                     : getInverseRotation(mViewport.orientation);
             // For orientation-aware devices that work in the un-rotated coordinate space, the
             // viewport update should be skipped if it is only a change in the orientation.
             skipViewportUpdate = !viewportDisplayIdChanged && mParameters.orientationAware &&
-                    mDisplayWidth == oldDisplayWidth && mDisplayHeight == oldDisplayHeight &&
-                    viewportOrientationChanged;
+                    mDisplayBounds == oldDisplayBounds && viewportOrientationChanged;
 
             // Apply the input device orientation for the device.
-            mInputDeviceOrientation =
-                    (mInputDeviceOrientation + static_cast<int32_t>(mParameters.orientation)) % 4;
+            mInputDeviceOrientation = mInputDeviceOrientation + mParameters.orientation;
         } else {
-            mPhysicalWidth = rawWidth;
-            mPhysicalHeight = rawHeight;
-            mPhysicalLeft = 0;
-            mPhysicalTop = 0;
-
-            mDisplayWidth = rawWidth;
-            mDisplayHeight = rawHeight;
-            mInputDeviceOrientation = DISPLAY_ORIENTATION_0;
+            mDisplayBounds = rawSize;
+            mPhysicalFrameInDisplay = Rect{mDisplayBounds};
+            mInputDeviceOrientation = ui::ROTATION_0;
         }
     }
 
@@ -1067,9 +977,9 @@
     }
 
     if ((viewportChanged && !skipViewportUpdate) || deviceModeChanged) {
-        ALOGI("Device reconfigured: id=%d, name='%s', size %dx%d, orientation %d, mode %d, "
+        ALOGI("Device reconfigured: id=%d, name='%s', size %s, orientation %d, mode %d, "
               "display id %d",
-              getDeviceId(), getDeviceName().c_str(), mDisplayWidth, mDisplayHeight,
+              getDeviceId(), getDeviceName().c_str(), toString(mDisplayBounds).c_str(),
               mInputDeviceOrientation, mDeviceMode, mViewport.displayId);
 
         configureVirtualKeys();
@@ -1081,8 +991,8 @@
 
         if (mDeviceMode == DeviceMode::POINTER) {
             // Compute pointer gesture detection parameters.
-            float rawDiagonal = hypotf(rawWidth, rawHeight);
-            float displayDiagonal = hypotf(mDisplayWidth, mDisplayHeight);
+            float rawDiagonal = hypotf(rawSize.width, rawSize.height);
+            float displayDiagonal = hypotf(mDisplayBounds.width, mDisplayBounds.height);
 
             // Scale movements such that one whole swipe of the touch pad covers a
             // given area relative to the diagonal size of the display when no acceleration
@@ -1118,12 +1028,8 @@
 
 void TouchInputMapper::dumpDisplay(std::string& dump) {
     dump += StringPrintf(INDENT3 "%s\n", mViewport.toString().c_str());
-    dump += StringPrintf(INDENT3 "DisplayWidth: %dpx\n", mDisplayWidth);
-    dump += StringPrintf(INDENT3 "DisplayHeight: %dpx\n", mDisplayHeight);
-    dump += StringPrintf(INDENT3 "PhysicalWidth: %dpx\n", mPhysicalWidth);
-    dump += StringPrintf(INDENT3 "PhysicalHeight: %dpx\n", mPhysicalHeight);
-    dump += StringPrintf(INDENT3 "PhysicalLeft: %d\n", mPhysicalLeft);
-    dump += StringPrintf(INDENT3 "PhysicalTop: %d\n", mPhysicalTop);
+    dump += StringPrintf(INDENT3 "DisplayBounds: %s\n", toString(mDisplayBounds).c_str());
+    dump += StringPrintf(INDENT3 "PhysicalFrame: %s\n", toString(mPhysicalFrameInDisplay).c_str());
     dump += StringPrintf(INDENT3 "InputDeviceOrientation: %d\n", mInputDeviceOrientation);
 }
 
@@ -1162,17 +1068,17 @@
         int32_t halfWidth = virtualKeyDefinition.width / 2;
         int32_t halfHeight = virtualKeyDefinition.height / 2;
 
-        virtualKey.hitLeft =
-                (virtualKeyDefinition.centerX - halfWidth) * touchScreenWidth / mDisplayWidth +
+        virtualKey.hitLeft = (virtualKeyDefinition.centerX - halfWidth) * touchScreenWidth /
+                        mDisplayBounds.width +
                 touchScreenLeft;
-        virtualKey.hitRight =
-                (virtualKeyDefinition.centerX + halfWidth) * touchScreenWidth / mDisplayWidth +
+        virtualKey.hitRight = (virtualKeyDefinition.centerX + halfWidth) * touchScreenWidth /
+                        mDisplayBounds.width +
                 touchScreenLeft;
-        virtualKey.hitTop =
-                (virtualKeyDefinition.centerY - halfHeight) * touchScreenHeight / mDisplayHeight +
+        virtualKey.hitTop = (virtualKeyDefinition.centerY - halfHeight) * touchScreenHeight /
+                        mDisplayBounds.height +
                 touchScreenTop;
-        virtualKey.hitBottom =
-                (virtualKeyDefinition.centerY + halfHeight) * touchScreenHeight / mDisplayHeight +
+        virtualKey.hitBottom = (virtualKeyDefinition.centerY + halfHeight) * touchScreenHeight /
+                        mDisplayBounds.height +
                 touchScreenTop;
         mVirtualKeys.push_back(virtualKey);
     }
@@ -1440,9 +1346,12 @@
 }
 
 std::list<NotifyArgs> TouchInputMapper::reset(nsecs_t when) {
+    std::list<NotifyArgs> out = cancelTouch(when, when);
+    updateTouchSpots();
+
     mCursorButtonAccumulator.reset(getDeviceContext());
     mCursorScrollAccumulator.reset(getDeviceContext());
-    mTouchButtonAccumulator.reset(getDeviceContext());
+    mTouchButtonAccumulator.reset();
 
     mPointerVelocityControl.reset();
     mWheelXVelocityControl.reset();
@@ -1470,14 +1379,15 @@
         mPointerController->clearSpots();
     }
 
-    return InputMapper::reset(when);
+    return out += InputMapper::reset(when);
 }
 
 void TouchInputMapper::resetExternalStylus() {
     mExternalStylusState.clear();
-    mExternalStylusId = -1;
+    mFusedStylusPointerId.reset();
     mExternalStylusFusionTimeout = LLONG_MAX;
     mExternalStylusDataPending = false;
+    mExternalStylusButtonsApplied = 0;
 }
 
 void TouchInputMapper::clearStylusDataPendingFlags() {
@@ -1527,6 +1437,10 @@
     const RawState& last =
             mRawStatesPending.size() == 1 ? mCurrentRawState : mRawStatesPending.rbegin()[1];
 
+    std::tie(next.when, next.readTime) =
+            applyBluetoothTimestampSmoothening(getDeviceContext().getDeviceIdentifier(), when,
+                                               readTime, last.when);
+
     // Assign pointer ids.
     if (!mHavePointerIds) {
         assignPointerIds(last, next);
@@ -1554,10 +1468,7 @@
 std::list<NotifyArgs> TouchInputMapper::processRawTouches(bool timeout) {
     std::list<NotifyArgs> out;
     if (mDeviceMode == DeviceMode::DISABLED) {
-        // Drop all input if the device is disabled.
-        out += cancelTouch(mCurrentRawState.when, mCurrentRawState.readTime);
-        mCurrentCookedState.clear();
-        updateTouchSpots();
+        // Do not process raw event while the device is disabled.
         return out;
     }
 
@@ -1578,7 +1489,7 @@
 
         // All ready to go.
         clearStylusDataPendingFlags();
-        mCurrentRawState.copyFrom(next);
+        mCurrentRawState = next;
         if (mCurrentRawState.when < mLastRawState.when) {
             mCurrentRawState.when = mLastRawState.when;
             mCurrentRawState.readTime = mLastRawState.readTime;
@@ -1593,7 +1504,7 @@
         if (timeout) {
             nsecs_t when = mExternalStylusFusionTimeout - STYLUS_DATA_LATENCY;
             clearStylusDataPendingFlags();
-            mCurrentRawState.copyFrom(mLastRawState);
+            mCurrentRawState = mLastRawState;
             ALOGD_IF(DEBUG_STYLUS_FUSION,
                      "Timeout expired, synthesizing event with new stylus data");
             const nsecs_t readTime = when; // consider this synthetic event to be zero latency
@@ -1658,8 +1569,7 @@
             uint32_t id = idBits.clearFirstMarkedBit();
             const RawPointerData::Pointer& pointer =
                     mCurrentRawState.rawPointerData.pointerForId(id);
-            if (pointer.toolType == AMOTION_EVENT_TOOL_TYPE_STYLUS ||
-                pointer.toolType == AMOTION_EVENT_TOOL_TYPE_ERASER) {
+            if (isStylusToolType(pointer.toolType)) {
                 mCurrentCookedState.stylusIdBits.markBit(id);
             } else if (pointer.toolType == AMOTION_EVENT_TOOL_TYPE_FINGER ||
                        pointer.toolType == AMOTION_EVENT_TOOL_TYPE_UNKNOWN) {
@@ -1672,8 +1582,7 @@
             uint32_t id = idBits.clearFirstMarkedBit();
             const RawPointerData::Pointer& pointer =
                     mCurrentRawState.rawPointerData.pointerForId(id);
-            if (pointer.toolType == AMOTION_EVENT_TOOL_TYPE_STYLUS ||
-                pointer.toolType == AMOTION_EVENT_TOOL_TYPE_ERASER) {
+            if (isStylusToolType(pointer.toolType)) {
                 mCurrentCookedState.stylusIdBits.markBit(id);
             }
         }
@@ -1718,8 +1627,8 @@
     mCurrentRawState.rawHScroll = 0;
 
     // Copy current touch to last touch in preparation for the next cycle.
-    mLastRawState.copyFrom(mCurrentRawState);
-    mLastCookedState.copyFrom(mCurrentCookedState);
+    mLastRawState = mCurrentRawState;
+    mLastCookedState = mCurrentCookedState;
     return out;
 }
 
@@ -1739,8 +1648,8 @@
     mPointerController->fade(PointerControllerInterface::Transition::GRADUAL);
 
     mPointerController->setButtonState(mCurrentRawState.buttonState);
-    mPointerController->setSpots(mCurrentCookedState.cookedPointerData.pointerCoords,
-                                 mCurrentCookedState.cookedPointerData.idToIndex,
+    mPointerController->setSpots(mCurrentCookedState.cookedPointerData.pointerCoords.cbegin(),
+                                 mCurrentCookedState.cookedPointerData.idToIndex.cbegin(),
                                  mCurrentCookedState.cookedPointerData.touchingIdBits,
                                  mViewport.displayId);
 }
@@ -1751,29 +1660,41 @@
 }
 
 void TouchInputMapper::applyExternalStylusButtonState(nsecs_t when) {
-    if (mDeviceMode == DeviceMode::DIRECT && hasExternalStylus() && mExternalStylusId != -1) {
-        mCurrentRawState.buttonState |= mExternalStylusState.buttons;
+    if (mDeviceMode == DeviceMode::DIRECT && hasExternalStylus()) {
+        // If any of the external buttons are already pressed by the touch device, ignore them.
+        const int32_t pressedButtons = ~mCurrentRawState.buttonState & mExternalStylusState.buttons;
+        const int32_t releasedButtons =
+                mExternalStylusButtonsApplied & ~mExternalStylusState.buttons;
+
+        mCurrentRawState.buttonState |= pressedButtons;
+        mCurrentRawState.buttonState &= ~releasedButtons;
+
+        mExternalStylusButtonsApplied |= pressedButtons;
+        mExternalStylusButtonsApplied &= ~releasedButtons;
     }
 }
 
 void TouchInputMapper::applyExternalStylusTouchState(nsecs_t when) {
     CookedPointerData& currentPointerData = mCurrentCookedState.cookedPointerData;
     const CookedPointerData& lastPointerData = mLastCookedState.cookedPointerData;
+    if (!mFusedStylusPointerId || !currentPointerData.isTouching(*mFusedStylusPointerId)) {
+        return;
+    }
 
-    if (mExternalStylusId != -1 && currentPointerData.isTouching(mExternalStylusId)) {
-        float pressure = mExternalStylusState.pressure;
-        if (pressure == 0.0f && lastPointerData.isTouching(mExternalStylusId)) {
-            const PointerCoords& coords = lastPointerData.pointerCoordsForId(mExternalStylusId);
-            pressure = coords.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE);
-        }
-        PointerCoords& coords = currentPointerData.editPointerCoordsWithId(mExternalStylusId);
-        coords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, pressure);
+    float pressure = lastPointerData.isTouching(*mFusedStylusPointerId)
+            ? lastPointerData.pointerCoordsForId(*mFusedStylusPointerId)
+                      .getAxisValue(AMOTION_EVENT_AXIS_PRESSURE)
+            : 0.f;
+    if (mExternalStylusState.pressure && *mExternalStylusState.pressure > 0.f) {
+        pressure = *mExternalStylusState.pressure;
+    }
+    PointerCoords& coords = currentPointerData.editPointerCoordsWithId(*mFusedStylusPointerId);
+    coords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, pressure);
 
+    if (mExternalStylusState.toolType != AMOTION_EVENT_TOOL_TYPE_UNKNOWN) {
         PointerProperties& properties =
-                currentPointerData.editPointerPropertiesWithId(mExternalStylusId);
-        if (mExternalStylusState.toolType != AMOTION_EVENT_TOOL_TYPE_UNKNOWN) {
-            properties.toolType = mExternalStylusState.toolType;
-        }
+                currentPointerData.editPointerPropertiesWithId(*mFusedStylusPointerId);
+        properties.toolType = mExternalStylusState.toolType;
     }
 }
 
@@ -1782,34 +1703,48 @@
         return false;
     }
 
+    // Check if the stylus pointer has gone up.
+    if (mFusedStylusPointerId &&
+        !state.rawPointerData.touchingIdBits.hasBit(*mFusedStylusPointerId)) {
+        ALOGD_IF(DEBUG_STYLUS_FUSION, "Stylus pointer is going up");
+        mFusedStylusPointerId.reset();
+        return false;
+    }
+
     const bool initialDown = mLastRawState.rawPointerData.pointerCount == 0 &&
             state.rawPointerData.pointerCount != 0;
-    if (initialDown) {
-        if (mExternalStylusState.pressure != 0.0f) {
-            ALOGD_IF(DEBUG_STYLUS_FUSION, "Have both stylus and touch data, beginning fusion");
-            mExternalStylusId = state.rawPointerData.touchingIdBits.firstMarkedBit();
-        } else if (timeout) {
-            ALOGD_IF(DEBUG_STYLUS_FUSION, "Timeout expired, assuming touch is not a stylus.");
-            resetExternalStylus();
-        } else {
-            if (mExternalStylusFusionTimeout == LLONG_MAX) {
-                mExternalStylusFusionTimeout = state.when + EXTERNAL_STYLUS_DATA_TIMEOUT;
-            }
-            ALOGD_IF(DEBUG_STYLUS_FUSION,
-                     "No stylus data but stylus is connected, requesting timeout (%" PRId64 "ms)",
-                     mExternalStylusFusionTimeout);
-            getContext()->requestTimeoutAtTime(mExternalStylusFusionTimeout);
-            return true;
-        }
+    if (!initialDown) {
+        return false;
     }
 
-    // Check if the stylus pointer has gone up.
-    if (mExternalStylusId != -1 && !state.rawPointerData.touchingIdBits.hasBit(mExternalStylusId)) {
-        ALOGD_IF(DEBUG_STYLUS_FUSION, "Stylus pointer is going up");
-        mExternalStylusId = -1;
+    if (!mExternalStylusState.pressure) {
+        ALOGD_IF(DEBUG_STYLUS_FUSION, "Stylus does not support pressure, no pointer fusion needed");
+        return false;
     }
 
-    return false;
+    if (*mExternalStylusState.pressure != 0.0f) {
+        ALOGD_IF(DEBUG_STYLUS_FUSION, "Have both stylus and touch data, beginning fusion");
+        mFusedStylusPointerId = state.rawPointerData.touchingIdBits.firstMarkedBit();
+        return false;
+    }
+
+    if (timeout) {
+        ALOGD_IF(DEBUG_STYLUS_FUSION, "Timeout expired, assuming touch is not a stylus.");
+        mFusedStylusPointerId.reset();
+        mExternalStylusFusionTimeout = LLONG_MAX;
+        return false;
+    }
+
+    // We are waiting for the external stylus to report a pressure value. Withhold touches from
+    // being processed until we either get pressure data or timeout.
+    if (mExternalStylusFusionTimeout == LLONG_MAX) {
+        mExternalStylusFusionTimeout = state.when + EXTERNAL_STYLUS_DATA_TIMEOUT;
+    }
+    ALOGD_IF(DEBUG_STYLUS_FUSION,
+             "No stylus data but stylus is connected, requesting timeout (%" PRId64 "ms)",
+             mExternalStylusFusionTimeout);
+    getContext()->requestTimeoutAtTime(mExternalStylusFusionTimeout);
+    return true;
 }
 
 std::list<NotifyArgs> TouchInputMapper::timeoutExpired(nsecs_t when) {
@@ -1821,7 +1756,7 @@
             out += dispatchPointerGestures(when, readTime, 0 /*policyFlags*/, true /*isTimeout*/);
         }
     } else if (mDeviceMode == DeviceMode::DIRECT) {
-        if (mExternalStylusFusionTimeout < when) {
+        if (mExternalStylusFusionTimeout <= when) {
             out += processRawTouches(true /*timeout*/);
         } else if (mExternalStylusFusionTimeout != LLONG_MAX) {
             getContext()->requestTimeoutAtTime(mExternalStylusFusionTimeout);
@@ -1832,11 +1767,14 @@
 
 std::list<NotifyArgs> TouchInputMapper::updateExternalStylusState(const StylusState& state) {
     std::list<NotifyArgs> out;
-    mExternalStylusState.copyFrom(state);
-    if (mExternalStylusId != -1 || mExternalStylusFusionTimeout != LLONG_MAX) {
-        // We're either in the middle of a fused stream of data or we're waiting on data before
-        // dispatching the initial down, so go ahead and dispatch now that we have fresh stylus
-        // data.
+    const bool buttonsChanged = mExternalStylusState.buttons != state.buttons;
+    mExternalStylusState = state;
+    if (mFusedStylusPointerId || mExternalStylusFusionTimeout != LLONG_MAX || buttonsChanged) {
+        // The following three cases are handled here:
+        // - We're in the middle of a fused stream of data;
+        // - We're waiting on external stylus data before dispatching the initial down; or
+        // - Only the button state, which is not reported through a specific pointer, has changed.
+        // Go ahead and dispatch now that we have fresh stylus data.
         mExternalStylusDataPending = true;
         out += processRawTouches(false /*timeout*/);
     }
@@ -1976,8 +1914,8 @@
         int32_t metaState = getContext()->getGlobalMetaState();
         int32_t buttonState = mCurrentCookedState.buttonState;
         out.push_back(dispatchMotion(when, readTime, policyFlags, mSource,
-                                     AMOTION_EVENT_ACTION_CANCEL, 0, 0, metaState, buttonState,
-                                     AMOTION_EVENT_EDGE_FLAG_NONE,
+                                     AMOTION_EVENT_ACTION_CANCEL, 0, AMOTION_EVENT_FLAG_CANCELED,
+                                     metaState, buttonState, AMOTION_EVENT_EDGE_FLAG_NONE,
                                      mCurrentCookedState.cookedPointerData.pointerProperties,
                                      mCurrentCookedState.cookedPointerData.pointerCoords,
                                      mCurrentCookedState.cookedPointerData.idToIndex, currentIdBits,
@@ -1988,6 +1926,36 @@
     return out;
 }
 
+// Updates pointer coords and properties for pointers with specified ids that have moved.
+// Returns true if any of them changed.
+static bool updateMovedPointers(const PropertiesArray& inProperties, CoordsArray& inCoords,
+                                const IdToIndexArray& inIdToIndex, PropertiesArray& outProperties,
+                                CoordsArray& outCoords, IdToIndexArray& outIdToIndex,
+                                BitSet32 idBits) {
+    bool changed = false;
+    while (!idBits.isEmpty()) {
+        uint32_t id = idBits.clearFirstMarkedBit();
+        uint32_t inIndex = inIdToIndex[id];
+        uint32_t outIndex = outIdToIndex[id];
+
+        const PointerProperties& curInProperties = inProperties[inIndex];
+        const PointerCoords& curInCoords = inCoords[inIndex];
+        PointerProperties& curOutProperties = outProperties[outIndex];
+        PointerCoords& curOutCoords = outCoords[outIndex];
+
+        if (curInProperties != curOutProperties) {
+            curOutProperties.copyFrom(curInProperties);
+            changed = true;
+        }
+
+        if (curInCoords != curOutCoords) {
+            curOutCoords.copyFrom(curInCoords);
+            changed = true;
+        }
+    }
+    return changed;
+}
+
 std::list<NotifyArgs> TouchInputMapper::dispatchTouches(nsecs_t when, nsecs_t readTime,
                                                         uint32_t policyFlags) {
     std::list<NotifyArgs> out;
@@ -2155,9 +2123,9 @@
         out.push_back(dispatchMotion(when, readTime, policyFlags, mSource,
                                      AMOTION_EVENT_ACTION_BUTTON_RELEASE, actionButton, 0,
                                      metaState, buttonState, 0,
-                                     mCurrentCookedState.cookedPointerData.pointerProperties,
-                                     mCurrentCookedState.cookedPointerData.pointerCoords,
-                                     mCurrentCookedState.cookedPointerData.idToIndex, idBits, -1,
+                                     mLastCookedState.cookedPointerData.pointerProperties,
+                                     mLastCookedState.cookedPointerData.pointerCoords,
+                                     mLastCookedState.cookedPointerData.idToIndex, idBits, -1,
                                      mOrientedXPrecision, mOrientedYPrecision, mDownTime,
                                      MotionClassification::NONE));
     }
@@ -2379,7 +2347,7 @@
         float left, top, right, bottom;
 
         switch (mInputDeviceOrientation) {
-            case DISPLAY_ORIENTATION_90:
+            case ui::ROTATION_90:
                 left = float(rawTop - mRawPointerAxes.y.minValue) * mYScale;
                 right = float(rawBottom - mRawPointerAxes.y.minValue) * mYScale;
                 bottom = float(mRawPointerAxes.x.maxValue - rawLeft) * mXScale;
@@ -2390,7 +2358,7 @@
                             (mOrientedRanges.orientation->max - mOrientedRanges.orientation->min);
                 }
                 break;
-            case DISPLAY_ORIENTATION_180:
+            case ui::ROTATION_180:
                 left = float(mRawPointerAxes.x.maxValue - rawRight) * mXScale;
                 right = float(mRawPointerAxes.x.maxValue - rawLeft) * mXScale;
                 bottom = float(mRawPointerAxes.y.maxValue - rawTop) * mYScale;
@@ -2401,7 +2369,7 @@
                             (mOrientedRanges.orientation->max - mOrientedRanges.orientation->min);
                 }
                 break;
-            case DISPLAY_ORIENTATION_270:
+            case ui::ROTATION_270:
                 left = float(mRawPointerAxes.y.maxValue - rawBottom) * mYScale;
                 right = float(mRawPointerAxes.y.maxValue - rawTop) * mYScale;
                 bottom = float(rawRight - mRawPointerAxes.x.minValue) * mXScale;
@@ -2534,8 +2502,8 @@
         }
 
         if (mPointerGesture.currentGestureMode == PointerGesture::Mode::FREEFORM) {
-            mPointerController->setSpots(mPointerGesture.currentGestureCoords,
-                                         mPointerGesture.currentGestureIdToIndex,
+            mPointerController->setSpots(mPointerGesture.currentGestureCoords.cbegin(),
+                                         mPointerGesture.currentGestureIdToIndex.cbegin(),
                                          mPointerGesture.currentGestureIdBits,
                                          mPointerController->getDisplayId());
         }
@@ -2617,8 +2585,9 @@
     BitSet32 dispatchedGestureIdBits(mPointerGesture.lastGestureIdBits);
     if (!dispatchedGestureIdBits.isEmpty()) {
         if (cancelPreviousGesture) {
+            const uint32_t cancelFlags = flags | AMOTION_EVENT_FLAG_CANCELED;
             out.push_back(dispatchMotion(when, readTime, policyFlags, mSource,
-                                         AMOTION_EVENT_ACTION_CANCEL, 0, flags, metaState,
+                                         AMOTION_EVENT_ACTION_CANCEL, 0, cancelFlags, metaState,
                                          buttonState, AMOTION_EVENT_EDGE_FLAG_NONE,
                                          mPointerGesture.lastGestureProperties,
                                          mPointerGesture.lastGestureCoords,
@@ -2754,8 +2723,8 @@
         int32_t metaState = getContext()->getGlobalMetaState();
         int32_t buttonState = mCurrentRawState.buttonState;
         out.push_back(dispatchMotion(when, readTime, policyFlags, mSource,
-                                     AMOTION_EVENT_ACTION_CANCEL, 0, 0, metaState, buttonState,
-                                     AMOTION_EVENT_EDGE_FLAG_NONE,
+                                     AMOTION_EVENT_ACTION_CANCEL, 0, AMOTION_EVENT_FLAG_CANCELED,
+                                     metaState, buttonState, AMOTION_EVENT_EDGE_FLAG_NONE,
                                      mPointerGesture.lastGestureProperties,
                                      mPointerGesture.lastGestureCoords,
                                      mPointerGesture.lastGestureIdToIndex,
@@ -2839,54 +2808,20 @@
     // Otherwise choose an arbitrary remaining pointer.
     // This guarantees we always have an active touch id when there is at least one pointer.
     // We keep the same active touch id for as long as possible.
-    int32_t lastActiveTouchId = mPointerGesture.activeTouchId;
-    int32_t activeTouchId = lastActiveTouchId;
-    if (activeTouchId < 0) {
+    if (mPointerGesture.activeTouchId < 0) {
         if (!mCurrentCookedState.fingerIdBits.isEmpty()) {
-            activeTouchId = mPointerGesture.activeTouchId =
-                    mCurrentCookedState.fingerIdBits.firstMarkedBit();
+            mPointerGesture.activeTouchId = mCurrentCookedState.fingerIdBits.firstMarkedBit();
             mPointerGesture.firstTouchTime = when;
         }
-    } else if (!mCurrentCookedState.fingerIdBits.hasBit(activeTouchId)) {
-        if (!mCurrentCookedState.fingerIdBits.isEmpty()) {
-            activeTouchId = mPointerGesture.activeTouchId =
-                    mCurrentCookedState.fingerIdBits.firstMarkedBit();
-        } else {
-            activeTouchId = mPointerGesture.activeTouchId = -1;
-        }
+    } else if (!mCurrentCookedState.fingerIdBits.hasBit(mPointerGesture.activeTouchId)) {
+        mPointerGesture.activeTouchId = !mCurrentCookedState.fingerIdBits.isEmpty()
+                ? mCurrentCookedState.fingerIdBits.firstMarkedBit()
+                : -1;
     }
-
-    // Determine whether we are in quiet time.
-    bool isQuietTime = false;
-    if (activeTouchId < 0) {
-        mPointerGesture.resetQuietTime();
-    } else {
-        isQuietTime = when < mPointerGesture.quietTime + mConfig.pointerGestureQuietInterval;
-        if (!isQuietTime) {
-            if ((mPointerGesture.lastGestureMode == PointerGesture::Mode::PRESS ||
-                 mPointerGesture.lastGestureMode == PointerGesture::Mode::SWIPE ||
-                 mPointerGesture.lastGestureMode == PointerGesture::Mode::FREEFORM) &&
-                currentFingerCount < 2) {
-                // Enter quiet time when exiting swipe or freeform state.
-                // This is to prevent accidentally entering the hover state and flinging the
-                // pointer when finishing a swipe and there is still one pointer left onscreen.
-                isQuietTime = true;
-            } else if (mPointerGesture.lastGestureMode ==
-                               PointerGesture::Mode::BUTTON_CLICK_OR_DRAG &&
-                       currentFingerCount >= 2 && !isPointerDown(mCurrentRawState.buttonState)) {
-                // Enter quiet time when releasing the button and there are still two or more
-                // fingers down.  This may indicate that one finger was used to press the button
-                // but it has not gone up yet.
-                isQuietTime = true;
-            }
-            if (isQuietTime) {
-                mPointerGesture.quietTime = when;
-            }
-        }
-    }
+    const int32_t& activeTouchId = mPointerGesture.activeTouchId;
 
     // Switch states based on button and pointer state.
-    if (isQuietTime) {
+    if (checkForTouchpadQuietTime(when)) {
         // Case 1: Quiet time. (QUIET)
         ALOGD_IF(DEBUG_GESTURES, "Gestures: QUIET for next %0.3fms",
                  (mPointerGesture.quietTime + mConfig.pointerGestureQuietInterval - when) *
@@ -2926,24 +2861,9 @@
         // Switch pointers if needed.
         // Find the fastest pointer and follow it.
         if (activeTouchId >= 0 && currentFingerCount > 1) {
-            int32_t bestId = -1;
-            float bestSpeed = mConfig.pointerGestureDragMinSwitchSpeed;
-            for (BitSet32 idBits(mCurrentCookedState.fingerIdBits); !idBits.isEmpty();) {
-                uint32_t id = idBits.clearFirstMarkedBit();
-                std::optional<float> vx =
-                        mPointerGesture.velocityTracker.getVelocity(AMOTION_EVENT_AXIS_X, id);
-                std::optional<float> vy =
-                        mPointerGesture.velocityTracker.getVelocity(AMOTION_EVENT_AXIS_Y, id);
-                if (vx && vy) {
-                    float speed = hypotf(*vx, *vy);
-                    if (speed > bestSpeed) {
-                        bestId = id;
-                        bestSpeed = speed;
-                    }
-                }
-            }
+            const auto [bestId, bestSpeed] = getFastestFinger();
             if (bestId >= 0 && bestId != activeTouchId) {
-                mPointerGesture.activeTouchId = activeTouchId = bestId;
+                mPointerGesture.activeTouchId = bestId;
                 ALOGD_IF(DEBUG_GESTURES,
                          "Gestures: BUTTON_CLICK_OR_DRAG switched pointers, bestId=%d, "
                          "bestSpeed=%0.3f",
@@ -3109,345 +3029,7 @@
         }
     } else {
         // Case 5. At least two fingers down, button is not pressed. (PRESS, SWIPE or FREEFORM)
-        // We need to provide feedback for each finger that goes down so we cannot wait
-        // for the fingers to move before deciding what to do.
-        //
-        // The ambiguous case is deciding what to do when there are two fingers down but they
-        // have not moved enough to determine whether they are part of a drag or part of a
-        // freeform gesture, or just a press or long-press at the pointer location.
-        //
-        // When there are two fingers we start with the PRESS hypothesis and we generate a
-        // down at the pointer location.
-        //
-        // When the two fingers move enough or when additional fingers are added, we make
-        // a decision to transition into SWIPE or FREEFORM mode accordingly.
-        ALOG_ASSERT(activeTouchId >= 0);
-
-        bool settled = when >=
-                mPointerGesture.firstTouchTime + mConfig.pointerGestureMultitouchSettleInterval;
-        if (mPointerGesture.lastGestureMode != PointerGesture::Mode::PRESS &&
-            mPointerGesture.lastGestureMode != PointerGesture::Mode::SWIPE &&
-            mPointerGesture.lastGestureMode != PointerGesture::Mode::FREEFORM) {
-            *outFinishPreviousGesture = true;
-        } else if (!settled && currentFingerCount > lastFingerCount) {
-            // Additional pointers have gone down but not yet settled.
-            // Reset the gesture.
-            ALOGD_IF(DEBUG_GESTURES,
-                     "Gestures: Resetting gesture since additional pointers went down for "
-                     "MULTITOUCH, settle time remaining %0.3fms",
-                     (mPointerGesture.firstTouchTime +
-                      mConfig.pointerGestureMultitouchSettleInterval - when) *
-                             0.000001f);
-            *outCancelPreviousGesture = true;
-        } else {
-            // Continue previous gesture.
-            mPointerGesture.currentGestureMode = mPointerGesture.lastGestureMode;
-        }
-
-        if (*outFinishPreviousGesture || *outCancelPreviousGesture) {
-            mPointerGesture.currentGestureMode = PointerGesture::Mode::PRESS;
-            mPointerGesture.activeGestureId = 0;
-            mPointerGesture.referenceIdBits.clear();
-            mPointerVelocityControl.reset();
-
-            // Use the centroid and pointer location as the reference points for the gesture.
-            ALOGD_IF(DEBUG_GESTURES,
-                     "Gestures: Using centroid as reference for MULTITOUCH, settle time remaining "
-                     "%0.3fms",
-                     (mPointerGesture.firstTouchTime +
-                      mConfig.pointerGestureMultitouchSettleInterval - when) *
-                             0.000001f);
-            mCurrentRawState.rawPointerData
-                    .getCentroidOfTouchingPointers(&mPointerGesture.referenceTouchX,
-                                                   &mPointerGesture.referenceTouchY);
-            mPointerController->getPosition(&mPointerGesture.referenceGestureX,
-                                            &mPointerGesture.referenceGestureY);
-        }
-
-        // Clear the reference deltas for fingers not yet included in the reference calculation.
-        for (BitSet32 idBits(mCurrentCookedState.fingerIdBits.value &
-                             ~mPointerGesture.referenceIdBits.value);
-             !idBits.isEmpty();) {
-            uint32_t id = idBits.clearFirstMarkedBit();
-            mPointerGesture.referenceDeltas[id].dx = 0;
-            mPointerGesture.referenceDeltas[id].dy = 0;
-        }
-        mPointerGesture.referenceIdBits = mCurrentCookedState.fingerIdBits;
-
-        // Add delta for all fingers and calculate a common movement delta.
-        int32_t commonDeltaRawX = 0, commonDeltaRawY = 0;
-        BitSet32 commonIdBits(mLastCookedState.fingerIdBits.value &
-                              mCurrentCookedState.fingerIdBits.value);
-        for (BitSet32 idBits(commonIdBits); !idBits.isEmpty();) {
-            bool first = (idBits == commonIdBits);
-            uint32_t id = idBits.clearFirstMarkedBit();
-            const RawPointerData::Pointer& cpd = mCurrentRawState.rawPointerData.pointerForId(id);
-            const RawPointerData::Pointer& lpd = mLastRawState.rawPointerData.pointerForId(id);
-            PointerGesture::Delta& delta = mPointerGesture.referenceDeltas[id];
-            delta.dx += cpd.x - lpd.x;
-            delta.dy += cpd.y - lpd.y;
-
-            if (first) {
-                commonDeltaRawX = delta.dx;
-                commonDeltaRawY = delta.dy;
-            } else {
-                commonDeltaRawX = calculateCommonVector(commonDeltaRawX, delta.dx);
-                commonDeltaRawY = calculateCommonVector(commonDeltaRawY, delta.dy);
-            }
-        }
-
-        // Consider transitions from PRESS to SWIPE or MULTITOUCH.
-        if (mPointerGesture.currentGestureMode == PointerGesture::Mode::PRESS) {
-            float dist[MAX_POINTER_ID + 1];
-            int32_t distOverThreshold = 0;
-            for (BitSet32 idBits(mPointerGesture.referenceIdBits); !idBits.isEmpty();) {
-                uint32_t id = idBits.clearFirstMarkedBit();
-                PointerGesture::Delta& delta = mPointerGesture.referenceDeltas[id];
-                dist[id] = hypotf(delta.dx * mPointerXZoomScale, delta.dy * mPointerYZoomScale);
-                if (dist[id] > mConfig.pointerGestureMultitouchMinDistance) {
-                    distOverThreshold += 1;
-                }
-            }
-
-            // Only transition when at least two pointers have moved further than
-            // the minimum distance threshold.
-            if (distOverThreshold >= 2) {
-                if (currentFingerCount > 2) {
-                    // There are more than two pointers, switch to FREEFORM.
-                    ALOGD_IF(DEBUG_GESTURES,
-                             "Gestures: PRESS transitioned to FREEFORM, number of pointers %d > 2",
-                             currentFingerCount);
-                    *outCancelPreviousGesture = true;
-                    mPointerGesture.currentGestureMode = PointerGesture::Mode::FREEFORM;
-                } else {
-                    // There are exactly two pointers.
-                    BitSet32 idBits(mCurrentCookedState.fingerIdBits);
-                    uint32_t id1 = idBits.clearFirstMarkedBit();
-                    uint32_t id2 = idBits.firstMarkedBit();
-                    const RawPointerData::Pointer& p1 =
-                            mCurrentRawState.rawPointerData.pointerForId(id1);
-                    const RawPointerData::Pointer& p2 =
-                            mCurrentRawState.rawPointerData.pointerForId(id2);
-                    float mutualDistance = distance(p1.x, p1.y, p2.x, p2.y);
-                    if (mutualDistance > mPointerGestureMaxSwipeWidth) {
-                        // There are two pointers but they are too far apart for a SWIPE,
-                        // switch to FREEFORM.
-                        ALOGD_IF(DEBUG_GESTURES,
-                                 "Gestures: PRESS transitioned to FREEFORM, distance %0.3f > %0.3f",
-                                 mutualDistance, mPointerGestureMaxSwipeWidth);
-                        *outCancelPreviousGesture = true;
-                        mPointerGesture.currentGestureMode = PointerGesture::Mode::FREEFORM;
-                    } else {
-                        // There are two pointers.  Wait for both pointers to start moving
-                        // before deciding whether this is a SWIPE or FREEFORM gesture.
-                        float dist1 = dist[id1];
-                        float dist2 = dist[id2];
-                        if (dist1 >= mConfig.pointerGestureMultitouchMinDistance &&
-                            dist2 >= mConfig.pointerGestureMultitouchMinDistance) {
-                            // Calculate the dot product of the displacement vectors.
-                            // When the vectors are oriented in approximately the same direction,
-                            // the angle betweeen them is near zero and the cosine of the angle
-                            // approches 1.0.  Recall that dot(v1, v2) = cos(angle) * mag(v1) *
-                            // mag(v2).
-                            PointerGesture::Delta& delta1 = mPointerGesture.referenceDeltas[id1];
-                            PointerGesture::Delta& delta2 = mPointerGesture.referenceDeltas[id2];
-                            float dx1 = delta1.dx * mPointerXZoomScale;
-                            float dy1 = delta1.dy * mPointerYZoomScale;
-                            float dx2 = delta2.dx * mPointerXZoomScale;
-                            float dy2 = delta2.dy * mPointerYZoomScale;
-                            float dot = dx1 * dx2 + dy1 * dy2;
-                            float cosine = dot / (dist1 * dist2); // denominator always > 0
-                            if (cosine >= mConfig.pointerGestureSwipeTransitionAngleCosine) {
-                                // Pointers are moving in the same direction.  Switch to SWIPE.
-                                ALOGD_IF(DEBUG_GESTURES,
-                                         "Gestures: PRESS transitioned to SWIPE, "
-                                         "dist1 %0.3f >= %0.3f, dist2 %0.3f >= %0.3f, "
-                                         "cosine %0.3f >= %0.3f",
-                                         dist1, mConfig.pointerGestureMultitouchMinDistance, dist2,
-                                         mConfig.pointerGestureMultitouchMinDistance, cosine,
-                                         mConfig.pointerGestureSwipeTransitionAngleCosine);
-                                mPointerGesture.currentGestureMode = PointerGesture::Mode::SWIPE;
-                            } else {
-                                // Pointers are moving in different directions.  Switch to FREEFORM.
-                                ALOGD_IF(DEBUG_GESTURES,
-                                         "Gestures: PRESS transitioned to FREEFORM, "
-                                         "dist1 %0.3f >= %0.3f, dist2 %0.3f >= %0.3f, "
-                                         "cosine %0.3f < %0.3f",
-                                         dist1, mConfig.pointerGestureMultitouchMinDistance, dist2,
-                                         mConfig.pointerGestureMultitouchMinDistance, cosine,
-                                         mConfig.pointerGestureSwipeTransitionAngleCosine);
-                                *outCancelPreviousGesture = true;
-                                mPointerGesture.currentGestureMode = PointerGesture::Mode::FREEFORM;
-                            }
-                        }
-                    }
-                }
-            }
-        } else if (mPointerGesture.currentGestureMode == PointerGesture::Mode::SWIPE) {
-            // Switch from SWIPE to FREEFORM if additional pointers go down.
-            // Cancel previous gesture.
-            if (currentFingerCount > 2) {
-                ALOGD_IF(DEBUG_GESTURES,
-                         "Gestures: SWIPE transitioned to FREEFORM, number of pointers %d > 2",
-                         currentFingerCount);
-                *outCancelPreviousGesture = true;
-                mPointerGesture.currentGestureMode = PointerGesture::Mode::FREEFORM;
-            }
-        }
-
-        // Move the reference points based on the overall group motion of the fingers
-        // except in PRESS mode while waiting for a transition to occur.
-        if (mPointerGesture.currentGestureMode != PointerGesture::Mode::PRESS &&
-            (commonDeltaRawX || commonDeltaRawY)) {
-            for (BitSet32 idBits(mPointerGesture.referenceIdBits); !idBits.isEmpty();) {
-                uint32_t id = idBits.clearFirstMarkedBit();
-                PointerGesture::Delta& delta = mPointerGesture.referenceDeltas[id];
-                delta.dx = 0;
-                delta.dy = 0;
-            }
-
-            mPointerGesture.referenceTouchX += commonDeltaRawX;
-            mPointerGesture.referenceTouchY += commonDeltaRawY;
-
-            float commonDeltaX = commonDeltaRawX * mPointerXMovementScale;
-            float commonDeltaY = commonDeltaRawY * mPointerYMovementScale;
-
-            rotateDelta(mInputDeviceOrientation, &commonDeltaX, &commonDeltaY);
-            mPointerVelocityControl.move(when, &commonDeltaX, &commonDeltaY);
-
-            mPointerGesture.referenceGestureX += commonDeltaX;
-            mPointerGesture.referenceGestureY += commonDeltaY;
-        }
-
-        // Report gestures.
-        if (mPointerGesture.currentGestureMode == PointerGesture::Mode::PRESS ||
-            mPointerGesture.currentGestureMode == PointerGesture::Mode::SWIPE) {
-            // PRESS or SWIPE mode.
-            ALOGD_IF(DEBUG_GESTURES,
-                     "Gestures: PRESS or SWIPE activeTouchId=%d, activeGestureId=%d, "
-                     "currentTouchPointerCount=%d",
-                     activeTouchId, mPointerGesture.activeGestureId, currentFingerCount);
-            ALOG_ASSERT(mPointerGesture.activeGestureId >= 0);
-
-            mPointerGesture.currentGestureIdBits.clear();
-            mPointerGesture.currentGestureIdBits.markBit(mPointerGesture.activeGestureId);
-            mPointerGesture.currentGestureIdToIndex[mPointerGesture.activeGestureId] = 0;
-            mPointerGesture.currentGestureProperties[0].clear();
-            mPointerGesture.currentGestureProperties[0].id = mPointerGesture.activeGestureId;
-            mPointerGesture.currentGestureProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_FINGER;
-            mPointerGesture.currentGestureCoords[0].clear();
-            mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X,
-                                                                 mPointerGesture.referenceGestureX);
-            mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y,
-                                                                 mPointerGesture.referenceGestureY);
-            mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 1.0f);
-            if (mPointerGesture.currentGestureMode == PointerGesture::Mode::SWIPE) {
-                float xOffset = static_cast<float>(commonDeltaRawX) /
-                        (mRawPointerAxes.x.maxValue - mRawPointerAxes.x.minValue);
-                float yOffset = static_cast<float>(commonDeltaRawY) /
-                        (mRawPointerAxes.y.maxValue - mRawPointerAxes.y.minValue);
-                mPointerGesture.currentGestureCoords[0]
-                        .setAxisValue(AMOTION_EVENT_AXIS_GESTURE_X_OFFSET, xOffset);
-                mPointerGesture.currentGestureCoords[0]
-                        .setAxisValue(AMOTION_EVENT_AXIS_GESTURE_Y_OFFSET, yOffset);
-            }
-        } else if (mPointerGesture.currentGestureMode == PointerGesture::Mode::FREEFORM) {
-            // FREEFORM mode.
-            ALOGD_IF(DEBUG_GESTURES,
-                     "Gestures: FREEFORM activeTouchId=%d, activeGestureId=%d, "
-                     "currentTouchPointerCount=%d",
-                     activeTouchId, mPointerGesture.activeGestureId, currentFingerCount);
-            ALOG_ASSERT(mPointerGesture.activeGestureId >= 0);
-
-            mPointerGesture.currentGestureIdBits.clear();
-
-            BitSet32 mappedTouchIdBits;
-            BitSet32 usedGestureIdBits;
-            if (mPointerGesture.lastGestureMode != PointerGesture::Mode::FREEFORM) {
-                // Initially, assign the active gesture id to the active touch point
-                // if there is one.  No other touch id bits are mapped yet.
-                if (!*outCancelPreviousGesture) {
-                    mappedTouchIdBits.markBit(activeTouchId);
-                    usedGestureIdBits.markBit(mPointerGesture.activeGestureId);
-                    mPointerGesture.freeformTouchToGestureIdMap[activeTouchId] =
-                            mPointerGesture.activeGestureId;
-                } else {
-                    mPointerGesture.activeGestureId = -1;
-                }
-            } else {
-                // Otherwise, assume we mapped all touches from the previous frame.
-                // Reuse all mappings that are still applicable.
-                mappedTouchIdBits.value = mLastCookedState.fingerIdBits.value &
-                        mCurrentCookedState.fingerIdBits.value;
-                usedGestureIdBits = mPointerGesture.lastGestureIdBits;
-
-                // Check whether we need to choose a new active gesture id because the
-                // current went went up.
-                for (BitSet32 upTouchIdBits(mLastCookedState.fingerIdBits.value &
-                                            ~mCurrentCookedState.fingerIdBits.value);
-                     !upTouchIdBits.isEmpty();) {
-                    uint32_t upTouchId = upTouchIdBits.clearFirstMarkedBit();
-                    uint32_t upGestureId = mPointerGesture.freeformTouchToGestureIdMap[upTouchId];
-                    if (upGestureId == uint32_t(mPointerGesture.activeGestureId)) {
-                        mPointerGesture.activeGestureId = -1;
-                        break;
-                    }
-                }
-            }
-
-            ALOGD_IF(DEBUG_GESTURES,
-                     "Gestures: FREEFORM follow up mappedTouchIdBits=0x%08x, "
-                     "usedGestureIdBits=0x%08x, activeGestureId=%d",
-                     mappedTouchIdBits.value, usedGestureIdBits.value,
-                     mPointerGesture.activeGestureId);
-
-            BitSet32 idBits(mCurrentCookedState.fingerIdBits);
-            for (uint32_t i = 0; i < currentFingerCount; i++) {
-                uint32_t touchId = idBits.clearFirstMarkedBit();
-                uint32_t gestureId;
-                if (!mappedTouchIdBits.hasBit(touchId)) {
-                    gestureId = usedGestureIdBits.markFirstUnmarkedBit();
-                    mPointerGesture.freeformTouchToGestureIdMap[touchId] = gestureId;
-                    ALOGD_IF(DEBUG_GESTURES,
-                             "Gestures: FREEFORM new mapping for touch id %d -> gesture id %d",
-                             touchId, gestureId);
-                } else {
-                    gestureId = mPointerGesture.freeformTouchToGestureIdMap[touchId];
-                    ALOGD_IF(DEBUG_GESTURES,
-                             "Gestures: FREEFORM existing mapping for touch id %d -> gesture id %d",
-                             touchId, gestureId);
-                }
-                mPointerGesture.currentGestureIdBits.markBit(gestureId);
-                mPointerGesture.currentGestureIdToIndex[gestureId] = i;
-
-                const RawPointerData::Pointer& pointer =
-                        mCurrentRawState.rawPointerData.pointerForId(touchId);
-                float deltaX = (pointer.x - mPointerGesture.referenceTouchX) * mPointerXZoomScale;
-                float deltaY = (pointer.y - mPointerGesture.referenceTouchY) * mPointerYZoomScale;
-                rotateDelta(mInputDeviceOrientation, &deltaX, &deltaY);
-
-                mPointerGesture.currentGestureProperties[i].clear();
-                mPointerGesture.currentGestureProperties[i].id = gestureId;
-                mPointerGesture.currentGestureProperties[i].toolType =
-                        AMOTION_EVENT_TOOL_TYPE_FINGER;
-                mPointerGesture.currentGestureCoords[i].clear();
-                mPointerGesture.currentGestureCoords[i]
-                        .setAxisValue(AMOTION_EVENT_AXIS_X,
-                                      mPointerGesture.referenceGestureX + deltaX);
-                mPointerGesture.currentGestureCoords[i]
-                        .setAxisValue(AMOTION_EVENT_AXIS_Y,
-                                      mPointerGesture.referenceGestureY + deltaY);
-                mPointerGesture.currentGestureCoords[i].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE,
-                                                                     1.0f);
-            }
-
-            if (mPointerGesture.activeGestureId < 0) {
-                mPointerGesture.activeGestureId =
-                        mPointerGesture.currentGestureIdBits.firstMarkedBit();
-                ALOGD_IF(DEBUG_GESTURES, "Gestures: FREEFORM new activeGestureId=%d",
-                         mPointerGesture.activeGestureId);
-            }
-        }
+        prepareMultiFingerPointerGestures(when, outCancelPreviousGesture, outFinishPreviousGesture);
     }
 
     mPointerController->setButtonState(mCurrentRawState.buttonState);
@@ -3485,6 +3067,399 @@
     return true;
 }
 
+bool TouchInputMapper::checkForTouchpadQuietTime(nsecs_t when) {
+    if (mPointerGesture.activeTouchId < 0) {
+        mPointerGesture.resetQuietTime();
+        return false;
+    }
+
+    if (when < mPointerGesture.quietTime + mConfig.pointerGestureQuietInterval) {
+        return true;
+    }
+
+    const uint32_t currentFingerCount = mCurrentCookedState.fingerIdBits.count();
+    bool isQuietTime = false;
+    if ((mPointerGesture.lastGestureMode == PointerGesture::Mode::PRESS ||
+         mPointerGesture.lastGestureMode == PointerGesture::Mode::SWIPE ||
+         mPointerGesture.lastGestureMode == PointerGesture::Mode::FREEFORM) &&
+        currentFingerCount < 2) {
+        // Enter quiet time when exiting swipe or freeform state.
+        // This is to prevent accidentally entering the hover state and flinging the
+        // pointer when finishing a swipe and there is still one pointer left onscreen.
+        isQuietTime = true;
+    } else if (mPointerGesture.lastGestureMode == PointerGesture::Mode::BUTTON_CLICK_OR_DRAG &&
+               currentFingerCount >= 2 && !isPointerDown(mCurrentRawState.buttonState)) {
+        // Enter quiet time when releasing the button and there are still two or more
+        // fingers down.  This may indicate that one finger was used to press the button
+        // but it has not gone up yet.
+        isQuietTime = true;
+    }
+    if (isQuietTime) {
+        mPointerGesture.quietTime = when;
+    }
+    return isQuietTime;
+}
+
+std::pair<int32_t, float> TouchInputMapper::getFastestFinger() {
+    int32_t bestId = -1;
+    float bestSpeed = mConfig.pointerGestureDragMinSwitchSpeed;
+    for (BitSet32 idBits(mCurrentCookedState.fingerIdBits); !idBits.isEmpty();) {
+        uint32_t id = idBits.clearFirstMarkedBit();
+        std::optional<float> vx =
+                mPointerGesture.velocityTracker.getVelocity(AMOTION_EVENT_AXIS_X, id);
+        std::optional<float> vy =
+                mPointerGesture.velocityTracker.getVelocity(AMOTION_EVENT_AXIS_Y, id);
+        if (vx && vy) {
+            float speed = hypotf(*vx, *vy);
+            if (speed > bestSpeed) {
+                bestId = id;
+                bestSpeed = speed;
+            }
+        }
+    }
+    return std::make_pair(bestId, bestSpeed);
+}
+
+void TouchInputMapper::prepareMultiFingerPointerGestures(nsecs_t when, bool* cancelPreviousGesture,
+                                                         bool* finishPreviousGesture) {
+    // We need to provide feedback for each finger that goes down so we cannot wait for the fingers
+    // to move before deciding what to do.
+    //
+    // The ambiguous case is deciding what to do when there are two fingers down but they have not
+    // moved enough to determine whether they are part of a drag or part of a freeform gesture, or
+    // just a press or long-press at the pointer location.
+    //
+    // When there are two fingers we start with the PRESS hypothesis and we generate a down at the
+    // pointer location.
+    //
+    // When the two fingers move enough or when additional fingers are added, we make a decision to
+    // transition into SWIPE or FREEFORM mode accordingly.
+    const int32_t activeTouchId = mPointerGesture.activeTouchId;
+    ALOG_ASSERT(activeTouchId >= 0);
+
+    const uint32_t currentFingerCount = mCurrentCookedState.fingerIdBits.count();
+    const uint32_t lastFingerCount = mLastCookedState.fingerIdBits.count();
+    bool settled =
+            when >= mPointerGesture.firstTouchTime + mConfig.pointerGestureMultitouchSettleInterval;
+    if (mPointerGesture.lastGestureMode != PointerGesture::Mode::PRESS &&
+        mPointerGesture.lastGestureMode != PointerGesture::Mode::SWIPE &&
+        mPointerGesture.lastGestureMode != PointerGesture::Mode::FREEFORM) {
+        *finishPreviousGesture = true;
+    } else if (!settled && currentFingerCount > lastFingerCount) {
+        // Additional pointers have gone down but not yet settled.
+        // Reset the gesture.
+        ALOGD_IF(DEBUG_GESTURES,
+                 "Gestures: Resetting gesture since additional pointers went down for "
+                 "MULTITOUCH, settle time remaining %0.3fms",
+                 (mPointerGesture.firstTouchTime + mConfig.pointerGestureMultitouchSettleInterval -
+                  when) * 0.000001f);
+        *cancelPreviousGesture = true;
+    } else {
+        // Continue previous gesture.
+        mPointerGesture.currentGestureMode = mPointerGesture.lastGestureMode;
+    }
+
+    if (*finishPreviousGesture || *cancelPreviousGesture) {
+        mPointerGesture.currentGestureMode = PointerGesture::Mode::PRESS;
+        mPointerGesture.activeGestureId = 0;
+        mPointerGesture.referenceIdBits.clear();
+        mPointerVelocityControl.reset();
+
+        // Use the centroid and pointer location as the reference points for the gesture.
+        ALOGD_IF(DEBUG_GESTURES,
+                 "Gestures: Using centroid as reference for MULTITOUCH, settle time remaining "
+                 "%0.3fms",
+                 (mPointerGesture.firstTouchTime + mConfig.pointerGestureMultitouchSettleInterval -
+                  when) * 0.000001f);
+        mCurrentRawState.rawPointerData
+                .getCentroidOfTouchingPointers(&mPointerGesture.referenceTouchX,
+                                               &mPointerGesture.referenceTouchY);
+        mPointerController->getPosition(&mPointerGesture.referenceGestureX,
+                                        &mPointerGesture.referenceGestureY);
+    }
+
+    // Clear the reference deltas for fingers not yet included in the reference calculation.
+    for (BitSet32 idBits(mCurrentCookedState.fingerIdBits.value &
+                         ~mPointerGesture.referenceIdBits.value);
+         !idBits.isEmpty();) {
+        uint32_t id = idBits.clearFirstMarkedBit();
+        mPointerGesture.referenceDeltas[id].dx = 0;
+        mPointerGesture.referenceDeltas[id].dy = 0;
+    }
+    mPointerGesture.referenceIdBits = mCurrentCookedState.fingerIdBits;
+
+    // Add delta for all fingers and calculate a common movement delta.
+    int32_t commonDeltaRawX = 0, commonDeltaRawY = 0;
+    BitSet32 commonIdBits(mLastCookedState.fingerIdBits.value &
+                          mCurrentCookedState.fingerIdBits.value);
+    for (BitSet32 idBits(commonIdBits); !idBits.isEmpty();) {
+        bool first = (idBits == commonIdBits);
+        uint32_t id = idBits.clearFirstMarkedBit();
+        const RawPointerData::Pointer& cpd = mCurrentRawState.rawPointerData.pointerForId(id);
+        const RawPointerData::Pointer& lpd = mLastRawState.rawPointerData.pointerForId(id);
+        PointerGesture::Delta& delta = mPointerGesture.referenceDeltas[id];
+        delta.dx += cpd.x - lpd.x;
+        delta.dy += cpd.y - lpd.y;
+
+        if (first) {
+            commonDeltaRawX = delta.dx;
+            commonDeltaRawY = delta.dy;
+        } else {
+            commonDeltaRawX = calculateCommonVector(commonDeltaRawX, delta.dx);
+            commonDeltaRawY = calculateCommonVector(commonDeltaRawY, delta.dy);
+        }
+    }
+
+    // Consider transitions from PRESS to SWIPE or MULTITOUCH.
+    if (mPointerGesture.currentGestureMode == PointerGesture::Mode::PRESS) {
+        float dist[MAX_POINTER_ID + 1];
+        int32_t distOverThreshold = 0;
+        for (BitSet32 idBits(mPointerGesture.referenceIdBits); !idBits.isEmpty();) {
+            uint32_t id = idBits.clearFirstMarkedBit();
+            PointerGesture::Delta& delta = mPointerGesture.referenceDeltas[id];
+            dist[id] = hypotf(delta.dx * mPointerXZoomScale, delta.dy * mPointerYZoomScale);
+            if (dist[id] > mConfig.pointerGestureMultitouchMinDistance) {
+                distOverThreshold += 1;
+            }
+        }
+
+        // Only transition when at least two pointers have moved further than
+        // the minimum distance threshold.
+        if (distOverThreshold >= 2) {
+            if (currentFingerCount > 2) {
+                // There are more than two pointers, switch to FREEFORM.
+                ALOGD_IF(DEBUG_GESTURES,
+                         "Gestures: PRESS transitioned to FREEFORM, number of pointers %d > 2",
+                         currentFingerCount);
+                *cancelPreviousGesture = true;
+                mPointerGesture.currentGestureMode = PointerGesture::Mode::FREEFORM;
+            } else {
+                // There are exactly two pointers.
+                BitSet32 idBits(mCurrentCookedState.fingerIdBits);
+                uint32_t id1 = idBits.clearFirstMarkedBit();
+                uint32_t id2 = idBits.firstMarkedBit();
+                const RawPointerData::Pointer& p1 =
+                        mCurrentRawState.rawPointerData.pointerForId(id1);
+                const RawPointerData::Pointer& p2 =
+                        mCurrentRawState.rawPointerData.pointerForId(id2);
+                float mutualDistance = distance(p1.x, p1.y, p2.x, p2.y);
+                if (mutualDistance > mPointerGestureMaxSwipeWidth) {
+                    // There are two pointers but they are too far apart for a SWIPE,
+                    // switch to FREEFORM.
+                    ALOGD_IF(DEBUG_GESTURES,
+                             "Gestures: PRESS transitioned to FREEFORM, distance %0.3f > %0.3f",
+                             mutualDistance, mPointerGestureMaxSwipeWidth);
+                    *cancelPreviousGesture = true;
+                    mPointerGesture.currentGestureMode = PointerGesture::Mode::FREEFORM;
+                } else {
+                    // There are two pointers.  Wait for both pointers to start moving
+                    // before deciding whether this is a SWIPE or FREEFORM gesture.
+                    float dist1 = dist[id1];
+                    float dist2 = dist[id2];
+                    if (dist1 >= mConfig.pointerGestureMultitouchMinDistance &&
+                        dist2 >= mConfig.pointerGestureMultitouchMinDistance) {
+                        // Calculate the dot product of the displacement vectors.
+                        // When the vectors are oriented in approximately the same direction,
+                        // the angle betweeen them is near zero and the cosine of the angle
+                        // approaches 1.0.  Recall that dot(v1, v2) = cos(angle) * mag(v1) *
+                        // mag(v2).
+                        PointerGesture::Delta& delta1 = mPointerGesture.referenceDeltas[id1];
+                        PointerGesture::Delta& delta2 = mPointerGesture.referenceDeltas[id2];
+                        float dx1 = delta1.dx * mPointerXZoomScale;
+                        float dy1 = delta1.dy * mPointerYZoomScale;
+                        float dx2 = delta2.dx * mPointerXZoomScale;
+                        float dy2 = delta2.dy * mPointerYZoomScale;
+                        float dot = dx1 * dx2 + dy1 * dy2;
+                        float cosine = dot / (dist1 * dist2); // denominator always > 0
+                        if (cosine >= mConfig.pointerGestureSwipeTransitionAngleCosine) {
+                            // Pointers are moving in the same direction.  Switch to SWIPE.
+                            ALOGD_IF(DEBUG_GESTURES,
+                                     "Gestures: PRESS transitioned to SWIPE, "
+                                     "dist1 %0.3f >= %0.3f, dist2 %0.3f >= %0.3f, "
+                                     "cosine %0.3f >= %0.3f",
+                                     dist1, mConfig.pointerGestureMultitouchMinDistance, dist2,
+                                     mConfig.pointerGestureMultitouchMinDistance, cosine,
+                                     mConfig.pointerGestureSwipeTransitionAngleCosine);
+                            mPointerGesture.currentGestureMode = PointerGesture::Mode::SWIPE;
+                        } else {
+                            // Pointers are moving in different directions.  Switch to FREEFORM.
+                            ALOGD_IF(DEBUG_GESTURES,
+                                     "Gestures: PRESS transitioned to FREEFORM, "
+                                     "dist1 %0.3f >= %0.3f, dist2 %0.3f >= %0.3f, "
+                                     "cosine %0.3f < %0.3f",
+                                     dist1, mConfig.pointerGestureMultitouchMinDistance, dist2,
+                                     mConfig.pointerGestureMultitouchMinDistance, cosine,
+                                     mConfig.pointerGestureSwipeTransitionAngleCosine);
+                            *cancelPreviousGesture = true;
+                            mPointerGesture.currentGestureMode = PointerGesture::Mode::FREEFORM;
+                        }
+                    }
+                }
+            }
+        }
+    } else if (mPointerGesture.currentGestureMode == PointerGesture::Mode::SWIPE) {
+        // Switch from SWIPE to FREEFORM if additional pointers go down.
+        // Cancel previous gesture.
+        if (currentFingerCount > 2) {
+            ALOGD_IF(DEBUG_GESTURES,
+                     "Gestures: SWIPE transitioned to FREEFORM, number of pointers %d > 2",
+                     currentFingerCount);
+            *cancelPreviousGesture = true;
+            mPointerGesture.currentGestureMode = PointerGesture::Mode::FREEFORM;
+        }
+    }
+
+    // Move the reference points based on the overall group motion of the fingers
+    // except in PRESS mode while waiting for a transition to occur.
+    if (mPointerGesture.currentGestureMode != PointerGesture::Mode::PRESS &&
+        (commonDeltaRawX || commonDeltaRawY)) {
+        for (BitSet32 idBits(mPointerGesture.referenceIdBits); !idBits.isEmpty();) {
+            uint32_t id = idBits.clearFirstMarkedBit();
+            PointerGesture::Delta& delta = mPointerGesture.referenceDeltas[id];
+            delta.dx = 0;
+            delta.dy = 0;
+        }
+
+        mPointerGesture.referenceTouchX += commonDeltaRawX;
+        mPointerGesture.referenceTouchY += commonDeltaRawY;
+
+        float commonDeltaX = commonDeltaRawX * mPointerXMovementScale;
+        float commonDeltaY = commonDeltaRawY * mPointerYMovementScale;
+
+        rotateDelta(mInputDeviceOrientation, &commonDeltaX, &commonDeltaY);
+        mPointerVelocityControl.move(when, &commonDeltaX, &commonDeltaY);
+
+        mPointerGesture.referenceGestureX += commonDeltaX;
+        mPointerGesture.referenceGestureY += commonDeltaY;
+    }
+
+    // Report gestures.
+    if (mPointerGesture.currentGestureMode == PointerGesture::Mode::PRESS ||
+        mPointerGesture.currentGestureMode == PointerGesture::Mode::SWIPE) {
+        // PRESS or SWIPE mode.
+        ALOGD_IF(DEBUG_GESTURES,
+                 "Gestures: PRESS or SWIPE activeTouchId=%d, activeGestureId=%d, "
+                 "currentTouchPointerCount=%d",
+                 activeTouchId, mPointerGesture.activeGestureId, currentFingerCount);
+        ALOG_ASSERT(mPointerGesture.activeGestureId >= 0);
+
+        mPointerGesture.currentGestureIdBits.clear();
+        mPointerGesture.currentGestureIdBits.markBit(mPointerGesture.activeGestureId);
+        mPointerGesture.currentGestureIdToIndex[mPointerGesture.activeGestureId] = 0;
+        mPointerGesture.currentGestureProperties[0].clear();
+        mPointerGesture.currentGestureProperties[0].id = mPointerGesture.activeGestureId;
+        mPointerGesture.currentGestureProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_FINGER;
+        mPointerGesture.currentGestureCoords[0].clear();
+        mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X,
+                                                             mPointerGesture.referenceGestureX);
+        mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y,
+                                                             mPointerGesture.referenceGestureY);
+        mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 1.0f);
+        if (mPointerGesture.currentGestureMode == PointerGesture::Mode::SWIPE) {
+            float xOffset = static_cast<float>(commonDeltaRawX) /
+                    (mRawPointerAxes.x.maxValue - mRawPointerAxes.x.minValue);
+            float yOffset = static_cast<float>(commonDeltaRawY) /
+                    (mRawPointerAxes.y.maxValue - mRawPointerAxes.y.minValue);
+            mPointerGesture.currentGestureCoords[0]
+                    .setAxisValue(AMOTION_EVENT_AXIS_GESTURE_X_OFFSET, xOffset);
+            mPointerGesture.currentGestureCoords[0]
+                    .setAxisValue(AMOTION_EVENT_AXIS_GESTURE_Y_OFFSET, yOffset);
+        }
+    } else if (mPointerGesture.currentGestureMode == PointerGesture::Mode::FREEFORM) {
+        // FREEFORM mode.
+        ALOGD_IF(DEBUG_GESTURES,
+                 "Gestures: FREEFORM activeTouchId=%d, activeGestureId=%d, "
+                 "currentTouchPointerCount=%d",
+                 activeTouchId, mPointerGesture.activeGestureId, currentFingerCount);
+        ALOG_ASSERT(mPointerGesture.activeGestureId >= 0);
+
+        mPointerGesture.currentGestureIdBits.clear();
+
+        BitSet32 mappedTouchIdBits;
+        BitSet32 usedGestureIdBits;
+        if (mPointerGesture.lastGestureMode != PointerGesture::Mode::FREEFORM) {
+            // Initially, assign the active gesture id to the active touch point
+            // if there is one.  No other touch id bits are mapped yet.
+            if (!*cancelPreviousGesture) {
+                mappedTouchIdBits.markBit(activeTouchId);
+                usedGestureIdBits.markBit(mPointerGesture.activeGestureId);
+                mPointerGesture.freeformTouchToGestureIdMap[activeTouchId] =
+                        mPointerGesture.activeGestureId;
+            } else {
+                mPointerGesture.activeGestureId = -1;
+            }
+        } else {
+            // Otherwise, assume we mapped all touches from the previous frame.
+            // Reuse all mappings that are still applicable.
+            mappedTouchIdBits.value =
+                    mLastCookedState.fingerIdBits.value & mCurrentCookedState.fingerIdBits.value;
+            usedGestureIdBits = mPointerGesture.lastGestureIdBits;
+
+            // Check whether we need to choose a new active gesture id because the
+            // current went went up.
+            for (BitSet32 upTouchIdBits(mLastCookedState.fingerIdBits.value &
+                                        ~mCurrentCookedState.fingerIdBits.value);
+                 !upTouchIdBits.isEmpty();) {
+                uint32_t upTouchId = upTouchIdBits.clearFirstMarkedBit();
+                uint32_t upGestureId = mPointerGesture.freeformTouchToGestureIdMap[upTouchId];
+                if (upGestureId == uint32_t(mPointerGesture.activeGestureId)) {
+                    mPointerGesture.activeGestureId = -1;
+                    break;
+                }
+            }
+        }
+
+        ALOGD_IF(DEBUG_GESTURES,
+                 "Gestures: FREEFORM follow up mappedTouchIdBits=0x%08x, usedGestureIdBits=0x%08x, "
+                 "activeGestureId=%d",
+                 mappedTouchIdBits.value, usedGestureIdBits.value, mPointerGesture.activeGestureId);
+
+        BitSet32 idBits(mCurrentCookedState.fingerIdBits);
+        for (uint32_t i = 0; i < currentFingerCount; i++) {
+            uint32_t touchId = idBits.clearFirstMarkedBit();
+            uint32_t gestureId;
+            if (!mappedTouchIdBits.hasBit(touchId)) {
+                gestureId = usedGestureIdBits.markFirstUnmarkedBit();
+                mPointerGesture.freeformTouchToGestureIdMap[touchId] = gestureId;
+                ALOGD_IF(DEBUG_GESTURES,
+                         "Gestures: FREEFORM new mapping for touch id %d -> gesture id %d", touchId,
+                         gestureId);
+            } else {
+                gestureId = mPointerGesture.freeformTouchToGestureIdMap[touchId];
+                ALOGD_IF(DEBUG_GESTURES,
+                         "Gestures: FREEFORM existing mapping for touch id %d -> gesture id %d",
+                         touchId, gestureId);
+            }
+            mPointerGesture.currentGestureIdBits.markBit(gestureId);
+            mPointerGesture.currentGestureIdToIndex[gestureId] = i;
+
+            const RawPointerData::Pointer& pointer =
+                    mCurrentRawState.rawPointerData.pointerForId(touchId);
+            float deltaX = (pointer.x - mPointerGesture.referenceTouchX) * mPointerXZoomScale;
+            float deltaY = (pointer.y - mPointerGesture.referenceTouchY) * mPointerYZoomScale;
+            rotateDelta(mInputDeviceOrientation, &deltaX, &deltaY);
+
+            mPointerGesture.currentGestureProperties[i].clear();
+            mPointerGesture.currentGestureProperties[i].id = gestureId;
+            mPointerGesture.currentGestureProperties[i].toolType = AMOTION_EVENT_TOOL_TYPE_FINGER;
+            mPointerGesture.currentGestureCoords[i].clear();
+            mPointerGesture.currentGestureCoords[i].setAxisValue(AMOTION_EVENT_AXIS_X,
+                                                                 mPointerGesture.referenceGestureX +
+                                                                         deltaX);
+            mPointerGesture.currentGestureCoords[i].setAxisValue(AMOTION_EVENT_AXIS_Y,
+                                                                 mPointerGesture.referenceGestureY +
+                                                                         deltaY);
+            mPointerGesture.currentGestureCoords[i].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 1.0f);
+        }
+
+        if (mPointerGesture.activeGestureId < 0) {
+            mPointerGesture.activeGestureId = mPointerGesture.currentGestureIdBits.firstMarkedBit();
+            ALOGD_IF(DEBUG_GESTURES, "Gestures: FREEFORM new activeGestureId=%d",
+                     mPointerGesture.activeGestureId);
+        }
+    }
+}
+
 void TouchInputMapper::moveMousePointerFromPointerDelta(nsecs_t when, uint32_t pointerId) {
     const RawPointerData::Pointer& currentPointer =
             mCurrentRawState.rawPointerData.pointerForId(pointerId);
@@ -3588,6 +3563,8 @@
 std::list<NotifyArgs> TouchInputMapper::dispatchPointerSimple(nsecs_t when, nsecs_t readTime,
                                                               uint32_t policyFlags, bool down,
                                                               bool hovering) {
+    LOG_ALWAYS_FATAL_IF(mDeviceMode != DeviceMode::POINTER,
+                        "%s cannot be used when the device is not in POINTER mode.", __func__);
     std::list<NotifyArgs> out;
     int32_t metaState = getContext()->getGlobalMetaState();
 
@@ -3714,6 +3691,10 @@
     if (down || hovering) {
         mPointerSimple.lastCoords.copyFrom(mPointerSimple.currentCoords);
         mPointerSimple.lastProperties.copyFrom(mPointerSimple.currentProperties);
+        mPointerSimple.displayId = displayId;
+        mPointerSimple.source = mSource;
+        mPointerSimple.lastCursorX = xCursorPosition;
+        mPointerSimple.lastCursorY = yCursorPosition;
     } else {
         mPointerSimple.reset();
     }
@@ -3722,17 +3703,32 @@
 
 std::list<NotifyArgs> TouchInputMapper::abortPointerSimple(nsecs_t when, nsecs_t readTime,
                                                            uint32_t policyFlags) {
-    mPointerSimple.currentCoords.clear();
-    mPointerSimple.currentProperties.clear();
-
-    return dispatchPointerSimple(when, readTime, policyFlags, false, false);
+    std::list<NotifyArgs> out;
+    if (mPointerSimple.down || mPointerSimple.hovering) {
+        int32_t metaState = getContext()->getGlobalMetaState();
+        out.push_back(NotifyMotionArgs(getContext()->getNextId(), when, readTime, getDeviceId(),
+                                       mPointerSimple.source, mPointerSimple.displayId, policyFlags,
+                                       AMOTION_EVENT_ACTION_CANCEL, 0, AMOTION_EVENT_FLAG_CANCELED,
+                                       metaState, mLastRawState.buttonState,
+                                       MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, 1,
+                                       &mPointerSimple.lastProperties, &mPointerSimple.lastCoords,
+                                       mOrientedXPrecision, mOrientedYPrecision,
+                                       mPointerSimple.lastCursorX, mPointerSimple.lastCursorY,
+                                       mPointerSimple.downTime,
+                                       /* videoFrames */ {}));
+        if (mPointerController != nullptr) {
+            mPointerController->fade(PointerControllerInterface::Transition::GRADUAL);
+        }
+    }
+    mPointerSimple.reset();
+    return out;
 }
 
 NotifyMotionArgs TouchInputMapper::dispatchMotion(
         nsecs_t when, nsecs_t readTime, uint32_t policyFlags, uint32_t source, int32_t action,
         int32_t actionButton, int32_t flags, int32_t metaState, int32_t buttonState,
-        int32_t edgeFlags, const PointerProperties* properties, const PointerCoords* coords,
-        const uint32_t* idToIndex, BitSet32 idBits, int32_t changedId, float xPrecision,
+        int32_t edgeFlags, const PropertiesArray& properties, const CoordsArray& coords,
+        const IdToIndexArray& idToIndex, BitSet32 idBits, int32_t changedId, float xPrecision,
         float yPrecision, nsecs_t downTime, MotionClassification classification) {
     PointerCoords pointerCoords[MAX_POINTERS];
     PointerProperties pointerProperties[MAX_POINTERS];
@@ -3786,36 +3782,6 @@
                             downTime, std::move(frames));
 }
 
-bool TouchInputMapper::updateMovedPointers(const PointerProperties* inProperties,
-                                           const PointerCoords* inCoords,
-                                           const uint32_t* inIdToIndex,
-                                           PointerProperties* outProperties,
-                                           PointerCoords* outCoords, const uint32_t* outIdToIndex,
-                                           BitSet32 idBits) const {
-    bool changed = false;
-    while (!idBits.isEmpty()) {
-        uint32_t id = idBits.clearFirstMarkedBit();
-        uint32_t inIndex = inIdToIndex[id];
-        uint32_t outIndex = outIdToIndex[id];
-
-        const PointerProperties& curInProperties = inProperties[inIndex];
-        const PointerCoords& curInCoords = inCoords[inIndex];
-        PointerProperties& curOutProperties = outProperties[outIndex];
-        PointerCoords& curOutCoords = outCoords[outIndex];
-
-        if (curInProperties != curOutProperties) {
-            curOutProperties.copyFrom(curInProperties);
-            changed = true;
-        }
-
-        if (curInCoords != curOutCoords) {
-            curOutCoords.copyFrom(curInCoords);
-            changed = true;
-        }
-    }
-    return changed;
-}
-
 std::list<NotifyArgs> TouchInputMapper::cancelTouch(nsecs_t when, nsecs_t readTime) {
     std::list<NotifyArgs> out;
     out += abortPointerUsage(when, readTime, 0 /*policyFlags*/);
@@ -3837,19 +3803,19 @@
     // 180 - reverse x, y.
     // 270 - swap x/y and reverse x.
     switch (mInputDeviceOrientation) {
-        case DISPLAY_ORIENTATION_0:
+        case ui::ROTATION_0:
             x = xScaled;
             y = yScaled;
             break;
-        case DISPLAY_ORIENTATION_90:
+        case ui::ROTATION_90:
             y = xScaledMax;
             x = yScaled;
             break;
-        case DISPLAY_ORIENTATION_180:
+        case ui::ROTATION_180:
             x = xScaledMax;
             y = yScaledMax;
             break;
-        case DISPLAY_ORIENTATION_270:
+        case ui::ROTATION_270:
             y = xScaled;
             x = yScaledMax;
             break;
@@ -3863,9 +3829,8 @@
     const float yScaled = (y - mRawPointerAxes.y.minValue) * mYScale;
 
     return x >= mRawPointerAxes.x.minValue && x <= mRawPointerAxes.x.maxValue &&
-            xScaled >= mPhysicalLeft && xScaled <= (mPhysicalLeft + mPhysicalWidth) &&
             y >= mRawPointerAxes.y.minValue && y <= mRawPointerAxes.y.maxValue &&
-            yScaled >= mPhysicalTop && yScaled <= (mPhysicalTop + mPhysicalHeight);
+            isPointInRect(mPhysicalFrameInDisplay, xScaled, yScaled);
 }
 
 const TouchInputMapper::VirtualKey* TouchInputMapper::findVirtualKeyHit(int32_t x, int32_t y) {
diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.h b/services/inputflinger/reader/mapper/TouchInputMapper.h
index 50f30c8..34ba625 100644
--- a/services/inputflinger/reader/mapper/TouchInputMapper.h
+++ b/services/inputflinger/reader/mapper/TouchInputMapper.h
@@ -17,6 +17,7 @@
 #pragma once
 
 #include <stdint.h>
+#include <ui/Rotation.h>
 
 #include "CursorButtonAccumulator.h"
 #include "CursorScrollAccumulator.h"
@@ -27,55 +28,65 @@
 
 namespace android {
 
+// Maximum amount of latency to add to touch events while waiting for data from an
+// external stylus.
+static constexpr nsecs_t EXTERNAL_STYLUS_DATA_TIMEOUT = ms2ns(72);
+
+// Maximum amount of time to wait on touch data before pushing out new pressure data.
+static constexpr nsecs_t TOUCH_DATA_TIMEOUT = ms2ns(20);
+
 /* Raw axis information from the driver. */
 struct RawPointerAxes {
-    RawAbsoluteAxisInfo x;
-    RawAbsoluteAxisInfo y;
-    RawAbsoluteAxisInfo pressure;
-    RawAbsoluteAxisInfo touchMajor;
-    RawAbsoluteAxisInfo touchMinor;
-    RawAbsoluteAxisInfo toolMajor;
-    RawAbsoluteAxisInfo toolMinor;
-    RawAbsoluteAxisInfo orientation;
-    RawAbsoluteAxisInfo distance;
-    RawAbsoluteAxisInfo tiltX;
-    RawAbsoluteAxisInfo tiltY;
-    RawAbsoluteAxisInfo trackingId;
-    RawAbsoluteAxisInfo slot;
+    RawAbsoluteAxisInfo x{};
+    RawAbsoluteAxisInfo y{};
+    RawAbsoluteAxisInfo pressure{};
+    RawAbsoluteAxisInfo touchMajor{};
+    RawAbsoluteAxisInfo touchMinor{};
+    RawAbsoluteAxisInfo toolMajor{};
+    RawAbsoluteAxisInfo toolMinor{};
+    RawAbsoluteAxisInfo orientation{};
+    RawAbsoluteAxisInfo distance{};
+    RawAbsoluteAxisInfo tiltX{};
+    RawAbsoluteAxisInfo tiltY{};
+    RawAbsoluteAxisInfo trackingId{};
+    RawAbsoluteAxisInfo slot{};
 
-    RawPointerAxes();
     inline int32_t getRawWidth() const { return x.maxValue - x.minValue + 1; }
     inline int32_t getRawHeight() const { return y.maxValue - y.minValue + 1; }
-    void clear();
+    inline void clear() { *this = RawPointerAxes(); }
 };
 
+using PropertiesArray = std::array<PointerProperties, MAX_POINTERS>;
+using CoordsArray = std::array<PointerCoords, MAX_POINTERS>;
+using IdToIndexArray = std::array<uint32_t, MAX_POINTER_ID + 1>;
+
 /* Raw data for a collection of pointers including a pointer id mapping table. */
 struct RawPointerData {
     struct Pointer {
-        uint32_t id;
-        int32_t x;
-        int32_t y;
-        int32_t pressure;
-        int32_t touchMajor;
-        int32_t touchMinor;
-        int32_t toolMajor;
-        int32_t toolMinor;
-        int32_t orientation;
-        int32_t distance;
-        int32_t tiltX;
-        int32_t tiltY;
-        int32_t toolType; // a fully decoded AMOTION_EVENT_TOOL_TYPE constant
-        bool isHovering;
+        uint32_t id{0xFFFFFFFF};
+        int32_t x{};
+        int32_t y{};
+        int32_t pressure{};
+        int32_t touchMajor{};
+        int32_t touchMinor{};
+        int32_t toolMajor{};
+        int32_t toolMinor{};
+        int32_t orientation{};
+        int32_t distance{};
+        int32_t tiltX{};
+        int32_t tiltY{};
+        // A fully decoded AMOTION_EVENT_TOOL_TYPE constant.
+        int32_t toolType{AMOTION_EVENT_TOOL_TYPE_UNKNOWN};
+        bool isHovering{false};
     };
 
-    uint32_t pointerCount;
-    Pointer pointers[MAX_POINTERS];
-    BitSet32 hoveringIdBits, touchingIdBits, canceledIdBits;
-    uint32_t idToIndex[MAX_POINTER_ID + 1];
+    uint32_t pointerCount{};
+    std::array<Pointer, MAX_POINTERS> pointers{};
+    BitSet32 hoveringIdBits{}, touchingIdBits{}, canceledIdBits{};
+    IdToIndexArray idToIndex{};
 
-    RawPointerData();
-    void clear();
-    void copyFrom(const RawPointerData& other);
+    inline void clear() { *this = RawPointerData(); }
+
     void getCentroidOfTouchingPointers(float* outX, float* outY) const;
 
     inline void markIdBit(uint32_t id, bool isHovering) {
@@ -99,15 +110,13 @@
 
 /* Cooked data for a collection of pointers including a pointer id mapping table. */
 struct CookedPointerData {
-    uint32_t pointerCount;
-    PointerProperties pointerProperties[MAX_POINTERS];
-    PointerCoords pointerCoords[MAX_POINTERS];
-    BitSet32 hoveringIdBits, touchingIdBits, canceledIdBits, validIdBits;
-    uint32_t idToIndex[MAX_POINTER_ID + 1];
+    uint32_t pointerCount{};
+    PropertiesArray pointerProperties{};
+    CoordsArray pointerCoords{};
+    BitSet32 hoveringIdBits{}, touchingIdBits{}, canceledIdBits{}, validIdBits{};
+    IdToIndexArray idToIndex{};
 
-    CookedPointerData();
-    void clear();
-    void copyFrom(const CookedPointerData& other);
+    inline void clear() { *this = CookedPointerData(); }
 
     inline const PointerCoords& pointerCoordsForId(uint32_t id) const {
         return pointerCoords[idToIndex[id]];
@@ -210,15 +219,7 @@
         bool associatedDisplayIsExternal;
         bool orientationAware;
 
-        enum class Orientation : int32_t {
-            ORIENTATION_0 = DISPLAY_ORIENTATION_0,
-            ORIENTATION_90 = DISPLAY_ORIENTATION_90,
-            ORIENTATION_180 = DISPLAY_ORIENTATION_180,
-            ORIENTATION_270 = DISPLAY_ORIENTATION_270,
-
-            ftl_last = ORIENTATION_270
-        };
-        Orientation orientation;
+        ui::Rotation orientation;
 
         bool hasButtonUnderPad;
         std::string uniqueDisplayId;
@@ -232,6 +233,12 @@
         GestureMode gestureMode;
 
         bool wake;
+
+        // Whether the device supports the Universal Stylus Initiative (USI) protocol for styluses.
+        bool supportsUsi;
+
+        // Allows touches while the display is off.
+        bool enableForInactiveViewport;
     } mParameters;
 
     // Immutable calibration parameters in parsed form.
@@ -311,65 +318,33 @@
     RawPointerAxes mRawPointerAxes;
 
     struct RawState {
-        nsecs_t when;
-        nsecs_t readTime;
+        nsecs_t when{std::numeric_limits<nsecs_t>::min()};
+        nsecs_t readTime{};
 
         // Raw pointer sample data.
-        RawPointerData rawPointerData;
+        RawPointerData rawPointerData{};
 
-        int32_t buttonState;
+        int32_t buttonState{};
 
         // Scroll state.
-        int32_t rawVScroll;
-        int32_t rawHScroll;
+        int32_t rawVScroll{};
+        int32_t rawHScroll{};
 
-        explicit inline RawState() { clear(); }
-
-        void copyFrom(const RawState& other) {
-            when = other.when;
-            readTime = other.readTime;
-            rawPointerData.copyFrom(other.rawPointerData);
-            buttonState = other.buttonState;
-            rawVScroll = other.rawVScroll;
-            rawHScroll = other.rawHScroll;
-        }
-
-        void clear() {
-            when = 0;
-            readTime = 0;
-            rawPointerData.clear();
-            buttonState = 0;
-            rawVScroll = 0;
-            rawHScroll = 0;
-        }
+        inline void clear() { *this = RawState(); }
     };
 
     struct CookedState {
         // Cooked pointer sample data.
-        CookedPointerData cookedPointerData;
+        CookedPointerData cookedPointerData{};
 
         // Id bits used to differentiate fingers, stylus and mouse tools.
-        BitSet32 fingerIdBits;
-        BitSet32 stylusIdBits;
-        BitSet32 mouseIdBits;
+        BitSet32 fingerIdBits{};
+        BitSet32 stylusIdBits{};
+        BitSet32 mouseIdBits{};
 
-        int32_t buttonState;
+        int32_t buttonState{};
 
-        void copyFrom(const CookedState& other) {
-            cookedPointerData.copyFrom(other.cookedPointerData);
-            fingerIdBits = other.fingerIdBits;
-            stylusIdBits = other.stylusIdBits;
-            mouseIdBits = other.mouseIdBits;
-            buttonState = other.buttonState;
-        }
-
-        void clear() {
-            cookedPointerData.clear();
-            fingerIdBits.clear();
-            stylusIdBits.clear();
-            mouseIdBits.clear();
-            buttonState = 0;
-        }
+        inline void clear() { *this = CookedState(); }
     };
 
     std::vector<RawState> mRawStatesPending;
@@ -380,9 +355,14 @@
 
     // State provided by an external stylus
     StylusState mExternalStylusState;
-    int64_t mExternalStylusId;
+    // If an external stylus is capable of reporting pointer-specific data like pressure, we will
+    // attempt to fuse the pointer data reported by the stylus to the first touch pointer. This is
+    // the id of the pointer to which the external stylus data is fused.
+    std::optional<uint32_t> mFusedStylusPointerId;
     nsecs_t mExternalStylusFusionTimeout;
     bool mExternalStylusDataPending;
+    // A subset of the buttons in mCurrentRawState that came from an external stylus.
+    int32_t mExternalStylusButtonsApplied;
 
     // True if we sent a HOVER_ENTER event.
     bool mSentHoverEnter;
@@ -425,22 +405,19 @@
     // The components of the viewport are specified in the display's rotated orientation.
     DisplayViewport mViewport;
 
-    // The width and height are obtained from the viewport and are specified
-    // in the natural orientation.
-    int32_t mDisplayWidth;
-    int32_t mDisplayHeight;
+    // We refer to the display as being in the "natural orientation" when there is no rotation
+    // applied. The display size obtained from the viewport in the natural orientation.
+    // Always starts at (0, 0).
+    ui::Size mDisplayBounds{ui::kInvalidSize};
 
-    // The physical frame is the rectangle in the display's coordinate space that maps to the
+    // The physical frame is the rectangle in the natural display's coordinate space that maps to
     // the logical display frame.
-    int32_t mPhysicalWidth;
-    int32_t mPhysicalHeight;
-    int32_t mPhysicalLeft;
-    int32_t mPhysicalTop;
+    Rect mPhysicalFrameInDisplay{Rect::INVALID_RECT};
 
     // The orientation of the input device relative to that of the display panel. It specifies
     // the rotation of the input device coordinates required to produce the display panel
     // orientation, so it will depend on whether the device is orientation aware.
-    int32_t mInputDeviceOrientation;
+    ui::Rotation mInputDeviceOrientation;
 
     // Translation and scaling factors, orientation-independent.
     float mXScale;
@@ -525,9 +502,9 @@
     float mPointerGestureMaxSwipeWidth;
 
     struct PointerDistanceHeapElement {
-        uint32_t currentPointerIndex : 8;
-        uint32_t lastPointerIndex : 8;
-        uint64_t distance : 48; // squared distance
+        uint32_t currentPointerIndex : 8 {};
+        uint32_t lastPointerIndex : 8 {};
+        uint64_t distance : 48 {}; // squared distance
     };
 
     enum class PointerUsage {
@@ -624,15 +601,15 @@
         // Pointer coords and ids for the current and previous pointer gesture.
         Mode currentGestureMode;
         BitSet32 currentGestureIdBits;
-        uint32_t currentGestureIdToIndex[MAX_POINTER_ID + 1];
-        PointerProperties currentGestureProperties[MAX_POINTERS];
-        PointerCoords currentGestureCoords[MAX_POINTERS];
+        IdToIndexArray currentGestureIdToIndex{};
+        PropertiesArray currentGestureProperties{};
+        CoordsArray currentGestureCoords{};
 
         Mode lastGestureMode;
         BitSet32 lastGestureIdBits;
-        uint32_t lastGestureIdToIndex[MAX_POINTER_ID + 1];
-        PointerProperties lastGestureProperties[MAX_POINTERS];
-        PointerCoords lastGestureCoords[MAX_POINTERS];
+        IdToIndexArray lastGestureIdToIndex{};
+        PropertiesArray lastGestureProperties{};
+        CoordsArray lastGestureCoords{};
 
         // Time the pointer gesture last went down.
         nsecs_t downTime;
@@ -706,6 +683,12 @@
         // Time the pointer last went down.
         nsecs_t downTime;
 
+        // Values reported for the last pointer event.
+        uint32_t source;
+        int32_t displayId;
+        float lastCursorX;
+        float lastCursorY;
+
         void reset() {
             currentCoords.clear();
             currentProperties.clear();
@@ -714,6 +697,10 @@
             down = false;
             hovering = false;
             downTime = 0;
+            source = 0;
+            displayId = ADISPLAY_ID_NONE;
+            lastCursorX = 0.f;
+            lastCursorY = 0.f;
         }
     } mPointerSimple;
 
@@ -770,6 +757,14 @@
     bool preparePointerGestures(nsecs_t when, bool* outCancelPreviousGesture,
                                 bool* outFinishPreviousGesture, bool isTimeout);
 
+    // Returns true if we're in a period of "quiet time" when touchpad gestures should be ignored.
+    bool checkForTouchpadQuietTime(nsecs_t when);
+
+    std::pair<int32_t, float> getFastestFinger();
+
+    void prepareMultiFingerPointerGestures(nsecs_t when, bool* outCancelPreviousGesture,
+                                           bool* outFinishPreviousGesture);
+
     // Moves the on-screen mouse pointer based on the movement of the pointer of the given ID
     // between the last and current events. Uses a relative motion.
     void moveMousePointerFromPointerDelta(nsecs_t when, uint32_t pointerId);
@@ -790,6 +785,8 @@
     [[nodiscard]] std::list<NotifyArgs> abortPointerSimple(nsecs_t when, nsecs_t readTime,
                                                            uint32_t policyFlags);
 
+    // Attempts to assign a pointer id to the external stylus. Returns true if the state should be
+    // withheld from further processing while waiting for data from the stylus.
     bool assignExternalStylusId(const RawState& state, bool timeout);
     void applyExternalStylusButtonState(nsecs_t when);
     void applyExternalStylusTouchState(nsecs_t when);
@@ -801,17 +798,10 @@
     [[nodiscard]] NotifyMotionArgs dispatchMotion(
             nsecs_t when, nsecs_t readTime, uint32_t policyFlags, uint32_t source, int32_t action,
             int32_t actionButton, int32_t flags, int32_t metaState, int32_t buttonState,
-            int32_t edgeFlags, const PointerProperties* properties, const PointerCoords* coords,
-            const uint32_t* idToIndex, BitSet32 idBits, int32_t changedId, float xPrecision,
+            int32_t edgeFlags, const PropertiesArray& properties, const CoordsArray& coords,
+            const IdToIndexArray& idToIndex, BitSet32 idBits, int32_t changedId, float xPrecision,
             float yPrecision, nsecs_t downTime, MotionClassification classification);
 
-    // Updates pointer coords and properties for pointers with specified ids that have moved.
-    // Returns true if any of them changed.
-    bool updateMovedPointers(const PointerProperties* inProperties, const PointerCoords* inCoords,
-                             const uint32_t* inIdToIndex, PointerProperties* outProperties,
-                             PointerCoords* outCoords, const uint32_t* outIdToIndex,
-                             BitSet32 idBits) const;
-
     // Returns if this touch device is a touch screen with an associated display.
     bool isTouchScreen();
     // Updates touch spots if they are enabled. Should only be used when this device is a
@@ -823,7 +813,6 @@
 
     static void assignPointerIds(const RawState& last, RawState& current);
 
-    const char* modeToString(DeviceMode deviceMode);
     void rotateAndScale(float& x, float& y) const;
 };
 
diff --git a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp
new file mode 100644
index 0000000..8c5bce7
--- /dev/null
+++ b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp
@@ -0,0 +1,180 @@
+/*
+ * 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 "../Macros.h"
+
+#include <log/log_main.h>
+#include <chrono>
+#include "TouchpadInputMapper.h"
+
+namespace android {
+
+namespace {
+
+short getMaxTouchCount(const InputDeviceContext& context) {
+    if (context.hasKeyCode(BTN_TOOL_QUINTTAP)) return 5;
+    if (context.hasKeyCode(BTN_TOOL_QUADTAP)) return 4;
+    if (context.hasKeyCode(BTN_TOOL_TRIPLETAP)) return 3;
+    if (context.hasKeyCode(BTN_TOOL_DOUBLETAP)) return 2;
+    if (context.hasKeyCode(BTN_TOOL_FINGER)) return 1;
+    return 0;
+}
+
+HardwareProperties createHardwareProperties(const InputDeviceContext& context) {
+    HardwareProperties props;
+    RawAbsoluteAxisInfo absMtPositionX;
+    context.getAbsoluteAxisInfo(ABS_MT_POSITION_X, &absMtPositionX);
+    props.left = absMtPositionX.minValue;
+    props.right = absMtPositionX.maxValue;
+    props.res_x = absMtPositionX.resolution;
+
+    RawAbsoluteAxisInfo absMtPositionY;
+    context.getAbsoluteAxisInfo(ABS_MT_POSITION_Y, &absMtPositionY);
+    props.top = absMtPositionY.minValue;
+    props.bottom = absMtPositionY.maxValue;
+    props.res_y = absMtPositionY.resolution;
+
+    RawAbsoluteAxisInfo absMtOrientation;
+    context.getAbsoluteAxisInfo(ABS_MT_ORIENTATION, &absMtOrientation);
+    props.orientation_minimum = absMtOrientation.minValue;
+    props.orientation_maximum = absMtOrientation.maxValue;
+
+    RawAbsoluteAxisInfo absMtSlot;
+    context.getAbsoluteAxisInfo(ABS_MT_SLOT, &absMtSlot);
+    props.max_finger_cnt = absMtSlot.maxValue - absMtSlot.minValue + 1;
+    props.max_touch_cnt = getMaxTouchCount(context);
+
+    // T5R2 ("Track 5, Report 2") is a feature of some old Synaptics touchpads that could track 5
+    // fingers but only report the coordinates of 2 of them. We don't know of any external touchpads
+    // that did this, so assume false.
+    props.supports_t5r2 = false;
+
+    props.support_semi_mt = context.hasInputProperty(INPUT_PROP_SEMI_MT);
+    props.is_button_pad = context.hasInputProperty(INPUT_PROP_BUTTONPAD);
+
+    // Mouse-only properties, which will always be false.
+    props.has_wheel = false;
+    props.wheel_is_hi_res = false;
+
+    // Linux Kernel haptic touchpad support isn't merged yet, so for now assume that no touchpads
+    // are haptic.
+    props.is_haptic_pad = false;
+    return props;
+}
+
+void gestureInterpreterCallback(void* clientData, const struct Gesture* gesture) {
+    // TODO(b/251196347): turn the gesture into a NotifyArgs and dispatch it.
+    ALOGD("Gesture ready: %s", gesture->String().c_str());
+}
+
+} // namespace
+
+TouchpadInputMapper::TouchpadInputMapper(InputDeviceContext& deviceContext)
+      : InputMapper(deviceContext),
+        mGestureInterpreter(NewGestureInterpreter(), DeleteGestureInterpreter),
+        mTouchButtonAccumulator(deviceContext) {
+    mGestureInterpreter->Initialize(GESTURES_DEVCLASS_TOUCHPAD);
+    mGestureInterpreter->SetHardwareProperties(createHardwareProperties(deviceContext));
+    mGestureInterpreter->SetCallback(gestureInterpreterCallback, nullptr);
+    // TODO(b/251196347): set a property provider, so we can change gesture properties.
+    // TODO(b/251196347): set a timer provider, so the library can use timers.
+
+    RawAbsoluteAxisInfo slotAxisInfo;
+    getAbsoluteAxisInfo(ABS_MT_SLOT, &slotAxisInfo);
+    if (!slotAxisInfo.valid || slotAxisInfo.maxValue <= 0) {
+        ALOGW("Touchpad \"%s\" doesn't have a valid ABS_MT_SLOT axis, and probably won't work "
+              "properly.",
+              getDeviceName().c_str());
+    }
+    mMotionAccumulator.configure(getDeviceContext(), slotAxisInfo.maxValue + 1, true);
+    mTouchButtonAccumulator.configure();
+}
+
+uint32_t TouchpadInputMapper::getSources() const {
+    return AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD;
+}
+
+std::list<NotifyArgs> TouchpadInputMapper::reset(nsecs_t when) {
+    mCursorButtonAccumulator.reset(getDeviceContext());
+    mTouchButtonAccumulator.reset();
+    mMscTimestamp = 0;
+    return InputMapper::reset(when);
+}
+
+std::list<NotifyArgs> TouchpadInputMapper::process(const RawEvent* rawEvent) {
+    if (rawEvent->type == EV_SYN && rawEvent->code == SYN_REPORT) {
+        sync(rawEvent->when);
+    }
+    if (rawEvent->type == EV_MSC && rawEvent->code == MSC_TIMESTAMP) {
+        mMscTimestamp = rawEvent->value;
+    }
+    mCursorButtonAccumulator.process(rawEvent);
+    mMotionAccumulator.process(rawEvent);
+    mTouchButtonAccumulator.process(rawEvent);
+    return {};
+}
+
+void TouchpadInputMapper::sync(nsecs_t when) {
+    HardwareState hwState;
+    // The gestures library uses doubles to represent timestamps in seconds.
+    hwState.timestamp = std::chrono::duration<stime_t>(std::chrono::nanoseconds(when)).count();
+    hwState.msc_timestamp =
+            std::chrono::duration<stime_t>(std::chrono::microseconds(mMscTimestamp)).count();
+
+    hwState.buttons_down = 0;
+    if (mCursorButtonAccumulator.isLeftPressed()) {
+        hwState.buttons_down |= GESTURES_BUTTON_LEFT;
+    }
+    if (mCursorButtonAccumulator.isMiddlePressed()) {
+        hwState.buttons_down |= GESTURES_BUTTON_MIDDLE;
+    }
+    if (mCursorButtonAccumulator.isRightPressed()) {
+        hwState.buttons_down |= GESTURES_BUTTON_RIGHT;
+    }
+    if (mCursorButtonAccumulator.isBackPressed() || mCursorButtonAccumulator.isSidePressed()) {
+        hwState.buttons_down |= GESTURES_BUTTON_BACK;
+    }
+    if (mCursorButtonAccumulator.isForwardPressed() || mCursorButtonAccumulator.isExtraPressed()) {
+        hwState.buttons_down |= GESTURES_BUTTON_FORWARD;
+    }
+
+    std::vector<FingerState> fingers;
+    for (size_t i = 0; i < mMotionAccumulator.getSlotCount(); i++) {
+        MultiTouchMotionAccumulator::Slot slot = mMotionAccumulator.getSlot(i);
+        if (slot.isInUse()) {
+            FingerState& fingerState = fingers.emplace_back();
+            fingerState = {};
+            fingerState.touch_major = slot.getTouchMajor();
+            fingerState.touch_minor = slot.getTouchMinor();
+            fingerState.width_major = slot.getToolMajor();
+            fingerState.width_minor = slot.getToolMinor();
+            fingerState.pressure = slot.getPressure();
+            fingerState.orientation = slot.getOrientation();
+            fingerState.position_x = slot.getX();
+            fingerState.position_y = slot.getY();
+            fingerState.tracking_id = slot.getTrackingId();
+        }
+    }
+    hwState.fingers = fingers.data();
+    hwState.finger_cnt = fingers.size();
+    hwState.touch_cnt = mTouchButtonAccumulator.getTouchCount();
+
+    mGestureInterpreter->PushHardwareState(&hwState);
+    mMotionAccumulator.finishSync();
+    mMscTimestamp = 0;
+}
+
+} // namespace android
diff --git a/services/inputflinger/reader/mapper/TouchpadInputMapper.h b/services/inputflinger/reader/mapper/TouchpadInputMapper.h
new file mode 100644
index 0000000..9d3a4b3
--- /dev/null
+++ b/services/inputflinger/reader/mapper/TouchpadInputMapper.h
@@ -0,0 +1,53 @@
+/*
+ * 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 <memory>
+
+#include "EventHub.h"
+#include "InputDevice.h"
+#include "InputMapper.h"
+#include "NotifyArgs.h"
+#include "accumulator/CursorButtonAccumulator.h"
+#include "accumulator/MultiTouchMotionAccumulator.h"
+#include "accumulator/TouchButtonAccumulator.h"
+
+#include "include/gestures.h"
+
+namespace android {
+
+class TouchpadInputMapper : public InputMapper {
+public:
+    explicit TouchpadInputMapper(InputDeviceContext& deviceContext);
+
+    uint32_t getSources() const override;
+    [[nodiscard]] std::list<NotifyArgs> reset(nsecs_t when) override;
+    [[nodiscard]] std::list<NotifyArgs> process(const RawEvent* rawEvent) override;
+
+private:
+    void sync(nsecs_t when);
+
+    std::unique_ptr<gestures::GestureInterpreter, void (*)(gestures::GestureInterpreter*)>
+            mGestureInterpreter;
+
+    CursorButtonAccumulator mCursorButtonAccumulator;
+    MultiTouchMotionAccumulator mMotionAccumulator;
+    TouchButtonAccumulator mTouchButtonAccumulator;
+    int32_t mMscTimestamp = 0;
+};
+
+} // namespace android
diff --git a/services/inputflinger/reader/mapper/accumulator/CursorButtonAccumulator.h b/services/inputflinger/reader/mapper/accumulator/CursorButtonAccumulator.h
index ed4c789..1380604 100644
--- a/services/inputflinger/reader/mapper/accumulator/CursorButtonAccumulator.h
+++ b/services/inputflinger/reader/mapper/accumulator/CursorButtonAccumulator.h
@@ -32,6 +32,14 @@
     void process(const RawEvent* rawEvent);
 
     uint32_t getButtonState() const;
+    inline bool isLeftPressed() const { return mBtnLeft; }
+    inline bool isRightPressed() const { return mBtnRight; }
+    inline bool isMiddlePressed() const { return mBtnMiddle; }
+    inline bool isBackPressed() const { return mBtnBack; }
+    inline bool isSidePressed() const { return mBtnSide; }
+    inline bool isForwardPressed() const { return mBtnForward; }
+    inline bool isExtraPressed() const { return mBtnExtra; }
+    inline bool isTaskPressed() const { return mBtnTask; }
 
 private:
     bool mBtnLeft;
diff --git a/services/inputflinger/reader/mapper/accumulator/HidUsageAccumulator.cpp b/services/inputflinger/reader/mapper/accumulator/HidUsageAccumulator.cpp
new file mode 100644
index 0000000..2da1d81
--- /dev/null
+++ b/services/inputflinger/reader/mapper/accumulator/HidUsageAccumulator.cpp
@@ -0,0 +1,39 @@
+/*
+ * 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 "HidUsageAccumulator.h"
+
+namespace android {
+
+void HidUsageAccumulator::process(const RawEvent& rawEvent) {
+    if (rawEvent.type == EV_MSC && rawEvent.code == MSC_SCAN) {
+        mCurrentHidUsage = rawEvent.value;
+        return;
+    }
+
+    if (rawEvent.type == EV_SYN && rawEvent.code == SYN_REPORT) {
+        reset();
+        return;
+    }
+}
+
+int32_t HidUsageAccumulator::consumeCurrentHidUsage() {
+    const int32_t currentHidUsage = mCurrentHidUsage;
+    reset();
+    return currentHidUsage;
+}
+
+} // namespace android
diff --git a/services/inputflinger/reader/mapper/accumulator/HidUsageAccumulator.h b/services/inputflinger/reader/mapper/accumulator/HidUsageAccumulator.h
new file mode 100644
index 0000000..740a710
--- /dev/null
+++ b/services/inputflinger/reader/mapper/accumulator/HidUsageAccumulator.h
@@ -0,0 +1,40 @@
+/*
+ * 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
+
+#include "EventHub.h"
+
+#include <cstdint>
+
+namespace android {
+
+/* Keeps track of the state of currently reported HID usage code. */
+class HidUsageAccumulator {
+public:
+    explicit HidUsageAccumulator() = default;
+    inline void reset() { *this = HidUsageAccumulator(); }
+
+    void process(const RawEvent& rawEvent);
+
+    /* This must be called when processing the `EV_KEY` event. Returns 0 if invalid. */
+    int32_t consumeCurrentHidUsage();
+
+private:
+    int32_t mCurrentHidUsage{};
+};
+
+} // namespace android
diff --git a/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.cpp b/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.cpp
new file mode 100644
index 0000000..8746729
--- /dev/null
+++ b/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.cpp
@@ -0,0 +1,171 @@
+/*
+ * 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.
+ */
+
+// clang-format off
+#include "../Macros.h"
+// clang-format on
+#include "MultiTouchMotionAccumulator.h"
+
+namespace android {
+
+// --- MultiTouchMotionAccumulator ---
+
+MultiTouchMotionAccumulator::MultiTouchMotionAccumulator()
+      : mCurrentSlot(-1), mUsingSlotsProtocol(false) {}
+
+void MultiTouchMotionAccumulator::configure(InputDeviceContext& deviceContext, size_t slotCount,
+                                            bool usingSlotsProtocol) {
+    mUsingSlotsProtocol = usingSlotsProtocol;
+    mSlots = std::vector<Slot>(slotCount);
+
+    mCurrentSlot = -1;
+    if (mUsingSlotsProtocol) {
+        // Query the driver for the current slot index and use it as the initial slot before we
+        // start reading events from the device.  It is possible that the current slot index will
+        // not be the same as it was when the first event was written into the evdev buffer, which
+        // means the input mapper could start out of sync with the initial state of the events in
+        // the evdev buffer. In the extremely unlikely case that this happens, the data from two
+        // slots will be confused until the next ABS_MT_SLOT event is received. This can cause the
+        // touch point to "jump", but at least there will be no stuck touches.
+        int32_t initialSlot;
+        if (const auto status = deviceContext.getAbsoluteAxisValue(ABS_MT_SLOT, &initialSlot);
+            status == OK) {
+            mCurrentSlot = initialSlot;
+        } else {
+            ALOGD("Could not retrieve current multi-touch slot index. status=%d", status);
+        }
+    }
+}
+
+void MultiTouchMotionAccumulator::resetSlots() {
+    for (Slot& slot : mSlots) {
+        slot.clear();
+    }
+    mCurrentSlot = -1;
+}
+
+void MultiTouchMotionAccumulator::process(const RawEvent* rawEvent) {
+    if (rawEvent->type == EV_ABS) {
+        bool newSlot = false;
+        if (mUsingSlotsProtocol) {
+            if (rawEvent->code == ABS_MT_SLOT) {
+                mCurrentSlot = rawEvent->value;
+                newSlot = true;
+            }
+        } else if (mCurrentSlot < 0) {
+            mCurrentSlot = 0;
+        }
+
+        if (mCurrentSlot < 0 || size_t(mCurrentSlot) >= mSlots.size()) {
+            if (newSlot) {
+                ALOGW_IF(DEBUG_POINTERS,
+                         "MultiTouch device emitted invalid slot index %d but it "
+                         "should be between 0 and %zd; ignoring this slot.",
+                         mCurrentSlot, mSlots.size() - 1);
+            }
+        } else {
+            Slot& slot = mSlots[mCurrentSlot];
+            // If mUsingSlotsProtocol is true, it means the raw pointer has axis info of
+            // ABS_MT_TRACKING_ID and ABS_MT_SLOT, so driver should send a valid trackingId while
+            // updating the slot.
+            if (!mUsingSlotsProtocol) {
+                slot.mInUse = true;
+            }
+
+            switch (rawEvent->code) {
+                case ABS_MT_POSITION_X:
+                    slot.mAbsMtPositionX = rawEvent->value;
+                    warnIfNotInUse(*rawEvent, slot);
+                    break;
+                case ABS_MT_POSITION_Y:
+                    slot.mAbsMtPositionY = rawEvent->value;
+                    warnIfNotInUse(*rawEvent, slot);
+                    break;
+                case ABS_MT_TOUCH_MAJOR:
+                    slot.mAbsMtTouchMajor = rawEvent->value;
+                    break;
+                case ABS_MT_TOUCH_MINOR:
+                    slot.mAbsMtTouchMinor = rawEvent->value;
+                    slot.mHaveAbsMtTouchMinor = true;
+                    break;
+                case ABS_MT_WIDTH_MAJOR:
+                    slot.mAbsMtWidthMajor = rawEvent->value;
+                    break;
+                case ABS_MT_WIDTH_MINOR:
+                    slot.mAbsMtWidthMinor = rawEvent->value;
+                    slot.mHaveAbsMtWidthMinor = true;
+                    break;
+                case ABS_MT_ORIENTATION:
+                    slot.mAbsMtOrientation = rawEvent->value;
+                    break;
+                case ABS_MT_TRACKING_ID:
+                    if (mUsingSlotsProtocol && rawEvent->value < 0) {
+                        // The slot is no longer in use but it retains its previous contents,
+                        // which may be reused for subsequent touches.
+                        slot.mInUse = false;
+                    } else {
+                        slot.mInUse = true;
+                        slot.mAbsMtTrackingId = rawEvent->value;
+                    }
+                    break;
+                case ABS_MT_PRESSURE:
+                    slot.mAbsMtPressure = rawEvent->value;
+                    break;
+                case ABS_MT_DISTANCE:
+                    slot.mAbsMtDistance = rawEvent->value;
+                    break;
+                case ABS_MT_TOOL_TYPE:
+                    slot.mAbsMtToolType = rawEvent->value;
+                    slot.mHaveAbsMtToolType = true;
+                    break;
+            }
+        }
+    } else if (rawEvent->type == EV_SYN && rawEvent->code == SYN_MT_REPORT) {
+        // MultiTouch Sync: The driver has returned all data for *one* of the pointers.
+        mCurrentSlot += 1;
+    }
+}
+
+void MultiTouchMotionAccumulator::finishSync() {
+    if (!mUsingSlotsProtocol) {
+        resetSlots();
+    }
+}
+
+void MultiTouchMotionAccumulator::warnIfNotInUse(const RawEvent& event, const Slot& slot) {
+    if (!slot.mInUse) {
+        ALOGW("Received unexpected event (0x%0x, 0x%0x) for slot %i with tracking id %i",
+              event.code, event.value, mCurrentSlot, slot.mAbsMtTrackingId);
+    }
+}
+
+// --- MultiTouchMotionAccumulator::Slot ---
+
+int32_t MultiTouchMotionAccumulator::Slot::getToolType() const {
+    if (mHaveAbsMtToolType) {
+        switch (mAbsMtToolType) {
+            case MT_TOOL_FINGER:
+                return AMOTION_EVENT_TOOL_TYPE_FINGER;
+            case MT_TOOL_PEN:
+                return AMOTION_EVENT_TOOL_TYPE_STYLUS;
+            case MT_TOOL_PALM:
+                return AMOTION_EVENT_TOOL_TYPE_PALM;
+        }
+    }
+    return AMOTION_EVENT_TOOL_TYPE_UNKNOWN;
+}
+
+} // namespace android
diff --git a/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.h b/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.h
new file mode 100644
index 0000000..62bc780
--- /dev/null
+++ b/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.h
@@ -0,0 +1,94 @@
+/*
+ * 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
+
+#include <linux/input-event-codes.h>
+#include <stdint.h>
+#include <vector>
+
+#include "EventHub.h"
+#include "InputDevice.h"
+
+namespace android {
+
+/* Keeps track of the state of multi-touch protocol. */
+class MultiTouchMotionAccumulator {
+public:
+    class Slot {
+    public:
+        inline bool isInUse() const { return mInUse; }
+        inline int32_t getX() const { return mAbsMtPositionX; }
+        inline int32_t getY() const { return mAbsMtPositionY; }
+        inline int32_t getTouchMajor() const { return mAbsMtTouchMajor; }
+        inline int32_t getTouchMinor() const {
+            return mHaveAbsMtTouchMinor ? mAbsMtTouchMinor : mAbsMtTouchMajor;
+        }
+        inline int32_t getToolMajor() const { return mAbsMtWidthMajor; }
+        inline int32_t getToolMinor() const {
+            return mHaveAbsMtWidthMinor ? mAbsMtWidthMinor : mAbsMtWidthMajor;
+        }
+        inline int32_t getOrientation() const { return mAbsMtOrientation; }
+        inline int32_t getTrackingId() const { return mAbsMtTrackingId; }
+        inline int32_t getPressure() const { return mAbsMtPressure; }
+        inline int32_t getDistance() const { return mAbsMtDistance; }
+        int32_t getToolType() const;
+
+    private:
+        friend class MultiTouchMotionAccumulator;
+
+        bool mInUse = false;
+        bool mHaveAbsMtTouchMinor = false;
+        bool mHaveAbsMtWidthMinor = false;
+        bool mHaveAbsMtToolType = false;
+
+        int32_t mAbsMtPositionX = 0;
+        int32_t mAbsMtPositionY = 0;
+        int32_t mAbsMtTouchMajor = 0;
+        int32_t mAbsMtTouchMinor = 0;
+        int32_t mAbsMtWidthMajor = 0;
+        int32_t mAbsMtWidthMinor = 0;
+        int32_t mAbsMtOrientation = 0;
+        int32_t mAbsMtTrackingId = -1;
+        int32_t mAbsMtPressure = 0;
+        int32_t mAbsMtDistance = 0;
+        int32_t mAbsMtToolType = 0;
+
+        void clear() { *this = Slot(); }
+    };
+
+    MultiTouchMotionAccumulator();
+
+    void configure(InputDeviceContext& deviceContext, size_t slotCount, bool usingSlotsProtocol);
+    void process(const RawEvent* rawEvent);
+    void finishSync();
+
+    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);
+        return mSlots[index];
+    }
+
+private:
+    int32_t mCurrentSlot;
+    std::vector<Slot> mSlots;
+    bool mUsingSlotsProtocol;
+
+    void resetSlots();
+    void warnIfNotInUse(const RawEvent& event, const Slot& slot);
+};
+
+} // namespace android
diff --git a/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.cpp b/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.cpp
index 86153d3..6601702 100644
--- a/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.cpp
+++ b/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.cpp
@@ -21,55 +21,40 @@
 
 namespace android {
 
-TouchButtonAccumulator::TouchButtonAccumulator() : mHaveBtnTouch(false), mHaveStylus(false) {
-    clearButtons();
+void TouchButtonAccumulator::configure() {
+    mHaveBtnTouch = mDeviceContext.hasScanCode(BTN_TOUCH);
+    mHaveStylus = mDeviceContext.hasScanCode(BTN_TOOL_PEN) ||
+            mDeviceContext.hasScanCode(BTN_TOOL_RUBBER) ||
+            mDeviceContext.hasScanCode(BTN_TOOL_BRUSH) ||
+            mDeviceContext.hasScanCode(BTN_TOOL_PENCIL) ||
+            mDeviceContext.hasScanCode(BTN_TOOL_AIRBRUSH);
 }
 
-void TouchButtonAccumulator::configure(InputDeviceContext& deviceContext) {
-    mHaveBtnTouch = deviceContext.hasScanCode(BTN_TOUCH);
-    mHaveStylus = deviceContext.hasScanCode(BTN_TOOL_PEN) ||
-            deviceContext.hasScanCode(BTN_TOOL_RUBBER) ||
-            deviceContext.hasScanCode(BTN_TOOL_BRUSH) ||
-            deviceContext.hasScanCode(BTN_TOOL_PENCIL) ||
-            deviceContext.hasScanCode(BTN_TOOL_AIRBRUSH);
-}
-
-void TouchButtonAccumulator::reset(InputDeviceContext& deviceContext) {
-    mBtnTouch = deviceContext.isKeyPressed(BTN_TOUCH);
-    mBtnStylus = deviceContext.isKeyPressed(BTN_STYLUS);
+void TouchButtonAccumulator::reset() {
+    mBtnTouch = mDeviceContext.isKeyPressed(BTN_TOUCH);
+    mBtnStylus = mDeviceContext.isKeyPressed(BTN_STYLUS) ||
+            mDeviceContext.isKeyCodePressed(AKEYCODE_STYLUS_BUTTON_PRIMARY);
     // BTN_0 is what gets mapped for the HID usage Digitizers.SecondaryBarrelSwitch
-    mBtnStylus2 = deviceContext.isKeyPressed(BTN_STYLUS2) || deviceContext.isKeyPressed(BTN_0);
-    mBtnToolFinger = deviceContext.isKeyPressed(BTN_TOOL_FINGER);
-    mBtnToolPen = deviceContext.isKeyPressed(BTN_TOOL_PEN);
-    mBtnToolRubber = deviceContext.isKeyPressed(BTN_TOOL_RUBBER);
-    mBtnToolBrush = deviceContext.isKeyPressed(BTN_TOOL_BRUSH);
-    mBtnToolPencil = deviceContext.isKeyPressed(BTN_TOOL_PENCIL);
-    mBtnToolAirbrush = deviceContext.isKeyPressed(BTN_TOOL_AIRBRUSH);
-    mBtnToolMouse = deviceContext.isKeyPressed(BTN_TOOL_MOUSE);
-    mBtnToolLens = deviceContext.isKeyPressed(BTN_TOOL_LENS);
-    mBtnToolDoubleTap = deviceContext.isKeyPressed(BTN_TOOL_DOUBLETAP);
-    mBtnToolTripleTap = deviceContext.isKeyPressed(BTN_TOOL_TRIPLETAP);
-    mBtnToolQuadTap = deviceContext.isKeyPressed(BTN_TOOL_QUADTAP);
-}
-
-void TouchButtonAccumulator::clearButtons() {
-    mBtnTouch = 0;
-    mBtnStylus = 0;
-    mBtnStylus2 = 0;
-    mBtnToolFinger = 0;
-    mBtnToolPen = 0;
-    mBtnToolRubber = 0;
-    mBtnToolBrush = 0;
-    mBtnToolPencil = 0;
-    mBtnToolAirbrush = 0;
-    mBtnToolMouse = 0;
-    mBtnToolLens = 0;
-    mBtnToolDoubleTap = 0;
-    mBtnToolTripleTap = 0;
-    mBtnToolQuadTap = 0;
+    mBtnStylus2 = mDeviceContext.isKeyPressed(BTN_STYLUS2) || mDeviceContext.isKeyPressed(BTN_0) ||
+            mDeviceContext.isKeyCodePressed(AKEYCODE_STYLUS_BUTTON_SECONDARY);
+    mBtnToolFinger = mDeviceContext.isKeyPressed(BTN_TOOL_FINGER);
+    mBtnToolPen = mDeviceContext.isKeyPressed(BTN_TOOL_PEN);
+    mBtnToolRubber = mDeviceContext.isKeyPressed(BTN_TOOL_RUBBER);
+    mBtnToolBrush = mDeviceContext.isKeyPressed(BTN_TOOL_BRUSH);
+    mBtnToolPencil = mDeviceContext.isKeyPressed(BTN_TOOL_PENCIL);
+    mBtnToolAirbrush = mDeviceContext.isKeyPressed(BTN_TOOL_AIRBRUSH);
+    mBtnToolMouse = mDeviceContext.isKeyPressed(BTN_TOOL_MOUSE);
+    mBtnToolLens = mDeviceContext.isKeyPressed(BTN_TOOL_LENS);
+    mBtnToolDoubleTap = mDeviceContext.isKeyPressed(BTN_TOOL_DOUBLETAP);
+    mBtnToolTripleTap = mDeviceContext.isKeyPressed(BTN_TOOL_TRIPLETAP);
+    mBtnToolQuadTap = mDeviceContext.isKeyPressed(BTN_TOOL_QUADTAP);
+    mBtnToolQuintTap = mDeviceContext.isKeyPressed(BTN_TOOL_QUINTTAP);
+    mHidUsageAccumulator.reset();
 }
 
 void TouchButtonAccumulator::process(const RawEvent* rawEvent) {
+    mHidUsageAccumulator.process(*rawEvent);
+
     if (rawEvent->type == EV_KEY) {
         switch (rawEvent->code) {
             case BTN_TOUCH:
@@ -116,7 +101,32 @@
             case BTN_TOOL_QUADTAP:
                 mBtnToolQuadTap = rawEvent->value;
                 break;
+            case BTN_TOOL_QUINTTAP:
+                mBtnToolQuintTap = rawEvent->value;
+                break;
+            default:
+                processMappedKey(rawEvent->code, rawEvent->value);
         }
+        return;
+    }
+}
+
+void TouchButtonAccumulator::processMappedKey(int32_t scanCode, bool down) {
+    int32_t keyCode, metaState;
+    uint32_t flags;
+    if (mDeviceContext.mapKey(scanCode, mHidUsageAccumulator.consumeCurrentHidUsage(),
+                              0 /*metaState*/, &keyCode, &metaState, &flags) != OK) {
+        return;
+    }
+    switch (keyCode) {
+        case AKEYCODE_STYLUS_BUTTON_PRIMARY:
+            mBtnStylus = down;
+            break;
+        case AKEYCODE_STYLUS_BUTTON_SECONDARY:
+            mBtnStylus2 = down;
+            break;
+        default:
+            break;
     }
 }
 
@@ -141,7 +151,8 @@
     if (mBtnToolPen || mBtnToolBrush || mBtnToolPencil || mBtnToolAirbrush) {
         return AMOTION_EVENT_TOOL_TYPE_STYLUS;
     }
-    if (mBtnToolFinger || mBtnToolDoubleTap || mBtnToolTripleTap || mBtnToolQuadTap) {
+    if (mBtnToolFinger || mBtnToolDoubleTap || mBtnToolTripleTap || mBtnToolQuadTap ||
+        mBtnToolQuintTap) {
         return AMOTION_EVENT_TOOL_TYPE_FINGER;
     }
     return AMOTION_EVENT_TOOL_TYPE_UNKNOWN;
@@ -150,7 +161,7 @@
 bool TouchButtonAccumulator::isToolActive() const {
     return mBtnTouch || mBtnToolFinger || mBtnToolPen || mBtnToolRubber || mBtnToolBrush ||
             mBtnToolPencil || mBtnToolAirbrush || mBtnToolMouse || mBtnToolLens ||
-            mBtnToolDoubleTap || mBtnToolTripleTap || mBtnToolQuadTap;
+            mBtnToolDoubleTap || mBtnToolTripleTap || mBtnToolQuadTap || mBtnToolQuintTap;
 }
 
 bool TouchButtonAccumulator::isHovering() const {
@@ -161,4 +172,19 @@
     return mHaveStylus;
 }
 
+bool TouchButtonAccumulator::hasButtonTouch() const {
+    return mHaveBtnTouch;
+}
+
+int TouchButtonAccumulator::getTouchCount() const {
+    if (mBtnTouch) {
+        if (mBtnToolQuintTap) return 5;
+        if (mBtnToolQuadTap) return 4;
+        if (mBtnToolTripleTap) return 3;
+        if (mBtnToolDoubleTap) return 2;
+        if (mBtnToolFinger) return 1;
+    }
+    return 0;
+}
+
 } // namespace android
diff --git a/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.h b/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.h
index 7b889be..2e70e2e 100644
--- a/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.h
+++ b/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.h
@@ -16,7 +16,8 @@
 
 #pragma once
 
-#include <stdint.h>
+#include <cstdint>
+#include "HidUsageAccumulator.h"
 
 namespace android {
 
@@ -26,9 +27,11 @@
 /* Keeps track of the state of touch, stylus and tool buttons. */
 class TouchButtonAccumulator {
 public:
-    TouchButtonAccumulator();
-    void configure(InputDeviceContext& deviceContext);
-    void reset(InputDeviceContext& deviceContext);
+    explicit TouchButtonAccumulator(InputDeviceContext& deviceContext)
+          : mDeviceContext(deviceContext){};
+
+    void configure();
+    void reset();
 
     void process(const RawEvent* rawEvent);
 
@@ -37,27 +40,34 @@
     bool isToolActive() const;
     bool isHovering() const;
     bool hasStylus() const;
+    bool hasButtonTouch() const;
+    int getTouchCount() const;
 
 private:
-    bool mHaveBtnTouch;
-    bool mHaveStylus;
+    bool mHaveBtnTouch{};
+    bool mHaveStylus{};
 
-    bool mBtnTouch;
-    bool mBtnStylus;
-    bool mBtnStylus2;
-    bool mBtnToolFinger;
-    bool mBtnToolPen;
-    bool mBtnToolRubber;
-    bool mBtnToolBrush;
-    bool mBtnToolPencil;
-    bool mBtnToolAirbrush;
-    bool mBtnToolMouse;
-    bool mBtnToolLens;
-    bool mBtnToolDoubleTap;
-    bool mBtnToolTripleTap;
-    bool mBtnToolQuadTap;
+    bool mBtnTouch{};
+    bool mBtnStylus{};
+    bool mBtnStylus2{};
+    bool mBtnToolFinger{};
+    bool mBtnToolPen{};
+    bool mBtnToolRubber{};
+    bool mBtnToolBrush{};
+    bool mBtnToolPencil{};
+    bool mBtnToolAirbrush{};
+    bool mBtnToolMouse{};
+    bool mBtnToolLens{};
+    bool mBtnToolDoubleTap{};
+    bool mBtnToolTripleTap{};
+    bool mBtnToolQuadTap{};
+    bool mBtnToolQuintTap{};
 
-    void clearButtons();
+    HidUsageAccumulator mHidUsageAccumulator{};
+
+    InputDeviceContext& mDeviceContext;
+
+    void processMappedKey(int32_t scanCode, bool down);
 };
 
 } // namespace android
diff --git a/services/inputflinger/reader/mapper/gestures/GesturesLogging.cpp b/services/inputflinger/reader/mapper/gestures/GesturesLogging.cpp
new file mode 100644
index 0000000..81b4968
--- /dev/null
+++ b/services/inputflinger/reader/mapper/gestures/GesturesLogging.cpp
@@ -0,0 +1,41 @@
+/*
+ * 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.
+ */
+
+#ifndef LOG_TAG
+#define LOG_TAG "Gestures"
+#endif
+
+#include <stdio.h>
+
+#include <log/log.h>
+
+#include "include/gestures.h"
+
+extern "C" {
+
+void gestures_log(int verb, const char* fmt, ...) {
+    va_list args;
+    va_start(args, fmt);
+    if (verb == GESTURES_LOG_ERROR) {
+        LOG_PRI_VA(ANDROID_LOG_ERROR, LOG_TAG, fmt, args);
+    } else if (verb == GESTURES_LOG_INFO) {
+        LOG_PRI_VA(ANDROID_LOG_INFO, LOG_TAG, fmt, args);
+    } else {
+        LOG_PRI_VA(ANDROID_LOG_DEBUG, LOG_TAG, fmt, args);
+    }
+    va_end(args);
+}
+}
diff --git a/services/inputflinger/reporter/Android.bp b/services/inputflinger/reporter/Android.bp
index 7430731..693ff06 100644
--- a/services/inputflinger/reporter/Android.bp
+++ b/services/inputflinger/reporter/Android.bp
@@ -23,13 +23,14 @@
 
 cc_library_headers {
     name: "libinputreporter_headers",
+    host_supported: true,
     export_include_dirs: ["."],
 }
 
 filegroup {
     name: "libinputreporter_sources",
     srcs: [
-            "InputReporter.cpp",
+        "InputReporter.cpp",
     ],
 }
 
diff --git a/services/inputflinger/tests/Android.bp b/services/inputflinger/tests/Android.bp
index fcbb98f..53d821f 100644
--- a/services/inputflinger/tests/Android.bp
+++ b/services/inputflinger/tests/Android.bp
@@ -23,6 +23,7 @@
 
 cc_test {
     name: "inputflinger_tests",
+    host_supported: true,
     defaults: [
         "inputflinger_defaults",
         // For all targets inside inputflinger, these tests build all of their sources using their
@@ -39,12 +40,18 @@
         "AnrTracker_test.cpp",
         "BlockingQueue_test.cpp",
         "EventHub_test.cpp",
+        "FakeEventHub.cpp",
+        "FakeInputReaderPolicy.cpp",
+        "FakePointerController.cpp",
         "FocusResolver_test.cpp",
+        "InputMapperTest.cpp",
         "InputProcessor_test.cpp",
         "InputProcessorConverter_test.cpp",
         "InputDispatcher_test.cpp",
         "InputReader_test.cpp",
+        "InstrumentedInputReader.cpp",
         "LatencyTracker_test.cpp",
+        "NotifyArgs_test.cpp",
         "PreferStylusOverTouch_test.cpp",
         "TestInputListener.cpp",
         "UinputDevice.cpp",
@@ -56,10 +63,33 @@
             "frameworks/native/libs/input",
         ],
     },
+    target: {
+        android: {
+            shared_libs: [
+                "libinput",
+                "libvintf",
+            ],
+        },
+        host: {
+            include_dirs: [
+                "bionic/libc/kernel/android/uapi/",
+                "bionic/libc/kernel/uapi",
+            ],
+            cflags: [
+                "-D__ANDROID_HOST__",
+            ],
+            static_libs: [
+                "libinput",
+            ],
+        },
+    },
     static_libs: [
         "libc++fs",
         "libgmock",
     ],
     require_root: true,
+    test_options: {
+        unit_test: true,
+    },
     test_suites: ["device-tests"],
 }
diff --git a/services/inputflinger/tests/AnrTracker_test.cpp b/services/inputflinger/tests/AnrTracker_test.cpp
index f8be48a..25adeea 100644
--- a/services/inputflinger/tests/AnrTracker_test.cpp
+++ b/services/inputflinger/tests/AnrTracker_test.cpp
@@ -137,7 +137,7 @@
 
     ASSERT_TRUE(tracker.empty());
 
-    ASSERT_EQ(LONG_LONG_MAX, tracker.firstTimeout());
+    ASSERT_EQ(LLONG_MAX, tracker.firstTimeout());
     // Can't call firstToken() if tracker.empty()
 }
 
diff --git a/services/inputflinger/tests/EventHub_test.cpp b/services/inputflinger/tests/EventHub_test.cpp
index 9380c71..2e296da 100644
--- a/services/inputflinger/tests/EventHub_test.cpp
+++ b/services/inputflinger/tests/EventHub_test.cpp
@@ -68,12 +68,18 @@
     int32_t mDeviceId;
 
     virtual void SetUp() override {
+#if !defined(__ANDROID__)
+        GTEST_SKIP() << "It's only possible to interact with uinput on device";
+#endif
         mEventHub = std::make_unique<EventHub>();
         consumeInitialDeviceAddedEvents();
         mKeyboard = createUinputDevice<UinputHomeKey>();
         ASSERT_NO_FATAL_FAILURE(mDeviceId = waitForDeviceCreation());
     }
     virtual void TearDown() override {
+#if !defined(__ANDROID__)
+        return;
+#endif
         mKeyboard.reset();
         waitForDeviceClose(mDeviceId);
         assertNoMoreEvents();
diff --git a/services/inputflinger/tests/FakeEventHub.cpp b/services/inputflinger/tests/FakeEventHub.cpp
new file mode 100644
index 0000000..f6cf1cc
--- /dev/null
+++ b/services/inputflinger/tests/FakeEventHub.cpp
@@ -0,0 +1,590 @@
+/*
+ * 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 "FakeEventHub.h"
+
+#include <android-base/thread_annotations.h>
+#include <gtest/gtest.h>
+#include <linux/input-event-codes.h>
+
+#include "TestConstants.h"
+
+namespace android {
+
+const std::string FakeEventHub::BATTERY_DEVPATH = "/sys/devices/mydevice/power_supply/mybattery";
+
+FakeEventHub::~FakeEventHub() {
+    for (size_t i = 0; i < mDevices.size(); i++) {
+        delete mDevices.valueAt(i);
+    }
+}
+
+void FakeEventHub::addDevice(int32_t deviceId, const std::string& name,
+                             ftl::Flags<InputDeviceClass> classes, int bus) {
+    Device* device = new Device(classes);
+    device->identifier.name = name;
+    device->identifier.bus = bus;
+    mDevices.add(deviceId, device);
+
+    enqueueEvent(ARBITRARY_TIME, READ_TIME, deviceId, EventHubInterface::DEVICE_ADDED, 0, 0);
+}
+
+void FakeEventHub::removeDevice(int32_t deviceId) {
+    delete mDevices.valueFor(deviceId);
+    mDevices.removeItem(deviceId);
+
+    enqueueEvent(ARBITRARY_TIME, READ_TIME, deviceId, EventHubInterface::DEVICE_REMOVED, 0, 0);
+}
+
+bool FakeEventHub::isDeviceEnabled(int32_t deviceId) const {
+    Device* device = getDevice(deviceId);
+    if (device == nullptr) {
+        ALOGE("Incorrect device id=%" PRId32 " provided to %s", deviceId, __func__);
+        return false;
+    }
+    return device->enabled;
+}
+
+status_t FakeEventHub::enableDevice(int32_t deviceId) {
+    status_t result;
+    Device* device = getDevice(deviceId);
+    if (device == nullptr) {
+        ALOGE("Incorrect device id=%" PRId32 " provided to %s", deviceId, __func__);
+        return BAD_VALUE;
+    }
+    if (device->enabled) {
+        ALOGW("Duplicate call to %s, device %" PRId32 " already enabled", __func__, deviceId);
+        return OK;
+    }
+    result = device->enable();
+    return result;
+}
+
+status_t FakeEventHub::disableDevice(int32_t deviceId) {
+    Device* device = getDevice(deviceId);
+    if (device == nullptr) {
+        ALOGE("Incorrect device id=%" PRId32 " provided to %s", deviceId, __func__);
+        return BAD_VALUE;
+    }
+    if (!device->enabled) {
+        ALOGW("Duplicate call to %s, device %" PRId32 " already disabled", __func__, deviceId);
+        return OK;
+    }
+    return device->disable();
+}
+
+void FakeEventHub::finishDeviceScan() {
+    enqueueEvent(ARBITRARY_TIME, READ_TIME, 0, EventHubInterface::FINISHED_DEVICE_SCAN, 0, 0);
+}
+
+void FakeEventHub::addConfigurationProperty(int32_t deviceId, const char* key, const char* value) {
+    getDevice(deviceId)->configuration.addProperty(key, value);
+}
+
+void FakeEventHub::addConfigurationMap(int32_t deviceId, const PropertyMap* configuration) {
+    getDevice(deviceId)->configuration.addAll(configuration);
+}
+
+void FakeEventHub::addAbsoluteAxis(int32_t deviceId, int axis, int32_t minValue, int32_t maxValue,
+                                   int flat, int fuzz, int resolution) {
+    Device* device = getDevice(deviceId);
+
+    RawAbsoluteAxisInfo info;
+    info.valid = true;
+    info.minValue = minValue;
+    info.maxValue = maxValue;
+    info.flat = flat;
+    info.fuzz = fuzz;
+    info.resolution = resolution;
+    device->absoluteAxes.add(axis, info);
+}
+
+void FakeEventHub::addRelativeAxis(int32_t deviceId, int32_t axis) {
+    getDevice(deviceId)->relativeAxes.add(axis, true);
+}
+
+void FakeEventHub::setKeyCodeState(int32_t deviceId, int32_t keyCode, int32_t state) {
+    getDevice(deviceId)->keyCodeStates.replaceValueFor(keyCode, state);
+}
+
+void FakeEventHub::setCountryCode(int32_t deviceId, InputDeviceCountryCode countryCode) {
+    getDevice(deviceId)->countryCode = countryCode;
+}
+
+void FakeEventHub::setScanCodeState(int32_t deviceId, int32_t scanCode, int32_t state) {
+    getDevice(deviceId)->scanCodeStates.replaceValueFor(scanCode, state);
+}
+
+void FakeEventHub::setSwitchState(int32_t deviceId, int32_t switchCode, int32_t state) {
+    getDevice(deviceId)->switchStates.replaceValueFor(switchCode, state);
+}
+
+void FakeEventHub::setAbsoluteAxisValue(int32_t deviceId, int32_t axis, int32_t value) {
+    getDevice(deviceId)->absoluteAxisValue.replaceValueFor(axis, value);
+}
+
+void FakeEventHub::addKey(int32_t deviceId, int32_t scanCode, int32_t usageCode, int32_t keyCode,
+                          uint32_t flags) {
+    Device* device = getDevice(deviceId);
+    KeyInfo info;
+    info.keyCode = keyCode;
+    info.flags = flags;
+    if (scanCode) {
+        device->keysByScanCode.add(scanCode, info);
+    }
+    if (usageCode) {
+        device->keysByUsageCode.add(usageCode, info);
+    }
+}
+
+void FakeEventHub::addKeyCodeMapping(int32_t deviceId, int32_t fromKeyCode, int32_t toKeyCode) {
+    getDevice(deviceId)->keyCodeMapping.insert_or_assign(fromKeyCode, toKeyCode);
+}
+
+void FakeEventHub::addLed(int32_t deviceId, int32_t led, bool initialState) {
+    getDevice(deviceId)->leds.add(led, initialState);
+}
+
+void FakeEventHub::addSensorAxis(int32_t deviceId, int32_t absCode,
+                                 InputDeviceSensorType sensorType, int32_t sensorDataIndex) {
+    SensorInfo info;
+    info.sensorType = sensorType;
+    info.sensorDataIndex = sensorDataIndex;
+    getDevice(deviceId)->sensorsByAbsCode.emplace(absCode, info);
+}
+
+void FakeEventHub::setMscEvent(int32_t deviceId, int32_t mscEvent) {
+    typename BitArray<MSC_MAX>::Buffer buffer;
+    buffer[mscEvent / 32] = 1 << mscEvent % 32;
+    getDevice(deviceId)->mscBitmask.loadFromBuffer(buffer);
+}
+
+void FakeEventHub::addRawLightInfo(int32_t rawId, RawLightInfo&& info) {
+    mRawLightInfos.emplace(rawId, std::move(info));
+}
+
+void FakeEventHub::fakeLightBrightness(int32_t rawId, int32_t brightness) {
+    mLightBrightness.emplace(rawId, brightness);
+}
+
+void FakeEventHub::fakeLightIntensities(int32_t rawId,
+                                        const std::unordered_map<LightColor, int32_t> intensities) {
+    mLightIntensities.emplace(rawId, std::move(intensities));
+}
+
+bool FakeEventHub::getLedState(int32_t deviceId, int32_t led) {
+    return getDevice(deviceId)->leds.valueFor(led);
+}
+
+std::vector<std::string>& FakeEventHub::getExcludedDevices() {
+    return mExcludedDevices;
+}
+
+void FakeEventHub::addVirtualKeyDefinition(int32_t deviceId,
+                                           const VirtualKeyDefinition& definition) {
+    getDevice(deviceId)->virtualKeys.push_back(definition);
+}
+
+void FakeEventHub::enqueueEvent(nsecs_t when, nsecs_t readTime, int32_t deviceId, int32_t type,
+                                int32_t code, int32_t value) {
+    std::scoped_lock<std::mutex> lock(mLock);
+    RawEvent event;
+    event.when = when;
+    event.readTime = readTime;
+    event.deviceId = deviceId;
+    event.type = type;
+    event.code = code;
+    event.value = value;
+    mEvents.push_back(event);
+
+    if (type == EV_ABS) {
+        setAbsoluteAxisValue(deviceId, code, value);
+    }
+}
+
+void FakeEventHub::setVideoFrames(
+        std::unordered_map<int32_t /*deviceId*/, std::vector<TouchVideoFrame>> videoFrames) {
+    mVideoFrames = std::move(videoFrames);
+}
+
+void FakeEventHub::assertQueueIsEmpty() {
+    std::unique_lock<std::mutex> lock(mLock);
+    base::ScopedLockAssertion assumeLocked(mLock);
+    const bool queueIsEmpty =
+            mEventsCondition.wait_for(lock, WAIT_TIMEOUT,
+                                      [this]() REQUIRES(mLock) { return mEvents.size() == 0; });
+    if (!queueIsEmpty) {
+        FAIL() << "Timed out waiting for EventHub queue to be emptied.";
+    }
+}
+
+FakeEventHub::Device* FakeEventHub::getDevice(int32_t deviceId) const {
+    ssize_t index = mDevices.indexOfKey(deviceId);
+    return index >= 0 ? mDevices.valueAt(index) : nullptr;
+}
+
+ftl::Flags<InputDeviceClass> FakeEventHub::getDeviceClasses(int32_t deviceId) const {
+    Device* device = getDevice(deviceId);
+    return device ? device->classes : ftl::Flags<InputDeviceClass>(0);
+}
+
+InputDeviceIdentifier FakeEventHub::getDeviceIdentifier(int32_t deviceId) const {
+    Device* device = getDevice(deviceId);
+    return device ? device->identifier : InputDeviceIdentifier();
+}
+
+int32_t FakeEventHub::getDeviceControllerNumber(int32_t) const {
+    return 0;
+}
+
+void FakeEventHub::getConfiguration(int32_t deviceId, PropertyMap* outConfiguration) const {
+    Device* device = getDevice(deviceId);
+    if (device) {
+        *outConfiguration = device->configuration;
+    }
+}
+
+status_t FakeEventHub::getAbsoluteAxisInfo(int32_t deviceId, int axis,
+                                           RawAbsoluteAxisInfo* outAxisInfo) const {
+    Device* device = getDevice(deviceId);
+    if (device) {
+        ssize_t index = device->absoluteAxes.indexOfKey(axis);
+        if (index >= 0) {
+            *outAxisInfo = device->absoluteAxes.valueAt(index);
+            return OK;
+        }
+    }
+    outAxisInfo->clear();
+    return -1;
+}
+
+bool FakeEventHub::hasRelativeAxis(int32_t deviceId, int axis) const {
+    Device* device = getDevice(deviceId);
+    if (device) {
+        return device->relativeAxes.indexOfKey(axis) >= 0;
+    }
+    return false;
+}
+
+bool FakeEventHub::hasInputProperty(int32_t, int) const {
+    return false;
+}
+
+bool FakeEventHub::hasMscEvent(int32_t deviceId, int mscEvent) const {
+    Device* device = getDevice(deviceId);
+    if (device) {
+        return mscEvent >= 0 && mscEvent <= MSC_MAX ? device->mscBitmask.test(mscEvent) : false;
+    }
+    return false;
+}
+
+status_t FakeEventHub::mapKey(int32_t deviceId, int32_t scanCode, int32_t usageCode,
+                              int32_t metaState, int32_t* outKeycode, int32_t* outMetaState,
+                              uint32_t* outFlags) const {
+    Device* device = getDevice(deviceId);
+    if (device) {
+        const KeyInfo* key = getKey(device, scanCode, usageCode);
+        if (key) {
+            if (outKeycode) {
+                *outKeycode = key->keyCode;
+            }
+            if (outFlags) {
+                *outFlags = key->flags;
+            }
+            if (outMetaState) {
+                *outMetaState = metaState;
+            }
+            return OK;
+        }
+    }
+    return NAME_NOT_FOUND;
+}
+
+const FakeEventHub::KeyInfo* FakeEventHub::getKey(Device* device, int32_t scanCode,
+                                                  int32_t usageCode) const {
+    if (usageCode) {
+        ssize_t index = device->keysByUsageCode.indexOfKey(usageCode);
+        if (index >= 0) {
+            return &device->keysByUsageCode.valueAt(index);
+        }
+    }
+    if (scanCode) {
+        ssize_t index = device->keysByScanCode.indexOfKey(scanCode);
+        if (index >= 0) {
+            return &device->keysByScanCode.valueAt(index);
+        }
+    }
+    return nullptr;
+}
+
+status_t FakeEventHub::mapAxis(int32_t, int32_t, AxisInfo*) const {
+    return NAME_NOT_FOUND;
+}
+
+base::Result<std::pair<InputDeviceSensorType, int32_t>> FakeEventHub::mapSensor(
+        int32_t deviceId, int32_t absCode) const {
+    Device* device = getDevice(deviceId);
+    if (!device) {
+        return Errorf("Sensor device not found.");
+    }
+    auto it = device->sensorsByAbsCode.find(absCode);
+    if (it == device->sensorsByAbsCode.end()) {
+        return Errorf("Sensor map not found.");
+    }
+    const SensorInfo& info = it->second;
+    return std::make_pair(info.sensorType, info.sensorDataIndex);
+}
+
+void FakeEventHub::setExcludedDevices(const std::vector<std::string>& devices) {
+    mExcludedDevices = devices;
+}
+
+std::vector<RawEvent> FakeEventHub::getEvents(int) {
+    std::scoped_lock lock(mLock);
+
+    std::vector<RawEvent> buffer;
+    std::swap(buffer, mEvents);
+
+    mEventsCondition.notify_all();
+    return buffer;
+}
+
+std::vector<TouchVideoFrame> FakeEventHub::getVideoFrames(int32_t deviceId) {
+    auto it = mVideoFrames.find(deviceId);
+    if (it != mVideoFrames.end()) {
+        std::vector<TouchVideoFrame> frames = std::move(it->second);
+        mVideoFrames.erase(deviceId);
+        return frames;
+    }
+    return {};
+}
+
+int32_t FakeEventHub::getScanCodeState(int32_t deviceId, int32_t scanCode) const {
+    Device* device = getDevice(deviceId);
+    if (device) {
+        ssize_t index = device->scanCodeStates.indexOfKey(scanCode);
+        if (index >= 0) {
+            return device->scanCodeStates.valueAt(index);
+        }
+    }
+    return AKEY_STATE_UNKNOWN;
+}
+
+InputDeviceCountryCode FakeEventHub::getCountryCode(int32_t deviceId) const {
+    Device* device = getDevice(deviceId);
+    return device ? device->countryCode : InputDeviceCountryCode::INVALID;
+}
+
+int32_t FakeEventHub::getKeyCodeState(int32_t deviceId, int32_t keyCode) const {
+    Device* device = getDevice(deviceId);
+    if (device) {
+        ssize_t index = device->keyCodeStates.indexOfKey(keyCode);
+        if (index >= 0) {
+            return device->keyCodeStates.valueAt(index);
+        }
+    }
+    return AKEY_STATE_UNKNOWN;
+}
+
+int32_t FakeEventHub::getSwitchState(int32_t deviceId, int32_t sw) const {
+    Device* device = getDevice(deviceId);
+    if (device) {
+        ssize_t index = device->switchStates.indexOfKey(sw);
+        if (index >= 0) {
+            return device->switchStates.valueAt(index);
+        }
+    }
+    return AKEY_STATE_UNKNOWN;
+}
+
+status_t FakeEventHub::getAbsoluteAxisValue(int32_t deviceId, int32_t axis,
+                                            int32_t* outValue) const {
+    Device* device = getDevice(deviceId);
+    if (device) {
+        ssize_t index = device->absoluteAxisValue.indexOfKey(axis);
+        if (index >= 0) {
+            *outValue = device->absoluteAxisValue.valueAt(index);
+            return OK;
+        }
+    }
+    *outValue = 0;
+    return -1;
+}
+
+int32_t FakeEventHub::getKeyCodeForKeyLocation(int32_t deviceId, int32_t locationKeyCode) const {
+    Device* device = getDevice(deviceId);
+    if (!device) {
+        return AKEYCODE_UNKNOWN;
+    }
+    auto it = device->keyCodeMapping.find(locationKeyCode);
+    return it != device->keyCodeMapping.end() ? it->second : locationKeyCode;
+}
+
+// Return true if the device has non-empty key layout.
+bool FakeEventHub::markSupportedKeyCodes(int32_t deviceId, const std::vector<int32_t>& keyCodes,
+                                         uint8_t* outFlags) const {
+    Device* device = getDevice(deviceId);
+    if (!device) return false;
+
+    bool result = device->keysByScanCode.size() > 0 || device->keysByUsageCode.size() > 0;
+    for (size_t i = 0; i < keyCodes.size(); i++) {
+        for (size_t j = 0; j < device->keysByScanCode.size(); j++) {
+            if (keyCodes[i] == device->keysByScanCode.valueAt(j).keyCode) {
+                outFlags[i] = 1;
+            }
+        }
+        for (size_t j = 0; j < device->keysByUsageCode.size(); j++) {
+            if (keyCodes[i] == device->keysByUsageCode.valueAt(j).keyCode) {
+                outFlags[i] = 1;
+            }
+        }
+    }
+    return result;
+}
+
+bool FakeEventHub::hasScanCode(int32_t deviceId, int32_t scanCode) const {
+    Device* device = getDevice(deviceId);
+    if (device) {
+        ssize_t index = device->keysByScanCode.indexOfKey(scanCode);
+        return index >= 0;
+    }
+    return false;
+}
+
+bool FakeEventHub::hasKeyCode(int32_t deviceId, int32_t keyCode) const {
+    Device* device = getDevice(deviceId);
+    if (!device) {
+        return false;
+    }
+    for (size_t i = 0; i < device->keysByScanCode.size(); i++) {
+        if (keyCode == device->keysByScanCode.valueAt(i).keyCode) {
+            return true;
+        }
+    }
+    for (size_t j = 0; j < device->keysByUsageCode.size(); j++) {
+        if (keyCode == device->keysByUsageCode.valueAt(j).keyCode) {
+            return true;
+        }
+    }
+    return false;
+}
+
+bool FakeEventHub::hasLed(int32_t deviceId, int32_t led) const {
+    Device* device = getDevice(deviceId);
+    return device && device->leds.indexOfKey(led) >= 0;
+}
+
+void FakeEventHub::setLedState(int32_t deviceId, int32_t led, bool on) {
+    Device* device = getDevice(deviceId);
+    if (device) {
+        ssize_t index = device->leds.indexOfKey(led);
+        if (index >= 0) {
+            device->leds.replaceValueAt(led, on);
+        } else {
+            ADD_FAILURE() << "Attempted to set the state of an LED that the EventHub declared "
+                             "was not present.  led="
+                          << led;
+        }
+    }
+}
+
+void FakeEventHub::getVirtualKeyDefinitions(
+        int32_t deviceId, std::vector<VirtualKeyDefinition>& outVirtualKeys) const {
+    outVirtualKeys.clear();
+
+    Device* device = getDevice(deviceId);
+    if (device) {
+        outVirtualKeys = device->virtualKeys;
+    }
+}
+
+const std::shared_ptr<KeyCharacterMap> FakeEventHub::getKeyCharacterMap(int32_t) const {
+    return nullptr;
+}
+
+bool FakeEventHub::setKeyboardLayoutOverlay(int32_t, std::shared_ptr<KeyCharacterMap>) {
+    return false;
+}
+
+std::vector<int32_t> FakeEventHub::getVibratorIds(int32_t deviceId) const {
+    return mVibrators;
+}
+
+std::optional<int32_t> FakeEventHub::getBatteryCapacity(int32_t, int32_t) const {
+    return BATTERY_CAPACITY;
+}
+
+std::optional<int32_t> FakeEventHub::getBatteryStatus(int32_t, int32_t) const {
+    return BATTERY_STATUS;
+}
+
+std::vector<int32_t> FakeEventHub::getRawBatteryIds(int32_t deviceId) const {
+    return {DEFAULT_BATTERY};
+}
+
+std::optional<RawBatteryInfo> FakeEventHub::getRawBatteryInfo(int32_t deviceId,
+                                                              int32_t batteryId) const {
+    if (batteryId != DEFAULT_BATTERY) return {};
+    static const auto BATTERY_INFO = RawBatteryInfo{.id = DEFAULT_BATTERY,
+                                                    .name = "default battery",
+                                                    .flags = InputBatteryClass::CAPACITY,
+                                                    .path = BATTERY_DEVPATH};
+    return BATTERY_INFO;
+}
+
+std::vector<int32_t> FakeEventHub::getRawLightIds(int32_t deviceId) const {
+    std::vector<int32_t> ids;
+    for (const auto& [rawId, info] : mRawLightInfos) {
+        ids.push_back(rawId);
+    }
+    return ids;
+}
+
+std::optional<RawLightInfo> FakeEventHub::getRawLightInfo(int32_t deviceId, int32_t lightId) const {
+    auto it = mRawLightInfos.find(lightId);
+    if (it == mRawLightInfos.end()) {
+        return std::nullopt;
+    }
+    return it->second;
+}
+
+void FakeEventHub::setLightBrightness(int32_t deviceId, int32_t lightId, int32_t brightness) {
+    mLightBrightness.emplace(lightId, brightness);
+}
+
+void FakeEventHub::setLightIntensities(int32_t deviceId, int32_t lightId,
+                                       std::unordered_map<LightColor, int32_t> intensities) {
+    mLightIntensities.emplace(lightId, intensities);
+};
+
+std::optional<int32_t> FakeEventHub::getLightBrightness(int32_t deviceId, int32_t lightId) const {
+    auto lightIt = mLightBrightness.find(lightId);
+    if (lightIt == mLightBrightness.end()) {
+        return std::nullopt;
+    }
+    return lightIt->second;
+}
+
+std::optional<std::unordered_map<LightColor, int32_t>> FakeEventHub::getLightIntensities(
+        int32_t deviceId, int32_t lightId) const {
+    auto lightIt = mLightIntensities.find(lightId);
+    if (lightIt == mLightIntensities.end()) {
+        return std::nullopt;
+    }
+    return lightIt->second;
+};
+
+} // namespace android
diff --git a/services/inputflinger/tests/FakeEventHub.h b/services/inputflinger/tests/FakeEventHub.h
new file mode 100644
index 0000000..21cb2f1
--- /dev/null
+++ b/services/inputflinger/tests/FakeEventHub.h
@@ -0,0 +1,224 @@
+/*
+ * 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 <condition_variable>
+#include <mutex>
+#include <optional>
+#include <unordered_map>
+#include <vector>
+
+#include <EventHub.h>
+#include <InputDevice.h>
+#include <ftl/flags.h>
+#include <input/PropertyMap.h>
+#include <input/VirtualKeyMap.h>
+#include <utils/Errors.h>
+#include <utils/KeyedVector.h>
+
+#include "android/hardware/input/InputDeviceCountryCode.h"
+
+using android::hardware::input::InputDeviceCountryCode;
+
+namespace android {
+
+class FakeEventHub : public EventHubInterface {
+    struct KeyInfo {
+        int32_t keyCode;
+        uint32_t flags;
+    };
+
+    struct SensorInfo {
+        InputDeviceSensorType sensorType;
+        int32_t sensorDataIndex;
+    };
+
+    struct Device {
+        InputDeviceIdentifier identifier;
+        ftl::Flags<InputDeviceClass> classes;
+        PropertyMap configuration;
+        KeyedVector<int, RawAbsoluteAxisInfo> absoluteAxes;
+        KeyedVector<int, bool> relativeAxes;
+        KeyedVector<int32_t, int32_t> keyCodeStates;
+        KeyedVector<int32_t, int32_t> scanCodeStates;
+        KeyedVector<int32_t, int32_t> switchStates;
+        KeyedVector<int32_t, int32_t> absoluteAxisValue;
+        KeyedVector<int32_t, KeyInfo> keysByScanCode;
+        KeyedVector<int32_t, KeyInfo> keysByUsageCode;
+        KeyedVector<int32_t, bool> leds;
+        // fake mapping which would normally come from keyCharacterMap
+        std::unordered_map<int32_t, int32_t> keyCodeMapping;
+        std::unordered_map<int32_t, SensorInfo> sensorsByAbsCode;
+        BitArray<MSC_MAX> mscBitmask;
+        std::vector<VirtualKeyDefinition> virtualKeys;
+        bool enabled;
+        InputDeviceCountryCode countryCode;
+
+        status_t enable() {
+            enabled = true;
+            return OK;
+        }
+
+        status_t disable() {
+            enabled = false;
+            return OK;
+        }
+
+        explicit Device(ftl::Flags<InputDeviceClass> classes) : classes(classes), enabled(true) {}
+    };
+
+    std::mutex mLock;
+    std::condition_variable mEventsCondition;
+
+    KeyedVector<int32_t, Device*> mDevices;
+    std::vector<std::string> mExcludedDevices;
+    std::vector<RawEvent> mEvents GUARDED_BY(mLock);
+    std::unordered_map<int32_t /*deviceId*/, std::vector<TouchVideoFrame>> mVideoFrames;
+    std::vector<int32_t> mVibrators = {0, 1};
+    std::unordered_map<int32_t, RawLightInfo> mRawLightInfos;
+    // Simulates a device light brightness, from light id to light brightness.
+    std::unordered_map<int32_t /* lightId */, int32_t /* brightness*/> mLightBrightness;
+    // Simulates a device light intensities, from light id to light intensities map.
+    std::unordered_map<int32_t /* lightId */, std::unordered_map<LightColor, int32_t>>
+            mLightIntensities;
+
+public:
+    static constexpr int32_t DEFAULT_BATTERY = 1;
+    static constexpr int32_t BATTERY_STATUS = 4;
+    static constexpr int32_t BATTERY_CAPACITY = 66;
+    static const std::string BATTERY_DEVPATH;
+
+    virtual ~FakeEventHub();
+    FakeEventHub() {}
+
+    void addDevice(int32_t deviceId, const std::string& name, ftl::Flags<InputDeviceClass> classes,
+                   int bus = 0);
+    void removeDevice(int32_t deviceId);
+
+    bool isDeviceEnabled(int32_t deviceId) const override;
+    status_t enableDevice(int32_t deviceId) override;
+    status_t disableDevice(int32_t deviceId) override;
+
+    void finishDeviceScan();
+
+    void addConfigurationProperty(int32_t deviceId, const char* key, const char* value);
+    void addConfigurationMap(int32_t deviceId, const PropertyMap* configuration);
+
+    void addAbsoluteAxis(int32_t deviceId, int axis, int32_t minValue, int32_t maxValue, int flat,
+                         int fuzz, int resolution = 0);
+    void addRelativeAxis(int32_t deviceId, int32_t axis);
+    void setAbsoluteAxisValue(int32_t deviceId, int32_t axis, int32_t value);
+
+    void setCountryCode(int32_t deviceId, InputDeviceCountryCode countryCode);
+
+    void setKeyCodeState(int32_t deviceId, int32_t keyCode, int32_t state);
+    void setScanCodeState(int32_t deviceId, int32_t scanCode, int32_t state);
+    void setSwitchState(int32_t deviceId, int32_t switchCode, int32_t state);
+
+    void addKey(int32_t deviceId, int32_t scanCode, int32_t usageCode, int32_t keyCode,
+                uint32_t flags);
+    void addKeyCodeMapping(int32_t deviceId, int32_t fromKeyCode, int32_t toKeyCode);
+    void addVirtualKeyDefinition(int32_t deviceId, const VirtualKeyDefinition& definition);
+
+    void addSensorAxis(int32_t deviceId, int32_t absCode, InputDeviceSensorType sensorType,
+                       int32_t sensorDataIndex);
+
+    void setMscEvent(int32_t deviceId, int32_t mscEvent);
+
+    void addLed(int32_t deviceId, int32_t led, bool initialState);
+    void addRawLightInfo(int32_t rawId, RawLightInfo&& info);
+    void fakeLightBrightness(int32_t rawId, int32_t brightness);
+    void fakeLightIntensities(int32_t rawId,
+                              const std::unordered_map<LightColor, int32_t> intensities);
+    bool getLedState(int32_t deviceId, int32_t led);
+
+    std::vector<std::string>& getExcludedDevices();
+
+    void setVideoFrames(
+            std::unordered_map<int32_t /*deviceId*/, std::vector<TouchVideoFrame>> videoFrames);
+
+    void enqueueEvent(nsecs_t when, nsecs_t readTime, int32_t deviceId, int32_t type, int32_t code,
+                      int32_t value);
+    void assertQueueIsEmpty();
+
+private:
+    Device* getDevice(int32_t deviceId) const;
+
+    ftl::Flags<InputDeviceClass> getDeviceClasses(int32_t deviceId) const override;
+    InputDeviceIdentifier getDeviceIdentifier(int32_t deviceId) const override;
+    int32_t getDeviceControllerNumber(int32_t) const override;
+    void getConfiguration(int32_t deviceId, PropertyMap* outConfiguration) const override;
+    status_t getAbsoluteAxisInfo(int32_t deviceId, int axis,
+                                 RawAbsoluteAxisInfo* outAxisInfo) const override;
+    bool hasRelativeAxis(int32_t deviceId, int axis) const override;
+    bool hasInputProperty(int32_t, int) const override;
+    bool hasMscEvent(int32_t deviceId, int mscEvent) const override final;
+    status_t mapKey(int32_t deviceId, int32_t scanCode, int32_t usageCode, int32_t metaState,
+                    int32_t* outKeycode, int32_t* outMetaState, uint32_t* outFlags) const override;
+    const KeyInfo* getKey(Device* device, int32_t scanCode, int32_t usageCode) const;
+
+    status_t mapAxis(int32_t, int32_t, AxisInfo*) const override;
+    base::Result<std::pair<InputDeviceSensorType, int32_t>> mapSensor(
+            int32_t deviceId, int32_t absCode) const override;
+    void setExcludedDevices(const std::vector<std::string>& devices) override;
+    std::vector<RawEvent> getEvents(int) override;
+    std::vector<TouchVideoFrame> getVideoFrames(int32_t deviceId) override;
+    int32_t getScanCodeState(int32_t deviceId, int32_t scanCode) const override;
+    InputDeviceCountryCode getCountryCode(int32_t deviceId) const override;
+    int32_t getKeyCodeState(int32_t deviceId, int32_t keyCode) const override;
+    int32_t getSwitchState(int32_t deviceId, int32_t sw) const override;
+    status_t getAbsoluteAxisValue(int32_t deviceId, int32_t axis, int32_t* outValue) const override;
+    int32_t getKeyCodeForKeyLocation(int32_t deviceId, int32_t locationKeyCode) const override;
+
+    // Return true if the device has non-empty key layout.
+    bool markSupportedKeyCodes(int32_t deviceId, const std::vector<int32_t>& keyCodes,
+                               uint8_t* outFlags) const override;
+    bool hasScanCode(int32_t deviceId, int32_t scanCode) const override;
+    bool hasKeyCode(int32_t deviceId, int32_t keyCode) const override;
+    bool hasLed(int32_t deviceId, int32_t led) const override;
+    void setLedState(int32_t deviceId, int32_t led, bool on) override;
+    void getVirtualKeyDefinitions(int32_t deviceId,
+                                  std::vector<VirtualKeyDefinition>& outVirtualKeys) const override;
+    const std::shared_ptr<KeyCharacterMap> getKeyCharacterMap(int32_t) const override;
+    bool setKeyboardLayoutOverlay(int32_t, std::shared_ptr<KeyCharacterMap>) override;
+
+    void vibrate(int32_t, const VibrationElement&) override {}
+    void cancelVibrate(int32_t) override {}
+    std::vector<int32_t> getVibratorIds(int32_t deviceId) const override;
+
+    std::optional<int32_t> getBatteryCapacity(int32_t, int32_t) const override;
+    std::optional<int32_t> getBatteryStatus(int32_t, int32_t) const override;
+    std::vector<int32_t> getRawBatteryIds(int32_t deviceId) const override;
+    std::optional<RawBatteryInfo> getRawBatteryInfo(int32_t deviceId,
+                                                    int32_t batteryId) const override;
+
+    std::vector<int32_t> getRawLightIds(int32_t deviceId) const override;
+    std::optional<RawLightInfo> getRawLightInfo(int32_t deviceId, int32_t lightId) const override;
+    void setLightBrightness(int32_t deviceId, int32_t lightId, int32_t brightness) override;
+    void setLightIntensities(int32_t deviceId, int32_t lightId,
+                             std::unordered_map<LightColor, int32_t> intensities) override;
+    std::optional<int32_t> getLightBrightness(int32_t deviceId, int32_t lightId) const override;
+    std::optional<std::unordered_map<LightColor, int32_t>> getLightIntensities(
+            int32_t deviceId, int32_t lightId) const override;
+
+    void dump(std::string&) const override {}
+    void monitor() const override {}
+    void requestReopenDevices() override {}
+    void wake() override {}
+};
+
+} // namespace android
diff --git a/services/inputflinger/tests/FakeInputReaderPolicy.cpp b/services/inputflinger/tests/FakeInputReaderPolicy.cpp
new file mode 100644
index 0000000..3af4298
--- /dev/null
+++ b/services/inputflinger/tests/FakeInputReaderPolicy.cpp
@@ -0,0 +1,237 @@
+/*
+ * 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 "FakeInputReaderPolicy.h"
+
+#include <android-base/thread_annotations.h>
+#include <gtest/gtest.h>
+
+#include "TestConstants.h"
+#include "ui/Rotation.h"
+
+namespace android {
+
+void FakeInputReaderPolicy::assertInputDevicesChanged() {
+    waitForInputDevices([](bool devicesChanged) {
+        if (!devicesChanged) {
+            FAIL() << "Timed out waiting for notifyInputDevicesChanged() to be called.";
+        }
+    });
+}
+
+void FakeInputReaderPolicy::assertInputDevicesNotChanged() {
+    waitForInputDevices([](bool devicesChanged) {
+        if (devicesChanged) {
+            FAIL() << "Expected notifyInputDevicesChanged() to not be called.";
+        }
+    });
+}
+
+void FakeInputReaderPolicy::assertStylusGestureNotified(int32_t deviceId) {
+    std::scoped_lock lock(mLock);
+    ASSERT_TRUE(mStylusGestureNotified);
+    ASSERT_EQ(deviceId, *mStylusGestureNotified);
+    mStylusGestureNotified.reset();
+}
+
+void FakeInputReaderPolicy::assertStylusGestureNotNotified() {
+    std::scoped_lock lock(mLock);
+    ASSERT_FALSE(mStylusGestureNotified);
+}
+
+void FakeInputReaderPolicy::clearViewports() {
+    mViewports.clear();
+    mConfig.setDisplayViewports(mViewports);
+}
+
+std::optional<DisplayViewport> FakeInputReaderPolicy::getDisplayViewportByUniqueId(
+        const std::string& uniqueId) const {
+    return mConfig.getDisplayViewportByUniqueId(uniqueId);
+}
+std::optional<DisplayViewport> FakeInputReaderPolicy::getDisplayViewportByType(
+        ViewportType type) const {
+    return mConfig.getDisplayViewportByType(type);
+}
+
+std::optional<DisplayViewport> FakeInputReaderPolicy::getDisplayViewportByPort(
+        uint8_t displayPort) const {
+    return mConfig.getDisplayViewportByPort(displayPort);
+}
+
+void FakeInputReaderPolicy::addDisplayViewport(DisplayViewport viewport) {
+    mViewports.push_back(std::move(viewport));
+    mConfig.setDisplayViewports(mViewports);
+}
+
+void FakeInputReaderPolicy::addDisplayViewport(int32_t displayId, int32_t width, int32_t height,
+                                               ui::Rotation orientation, bool isActive,
+                                               const std::string& uniqueId,
+                                               std::optional<uint8_t> physicalPort,
+                                               ViewportType type) {
+    const bool isRotated = orientation == ui::ROTATION_90 || orientation == ui::ROTATION_270;
+    DisplayViewport v;
+    v.displayId = displayId;
+    v.orientation = orientation;
+    v.logicalLeft = 0;
+    v.logicalTop = 0;
+    v.logicalRight = isRotated ? height : width;
+    v.logicalBottom = isRotated ? width : height;
+    v.physicalLeft = 0;
+    v.physicalTop = 0;
+    v.physicalRight = isRotated ? height : width;
+    v.physicalBottom = isRotated ? width : height;
+    v.deviceWidth = isRotated ? height : width;
+    v.deviceHeight = isRotated ? width : height;
+    v.isActive = isActive;
+    v.uniqueId = uniqueId;
+    v.physicalPort = physicalPort;
+    v.type = type;
+
+    addDisplayViewport(v);
+}
+
+bool FakeInputReaderPolicy::updateViewport(const DisplayViewport& viewport) {
+    size_t count = mViewports.size();
+    for (size_t i = 0; i < count; i++) {
+        const DisplayViewport& currentViewport = mViewports[i];
+        if (currentViewport.displayId == viewport.displayId) {
+            mViewports[i] = viewport;
+            mConfig.setDisplayViewports(mViewports);
+            return true;
+        }
+    }
+    // no viewport found.
+    return false;
+}
+
+void FakeInputReaderPolicy::addExcludedDeviceName(const std::string& deviceName) {
+    mConfig.excludedDeviceNames.push_back(deviceName);
+}
+
+void FakeInputReaderPolicy::addInputPortAssociation(const std::string& inputPort,
+                                                    uint8_t displayPort) {
+    mConfig.portAssociations.insert({inputPort, displayPort});
+}
+
+void FakeInputReaderPolicy::addInputUniqueIdAssociation(const std::string& inputUniqueId,
+                                                        const std::string& displayUniqueId) {
+    mConfig.uniqueIdAssociations.insert({inputUniqueId, displayUniqueId});
+}
+
+void FakeInputReaderPolicy::addDisabledDevice(int32_t deviceId) {
+    mConfig.disabledDevices.insert(deviceId);
+}
+
+void FakeInputReaderPolicy::removeDisabledDevice(int32_t deviceId) {
+    mConfig.disabledDevices.erase(deviceId);
+}
+
+void FakeInputReaderPolicy::setPointerController(
+        std::shared_ptr<FakePointerController> controller) {
+    mPointerController = std::move(controller);
+}
+
+const InputReaderConfiguration* FakeInputReaderPolicy::getReaderConfiguration() const {
+    return &mConfig;
+}
+
+const std::vector<InputDeviceInfo>& FakeInputReaderPolicy::getInputDevices() const {
+    return mInputDevices;
+}
+
+TouchAffineTransformation FakeInputReaderPolicy::getTouchAffineTransformation(
+        const std::string& inputDeviceDescriptor, ui::Rotation surfaceRotation) {
+    return transform;
+}
+
+void FakeInputReaderPolicy::setTouchAffineTransformation(const TouchAffineTransformation t) {
+    transform = t;
+}
+
+PointerCaptureRequest FakeInputReaderPolicy::setPointerCapture(bool enabled) {
+    mConfig.pointerCaptureRequest = {enabled, mNextPointerCaptureSequenceNumber++};
+    return mConfig.pointerCaptureRequest;
+}
+
+void FakeInputReaderPolicy::setShowTouches(bool enabled) {
+    mConfig.showTouches = enabled;
+}
+
+void FakeInputReaderPolicy::setDefaultPointerDisplayId(int32_t pointerDisplayId) {
+    mConfig.defaultPointerDisplayId = pointerDisplayId;
+}
+
+void FakeInputReaderPolicy::setPointerGestureEnabled(bool enabled) {
+    mConfig.pointerGesturesEnabled = enabled;
+}
+
+float FakeInputReaderPolicy::getPointerGestureMovementSpeedRatio() {
+    return mConfig.pointerGestureMovementSpeedRatio;
+}
+
+float FakeInputReaderPolicy::getPointerGestureZoomSpeedRatio() {
+    return mConfig.pointerGestureZoomSpeedRatio;
+}
+
+void FakeInputReaderPolicy::setVelocityControlParams(const VelocityControlParameters& params) {
+    mConfig.pointerVelocityControlParameters = params;
+    mConfig.wheelVelocityControlParameters = params;
+}
+
+void FakeInputReaderPolicy::getReaderConfiguration(InputReaderConfiguration* outConfig) {
+    *outConfig = mConfig;
+}
+
+std::shared_ptr<PointerControllerInterface> FakeInputReaderPolicy::obtainPointerController(
+        int32_t /*deviceId*/) {
+    return mPointerController;
+}
+
+void FakeInputReaderPolicy::notifyInputDevicesChanged(
+        const std::vector<InputDeviceInfo>& inputDevices) {
+    std::scoped_lock<std::mutex> lock(mLock);
+    mInputDevices = inputDevices;
+    mInputDevicesChanged = true;
+    mDevicesChangedCondition.notify_all();
+}
+
+std::shared_ptr<KeyCharacterMap> FakeInputReaderPolicy::getKeyboardLayoutOverlay(
+        const InputDeviceIdentifier&) {
+    return nullptr;
+}
+
+std::string FakeInputReaderPolicy::getDeviceAlias(const InputDeviceIdentifier&) {
+    return "";
+}
+
+void FakeInputReaderPolicy::waitForInputDevices(std::function<void(bool)> processDevicesChanged) {
+    std::unique_lock<std::mutex> lock(mLock);
+    base::ScopedLockAssertion assumeLocked(mLock);
+
+    const bool devicesChanged =
+            mDevicesChangedCondition.wait_for(lock, WAIT_TIMEOUT, [this]() REQUIRES(mLock) {
+                return mInputDevicesChanged;
+            });
+    ASSERT_NO_FATAL_FAILURE(processDevicesChanged(devicesChanged));
+    mInputDevicesChanged = false;
+}
+
+void FakeInputReaderPolicy::notifyStylusGestureStarted(int32_t deviceId, nsecs_t eventTime) {
+    std::scoped_lock<std::mutex> lock(mLock);
+    mStylusGestureNotified = deviceId;
+}
+
+} // namespace android
diff --git a/services/inputflinger/tests/FakeInputReaderPolicy.h b/services/inputflinger/tests/FakeInputReaderPolicy.h
new file mode 100644
index 0000000..c16cda4
--- /dev/null
+++ b/services/inputflinger/tests/FakeInputReaderPolicy.h
@@ -0,0 +1,101 @@
+/*
+ * 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 <condition_variable>
+#include <memory>
+#include <mutex>
+#include <optional>
+#include <string>
+#include <vector>
+
+#include <InputDevice.h>
+#include <InputReaderBase.h>
+
+#include "FakePointerController.h"
+#include "input/DisplayViewport.h"
+#include "input/InputDevice.h"
+
+namespace android {
+
+class FakeInputReaderPolicy : public InputReaderPolicyInterface {
+protected:
+    virtual ~FakeInputReaderPolicy() {}
+
+public:
+    FakeInputReaderPolicy() {}
+
+    void assertInputDevicesChanged();
+    void assertInputDevicesNotChanged();
+    void assertStylusGestureNotified(int32_t deviceId);
+    void assertStylusGestureNotNotified();
+
+    virtual void clearViewports();
+    std::optional<DisplayViewport> getDisplayViewportByUniqueId(const std::string& uniqueId) const;
+    std::optional<DisplayViewport> getDisplayViewportByType(ViewportType type) const;
+    std::optional<DisplayViewport> getDisplayViewportByPort(uint8_t displayPort) const;
+    void addDisplayViewport(DisplayViewport viewport);
+    void addDisplayViewport(int32_t displayId, int32_t width, int32_t height,
+                            ui::Rotation orientation, bool isActive, const std::string& uniqueId,
+                            std::optional<uint8_t> physicalPort, ViewportType type);
+    bool updateViewport(const DisplayViewport& viewport);
+    void addExcludedDeviceName(const std::string& deviceName);
+    void addInputPortAssociation(const std::string& inputPort, uint8_t displayPort);
+    void addInputUniqueIdAssociation(const std::string& inputUniqueId,
+                                     const std::string& displayUniqueId);
+    void addDisabledDevice(int32_t deviceId);
+    void removeDisabledDevice(int32_t deviceId);
+    void setPointerController(std::shared_ptr<FakePointerController> controller);
+    const InputReaderConfiguration* getReaderConfiguration() const;
+    const std::vector<InputDeviceInfo>& getInputDevices() const;
+    TouchAffineTransformation getTouchAffineTransformation(const std::string& inputDeviceDescriptor,
+                                                           ui::Rotation surfaceRotation);
+    void setTouchAffineTransformation(const TouchAffineTransformation t);
+    PointerCaptureRequest setPointerCapture(bool enabled);
+    void setShowTouches(bool enabled);
+    void setDefaultPointerDisplayId(int32_t pointerDisplayId);
+    void setPointerGestureEnabled(bool enabled);
+    float getPointerGestureMovementSpeedRatio();
+    float getPointerGestureZoomSpeedRatio();
+    void setVelocityControlParams(const VelocityControlParameters& params);
+
+private:
+    void getReaderConfiguration(InputReaderConfiguration* outConfig) override;
+    std::shared_ptr<PointerControllerInterface> obtainPointerController(
+            int32_t /*deviceId*/) override;
+    void notifyInputDevicesChanged(const std::vector<InputDeviceInfo>& inputDevices) override;
+    std::shared_ptr<KeyCharacterMap> getKeyboardLayoutOverlay(
+            const InputDeviceIdentifier&) override;
+    std::string getDeviceAlias(const InputDeviceIdentifier&) override;
+    void waitForInputDevices(std::function<void(bool)> processDevicesChanged);
+    void notifyStylusGestureStarted(int32_t deviceId, nsecs_t eventTime) override;
+
+    std::mutex mLock;
+    std::condition_variable mDevicesChangedCondition;
+
+    InputReaderConfiguration mConfig;
+    std::shared_ptr<FakePointerController> mPointerController;
+    std::vector<InputDeviceInfo> mInputDevices GUARDED_BY(mLock);
+    bool mInputDevicesChanged GUARDED_BY(mLock){false};
+    std::vector<DisplayViewport> mViewports;
+    TouchAffineTransformation transform;
+    std::optional<int32_t /*deviceId*/> mStylusGestureNotified GUARDED_BY(mLock){};
+
+    uint32_t mNextPointerCaptureSequenceNumber{0};
+};
+
+} // namespace android
diff --git a/services/inputflinger/tests/FakePointerController.cpp b/services/inputflinger/tests/FakePointerController.cpp
new file mode 100644
index 0000000..635366b
--- /dev/null
+++ b/services/inputflinger/tests/FakePointerController.cpp
@@ -0,0 +1,93 @@
+/*
+ * 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 "FakePointerController.h"
+
+namespace android {
+
+void FakePointerController::setBounds(float minX, float minY, float maxX, float maxY) {
+    mHaveBounds = true;
+    mMinX = minX;
+    mMinY = minY;
+    mMaxX = maxX;
+    mMaxY = maxY;
+}
+
+const std::map<int32_t, std::vector<int32_t>>& FakePointerController::getSpots() {
+    return mSpotsByDisplay;
+}
+
+void FakePointerController::setPosition(float x, float y) {
+    mX = x;
+    mY = y;
+}
+
+void FakePointerController::setButtonState(int32_t buttonState) {
+    mButtonState = buttonState;
+}
+
+int32_t FakePointerController::getButtonState() const {
+    return mButtonState;
+}
+
+void FakePointerController::getPosition(float* outX, float* outY) const {
+    *outX = mX;
+    *outY = mY;
+}
+
+int32_t FakePointerController::getDisplayId() const {
+    return mDisplayId;
+}
+
+void FakePointerController::setDisplayViewport(const DisplayViewport& viewport) {
+    mDisplayId = viewport.displayId;
+}
+
+bool FakePointerController::getBounds(float* outMinX, float* outMinY, float* outMaxX,
+                                      float* outMaxY) const {
+    *outMinX = mMinX;
+    *outMinY = mMinY;
+    *outMaxX = mMaxX;
+    *outMaxY = mMaxY;
+    return mHaveBounds;
+}
+
+void FakePointerController::move(float deltaX, float deltaY) {
+    mX += deltaX;
+    if (mX < mMinX) mX = mMinX;
+    if (mX > mMaxX) mX = mMaxX;
+    mY += deltaY;
+    if (mY < mMinY) mY = mMinY;
+    if (mY > mMaxY) mY = mMaxY;
+}
+
+void FakePointerController::setSpots(const PointerCoords*, const uint32_t*, BitSet32 spotIdBits,
+                                     int32_t displayId) {
+    std::vector<int32_t> newSpots;
+    // Add spots for fingers that are down.
+    for (BitSet32 idBits(spotIdBits); !idBits.isEmpty();) {
+        uint32_t id = idBits.clearFirstMarkedBit();
+        newSpots.push_back(id);
+    }
+
+    mSpotsByDisplay[displayId] = newSpots;
+}
+
+void FakePointerController::clearSpots() {
+    mSpotsByDisplay.clear();
+}
+
+} // namespace android
diff --git a/services/inputflinger/tests/FakePointerController.h b/services/inputflinger/tests/FakePointerController.h
new file mode 100644
index 0000000..f00870f
--- /dev/null
+++ b/services/inputflinger/tests/FakePointerController.h
@@ -0,0 +1,60 @@
+/*
+ * 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 <PointerControllerInterface.h>
+#include <gui/constants.h>
+#include <input/DisplayViewport.h>
+#include <input/Input.h>
+#include <utils/BitSet.h>
+
+namespace android {
+
+class FakePointerController : public PointerControllerInterface {
+public:
+    virtual ~FakePointerController() {}
+
+    void setBounds(float minX, float minY, float maxX, float maxY);
+    const std::map<int32_t, std::vector<int32_t>>& getSpots();
+
+    void setPosition(float x, float y) override;
+    void setButtonState(int32_t buttonState) override;
+    int32_t getButtonState() const override;
+    void getPosition(float* outX, float* outY) const override;
+    int32_t getDisplayId() const override;
+    void setDisplayViewport(const DisplayViewport& viewport) override;
+
+private:
+    bool getBounds(float* outMinX, float* outMinY, float* outMaxX, float* outMaxY) 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,
+                  int32_t displayId) override;
+    void clearSpots() override;
+
+    bool mHaveBounds{false};
+    float mMinX{0}, mMinY{0}, mMaxX{0}, mMaxY{0};
+    float mX{0}, mY{0};
+    int32_t mButtonState{0};
+    int32_t mDisplayId{ADISPLAY_ID_DEFAULT};
+
+    std::map<int32_t, std::vector<int32_t>> mSpotsByDisplay;
+};
+
+} // namespace android
diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp
index 985c9c5..9881cd4 100644
--- a/services/inputflinger/tests/InputDispatcher_test.cpp
+++ b/services/inputflinger/tests/InputDispatcher_test.cpp
@@ -22,6 +22,7 @@
 #include <android-base/thread_annotations.h>
 #include <binder/Binder.h>
 #include <fcntl.h>
+#include <gmock/gmock.h>
 #include <gtest/gtest.h>
 #include <input/Input.h>
 #include <linux/input.h>
@@ -43,6 +44,7 @@
 namespace android::inputdispatcher {
 
 using namespace ftl::flag_operators;
+using testing::AllOf;
 
 // An arbitrary time value.
 static constexpr nsecs_t ARBITRARY_TIME = 1234;
@@ -102,6 +104,39 @@
             << MotionEvent::actionToString(receivedAction);
 }
 
+MATCHER_P(WithMotionAction, action, "MotionEvent with specified action") {
+    bool matches = action == arg.getAction();
+    if (!matches) {
+        *result_listener << "expected action " << MotionEvent::actionToString(action)
+                         << ", but got " << MotionEvent::actionToString(arg.getAction());
+    }
+    if (action == AMOTION_EVENT_ACTION_DOWN) {
+        if (!matches) {
+            *result_listener << "; ";
+        }
+        *result_listener << "downTime should match eventTime for ACTION_DOWN events";
+        matches &= arg.getDownTime() == arg.getEventTime();
+    }
+    if (action == AMOTION_EVENT_ACTION_CANCEL) {
+        if (!matches) {
+            *result_listener << "; ";
+        }
+        *result_listener << "expected FLAG_CANCELED to be set with ACTION_CANCEL, but was not set";
+        matches &= (arg.getFlags() & AMOTION_EVENT_FLAG_CANCELED) != 0;
+    }
+    return matches;
+}
+
+MATCHER_P(WithDownTime, downTime, "InputEvent with specified downTime") {
+    return arg.getDownTime() == downTime;
+}
+
+MATCHER_P(WithSource, source, "InputEvent with specified source") {
+    *result_listener << "expected source " << inputEventSourceToString(source) << ", but got "
+                     << inputEventSourceToString(arg.getSource());
+    return arg.getSource() == source;
+}
+
 // --- FakeInputDispatcherPolicy ---
 
 class FakeInputDispatcherPolicy : public InputDispatcherPolicyInterface {
@@ -1186,6 +1221,7 @@
     void consumeMotionOutsideWithZeroedCoords(int32_t expectedDisplayId = ADISPLAY_ID_DEFAULT,
                                               int32_t expectedFlags = 0) {
         InputEvent* event = consume();
+        ASSERT_NE(nullptr, event);
         ASSERT_EQ(AINPUT_EVENT_TYPE_MOTION, event->getType());
         const MotionEvent& motionEvent = static_cast<MotionEvent&>(*event);
         EXPECT_EQ(AMOTION_EVENT_ACTION_OUTSIDE, motionEvent.getActionMasked());
@@ -1205,6 +1241,12 @@
         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(int32_t expectedEventType, int32_t expectedAction,
                       std::optional<int32_t> expectedDisplayId,
                       std::optional<int32_t> expectedFlags) {
@@ -1892,6 +1934,64 @@
     wallpaperWindow->assertNoEvents();
 }
 
+TEST_F(InputDispatcherTest, WallpaperWindowReceivesMultiTouch) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> window =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Top", ADISPLAY_ID_DEFAULT);
+    window->setDupTouchToWallpaper(true);
+
+    sp<FakeWindowHandle> wallpaperWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Wallpaper", ADISPLAY_ID_DEFAULT);
+    wallpaperWindow->setIsWallpaper(true);
+    constexpr int expectedWallpaperFlags =
+            AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED | AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED;
+    wallpaperWindow->setPreventSplitting(true);
+
+    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window, wallpaperWindow}}});
+
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+                               {50, 50}))
+            << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
+    window->consumeMotionDown(ADISPLAY_ID_DEFAULT);
+    wallpaperWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
+
+    const MotionEvent secondFingerDownEvent =
+            MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                    .displayId(ADISPLAY_ID_DEFAULT)
+                    .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
+                    .pointer(PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50))
+                    .pointer(PointerBuilder(/* id */ 1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(10).y(10))
+                    .build();
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionEvent(mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT,
+                                InputEventInjectionSync::WAIT_FOR_RESULT))
+            << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
+
+    window->consumeMotionPointerDown(1);
+    wallpaperWindow->consumeMotionPointerDown(1, ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
+
+    const MotionEvent secondFingerUpEvent =
+            MotionEventBuilder(POINTER_1_UP, AINPUT_SOURCE_TOUCHSCREEN)
+                    .displayId(ADISPLAY_ID_DEFAULT)
+                    .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
+                    .pointer(PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50))
+                    .pointer(PointerBuilder(/* id */ 1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(10).y(10))
+                    .build();
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionEvent(mDispatcher, secondFingerUpEvent, INJECT_EVENT_TIMEOUT,
+                                InputEventInjectionSync::WAIT_FOR_RESULT))
+            << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
+    window->consumeMotionPointerUp(1);
+    wallpaperWindow->consumeMotionPointerUp(1, ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
+
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionUp(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, {50, 50}))
+            << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
+    window->consumeMotionUp(ADISPLAY_ID_DEFAULT);
+    wallpaperWindow->consumeMotionUp(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
+}
+
 /**
  * On the display, have a single window, and also an area where there's no window.
  * First pointer touches the "no window" area of the screen. Second pointer touches the window.
@@ -1968,6 +2068,7 @@
 
     mDispatcher->waitForIdle();
     InputEvent* inputEvent1 = window1->consume();
+    ASSERT_NE(inputEvent1, nullptr);
     window2->assertNoEvents();
     MotionEvent& motionEvent1 = static_cast<MotionEvent&>(*inputEvent1);
     nsecs_t downTimeForWindow1 = motionEvent1.getDownTime();
@@ -1977,6 +2078,7 @@
     mDispatcher->notifyMotion(&(args = generateTouchArgs(POINTER_1_DOWN, {{50, 50}, {150, 50}})));
     mDispatcher->waitForIdle();
     InputEvent* inputEvent2 = window2->consume();
+    ASSERT_NE(inputEvent2, nullptr);
     MotionEvent& motionEvent2 = static_cast<MotionEvent&>(*inputEvent2);
     nsecs_t downTimeForWindow2 = motionEvent2.getDownTime();
     ASSERT_NE(downTimeForWindow1, downTimeForWindow2);
@@ -1986,17 +2088,13 @@
     mDispatcher->notifyMotion(
             &(args = generateTouchArgs(AMOTION_EVENT_ACTION_MOVE, {{50, 50}, {151, 51}})));
     mDispatcher->waitForIdle();
-    InputEvent* inputEvent3 = window2->consume();
-    MotionEvent& motionEvent3 = static_cast<MotionEvent&>(*inputEvent3);
-    ASSERT_EQ(motionEvent3.getDownTime(), downTimeForWindow2);
+    window2->consumeMotionEvent(WithDownTime(downTimeForWindow2));
 
     // Now add new touch down on the second window
     mDispatcher->notifyMotion(
             &(args = generateTouchArgs(POINTER_2_DOWN, {{50, 50}, {151, 51}, {150, 50}})));
     mDispatcher->waitForIdle();
-    InputEvent* inputEvent4 = window2->consume();
-    MotionEvent& motionEvent4 = static_cast<MotionEvent&>(*inputEvent4);
-    ASSERT_EQ(motionEvent4.getDownTime(), downTimeForWindow2);
+    window2->consumeMotionEvent(WithDownTime(downTimeForWindow2));
 
     // TODO(b/232530217): do not send the unnecessary MOVE event and delete the next line
     window1->consumeMotionMove();
@@ -2006,16 +2104,12 @@
     mDispatcher->notifyMotion(
             &(args = generateTouchArgs(AMOTION_EVENT_ACTION_MOVE, {{51, 51}, {151, 51}})));
     mDispatcher->waitForIdle();
-    InputEvent* inputEvent5 = window1->consume();
-    MotionEvent& motionEvent5 = static_cast<MotionEvent&>(*inputEvent5);
-    ASSERT_EQ(motionEvent5.getDownTime(), downTimeForWindow1);
+    window1->consumeMotionEvent(WithDownTime(downTimeForWindow1));
 
     mDispatcher->notifyMotion(&(
             args = generateTouchArgs(POINTER_3_DOWN, {{51, 51}, {151, 51}, {150, 50}, {50, 50}})));
     mDispatcher->waitForIdle();
-    InputEvent* inputEvent6 = window1->consume();
-    MotionEvent& motionEvent6 = static_cast<MotionEvent&>(*inputEvent6);
-    ASSERT_EQ(motionEvent6.getDownTime(), downTimeForWindow1);
+    window1->consumeMotionEvent(WithDownTime(downTimeForWindow1));
 }
 
 TEST_F(InputDispatcherTest, HoverMoveEnterMouseClickAndHoverMoveExit) {
@@ -2040,10 +2134,7 @@
                                                          .x(900)
                                                          .y(400))
                                         .build()));
-    windowRight->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_HOVER_ENTER,
-                              ADISPLAY_ID_DEFAULT, 0 /* expectedFlag */);
-    windowRight->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_HOVER_MOVE,
-                              ADISPLAY_ID_DEFAULT, 0 /* expectedFlag */);
+    windowRight->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER));
 
     // Move cursor into left window
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
@@ -2054,12 +2145,8 @@
                                                          .x(300)
                                                          .y(400))
                                         .build()));
-    windowRight->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_HOVER_EXIT,
-                              ADISPLAY_ID_DEFAULT, 0 /* expectedFlag */);
-    windowLeft->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_HOVER_ENTER,
-                             ADISPLAY_ID_DEFAULT, 0 /* expectedFlag */);
-    windowLeft->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_HOVER_MOVE,
-                             ADISPLAY_ID_DEFAULT, 0 /* expectedFlag */);
+    windowRight->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT));
+    windowLeft->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER));
 
     // Inject a series of mouse events for a mouse click
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
@@ -2082,8 +2169,7 @@
                                                          .x(300)
                                                          .y(400))
                                         .build()));
-    windowLeft->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_BUTTON_PRESS,
-                             ADISPLAY_ID_DEFAULT, 0 /* expectedFlag */);
+    windowLeft->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS));
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectMotionEvent(mDispatcher,
@@ -2095,8 +2181,7 @@
                                                          .x(300)
                                                          .y(400))
                                         .build()));
-    windowLeft->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_BUTTON_RELEASE,
-                             ADISPLAY_ID_DEFAULT, 0 /* expectedFlag */);
+    windowLeft->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE));
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectMotionEvent(mDispatcher,
@@ -2117,12 +2202,46 @@
                                                          .x(900)
                                                          .y(400))
                                         .build()));
-    windowLeft->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_HOVER_EXIT,
-                             ADISPLAY_ID_DEFAULT, 0 /* expectedFlag */);
-    windowRight->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_HOVER_ENTER,
-                              ADISPLAY_ID_DEFAULT, 0 /* expectedFlag */);
-    windowRight->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_HOVER_MOVE,
-                              ADISPLAY_ID_DEFAULT, 0 /* expectedFlag */);
+    windowLeft->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT));
+    windowRight->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER));
+
+    // No more events
+    windowLeft->assertNoEvents();
+    windowRight->assertNoEvents();
+}
+
+TEST_F(InputDispatcherTest, HoverWithSpyWindows) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+
+    sp<FakeWindowHandle> spyWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Spy", ADISPLAY_ID_DEFAULT);
+    spyWindow->setFrame(Rect(0, 0, 600, 800));
+    spyWindow->setTrustedOverlay(true);
+    spyWindow->setSpy(true);
+    sp<FakeWindowHandle> window =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+    window->setFrame(Rect(0, 0, 600, 800));
+
+    mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
+    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {spyWindow, window}}});
+
+    // Send mouse cursor to the window
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionEvent(mDispatcher,
+                                MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER,
+                                                   AINPUT_SOURCE_MOUSE)
+                                        .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE)
+                                                         .x(100)
+                                                         .y(100))
+                                        .build()));
+
+    window->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
+                                     WithSource(AINPUT_SOURCE_MOUSE)));
+    spyWindow->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
+                                        WithSource(AINPUT_SOURCE_MOUSE)));
+
+    window->assertNoEvents();
+    spyWindow->assertNoEvents();
 }
 
 // This test is different from the test above that HOVER_ENTER and HOVER_EXIT events are injected
@@ -2145,9 +2264,7 @@
                                                          .x(300)
                                                          .y(400))
                                         .build()));
-    window->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_HOVER_ENTER,
-                         ADISPLAY_ID_DEFAULT, 0 /* expectedFlag */);
-
+    window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER));
     // Inject a series of mouse events for a mouse click
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectMotionEvent(mDispatcher,
@@ -2169,8 +2286,7 @@
                                                          .x(300)
                                                          .y(400))
                                         .build()));
-    window->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_BUTTON_PRESS,
-                         ADISPLAY_ID_DEFAULT, 0 /* expectedFlag */);
+    window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS));
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectMotionEvent(mDispatcher,
@@ -2182,8 +2298,7 @@
                                                          .x(300)
                                                          .y(400))
                                         .build()));
-    window->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_BUTTON_RELEASE,
-                         ADISPLAY_ID_DEFAULT, 0 /* expectedFlag */);
+    window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE));
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectMotionEvent(mDispatcher,
@@ -2203,8 +2318,132 @@
                                                          .x(300)
                                                          .y(400))
                                         .build()));
-    window->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_HOVER_EXIT,
-                         ADISPLAY_ID_DEFAULT, 0 /* expectedFlag */);
+    window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT));
+}
+
+/**
+ * Hover over a window, and then remove that window. Make sure that HOVER_EXIT for that event
+ * is generated.
+ */
+TEST_F(InputDispatcherTest, HoverExitIsSentToRemovedWindow) {
+    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, 1200, 800));
+
+    mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
+
+    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionEvent(mDispatcher,
+                                MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER,
+                                                   AINPUT_SOURCE_MOUSE)
+                                        .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE)
+                                                         .x(300)
+                                                         .y(400))
+                                        .build()));
+    window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER));
+
+    // Remove the window, but keep the channel.
+    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {}}});
+    window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT));
+}
+
+/**
+ * Inject a mouse hover event followed by a tap from touchscreen.
+ * The tap causes a HOVER_EXIT event to be generated because the current event
+ * stream's source has been switched.
+ */
+TEST_F(InputDispatcherTest, MouseHoverAndTouchTap) {
+    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, 100, 100));
+
+    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+
+    // Inject a hover_move from mouse.
+    NotifyMotionArgs motionArgs =
+            generateMotionArgs(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE,
+                               ADISPLAY_ID_DEFAULT, {{50, 50}});
+    motionArgs.xCursorPosition = 50;
+    motionArgs.yCursorPosition = 50;
+    mDispatcher->notifyMotion(&motionArgs);
+    ASSERT_NO_FATAL_FAILURE(
+            window->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
+                                             WithSource(AINPUT_SOURCE_MOUSE))));
+
+    // Tap on the window
+    motionArgs = generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
+                                    ADISPLAY_ID_DEFAULT, {{10, 10}});
+    mDispatcher->notifyMotion(&motionArgs);
+    ASSERT_NO_FATAL_FAILURE(
+            window->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT),
+                                             WithSource(AINPUT_SOURCE_MOUSE))));
+
+    ASSERT_NO_FATAL_FAILURE(
+            window->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+                                             WithSource(AINPUT_SOURCE_TOUCHSCREEN))));
+
+    motionArgs = generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN,
+                                    ADISPLAY_ID_DEFAULT, {{10, 10}});
+    mDispatcher->notifyMotion(&motionArgs);
+    ASSERT_NO_FATAL_FAILURE(
+            window->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                                             WithSource(AINPUT_SOURCE_TOUCHSCREEN))));
+}
+
+TEST_F(InputDispatcherTest, HoverEnterMoveRemoveWindowsInSecondDisplay) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> windowDefaultDisplay =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "DefaultDisplay",
+                                       ADISPLAY_ID_DEFAULT);
+    windowDefaultDisplay->setFrame(Rect(0, 0, 600, 800));
+    sp<FakeWindowHandle> windowSecondDisplay =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "SecondDisplay",
+                                       SECOND_DISPLAY_ID);
+    windowSecondDisplay->setFrame(Rect(0, 0, 600, 800));
+
+    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {windowDefaultDisplay}},
+                                  {SECOND_DISPLAY_ID, {windowSecondDisplay}}});
+
+    // Set cursor position in window in default display and check that hover enter and move
+    // events are generated.
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionEvent(mDispatcher,
+                                MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE,
+                                                   AINPUT_SOURCE_MOUSE)
+                                        .displayId(ADISPLAY_ID_DEFAULT)
+                                        .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE)
+                                                         .x(300)
+                                                         .y(600))
+                                        .build()));
+    windowDefaultDisplay->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER));
+
+    // Remove all windows in secondary display and check that no event happens on window in
+    // primary display.
+    mDispatcher->setInputWindows(
+            {{ADISPLAY_ID_DEFAULT, {windowDefaultDisplay}}, {SECOND_DISPLAY_ID, {}}});
+    windowDefaultDisplay->assertNoEvents();
+
+    // Move cursor position in window in default display and check that only hover move
+    // event is generated and not hover enter event.
+    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {windowDefaultDisplay}},
+                                  {SECOND_DISPLAY_ID, {windowSecondDisplay}}});
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionEvent(mDispatcher,
+                                MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE,
+                                                   AINPUT_SOURCE_MOUSE)
+                                        .displayId(ADISPLAY_ID_DEFAULT)
+                                        .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE)
+                                                         .x(400)
+                                                         .y(700))
+                                        .build()));
+    windowDefaultDisplay->consumeMotionEvent(
+            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+                  WithSource(AINPUT_SOURCE_MOUSE)));
+    windowDefaultDisplay->assertNoEvents();
 }
 
 TEST_F(InputDispatcherTest, DispatchMouseEventsUnderCursor) {
@@ -2400,6 +2639,43 @@
     window->assertNoEvents();
 }
 
+TEST_F(InputDispatcherTest, NonSplitTouchableWindowReceivesMultiTouch) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher,
+                                                             "Fake Window", ADISPLAY_ID_DEFAULT);
+    // Ensure window is non-split and have some transform.
+    window->setPreventSplitting(true);
+    window->setWindowOffset(20, 40);
+    mDispatcher->onWindowInfosChanged({*window->getInfo()}, {});
+
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+                               {50, 50}))
+            << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
+    window->consumeMotionDown(ADISPLAY_ID_DEFAULT);
+
+    const MotionEvent secondFingerDownEvent =
+            MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                    .displayId(ADISPLAY_ID_DEFAULT)
+                    .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
+                    .pointer(PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50))
+                    .pointer(PointerBuilder(/* id */ 1, AMOTION_EVENT_TOOL_TYPE_FINGER)
+                                     .x(-30)
+                                     .y(-50))
+                    .build();
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionEvent(mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT,
+                                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
+}
+
 /**
  * Ensure the correct coordinate spaces are used by InputDispatcher.
  *
@@ -4449,6 +4725,7 @@
 
         const MotionEvent& motionEvent = static_cast<const MotionEvent&>(*event);
         assertMotionAction(expectedAction, motionEvent.getAction());
+        ASSERT_EQ(points.size(), motionEvent.getPointerCount());
 
         for (size_t i = 0; i < points.size(); i++) {
             float expectedX = points[i].x;
@@ -7027,8 +7304,8 @@
             break; // epoll_wait timed out
         }
         for (int i = 0; i < nFds; i++) {
-            ASSERT_EQ(EPOLLIN, events[i].events);
-            eventOrder.push_back(events[i].data.u64);
+            ASSERT_EQ(static_cast<uint32_t>(EPOLLIN), events[i].events);
+            eventOrder.push_back(static_cast<size_t>(events[i].data.u64));
             channels[i]->consumeMotionDown();
         }
     }
diff --git a/services/inputflinger/tests/InputMapperTest.cpp b/services/inputflinger/tests/InputMapperTest.cpp
new file mode 100644
index 0000000..3cd7c1b
--- /dev/null
+++ b/services/inputflinger/tests/InputMapperTest.cpp
@@ -0,0 +1,176 @@
+/*
+ * 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 "InputMapperTest.h"
+
+#include <InputReaderBase.h>
+#include <gtest/gtest.h>
+#include <ui/Rotation.h>
+
+namespace android {
+
+const char* InputMapperTest::DEVICE_NAME = "device";
+const char* InputMapperTest::DEVICE_LOCATION = "USB1";
+const ftl::Flags<InputDeviceClass> InputMapperTest::DEVICE_CLASSES =
+        ftl::Flags<InputDeviceClass>(0); // not needed for current tests
+
+void InputMapperTest::SetUp(ftl::Flags<InputDeviceClass> classes, int bus) {
+    mFakeEventHub = std::make_unique<FakeEventHub>();
+    mFakePolicy = sp<FakeInputReaderPolicy>::make();
+    mFakeListener = std::make_unique<TestInputListener>();
+    mReader = std::make_unique<InstrumentedInputReader>(mFakeEventHub, mFakePolicy, *mFakeListener);
+    mDevice = newDevice(DEVICE_ID, DEVICE_NAME, DEVICE_LOCATION, EVENTHUB_ID, classes, bus);
+    // Consume the device reset notification generated when adding a new device.
+    mFakeListener->assertNotifyDeviceResetWasCalled();
+}
+
+void InputMapperTest::SetUp() {
+    SetUp(DEVICE_CLASSES);
+}
+
+void InputMapperTest::TearDown() {
+    mFakeListener.reset();
+    mFakePolicy.clear();
+}
+
+void InputMapperTest::addConfigurationProperty(const char* key, const char* value) {
+    mFakeEventHub->addConfigurationProperty(EVENTHUB_ID, key, value);
+}
+
+std::list<NotifyArgs> InputMapperTest::configureDevice(uint32_t changes) {
+    if (!changes ||
+        (changes &
+         (InputReaderConfiguration::CHANGE_DISPLAY_INFO |
+          InputReaderConfiguration::CHANGE_POINTER_CAPTURE))) {
+        mReader->requestRefreshConfiguration(changes);
+        mReader->loopOnce();
+    }
+    std::list<NotifyArgs> out =
+            mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), changes);
+    // Loop the reader to flush the input listener queue.
+    for (const NotifyArgs& args : out) {
+        mFakeListener->notify(args);
+    }
+    mReader->loopOnce();
+    return out;
+}
+
+std::shared_ptr<InputDevice> InputMapperTest::newDevice(int32_t deviceId, const std::string& name,
+                                                        const std::string& location,
+                                                        int32_t eventHubId,
+                                                        ftl::Flags<InputDeviceClass> classes,
+                                                        int bus) {
+    InputDeviceIdentifier identifier;
+    identifier.name = name;
+    identifier.location = location;
+    identifier.bus = bus;
+    std::shared_ptr<InputDevice> device =
+            std::make_shared<InputDevice>(mReader->getContext(), deviceId, DEVICE_GENERATION,
+                                          identifier);
+    mReader->pushNextDevice(device);
+    mFakeEventHub->addDevice(eventHubId, name, classes, bus);
+    mReader->loopOnce();
+    return device;
+}
+
+void InputMapperTest::setDisplayInfoAndReconfigure(int32_t displayId, int32_t width, int32_t height,
+                                                   ui::Rotation orientation,
+                                                   const std::string& uniqueId,
+                                                   std::optional<uint8_t> physicalPort,
+                                                   ViewportType viewportType) {
+    mFakePolicy->addDisplayViewport(displayId, width, height, orientation, /* isActive= */ true,
+                                    uniqueId, physicalPort, viewportType);
+    configureDevice(InputReaderConfiguration::CHANGE_DISPLAY_INFO);
+}
+
+void InputMapperTest::clearViewports() {
+    mFakePolicy->clearViewports();
+}
+
+std::list<NotifyArgs> InputMapperTest::process(InputMapper& mapper, nsecs_t when, nsecs_t readTime,
+                                               int32_t type, int32_t code, int32_t value) {
+    RawEvent event;
+    event.when = when;
+    event.readTime = readTime;
+    event.deviceId = mapper.getDeviceContext().getEventHubId();
+    event.type = type;
+    event.code = code;
+    event.value = value;
+    std::list<NotifyArgs> processArgList = mapper.process(&event);
+    for (const NotifyArgs& args : processArgList) {
+        mFakeListener->notify(args);
+    }
+    // Loop the reader to flush the input listener queue.
+    mReader->loopOnce();
+    return processArgList;
+}
+
+void InputMapperTest::resetMapper(InputMapper& mapper, nsecs_t when) {
+    const auto resetArgs = mapper.reset(when);
+    for (const auto args : resetArgs) {
+        mFakeListener->notify(args);
+    }
+    // Loop the reader to flush the input listener queue.
+    mReader->loopOnce();
+}
+
+std::list<NotifyArgs> InputMapperTest::handleTimeout(InputMapper& mapper, nsecs_t when) {
+    std::list<NotifyArgs> generatedArgs = mapper.timeoutExpired(when);
+    for (const NotifyArgs& args : generatedArgs) {
+        mFakeListener->notify(args);
+    }
+    // Loop the reader to flush the input listener queue.
+    mReader->loopOnce();
+    return generatedArgs;
+}
+
+void InputMapperTest::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;
+    ASSERT_EQ(source, range->source) << "Axis: " << axis << " Source: " << source;
+    ASSERT_NEAR(min, range->min, EPSILON) << "Axis: " << axis << " Source: " << source;
+    ASSERT_NEAR(max, range->max, EPSILON) << "Axis: " << axis << " Source: " << source;
+    ASSERT_NEAR(flat, range->flat, EPSILON) << "Axis: " << axis << " Source: " << source;
+    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) {
+    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);
+    ASSERT_NEAR(size, coords.getAxisValue(AMOTION_EVENT_AXIS_SIZE), EPSILON);
+    ASSERT_NEAR(touchMajor, coords.getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR), scaledAxisEpsilon);
+    ASSERT_NEAR(touchMinor, coords.getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR), scaledAxisEpsilon);
+    ASSERT_NEAR(toolMajor, coords.getAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR), scaledAxisEpsilon);
+    ASSERT_NEAR(toolMinor, coords.getAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR), scaledAxisEpsilon);
+    ASSERT_NEAR(orientation, coords.getAxisValue(AMOTION_EVENT_AXIS_ORIENTATION), EPSILON);
+    ASSERT_NEAR(distance, coords.getAxisValue(AMOTION_EVENT_AXIS_DISTANCE), EPSILON);
+}
+
+void InputMapperTest::assertPosition(const FakePointerController& controller, float x, float y) {
+    float actualX, actualY;
+    controller.getPosition(&actualX, &actualY);
+    ASSERT_NEAR(x, actualX, 1);
+    ASSERT_NEAR(y, actualY, 1);
+}
+
+} // namespace android
diff --git a/services/inputflinger/tests/InputMapperTest.h b/services/inputflinger/tests/InputMapperTest.h
new file mode 100644
index 0000000..b3401c3
--- /dev/null
+++ b/services/inputflinger/tests/InputMapperTest.h
@@ -0,0 +1,95 @@
+/*
+ * 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 <list>
+#include <memory>
+
+#include <InputDevice.h>
+#include <InputMapper.h>
+#include <NotifyArgs.h>
+#include <ftl/flags.h>
+#include <utils/StrongPointer.h>
+
+#include "FakeEventHub.h"
+#include "FakeInputReaderPolicy.h"
+#include "InstrumentedInputReader.h"
+#include "TestConstants.h"
+#include "TestInputListener.h"
+
+namespace android {
+
+class InputMapperTest : public testing::Test {
+protected:
+    static const char* DEVICE_NAME;
+    static const char* DEVICE_LOCATION;
+    static constexpr int32_t DEVICE_ID = END_RESERVED_ID + 1000;
+    static constexpr int32_t DEVICE_GENERATION = 2;
+    static constexpr int32_t DEVICE_CONTROLLER_NUMBER = 0;
+    static const ftl::Flags<InputDeviceClass> DEVICE_CLASSES;
+    static constexpr int32_t EVENTHUB_ID = 1;
+
+    std::shared_ptr<FakeEventHub> mFakeEventHub;
+    sp<FakeInputReaderPolicy> mFakePolicy;
+    std::unique_ptr<TestInputListener> mFakeListener;
+    std::unique_ptr<InstrumentedInputReader> mReader;
+    std::shared_ptr<InputDevice> mDevice;
+
+    virtual void SetUp(ftl::Flags<InputDeviceClass> classes, int bus = 0);
+    void SetUp() override;
+    void TearDown() override;
+
+    void addConfigurationProperty(const char* key, const char* value);
+    std::list<NotifyArgs> configureDevice(uint32_t changes);
+    std::shared_ptr<InputDevice> newDevice(int32_t deviceId, const std::string& name,
+                                           const std::string& location, int32_t eventHubId,
+                                           ftl::Flags<InputDeviceClass> classes, int bus = 0);
+    template <class T, typename... Args>
+    T& addMapperAndConfigure(Args... args) {
+        T& mapper = mDevice->addMapper<T>(EVENTHUB_ID, args...);
+        configureDevice(0);
+        std::list<NotifyArgs> resetArgList = mDevice->reset(ARBITRARY_TIME);
+        resetArgList += mapper.reset(ARBITRARY_TIME);
+        // Loop the reader to flush the input listener queue.
+        for (const NotifyArgs& loopArgs : resetArgList) {
+            mFakeListener->notify(loopArgs);
+        }
+        mReader->loopOnce();
+        return mapper;
+    }
+
+    void setDisplayInfoAndReconfigure(int32_t displayId, int32_t width, int32_t height,
+                                      ui::Rotation orientation, const std::string& uniqueId,
+                                      std::optional<uint8_t> physicalPort,
+                                      ViewportType viewportType);
+    void clearViewports();
+    std::list<NotifyArgs> process(InputMapper& mapper, nsecs_t when, nsecs_t readTime, int32_t type,
+                                  int32_t code, int32_t value);
+    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);
+    static void assertPosition(const FakePointerController& controller, float x, float y);
+};
+
+} // namespace android
diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp
index 1e26265..eef5690 100644
--- a/services/inputflinger/tests/InputReader_test.cpp
+++ b/services/inputflinger/tests/InputReader_test.cpp
@@ -36,9 +36,18 @@
 #include <UinputDevice.h>
 #include <VibratorInputMapper.h>
 #include <android-base/thread_annotations.h>
+#include <ftl/enum.h>
 #include <gtest/gtest.h>
 #include <gui/constants.h>
+#include <ui/Rotation.h>
 
+#include <thread>
+#include "FakeEventHub.h"
+#include "FakeInputReaderPolicy.h"
+#include "FakePointerController.h"
+#include "InputMapperTest.h"
+#include "InstrumentedInputReader.h"
+#include "TestConstants.h"
 #include "android/hardware/input/InputDeviceCountryCode.h"
 #include "input/DisplayViewport.h"
 #include "input/Input.h"
@@ -51,13 +60,6 @@
 using testing::AllOf;
 using std::chrono_literals::operator""ms;
 
-// Timeout for waiting for an expected event
-static constexpr std::chrono::duration WAIT_TIMEOUT = 100ms;
-
-// An arbitrary time value.
-static constexpr nsecs_t ARBITRARY_TIME = 1234;
-static constexpr nsecs_t READ_TIME = 4321;
-
 // Arbitrary display properties.
 static constexpr int32_t DISPLAY_ID = 0;
 static const std::string DISPLAY_UNIQUE_ID = "local:1";
@@ -78,10 +80,6 @@
 static constexpr int32_t FIRST_TRACKING_ID = 0;
 static constexpr int32_t SECOND_TRACKING_ID = 1;
 static constexpr int32_t THIRD_TRACKING_ID = 2;
-static constexpr int32_t DEFAULT_BATTERY = 1;
-static constexpr int32_t BATTERY_STATUS = 4;
-static constexpr int32_t BATTERY_CAPACITY = 66;
-static const std::string BATTERY_DEVPATH = "/sys/devices/mydevice/power_supply/mybattery";
 static constexpr int32_t LIGHT_BRIGHTNESS = 0x55000000;
 static constexpr int32_t LIGHT_COLOR = 0x7F448866;
 static constexpr int32_t LIGHT_PLAYER_ID = 2;
@@ -95,8 +93,10 @@
 static constexpr int32_t ACTION_POINTER_1_UP =
         AMOTION_EVENT_ACTION_POINTER_UP | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
 
-// Error tolerance for floating point assertions.
-static const float EPSILON = 0.001f;
+// 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);
 
 template<typename T>
 static inline T min(T a, T b) {
@@ -112,12 +112,12 @@
                                                                   {"green", LightColor::GREEN},
                                                                   {"blue", LightColor::BLUE}};
 
-static int32_t getInverseRotation(int32_t orientation) {
+static ui::Rotation getInverseRotation(ui::Rotation orientation) {
     switch (orientation) {
-        case DISPLAY_ORIENTATION_90:
-            return DISPLAY_ORIENTATION_270;
-        case DISPLAY_ORIENTATION_270:
-            return DISPLAY_ORIENTATION_90;
+        case ui::ROTATION_90:
+            return ui::ROTATION_270;
+        case ui::ROTATION_270:
+            return ui::ROTATION_90;
         default:
             return orientation;
     }
@@ -141,940 +141,15 @@
     ASSERT_EQ(nullptr, motionRange);
 }
 
-// --- FakePointerController ---
-
-class FakePointerController : public PointerControllerInterface {
-    bool mHaveBounds;
-    float mMinX, mMinY, mMaxX, mMaxY;
-    float mX, mY;
-    int32_t mButtonState;
-    int32_t mDisplayId;
-
-public:
-    FakePointerController() :
-        mHaveBounds(false), mMinX(0), mMinY(0), mMaxX(0), mMaxY(0), mX(0), mY(0),
-        mButtonState(0), mDisplayId(ADISPLAY_ID_DEFAULT) {
+[[maybe_unused]] static void dumpReader(InputReader& reader) {
+    std::string dump;
+    reader.dump(dump);
+    std::istringstream iss(dump);
+    for (std::string line; std::getline(iss, line);) {
+        ALOGE("%s", line.c_str());
+        std::this_thread::sleep_for(std::chrono::milliseconds(1));
     }
-
-    virtual ~FakePointerController() {}
-
-    void setBounds(float minX, float minY, float maxX, float maxY) {
-        mHaveBounds = true;
-        mMinX = minX;
-        mMinY = minY;
-        mMaxX = maxX;
-        mMaxY = maxY;
-    }
-
-    void setPosition(float x, float y) override {
-        mX = x;
-        mY = y;
-    }
-
-    void setButtonState(int32_t buttonState) override { mButtonState = buttonState; }
-
-    int32_t getButtonState() const override { return mButtonState; }
-
-    void getPosition(float* outX, float* outY) const override {
-        *outX = mX;
-        *outY = mY;
-    }
-
-    int32_t getDisplayId() const override { return mDisplayId; }
-
-    void setDisplayViewport(const DisplayViewport& viewport) override {
-        mDisplayId = viewport.displayId;
-    }
-
-    const std::map<int32_t, std::vector<int32_t>>& getSpots() {
-        return mSpotsByDisplay;
-    }
-
-private:
-    bool getBounds(float* outMinX, float* outMinY, float* outMaxX, float* outMaxY) const override {
-        *outMinX = mMinX;
-        *outMinY = mMinY;
-        *outMaxX = mMaxX;
-        *outMaxY = mMaxY;
-        return mHaveBounds;
-    }
-
-    void move(float deltaX, float deltaY) override {
-        mX += deltaX;
-        if (mX < mMinX) mX = mMinX;
-        if (mX > mMaxX) mX = mMaxX;
-        mY += deltaY;
-        if (mY < mMinY) mY = mMinY;
-        if (mY > mMaxY) mY = mMaxY;
-    }
-
-    void fade(Transition) override {}
-
-    void unfade(Transition) override {}
-
-    void setPresentation(Presentation) override {}
-
-    void setSpots(const PointerCoords*, const uint32_t*, BitSet32 spotIdBits,
-                  int32_t displayId) override {
-        std::vector<int32_t> newSpots;
-        // Add spots for fingers that are down.
-        for (BitSet32 idBits(spotIdBits); !idBits.isEmpty(); ) {
-            uint32_t id = idBits.clearFirstMarkedBit();
-            newSpots.push_back(id);
-        }
-
-        mSpotsByDisplay[displayId] = newSpots;
-    }
-
-    void clearSpots() override { mSpotsByDisplay.clear(); }
-
-    std::map<int32_t, std::vector<int32_t>> mSpotsByDisplay;
-};
-
-
-// --- FakeInputReaderPolicy ---
-
-class FakeInputReaderPolicy : public InputReaderPolicyInterface {
-    std::mutex mLock;
-    std::condition_variable mDevicesChangedCondition;
-
-    InputReaderConfiguration mConfig;
-    std::shared_ptr<FakePointerController> mPointerController;
-    std::vector<InputDeviceInfo> mInputDevices GUARDED_BY(mLock);
-    bool mInputDevicesChanged GUARDED_BY(mLock){false};
-    std::vector<DisplayViewport> mViewports;
-    TouchAffineTransformation transform;
-
-protected:
-    virtual ~FakeInputReaderPolicy() {}
-
-public:
-    FakeInputReaderPolicy() {
-    }
-
-    void assertInputDevicesChanged() {
-        waitForInputDevices([](bool devicesChanged) {
-            if (!devicesChanged) {
-                FAIL() << "Timed out waiting for notifyInputDevicesChanged() to be called.";
-            }
-        });
-    }
-
-    void assertInputDevicesNotChanged() {
-        waitForInputDevices([](bool devicesChanged) {
-            if (devicesChanged) {
-                FAIL() << "Expected notifyInputDevicesChanged() to not be called.";
-            }
-        });
-    }
-
-    virtual void clearViewports() {
-        mViewports.clear();
-        mConfig.setDisplayViewports(mViewports);
-    }
-
-    std::optional<DisplayViewport> getDisplayViewportByUniqueId(const std::string& uniqueId) const {
-        return mConfig.getDisplayViewportByUniqueId(uniqueId);
-    }
-    std::optional<DisplayViewport> getDisplayViewportByType(ViewportType type) const {
-        return mConfig.getDisplayViewportByType(type);
-    }
-
-    std::optional<DisplayViewport> getDisplayViewportByPort(uint8_t displayPort) const {
-        return mConfig.getDisplayViewportByPort(displayPort);
-    }
-
-    void addDisplayViewport(DisplayViewport viewport) {
-        mViewports.push_back(std::move(viewport));
-        mConfig.setDisplayViewports(mViewports);
-    }
-
-    void addDisplayViewport(int32_t displayId, int32_t width, int32_t height, int32_t orientation,
-                            bool isActive, const std::string& uniqueId,
-                            std::optional<uint8_t> physicalPort, ViewportType type) {
-        const bool isRotated =
-                (orientation == DISPLAY_ORIENTATION_90 || orientation == DISPLAY_ORIENTATION_270);
-        DisplayViewport v;
-        v.displayId = displayId;
-        v.orientation = orientation;
-        v.logicalLeft = 0;
-        v.logicalTop = 0;
-        v.logicalRight = isRotated ? height : width;
-        v.logicalBottom = isRotated ? width : height;
-        v.physicalLeft = 0;
-        v.physicalTop = 0;
-        v.physicalRight = isRotated ? height : width;
-        v.physicalBottom = isRotated ? width : height;
-        v.deviceWidth = isRotated ? height : width;
-        v.deviceHeight = isRotated ? width : height;
-        v.isActive = isActive;
-        v.uniqueId = uniqueId;
-        v.physicalPort = physicalPort;
-        v.type = type;
-
-        addDisplayViewport(v);
-    }
-
-    bool updateViewport(const DisplayViewport& viewport) {
-        size_t count = mViewports.size();
-        for (size_t i = 0; i < count; i++) {
-            const DisplayViewport& currentViewport = mViewports[i];
-            if (currentViewport.displayId == viewport.displayId) {
-                mViewports[i] = viewport;
-                mConfig.setDisplayViewports(mViewports);
-                return true;
-            }
-        }
-        // no viewport found.
-        return false;
-    }
-
-    void addExcludedDeviceName(const std::string& deviceName) {
-        mConfig.excludedDeviceNames.push_back(deviceName);
-    }
-
-    void addInputPortAssociation(const std::string& inputPort, uint8_t displayPort) {
-        mConfig.portAssociations.insert({inputPort, displayPort});
-    }
-
-    void addInputUniqueIdAssociation(const std::string& inputUniqueId,
-                                     const std::string& displayUniqueId) {
-        mConfig.uniqueIdAssociations.insert({inputUniqueId, displayUniqueId});
-    }
-
-    void addDisabledDevice(int32_t deviceId) { mConfig.disabledDevices.insert(deviceId); }
-
-    void removeDisabledDevice(int32_t deviceId) { mConfig.disabledDevices.erase(deviceId); }
-
-    void setPointerController(std::shared_ptr<FakePointerController> controller) {
-        mPointerController = std::move(controller);
-    }
-
-    const InputReaderConfiguration* getReaderConfiguration() const {
-        return &mConfig;
-    }
-
-    const std::vector<InputDeviceInfo>& getInputDevices() const {
-        return mInputDevices;
-    }
-
-    TouchAffineTransformation getTouchAffineTransformation(const std::string& inputDeviceDescriptor,
-            int32_t surfaceRotation) {
-        return transform;
-    }
-
-    void setTouchAffineTransformation(const TouchAffineTransformation t) {
-        transform = t;
-    }
-
-    PointerCaptureRequest setPointerCapture(bool enabled) {
-        mConfig.pointerCaptureRequest = {enabled, mNextPointerCaptureSequenceNumber++};
-        return mConfig.pointerCaptureRequest;
-    }
-
-    void setShowTouches(bool enabled) {
-        mConfig.showTouches = enabled;
-    }
-
-    void setDefaultPointerDisplayId(int32_t pointerDisplayId) {
-        mConfig.defaultPointerDisplayId = pointerDisplayId;
-    }
-
-    void setPointerGestureEnabled(bool enabled) { mConfig.pointerGesturesEnabled = enabled; }
-
-    float getPointerGestureMovementSpeedRatio() { return mConfig.pointerGestureMovementSpeedRatio; }
-
-    float getPointerGestureZoomSpeedRatio() { return mConfig.pointerGestureZoomSpeedRatio; }
-
-    void setVelocityControlParams(const VelocityControlParameters& params) {
-        mConfig.pointerVelocityControlParameters = params;
-        mConfig.wheelVelocityControlParameters = params;
-    }
-
-private:
-    uint32_t mNextPointerCaptureSequenceNumber = 0;
-
-    void getReaderConfiguration(InputReaderConfiguration* outConfig) override {
-        *outConfig = mConfig;
-    }
-
-    std::shared_ptr<PointerControllerInterface> obtainPointerController(
-            int32_t /*deviceId*/) override {
-        return mPointerController;
-    }
-
-    void notifyInputDevicesChanged(const std::vector<InputDeviceInfo>& inputDevices) override {
-        std::scoped_lock<std::mutex> lock(mLock);
-        mInputDevices = inputDevices;
-        mInputDevicesChanged = true;
-        mDevicesChangedCondition.notify_all();
-    }
-
-    std::shared_ptr<KeyCharacterMap> getKeyboardLayoutOverlay(
-            const InputDeviceIdentifier&) override {
-        return nullptr;
-    }
-
-    std::string getDeviceAlias(const InputDeviceIdentifier&) override { return ""; }
-
-    void waitForInputDevices(std::function<void(bool)> processDevicesChanged) {
-        std::unique_lock<std::mutex> lock(mLock);
-        base::ScopedLockAssertion assumeLocked(mLock);
-
-        const bool devicesChanged =
-                mDevicesChangedCondition.wait_for(lock, WAIT_TIMEOUT, [this]() REQUIRES(mLock) {
-                    return mInputDevicesChanged;
-                });
-        ASSERT_NO_FATAL_FAILURE(processDevicesChanged(devicesChanged));
-        mInputDevicesChanged = false;
-    }
-};
-
-// --- FakeEventHub ---
-
-class FakeEventHub : public EventHubInterface {
-    struct KeyInfo {
-        int32_t keyCode;
-        uint32_t flags;
-    };
-
-    struct SensorInfo {
-        InputDeviceSensorType sensorType;
-        int32_t sensorDataIndex;
-    };
-
-    struct Device {
-        InputDeviceIdentifier identifier;
-        ftl::Flags<InputDeviceClass> classes;
-        PropertyMap configuration;
-        KeyedVector<int, RawAbsoluteAxisInfo> absoluteAxes;
-        KeyedVector<int, bool> relativeAxes;
-        KeyedVector<int32_t, int32_t> keyCodeStates;
-        KeyedVector<int32_t, int32_t> scanCodeStates;
-        KeyedVector<int32_t, int32_t> switchStates;
-        KeyedVector<int32_t, int32_t> absoluteAxisValue;
-        KeyedVector<int32_t, KeyInfo> keysByScanCode;
-        KeyedVector<int32_t, KeyInfo> keysByUsageCode;
-        KeyedVector<int32_t, bool> leds;
-        // fake mapping which would normally come from keyCharacterMap
-        std::unordered_map<int32_t, int32_t> keyCodeMapping;
-        std::unordered_map<int32_t, SensorInfo> sensorsByAbsCode;
-        BitArray<MSC_MAX> mscBitmask;
-        std::vector<VirtualKeyDefinition> virtualKeys;
-        bool enabled;
-        InputDeviceCountryCode countryCode;
-
-        status_t enable() {
-            enabled = true;
-            return OK;
-        }
-
-        status_t disable() {
-            enabled = false;
-            return OK;
-        }
-
-        explicit Device(ftl::Flags<InputDeviceClass> classes) : classes(classes), enabled(true) {}
-    };
-
-    std::mutex mLock;
-    std::condition_variable mEventsCondition;
-
-    KeyedVector<int32_t, Device*> mDevices;
-    std::vector<std::string> mExcludedDevices;
-    std::vector<RawEvent> mEvents GUARDED_BY(mLock);
-    std::unordered_map<int32_t /*deviceId*/, std::vector<TouchVideoFrame>> mVideoFrames;
-    std::vector<int32_t> mVibrators = {0, 1};
-    std::unordered_map<int32_t, RawLightInfo> mRawLightInfos;
-    // Simulates a device light brightness, from light id to light brightness.
-    std::unordered_map<int32_t /* lightId */, int32_t /* brightness*/> mLightBrightness;
-    // Simulates a device light intensities, from light id to light intensities map.
-    std::unordered_map<int32_t /* lightId */, std::unordered_map<LightColor, int32_t>>
-            mLightIntensities;
-
-public:
-    virtual ~FakeEventHub() {
-        for (size_t i = 0; i < mDevices.size(); i++) {
-            delete mDevices.valueAt(i);
-        }
-    }
-
-    FakeEventHub() { }
-
-    void addDevice(int32_t deviceId, const std::string& name,
-                   ftl::Flags<InputDeviceClass> classes) {
-        Device* device = new Device(classes);
-        device->identifier.name = name;
-        mDevices.add(deviceId, device);
-
-        enqueueEvent(ARBITRARY_TIME, READ_TIME, deviceId, EventHubInterface::DEVICE_ADDED, 0, 0);
-    }
-
-    void removeDevice(int32_t deviceId) {
-        delete mDevices.valueFor(deviceId);
-        mDevices.removeItem(deviceId);
-
-        enqueueEvent(ARBITRARY_TIME, READ_TIME, deviceId, EventHubInterface::DEVICE_REMOVED, 0, 0);
-    }
-
-    bool isDeviceEnabled(int32_t deviceId) const override {
-        Device* device = getDevice(deviceId);
-        if (device == nullptr) {
-            ALOGE("Incorrect device id=%" PRId32 " provided to %s", deviceId, __func__);
-            return false;
-        }
-        return device->enabled;
-    }
-
-    status_t enableDevice(int32_t deviceId) override {
-        status_t result;
-        Device* device = getDevice(deviceId);
-        if (device == nullptr) {
-            ALOGE("Incorrect device id=%" PRId32 " provided to %s", deviceId, __func__);
-            return BAD_VALUE;
-        }
-        if (device->enabled) {
-            ALOGW("Duplicate call to %s, device %" PRId32 " already enabled", __func__, deviceId);
-            return OK;
-        }
-        result = device->enable();
-        return result;
-    }
-
-    status_t disableDevice(int32_t deviceId) override {
-        Device* device = getDevice(deviceId);
-        if (device == nullptr) {
-            ALOGE("Incorrect device id=%" PRId32 " provided to %s", deviceId, __func__);
-            return BAD_VALUE;
-        }
-        if (!device->enabled) {
-            ALOGW("Duplicate call to %s, device %" PRId32 " already disabled", __func__, deviceId);
-            return OK;
-        }
-        return device->disable();
-    }
-
-    void finishDeviceScan() {
-        enqueueEvent(ARBITRARY_TIME, READ_TIME, 0, EventHubInterface::FINISHED_DEVICE_SCAN, 0, 0);
-    }
-
-    void addConfigurationProperty(int32_t deviceId, const char* key, const char* value) {
-        Device* device = getDevice(deviceId);
-        device->configuration.addProperty(key, value);
-    }
-
-    void addConfigurationMap(int32_t deviceId, const PropertyMap* configuration) {
-        Device* device = getDevice(deviceId);
-        device->configuration.addAll(configuration);
-    }
-
-    void addAbsoluteAxis(int32_t deviceId, int axis,
-            int32_t minValue, int32_t maxValue, int flat, int fuzz, int resolution = 0) {
-        Device* device = getDevice(deviceId);
-
-        RawAbsoluteAxisInfo info;
-        info.valid = true;
-        info.minValue = minValue;
-        info.maxValue = maxValue;
-        info.flat = flat;
-        info.fuzz = fuzz;
-        info.resolution = resolution;
-        device->absoluteAxes.add(axis, info);
-    }
-
-    void addRelativeAxis(int32_t deviceId, int32_t axis) {
-        Device* device = getDevice(deviceId);
-        device->relativeAxes.add(axis, true);
-    }
-
-    void setKeyCodeState(int32_t deviceId, int32_t keyCode, int32_t state) {
-        Device* device = getDevice(deviceId);
-        device->keyCodeStates.replaceValueFor(keyCode, state);
-    }
-
-    void setCountryCode(int32_t deviceId, InputDeviceCountryCode countryCode) {
-        Device* device = getDevice(deviceId);
-        device->countryCode = countryCode;
-    }
-
-    void setScanCodeState(int32_t deviceId, int32_t scanCode, int32_t state) {
-        Device* device = getDevice(deviceId);
-        device->scanCodeStates.replaceValueFor(scanCode, state);
-    }
-
-    void setSwitchState(int32_t deviceId, int32_t switchCode, int32_t state) {
-        Device* device = getDevice(deviceId);
-        device->switchStates.replaceValueFor(switchCode, state);
-    }
-
-    void setAbsoluteAxisValue(int32_t deviceId, int32_t axis, int32_t value) {
-        Device* device = getDevice(deviceId);
-        device->absoluteAxisValue.replaceValueFor(axis, value);
-    }
-
-    void addKey(int32_t deviceId, int32_t scanCode, int32_t usageCode,
-            int32_t keyCode, uint32_t flags) {
-        Device* device = getDevice(deviceId);
-        KeyInfo info;
-        info.keyCode = keyCode;
-        info.flags = flags;
-        if (scanCode) {
-            device->keysByScanCode.add(scanCode, info);
-        }
-        if (usageCode) {
-            device->keysByUsageCode.add(usageCode, info);
-        }
-    }
-
-    void addKeyCodeMapping(int32_t deviceId, int32_t fromKeyCode, int32_t toKeyCode) {
-        Device* device = getDevice(deviceId);
-        device->keyCodeMapping.insert_or_assign(fromKeyCode, toKeyCode);
-    }
-
-    void addLed(int32_t deviceId, int32_t led, bool initialState) {
-        Device* device = getDevice(deviceId);
-        device->leds.add(led, initialState);
-    }
-
-    void addSensorAxis(int32_t deviceId, int32_t absCode, InputDeviceSensorType sensorType,
-                       int32_t sensorDataIndex) {
-        Device* device = getDevice(deviceId);
-        SensorInfo info;
-        info.sensorType = sensorType;
-        info.sensorDataIndex = sensorDataIndex;
-        device->sensorsByAbsCode.emplace(absCode, info);
-    }
-
-    void setMscEvent(int32_t deviceId, int32_t mscEvent) {
-        Device* device = getDevice(deviceId);
-        typename BitArray<MSC_MAX>::Buffer buffer;
-        buffer[mscEvent / 32] = 1 << mscEvent % 32;
-        device->mscBitmask.loadFromBuffer(buffer);
-    }
-
-    void addRawLightInfo(int32_t rawId, RawLightInfo&& info) {
-        mRawLightInfos.emplace(rawId, std::move(info));
-    }
-
-    void fakeLightBrightness(int32_t rawId, int32_t brightness) {
-        mLightBrightness.emplace(rawId, brightness);
-    }
-
-    void fakeLightIntensities(int32_t rawId,
-                              const std::unordered_map<LightColor, int32_t> intensities) {
-        mLightIntensities.emplace(rawId, std::move(intensities));
-    }
-
-    bool getLedState(int32_t deviceId, int32_t led) {
-        Device* device = getDevice(deviceId);
-        return device->leds.valueFor(led);
-    }
-
-    std::vector<std::string>& getExcludedDevices() {
-        return mExcludedDevices;
-    }
-
-    void addVirtualKeyDefinition(int32_t deviceId, const VirtualKeyDefinition& definition) {
-        Device* device = getDevice(deviceId);
-        device->virtualKeys.push_back(definition);
-    }
-
-    void enqueueEvent(nsecs_t when, nsecs_t readTime, int32_t deviceId, int32_t type, int32_t code,
-                      int32_t value) {
-        std::scoped_lock<std::mutex> lock(mLock);
-        RawEvent event;
-        event.when = when;
-        event.readTime = readTime;
-        event.deviceId = deviceId;
-        event.type = type;
-        event.code = code;
-        event.value = value;
-        mEvents.push_back(event);
-
-        if (type == EV_ABS) {
-            setAbsoluteAxisValue(deviceId, code, value);
-        }
-    }
-
-    void setVideoFrames(std::unordered_map<int32_t /*deviceId*/,
-            std::vector<TouchVideoFrame>> videoFrames) {
-        mVideoFrames = std::move(videoFrames);
-    }
-
-    void assertQueueIsEmpty() {
-        std::unique_lock<std::mutex> lock(mLock);
-        base::ScopedLockAssertion assumeLocked(mLock);
-        const bool queueIsEmpty =
-                mEventsCondition.wait_for(lock, WAIT_TIMEOUT,
-                                          [this]() REQUIRES(mLock) { return mEvents.size() == 0; });
-        if (!queueIsEmpty) {
-            FAIL() << "Timed out waiting for EventHub queue to be emptied.";
-        }
-    }
-
-private:
-    Device* getDevice(int32_t deviceId) const {
-        ssize_t index = mDevices.indexOfKey(deviceId);
-        return index >= 0 ? mDevices.valueAt(index) : nullptr;
-    }
-
-    ftl::Flags<InputDeviceClass> getDeviceClasses(int32_t deviceId) const override {
-        Device* device = getDevice(deviceId);
-        return device ? device->classes : ftl::Flags<InputDeviceClass>(0);
-    }
-
-    InputDeviceIdentifier getDeviceIdentifier(int32_t deviceId) const override {
-        Device* device = getDevice(deviceId);
-        return device ? device->identifier : InputDeviceIdentifier();
-    }
-
-    int32_t getDeviceControllerNumber(int32_t) const override { return 0; }
-
-    void getConfiguration(int32_t deviceId, PropertyMap* outConfiguration) const override {
-        Device* device = getDevice(deviceId);
-        if (device) {
-            *outConfiguration = device->configuration;
-        }
-    }
-
-    status_t getAbsoluteAxisInfo(int32_t deviceId, int axis,
-                                 RawAbsoluteAxisInfo* outAxisInfo) const override {
-        Device* device = getDevice(deviceId);
-        if (device && device->enabled) {
-            ssize_t index = device->absoluteAxes.indexOfKey(axis);
-            if (index >= 0) {
-                *outAxisInfo = device->absoluteAxes.valueAt(index);
-                return OK;
-            }
-        }
-        outAxisInfo->clear();
-        return -1;
-    }
-
-    bool hasRelativeAxis(int32_t deviceId, int axis) const override {
-        Device* device = getDevice(deviceId);
-        if (device) {
-            return device->relativeAxes.indexOfKey(axis) >= 0;
-        }
-        return false;
-    }
-
-    bool hasInputProperty(int32_t, int) const override { return false; }
-
-    bool hasMscEvent(int32_t deviceId, int mscEvent) const override final {
-        Device* device = getDevice(deviceId);
-        if (device) {
-            return mscEvent >= 0 && mscEvent <= MSC_MAX ? device->mscBitmask.test(mscEvent) : false;
-        }
-        return false;
-    }
-
-    status_t mapKey(int32_t deviceId, int32_t scanCode, int32_t usageCode, int32_t metaState,
-                    int32_t* outKeycode, int32_t* outMetaState, uint32_t* outFlags) const override {
-        Device* device = getDevice(deviceId);
-        if (device) {
-            const KeyInfo* key = getKey(device, scanCode, usageCode);
-            if (key) {
-                if (outKeycode) {
-                    *outKeycode = key->keyCode;
-                }
-                if (outFlags) {
-                    *outFlags = key->flags;
-                }
-                if (outMetaState) {
-                    *outMetaState = metaState;
-                }
-                return OK;
-            }
-        }
-        return NAME_NOT_FOUND;
-    }
-
-    const KeyInfo* getKey(Device* device, int32_t scanCode, int32_t usageCode) const {
-        if (usageCode) {
-            ssize_t index = device->keysByUsageCode.indexOfKey(usageCode);
-            if (index >= 0) {
-                return &device->keysByUsageCode.valueAt(index);
-            }
-        }
-        if (scanCode) {
-            ssize_t index = device->keysByScanCode.indexOfKey(scanCode);
-            if (index >= 0) {
-                return &device->keysByScanCode.valueAt(index);
-            }
-        }
-        return nullptr;
-    }
-
-    status_t mapAxis(int32_t, int32_t, AxisInfo*) const override { return NAME_NOT_FOUND; }
-
-    base::Result<std::pair<InputDeviceSensorType, int32_t>> mapSensor(
-            int32_t deviceId, int32_t absCode) const override {
-        Device* device = getDevice(deviceId);
-        if (!device) {
-            return Errorf("Sensor device not found.");
-        }
-        auto it = device->sensorsByAbsCode.find(absCode);
-        if (it == device->sensorsByAbsCode.end()) {
-            return Errorf("Sensor map not found.");
-        }
-        const SensorInfo& info = it->second;
-        return std::make_pair(info.sensorType, info.sensorDataIndex);
-    }
-
-    void setExcludedDevices(const std::vector<std::string>& devices) override {
-        mExcludedDevices = devices;
-    }
-
-    std::vector<RawEvent> getEvents(int) override {
-        std::scoped_lock lock(mLock);
-
-        std::vector<RawEvent> buffer;
-        std::swap(buffer, mEvents);
-
-        mEventsCondition.notify_all();
-        return buffer;
-    }
-
-    std::vector<TouchVideoFrame> getVideoFrames(int32_t deviceId) override {
-        auto it = mVideoFrames.find(deviceId);
-        if (it != mVideoFrames.end()) {
-            std::vector<TouchVideoFrame> frames = std::move(it->second);
-            mVideoFrames.erase(deviceId);
-            return frames;
-        }
-        return {};
-    }
-
-    int32_t getScanCodeState(int32_t deviceId, int32_t scanCode) const override {
-        Device* device = getDevice(deviceId);
-        if (device) {
-            ssize_t index = device->scanCodeStates.indexOfKey(scanCode);
-            if (index >= 0) {
-                return device->scanCodeStates.valueAt(index);
-            }
-        }
-        return AKEY_STATE_UNKNOWN;
-    }
-
-    InputDeviceCountryCode getCountryCode(int32_t deviceId) const override {
-        Device* device = getDevice(deviceId);
-        if (device) {
-            return device->countryCode;
-        }
-        return InputDeviceCountryCode::INVALID;
-    }
-
-    int32_t getKeyCodeState(int32_t deviceId, int32_t keyCode) const override {
-        Device* device = getDevice(deviceId);
-        if (device) {
-            ssize_t index = device->keyCodeStates.indexOfKey(keyCode);
-            if (index >= 0) {
-                return device->keyCodeStates.valueAt(index);
-            }
-        }
-        return AKEY_STATE_UNKNOWN;
-    }
-
-    int32_t getSwitchState(int32_t deviceId, int32_t sw) const override {
-        Device* device = getDevice(deviceId);
-        if (device) {
-            ssize_t index = device->switchStates.indexOfKey(sw);
-            if (index >= 0) {
-                return device->switchStates.valueAt(index);
-            }
-        }
-        return AKEY_STATE_UNKNOWN;
-    }
-
-    status_t getAbsoluteAxisValue(int32_t deviceId, int32_t axis,
-                                  int32_t* outValue) const override {
-        Device* device = getDevice(deviceId);
-        if (device) {
-            ssize_t index = device->absoluteAxisValue.indexOfKey(axis);
-            if (index >= 0) {
-                *outValue = device->absoluteAxisValue.valueAt(index);
-                return OK;
-            }
-        }
-        *outValue = 0;
-        return -1;
-    }
-
-    int32_t getKeyCodeForKeyLocation(int32_t deviceId, int32_t locationKeyCode) const override {
-        Device* device = getDevice(deviceId);
-        if (!device) {
-            return AKEYCODE_UNKNOWN;
-        }
-        auto it = device->keyCodeMapping.find(locationKeyCode);
-        return it != device->keyCodeMapping.end() ? it->second : locationKeyCode;
-    }
-
-    // Return true if the device has non-empty key layout.
-    bool markSupportedKeyCodes(int32_t deviceId, const std::vector<int32_t>& keyCodes,
-                               uint8_t* outFlags) const override {
-        bool result = false;
-        Device* device = getDevice(deviceId);
-        if (device) {
-            result = device->keysByScanCode.size() > 0 || device->keysByUsageCode.size() > 0;
-            for (size_t i = 0; i < keyCodes.size(); i++) {
-                for (size_t j = 0; j < device->keysByScanCode.size(); j++) {
-                    if (keyCodes[i] == device->keysByScanCode.valueAt(j).keyCode) {
-                        outFlags[i] = 1;
-                    }
-                }
-                for (size_t j = 0; j < device->keysByUsageCode.size(); j++) {
-                    if (keyCodes[i] == device->keysByUsageCode.valueAt(j).keyCode) {
-                        outFlags[i] = 1;
-                    }
-                }
-            }
-        }
-        return result;
-    }
-
-    bool hasScanCode(int32_t deviceId, int32_t scanCode) const override {
-        Device* device = getDevice(deviceId);
-        if (device) {
-            ssize_t index = device->keysByScanCode.indexOfKey(scanCode);
-            return index >= 0;
-        }
-        return false;
-    }
-
-    bool hasKeyCode(int32_t deviceId, int32_t keyCode) const override {
-        Device* device = getDevice(deviceId);
-        if (!device) {
-            return false;
-        }
-        for (size_t i = 0; i < device->keysByScanCode.size(); i++) {
-            if (keyCode == device->keysByScanCode.valueAt(i).keyCode) {
-                return true;
-            }
-        }
-        for (size_t j = 0; j < device->keysByUsageCode.size(); j++) {
-            if (keyCode == device->keysByUsageCode.valueAt(j).keyCode) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    bool hasLed(int32_t deviceId, int32_t led) const override {
-        Device* device = getDevice(deviceId);
-        return device && device->leds.indexOfKey(led) >= 0;
-    }
-
-    void setLedState(int32_t deviceId, int32_t led, bool on) override {
-        Device* device = getDevice(deviceId);
-        if (device) {
-            ssize_t index = device->leds.indexOfKey(led);
-            if (index >= 0) {
-                device->leds.replaceValueAt(led, on);
-            } else {
-                ADD_FAILURE()
-                        << "Attempted to set the state of an LED that the EventHub declared "
-                        "was not present.  led=" << led;
-            }
-        }
-    }
-
-    void getVirtualKeyDefinitions(
-            int32_t deviceId, std::vector<VirtualKeyDefinition>& outVirtualKeys) const override {
-        outVirtualKeys.clear();
-
-        Device* device = getDevice(deviceId);
-        if (device) {
-            outVirtualKeys = device->virtualKeys;
-        }
-    }
-
-    const std::shared_ptr<KeyCharacterMap> getKeyCharacterMap(int32_t) const override {
-        return nullptr;
-    }
-
-    bool setKeyboardLayoutOverlay(int32_t, std::shared_ptr<KeyCharacterMap>) override {
-        return false;
-    }
-
-    void vibrate(int32_t, const VibrationElement&) override {}
-
-    void cancelVibrate(int32_t) override {}
-
-    std::vector<int32_t> getVibratorIds(int32_t deviceId) const override { return mVibrators; };
-
-    std::optional<int32_t> getBatteryCapacity(int32_t, int32_t) const override {
-        return BATTERY_CAPACITY;
-    }
-
-    std::optional<int32_t> getBatteryStatus(int32_t, int32_t) const override {
-        return BATTERY_STATUS;
-    }
-
-    std::vector<int32_t> getRawBatteryIds(int32_t deviceId) const override {
-        return {DEFAULT_BATTERY};
-    }
-
-    std::optional<RawBatteryInfo> getRawBatteryInfo(int32_t deviceId,
-                                                    int32_t batteryId) const override {
-        if (batteryId != DEFAULT_BATTERY) return {};
-        static const auto BATTERY_INFO = RawBatteryInfo{.id = DEFAULT_BATTERY,
-                                                        .name = "default battery",
-                                                        .flags = InputBatteryClass::CAPACITY,
-                                                        .path = BATTERY_DEVPATH};
-        return BATTERY_INFO;
-    }
-
-    std::vector<int32_t> getRawLightIds(int32_t deviceId) const override {
-        std::vector<int32_t> ids;
-        for (const auto& [rawId, info] : mRawLightInfos) {
-            ids.push_back(rawId);
-        }
-        return ids;
-    }
-
-    std::optional<RawLightInfo> getRawLightInfo(int32_t deviceId, int32_t lightId) const override {
-        auto it = mRawLightInfos.find(lightId);
-        if (it == mRawLightInfos.end()) {
-            return std::nullopt;
-        }
-        return it->second;
-    }
-
-    void setLightBrightness(int32_t deviceId, int32_t lightId, int32_t brightness) override {
-        mLightBrightness.emplace(lightId, brightness);
-    }
-
-    void setLightIntensities(int32_t deviceId, int32_t lightId,
-                             std::unordered_map<LightColor, int32_t> intensities) override {
-        mLightIntensities.emplace(lightId, intensities);
-    };
-
-    std::optional<int32_t> getLightBrightness(int32_t deviceId, int32_t lightId) const override {
-        auto lightIt = mLightBrightness.find(lightId);
-        if (lightIt == mLightBrightness.end()) {
-            return std::nullopt;
-        }
-        return lightIt->second;
-    }
-
-    std::optional<std::unordered_map<LightColor, int32_t>> getLightIntensities(
-            int32_t deviceId, int32_t lightId) const override {
-        auto lightIt = mLightIntensities.find(lightId);
-        if (lightIt == mLightIntensities.end()) {
-            return std::nullopt;
-        }
-        return lightIt->second;
-    };
-
-    void dump(std::string&) const override {}
-
-    void monitor() const override {}
-
-    void requestReopenDevices() override {}
-
-    void wake() override {}
-};
+}
 
 // --- FakeInputMapper ---
 
@@ -1269,92 +344,6 @@
     }
 };
 
-
-// --- InstrumentedInputReader ---
-
-class InstrumentedInputReader : public InputReader {
-    std::queue<std::shared_ptr<InputDevice>> mNextDevices;
-
-public:
-    InstrumentedInputReader(std::shared_ptr<EventHubInterface> eventHub,
-                            const sp<InputReaderPolicyInterface>& policy,
-                            InputListenerInterface& listener)
-          : InputReader(eventHub, policy, listener), mFakeContext(this) {}
-
-    virtual ~InstrumentedInputReader() {}
-
-    void pushNextDevice(std::shared_ptr<InputDevice> device) { mNextDevices.push(device); }
-
-    std::shared_ptr<InputDevice> newDevice(int32_t deviceId, const std::string& name,
-                                           const std::string& location = "") {
-        InputDeviceIdentifier identifier;
-        identifier.name = name;
-        identifier.location = location;
-        int32_t generation = deviceId + 1;
-        return std::make_shared<InputDevice>(&mFakeContext, deviceId, generation, identifier);
-    }
-
-    // Make the protected loopOnce method accessible to tests.
-    using InputReader::loopOnce;
-
-protected:
-    virtual std::shared_ptr<InputDevice> createDeviceLocked(int32_t eventHubId,
-                                                            const InputDeviceIdentifier& identifier)
-            REQUIRES(mLock) {
-        if (!mNextDevices.empty()) {
-            std::shared_ptr<InputDevice> device(std::move(mNextDevices.front()));
-            mNextDevices.pop();
-            return device;
-        }
-        return InputReader::createDeviceLocked(eventHubId, identifier);
-    }
-
-    // --- FakeInputReaderContext ---
-    class FakeInputReaderContext : public ContextImpl {
-        int32_t mGlobalMetaState;
-        bool mUpdateGlobalMetaStateWasCalled;
-        int32_t mGeneration;
-
-    public:
-        FakeInputReaderContext(InputReader* reader)
-              : ContextImpl(reader),
-                mGlobalMetaState(0),
-                mUpdateGlobalMetaStateWasCalled(false),
-                mGeneration(1) {}
-
-        virtual ~FakeInputReaderContext() {}
-
-        void assertUpdateGlobalMetaStateWasCalled() {
-            ASSERT_TRUE(mUpdateGlobalMetaStateWasCalled)
-                    << "Expected updateGlobalMetaState() to have been called.";
-            mUpdateGlobalMetaStateWasCalled = false;
-        }
-
-        void setGlobalMetaState(int32_t state) { mGlobalMetaState = state; }
-
-        uint32_t getGeneration() { return mGeneration; }
-
-        void updateGlobalMetaState() override {
-            mUpdateGlobalMetaStateWasCalled = true;
-            ContextImpl::updateGlobalMetaState();
-        }
-
-        int32_t getGlobalMetaState() override {
-            return mGlobalMetaState | ContextImpl::getGlobalMetaState();
-        }
-
-        int32_t bumpGeneration() override {
-            mGeneration = ContextImpl::bumpGeneration();
-            return mGeneration;
-        }
-    } mFakeContext;
-
-    friend class InputReaderTest;
-
-public:
-    FakeInputReaderContext* getContext() { return &mFakeContext; }
-};
-
 // --- InputReaderPolicyTest ---
 class InputReaderPolicyTest : public testing::Test {
 protected:
@@ -1380,9 +369,8 @@
     ASSERT_FALSE(internalViewport);
 
     // Add an internal viewport, then clear it
-    mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT,
-                                    DISPLAY_ORIENTATION_0, true /*isActive*/, uniqueId, NO_PORT,
-                                    ViewportType::INTERNAL);
+    mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
+                                    true /*isActive*/, uniqueId, NO_PORT, ViewportType::INTERNAL);
 
     // Check matching by uniqueId
     internalViewport = mFakePolicy->getDisplayViewportByUniqueId(uniqueId);
@@ -1411,21 +399,21 @@
     constexpr int32_t virtualDisplayId2 = 3;
 
     // Add an internal viewport
-    mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT,
-                                    DISPLAY_ORIENTATION_0, true /*isActive*/, internalUniqueId,
-                                    NO_PORT, ViewportType::INTERNAL);
+    mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
+                                    true /*isActive*/, internalUniqueId, NO_PORT,
+                                    ViewportType::INTERNAL);
     // Add an external viewport
-    mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT,
-                                    DISPLAY_ORIENTATION_0, true /*isActive*/, externalUniqueId,
-                                    NO_PORT, ViewportType::EXTERNAL);
+    mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
+                                    true /*isActive*/, externalUniqueId, NO_PORT,
+                                    ViewportType::EXTERNAL);
     // Add an virtual viewport
     mFakePolicy->addDisplayViewport(virtualDisplayId1, DISPLAY_WIDTH, DISPLAY_HEIGHT,
-                                    DISPLAY_ORIENTATION_0, true /*isActive*/, virtualUniqueId1,
-                                    NO_PORT, ViewportType::VIRTUAL);
+                                    ui::ROTATION_0, true /*isActive*/, virtualUniqueId1, NO_PORT,
+                                    ViewportType::VIRTUAL);
     // Add another virtual viewport
     mFakePolicy->addDisplayViewport(virtualDisplayId2, DISPLAY_WIDTH, DISPLAY_HEIGHT,
-                                    DISPLAY_ORIENTATION_0, true /*isActive*/, virtualUniqueId2,
-                                    NO_PORT, ViewportType::VIRTUAL);
+                                    ui::ROTATION_0, true /*isActive*/, virtualUniqueId2, NO_PORT,
+                                    ViewportType::VIRTUAL);
 
     // Check matching by type for internal
     std::optional<DisplayViewport> internalViewport =
@@ -1473,13 +461,11 @@
     for (const ViewportType& type : types) {
         mFakePolicy->clearViewports();
         // Add a viewport
-        mFakePolicy->addDisplayViewport(displayId1, DISPLAY_WIDTH, DISPLAY_HEIGHT,
-                                        DISPLAY_ORIENTATION_0, true /*isActive*/, uniqueId1,
-                                        NO_PORT, type);
+        mFakePolicy->addDisplayViewport(displayId1, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
+                                        true /*isActive*/, uniqueId1, NO_PORT, type);
         // Add another viewport
-        mFakePolicy->addDisplayViewport(displayId2, DISPLAY_WIDTH, DISPLAY_HEIGHT,
-                                        DISPLAY_ORIENTATION_0, true /*isActive*/, uniqueId2,
-                                        NO_PORT, type);
+        mFakePolicy->addDisplayViewport(displayId2, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
+                                        true /*isActive*/, uniqueId2, NO_PORT, type);
 
         // Check that correct display viewport was returned by comparing the display IDs.
         std::optional<DisplayViewport> viewport1 =
@@ -1519,10 +505,10 @@
     // Add the default display first and ensure it gets returned.
     mFakePolicy->clearViewports();
     mFakePolicy->addDisplayViewport(ADISPLAY_ID_DEFAULT, DISPLAY_WIDTH, DISPLAY_HEIGHT,
-                                    DISPLAY_ORIENTATION_0, true /*isActive*/, uniqueId1, NO_PORT,
+                                    ui::ROTATION_0, true /*isActive*/, uniqueId1, NO_PORT,
                                     ViewportType::INTERNAL);
     mFakePolicy->addDisplayViewport(nonDefaultDisplayId, DISPLAY_WIDTH, DISPLAY_HEIGHT,
-                                    DISPLAY_ORIENTATION_0, true /*isActive*/, uniqueId2, NO_PORT,
+                                    ui::ROTATION_0, true /*isActive*/, uniqueId2, NO_PORT,
                                     ViewportType::INTERNAL);
 
     std::optional<DisplayViewport> viewport =
@@ -1534,10 +520,10 @@
     // Add the default display second to make sure order doesn't matter.
     mFakePolicy->clearViewports();
     mFakePolicy->addDisplayViewport(nonDefaultDisplayId, DISPLAY_WIDTH, DISPLAY_HEIGHT,
-                                    DISPLAY_ORIENTATION_0, true /*isActive*/, uniqueId2, NO_PORT,
+                                    ui::ROTATION_0, true /*isActive*/, uniqueId2, NO_PORT,
                                     ViewportType::INTERNAL);
     mFakePolicy->addDisplayViewport(ADISPLAY_ID_DEFAULT, DISPLAY_WIDTH, DISPLAY_HEIGHT,
-                                    DISPLAY_ORIENTATION_0, true /*isActive*/, uniqueId1, NO_PORT,
+                                    ui::ROTATION_0, true /*isActive*/, uniqueId1, NO_PORT,
                                     ViewportType::INTERNAL);
 
     viewport = mFakePolicy->getDisplayViewportByType(ViewportType::INTERNAL);
@@ -1561,13 +547,11 @@
 
     mFakePolicy->clearViewports();
     // Add a viewport that's associated with some display port that's not of interest.
-    mFakePolicy->addDisplayViewport(displayId1, DISPLAY_WIDTH, DISPLAY_HEIGHT,
-                                    DISPLAY_ORIENTATION_0, true /*isActive*/, uniqueId1, hdmi3,
-                                    type);
+    mFakePolicy->addDisplayViewport(displayId1, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
+                                    true /*isActive*/, uniqueId1, hdmi3, type);
     // Add another viewport, connected to HDMI1 port
-    mFakePolicy->addDisplayViewport(displayId2, DISPLAY_WIDTH, DISPLAY_HEIGHT,
-                                    DISPLAY_ORIENTATION_0, true /*isActive*/, uniqueId2, hdmi1,
-                                    type);
+    mFakePolicy->addDisplayViewport(displayId2, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
+                                    true /*isActive*/, uniqueId2, hdmi1, type);
 
     // Check that correct display viewport was returned by comparing the display ports.
     std::optional<DisplayViewport> hdmi1Viewport = mFakePolicy->getDisplayViewportByPort(hdmi1);
@@ -2020,11 +1004,10 @@
 
     // Add default and second display.
     mFakePolicy->clearViewports();
-    mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT,
-                                    DISPLAY_ORIENTATION_0, true /*isActive*/, "local:0", NO_PORT,
-                                    ViewportType::INTERNAL);
+    mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
+                                    true /*isActive*/, "local:0", NO_PORT, ViewportType::INTERNAL);
     mFakePolicy->addDisplayViewport(SECONDARY_DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT,
-                                    DISPLAY_ORIENTATION_0, true /*isActive*/, "local:1", hdmi1,
+                                    ui::ROTATION_0, true /*isActive*/, "local:1", hdmi1,
                                     ViewportType::EXTERNAL);
     mReader->requestRefreshConfiguration(InputReaderConfiguration::CHANGE_DISPLAY_INFO);
     mReader->loopOnce();
@@ -2223,8 +1206,9 @@
 
     ASSERT_NO_FATAL_FAILURE(addDevice(eventHubId, "fake", deviceClass, nullptr));
 
-    ASSERT_EQ(controller.getBatteryCapacity(DEFAULT_BATTERY), BATTERY_CAPACITY);
-    ASSERT_EQ(mReader->getBatteryCapacity(deviceId), BATTERY_CAPACITY);
+    ASSERT_EQ(controller.getBatteryCapacity(FakeEventHub::DEFAULT_BATTERY),
+              FakeEventHub::BATTERY_CAPACITY);
+    ASSERT_EQ(mReader->getBatteryCapacity(deviceId), FakeEventHub::BATTERY_CAPACITY);
 }
 
 TEST_F(InputReaderTest, BatteryGetStatus) {
@@ -2240,8 +1224,9 @@
 
     ASSERT_NO_FATAL_FAILURE(addDevice(eventHubId, "fake", deviceClass, nullptr));
 
-    ASSERT_EQ(controller.getBatteryStatus(DEFAULT_BATTERY), BATTERY_STATUS);
-    ASSERT_EQ(mReader->getBatteryStatus(deviceId), BATTERY_STATUS);
+    ASSERT_EQ(controller.getBatteryStatus(FakeEventHub::DEFAULT_BATTERY),
+              FakeEventHub::BATTERY_STATUS);
+    ASSERT_EQ(mReader->getBatteryStatus(deviceId), FakeEventHub::BATTERY_STATUS);
 }
 
 TEST_F(InputReaderTest, BatteryGetDevicePath) {
@@ -2256,7 +1241,7 @@
 
     ASSERT_NO_FATAL_FAILURE(addDevice(eventHubId, "fake", deviceClass, nullptr));
 
-    ASSERT_EQ(mReader->getBatteryDevicePath(deviceId), BATTERY_DEVPATH);
+    ASSERT_EQ(mReader->getBatteryDevicePath(deviceId), FakeEventHub::BATTERY_DEVPATH);
 }
 
 TEST_F(InputReaderTest, LightGetColor) {
@@ -2300,6 +1285,9 @@
     std::shared_ptr<FakePointerController> mFakePointerController;
 
     void SetUp() override {
+#if !defined(__ANDROID__)
+        GTEST_SKIP();
+#endif
         mFakePolicy = sp<FakeInputReaderPolicy>::make();
         mFakePointerController = std::make_shared<FakePointerController>();
         mFakePolicy->setPointerController(mFakePointerController);
@@ -2318,18 +1306,30 @@
     }
 
     void TearDown() override {
+#if !defined(__ANDROID__)
+        return;
+#endif
         ASSERT_EQ(mReader->stop(), OK);
         mReader.reset();
         mTestListener.reset();
         mFakePolicy.clear();
     }
+
+    std::optional<InputDeviceInfo> findDeviceByName(const std::string& name) {
+        const std::vector<InputDeviceInfo> inputDevices = mFakePolicy->getInputDevices();
+        const auto& it = std::find_if(inputDevices.begin(), inputDevices.end(),
+                                      [&name](const InputDeviceInfo& info) {
+                                          return info.getIdentifier().name == name;
+                                      });
+        return it != inputDevices.end() ? std::make_optional(*it) : std::nullopt;
+    }
 };
 
 TEST_F(InputReaderIntegrationTest, TestInvalidDevice) {
     // An invalid input device that is only used for this test.
     class InvalidUinputDevice : public UinputDevice {
     public:
-        InvalidUinputDevice() : UinputDevice("Invalid Device") {}
+        InvalidUinputDevice() : UinputDevice("Invalid Device", 99 /*productId*/) {}
 
     private:
         void configureDevice(int fd, uinput_user_dev* device) override {}
@@ -2358,18 +1358,11 @@
     ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyConfigurationChangedWasCalled());
     ASSERT_EQ(initialNumDevices + 1, mFakePolicy->getInputDevices().size());
 
-    // Find the test device by its name.
-    const std::vector<InputDeviceInfo> inputDevices = mFakePolicy->getInputDevices();
-    const auto& it =
-            std::find_if(inputDevices.begin(), inputDevices.end(),
-                         [&keyboard](const InputDeviceInfo& info) {
-                             return info.getIdentifier().name == keyboard->getName();
-                         });
-
-    ASSERT_NE(it, inputDevices.end());
-    ASSERT_EQ(AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC, it->getKeyboardType());
-    ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, it->getSources());
-    ASSERT_EQ(0U, it->getMotionRanges().size());
+    const auto device = findDeviceByName(keyboard->getName());
+    ASSERT_TRUE(device.has_value());
+    ASSERT_EQ(AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC, device->getKeyboardType());
+    ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, device->getSources());
+    ASSERT_EQ(0U, device->getMotionRanges().size());
 
     keyboard.reset();
     ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertInputDevicesChanged());
@@ -2404,6 +1397,41 @@
     ASSERT_LE(keyArgs.eventTime, keyArgs.readTime);
 }
 
+TEST_F(InputReaderIntegrationTest, ExternalStylusesButtons) {
+    std::unique_ptr<UinputExternalStylus> stylus = createUinputDevice<UinputExternalStylus>();
+    ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertInputDevicesChanged());
+
+    const auto device = findDeviceByName(stylus->getName());
+    ASSERT_TRUE(device.has_value());
+
+    // An external stylus with buttons should also be recognized as a keyboard.
+    ASSERT_EQ(AINPUT_SOURCE_KEYBOARD | AINPUT_SOURCE_STYLUS, device->getSources())
+            << "Unexpected source " << inputEventSourceToString(device->getSources()).c_str();
+    ASSERT_EQ(AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC, device->getKeyboardType());
+
+    const auto DOWN =
+            AllOf(WithKeyAction(AKEY_EVENT_ACTION_DOWN), WithSource(AINPUT_SOURCE_KEYBOARD));
+    const auto UP = AllOf(WithKeyAction(AKEY_EVENT_ACTION_UP), WithSource(AINPUT_SOURCE_KEYBOARD));
+
+    stylus->pressAndReleaseKey(BTN_STYLUS);
+    ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyKeyWasCalled(
+            AllOf(DOWN, WithKeyCode(AKEYCODE_STYLUS_BUTTON_PRIMARY))));
+    ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyKeyWasCalled(
+            AllOf(UP, WithKeyCode(AKEYCODE_STYLUS_BUTTON_PRIMARY))));
+
+    stylus->pressAndReleaseKey(BTN_STYLUS2);
+    ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyKeyWasCalled(
+            AllOf(DOWN, WithKeyCode(AKEYCODE_STYLUS_BUTTON_SECONDARY))));
+    ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyKeyWasCalled(
+            AllOf(UP, WithKeyCode(AKEYCODE_STYLUS_BUTTON_SECONDARY))));
+
+    stylus->pressAndReleaseKey(BTN_STYLUS3);
+    ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyKeyWasCalled(
+            AllOf(DOWN, WithKeyCode(AKEYCODE_STYLUS_BUTTON_TERTIARY))));
+    ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyKeyWasCalled(
+            AllOf(UP, WithKeyCode(AKEYCODE_STYLUS_BUTTON_TERTIARY))));
+}
+
 /**
  * The Steam controller sends BTN_GEAR_DOWN and BTN_GEAR_UP for the two "paddle" buttons
  * on the back. In this test, we make sure that BTN_GEAR_DOWN / BTN_WHEEL and BTN_GEAR_UP
@@ -2426,25 +1454,31 @@
     ASSERT_EQ(BTN_GEAR_UP, keyArgs.scanCode);
 }
 
-// --- TouchProcessTest ---
+// --- TouchIntegrationTest ---
+
 class TouchIntegrationTest : public InputReaderIntegrationTest {
 protected:
     const std::string UNIQUE_ID = "local:0";
 
     void SetUp() override {
+#if !defined(__ANDROID__)
+        GTEST_SKIP();
+#endif
         InputReaderIntegrationTest::SetUp();
         // At least add an internal display.
-        setDisplayInfoAndReconfigure(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT,
-                                     DISPLAY_ORIENTATION_0, UNIQUE_ID, NO_PORT,
-                                     ViewportType::INTERNAL);
+        setDisplayInfoAndReconfigure(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
+                                     UNIQUE_ID, NO_PORT, ViewportType::INTERNAL);
 
         mDevice = createUinputDevice<UinputTouchScreen>(Rect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT));
         ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertInputDevicesChanged());
         ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyConfigurationChangedWasCalled());
+        const auto info = findDeviceByName(mDevice->getName());
+        ASSERT_TRUE(info);
+        mDeviceInfo = *info;
     }
 
     void setDisplayInfoAndReconfigure(int32_t displayId, int32_t width, int32_t height,
-                                      int32_t orientation, const std::string& uniqueId,
+                                      ui::Rotation orientation, const std::string& uniqueId,
                                       std::optional<uint8_t> physicalPort,
                                       ViewportType viewportType) {
         mFakePolicy->addDisplayViewport(displayId, width, height, orientation, true /*isActive*/,
@@ -2464,8 +1498,17 @@
     }
 
     std::unique_ptr<UinputTouchScreen> mDevice;
+    InputDeviceInfo mDeviceInfo;
 };
 
+TEST_F(TouchIntegrationTest, MultiTouchDeviceSource) {
+    // The UinputTouchScreen is an MT device that supports MT_TOOL_TYPE and also supports stylus
+    // buttons. It should show up as a touchscreen, stylus, and keyboard (for reporting button
+    // presses).
+    ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN | AINPUT_SOURCE_STYLUS | AINPUT_SOURCE_KEYBOARD,
+              mDeviceInfo.getSources());
+}
+
 TEST_F(TouchIntegrationTest, InputEvent_ProcessSingleTouch) {
     NotifyMotionArgs args;
     const Point centerPoint = mDevice->getCenterPoint();
@@ -2680,6 +1723,466 @@
     ASSERT_EQ(AMOTION_EVENT_ACTION_UP, args.action);
 }
 
+TEST_F(TouchIntegrationTest, NotifiesPolicyWhenStylusGestureStarted) {
+    const Point centerPoint = mDevice->getCenterPoint();
+
+    // Send down with the pen tool selected. The policy should be notified of the stylus presence.
+    mDevice->sendSlot(FIRST_SLOT);
+    mDevice->sendTrackingId(FIRST_TRACKING_ID);
+    mDevice->sendToolType(MT_TOOL_PEN);
+    mDevice->sendDown(centerPoint);
+    mDevice->sendSync();
+    ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(
+            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+                  WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS))));
+
+    ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertStylusGestureNotified(mDeviceInfo.getId()));
+
+    // Release the stylus touch.
+    mDevice->sendUp();
+    mDevice->sendSync();
+    ASSERT_NO_FATAL_FAILURE(
+            mTestListener->assertNotifyMotionWasCalled(WithMotionAction(AMOTION_EVENT_ACTION_UP)));
+
+    ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertStylusGestureNotNotified());
+
+    // Touch down with the finger, without the pen tool selected. The policy is not notified.
+    mDevice->sendTrackingId(FIRST_TRACKING_ID);
+    mDevice->sendToolType(MT_TOOL_FINGER);
+    mDevice->sendDown(centerPoint);
+    mDevice->sendSync();
+    ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(
+            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+                  WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER))));
+
+    ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertStylusGestureNotNotified());
+
+    mDevice->sendUp();
+    mDevice->sendSync();
+    ASSERT_NO_FATAL_FAILURE(
+            mTestListener->assertNotifyMotionWasCalled(WithMotionAction(AMOTION_EVENT_ACTION_UP)));
+
+    // Send a move event with the stylus tool without BTN_TOUCH to generate a hover enter.
+    // The policy should be notified of the stylus presence.
+    mDevice->sendTrackingId(FIRST_TRACKING_ID);
+    mDevice->sendToolType(MT_TOOL_PEN);
+    mDevice->sendMove(centerPoint);
+    mDevice->sendSync();
+    ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(
+            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
+                  WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS))));
+
+    ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertStylusGestureNotified(mDeviceInfo.getId()));
+}
+
+// --- StylusButtonIntegrationTest ---
+
+// Verify the behavior of button presses reported by various kinds of styluses, including buttons
+// reported by the touchscreen's device, by a fused external stylus, and by an un-fused external
+// stylus.
+template <typename UinputStylusDevice>
+class StylusButtonIntegrationTest : public TouchIntegrationTest {
+protected:
+    void SetUp() override {
+#if !defined(__ANDROID__)
+        GTEST_SKIP();
+#endif
+        TouchIntegrationTest::SetUp();
+        mTouchscreen = mDevice.get();
+        mTouchscreenInfo = mDeviceInfo;
+
+        setUpStylusDevice();
+    }
+
+    UinputStylusDevice* mStylus{nullptr};
+    InputDeviceInfo mStylusInfo{};
+
+    UinputTouchScreen* mTouchscreen{nullptr};
+    InputDeviceInfo mTouchscreenInfo{};
+
+private:
+    // When we are attempting to test stylus button events that are sent from the touchscreen,
+    // use the same Uinput device for the touchscreen and the stylus.
+    template <typename T = UinputStylusDevice>
+    std::enable_if_t<std::is_same_v<UinputTouchScreen, T>, void> setUpStylusDevice() {
+        mStylus = mDevice.get();
+        mStylusInfo = mDeviceInfo;
+    }
+
+    // When we are attempting to stylus buttons from an external stylus being merged with touches
+    // from a touchscreen, create a new Uinput device through which stylus buttons can be injected.
+    template <typename T = UinputStylusDevice>
+    std::enable_if_t<!std::is_same_v<UinputTouchScreen, T>, void> setUpStylusDevice() {
+        mStylusDeviceLifecycleTracker = createUinputDevice<T>();
+        mStylus = mStylusDeviceLifecycleTracker.get();
+        ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertInputDevicesChanged());
+        ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyConfigurationChangedWasCalled());
+        const auto info = findDeviceByName(mStylus->getName());
+        ASSERT_TRUE(info);
+        mStylusInfo = *info;
+    }
+
+    std::unique_ptr<UinputStylusDevice> mStylusDeviceLifecycleTracker{};
+
+    // Hide the base class's device to expose it with a different name for readability.
+    using TouchIntegrationTest::mDevice;
+    using TouchIntegrationTest::mDeviceInfo;
+};
+
+using StylusButtonIntegrationTestTypes =
+        ::testing::Types<UinputTouchScreen, UinputExternalStylus, UinputExternalStylusWithPressure>;
+TYPED_TEST_SUITE(StylusButtonIntegrationTest, StylusButtonIntegrationTestTypes);
+
+TYPED_TEST(StylusButtonIntegrationTest, StylusButtonsGenerateKeyEvents) {
+    const auto stylusId = TestFixture::mStylusInfo.getId();
+
+    TestFixture::mStylus->pressKey(BTN_STYLUS);
+    ASSERT_NO_FATAL_FAILURE(TestFixture::mTestListener->assertNotifyKeyWasCalled(
+            AllOf(WithKeyAction(AKEY_EVENT_ACTION_DOWN), WithSource(AINPUT_SOURCE_KEYBOARD),
+                  WithKeyCode(AKEYCODE_STYLUS_BUTTON_PRIMARY), WithDeviceId(stylusId))));
+
+    TestFixture::mStylus->releaseKey(BTN_STYLUS);
+    ASSERT_NO_FATAL_FAILURE(TestFixture::mTestListener->assertNotifyKeyWasCalled(
+            AllOf(WithKeyAction(AKEY_EVENT_ACTION_UP), WithSource(AINPUT_SOURCE_KEYBOARD),
+                  WithKeyCode(AKEYCODE_STYLUS_BUTTON_PRIMARY), WithDeviceId(stylusId))));
+}
+
+TYPED_TEST(StylusButtonIntegrationTest, StylusButtonsSurroundingTouchGesture) {
+    const Point centerPoint = TestFixture::mTouchscreen->getCenterPoint();
+    const auto touchscreenId = TestFixture::mTouchscreenInfo.getId();
+    const auto stylusId = TestFixture::mStylusInfo.getId();
+
+    // Press the stylus button.
+    TestFixture::mStylus->pressKey(BTN_STYLUS);
+    ASSERT_NO_FATAL_FAILURE(TestFixture::mTestListener->assertNotifyKeyWasCalled(
+            AllOf(WithKeyAction(AKEY_EVENT_ACTION_DOWN), WithSource(AINPUT_SOURCE_KEYBOARD),
+                  WithKeyCode(AKEYCODE_STYLUS_BUTTON_PRIMARY), WithDeviceId(stylusId))));
+
+    // Start and finish a stylus gesture.
+    TestFixture::mTouchscreen->sendSlot(FIRST_SLOT);
+    TestFixture::mTouchscreen->sendTrackingId(FIRST_TRACKING_ID);
+    TestFixture::mTouchscreen->sendToolType(MT_TOOL_PEN);
+    TestFixture::mTouchscreen->sendDown(centerPoint);
+    TestFixture::mTouchscreen->sendSync();
+    ASSERT_NO_FATAL_FAILURE(TestFixture::mTestListener->assertNotifyMotionWasCalled(
+            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+                  WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS),
+                  WithButtonState(AMOTION_EVENT_BUTTON_STYLUS_PRIMARY),
+                  WithDeviceId(touchscreenId))));
+    ASSERT_NO_FATAL_FAILURE(TestFixture::mTestListener->assertNotifyMotionWasCalled(
+            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
+                  WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS),
+                  WithButtonState(AMOTION_EVENT_BUTTON_STYLUS_PRIMARY),
+                  WithDeviceId(touchscreenId))));
+
+    TestFixture::mTouchscreen->sendTrackingId(INVALID_TRACKING_ID);
+    TestFixture::mTouchscreen->sendSync();
+    ASSERT_NO_FATAL_FAILURE(TestFixture::mTestListener->assertNotifyMotionWasCalled(
+            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
+                  WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS), WithButtonState(0),
+                  WithDeviceId(touchscreenId))));
+    ASSERT_NO_FATAL_FAILURE(TestFixture::mTestListener->assertNotifyMotionWasCalled(
+            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                  WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS), WithButtonState(0),
+                  WithDeviceId(touchscreenId))));
+
+    // Release the stylus button.
+    TestFixture::mStylus->releaseKey(BTN_STYLUS);
+    ASSERT_NO_FATAL_FAILURE(TestFixture::mTestListener->assertNotifyKeyWasCalled(
+            AllOf(WithKeyAction(AKEY_EVENT_ACTION_UP), WithSource(AINPUT_SOURCE_KEYBOARD),
+                  WithKeyCode(AKEYCODE_STYLUS_BUTTON_PRIMARY), WithDeviceId(stylusId))));
+}
+
+TYPED_TEST(StylusButtonIntegrationTest, StylusButtonsSurroundingHoveringTouchGesture) {
+    const Point centerPoint = TestFixture::mTouchscreen->getCenterPoint();
+    const auto touchscreenId = TestFixture::mTouchscreenInfo.getId();
+    const auto stylusId = TestFixture::mStylusInfo.getId();
+    auto toolTypeDevice =
+            AllOf(WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS), WithDeviceId(touchscreenId));
+
+    // Press the stylus button.
+    TestFixture::mStylus->pressKey(BTN_STYLUS);
+    ASSERT_NO_FATAL_FAILURE(TestFixture::mTestListener->assertNotifyKeyWasCalled(
+            AllOf(WithKeyAction(AKEY_EVENT_ACTION_DOWN), WithSource(AINPUT_SOURCE_KEYBOARD),
+                  WithKeyCode(AKEYCODE_STYLUS_BUTTON_PRIMARY), WithDeviceId(stylusId))));
+
+    // Start hovering with the stylus.
+    TestFixture::mTouchscreen->sendSlot(FIRST_SLOT);
+    TestFixture::mTouchscreen->sendTrackingId(FIRST_TRACKING_ID);
+    TestFixture::mTouchscreen->sendToolType(MT_TOOL_PEN);
+    TestFixture::mTouchscreen->sendMove(centerPoint);
+    TestFixture::mTouchscreen->sendSync();
+    ASSERT_NO_FATAL_FAILURE(TestFixture::mTestListener->assertNotifyMotionWasCalled(
+            AllOf(toolTypeDevice, WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
+                  WithButtonState(AMOTION_EVENT_BUTTON_STYLUS_PRIMARY))));
+    ASSERT_NO_FATAL_FAILURE(TestFixture::mTestListener->assertNotifyMotionWasCalled(
+            AllOf(toolTypeDevice, WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+                  WithButtonState(AMOTION_EVENT_BUTTON_STYLUS_PRIMARY))));
+    ASSERT_NO_FATAL_FAILURE(TestFixture::mTestListener->assertNotifyMotionWasCalled(
+            AllOf(toolTypeDevice, WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
+                  WithButtonState(AMOTION_EVENT_BUTTON_STYLUS_PRIMARY))));
+
+    // Touch down with the stylus.
+    TestFixture::mTouchscreen->sendTrackingId(FIRST_TRACKING_ID);
+    TestFixture::mTouchscreen->sendToolType(MT_TOOL_PEN);
+    TestFixture::mTouchscreen->sendDown(centerPoint);
+    TestFixture::mTouchscreen->sendSync();
+    ASSERT_NO_FATAL_FAILURE(TestFixture::mTestListener->assertNotifyMotionWasCalled(
+            AllOf(toolTypeDevice, WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT),
+                  WithButtonState(AMOTION_EVENT_BUTTON_STYLUS_PRIMARY))));
+
+    ASSERT_NO_FATAL_FAILURE(TestFixture::mTestListener->assertNotifyMotionWasCalled(
+            AllOf(toolTypeDevice, WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+                  WithButtonState(AMOTION_EVENT_BUTTON_STYLUS_PRIMARY))));
+
+    // Stop touching with the stylus, and start hovering.
+    TestFixture::mTouchscreen->sendUp();
+    TestFixture::mTouchscreen->sendTrackingId(FIRST_TRACKING_ID);
+    TestFixture::mTouchscreen->sendToolType(MT_TOOL_PEN);
+    TestFixture::mTouchscreen->sendMove(centerPoint);
+    TestFixture::mTouchscreen->sendSync();
+    ASSERT_NO_FATAL_FAILURE(TestFixture::mTestListener->assertNotifyMotionWasCalled(
+            AllOf(toolTypeDevice, WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                  WithButtonState(AMOTION_EVENT_BUTTON_STYLUS_PRIMARY))));
+    ASSERT_NO_FATAL_FAILURE(TestFixture::mTestListener->assertNotifyMotionWasCalled(
+            AllOf(toolTypeDevice, WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
+                  WithButtonState(AMOTION_EVENT_BUTTON_STYLUS_PRIMARY))));
+    ASSERT_NO_FATAL_FAILURE(TestFixture::mTestListener->assertNotifyMotionWasCalled(
+            AllOf(toolTypeDevice, WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+                  WithButtonState(AMOTION_EVENT_BUTTON_STYLUS_PRIMARY))));
+
+    // Stop hovering.
+    TestFixture::mTouchscreen->sendTrackingId(INVALID_TRACKING_ID);
+    TestFixture::mTouchscreen->sendSync();
+    ASSERT_NO_FATAL_FAILURE(TestFixture::mTestListener->assertNotifyMotionWasCalled(
+            AllOf(toolTypeDevice, WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
+                  WithButtonState(0))));
+    // TODO(b/257971675): Fix inconsistent button state when exiting hover.
+    ASSERT_NO_FATAL_FAILURE(TestFixture::mTestListener->assertNotifyMotionWasCalled(
+            AllOf(toolTypeDevice, WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT),
+                  WithButtonState(AMOTION_EVENT_BUTTON_STYLUS_PRIMARY))));
+
+    // Release the stylus button.
+    TestFixture::mStylus->releaseKey(BTN_STYLUS);
+    ASSERT_NO_FATAL_FAILURE(TestFixture::mTestListener->assertNotifyKeyWasCalled(
+            AllOf(WithKeyAction(AKEY_EVENT_ACTION_UP), WithSource(AINPUT_SOURCE_KEYBOARD),
+                  WithKeyCode(AKEYCODE_STYLUS_BUTTON_PRIMARY), WithDeviceId(stylusId))));
+}
+
+TYPED_TEST(StylusButtonIntegrationTest, StylusButtonsWithinTouchGesture) {
+    const Point centerPoint = TestFixture::mTouchscreen->getCenterPoint();
+    const auto touchscreenId = TestFixture::mTouchscreenInfo.getId();
+    const auto stylusId = TestFixture::mStylusInfo.getId();
+
+    // Start a stylus gesture.
+    TestFixture::mTouchscreen->sendSlot(FIRST_SLOT);
+    TestFixture::mTouchscreen->sendTrackingId(FIRST_TRACKING_ID);
+    TestFixture::mTouchscreen->sendToolType(MT_TOOL_PEN);
+    TestFixture::mTouchscreen->sendDown(centerPoint);
+    TestFixture::mTouchscreen->sendSync();
+    ASSERT_NO_FATAL_FAILURE(TestFixture::mTestListener->assertNotifyMotionWasCalled(
+            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+                  WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS), WithButtonState(0),
+                  WithDeviceId(touchscreenId))));
+
+    // Press and release a stylus button. Each change in button state also generates a MOVE event.
+    TestFixture::mStylus->pressKey(BTN_STYLUS);
+    ASSERT_NO_FATAL_FAILURE(TestFixture::mTestListener->assertNotifyKeyWasCalled(
+            AllOf(WithKeyAction(AKEY_EVENT_ACTION_DOWN), WithSource(AINPUT_SOURCE_KEYBOARD),
+                  WithKeyCode(AKEYCODE_STYLUS_BUTTON_PRIMARY), WithDeviceId(stylusId))));
+    ASSERT_NO_FATAL_FAILURE(TestFixture::mTestListener->assertNotifyMotionWasCalled(
+            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+                  WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS),
+                  WithButtonState(AMOTION_EVENT_BUTTON_STYLUS_PRIMARY),
+                  WithDeviceId(touchscreenId))));
+    ASSERT_NO_FATAL_FAILURE(TestFixture::mTestListener->assertNotifyMotionWasCalled(
+            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
+                  WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS),
+                  WithButtonState(AMOTION_EVENT_BUTTON_STYLUS_PRIMARY),
+                  WithDeviceId(touchscreenId))));
+
+    TestFixture::mStylus->releaseKey(BTN_STYLUS);
+    ASSERT_NO_FATAL_FAILURE(TestFixture::mTestListener->assertNotifyKeyWasCalled(
+            AllOf(WithKeyAction(AKEY_EVENT_ACTION_UP), WithSource(AINPUT_SOURCE_KEYBOARD),
+                  WithKeyCode(AKEYCODE_STYLUS_BUTTON_PRIMARY), WithDeviceId(stylusId))));
+    ASSERT_NO_FATAL_FAILURE(TestFixture::mTestListener->assertNotifyMotionWasCalled(
+            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
+                  WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS), WithButtonState(0),
+                  WithDeviceId(touchscreenId))));
+    ASSERT_NO_FATAL_FAILURE(TestFixture::mTestListener->assertNotifyMotionWasCalled(
+            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+                  WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS), WithButtonState(0),
+                  WithDeviceId(touchscreenId))));
+
+    // Finish the stylus gesture.
+    TestFixture::mTouchscreen->sendTrackingId(INVALID_TRACKING_ID);
+    TestFixture::mTouchscreen->sendSync();
+    ASSERT_NO_FATAL_FAILURE(TestFixture::mTestListener->assertNotifyMotionWasCalled(
+            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                  WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS), WithButtonState(0),
+                  WithDeviceId(touchscreenId))));
+}
+
+// --- ExternalStylusIntegrationTest ---
+
+// Verify the behavior of an external stylus. An external stylus can report pressure or button
+// data independently of the touchscreen, which is then sent as a MotionEvent as part of an
+// ongoing stylus gesture that is being emitted by the touchscreen.
+using ExternalStylusIntegrationTest = TouchIntegrationTest;
+
+TEST_F(ExternalStylusIntegrationTest, FusedExternalStylusPressureReported) {
+    const Point centerPoint = mDevice->getCenterPoint();
+
+    // Create an external stylus capable of reporting pressure data that
+    // should be fused with a touch pointer.
+    std::unique_ptr<UinputExternalStylusWithPressure> stylus =
+            createUinputDevice<UinputExternalStylusWithPressure>();
+    ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertInputDevicesChanged());
+    ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyConfigurationChangedWasCalled());
+    const auto stylusInfo = findDeviceByName(stylus->getName());
+    ASSERT_TRUE(stylusInfo);
+
+    ASSERT_EQ(AINPUT_SOURCE_STYLUS | AINPUT_SOURCE_KEYBOARD, stylusInfo->getSources());
+
+    const auto touchscreenId = mDeviceInfo.getId();
+
+    // Set a pressure value on the stylus. It doesn't generate any events.
+    const auto& RAW_PRESSURE_MAX = UinputExternalStylusWithPressure::RAW_PRESSURE_MAX;
+    stylus->setPressure(100);
+    ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasNotCalled());
+
+    // Start a finger gesture, and ensure it shows up as stylus gesture
+    // with the pressure set by the external stylus.
+    mDevice->sendSlot(FIRST_SLOT);
+    mDevice->sendTrackingId(FIRST_TRACKING_ID);
+    mDevice->sendToolType(MT_TOOL_FINGER);
+    mDevice->sendDown(centerPoint);
+    mDevice->sendSync();
+    ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(
+            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+                  WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS), WithButtonState(0),
+                  WithDeviceId(touchscreenId), WithPressure(100.f / RAW_PRESSURE_MAX))));
+
+    // Change the pressure on the external stylus, and ensure the touchscreen generates a MOVE
+    // event with the updated pressure.
+    stylus->setPressure(200);
+    ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(
+            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+                  WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS), WithButtonState(0),
+                  WithDeviceId(touchscreenId), WithPressure(200.f / RAW_PRESSURE_MAX))));
+
+    // The external stylus did not generate any events.
+    ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasNotCalled());
+    ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyKeyWasNotCalled());
+}
+
+TEST_F(ExternalStylusIntegrationTest, FusedExternalStylusPressureNotReported) {
+    const Point centerPoint = mDevice->getCenterPoint();
+
+    // Create an external stylus capable of reporting pressure data that
+    // should be fused with a touch pointer.
+    std::unique_ptr<UinputExternalStylusWithPressure> stylus =
+            createUinputDevice<UinputExternalStylusWithPressure>();
+    ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertInputDevicesChanged());
+    ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyConfigurationChangedWasCalled());
+    const auto stylusInfo = findDeviceByName(stylus->getName());
+    ASSERT_TRUE(stylusInfo);
+
+    ASSERT_EQ(AINPUT_SOURCE_STYLUS | AINPUT_SOURCE_KEYBOARD, stylusInfo->getSources());
+
+    const auto touchscreenId = mDeviceInfo.getId();
+
+    // Set a pressure value of 0 on the stylus. It doesn't generate any events.
+    const auto& RAW_PRESSURE_MAX = UinputExternalStylusWithPressure::RAW_PRESSURE_MAX;
+    // Send a non-zero value first to prevent the kernel from consuming the zero event.
+    stylus->setPressure(100);
+    stylus->setPressure(0);
+    ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasNotCalled());
+
+    // Start a finger gesture. The touch device will withhold generating any touches for
+    // up to 72 milliseconds while waiting for pressure data from the external stylus.
+    mDevice->sendSlot(FIRST_SLOT);
+    mDevice->sendTrackingId(FIRST_TRACKING_ID);
+    mDevice->sendToolType(MT_TOOL_FINGER);
+    mDevice->sendDown(centerPoint);
+    auto waitUntil = std::chrono::system_clock::now() +
+            std::chrono::milliseconds(ns2ms(EXTERNAL_STYLUS_DATA_TIMEOUT));
+    mDevice->sendSync();
+    ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasNotCalled(waitUntil));
+
+    // Since the external stylus did not report a pressure value within the timeout,
+    // it shows up as a finger pointer.
+    ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(
+            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+                  WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER), WithDeviceId(touchscreenId),
+                  WithPressure(1.f))));
+
+    // Change the pressure on the external stylus. Since the pressure was not present at the start
+    // of the gesture, it is ignored for now.
+    stylus->setPressure(200);
+    ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasNotCalled());
+
+    // Finish the finger gesture.
+    mDevice->sendTrackingId(INVALID_TRACKING_ID);
+    mDevice->sendSync();
+    ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(
+            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                  WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER))));
+
+    // Start a new gesture. Since we have a valid pressure value, it shows up as a stylus.
+    mDevice->sendTrackingId(FIRST_TRACKING_ID);
+    mDevice->sendToolType(MT_TOOL_FINGER);
+    mDevice->sendDown(centerPoint);
+    mDevice->sendSync();
+    ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(
+            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+                  WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS), WithButtonState(0),
+                  WithDeviceId(touchscreenId), WithPressure(200.f / RAW_PRESSURE_MAX))));
+
+    // The external stylus did not generate any events.
+    ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasNotCalled());
+    ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyKeyWasNotCalled());
+}
+
+TEST_F(ExternalStylusIntegrationTest, UnfusedExternalStylus) {
+    const Point centerPoint = mDevice->getCenterPoint();
+
+    // Create an external stylus device that does not support pressure. It should not affect any
+    // touch pointers.
+    std::unique_ptr<UinputExternalStylus> stylus = createUinputDevice<UinputExternalStylus>();
+    ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertInputDevicesChanged());
+    ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyConfigurationChangedWasCalled());
+    const auto stylusInfo = findDeviceByName(stylus->getName());
+    ASSERT_TRUE(stylusInfo);
+
+    ASSERT_EQ(AINPUT_SOURCE_STYLUS | AINPUT_SOURCE_KEYBOARD, stylusInfo->getSources());
+
+    const auto touchscreenId = mDeviceInfo.getId();
+
+    // Start a finger gesture and ensure a finger pointer is generated for it, without waiting for
+    // pressure data from the external stylus.
+    mDevice->sendSlot(FIRST_SLOT);
+    mDevice->sendTrackingId(FIRST_TRACKING_ID);
+    mDevice->sendToolType(MT_TOOL_FINGER);
+    mDevice->sendDown(centerPoint);
+    auto waitUntil = std::chrono::system_clock::now() +
+            std::chrono::milliseconds(ns2ms(EXTERNAL_STYLUS_DATA_TIMEOUT));
+    mDevice->sendSync();
+    ASSERT_NO_FATAL_FAILURE(
+            mTestListener
+                    ->assertNotifyMotionWasCalled(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+                                                        WithToolType(
+                                                                AMOTION_EVENT_TOOL_TYPE_FINGER),
+                                                        WithButtonState(0),
+                                                        WithDeviceId(touchscreenId),
+                                                        WithPressure(1.f)),
+                                                  waitUntil));
+
+    // The external stylus did not generate any events.
+    ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasNotCalled());
+    ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyKeyWasNotCalled());
+}
+
 // --- InputDeviceTest ---
 class InputDeviceTest : public testing::Test {
 protected:
@@ -2690,6 +2193,7 @@
     static const int32_t DEVICE_CONTROLLER_NUMBER;
     static const ftl::Flags<InputDeviceClass> DEVICE_CLASSES;
     static const int32_t EVENTHUB_ID;
+    static const std::string DEVICE_BLUETOOTH_ADDRESS;
 
     std::shared_ptr<FakeEventHub> mFakeEventHub;
     sp<FakeInputReaderPolicy> mFakePolicy;
@@ -2706,6 +2210,7 @@
         InputDeviceIdentifier identifier;
         identifier.name = DEVICE_NAME;
         identifier.location = DEVICE_LOCATION;
+        identifier.bluetoothAddress = DEVICE_BLUETOOTH_ADDRESS;
         mDevice = std::make_shared<InputDevice>(mReader->getContext(), DEVICE_ID, DEVICE_GENERATION,
                                                 identifier);
         mReader->pushNextDevice(mDevice);
@@ -2727,6 +2232,7 @@
 const ftl::Flags<InputDeviceClass> InputDeviceTest::DEVICE_CLASSES =
         InputDeviceClass::KEYBOARD | InputDeviceClass::TOUCH | InputDeviceClass::JOYSTICK;
 const int32_t InputDeviceTest::EVENTHUB_ID = 1;
+const std::string InputDeviceTest::DEVICE_BLUETOOTH_ADDRESS = "11:AA:22:BB:33:CC";
 
 TEST_F(InputDeviceTest, ImmutableProperties) {
     ASSERT_EQ(DEVICE_ID, mDevice->getId());
@@ -2910,7 +2416,7 @@
 
     // Prepare displays.
     mFakePolicy->addDisplayViewport(SECONDARY_DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT,
-                                    DISPLAY_ORIENTATION_0, true /*isActive*/, UNIQUE_ID, hdmi,
+                                    ui::ROTATION_0, true /*isActive*/, UNIQUE_ID, hdmi,
                                     ViewportType::INTERNAL);
     unused += mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
                                  InputReaderConfiguration::CHANGE_DISPLAY_INFO);
@@ -2945,7 +2451,7 @@
 
     // Device should be enabled when a display is found.
     mFakePolicy->addDisplayViewport(SECONDARY_DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT,
-                                    DISPLAY_ORIENTATION_0, /* isActive= */ true, DISPLAY_UNIQUE_ID,
+                                    ui::ROTATION_0, /* isActive= */ true, DISPLAY_UNIQUE_ID,
                                     NO_PORT, ViewportType::INTERNAL);
     unused += mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
                                  InputReaderConfiguration::CHANGE_DISPLAY_INFO);
@@ -2971,7 +2477,7 @@
 
     mFakePolicy->addInputUniqueIdAssociation(DEVICE_LOCATION, DISPLAY_UNIQUE_ID);
     mFakePolicy->addDisplayViewport(SECONDARY_DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT,
-                                    DISPLAY_ORIENTATION_0, /* isActive= */ true, DISPLAY_UNIQUE_ID,
+                                    ui::ROTATION_0, /* isActive= */ true, DISPLAY_UNIQUE_ID,
                                     NO_PORT, ViewportType::INTERNAL);
     unused += mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
                                  InputReaderConfiguration::CHANGE_DISPLAY_INFO);
@@ -2993,173 +2499,11 @@
     device.dump(dumpStr, eventHubDevStr);
 }
 
-// --- InputMapperTest ---
-
-class InputMapperTest : public testing::Test {
-protected:
-    static const char* DEVICE_NAME;
-    static const char* DEVICE_LOCATION;
-    static const int32_t DEVICE_ID;
-    static const int32_t DEVICE_GENERATION;
-    static const int32_t DEVICE_CONTROLLER_NUMBER;
-    static const ftl::Flags<InputDeviceClass> DEVICE_CLASSES;
-    static const int32_t EVENTHUB_ID;
-
-    std::shared_ptr<FakeEventHub> mFakeEventHub;
-    sp<FakeInputReaderPolicy> mFakePolicy;
-    std::unique_ptr<TestInputListener> mFakeListener;
-    std::unique_ptr<InstrumentedInputReader> mReader;
-    std::shared_ptr<InputDevice> mDevice;
-
-    virtual void SetUp(ftl::Flags<InputDeviceClass> classes) {
-        mFakeEventHub = std::make_unique<FakeEventHub>();
-        mFakePolicy = sp<FakeInputReaderPolicy>::make();
-        mFakeListener = std::make_unique<TestInputListener>();
-        mReader = std::make_unique<InstrumentedInputReader>(mFakeEventHub, mFakePolicy,
-                                                            *mFakeListener);
-        mDevice = newDevice(DEVICE_ID, DEVICE_NAME, DEVICE_LOCATION, EVENTHUB_ID, classes);
-        // Consume the device reset notification generated when adding a new device.
-        mFakeListener->assertNotifyDeviceResetWasCalled();
-    }
-
-    void SetUp() override {
-        SetUp(DEVICE_CLASSES);
-    }
-
-    void TearDown() override {
-        mFakeListener.reset();
-        mFakePolicy.clear();
-    }
-
-    void addConfigurationProperty(const char* key, const char* value) {
-        mFakeEventHub->addConfigurationProperty(EVENTHUB_ID, key, value);
-    }
-
-    std::list<NotifyArgs> configureDevice(uint32_t changes) {
-        if (!changes ||
-            (changes &
-             (InputReaderConfiguration::CHANGE_DISPLAY_INFO |
-              InputReaderConfiguration::CHANGE_POINTER_CAPTURE))) {
-            mReader->requestRefreshConfiguration(changes);
-            mReader->loopOnce();
-        }
-        std::list<NotifyArgs> out =
-                mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), changes);
-        // Loop the reader to flush the input listener queue.
-        for (const NotifyArgs& args : out) {
-            mFakeListener->notify(args);
-        }
-        mReader->loopOnce();
-        return out;
-    }
-
-    std::shared_ptr<InputDevice> newDevice(int32_t deviceId, const std::string& name,
-                                           const std::string& location, int32_t eventHubId,
-                                           ftl::Flags<InputDeviceClass> classes) {
-        InputDeviceIdentifier identifier;
-        identifier.name = name;
-        identifier.location = location;
-        std::shared_ptr<InputDevice> device =
-                std::make_shared<InputDevice>(mReader->getContext(), deviceId, DEVICE_GENERATION,
-                                              identifier);
-        mReader->pushNextDevice(device);
-        mFakeEventHub->addDevice(eventHubId, name, classes);
-        mReader->loopOnce();
-        return device;
-    }
-
-    template <class T, typename... Args>
-    T& addMapperAndConfigure(Args... args) {
-        T& mapper = mDevice->addMapper<T>(EVENTHUB_ID, args...);
-        configureDevice(0);
-        std::list<NotifyArgs> resetArgList = mDevice->reset(ARBITRARY_TIME);
-        resetArgList += mapper.reset(ARBITRARY_TIME);
-        // Loop the reader to flush the input listener queue.
-        for (const NotifyArgs& loopArgs : resetArgList) {
-            mFakeListener->notify(loopArgs);
-        }
-        mReader->loopOnce();
-        return mapper;
-    }
-
-    void setDisplayInfoAndReconfigure(int32_t displayId, int32_t width, int32_t height,
-            int32_t orientation, const std::string& uniqueId,
-            std::optional<uint8_t> physicalPort, ViewportType viewportType) {
-        mFakePolicy->addDisplayViewport(displayId, width, height, orientation, true /*isActive*/,
-                                        uniqueId, physicalPort, viewportType);
-        configureDevice(InputReaderConfiguration::CHANGE_DISPLAY_INFO);
-    }
-
-    void clearViewports() {
-        mFakePolicy->clearViewports();
-    }
-
-    std::list<NotifyArgs> process(InputMapper& mapper, nsecs_t when, nsecs_t readTime, int32_t type,
-                                  int32_t code, int32_t value) {
-        RawEvent event;
-        event.when = when;
-        event.readTime = readTime;
-        event.deviceId = mapper.getDeviceContext().getEventHubId();
-        event.type = type;
-        event.code = code;
-        event.value = value;
-        std::list<NotifyArgs> processArgList = mapper.process(&event);
-        for (const NotifyArgs& args : processArgList) {
-            mFakeListener->notify(args);
-        }
-        // Loop the reader to flush the input listener queue.
-        mReader->loopOnce();
-        return processArgList;
-    }
-
-    static 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;
-        ASSERT_EQ(source, range->source) << "Axis: " << axis << " Source: " << source;
-        ASSERT_NEAR(min, range->min, EPSILON) << "Axis: " << axis << " Source: " << source;
-        ASSERT_NEAR(max, range->max, EPSILON) << "Axis: " << axis << " Source: " << source;
-        ASSERT_NEAR(flat, range->flat, EPSILON) << "Axis: " << axis << " Source: " << source;
-        ASSERT_NEAR(fuzz, range->fuzz, EPSILON) << "Axis: " << axis << " Source: " << source;
-    }
-
-    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) {
-        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);
-        ASSERT_NEAR(size, coords.getAxisValue(AMOTION_EVENT_AXIS_SIZE), EPSILON);
-        ASSERT_NEAR(touchMajor, coords.getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR),
-                    scaledAxisEpsilon);
-        ASSERT_NEAR(touchMinor, coords.getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR),
-                    scaledAxisEpsilon);
-        ASSERT_NEAR(toolMajor, coords.getAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR),
-                    scaledAxisEpsilon);
-        ASSERT_NEAR(toolMinor, coords.getAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR),
-                    scaledAxisEpsilon);
-        ASSERT_NEAR(orientation, coords.getAxisValue(AMOTION_EVENT_AXIS_ORIENTATION), EPSILON);
-        ASSERT_NEAR(distance, coords.getAxisValue(AMOTION_EVENT_AXIS_DISTANCE), EPSILON);
-    }
-
-    static void assertPosition(const FakePointerController& controller, float x, float y) {
-        float actualX, actualY;
-        controller.getPosition(&actualX, &actualY);
-        ASSERT_NEAR(x, actualX, 1);
-        ASSERT_NEAR(y, actualY, 1);
-    }
-};
-
-const char* InputMapperTest::DEVICE_NAME = "device";
-const char* InputMapperTest::DEVICE_LOCATION = "USB1";
-const int32_t InputMapperTest::DEVICE_ID = END_RESERVED_ID + 1000;
-const int32_t InputMapperTest::DEVICE_GENERATION = 2;
-const int32_t InputMapperTest::DEVICE_CONTROLLER_NUMBER = 0;
-const ftl::Flags<InputDeviceClass> InputMapperTest::DEVICE_CLASSES =
-        ftl::Flags<InputDeviceClass>(0); // not needed for current tests
-const int32_t InputMapperTest::EVENTHUB_ID = 1;
+TEST_F(InputDeviceTest, GetBluetoothAddress) {
+    const auto& address = mReader->getBluetoothAddress(DEVICE_ID);
+    ASSERT_TRUE(address);
+    ASSERT_EQ(DEVICE_BLUETOOTH_ADDRESS, *address);
+}
 
 // --- SwitchInputMapperTest ---
 
@@ -3420,7 +2764,7 @@
 protected:
     const std::string UNIQUE_ID = "local:0";
 
-    void prepareDisplay(int32_t orientation);
+    void prepareDisplay(ui::Rotation orientation);
 
     void testDPadKeyRotation(KeyboardInputMapper& mapper, int32_t originalScanCode,
                              int32_t originalKeyCode, int32_t rotatedKeyCode,
@@ -3430,7 +2774,7 @@
 /* Similar to setDisplayInfoAndReconfigure, but pre-populates all parameters except for the
  * orientation.
  */
-void KeyboardInputMapperTest::prepareDisplay(int32_t orientation) {
+void KeyboardInputMapperTest::prepareDisplay(ui::Rotation orientation) {
     setDisplayInfoAndReconfigure(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, orientation, UNIQUE_ID,
                                  NO_PORT, ViewportType::INTERNAL);
 }
@@ -3642,7 +2986,7 @@
             addMapperAndConfigure<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
                                                        AINPUT_KEYBOARD_TYPE_ALPHABETIC);
 
-    prepareDisplay(DISPLAY_ORIENTATION_90);
+    prepareDisplay(ui::ROTATION_90);
     ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper,
             KEY_UP, AKEYCODE_DPAD_UP, AKEYCODE_DPAD_UP));
     ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper,
@@ -3664,7 +3008,7 @@
             addMapperAndConfigure<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
                                                        AINPUT_KEYBOARD_TYPE_ALPHABETIC);
 
-    prepareDisplay(DISPLAY_ORIENTATION_0);
+    prepareDisplay(ui::ROTATION_0);
     ASSERT_NO_FATAL_FAILURE(
             testDPadKeyRotation(mapper, KEY_UP, AKEYCODE_DPAD_UP, AKEYCODE_DPAD_UP, DISPLAY_ID));
     ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper, KEY_RIGHT, AKEYCODE_DPAD_RIGHT,
@@ -3675,7 +3019,7 @@
                                                 AKEYCODE_DPAD_LEFT, DISPLAY_ID));
 
     clearViewports();
-    prepareDisplay(DISPLAY_ORIENTATION_90);
+    prepareDisplay(ui::ROTATION_90);
     ASSERT_NO_FATAL_FAILURE(
             testDPadKeyRotation(mapper, KEY_UP, AKEYCODE_DPAD_UP, AKEYCODE_DPAD_LEFT, DISPLAY_ID));
     ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper, KEY_RIGHT, AKEYCODE_DPAD_RIGHT,
@@ -3686,7 +3030,7 @@
                                                 AKEYCODE_DPAD_DOWN, DISPLAY_ID));
 
     clearViewports();
-    prepareDisplay(DISPLAY_ORIENTATION_180);
+    prepareDisplay(ui::ROTATION_180);
     ASSERT_NO_FATAL_FAILURE(
             testDPadKeyRotation(mapper, KEY_UP, AKEYCODE_DPAD_UP, AKEYCODE_DPAD_DOWN, DISPLAY_ID));
     ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper, KEY_RIGHT, AKEYCODE_DPAD_RIGHT,
@@ -3697,7 +3041,7 @@
                                                 AKEYCODE_DPAD_RIGHT, DISPLAY_ID));
 
     clearViewports();
-    prepareDisplay(DISPLAY_ORIENTATION_270);
+    prepareDisplay(ui::ROTATION_270);
     ASSERT_NO_FATAL_FAILURE(
             testDPadKeyRotation(mapper, KEY_UP, AKEYCODE_DPAD_UP, AKEYCODE_DPAD_RIGHT, DISPLAY_ID));
     ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper, KEY_RIGHT, AKEYCODE_DPAD_RIGHT,
@@ -3711,7 +3055,7 @@
     // in the key up as we did in the key down.
     NotifyKeyArgs args;
     clearViewports();
-    prepareDisplay(DISPLAY_ORIENTATION_270);
+    prepareDisplay(ui::ROTATION_270);
     process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_UP, 1);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
     ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, args.action);
@@ -3719,7 +3063,7 @@
     ASSERT_EQ(AKEYCODE_DPAD_RIGHT, args.keyCode);
 
     clearViewports();
-    prepareDisplay(DISPLAY_ORIENTATION_180);
+    prepareDisplay(ui::ROTATION_180);
     process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_UP, 0);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
     ASSERT_EQ(AKEY_EVENT_ACTION_UP, args.action);
@@ -3744,7 +3088,7 @@
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
     ASSERT_EQ(ADISPLAY_ID_NONE, args.displayId);
 
-    prepareDisplay(DISPLAY_ORIENTATION_0);
+    prepareDisplay(ui::ROTATION_0);
     process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_UP, 1);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
     process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_UP, 0);
@@ -3766,7 +3110,7 @@
     // Display id should be ADISPLAY_ID_NONE without any display configuration.
     // ^--- already checked by the previous test
 
-    setDisplayInfoAndReconfigure(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_ORIENTATION_0,
+    setDisplayInfoAndReconfigure(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
                                  UNIQUE_ID, NO_PORT, ViewportType::INTERNAL);
     process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_UP, 1);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
@@ -3776,7 +3120,7 @@
 
     constexpr int32_t newDisplayId = 2;
     clearViewports();
-    setDisplayInfoAndReconfigure(newDisplayId, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_ORIENTATION_0,
+    setDisplayInfoAndReconfigure(newDisplayId, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
                                  UNIQUE_ID, NO_PORT, ViewportType::INTERNAL);
     process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_UP, 1);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
@@ -3986,9 +3330,9 @@
 
     // Prepare second display.
     constexpr int32_t newDisplayId = 2;
-    setDisplayInfoAndReconfigure(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_ORIENTATION_0,
+    setDisplayInfoAndReconfigure(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
                                  UNIQUE_ID, hdmi1, ViewportType::INTERNAL);
-    setDisplayInfoAndReconfigure(newDisplayId, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_ORIENTATION_0,
+    setDisplayInfoAndReconfigure(newDisplayId, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
                                  SECONDARY_UNIQUE_ID, hdmi2, ViewportType::EXTERNAL);
     // Default device will reconfigure above, need additional reconfiguration for another device.
     unused += device2->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
@@ -4319,14 +3663,14 @@
     void testMotionRotation(CursorInputMapper& mapper, int32_t originalX, int32_t originalY,
                             int32_t rotatedX, int32_t rotatedY);
 
-    void prepareDisplay(int32_t orientation) {
+    void prepareDisplay(ui::Rotation orientation) {
         setDisplayInfoAndReconfigure(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, orientation,
                                      DISPLAY_UNIQUE_ID, NO_PORT, ViewportType::INTERNAL);
     }
 
     void prepareSecondaryDisplay() {
         setDisplayInfoAndReconfigure(SECONDARY_DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT,
-                                     DISPLAY_ORIENTATION_0, SECONDARY_DISPLAY_UNIQUE_ID, NO_PORT,
+                                     ui::ROTATION_0, SECONDARY_DISPLAY_UNIQUE_ID, NO_PORT,
                                      ViewportType::EXTERNAL);
     }
 
@@ -4611,7 +3955,7 @@
     addConfigurationProperty("cursor.orientationAware", "1");
     CursorInputMapper& mapper = addMapperAndConfigure<CursorInputMapper>();
 
-    prepareDisplay(DISPLAY_ORIENTATION_90);
+    prepareDisplay(ui::ROTATION_90);
     ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  0,  1,  0,  1));
     ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  1,  1,  1,  1));
     ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  1,  0,  1,  0));
@@ -4630,7 +3974,7 @@
     CursorInputMapper& mapper = addMapperAndConfigure<CursorInputMapper>();
 
     clearViewports();
-    prepareDisplay(DISPLAY_ORIENTATION_0);
+    prepareDisplay(ui::ROTATION_0);
     ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  0,  1,  0,  1));
     ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  1,  1,  1,  1));
     ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  1,  0,  1,  0));
@@ -4641,7 +3985,7 @@
     ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1,  1, -1,  1));
 
     clearViewports();
-    prepareDisplay(DISPLAY_ORIENTATION_90);
+    prepareDisplay(ui::ROTATION_90);
     ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  0,  1, -1,  0));
     ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  1,  1, -1,  1));
     ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  1,  0,  0,  1));
@@ -4652,7 +3996,7 @@
     ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1,  1, -1, -1));
 
     clearViewports();
-    prepareDisplay(DISPLAY_ORIENTATION_180);
+    prepareDisplay(ui::ROTATION_180);
     ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  0,  1,  0, -1));
     ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  1,  1, -1, -1));
     ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  1,  0, -1,  0));
@@ -4663,7 +4007,7 @@
     ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1,  1,  1, -1));
 
     clearViewports();
-    prepareDisplay(DISPLAY_ORIENTATION_270);
+    prepareDisplay(ui::ROTATION_270);
     ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  0,  1,  1,  0));
     ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  1,  1,  1, -1));
     ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper,  1,  0,  0, -1));
@@ -5127,7 +4471,7 @@
     ASSERT_EQ(DEVICE_ID, resetArgs.deviceId);
 
     // Ensure the display is rotated.
-    prepareDisplay(DISPLAY_ORIENTATION_90);
+    prepareDisplay(ui::ROTATION_90);
 
     NotifyMotionArgs args;
 
@@ -5163,7 +4507,7 @@
     CursorInputMapper& mapper = addMapperAndConfigure<CursorInputMapper>();
 
     // Set up the default display.
-    prepareDisplay(DISPLAY_ORIENTATION_90);
+    prepareDisplay(ui::ROTATION_90);
 
     // Set up the secondary display as the display on which the pointer should be shown.
     // The InputDevice is not associated with any display.
@@ -5190,7 +4534,7 @@
     CursorInputMapper& mapper = addMapperAndConfigure<CursorInputMapper>();
 
     // Set up the default display.
-    prepareDisplay(DISPLAY_ORIENTATION_90);
+    prepareDisplay(ui::ROTATION_90);
 
     // Set up the secondary display as the display on which the pointer should be shown,
     // and associate the InputDevice with the secondary display.
@@ -5217,7 +4561,7 @@
     CursorInputMapper& mapper = addMapperAndConfigure<CursorInputMapper>();
 
     // Set up the default display as the display on which the pointer should be shown.
-    prepareDisplay(DISPLAY_ORIENTATION_90);
+    prepareDisplay(ui::ROTATION_90);
     mFakePolicy->setDefaultPointerDisplayId(DISPLAY_ID);
 
     // Associate the InputDevice with the secondary display.
@@ -5233,6 +4577,106 @@
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled());
 }
 
+// --- BluetoothCursorInputMapperTest ---
+
+class BluetoothCursorInputMapperTest : public CursorInputMapperTest {
+protected:
+    void SetUp() override {
+        InputMapperTest::SetUp(DEVICE_CLASSES | InputDeviceClass::EXTERNAL, BUS_BLUETOOTH);
+
+        mFakePointerController = std::make_shared<FakePointerController>();
+        mFakePolicy->setPointerController(mFakePointerController);
+    }
+};
+
+TEST_F(BluetoothCursorInputMapperTest, TimestampSmoothening) {
+    addConfigurationProperty("cursor.mode", "pointer");
+    CursorInputMapper& mapper = addMapperAndConfigure<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))));
+
+    // 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;
+
+        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))));
+    }
+}
+
+TEST_F(BluetoothCursorInputMapperTest, TimestampSmootheningIsCapped) {
+    addConfigurationProperty("cursor.mode", "pointer");
+    CursorInputMapper& mapper = addMapperAndConfigure<CursorInputMapper>();
+
+    nsecs_t expectedEventTime = ARBITRARY_TIME;
+    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))));
+
+    // 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))));
+    }
+}
+
+TEST_F(BluetoothCursorInputMapperTest, TimestampSmootheningNotUsed) {
+    addConfigurationProperty("cursor.mode", "pointer");
+    CursorInputMapper& mapper = addMapperAndConfigure<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))));
+
+    // 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))));
+}
+
 // --- TouchInputMapperTest ---
 
 class TouchInputMapperTest : public InputMapperTest {
@@ -5284,9 +4728,9 @@
         TOOL_TYPE = 1 << 10,
     };
 
-    void prepareDisplay(int32_t orientation, std::optional<uint8_t> port = NO_PORT);
+    void prepareDisplay(ui::Rotation orientation, std::optional<uint8_t> port = NO_PORT);
     void prepareSecondaryDisplay(ViewportType type, std::optional<uint8_t> port = NO_PORT);
-    void prepareVirtualDisplay(int32_t orientation);
+    void prepareVirtualDisplay(ui::Rotation orientation);
     void prepareVirtualKeys();
     void prepareLocationCalibration();
     int32_t toRawX(float displayX);
@@ -5340,17 +4784,17 @@
         { KEY_MENU, DISPLAY_HEIGHT - 60, DISPLAY_WIDTH + 15, 20, 20 },
 };
 
-void TouchInputMapperTest::prepareDisplay(int32_t orientation, std::optional<uint8_t> port) {
+void TouchInputMapperTest::prepareDisplay(ui::Rotation orientation, std::optional<uint8_t> port) {
     setDisplayInfoAndReconfigure(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, orientation, UNIQUE_ID,
                                  port, ViewportType::INTERNAL);
 }
 
 void TouchInputMapperTest::prepareSecondaryDisplay(ViewportType type, std::optional<uint8_t> port) {
     setDisplayInfoAndReconfigure(SECONDARY_DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT,
-            DISPLAY_ORIENTATION_0, SECONDARY_UNIQUE_ID, port, type);
+                                 ui::ROTATION_0, SECONDARY_UNIQUE_ID, port, type);
 }
 
-void TouchInputMapperTest::prepareVirtualDisplay(int32_t orientation) {
+void TouchInputMapperTest::prepareVirtualDisplay(ui::Rotation orientation) {
     setDisplayInfoAndReconfigure(VIRTUAL_DISPLAY_ID, VIRTUAL_DISPLAY_WIDTH, VIRTUAL_DISPLAY_HEIGHT,
                                  orientation, VIRTUAL_DISPLAY_UNIQUE_ID, NO_PORT,
                                  ViewportType::VIRTUAL);
@@ -5503,7 +4947,7 @@
     prepareAxes(POSITION);
     SingleTouchInputMapper& mapper = addMapperAndConfigure<SingleTouchInputMapper>();
 
-    ASSERT_EQ(AINPUT_SOURCE_MOUSE, mapper.getSources());
+    ASSERT_EQ(AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD, mapper.getSources());
 }
 
 TEST_F(SingleTouchInputMapperTest, GetSources_WhenDeviceTypeIsTouchScreen_ReturnsTouchScreen) {
@@ -5517,7 +4961,7 @@
 
 TEST_F(SingleTouchInputMapperTest, GetKeyCodeState) {
     addConfigurationProperty("touch.deviceType", "touchScreen");
-    prepareDisplay(DISPLAY_ORIENTATION_0);
+    prepareDisplay(ui::ROTATION_0);
     prepareButtons();
     prepareAxes(POSITION);
     prepareVirtualKeys();
@@ -5545,7 +4989,7 @@
 
 TEST_F(SingleTouchInputMapperTest, GetScanCodeState) {
     addConfigurationProperty("touch.deviceType", "touchScreen");
-    prepareDisplay(DISPLAY_ORIENTATION_0);
+    prepareDisplay(ui::ROTATION_0);
     prepareButtons();
     prepareAxes(POSITION);
     prepareVirtualKeys();
@@ -5573,7 +5017,7 @@
 
 TEST_F(SingleTouchInputMapperTest, MarkSupportedKeyCodes) {
     addConfigurationProperty("touch.deviceType", "touchScreen");
-    prepareDisplay(DISPLAY_ORIENTATION_0);
+    prepareDisplay(ui::ROTATION_0);
     prepareButtons();
     prepareAxes(POSITION);
     prepareVirtualKeys();
@@ -5588,7 +5032,7 @@
 
 TEST_F(SingleTouchInputMapperTest, Process_WhenVirtualKeyIsPressedAndReleasedNormally_SendsKeyDownAndKeyUp) {
     addConfigurationProperty("touch.deviceType", "touchScreen");
-    prepareDisplay(DISPLAY_ORIENTATION_0);
+    prepareDisplay(ui::ROTATION_0);
     prepareButtons();
     prepareAxes(POSITION);
     prepareVirtualKeys();
@@ -5638,7 +5082,7 @@
 
 TEST_F(SingleTouchInputMapperTest, Process_WhenVirtualKeyIsPressedAndMovedOutOfBounds_SendsKeyDownAndKeyCancel) {
     addConfigurationProperty("touch.deviceType", "touchScreen");
-    prepareDisplay(DISPLAY_ORIENTATION_0);
+    prepareDisplay(ui::ROTATION_0);
     prepareButtons();
     prepareAxes(POSITION);
     prepareVirtualKeys();
@@ -5759,7 +5203,7 @@
 
 TEST_F(SingleTouchInputMapperTest, Process_WhenTouchStartsOutsideDisplayAndMovesIn_SendsDownAsTouchEntersDisplay) {
     addConfigurationProperty("touch.deviceType", "touchScreen");
-    prepareDisplay(DISPLAY_ORIENTATION_0);
+    prepareDisplay(ui::ROTATION_0);
     prepareButtons();
     prepareAxes(POSITION);
     prepareVirtualKeys();
@@ -5834,7 +5278,7 @@
     addConfigurationProperty("touch.deviceType", "touchScreen");
     addConfigurationProperty("touch.displayId", VIRTUAL_DISPLAY_UNIQUE_ID);
 
-    prepareVirtualDisplay(DISPLAY_ORIENTATION_0);
+    prepareVirtualDisplay(ui::ROTATION_0);
     prepareButtons();
     prepareAxes(POSITION);
     prepareVirtualKeys();
@@ -5930,7 +5374,7 @@
 
 TEST_F(SingleTouchInputMapperTest, Process_NormalSingleTouchGesture) {
     addConfigurationProperty("touch.deviceType", "touchScreen");
-    prepareDisplay(DISPLAY_ORIENTATION_0);
+    prepareDisplay(ui::ROTATION_0);
     prepareButtons();
     prepareAxes(POSITION);
     prepareVirtualKeys();
@@ -6029,7 +5473,7 @@
     NotifyMotionArgs args;
 
     // Rotation 90.
-    prepareDisplay(DISPLAY_ORIENTATION_90);
+    prepareDisplay(ui::ROTATION_90);
     processDown(mapper, toRawX(50), toRawY(75));
     processSync(mapper);
 
@@ -6055,7 +5499,7 @@
 
     // Rotation 0.
     clearViewports();
-    prepareDisplay(DISPLAY_ORIENTATION_0);
+    prepareDisplay(ui::ROTATION_0);
     processDown(mapper, toRawX(50), toRawY(75));
     processSync(mapper);
 
@@ -6069,7 +5513,7 @@
 
     // Rotation 90.
     clearViewports();
-    prepareDisplay(DISPLAY_ORIENTATION_90);
+    prepareDisplay(ui::ROTATION_90);
     processDown(mapper, toRawX(75), RAW_Y_MAX - toRawY(50) + RAW_Y_MIN);
     processSync(mapper);
 
@@ -6083,7 +5527,7 @@
 
     // Rotation 180.
     clearViewports();
-    prepareDisplay(DISPLAY_ORIENTATION_180);
+    prepareDisplay(ui::ROTATION_180);
     processDown(mapper, RAW_X_MAX - toRawX(50) + RAW_X_MIN, RAW_Y_MAX - toRawY(75) + RAW_Y_MIN);
     processSync(mapper);
 
@@ -6097,7 +5541,7 @@
 
     // Rotation 270.
     clearViewports();
-    prepareDisplay(DISPLAY_ORIENTATION_270);
+    prepareDisplay(ui::ROTATION_270);
     processDown(mapper, RAW_X_MAX - toRawX(75) + RAW_X_MIN, toRawY(50));
     processSync(mapper);
 
@@ -6117,7 +5561,7 @@
     addConfigurationProperty("touch.orientationAware", "1");
     addConfigurationProperty("touch.orientation", "ORIENTATION_0");
     clearViewports();
-    prepareDisplay(DISPLAY_ORIENTATION_0);
+    prepareDisplay(ui::ROTATION_0);
     auto& mapper = addMapperAndConfigure<SingleTouchInputMapper>();
     NotifyMotionArgs args;
 
@@ -6141,7 +5585,7 @@
     addConfigurationProperty("touch.orientationAware", "1");
     addConfigurationProperty("touch.orientation", "ORIENTATION_90");
     clearViewports();
-    prepareDisplay(DISPLAY_ORIENTATION_0);
+    prepareDisplay(ui::ROTATION_0);
     auto& mapper = addMapperAndConfigure<SingleTouchInputMapper>();
     NotifyMotionArgs args;
 
@@ -6165,7 +5609,7 @@
     addConfigurationProperty("touch.orientationAware", "1");
     addConfigurationProperty("touch.orientation", "ORIENTATION_180");
     clearViewports();
-    prepareDisplay(DISPLAY_ORIENTATION_0);
+    prepareDisplay(ui::ROTATION_0);
     auto& mapper = addMapperAndConfigure<SingleTouchInputMapper>();
     NotifyMotionArgs args;
 
@@ -6189,7 +5633,7 @@
     addConfigurationProperty("touch.orientationAware", "1");
     addConfigurationProperty("touch.orientation", "ORIENTATION_270");
     clearViewports();
-    prepareDisplay(DISPLAY_ORIENTATION_0);
+    prepareDisplay(ui::ROTATION_0);
     auto& mapper = addMapperAndConfigure<SingleTouchInputMapper>();
     NotifyMotionArgs args;
 
@@ -6220,7 +5664,7 @@
 
     // Orientation 90, Rotation 0.
     clearViewports();
-    prepareDisplay(DISPLAY_ORIENTATION_0);
+    prepareDisplay(ui::ROTATION_0);
     processDown(mapper, RAW_X_MAX - toRotatedRawX(75) + RAW_X_MIN, toRotatedRawY(50));
     processSync(mapper);
 
@@ -6234,7 +5678,7 @@
 
     // Orientation 90, Rotation 90.
     clearViewports();
-    prepareDisplay(DISPLAY_ORIENTATION_90);
+    prepareDisplay(ui::ROTATION_90);
     processDown(mapper, toRotatedRawX(50), toRotatedRawY(75));
     processSync(mapper);
 
@@ -6248,7 +5692,7 @@
 
     // Orientation 90, Rotation 180.
     clearViewports();
-    prepareDisplay(DISPLAY_ORIENTATION_180);
+    prepareDisplay(ui::ROTATION_180);
     processDown(mapper, toRotatedRawX(75), RAW_Y_MAX - toRotatedRawY(50) + RAW_Y_MIN);
     processSync(mapper);
 
@@ -6262,7 +5706,7 @@
 
     // Orientation 90, Rotation 270.
     clearViewports();
-    prepareDisplay(DISPLAY_ORIENTATION_270);
+    prepareDisplay(ui::ROTATION_270);
     processDown(mapper, RAW_X_MAX - toRotatedRawX(50) + RAW_X_MIN,
                 RAW_Y_MAX - toRotatedRawY(75) + RAW_Y_MIN);
     processSync(mapper);
@@ -6278,7 +5722,7 @@
 
 TEST_F(SingleTouchInputMapperTest, Process_AllAxes_DefaultCalibration) {
     addConfigurationProperty("touch.deviceType", "touchScreen");
-    prepareDisplay(DISPLAY_ORIENTATION_0);
+    prepareDisplay(ui::ROTATION_0);
     prepareButtons();
     prepareAxes(POSITION | PRESSURE | TOOL | DISTANCE | TILT);
     SingleTouchInputMapper& mapper = addMapperAndConfigure<SingleTouchInputMapper>();
@@ -6322,7 +5766,7 @@
 
 TEST_F(SingleTouchInputMapperTest, Process_XYAxes_AffineCalibration) {
     addConfigurationProperty("touch.deviceType", "touchScreen");
-    prepareDisplay(DISPLAY_ORIENTATION_0);
+    prepareDisplay(ui::ROTATION_0);
     prepareLocationCalibration();
     prepareButtons();
     prepareAxes(POSITION);
@@ -6345,7 +5789,7 @@
 
 TEST_F(SingleTouchInputMapperTest, Process_ShouldHandleAllButtons) {
     addConfigurationProperty("touch.deviceType", "touchScreen");
-    prepareDisplay(DISPLAY_ORIENTATION_0);
+    prepareDisplay(ui::ROTATION_0);
     prepareButtons();
     prepareAxes(POSITION);
     SingleTouchInputMapper& mapper = addMapperAndConfigure<SingleTouchInputMapper>();
@@ -6588,7 +6032,7 @@
 
 TEST_F(SingleTouchInputMapperTest, Process_ShouldHandleAllToolTypes) {
     addConfigurationProperty("touch.deviceType", "touchScreen");
-    prepareDisplay(DISPLAY_ORIENTATION_0);
+    prepareDisplay(ui::ROTATION_0);
     prepareButtons();
     prepareAxes(POSITION);
     SingleTouchInputMapper& mapper = addMapperAndConfigure<SingleTouchInputMapper>();
@@ -6723,7 +6167,7 @@
 
 TEST_F(SingleTouchInputMapperTest, Process_WhenBtnTouchPresent_HoversIfItsValueIsZero) {
     addConfigurationProperty("touch.deviceType", "touchScreen");
-    prepareDisplay(DISPLAY_ORIENTATION_0);
+    prepareDisplay(ui::ROTATION_0);
     prepareButtons();
     prepareAxes(POSITION);
     mFakeEventHub->addKey(EVENTHUB_ID, BTN_TOOL_FINGER, 0, AKEYCODE_UNKNOWN, 0);
@@ -6795,7 +6239,7 @@
 
 TEST_F(SingleTouchInputMapperTest, Process_WhenAbsPressureIsPresent_HoversIfItsValueIsZero) {
     addConfigurationProperty("touch.deviceType", "touchScreen");
-    prepareDisplay(DISPLAY_ORIENTATION_0);
+    prepareDisplay(ui::ROTATION_0);
     prepareButtons();
     prepareAxes(POSITION | PRESSURE);
     SingleTouchInputMapper& mapper = addMapperAndConfigure<SingleTouchInputMapper>();
@@ -6864,9 +6308,31 @@
             toDisplayX(150), toDisplayY(250), 0, 0, 0, 0, 0, 0, 0, 0));
 }
 
+TEST_F(SingleTouchInputMapperTest, Reset_CancelsOngoingGesture) {
+    addConfigurationProperty("touch.deviceType", "touchScreen");
+    prepareDisplay(ui::ROTATION_0);
+    prepareButtons();
+    prepareAxes(POSITION | PRESSURE);
+    SingleTouchInputMapper& mapper = addMapperAndConfigure<SingleTouchInputMapper>();
+
+    // Touch down.
+    processDown(mapper, 100, 200);
+    processPressure(mapper, 1);
+    processSync(mapper);
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+            WithMotionAction(AMOTION_EVENT_ACTION_DOWN)));
+
+    // Reset the mapper. This should cancel the ongoing gesture.
+    resetMapper(mapper, ARBITRARY_TIME);
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+            WithMotionAction(AMOTION_EVENT_ACTION_CANCEL)));
+
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled());
+}
+
 TEST_F(SingleTouchInputMapperTest, Reset_RecreatesTouchState) {
     addConfigurationProperty("touch.deviceType", "touchScreen");
-    prepareDisplay(DISPLAY_ORIENTATION_0);
+    prepareDisplay(ui::ROTATION_0);
     prepareButtons();
     prepareAxes(POSITION | PRESSURE);
     SingleTouchInputMapper& mapper = addMapperAndConfigure<SingleTouchInputMapper>();
@@ -6878,8 +6344,9 @@
     mFakeEventHub->setScanCodeState(EVENTHUB_ID, BTN_TOUCH, 1);
 
     // Reset the mapper. When the mapper is reset, we expect it to attempt to recreate the touch
-    // state by reading the current axis values.
-    std::list<NotifyArgs> unused = mapper.reset(ARBITRARY_TIME);
+    // state by reading the current axis values. Since there was no ongoing gesture, calling reset
+    // does not generate any events.
+    resetMapper(mapper, ARBITRARY_TIME);
 
     // Send a sync to simulate an empty touch frame where nothing changes. The mapper should use
     // the recreated touch state to generate a down event.
@@ -6893,7 +6360,7 @@
 TEST_F(SingleTouchInputMapperTest,
        Process_WhenViewportDisplayIdChanged_TouchIsCanceledAndDeviceIsReset) {
     addConfigurationProperty("touch.deviceType", "touchScreen");
-    prepareDisplay(DISPLAY_ORIENTATION_0);
+    prepareDisplay(ui::ROTATION_0);
     prepareButtons();
     prepareAxes(POSITION);
     SingleTouchInputMapper& mapper = addMapperAndConfigure<SingleTouchInputMapper>();
@@ -6921,7 +6388,7 @@
 TEST_F(SingleTouchInputMapperTest,
        Process_WhenViewportActiveStatusChanged_TouchIsCanceledAndDeviceIsReset) {
     addConfigurationProperty("touch.deviceType", "touchScreen");
-    prepareDisplay(DISPLAY_ORIENTATION_0);
+    prepareDisplay(ui::ROTATION_0);
     prepareButtons();
     prepareAxes(POSITION);
     SingleTouchInputMapper& mapper = addMapperAndConfigure<SingleTouchInputMapper>();
@@ -6979,6 +6446,44 @@
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasNotCalled());
 }
 
+TEST_F(SingleTouchInputMapperTest, ButtonIsReleasedOnTouchUp) {
+    addConfigurationProperty("touch.deviceType", "touchScreen");
+    prepareDisplay(ui::ROTATION_0);
+    prepareButtons();
+    prepareAxes(POSITION);
+    SingleTouchInputMapper& mapper = addMapperAndConfigure<SingleTouchInputMapper>();
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled());
+
+    // Press a stylus button.
+    processKey(mapper, BTN_STYLUS, 1);
+    processSync(mapper);
+
+    // Start a touch gesture and ensure the BUTTON_PRESS event is generated.
+    processDown(mapper, 100, 200);
+    processSync(mapper);
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+                  WithCoords(toDisplayX(100), toDisplayY(200)),
+                  WithButtonState(AMOTION_EVENT_BUTTON_STYLUS_PRIMARY))));
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
+                  WithCoords(toDisplayX(100), toDisplayY(200)),
+                  WithButtonState(AMOTION_EVENT_BUTTON_STYLUS_PRIMARY))));
+
+    // Release the touch gesture. Ensure that the BUTTON_RELEASE event is generated even though
+    // the button has not actually been released, since there will be no pointers through which the
+    // button state can be reported. The event is generated at the location of the pointer before
+    // it went up.
+    processUp(mapper);
+    processSync(mapper);
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
+                  WithCoords(toDisplayX(100), toDisplayY(200)), WithButtonState(0))));
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                  WithCoords(toDisplayX(100), toDisplayY(200)), WithButtonState(0))));
+}
+
 // --- TouchDisplayProjectionTest ---
 
 class TouchDisplayProjectionTest : public SingleTouchInputMapperTest {
@@ -6986,27 +6491,25 @@
     // The values inside DisplayViewport are expected to be pre-rotated. This updates the current
     // DisplayViewport to pre-rotate the values. The viewport's physical display will be set to the
     // rotated equivalent of the given un-rotated physical display bounds.
-    void configurePhysicalDisplay(int32_t orientation, Rect naturalPhysicalDisplay) {
+    void configurePhysicalDisplay(ui::Rotation orientation, Rect naturalPhysicalDisplay) {
         uint32_t inverseRotationFlags;
         auto width = DISPLAY_WIDTH;
         auto height = DISPLAY_HEIGHT;
         switch (orientation) {
-            case DISPLAY_ORIENTATION_90:
+            case ui::ROTATION_90:
                 inverseRotationFlags = ui::Transform::ROT_270;
                 std::swap(width, height);
                 break;
-            case DISPLAY_ORIENTATION_180:
+            case ui::ROTATION_180:
                 inverseRotationFlags = ui::Transform::ROT_180;
                 break;
-            case DISPLAY_ORIENTATION_270:
+            case ui::ROTATION_270:
                 inverseRotationFlags = ui::Transform::ROT_90;
                 std::swap(width, height);
                 break;
-            case DISPLAY_ORIENTATION_0:
+            case ui::ROTATION_0:
                 inverseRotationFlags = ui::Transform::ROT_0;
                 break;
-            default:
-                FAIL() << "Invalid orientation: " << orientation;
         }
 
         const ui::Transform rotation(inverseRotationFlags, width, height);
@@ -7050,7 +6553,7 @@
 
 TEST_F(TouchDisplayProjectionTest, IgnoresTouchesOutsidePhysicalDisplay) {
     addConfigurationProperty("touch.deviceType", "touchScreen");
-    prepareDisplay(DISPLAY_ORIENTATION_0);
+    prepareDisplay(ui::ROTATION_0);
 
     prepareButtons();
     prepareAxes(POSITION);
@@ -7065,8 +6568,7 @@
     static const std::array<Point, 6> kPointsOutsidePhysicalDisplay{
             {{-10, -10}, {0, 0}, {5, 100}, {50, 15}, {75, 100}, {50, 165}}};
 
-    for (auto orientation : {DISPLAY_ORIENTATION_0, DISPLAY_ORIENTATION_90, DISPLAY_ORIENTATION_180,
-                             DISPLAY_ORIENTATION_270}) {
+    for (auto orientation : {ui::ROTATION_0, ui::ROTATION_90, ui::ROTATION_180, ui::ROTATION_270}) {
         configurePhysicalDisplay(orientation, kPhysicalDisplay);
 
         // Touches outside the physical display should be ignored, and should not generate any
@@ -7086,7 +6588,7 @@
 
 TEST_F(TouchDisplayProjectionTest, EmitsTouchDownAfterEnteringPhysicalDisplay) {
     addConfigurationProperty("touch.deviceType", "touchScreen");
-    prepareDisplay(DISPLAY_ORIENTATION_0);
+    prepareDisplay(ui::ROTATION_0);
 
     prepareButtons();
     prepareAxes(POSITION);
@@ -7099,8 +6601,7 @@
     // points (10, 20) and (70, 160) inside the display space, which is of the size 400 x 800.
     static const Rect kPhysicalDisplay{10, 20, 70, 160};
 
-    for (auto orientation : {DISPLAY_ORIENTATION_0, DISPLAY_ORIENTATION_90, DISPLAY_ORIENTATION_180,
-                             DISPLAY_ORIENTATION_270}) {
+    for (auto orientation : {ui::ROTATION_0, ui::ROTATION_90, ui::ROTATION_180, ui::ROTATION_270}) {
         configurePhysicalDisplay(orientation, kPhysicalDisplay);
 
         // Touches that start outside the physical display should be ignored until it enters the
@@ -7145,6 +6646,360 @@
     }
 }
 
+// --- ExternalStylusFusionTest ---
+
+class ExternalStylusFusionTest : public SingleTouchInputMapperTest {
+public:
+    SingleTouchInputMapper& initializeInputMapperWithExternalStylus() {
+        addConfigurationProperty("touch.deviceType", "touchScreen");
+        prepareDisplay(ui::ROTATION_0);
+        prepareButtons();
+        prepareAxes(POSITION);
+        auto& mapper = addMapperAndConfigure<SingleTouchInputMapper>();
+
+        mStylusState.when = ARBITRARY_TIME;
+        mStylusState.pressure = 0.f;
+        mStylusState.toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS;
+        mReader->getContext()->setExternalStylusDevices({mExternalStylusDeviceInfo});
+        configureDevice(InputReaderConfiguration::CHANGE_EXTERNAL_STYLUS_PRESENCE);
+        processExternalStylusState(mapper);
+        return mapper;
+    }
+
+    std::list<NotifyArgs> processExternalStylusState(InputMapper& mapper) {
+        std::list<NotifyArgs> generatedArgs = mapper.updateExternalStylusState(mStylusState);
+        for (const NotifyArgs& args : generatedArgs) {
+            mFakeListener->notify(args);
+        }
+        // Loop the reader to flush the input listener queue.
+        mReader->loopOnce();
+        return generatedArgs;
+    }
+
+protected:
+    StylusState mStylusState{};
+    static constexpr uint32_t EXPECTED_SOURCE =
+            AINPUT_SOURCE_TOUCHSCREEN | AINPUT_SOURCE_BLUETOOTH_STYLUS;
+
+    void testStartFusedStylusGesture(SingleTouchInputMapper& mapper) {
+        auto toolTypeSource =
+                AllOf(WithSource(EXPECTED_SOURCE), WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS));
+
+        // The first pointer is withheld.
+        processDown(mapper, 100, 200);
+        processSync(mapper);
+        ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled());
+        ASSERT_NO_FATAL_FAILURE(mReader->getContext()->assertTimeoutWasRequested(
+                ARBITRARY_TIME + EXTERNAL_STYLUS_DATA_TIMEOUT));
+
+        // The external stylus reports pressure. The withheld finger pointer is released as a
+        // stylus.
+        mStylusState.pressure = 1.f;
+        processExternalStylusState(mapper);
+        ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+                AllOf(toolTypeSource, WithMotionAction(AMOTION_EVENT_ACTION_DOWN))));
+        ASSERT_NO_FATAL_FAILURE(mReader->getContext()->assertTimeoutWasNotRequested());
+
+        // Subsequent pointer events are not withheld.
+        processMove(mapper, 101, 201);
+        processSync(mapper);
+        ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+                AllOf(toolTypeSource, WithMotionAction(AMOTION_EVENT_ACTION_MOVE))));
+
+        ASSERT_NO_FATAL_FAILURE(mReader->getContext()->assertTimeoutWasNotRequested());
+        ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled());
+    }
+
+    void testSuccessfulFusionGesture(SingleTouchInputMapper& mapper) {
+        ASSERT_NO_FATAL_FAILURE(testStartFusedStylusGesture(mapper));
+
+        // Releasing the touch pointer ends the gesture.
+        processUp(mapper);
+        processSync(mapper);
+        ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithSource(EXPECTED_SOURCE),
+                      WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS))));
+
+        mStylusState.pressure = 0.f;
+        processExternalStylusState(mapper);
+        ASSERT_NO_FATAL_FAILURE(mReader->getContext()->assertTimeoutWasNotRequested());
+        ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled());
+    }
+
+    void testUnsuccessfulFusionGesture(SingleTouchInputMapper& mapper) {
+        auto toolTypeSource =
+                AllOf(WithSource(EXPECTED_SOURCE), WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER));
+
+        // The first pointer is withheld when an external stylus is connected,
+        // and a timeout is requested.
+        processDown(mapper, 100, 200);
+        processSync(mapper);
+        ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled());
+        ASSERT_NO_FATAL_FAILURE(mReader->getContext()->assertTimeoutWasRequested(
+                ARBITRARY_TIME + EXTERNAL_STYLUS_DATA_TIMEOUT));
+
+        // If the timeout expires early, it is requested again.
+        handleTimeout(mapper, ARBITRARY_TIME + 1);
+        ASSERT_NO_FATAL_FAILURE(mReader->getContext()->assertTimeoutWasRequested(
+                ARBITRARY_TIME + EXTERNAL_STYLUS_DATA_TIMEOUT));
+
+        // When the timeout expires, the withheld touch is released as a finger pointer.
+        handleTimeout(mapper, ARBITRARY_TIME + EXTERNAL_STYLUS_DATA_TIMEOUT);
+        ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+                AllOf(toolTypeSource, WithMotionAction(AMOTION_EVENT_ACTION_DOWN))));
+
+        // Subsequent pointer events are not withheld.
+        processMove(mapper, 101, 201);
+        processSync(mapper);
+        ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+                AllOf(toolTypeSource, WithMotionAction(AMOTION_EVENT_ACTION_MOVE))));
+        processUp(mapper);
+        processSync(mapper);
+        ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+                AllOf(toolTypeSource, WithMotionAction(AMOTION_EVENT_ACTION_UP))));
+
+        ASSERT_NO_FATAL_FAILURE(mReader->getContext()->assertTimeoutWasNotRequested());
+        ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled());
+    }
+
+private:
+    InputDeviceInfo mExternalStylusDeviceInfo{};
+};
+
+TEST_F(ExternalStylusFusionTest, UsesBluetoothStylusSource) {
+    SingleTouchInputMapper& mapper = initializeInputMapperWithExternalStylus();
+    ASSERT_EQ(EXPECTED_SOURCE, mapper.getSources());
+}
+
+TEST_F(ExternalStylusFusionTest, UnsuccessfulFusion) {
+    SingleTouchInputMapper& mapper = initializeInputMapperWithExternalStylus();
+    ASSERT_NO_FATAL_FAILURE(testUnsuccessfulFusionGesture(mapper));
+}
+
+TEST_F(ExternalStylusFusionTest, SuccessfulFusion_TouchFirst) {
+    SingleTouchInputMapper& mapper = initializeInputMapperWithExternalStylus();
+    ASSERT_NO_FATAL_FAILURE(testSuccessfulFusionGesture(mapper));
+}
+
+// Test a successful stylus fusion gesture where the pressure is reported by the external
+// before the touch is reported by the touchscreen.
+TEST_F(ExternalStylusFusionTest, SuccessfulFusion_PressureFirst) {
+    SingleTouchInputMapper& mapper = initializeInputMapperWithExternalStylus();
+    auto toolTypeSource =
+            AllOf(WithSource(EXPECTED_SOURCE), WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS));
+
+    // The external stylus reports pressure first. It is ignored for now.
+    mStylusState.pressure = 1.f;
+    processExternalStylusState(mapper);
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled());
+    ASSERT_NO_FATAL_FAILURE(mReader->getContext()->assertTimeoutWasNotRequested());
+
+    // When the touch goes down afterwards, it is reported as a stylus pointer.
+    processDown(mapper, 100, 200);
+    processSync(mapper);
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+            AllOf(toolTypeSource, WithMotionAction(AMOTION_EVENT_ACTION_DOWN))));
+    ASSERT_NO_FATAL_FAILURE(mReader->getContext()->assertTimeoutWasNotRequested());
+
+    processMove(mapper, 101, 201);
+    processSync(mapper);
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+            AllOf(toolTypeSource, WithMotionAction(AMOTION_EVENT_ACTION_MOVE))));
+    processUp(mapper);
+    processSync(mapper);
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+            AllOf(toolTypeSource, WithMotionAction(AMOTION_EVENT_ACTION_UP))));
+
+    ASSERT_NO_FATAL_FAILURE(mReader->getContext()->assertTimeoutWasNotRequested());
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled());
+}
+
+TEST_F(ExternalStylusFusionTest, FusionIsRepeatedForEachNewGesture) {
+    SingleTouchInputMapper& mapper = initializeInputMapperWithExternalStylus();
+
+    ASSERT_NO_FATAL_FAILURE(testSuccessfulFusionGesture(mapper));
+    ASSERT_NO_FATAL_FAILURE(testUnsuccessfulFusionGesture(mapper));
+
+    ASSERT_NO_FATAL_FAILURE(testSuccessfulFusionGesture(mapper));
+    ASSERT_NO_FATAL_FAILURE(testSuccessfulFusionGesture(mapper));
+    ASSERT_NO_FATAL_FAILURE(testUnsuccessfulFusionGesture(mapper));
+    ASSERT_NO_FATAL_FAILURE(testUnsuccessfulFusionGesture(mapper));
+}
+
+TEST_F(ExternalStylusFusionTest, FusedPointerReportsPressureChanges) {
+    SingleTouchInputMapper& mapper = initializeInputMapperWithExternalStylus();
+    auto toolTypeSource =
+            AllOf(WithSource(EXPECTED_SOURCE), WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS));
+
+    mStylusState.pressure = 0.8f;
+    processExternalStylusState(mapper);
+    processDown(mapper, 100, 200);
+    processSync(mapper);
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+            AllOf(toolTypeSource, WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+                  WithPressure(0.8f))));
+    ASSERT_NO_FATAL_FAILURE(mReader->getContext()->assertTimeoutWasNotRequested());
+
+    // The external stylus reports a pressure change. We wait for some time for a touch event.
+    mStylusState.pressure = 0.6f;
+    processExternalStylusState(mapper);
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled());
+    ASSERT_NO_FATAL_FAILURE(
+            mReader->getContext()->assertTimeoutWasRequested(ARBITRARY_TIME + TOUCH_DATA_TIMEOUT));
+
+    // If a touch is reported within the timeout, it reports the updated pressure.
+    processMove(mapper, 101, 201);
+    processSync(mapper);
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+            AllOf(toolTypeSource, WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+                  WithPressure(0.6f))));
+    ASSERT_NO_FATAL_FAILURE(mReader->getContext()->assertTimeoutWasNotRequested());
+
+    // There is another pressure change.
+    mStylusState.pressure = 0.5f;
+    processExternalStylusState(mapper);
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled());
+    ASSERT_NO_FATAL_FAILURE(
+            mReader->getContext()->assertTimeoutWasRequested(ARBITRARY_TIME + TOUCH_DATA_TIMEOUT));
+
+    // If a touch is not reported within the timeout, a move event is generated to report
+    // the new pressure.
+    handleTimeout(mapper, ARBITRARY_TIME + TOUCH_DATA_TIMEOUT);
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+            AllOf(toolTypeSource, WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+                  WithPressure(0.5f))));
+
+    // If a zero pressure is reported before the touch goes up, the previous pressure value is
+    // repeated indefinitely.
+    mStylusState.pressure = 0.0f;
+    processExternalStylusState(mapper);
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled());
+    ASSERT_NO_FATAL_FAILURE(
+            mReader->getContext()->assertTimeoutWasRequested(ARBITRARY_TIME + TOUCH_DATA_TIMEOUT));
+    processMove(mapper, 102, 202);
+    processSync(mapper);
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+            AllOf(toolTypeSource, WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+                  WithPressure(0.5f))));
+    processMove(mapper, 103, 203);
+    processSync(mapper);
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+            AllOf(toolTypeSource, WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+                  WithPressure(0.5f))));
+
+    processUp(mapper);
+    processSync(mapper);
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithSource(EXPECTED_SOURCE),
+                  WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS))));
+
+    ASSERT_NO_FATAL_FAILURE(mReader->getContext()->assertTimeoutWasNotRequested());
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled());
+}
+
+TEST_F(ExternalStylusFusionTest, FusedPointerReportsToolTypeChanges) {
+    SingleTouchInputMapper& mapper = initializeInputMapperWithExternalStylus();
+    auto source = WithSource(EXPECTED_SOURCE);
+
+    mStylusState.pressure = 1.f;
+    mStylusState.toolType = AMOTION_EVENT_TOOL_TYPE_ERASER;
+    processExternalStylusState(mapper);
+    processDown(mapper, 100, 200);
+    processSync(mapper);
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+            AllOf(source, WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+                  WithToolType(AMOTION_EVENT_TOOL_TYPE_ERASER))));
+    ASSERT_NO_FATAL_FAILURE(mReader->getContext()->assertTimeoutWasNotRequested());
+
+    // The external stylus reports a tool change. We wait for some time for a touch event.
+    mStylusState.toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS;
+    processExternalStylusState(mapper);
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled());
+    ASSERT_NO_FATAL_FAILURE(
+            mReader->getContext()->assertTimeoutWasRequested(ARBITRARY_TIME + TOUCH_DATA_TIMEOUT));
+
+    // If a touch is reported within the timeout, it reports the updated pressure.
+    processMove(mapper, 101, 201);
+    processSync(mapper);
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+            AllOf(source, WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+                  WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS))));
+    ASSERT_NO_FATAL_FAILURE(mReader->getContext()->assertTimeoutWasNotRequested());
+
+    // There is another tool type change.
+    mStylusState.toolType = AMOTION_EVENT_TOOL_TYPE_FINGER;
+    processExternalStylusState(mapper);
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled());
+    ASSERT_NO_FATAL_FAILURE(
+            mReader->getContext()->assertTimeoutWasRequested(ARBITRARY_TIME + TOUCH_DATA_TIMEOUT));
+
+    // If a touch is not reported within the timeout, a move event is generated to report
+    // the new tool type.
+    handleTimeout(mapper, ARBITRARY_TIME + TOUCH_DATA_TIMEOUT);
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+            AllOf(source, WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+                  WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER))));
+
+    processUp(mapper);
+    processSync(mapper);
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+            AllOf(source, WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                  WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER))));
+
+    ASSERT_NO_FATAL_FAILURE(mReader->getContext()->assertTimeoutWasNotRequested());
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled());
+}
+
+TEST_F(ExternalStylusFusionTest, FusedPointerReportsButtons) {
+    SingleTouchInputMapper& mapper = initializeInputMapperWithExternalStylus();
+    auto toolTypeSource =
+            AllOf(WithSource(EXPECTED_SOURCE), WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS));
+
+    ASSERT_NO_FATAL_FAILURE(testStartFusedStylusGesture(mapper));
+
+    // The external stylus reports a button change. We wait for some time for a touch event.
+    mStylusState.buttons = AMOTION_EVENT_BUTTON_STYLUS_PRIMARY;
+    processExternalStylusState(mapper);
+    ASSERT_NO_FATAL_FAILURE(
+            mReader->getContext()->assertTimeoutWasRequested(ARBITRARY_TIME + TOUCH_DATA_TIMEOUT));
+
+    // If a touch is reported within the timeout, it reports the updated button state.
+    processMove(mapper, 101, 201);
+    processSync(mapper);
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+            AllOf(toolTypeSource, WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+                  WithButtonState(AMOTION_EVENT_BUTTON_STYLUS_PRIMARY))));
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+            AllOf(toolTypeSource, WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
+                  WithButtonState(AMOTION_EVENT_BUTTON_STYLUS_PRIMARY))));
+    ASSERT_NO_FATAL_FAILURE(mReader->getContext()->assertTimeoutWasNotRequested());
+
+    // The button is now released.
+    mStylusState.buttons = 0;
+    processExternalStylusState(mapper);
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled());
+    ASSERT_NO_FATAL_FAILURE(
+            mReader->getContext()->assertTimeoutWasRequested(ARBITRARY_TIME + TOUCH_DATA_TIMEOUT));
+
+    // If a touch is not reported within the timeout, a move event is generated to report
+    // the new button state.
+    handleTimeout(mapper, ARBITRARY_TIME + TOUCH_DATA_TIMEOUT);
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+            AllOf(toolTypeSource, WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
+                  WithButtonState(0))));
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+            AllOf(toolTypeSource, WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+                  WithButtonState(0))));
+
+    processUp(mapper);
+    processSync(mapper);
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+            AllOf(toolTypeSource, WithMotionAction(AMOTION_EVENT_ACTION_UP), WithButtonState(0))));
+
+    ASSERT_NO_FATAL_FAILURE(mReader->getContext()->assertTimeoutWasNotRequested());
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled());
+}
+
 // --- MultiTouchInputMapperTest ---
 
 class MultiTouchInputMapperTest : public TouchInputMapperTest {
@@ -7163,8 +7018,10 @@
     void processSlot(MultiTouchInputMapper& mapper, int32_t slot);
     void processToolType(MultiTouchInputMapper& mapper, int32_t toolType);
     void processKey(MultiTouchInputMapper& mapper, int32_t code, int32_t value);
+    void processHidUsage(MultiTouchInputMapper& mapper, int32_t usageCode, int32_t value);
     void processMTSync(MultiTouchInputMapper& mapper);
-    void processSync(MultiTouchInputMapper& mapper);
+    void processSync(MultiTouchInputMapper& mapper, nsecs_t eventTime = ARBITRARY_TIME,
+                     nsecs_t readTime = READ_TIME);
 };
 
 void MultiTouchInputMapperTest::prepareAxes(int axes) {
@@ -7267,17 +7124,24 @@
     process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, code, value);
 }
 
+void MultiTouchInputMapperTest::processHidUsage(MultiTouchInputMapper& mapper, int32_t usageCode,
+                                                int32_t value) {
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_MSC, MSC_SCAN, usageCode);
+    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_UNKNOWN, value);
+}
+
 void MultiTouchInputMapperTest::processMTSync(MultiTouchInputMapper& mapper) {
     process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_MT_REPORT, 0);
 }
 
-void MultiTouchInputMapperTest::processSync(MultiTouchInputMapper& mapper) {
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
+void MultiTouchInputMapperTest::processSync(MultiTouchInputMapper& mapper, nsecs_t eventTime,
+                                            nsecs_t readTime) {
+    process(mapper, eventTime, readTime, EV_SYN, SYN_REPORT, 0);
 }
 
 TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithoutTrackingIds) {
     addConfigurationProperty("touch.deviceType", "touchScreen");
-    prepareDisplay(DISPLAY_ORIENTATION_0);
+    prepareDisplay(ui::ROTATION_0);
     prepareAxes(POSITION);
     prepareVirtualKeys();
     MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
@@ -7549,7 +7413,7 @@
 
 TEST_F(MultiTouchInputMapperTest, AxisResolution_IsPopulated) {
     addConfigurationProperty("touch.deviceType", "touchScreen");
-    prepareDisplay(DISPLAY_ORIENTATION_0);
+    prepareDisplay(ui::ROTATION_0);
 
     mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_POSITION_X, RAW_X_MIN, RAW_X_MAX, /*flat*/ 0,
                                    /*fuzz*/ 0, /*resolution*/ 10);
@@ -7579,7 +7443,7 @@
 
 TEST_F(MultiTouchInputMapperTest, TouchMajorAndMinorAxes_DoNotAppearIfNotSupported) {
     addConfigurationProperty("touch.deviceType", "touchScreen");
-    prepareDisplay(DISPLAY_ORIENTATION_0);
+    prepareDisplay(ui::ROTATION_0);
 
     mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_POSITION_X, RAW_X_MIN, RAW_X_MAX, /*flat*/ 0,
                                    /*fuzz*/ 0, /*resolution*/ 10);
@@ -7600,7 +7464,7 @@
 
 TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithTrackingIds) {
     addConfigurationProperty("touch.deviceType", "touchScreen");
-    prepareDisplay(DISPLAY_ORIENTATION_0);
+    prepareDisplay(ui::ROTATION_0);
     prepareAxes(POSITION | ID);
     prepareVirtualKeys();
     MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
@@ -7771,7 +7635,7 @@
 
 TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithSlots) {
     addConfigurationProperty("touch.deviceType", "touchScreen");
-    prepareDisplay(DISPLAY_ORIENTATION_0);
+    prepareDisplay(ui::ROTATION_0);
     prepareAxes(POSITION | ID | SLOT);
     prepareVirtualKeys();
     MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
@@ -7937,7 +7801,7 @@
 
 TEST_F(MultiTouchInputMapperTest, Process_AllAxes_WithDefaultCalibration) {
     addConfigurationProperty("touch.deviceType", "touchScreen");
-    prepareDisplay(DISPLAY_ORIENTATION_0);
+    prepareDisplay(ui::ROTATION_0);
     prepareAxes(POSITION | TOUCH | TOOL | PRESSURE | ORIENTATION | ID | MINOR | DISTANCE);
     MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
 
@@ -7986,7 +7850,7 @@
 
 TEST_F(MultiTouchInputMapperTest, Process_TouchAndToolAxes_GeometricCalibration) {
     addConfigurationProperty("touch.deviceType", "touchScreen");
-    prepareDisplay(DISPLAY_ORIENTATION_0);
+    prepareDisplay(ui::ROTATION_0);
     prepareAxes(POSITION | TOUCH | TOOL | MINOR);
     addConfigurationProperty("touch.size.calibration", "geometric");
     MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
@@ -8023,7 +7887,7 @@
 
 TEST_F(MultiTouchInputMapperTest, Process_TouchAndToolAxes_SummedLinearCalibration) {
     addConfigurationProperty("touch.deviceType", "touchScreen");
-    prepareDisplay(DISPLAY_ORIENTATION_0);
+    prepareDisplay(ui::ROTATION_0);
     prepareAxes(POSITION | TOUCH | TOOL);
     addConfigurationProperty("touch.size.calibration", "diameter");
     addConfigurationProperty("touch.size.scale", "10");
@@ -8074,7 +7938,7 @@
 
 TEST_F(MultiTouchInputMapperTest, Process_TouchAndToolAxes_AreaCalibration) {
     addConfigurationProperty("touch.deviceType", "touchScreen");
-    prepareDisplay(DISPLAY_ORIENTATION_0);
+    prepareDisplay(ui::ROTATION_0);
     prepareAxes(POSITION | TOUCH | TOOL);
     addConfigurationProperty("touch.size.calibration", "area");
     addConfigurationProperty("touch.size.scale", "43");
@@ -8107,7 +7971,7 @@
 
 TEST_F(MultiTouchInputMapperTest, Process_PressureAxis_AmplitudeCalibration) {
     addConfigurationProperty("touch.deviceType", "touchScreen");
-    prepareDisplay(DISPLAY_ORIENTATION_0);
+    prepareDisplay(ui::ROTATION_0);
     prepareAxes(POSITION | PRESSURE);
     addConfigurationProperty("touch.pressure.calibration", "amplitude");
     addConfigurationProperty("touch.pressure.scale", "0.01");
@@ -8141,7 +8005,7 @@
 
 TEST_F(MultiTouchInputMapperTest, Process_ShouldHandleAllButtons) {
     addConfigurationProperty("touch.deviceType", "touchScreen");
-    prepareDisplay(DISPLAY_ORIENTATION_0);
+    prepareDisplay(ui::ROTATION_0);
     prepareAxes(POSITION | ID | SLOT);
     MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
 
@@ -8382,9 +8246,66 @@
     ASSERT_EQ(0, motionArgs.buttonState);
 }
 
+TEST_F(MultiTouchInputMapperTest, Process_ShouldHandleMappedStylusButtons) {
+    addConfigurationProperty("touch.deviceType", "touchScreen");
+    prepareDisplay(ui::ROTATION_0);
+    prepareAxes(POSITION | ID | SLOT);
+    MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
+
+    mFakeEventHub->addKey(EVENTHUB_ID, BTN_A, 0, AKEYCODE_STYLUS_BUTTON_PRIMARY, 0);
+    mFakeEventHub->addKey(EVENTHUB_ID, 0, 0xabcd, AKEYCODE_STYLUS_BUTTON_SECONDARY, 0);
+
+    // Touch down.
+    processId(mapper, 1);
+    processPosition(mapper, 100, 200);
+    processSync(mapper);
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithButtonState(0))));
+
+    // Press and release button mapped to the primary stylus button.
+    processKey(mapper, BTN_A, 1);
+    processSync(mapper);
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+                  WithButtonState(AMOTION_EVENT_BUTTON_STYLUS_PRIMARY))));
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
+                  WithButtonState(AMOTION_EVENT_BUTTON_STYLUS_PRIMARY))));
+
+    processKey(mapper, BTN_A, 0);
+    processSync(mapper);
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE), WithButtonState(0))));
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithButtonState(0))));
+
+    // Press and release the HID usage mapped to the secondary stylus button.
+    processHidUsage(mapper, 0xabcd, 1);
+    processSync(mapper);
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+                  WithButtonState(AMOTION_EVENT_BUTTON_STYLUS_SECONDARY))));
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
+                  WithButtonState(AMOTION_EVENT_BUTTON_STYLUS_SECONDARY))));
+
+    processHidUsage(mapper, 0xabcd, 0);
+    processSync(mapper);
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE), WithButtonState(0))));
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithButtonState(0))));
+
+    // Release touch.
+    processId(mapper, -1);
+    processSync(mapper);
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithButtonState(0))));
+}
+
 TEST_F(MultiTouchInputMapperTest, Process_ShouldHandleAllToolTypes) {
     addConfigurationProperty("touch.deviceType", "touchScreen");
-    prepareDisplay(DISPLAY_ORIENTATION_0);
+    prepareDisplay(ui::ROTATION_0);
     prepareAxes(POSITION | ID | SLOT | TOOL_TYPE);
     MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
 
@@ -8534,7 +8455,7 @@
 
 TEST_F(MultiTouchInputMapperTest, Process_WhenBtnTouchPresent_HoversIfItsValueIsZero) {
     addConfigurationProperty("touch.deviceType", "touchScreen");
-    prepareDisplay(DISPLAY_ORIENTATION_0);
+    prepareDisplay(ui::ROTATION_0);
     prepareAxes(POSITION | ID | SLOT);
     mFakeEventHub->addKey(EVENTHUB_ID, BTN_TOUCH, 0, AKEYCODE_UNKNOWN, 0);
     MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
@@ -8605,7 +8526,7 @@
 
 TEST_F(MultiTouchInputMapperTest, Process_WhenAbsMTPressureIsPresent_HoversIfItsValueIsZero) {
     addConfigurationProperty("touch.deviceType", "touchScreen");
-    prepareDisplay(DISPLAY_ORIENTATION_0);
+    prepareDisplay(ui::ROTATION_0);
     prepareAxes(POSITION | ID | SLOT | PRESSURE);
     MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
 
@@ -8705,7 +8626,7 @@
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled());
 
     // Add viewport for display 1 on hdmi1
-    prepareDisplay(DISPLAY_ORIENTATION_0, hdmi1);
+    prepareDisplay(ui::ROTATION_0, hdmi1);
     // Send a touch event again
     processPosition(mapper, 100, 100);
     processSync(mapper);
@@ -8722,8 +8643,8 @@
 
     mFakePolicy->addInputUniqueIdAssociation(DEVICE_LOCATION, VIRTUAL_DISPLAY_UNIQUE_ID);
 
-    prepareDisplay(DISPLAY_ORIENTATION_0);
-    prepareVirtualDisplay(DISPLAY_ORIENTATION_0);
+    prepareDisplay(ui::ROTATION_0);
+    prepareVirtualDisplay(ui::ROTATION_0);
 
     // Send a touch event
     processPosition(mapper, 100, 100);
@@ -8746,12 +8667,12 @@
     mFakePolicy->setDefaultPointerDisplayId(SECONDARY_DISPLAY_ID);
     prepareSecondaryDisplay(ViewportType::EXTERNAL);
 
-    prepareDisplay(DISPLAY_ORIENTATION_0);
+    prepareDisplay(ui::ROTATION_0);
     prepareAxes(POSITION);
     MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
 
-    // Check source is mouse that would obtain the PointerController.
-    ASSERT_EQ(AINPUT_SOURCE_MOUSE, mapper.getSources());
+    // Check source is a touchpad that would obtain the PointerController.
+    ASSERT_EQ(AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD, mapper.getSources());
 
     NotifyMotionArgs motionArgs;
     processPosition(mapper, 100, 100);
@@ -8770,7 +8691,7 @@
     prepareAxes(POSITION);
     MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
 
-    prepareDisplay(DISPLAY_ORIENTATION_0);
+    prepareDisplay(ui::ROTATION_0);
     process(mapper, 10, 11 /*readTime*/, EV_ABS, ABS_MT_TRACKING_ID, 1);
     process(mapper, 15, 16 /*readTime*/, EV_ABS, ABS_MT_POSITION_X, 100);
     process(mapper, 20, 21 /*readTime*/, EV_ABS, ABS_MT_POSITION_Y, 100);
@@ -8794,9 +8715,9 @@
  */
 TEST_F(MultiTouchInputMapperTest, WhenViewportIsNotActive_TouchesAreDropped) {
     addConfigurationProperty("touch.deviceType", "touchScreen");
-    mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT,
-                                    DISPLAY_ORIENTATION_0, false /*isActive*/, UNIQUE_ID, NO_PORT,
-                                    ViewportType::INTERNAL);
+    // Don't set touch.enableForInactiveViewport to verify the default behavior.
+    mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
+                                    false /*isActive*/, UNIQUE_ID, NO_PORT, ViewportType::INTERNAL);
     configureDevice(InputReaderConfiguration::CHANGE_DISPLAY_INFO);
     prepareAxes(POSITION);
     MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
@@ -8808,11 +8729,32 @@
     mFakeListener->assertNotifyMotionWasNotCalled();
 }
 
+/**
+ * When the viewport is not active (isActive=false) and touch.enableForInactiveViewport is true,
+ * the touch mapper can process the events and the events can be delivered to the listener.
+ */
+TEST_F(MultiTouchInputMapperTest, WhenViewportIsNotActive_TouchesAreProcessed) {
+    addConfigurationProperty("touch.deviceType", "touchScreen");
+    addConfigurationProperty("touch.enableForInactiveViewport", "1");
+    mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
+                                    false /*isActive*/, UNIQUE_ID, NO_PORT, ViewportType::INTERNAL);
+    configureDevice(InputReaderConfiguration::CHANGE_DISPLAY_INFO);
+    prepareAxes(POSITION);
+    MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
+
+    NotifyMotionArgs motionArgs;
+    processPosition(mapper, 100, 100);
+    processSync(mapper);
+
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+    EXPECT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action);
+}
+
 TEST_F(MultiTouchInputMapperTest, Process_DeactivateViewport_AbortTouches) {
     addConfigurationProperty("touch.deviceType", "touchScreen");
-    mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT,
-                                    DISPLAY_ORIENTATION_0, true /*isActive*/, UNIQUE_ID, NO_PORT,
-                                    ViewportType::INTERNAL);
+    addConfigurationProperty("touch.enableForInactiveViewport", "0");
+    mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
+                                    true /*isActive*/, UNIQUE_ID, NO_PORT, ViewportType::INTERNAL);
     std::optional<DisplayViewport> optionalDisplayViewport =
             mFakePolicy->getDisplayViewportByUniqueId(UNIQUE_ID);
     ASSERT_TRUE(optionalDisplayViewport.has_value());
@@ -8906,7 +8848,7 @@
     mFakePolicy->setShowTouches(true);
 
     // Create displays.
-    prepareDisplay(DISPLAY_ORIENTATION_0, hdmi1);
+    prepareDisplay(ui::ROTATION_0, hdmi1);
     prepareSecondaryDisplay(ViewportType::EXTERNAL, hdmi2);
 
     // Default device will reconfigure above, need additional reconfiguration for another device.
@@ -8951,7 +8893,7 @@
 TEST_F(MultiTouchInputMapperTest, VideoFrames_ReceivedByListener) {
     prepareAxes(POSITION);
     addConfigurationProperty("touch.deviceType", "touchScreen");
-    prepareDisplay(DISPLAY_ORIENTATION_0);
+    prepareDisplay(ui::ROTATION_0);
     MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
 
     NotifyMotionArgs motionArgs;
@@ -8982,8 +8924,7 @@
     NotifyMotionArgs motionArgs;
 
     // Test all 4 orientations
-    for (int32_t orientation : {DISPLAY_ORIENTATION_0, DISPLAY_ORIENTATION_90,
-                                DISPLAY_ORIENTATION_180, DISPLAY_ORIENTATION_270}) {
+    for (ui::Rotation orientation : ftl::enum_range<ui::Rotation>()) {
         SCOPED_TRACE("Orientation " + StringPrintf("%i", orientation));
         clearViewports();
         prepareDisplay(orientation);
@@ -9008,8 +8949,7 @@
     NotifyMotionArgs motionArgs;
 
     // Test all 4 orientations
-    for (int32_t orientation : {DISPLAY_ORIENTATION_0, DISPLAY_ORIENTATION_90,
-             DISPLAY_ORIENTATION_180, DISPLAY_ORIENTATION_270}) {
+    for (ui::Rotation orientation : ftl::enum_range<ui::Rotation>()) {
         SCOPED_TRACE("Orientation " + StringPrintf("%i", orientation));
         clearViewports();
         prepareDisplay(orientation);
@@ -9043,7 +8983,7 @@
     std::vector<TouchVideoFrame> frames{frame1, frame2, frame3};
     NotifyMotionArgs motionArgs;
 
-    prepareDisplay(DISPLAY_ORIENTATION_90);
+    prepareDisplay(ui::ROTATION_90);
     mFakeEventHub->setVideoFrames({{EVENTHUB_ID, frames}});
     processPosition(mapper, 100, 200);
     processSync(mapper);
@@ -9066,7 +9006,7 @@
     std::vector<TouchVideoFrame> frames{frame1, frame2, frame3};
     NotifyMotionArgs motionArgs;
 
-    prepareDisplay(DISPLAY_ORIENTATION_90);
+    prepareDisplay(ui::ROTATION_90);
     mFakeEventHub->setVideoFrames({{EVENTHUB_ID, frames}});
     processPosition(mapper, 100, 200);
     processSync(mapper);
@@ -9076,7 +9016,7 @@
         // compared to the display. This is so that when the window transform (which contains the
         // display rotation) is applied later by InputDispatcher, the coordinates end up in the
         // window's coordinate space.
-        frame.rotate(getInverseRotation(DISPLAY_ORIENTATION_90));
+        frame.rotate(getInverseRotation(ui::ROTATION_90));
     });
     ASSERT_EQ(frames, motionArgs.videoFrames);
 }
@@ -9113,7 +9053,7 @@
 
 TEST_F(MultiTouchInputMapperTest, Process_ShouldHandleSingleTouch) {
     addConfigurationProperty("touch.deviceType", "touchScreen");
-    prepareDisplay(DISPLAY_ORIENTATION_0);
+    prepareDisplay(ui::ROTATION_0);
     prepareAxes(POSITION | ID | SLOT | TOOL_TYPE);
     MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
 
@@ -9158,7 +9098,7 @@
  */
 TEST_F(MultiTouchInputMapperTest, Process_ShouldHandlePalmToolType_SinglePointer) {
     addConfigurationProperty("touch.deviceType", "touchScreen");
-    prepareDisplay(DISPLAY_ORIENTATION_0);
+    prepareDisplay(ui::ROTATION_0);
     prepareAxes(POSITION | ID | SLOT | TOOL_TYPE);
     MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
 
@@ -9206,7 +9146,7 @@
  */
 TEST_F(MultiTouchInputMapperTest, Process_ShouldHandlePalmToolType_TwoPointers) {
     addConfigurationProperty("touch.deviceType", "touchScreen");
-    prepareDisplay(DISPLAY_ORIENTATION_0);
+    prepareDisplay(ui::ROTATION_0);
     prepareAxes(POSITION | ID | SLOT | TOOL_TYPE);
     MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
 
@@ -9281,7 +9221,7 @@
  */
 TEST_F(MultiTouchInputMapperTest, Process_ShouldHandlePalmToolType_ShouldCancelWhenAllTouchIsPalm) {
     addConfigurationProperty("touch.deviceType", "touchScreen");
-    prepareDisplay(DISPLAY_ORIENTATION_0);
+    prepareDisplay(ui::ROTATION_0);
     prepareAxes(POSITION | ID | SLOT | TOOL_TYPE);
     MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
 
@@ -9379,7 +9319,7 @@
  */
 TEST_F(MultiTouchInputMapperTest, Process_ShouldHandlePalmToolType_KeepFirstPointer) {
     addConfigurationProperty("touch.deviceType", "touchScreen");
-    prepareDisplay(DISPLAY_ORIENTATION_0);
+    prepareDisplay(ui::ROTATION_0);
     prepareAxes(POSITION | ID | SLOT | TOOL_TYPE);
     MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
 
@@ -9451,7 +9391,7 @@
  */
 TEST_F(MultiTouchInputMapperTest, Process_MultiTouch_WithInvalidTrackingId) {
     addConfigurationProperty("touch.deviceType", "touchScreen");
-    prepareDisplay(DISPLAY_ORIENTATION_0);
+    prepareDisplay(ui::ROTATION_0);
     prepareAxes(POSITION | ID | SLOT | PRESSURE);
     MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
 
@@ -9508,7 +9448,7 @@
 
 TEST_F(MultiTouchInputMapperTest, Reset_PreservesLastTouchState) {
     addConfigurationProperty("touch.deviceType", "touchScreen");
-    prepareDisplay(DISPLAY_ORIENTATION_0);
+    prepareDisplay(ui::ROTATION_0);
     prepareAxes(POSITION | ID | SLOT | PRESSURE);
     MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
 
@@ -9530,9 +9470,10 @@
             mFakeListener->assertNotifyMotionWasCalled(WithMotionAction(ACTION_POINTER_1_DOWN)));
 
     // Reset the mapper. When the mapper is reset, we expect the current multi-touch state to be
-    // preserved. Resetting should not generate any events.
-    std::list<NotifyArgs> unused = mapper.reset(ARBITRARY_TIME);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled());
+    // preserved. Resetting should cancel the ongoing gesture.
+    resetMapper(mapper, ARBITRARY_TIME);
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+            WithMotionAction(AMOTION_EVENT_ACTION_CANCEL)));
 
     // Send a sync to simulate an empty touch frame where nothing changes. The mapper should use
     // the existing touch state to generate a down event.
@@ -9548,7 +9489,7 @@
 
 TEST_F(MultiTouchInputMapperTest, Reset_PreservesLastTouchState_NoPointersDown) {
     addConfigurationProperty("touch.deviceType", "touchScreen");
-    prepareDisplay(DISPLAY_ORIENTATION_0);
+    prepareDisplay(ui::ROTATION_0);
     prepareAxes(POSITION | ID | SLOT | PRESSURE);
     MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
 
@@ -9566,7 +9507,7 @@
 
     // Reset the mapper. When the mapper is reset, we expect it to restore the latest
     // raw state where no pointers are down.
-    std::list<NotifyArgs> unused = mapper.reset(ARBITRARY_TIME);
+    resetMapper(mapper, ARBITRARY_TIME);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled());
 
     // Send an empty sync frame. Since there are no pointers, no events are generated.
@@ -9574,6 +9515,54 @@
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled());
 }
 
+TEST_F(MultiTouchInputMapperTest, StylusSourceIsAddedDynamicallyFromToolType) {
+    addConfigurationProperty("touch.deviceType", "touchScreen");
+    prepareDisplay(ui::ROTATION_0);
+    prepareAxes(POSITION | ID | SLOT | PRESSURE | TOOL_TYPE);
+    MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled());
+
+    // Even if the device supports reporting the ABS_MT_TOOL_TYPE axis, which could give it the
+    // ability to report MT_TOOL_PEN, we do not report the device as coming from a stylus source.
+    // Due to limitations in the evdev protocol, we cannot say for certain that a device is capable
+    // of reporting stylus events just because it supports ABS_MT_TOOL_TYPE.
+    ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, mapper.getSources());
+
+    // However, if the device ever ends up reporting an event with MT_TOOL_PEN, it should be
+    // reported with the stylus source.
+    processId(mapper, FIRST_TRACKING_ID);
+    processToolType(mapper, MT_TOOL_PEN);
+    processPosition(mapper, 100, 200);
+    processPressure(mapper, RAW_PRESSURE_MAX);
+    processSync(mapper);
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+                  WithSource(AINPUT_SOURCE_TOUCHSCREEN | AINPUT_SOURCE_STYLUS),
+                  WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS))));
+
+    // Now that we know the device supports styluses, ensure that the device is re-configured with
+    // the stylus source.
+    ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN | AINPUT_SOURCE_STYLUS, mapper.getSources());
+    {
+        const auto& devices = mReader->getInputDevices();
+        auto deviceInfo =
+                std::find_if(devices.begin(), devices.end(),
+                             [](const InputDeviceInfo& info) { return info.getId() == DEVICE_ID; });
+        LOG_ALWAYS_FATAL_IF(deviceInfo == devices.end(), "Cannot find InputDevice");
+        ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN | AINPUT_SOURCE_STYLUS, deviceInfo->getSources());
+    }
+
+    // Ensure the device was not reset to prevent interruptions of any ongoing gestures.
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasNotCalled());
+
+    processId(mapper, INVALID_TRACKING_ID);
+    processSync(mapper);
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                  WithSource(AINPUT_SOURCE_TOUCHSCREEN | AINPUT_SOURCE_STYLUS),
+                  WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS))));
+}
+
 // --- MultiTouchInputMapperTest_ExternalDevice ---
 
 class MultiTouchInputMapperTest_ExternalDevice : public MultiTouchInputMapperTest {
@@ -9587,7 +9576,7 @@
 TEST_F(MultiTouchInputMapperTest_ExternalDevice, Viewports_Fallback) {
     prepareAxes(POSITION);
     addConfigurationProperty("touch.deviceType", "touchScreen");
-    prepareDisplay(DISPLAY_ORIENTATION_0);
+    prepareDisplay(ui::ROTATION_0);
     MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
 
     ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, mapper.getSources());
@@ -9618,7 +9607,7 @@
     fakePointerController->setButtonState(0);
 
     // prepare device and capture
-    prepareDisplay(DISPLAY_ORIENTATION_0);
+    prepareDisplay(ui::ROTATION_0);
     prepareAxes(POSITION | ID | SLOT);
     mFakeEventHub->addKey(EVENTHUB_ID, BTN_LEFT, 0, AKEYCODE_UNKNOWN, 0);
     mFakeEventHub->addKey(EVENTHUB_ID, BTN_TOUCH, 0, AKEYCODE_UNKNOWN, 0);
@@ -9753,11 +9742,11 @@
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
     ASSERT_EQ(AMOTION_EVENT_ACTION_UP, args.action);
 
-    // non captured touchpad should be a mouse source
+    // A non captured touchpad should have a mouse and touchpad source.
     mFakePolicy->setPointerCapture(false);
     configureDevice(InputReaderConfiguration::CHANGE_POINTER_CAPTURE);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled(&resetArgs));
-    ASSERT_EQ(AINPUT_SOURCE_MOUSE, mapper.getSources());
+    ASSERT_EQ(AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD, mapper.getSources());
 }
 
 TEST_F(MultiTouchInputMapperTest, Process_UnCapturedTouchpadPointer) {
@@ -9768,7 +9757,7 @@
     fakePointerController->setButtonState(0);
 
     // prepare device and capture
-    prepareDisplay(DISPLAY_ORIENTATION_0);
+    prepareDisplay(ui::ROTATION_0);
     prepareAxes(POSITION | ID | SLOT);
     mFakeEventHub->addKey(EVENTHUB_ID, BTN_LEFT, 0, AKEYCODE_UNKNOWN, 0);
     mFakeEventHub->addKey(EVENTHUB_ID, BTN_TOUCH, 0, AKEYCODE_UNKNOWN, 0);
@@ -9809,22 +9798,72 @@
     std::shared_ptr<FakePointerController> fakePointerController =
             std::make_shared<FakePointerController>();
 
-    prepareDisplay(DISPLAY_ORIENTATION_0);
+    prepareDisplay(ui::ROTATION_0);
     prepareAxes(POSITION | ID | SLOT);
     mFakeEventHub->addKey(EVENTHUB_ID, BTN_LEFT, 0, AKEYCODE_UNKNOWN, 0);
     mFakePolicy->setPointerController(fakePointerController);
     mFakePolicy->setPointerCapture(false);
     MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
 
-    // uncaptured touchpad should be a pointer device
-    ASSERT_EQ(AINPUT_SOURCE_MOUSE, mapper.getSources());
+    // An uncaptured touchpad should be a pointer device, with additional touchpad source.
+    ASSERT_EQ(AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD, mapper.getSources());
 
-    // captured touchpad should be a touchpad device
+    // A captured touchpad should just have a touchpad source.
     mFakePolicy->setPointerCapture(true);
     configureDevice(InputReaderConfiguration::CHANGE_POINTER_CAPTURE);
     ASSERT_EQ(AINPUT_SOURCE_TOUCHPAD, mapper.getSources());
 }
 
+// --- BluetoothMultiTouchInputMapperTest ---
+
+class BluetoothMultiTouchInputMapperTest : public MultiTouchInputMapperTest {
+protected:
+    void SetUp() override {
+        InputMapperTest::SetUp(DEVICE_CLASSES | InputDeviceClass::EXTERNAL, BUS_BLUETOOTH);
+    }
+};
+
+TEST_F(BluetoothMultiTouchInputMapperTest, TimestampSmoothening) {
+    addConfigurationProperty("touch.deviceType", "touchScreen");
+    prepareDisplay(ui::ROTATION_0);
+    prepareAxes(POSITION | ID | SLOT | PRESSURE);
+    MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
+
+    nsecs_t kernelEventTime = ARBITRARY_TIME;
+    nsecs_t expectedEventTime = ARBITRARY_TIME;
+    // Touch down.
+    processId(mapper, FIRST_TRACKING_ID);
+    processPosition(mapper, 100, 200);
+    processPressure(mapper, RAW_PRESSURE_MAX);
+    processSync(mapper, ARBITRARY_TIME);
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithEventTime(ARBITRARY_TIME))));
+
+    // 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;
+
+        processPosition(mapper, 101 + i, 201 + i);
+        processSync(mapper, kernelEventTime);
+        ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+                      WithEventTime(expectedEventTime))));
+    }
+
+    // Release the touch.
+    processId(mapper, INVALID_TRACKING_ID);
+    processPressure(mapper, RAW_PRESSURE_MIN);
+    processSync(mapper, ARBITRARY_TIME + ms2ns(50));
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                  WithEventTime(ARBITRARY_TIME + ms2ns(50)))));
+}
+
+// --- MultiTouchPointerModeTest ---
+
 class MultiTouchPointerModeTest : public MultiTouchInputMapperTest {
 protected:
     float mPointerMovementScale;
@@ -9836,7 +9875,7 @@
         fakePointerController->setBounds(0, 0, DISPLAY_WIDTH - 1, DISPLAY_HEIGHT - 1);
         fakePointerController->setPosition(0, 0);
         fakePointerController->setButtonState(0);
-        prepareDisplay(DISPLAY_ORIENTATION_0);
+        prepareDisplay(ui::ROTATION_0);
 
         prepareAxes(POSITION);
         prepareAbsoluteAxisResolution(xAxisResolution, yAxisResolution);
@@ -10131,6 +10170,46 @@
     ASSERT_GT(motionArgs.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_GESTURE_Y_OFFSET), 0);
 }
 
+TEST_F(MultiTouchPointerModeTest, WhenViewportActiveStatusChanged_PointerGestureIsReset) {
+    preparePointerMode(25 /*xResolution*/, 25 /*yResolution*/);
+    mFakeEventHub->addKey(EVENTHUB_ID, BTN_TOOL_PEN, 0, AKEYCODE_UNKNOWN, 0);
+    MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled());
+
+    // Start a stylus gesture.
+    processKey(mapper, BTN_TOOL_PEN, 1);
+    processId(mapper, FIRST_TRACKING_ID);
+    processPosition(mapper, 100, 200);
+    processSync(mapper);
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+                  WithSource(AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_STYLUS),
+                  WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS))));
+    // TODO(b/257078296): Pointer mode generates extra event.
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+                  WithSource(AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_STYLUS),
+                  WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS))));
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled());
+
+    // Make the viewport inactive. This will put the device in disabled mode, and the ongoing stylus
+    // gesture should be disabled.
+    auto viewport = mFakePolicy->getDisplayViewportByType(ViewportType::INTERNAL);
+    viewport->isActive = false;
+    mFakePolicy->updateViewport(*viewport);
+    configureDevice(InputReaderConfiguration::CHANGE_DISPLAY_INFO);
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_CANCEL),
+                  WithSource(AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_STYLUS),
+                  WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS))));
+    // TODO(b/257078296): Pointer mode generates extra event.
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_CANCEL),
+                  WithSource(AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_STYLUS),
+                  WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS))));
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled());
+}
+
 // --- JoystickInputMapperTest ---
 
 class JoystickInputMapperTest : public InputMapperTest {
@@ -10156,7 +10235,7 @@
         process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
     }
 
-    void prepareVirtualDisplay(int32_t orientation) {
+    void prepareVirtualDisplay(ui::Rotation orientation) {
         setDisplayInfoAndReconfigure(VIRTUAL_DISPLAY_ID, VIRTUAL_DISPLAY_WIDTH,
                                      VIRTUAL_DISPLAY_HEIGHT, orientation, VIRTUAL_DISPLAY_UNIQUE_ID,
                                      NO_PORT, ViewportType::VIRTUAL);
@@ -10174,7 +10253,7 @@
 
     mFakePolicy->addInputUniqueIdAssociation(DEVICE_LOCATION, VIRTUAL_DISPLAY_UNIQUE_ID);
 
-    prepareVirtualDisplay(DISPLAY_ORIENTATION_0);
+    prepareVirtualDisplay(ui::ROTATION_0);
 
     // Send an axis event
     processAxis(mapper, ABS_X, 100);
@@ -10277,15 +10356,17 @@
 TEST_F(BatteryControllerTest, GetBatteryCapacity) {
     PeripheralController& controller = addControllerAndConfigure<PeripheralController>();
 
-    ASSERT_TRUE(controller.getBatteryCapacity(DEFAULT_BATTERY));
-    ASSERT_EQ(controller.getBatteryCapacity(DEFAULT_BATTERY).value_or(-1), BATTERY_CAPACITY);
+    ASSERT_TRUE(controller.getBatteryCapacity(FakeEventHub::DEFAULT_BATTERY));
+    ASSERT_EQ(controller.getBatteryCapacity(FakeEventHub::DEFAULT_BATTERY).value_or(-1),
+              FakeEventHub::BATTERY_CAPACITY);
 }
 
 TEST_F(BatteryControllerTest, GetBatteryStatus) {
     PeripheralController& controller = addControllerAndConfigure<PeripheralController>();
 
-    ASSERT_TRUE(controller.getBatteryStatus(DEFAULT_BATTERY));
-    ASSERT_EQ(controller.getBatteryStatus(DEFAULT_BATTERY).value_or(-1), BATTERY_STATUS);
+    ASSERT_TRUE(controller.getBatteryStatus(FakeEventHub::DEFAULT_BATTERY));
+    ASSERT_EQ(controller.getBatteryStatus(FakeEventHub::DEFAULT_BATTERY).value_or(-1),
+              FakeEventHub::BATTERY_STATUS);
 }
 
 // --- LightControllerTest ---
diff --git a/services/inputflinger/tests/InstrumentedInputReader.cpp b/services/inputflinger/tests/InstrumentedInputReader.cpp
new file mode 100644
index 0000000..1f8cd12
--- /dev/null
+++ b/services/inputflinger/tests/InstrumentedInputReader.cpp
@@ -0,0 +1,50 @@
+/*
+ * 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 "InstrumentedInputReader.h"
+
+namespace android {
+
+InstrumentedInputReader::InstrumentedInputReader(std::shared_ptr<EventHubInterface> eventHub,
+                                                 const sp<InputReaderPolicyInterface>& policy,
+                                                 InputListenerInterface& listener)
+      : InputReader(eventHub, policy, listener), mFakeContext(this) {}
+
+void InstrumentedInputReader::pushNextDevice(std::shared_ptr<InputDevice> device) {
+    mNextDevices.push(device);
+}
+
+std::shared_ptr<InputDevice> InstrumentedInputReader::newDevice(int32_t deviceId,
+                                                                const std::string& name,
+                                                                const std::string& location) {
+    InputDeviceIdentifier identifier;
+    identifier.name = name;
+    identifier.location = location;
+    int32_t generation = deviceId + 1;
+    return std::make_shared<InputDevice>(&mFakeContext, deviceId, generation, identifier);
+}
+
+std::shared_ptr<InputDevice> InstrumentedInputReader::createDeviceLocked(
+        int32_t eventHubId, const InputDeviceIdentifier& identifier) REQUIRES(mLock) {
+    if (!mNextDevices.empty()) {
+        std::shared_ptr<InputDevice> device(std::move(mNextDevices.front()));
+        mNextDevices.pop();
+        return device;
+    }
+    return InputReader::createDeviceLocked(eventHubId, identifier);
+}
+
+} // namespace android
diff --git a/services/inputflinger/tests/InstrumentedInputReader.h b/services/inputflinger/tests/InstrumentedInputReader.h
new file mode 100644
index 0000000..7f8d556
--- /dev/null
+++ b/services/inputflinger/tests/InstrumentedInputReader.h
@@ -0,0 +1,123 @@
+/*
+ * 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 <memory>
+#include <queue>
+#include <string>
+
+#include <InputDevice.h>
+#include <InputReader.h>
+#include <gtest/gtest.h>
+#include <utils/StrongPointer.h>
+
+namespace android {
+
+class InstrumentedInputReader : public InputReader {
+public:
+    InstrumentedInputReader(std::shared_ptr<EventHubInterface> eventHub,
+                            const sp<InputReaderPolicyInterface>& policy,
+                            InputListenerInterface& listener);
+    virtual ~InstrumentedInputReader() {}
+
+    void pushNextDevice(std::shared_ptr<InputDevice> device);
+
+    std::shared_ptr<InputDevice> newDevice(int32_t deviceId, const std::string& name,
+                                           const std::string& location = "");
+
+    // Make the protected loopOnce method accessible to tests.
+    using InputReader::loopOnce;
+
+protected:
+    virtual std::shared_ptr<InputDevice> createDeviceLocked(
+            int32_t eventHubId, const InputDeviceIdentifier& identifier);
+
+    class FakeInputReaderContext : public ContextImpl {
+    public:
+        FakeInputReaderContext(InputReader* reader)
+              : ContextImpl(reader),
+                mGlobalMetaState(0),
+                mUpdateGlobalMetaStateWasCalled(false),
+                mGeneration(1) {}
+
+        virtual ~FakeInputReaderContext() {}
+
+        void assertUpdateGlobalMetaStateWasCalled() {
+            ASSERT_TRUE(mUpdateGlobalMetaStateWasCalled)
+                    << "Expected updateGlobalMetaState() to have been called.";
+            mUpdateGlobalMetaStateWasCalled = false;
+        }
+
+        void setGlobalMetaState(int32_t state) { mGlobalMetaState = state; }
+
+        uint32_t getGeneration() { return mGeneration; }
+
+        void updateGlobalMetaState() override {
+            mUpdateGlobalMetaStateWasCalled = true;
+            ContextImpl::updateGlobalMetaState();
+        }
+
+        int32_t getGlobalMetaState() override {
+            return mGlobalMetaState | ContextImpl::getGlobalMetaState();
+        }
+
+        int32_t bumpGeneration() override {
+            mGeneration = ContextImpl::bumpGeneration();
+            return mGeneration;
+        }
+
+        void requestTimeoutAtTime(nsecs_t when) override { mRequestedTimeout = when; }
+
+        void assertTimeoutWasRequested(nsecs_t when) {
+            ASSERT_TRUE(mRequestedTimeout) << "Expected timeout at time " << when
+                                           << " but there was no timeout requested.";
+            ASSERT_EQ(when, *mRequestedTimeout);
+            mRequestedTimeout.reset();
+        }
+
+        void assertTimeoutWasNotRequested() {
+            ASSERT_FALSE(mRequestedTimeout) << "Expected no timeout to have been requested,"
+                                               " but one was requested at time "
+                                            << *mRequestedTimeout;
+        }
+
+        void getExternalStylusDevices(std::vector<InputDeviceInfo>& outDevices) override {
+            outDevices = mExternalStylusDevices;
+        }
+
+        void setExternalStylusDevices(std::vector<InputDeviceInfo>&& devices) {
+            mExternalStylusDevices = devices;
+        }
+
+    private:
+        int32_t mGlobalMetaState;
+        bool mUpdateGlobalMetaStateWasCalled;
+        int32_t mGeneration;
+        std::optional<nsecs_t> mRequestedTimeout;
+        std::vector<InputDeviceInfo> mExternalStylusDevices;
+    } mFakeContext;
+
+    friend class InputReaderTest;
+
+public:
+    FakeInputReaderContext* getContext() { return &mFakeContext; }
+
+private:
+    std::queue<std::shared_ptr<InputDevice>> mNextDevices;
+};
+
+} // namespace android
diff --git a/services/inputflinger/tests/NotifyArgs_test.cpp b/services/inputflinger/tests/NotifyArgs_test.cpp
new file mode 100644
index 0000000..6715585
--- /dev/null
+++ b/services/inputflinger/tests/NotifyArgs_test.cpp
@@ -0,0 +1,85 @@
+/*
+ * 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 <NotifyArgs.h>
+#include <utils/Timers.h>
+
+#include <gtest/gtest.h>
+#include "android/input.h"
+#include "input/Input.h"
+#include "input/TouchVideoFrame.h"
+
+namespace android {
+
+// --- NotifyArgsTest ---
+
+/**
+ * Validate basic copy assignment.
+ */
+TEST(NotifyMotionArgsTest, TestCopyAssignmentOperator) {
+    int32_t id = 123;
+    nsecs_t downTime = systemTime();
+    nsecs_t eventTime = downTime++;
+    nsecs_t readTime = downTime++;
+    int32_t deviceId = 7;
+    uint32_t source = AINPUT_SOURCE_TOUCHSCREEN;
+    int32_t displayId = 42;
+    uint32_t policyFlags = POLICY_FLAG_GESTURE;
+    int32_t action = AMOTION_EVENT_ACTION_HOVER_MOVE;
+    int32_t actionButton = AMOTION_EVENT_BUTTON_PRIMARY;
+    int32_t flags = AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED;
+    int32_t metaState = AMETA_SCROLL_LOCK_ON;
+    uint32_t buttonState = AMOTION_EVENT_BUTTON_PRIMARY | AMOTION_EVENT_BUTTON_SECONDARY;
+    MotionClassification classification = MotionClassification::DEEP_PRESS;
+    int32_t edgeFlags = AMOTION_EVENT_EDGE_FLAG_TOP;
+    uint32_t pointerCount = 2;
+    PointerProperties pointerProperties[pointerCount];
+    PointerCoords pointerCoords[pointerCount];
+    float x = 0;
+    float y = 10;
+
+    for (size_t i = 0; i < pointerCount; i++) {
+        pointerProperties[i].clear();
+        pointerProperties[i].id = i;
+        pointerProperties[i].toolType = AMOTION_EVENT_TOOL_TYPE_FINGER;
+
+        pointerCoords[i].clear();
+        pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_X, x++);
+        pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_Y, y++);
+    }
+
+    float xPrecision = 1.2f;
+    float yPrecision = 3.4f;
+    float xCursorPosition = 5.6f;
+    float yCursorPosition = 7.8f;
+
+    std::vector<int16_t> videoData = {1, 2, 3, 4};
+    timeval timestamp = {5, 6};
+    TouchVideoFrame frame(2, 2, std::move(videoData), timestamp);
+    std::vector<TouchVideoFrame> videoFrames = {frame};
+    const NotifyMotionArgs args(id, eventTime, readTime, deviceId, source, displayId, policyFlags,
+                                action, actionButton, flags, metaState, buttonState, classification,
+                                edgeFlags, pointerCount, pointerProperties, pointerCoords,
+                                xPrecision, yPrecision, xCursorPosition, yCursorPosition, downTime,
+                                videoFrames);
+
+    NotifyMotionArgs otherArgs{};
+    otherArgs = args;
+
+    EXPECT_EQ(args, otherArgs);
+}
+
+} // namespace android
\ No newline at end of file
diff --git a/services/inputflinger/tests/TestConstants.h b/services/inputflinger/tests/TestConstants.h
new file mode 100644
index 0000000..27881f6
--- /dev/null
+++ b/services/inputflinger/tests/TestConstants.h
@@ -0,0 +1,33 @@
+/*
+ * 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
+
+namespace android {
+
+using std::chrono_literals::operator""ms;
+
+// Timeout for waiting for an expected event
+static constexpr std::chrono::duration WAIT_TIMEOUT = 100ms;
+
+// An arbitrary time value.
+static constexpr nsecs_t ARBITRARY_TIME = 1234;
+static constexpr nsecs_t READ_TIME = 4321;
+
+// Error tolerance for floating point assertions.
+static const float EPSILON = 0.001f;
+
+} // namespace android
diff --git a/services/inputflinger/tests/TestInputListener.cpp b/services/inputflinger/tests/TestInputListener.cpp
index 29093ef..2801072 100644
--- a/services/inputflinger/tests/TestInputListener.cpp
+++ b/services/inputflinger/tests/TestInputListener.cpp
@@ -59,26 +59,34 @@
             assertCalled<NotifyKeyArgs>(outEventArgs, "Expected notifyKey() to have been called."));
 }
 
+void TestInputListener::assertNotifyKeyWasCalled(const ::testing::Matcher<NotifyKeyArgs>& matcher) {
+    NotifyKeyArgs outEventArgs;
+    ASSERT_NO_FATAL_FAILURE(assertNotifyKeyWasCalled(&outEventArgs));
+    ASSERT_THAT(outEventArgs, matcher);
+}
+
 void TestInputListener::assertNotifyKeyWasNotCalled() {
     ASSERT_NO_FATAL_FAILURE(assertNotCalled<NotifyKeyArgs>("notifyKey() should not be called."));
 }
 
-void TestInputListener::assertNotifyMotionWasCalled(NotifyMotionArgs* outEventArgs) {
+void TestInputListener::assertNotifyMotionWasCalled(NotifyMotionArgs* outEventArgs,
+                                                    std::optional<TimePoint> waitUntil) {
     ASSERT_NO_FATAL_FAILURE(
             assertCalled<NotifyMotionArgs>(outEventArgs,
-                                           "Expected notifyMotion() to have been called."));
+                                           "Expected notifyMotion() to have been called.",
+                                           waitUntil));
 }
 
 void TestInputListener::assertNotifyMotionWasCalled(
-        const ::testing::Matcher<NotifyMotionArgs>& matcher) {
+        const ::testing::Matcher<NotifyMotionArgs>& matcher, std::optional<TimePoint> waitUntil) {
     NotifyMotionArgs outEventArgs;
-    ASSERT_NO_FATAL_FAILURE(assertNotifyMotionWasCalled(&outEventArgs));
+    ASSERT_NO_FATAL_FAILURE(assertNotifyMotionWasCalled(&outEventArgs, waitUntil));
     ASSERT_THAT(outEventArgs, matcher);
 }
 
-void TestInputListener::assertNotifyMotionWasNotCalled() {
+void TestInputListener::assertNotifyMotionWasNotCalled(std::optional<TimePoint> waitUntil) {
     ASSERT_NO_FATAL_FAILURE(
-            assertNotCalled<NotifyMotionArgs>("notifyMotion() should not be called."));
+            assertNotCalled<NotifyMotionArgs>("notifyMotion() should not be called.", waitUntil));
 }
 
 void TestInputListener::assertNotifySwitchWasCalled(NotifySwitchArgs* outEventArgs) {
@@ -113,15 +121,18 @@
 }
 
 template <class NotifyArgsType>
-void TestInputListener::assertCalled(NotifyArgsType* outEventArgs, std::string message) {
+void TestInputListener::assertCalled(NotifyArgsType* outEventArgs, std::string message,
+                                     std::optional<TimePoint> waitUntil) {
     std::unique_lock<std::mutex> lock(mLock);
     base::ScopedLockAssertion assumeLocked(mLock);
 
     std::vector<NotifyArgsType>& queue = std::get<std::vector<NotifyArgsType>>(mQueues);
     if (queue.empty()) {
-        const bool eventReceived =
-                mCondition.wait_for(lock, mEventHappenedTimeout,
-                                    [&queue]() REQUIRES(mLock) { return !queue.empty(); });
+        const auto time =
+                waitUntil.value_or(std::chrono::system_clock::now() + mEventHappenedTimeout);
+        const bool eventReceived = mCondition.wait_until(lock, time, [&queue]() REQUIRES(mLock) {
+            return !queue.empty();
+        });
         if (!eventReceived) {
             FAIL() << "Timed out waiting for event: " << message.c_str();
         }
@@ -133,14 +144,16 @@
 }
 
 template <class NotifyArgsType>
-void TestInputListener::assertNotCalled(std::string message) {
+void TestInputListener::assertNotCalled(std::string message, std::optional<TimePoint> waitUntil) {
     std::unique_lock<std::mutex> lock(mLock);
     base::ScopedLockAssertion assumeLocked(mLock);
 
     std::vector<NotifyArgsType>& queue = std::get<std::vector<NotifyArgsType>>(mQueues);
-    const bool eventReceived =
-            mCondition.wait_for(lock, mEventDidNotHappenTimeout,
-                                [&queue]() REQUIRES(mLock) { return !queue.empty(); });
+    const auto time =
+            waitUntil.value_or(std::chrono::system_clock::now() + mEventDidNotHappenTimeout);
+    const bool eventReceived = mCondition.wait_until(lock, time, [&queue]() REQUIRES(mLock) {
+        return !queue.empty();
+    });
     if (eventReceived) {
         FAIL() << "Unexpected event: " << message.c_str();
     }
diff --git a/services/inputflinger/tests/TestInputListener.h b/services/inputflinger/tests/TestInputListener.h
index 4ad1c42..9665f70 100644
--- a/services/inputflinger/tests/TestInputListener.h
+++ b/services/inputflinger/tests/TestInputListener.h
@@ -33,6 +33,8 @@
                       std::chrono::milliseconds eventDidNotHappenTimeout = 0ms);
     virtual ~TestInputListener();
 
+    using TimePoint = std::chrono::time_point<std::chrono::system_clock>;
+
     void assertNotifyConfigurationChangedWasCalled(
             NotifyConfigurationChangedArgs* outEventArgs = nullptr);
 
@@ -44,13 +46,17 @@
 
     void assertNotifyKeyWasCalled(NotifyKeyArgs* outEventArgs = nullptr);
 
+    void assertNotifyKeyWasCalled(const ::testing::Matcher<NotifyKeyArgs>& matcher);
+
     void assertNotifyKeyWasNotCalled();
 
-    void assertNotifyMotionWasCalled(NotifyMotionArgs* outEventArgs = nullptr);
+    void assertNotifyMotionWasCalled(NotifyMotionArgs* outEventArgs = nullptr,
+                                     std::optional<TimePoint> waitUntil = {});
 
-    void assertNotifyMotionWasCalled(const ::testing::Matcher<NotifyMotionArgs>& matcher);
+    void assertNotifyMotionWasCalled(const ::testing::Matcher<NotifyMotionArgs>& matcher,
+                                     std::optional<TimePoint> waitUntil = {});
 
-    void assertNotifyMotionWasNotCalled();
+    void assertNotifyMotionWasNotCalled(std::optional<TimePoint> waitUntil = {});
 
     void assertNotifySwitchWasCalled(NotifySwitchArgs* outEventArgs = nullptr);
 
@@ -61,10 +67,11 @@
 
 private:
     template <class NotifyArgsType>
-    void assertCalled(NotifyArgsType* outEventArgs, std::string message);
+    void assertCalled(NotifyArgsType* outEventArgs, std::string message,
+                      std::optional<TimePoint> waitUntil = {});
 
     template <class NotifyArgsType>
-    void assertNotCalled(std::string message);
+    void assertNotCalled(std::string message, std::optional<TimePoint> timeout = {});
 
     template <class NotifyArgsType>
     void addToQueue(const NotifyArgsType* args);
diff --git a/services/inputflinger/tests/TestInputListenerMatchers.h b/services/inputflinger/tests/TestInputListenerMatchers.h
index 2580405..9db3422 100644
--- a/services/inputflinger/tests/TestInputListenerMatchers.h
+++ b/services/inputflinger/tests/TestInputListenerMatchers.h
@@ -19,21 +19,35 @@
 #include <android/input.h>
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
+#include <input/Input.h>
 
 namespace android {
 
-MATCHER_P(WithMotionAction, action, "InputEvent with specified action") {
-    if (action == AMOTION_EVENT_ACTION_CANCEL) {
-        *result_listener << "expected FLAG_CANCELED to be set with ACTION_CANCEL, but was not set";
-        return (arg.flags & AMOTION_EVENT_FLAG_CANCELED) != 0;
+MATCHER_P(WithMotionAction, action, "MotionEvent with specified action") {
+    bool matches = action == arg.action;
+    if (!matches) {
+        *result_listener << "expected action " << MotionEvent::actionToString(action)
+                         << ", but got " << MotionEvent::actionToString(arg.action);
     }
-    *result_listener << "expected action " << MotionEvent::actionToString(action) << ", but got "
-                     << MotionEvent::actionToString(arg.action);
-    return action == arg.action;
+    if (action == AMOTION_EVENT_ACTION_CANCEL) {
+        if (!matches) {
+            *result_listener << "; ";
+        }
+        *result_listener << "expected FLAG_CANCELED to be set with ACTION_CANCEL, but was not set";
+        matches &= (arg.flags & AMOTION_EVENT_FLAG_CANCELED) != 0;
+    }
+    return matches;
+}
+
+MATCHER_P(WithKeyAction, action, "KeyEvent with specified action") {
+    *result_listener << "expected action " << KeyEvent::actionToString(action) << ", but got "
+                     << KeyEvent::actionToString(arg.action);
+    return arg.action == action;
 }
 
 MATCHER_P(WithSource, source, "InputEvent with specified source") {
-    *result_listener << "expected source " << source << ", but got " << arg.source;
+    *result_listener << "expected source " << inputEventSourceToString(source) << ", but got "
+                     << inputEventSourceToString(arg.source);
     return arg.source == source;
 }
 
@@ -42,6 +56,16 @@
     return arg.displayId == displayId;
 }
 
+MATCHER_P(WithDeviceId, deviceId, "InputEvent with specified deviceId") {
+    *result_listener << "expected deviceId " << deviceId << ", but got " << arg.deviceId;
+    return arg.deviceId == deviceId;
+}
+
+MATCHER_P(WithKeyCode, keyCode, "KeyEvent with specified key code") {
+    *result_listener << "expected key code " << keyCode << ", but got " << arg.keyCode;
+    return arg.keyCode == keyCode;
+}
+
 MATCHER_P2(WithCoords, x, y, "InputEvent with specified coords") {
     const auto argX = arg.pointerCoords[0].getX();
     const auto argY = arg.pointerCoords[0].getY();
@@ -52,8 +76,15 @@
 
 MATCHER_P(WithPressure, pressure, "InputEvent with specified pressure") {
     const auto argPressure = arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_PRESSURE);
-    *result_listener << "expected pressure " << pressure << ", but got " << pressure;
-    return argPressure;
+    *result_listener << "expected pressure " << pressure << ", but got " << argPressure;
+    return argPressure == pressure;
+}
+
+MATCHER_P(WithToolType, toolType, "InputEvent with specified tool type") {
+    const auto argToolType = arg.pointerProperties[0].toolType;
+    *result_listener << "expected tool type " << motionToolTypeToString(toolType) << ", but got "
+                     << motionToolTypeToString(argToolType);
+    return argToolType == toolType;
 }
 
 MATCHER_P(WithFlags, flags, "InputEvent with specified flags") {
@@ -61,4 +92,14 @@
     return arg.flags == flags;
 }
 
+MATCHER_P(WithButtonState, buttons, "InputEvent with specified button state") {
+    *result_listener << "expected button state " << buttons << ", but got " << arg.buttonState;
+    return arg.buttonState == buttons;
+}
+
+MATCHER_P(WithEventTime, eventTime, "InputEvent with specified eventTime") {
+    *result_listener << "expected event time " << eventTime << ", but got " << arg.eventTime;
+    return arg.eventTime == eventTime;
+}
+
 } // namespace android
diff --git a/services/inputflinger/tests/UinputDevice.cpp b/services/inputflinger/tests/UinputDevice.cpp
index 9c93919..97a2614 100644
--- a/services/inputflinger/tests/UinputDevice.cpp
+++ b/services/inputflinger/tests/UinputDevice.cpp
@@ -17,13 +17,15 @@
 #include "UinputDevice.h"
 
 #include <android-base/stringprintf.h>
+#include <cutils/memory.h>
 #include <fcntl.h>
 
 namespace android {
 
 // --- UinputDevice ---
 
-UinputDevice::UinputDevice(const char* name) : mName(name) {}
+UinputDevice::UinputDevice(const char* name, int16_t productId)
+      : mName(name), mProductId(productId) {}
 
 UinputDevice::~UinputDevice() {
     if (ioctl(mDeviceFd, UI_DEV_DESTROY)) {
@@ -42,7 +44,7 @@
     strlcpy(device.name, mName, UINPUT_MAX_NAME_SIZE);
     device.id.bustype = BUS_USB;
     device.id.vendor = 0x01;
-    device.id.product = 0x01;
+    device.id.product = mProductId;
     device.id.version = 1;
 
     ASSERT_NO_FATAL_FAILURE(configureDevice(mDeviceFd, &device));
@@ -58,11 +60,11 @@
 }
 
 void UinputDevice::injectEvent(uint16_t type, uint16_t code, int32_t value) {
+    // uinput ignores the timestamp
     struct input_event event = {};
     event.type = type;
     event.code = code;
     event.value = value;
-    event.time = {}; // uinput ignores the timestamp
 
     if (write(mDeviceFd, &event, sizeof(input_event)) < 0) {
         std::string msg = base::StringPrintf("Could not write event %" PRIu16 " %" PRIu16
@@ -75,8 +77,8 @@
 
 // --- UinputKeyboard ---
 
-UinputKeyboard::UinputKeyboard(std::initializer_list<int> keys)
-      : UinputDevice(UinputKeyboard::KEYBOARD_NAME), mKeys(keys.begin(), keys.end()) {}
+UinputKeyboard::UinputKeyboard(const char* name, int16_t productId, std::initializer_list<int> keys)
+      : UinputDevice(name, productId), mKeys(keys.begin(), keys.end()) {}
 
 void UinputKeyboard::configureDevice(int fd, uinput_user_dev* device) {
     // enable key press/release event
@@ -120,22 +122,52 @@
 
 // --- UinputHomeKey ---
 
-UinputHomeKey::UinputHomeKey() : UinputKeyboard({KEY_HOME}) {}
+UinputHomeKey::UinputHomeKey() : UinputKeyboard(DEVICE_NAME, PRODUCT_ID, {KEY_HOME}) {}
 
 void UinputHomeKey::pressAndReleaseHomeKey() {
     pressAndReleaseKey(KEY_HOME);
 }
 
-// --- UinputSteamController
-UinputSteamController::UinputSteamController() : UinputKeyboard({BTN_GEAR_DOWN, BTN_GEAR_UP}) {}
+// --- UinputSteamController ---
+
+UinputSteamController::UinputSteamController()
+      : UinputKeyboard(DEVICE_NAME, PRODUCT_ID, {BTN_GEAR_DOWN, BTN_GEAR_UP}) {}
+
+// --- UinputExternalStylus ---
+
+UinputExternalStylus::UinputExternalStylus()
+      : UinputKeyboard(DEVICE_NAME, PRODUCT_ID, {BTN_STYLUS, BTN_STYLUS2, BTN_STYLUS3}) {}
+
+// --- UinputExternalStylusWithPressure ---
+
+UinputExternalStylusWithPressure::UinputExternalStylusWithPressure()
+      : UinputKeyboard(DEVICE_NAME, PRODUCT_ID, {BTN_STYLUS, BTN_STYLUS2, BTN_STYLUS3}) {}
+
+void UinputExternalStylusWithPressure::configureDevice(int fd, uinput_user_dev* device) {
+    UinputKeyboard::configureDevice(fd, device);
+
+    ioctl(fd, UI_SET_EVBIT, EV_ABS);
+    ioctl(fd, UI_SET_ABSBIT, ABS_PRESSURE);
+    device->absmin[ABS_PRESSURE] = RAW_PRESSURE_MIN;
+    device->absmax[ABS_PRESSURE] = RAW_PRESSURE_MAX;
+}
+
+void UinputExternalStylusWithPressure::setPressure(int32_t pressure) {
+    injectEvent(EV_ABS, ABS_PRESSURE, pressure);
+    injectEvent(EV_SYN, SYN_REPORT, 0);
+}
 
 // --- UinputTouchScreen ---
-UinputTouchScreen::UinputTouchScreen(const Rect* size)
-      : UinputDevice(UinputTouchScreen::DEVICE_NAME), mSize(*size) {}
+
+UinputTouchScreen::UinputTouchScreen(const Rect& size)
+      : UinputKeyboard(DEVICE_NAME, PRODUCT_ID,
+                       {BTN_TOUCH, BTN_TOOL_PEN, BTN_STYLUS, BTN_STYLUS2, BTN_STYLUS3}),
+        mSize(size) {}
 
 void UinputTouchScreen::configureDevice(int fd, uinput_user_dev* device) {
+    UinputKeyboard::configureDevice(fd, device);
+
     // Setup the touch screen device
-    ioctl(fd, UI_SET_EVBIT, EV_KEY);
     ioctl(fd, UI_SET_EVBIT, EV_REL);
     ioctl(fd, UI_SET_EVBIT, EV_ABS);
     ioctl(fd, UI_SET_ABSBIT, ABS_MT_SLOT);
@@ -145,7 +177,6 @@
     ioctl(fd, UI_SET_ABSBIT, ABS_MT_TRACKING_ID);
     ioctl(fd, UI_SET_ABSBIT, ABS_MT_TOOL_TYPE);
     ioctl(fd, UI_SET_PROPBIT, INPUT_PROP_DIRECT);
-    ioctl(fd, UI_SET_KEYBIT, BTN_TOUCH);
 
     device->absmin[ABS_MT_SLOT] = RAW_SLOT_MIN;
     device->absmax[ABS_MT_SLOT] = RAW_SLOT_MAX;
@@ -157,6 +188,8 @@
     device->absmax[ABS_MT_POSITION_Y] = mSize.bottom - 1;
     device->absmin[ABS_MT_TRACKING_ID] = RAW_ID_MIN;
     device->absmax[ABS_MT_TRACKING_ID] = RAW_ID_MAX;
+    device->absmin[ABS_MT_TOOL_TYPE] = MT_TOOL_FINGER;
+    device->absmax[ABS_MT_TOOL_TYPE] = MT_TOOL_MAX;
 }
 
 void UinputTouchScreen::sendSlot(int32_t slot) {
diff --git a/services/inputflinger/tests/UinputDevice.h b/services/inputflinger/tests/UinputDevice.h
index e0ff8c3..51e331d 100644
--- a/services/inputflinger/tests/UinputDevice.h
+++ b/services/inputflinger/tests/UinputDevice.h
@@ -32,7 +32,7 @@
 template <class D, class... Ts>
 std::unique_ptr<D> createUinputDevice(Ts... args) {
     // Using `new` to access non-public constructors.
-    std::unique_ptr<D> dev(new D(&args...));
+    std::unique_ptr<D> dev(new D(args...));
     EXPECT_NO_FATAL_FAILURE(dev->init());
     return dev;
 }
@@ -51,8 +51,9 @@
 
 protected:
     const char* mName;
+    const int16_t mProductId;
 
-    UinputDevice(const char* name);
+    explicit UinputDevice(const char* name, int16_t productId);
 
     // Signals which types of events this device supports before it is created.
     // This must be overridden by subclasses.
@@ -71,7 +72,8 @@
 
 class UinputKeyboard : public UinputDevice {
 public:
-    static constexpr const char* KEYBOARD_NAME = "Test Keyboard Device";
+    static constexpr const char* KEYBOARD_NAME = "Test Uinput Keyboard Device";
+    static constexpr int16_t PRODUCT_ID = 42;
 
     // Injects key press and sync.
     void pressKey(int key);
@@ -84,11 +86,12 @@
     friend std::unique_ptr<D> createUinputDevice(Ts... args);
 
 protected:
-    UinputKeyboard(std::initializer_list<int> keys = {});
+    explicit UinputKeyboard(const char* name, int16_t productId = PRODUCT_ID,
+                            std::initializer_list<int> keys = {});
 
-private:
     void configureDevice(int fd, uinput_user_dev* device) override;
 
+private:
     std::set<int> mKeys;
 };
 
@@ -97,6 +100,9 @@
 // A keyboard device that has a single HOME key.
 class UinputHomeKey : public UinputKeyboard {
 public:
+    static constexpr const char* DEVICE_NAME = "Test Uinput Home Key";
+    static constexpr int16_t PRODUCT_ID = 43;
+
     // Injects 4 events: key press, sync, key release, and sync.
     void pressAndReleaseHomeKey();
 
@@ -104,24 +110,69 @@
     friend std::unique_ptr<D> createUinputDevice(Ts... args);
 
 private:
-    UinputHomeKey();
+    explicit UinputHomeKey();
 };
 
+// --- UinputSteamController ---
+
 // A joystick device that sends a BTN_GEAR_DOWN / BTN_WHEEL key.
 class UinputSteamController : public UinputKeyboard {
 public:
+    static constexpr const char* DEVICE_NAME = "Test Uinput Steam Controller";
+    static constexpr int16_t PRODUCT_ID = 44;
+
     template <class D, class... Ts>
     friend std::unique_ptr<D> createUinputDevice(Ts... args);
 
 private:
-    UinputSteamController();
+    explicit UinputSteamController();
+};
+
+// --- UinputExternalStylus ---
+
+// A stylus that reports button presses.
+class UinputExternalStylus : public UinputKeyboard {
+public:
+    static constexpr const char* DEVICE_NAME = "Test Uinput External Stylus";
+    static constexpr int16_t PRODUCT_ID = 45;
+
+    template <class D, class... Ts>
+    friend std::unique_ptr<D> createUinputDevice(Ts... args);
+
+private:
+    explicit UinputExternalStylus();
+};
+
+// --- UinputExternalStylusWithPressure ---
+
+// A stylus that reports button presses and pressure values.
+class UinputExternalStylusWithPressure : public UinputKeyboard {
+public:
+    static constexpr const char* DEVICE_NAME = "Test Uinput External Stylus With Pressure";
+    static constexpr int16_t PRODUCT_ID = 46;
+
+    static constexpr int32_t RAW_PRESSURE_MIN = 0;
+    static constexpr int32_t RAW_PRESSURE_MAX = 255;
+
+    void setPressure(int32_t pressure);
+
+    template <class D, class... Ts>
+    friend std::unique_ptr<D> createUinputDevice(Ts... args);
+
+private:
+    void configureDevice(int fd, uinput_user_dev* device) override;
+
+    explicit UinputExternalStylusWithPressure();
 };
 
 // --- UinputTouchScreen ---
-// A touch screen device with specific size.
-class UinputTouchScreen : public UinputDevice {
+
+// A multi-touch touchscreen device with specific size that also supports styluses.
+class UinputTouchScreen : public UinputKeyboard {
 public:
-    static constexpr const char* DEVICE_NAME = "Test Touch Screen";
+    static constexpr const char* DEVICE_NAME = "Test Uinput Touch Screen";
+    static constexpr int16_t PRODUCT_ID = 47;
+
     static const int32_t RAW_TOUCH_MIN = 0;
     static const int32_t RAW_TOUCH_MAX = 31;
     static const int32_t RAW_ID_MIN = 0;
@@ -146,7 +197,7 @@
     const Point getCenterPoint();
 
 protected:
-    UinputTouchScreen(const Rect* size);
+    explicit UinputTouchScreen(const Rect& size);
 
 private:
     void configureDevice(int fd, uinput_user_dev* device) override;
diff --git a/services/inputflinger/tests/fuzzers/InputReaderFuzzer.cpp b/services/inputflinger/tests/fuzzers/InputReaderFuzzer.cpp
index a9f5a3a..2eed997 100644
--- a/services/inputflinger/tests/fuzzers/InputReaderFuzzer.cpp
+++ b/services/inputflinger/tests/fuzzers/InputReaderFuzzer.cpp
@@ -157,6 +157,10 @@
         return reader->getKeyCodeForKeyLocation(deviceId, locationKeyCode);
     }
 
+    std::optional<std::string> getBluetoothAddress(int32_t deviceId) const {
+        return reader->getBluetoothAddress(deviceId);
+    }
+
 private:
     std::unique_ptr<InputReaderInterface> reader;
 };
@@ -273,6 +277,7 @@
                                          std::chrono::microseconds(fdp->ConsumeIntegral<size_t>()),
                                          std::chrono::microseconds(fdp->ConsumeIntegral<size_t>()));
                 },
+                [&]() -> void { reader->getBluetoothAddress(fdp->ConsumeIntegral<int32_t>()); },
         })();
     }
 
diff --git a/services/inputflinger/tests/fuzzers/MapperHelpers.h b/services/inputflinger/tests/fuzzers/MapperHelpers.h
index bd81761..445ed18 100644
--- a/services/inputflinger/tests/fuzzers/MapperHelpers.h
+++ b/services/inputflinger/tests/fuzzers/MapperHelpers.h
@@ -311,10 +311,11 @@
         return mFdp->ConsumeRandomLengthString(32);
     }
     TouchAffineTransformation getTouchAffineTransformation(const std::string& inputDeviceDescriptor,
-                                                           int32_t surfaceRotation) override {
+                                                           ui::Rotation surfaceRotation) override {
         return mTransform;
     }
     void setTouchAffineTransformation(const TouchAffineTransformation t) { mTransform = t; }
+    void notifyStylusGestureStarted(int32_t, nsecs_t) {}
 };
 
 class FuzzInputListener : public virtual InputListenerInterface {
@@ -363,6 +364,7 @@
 
     void updateLedMetaState(int32_t metaState) override{};
     int32_t getLedMetaState() override { return mFdp->ConsumeIntegral<int32_t>(); };
+    void notifyStylusGestureStarted(int32_t, nsecs_t) {}
 };
 
 } // namespace android
diff --git a/services/powermanager/Android.bp b/services/powermanager/Android.bp
index 6fbba3f..7fb33e5 100644
--- a/services/powermanager/Android.bp
+++ b/services/powermanager/Android.bp
@@ -38,7 +38,9 @@
         "libutils",
         "android.hardware.power@1.0",
         "android.hardware.power@1.1",
-        "android.hardware.power-V3-cpp",
+        "android.hardware.power@1.2",
+        "android.hardware.power@1.3",
+        "android.hardware.power-V4-cpp",
     ],
 
     cflags: [
diff --git a/services/powermanager/PowerHalController.cpp b/services/powermanager/PowerHalController.cpp
index 8c225d5..f89035f 100644
--- a/services/powermanager/PowerHalController.cpp
+++ b/services/powermanager/PowerHalController.cpp
@@ -33,16 +33,20 @@
 // -------------------------------------------------------------------------------------------------
 
 std::unique_ptr<HalWrapper> HalConnector::connect() {
-    sp<IPower> halAidl = PowerHalLoader::loadAidl();
-    if (halAidl) {
+    if (sp<IPower> halAidl = PowerHalLoader::loadAidl()) {
         return std::make_unique<AidlHalWrapper>(halAidl);
     }
-    sp<V1_0::IPower> halHidlV1_0 = PowerHalLoader::loadHidlV1_0();
-    sp<V1_1::IPower> halHidlV1_1 = PowerHalLoader::loadHidlV1_1();
-    if (halHidlV1_1) {
-        return std::make_unique<HidlHalWrapperV1_1>(halHidlV1_0, halHidlV1_1);
-    }
-    if (halHidlV1_0) {
+    // If V1_0 isn't defined, none of them are
+    if (sp<V1_0::IPower> halHidlV1_0 = PowerHalLoader::loadHidlV1_0()) {
+        if (sp<V1_3::IPower> halHidlV1_3 = PowerHalLoader::loadHidlV1_3()) {
+            return std::make_unique<HidlHalWrapperV1_3>(halHidlV1_3);
+        }
+        if (sp<V1_2::IPower> halHidlV1_2 = PowerHalLoader::loadHidlV1_2()) {
+            return std::make_unique<HidlHalWrapperV1_2>(halHidlV1_2);
+        }
+        if (sp<V1_1::IPower> halHidlV1_1 = PowerHalLoader::loadHidlV1_1()) {
+            return std::make_unique<HidlHalWrapperV1_1>(halHidlV1_1);
+        }
         return std::make_unique<HidlHalWrapperV1_0>(halHidlV1_0);
     }
     return nullptr;
diff --git a/services/powermanager/PowerHalLoader.cpp b/services/powermanager/PowerHalLoader.cpp
index 1f1b43a..6bd40f8 100644
--- a/services/powermanager/PowerHalLoader.cpp
+++ b/services/powermanager/PowerHalLoader.cpp
@@ -17,6 +17,8 @@
 #define LOG_TAG "PowerHalLoader"
 
 #include <android/hardware/power/1.1/IPower.h>
+#include <android/hardware/power/1.2/IPower.h>
+#include <android/hardware/power/1.3/IPower.h>
 #include <android/hardware/power/IPower.h>
 #include <binder/IServiceManager.h>
 #include <hardware/power.h>
@@ -55,12 +57,16 @@
 sp<IPower> PowerHalLoader::gHalAidl = nullptr;
 sp<V1_0::IPower> PowerHalLoader::gHalHidlV1_0 = nullptr;
 sp<V1_1::IPower> PowerHalLoader::gHalHidlV1_1 = nullptr;
+sp<V1_2::IPower> PowerHalLoader::gHalHidlV1_2 = nullptr;
+sp<V1_3::IPower> PowerHalLoader::gHalHidlV1_3 = nullptr;
 
 void PowerHalLoader::unloadAll() {
     std::lock_guard<std::mutex> lock(gHalMutex);
     gHalAidl = nullptr;
     gHalHidlV1_0 = nullptr;
     gHalHidlV1_1 = nullptr;
+    gHalHidlV1_2 = nullptr;
+    gHalHidlV1_3 = nullptr;
 }
 
 sp<IPower> PowerHalLoader::loadAidl() {
@@ -82,6 +88,20 @@
     return loadHal<V1_1::IPower>(gHalExists, gHalHidlV1_1, loadFn, "HIDL v1.1");
 }
 
+sp<V1_2::IPower> PowerHalLoader::loadHidlV1_2() {
+    std::lock_guard<std::mutex> lock(gHalMutex);
+    static bool gHalExists = true;
+    static auto loadFn = []() { return V1_2::IPower::castFrom(loadHidlV1_0Locked()); };
+    return loadHal<V1_2::IPower>(gHalExists, gHalHidlV1_2, loadFn, "HIDL v1.2");
+}
+
+sp<V1_3::IPower> PowerHalLoader::loadHidlV1_3() {
+    std::lock_guard<std::mutex> lock(gHalMutex);
+    static bool gHalExists = true;
+    static auto loadFn = []() { return V1_3::IPower::castFrom(loadHidlV1_0Locked()); };
+    return loadHal<V1_3::IPower>(gHalExists, gHalHidlV1_3, loadFn, "HIDL v1.3");
+}
+
 sp<V1_0::IPower> PowerHalLoader::loadHidlV1_0Locked() {
     static bool gHalExists = true;
     static auto loadFn = []() { return V1_0::IPower::getService(); };
diff --git a/services/powermanager/PowerHalWrapper.cpp b/services/powermanager/PowerHalWrapper.cpp
index d74bd23..9e7adf8 100644
--- a/services/powermanager/PowerHalWrapper.cpp
+++ b/services/powermanager/PowerHalWrapper.cpp
@@ -24,8 +24,6 @@
 #include <cinttypes>
 
 using namespace android::hardware::power;
-namespace V1_0 = android::hardware::power::V1_0;
-namespace V1_1 = android::hardware::power::V1_1;
 namespace Aidl = android::hardware::power;
 
 namespace android {
@@ -108,7 +106,7 @@
 
 HalResult<void> HidlHalWrapperV1_0::setBoost(Boost boost, int32_t durationMs) {
     if (boost == Boost::INTERACTION) {
-        return sendPowerHint(V1_0::PowerHint::INTERACTION, durationMs);
+        return sendPowerHint(V1_3::PowerHint::INTERACTION, durationMs);
     } else {
         ALOGV("Skipped setBoost %s because Power HAL AIDL not available", toString(boost).c_str());
         return HalResult<void>::unsupported();
@@ -119,13 +117,13 @@
     uint32_t data = enabled ? 1 : 0;
     switch (mode) {
         case Mode::LAUNCH:
-            return sendPowerHint(V1_0::PowerHint::LAUNCH, data);
+            return sendPowerHint(V1_3::PowerHint::LAUNCH, data);
         case Mode::LOW_POWER:
-            return sendPowerHint(V1_0::PowerHint::LOW_POWER, data);
+            return sendPowerHint(V1_3::PowerHint::LOW_POWER, data);
         case Mode::SUSTAINED_PERFORMANCE:
-            return sendPowerHint(V1_0::PowerHint::SUSTAINED_PERFORMANCE, data);
+            return sendPowerHint(V1_3::PowerHint::SUSTAINED_PERFORMANCE, data);
         case Mode::VR:
-            return sendPowerHint(V1_0::PowerHint::VR_MODE, data);
+            return sendPowerHint(V1_3::PowerHint::VR_MODE, data);
         case Mode::INTERACTIVE:
             return setInteractive(enabled);
         case Mode::DOUBLE_TAP_TO_WAKE:
@@ -137,8 +135,8 @@
     }
 }
 
-HalResult<void> HidlHalWrapperV1_0::sendPowerHint(V1_0::PowerHint hintId, uint32_t data) {
-    auto ret = mHandleV1_0->powerHint(hintId, data);
+HalResult<void> HidlHalWrapperV1_0::sendPowerHint(V1_3::PowerHint hintId, uint32_t data) {
+    auto ret = mHandleV1_0->powerHint(static_cast<V1_0::PowerHint>(hintId), data);
     return HalResult<void>::fromReturn(ret);
 }
 
@@ -152,7 +150,7 @@
     return HalResult<void>::fromReturn(ret);
 }
 
-HalResult<sp<Aidl::IPowerHintSession>> HidlHalWrapperV1_0::createHintSession(
+HalResult<sp<hardware::power::IPowerHintSession>> HidlHalWrapperV1_0::createHintSession(
         int32_t, int32_t, const std::vector<int32_t>& threadIds, int64_t) {
     ALOGV("Skipped createHintSession(task num=%zu) because Power HAL not available",
           threadIds.size());
@@ -166,8 +164,59 @@
 
 // -------------------------------------------------------------------------------------------------
 
-HalResult<void> HidlHalWrapperV1_1::sendPowerHint(V1_0::PowerHint hintId, uint32_t data) {
-    auto ret = mHandleV1_1->powerHintAsync(hintId, data);
+HalResult<void> HidlHalWrapperV1_1::sendPowerHint(V1_3::PowerHint hintId, uint32_t data) {
+    auto handle = static_cast<V1_1::IPower*>(mHandleV1_0.get());
+    auto ret = handle->powerHintAsync(static_cast<V1_0::PowerHint>(hintId), data);
+    return HalResult<void>::fromReturn(ret);
+}
+
+// -------------------------------------------------------------------------------------------------
+
+HalResult<void> HidlHalWrapperV1_2::sendPowerHint(V1_3::PowerHint hintId, uint32_t data) {
+    auto handle = static_cast<V1_2::IPower*>(mHandleV1_0.get());
+    auto ret = handle->powerHintAsync_1_2(static_cast<V1_2::PowerHint>(hintId), data);
+    return HalResult<void>::fromReturn(ret);
+}
+
+HalResult<void> HidlHalWrapperV1_2::setBoost(Boost boost, int32_t durationMs) {
+    switch (boost) {
+        case Boost::CAMERA_SHOT:
+            return sendPowerHint(V1_3::PowerHint::CAMERA_SHOT, durationMs);
+        case Boost::CAMERA_LAUNCH:
+            return sendPowerHint(V1_3::PowerHint::CAMERA_LAUNCH, durationMs);
+        default:
+            return HidlHalWrapperV1_1::setBoost(boost, durationMs);
+    }
+}
+
+HalResult<void> HidlHalWrapperV1_2::setMode(Mode mode, bool enabled) {
+    uint32_t data = enabled ? 1 : 0;
+    switch (mode) {
+        case Mode::CAMERA_STREAMING_SECURE:
+        case Mode::CAMERA_STREAMING_LOW:
+        case Mode::CAMERA_STREAMING_MID:
+        case Mode::CAMERA_STREAMING_HIGH:
+            return sendPowerHint(V1_3::PowerHint::CAMERA_STREAMING, data);
+        case Mode::AUDIO_STREAMING_LOW_LATENCY:
+            return sendPowerHint(V1_3::PowerHint::AUDIO_LOW_LATENCY, data);
+        default:
+            return HidlHalWrapperV1_1::setMode(mode, enabled);
+    }
+}
+
+// -------------------------------------------------------------------------------------------------
+
+HalResult<void> HidlHalWrapperV1_3::setMode(Mode mode, bool enabled) {
+    uint32_t data = enabled ? 1 : 0;
+    if (mode == Mode::EXPENSIVE_RENDERING) {
+        return sendPowerHint(V1_3::PowerHint::EXPENSIVE_RENDERING, data);
+    }
+    return HidlHalWrapperV1_2::setMode(mode, enabled);
+}
+
+HalResult<void> HidlHalWrapperV1_3::sendPowerHint(V1_3::PowerHint hintId, uint32_t data) {
+    auto handle = static_cast<V1_3::IPower*>(mHandleV1_0.get());
+    auto ret = handle->powerHintAsync_1_3(hintId, data);
     return HalResult<void>::fromReturn(ret);
 }
 
diff --git a/services/powermanager/benchmarks/Android.bp b/services/powermanager/benchmarks/Android.bp
index fcb012f..4343aec 100644
--- a/services/powermanager/benchmarks/Android.bp
+++ b/services/powermanager/benchmarks/Android.bp
@@ -38,7 +38,9 @@
         "libutils",
         "android.hardware.power@1.0",
         "android.hardware.power@1.1",
-        "android.hardware.power-V3-cpp",
+        "android.hardware.power@1.2",
+        "android.hardware.power@1.3",
+        "android.hardware.power-V4-cpp",
     ],
     static_libs: [
         "libtestUtil",
diff --git a/services/powermanager/tests/Android.bp b/services/powermanager/tests/Android.bp
index 962784c..54dffcf 100644
--- a/services/powermanager/tests/Android.bp
+++ b/services/powermanager/tests/Android.bp
@@ -31,6 +31,8 @@
         "PowerHalWrapperAidlTest.cpp",
         "PowerHalWrapperHidlV1_0Test.cpp",
         "PowerHalWrapperHidlV1_1Test.cpp",
+        "PowerHalWrapperHidlV1_2Test.cpp",
+        "PowerHalWrapperHidlV1_3Test.cpp",
         "WorkSourceTest.cpp",
     ],
     cflags: [
@@ -47,7 +49,9 @@
         "libutils",
         "android.hardware.power@1.0",
         "android.hardware.power@1.1",
-        "android.hardware.power-V3-cpp",
+        "android.hardware.power@1.2",
+        "android.hardware.power@1.3",
+        "android.hardware.power-V4-cpp",
     ],
     static_libs: [
         "libgmock",
diff --git a/services/powermanager/tests/PowerHalLoaderTest.cpp b/services/powermanager/tests/PowerHalLoaderTest.cpp
index 058e1b5..e36deed 100644
--- a/services/powermanager/tests/PowerHalLoaderTest.cpp
+++ b/services/powermanager/tests/PowerHalLoaderTest.cpp
@@ -26,6 +26,8 @@
 
 using IPowerV1_0 = android::hardware::power::V1_0::IPower;
 using IPowerV1_1 = android::hardware::power::V1_1::IPower;
+using IPowerV1_2 = android::hardware::power::V1_2::IPower;
+using IPowerV1_3 = android::hardware::power::V1_3::IPower;
 using IPowerAidl = android::hardware::power::IPower;
 
 using namespace android;
@@ -52,6 +54,16 @@
     return PowerHalLoader::loadHidlV1_1();
 }
 
+template <>
+sp<IPowerV1_2> loadHal<IPowerV1_2>() {
+    return PowerHalLoader::loadHidlV1_2();
+}
+
+template <>
+sp<IPowerV1_3> loadHal<IPowerV1_3>() {
+    return PowerHalLoader::loadHidlV1_3();
+}
+
 // -------------------------------------------------------------------------------------------------
 
 template <typename T>
@@ -63,7 +75,7 @@
 
 // -------------------------------------------------------------------------------------------------
 
-typedef ::testing::Types<IPowerAidl, IPowerV1_0, IPowerV1_1> PowerHalTypes;
+typedef ::testing::Types<IPowerAidl, IPowerV1_0, IPowerV1_1, IPowerV1_2, IPowerV1_3> PowerHalTypes;
 TYPED_TEST_SUITE(PowerHalLoaderTest, PowerHalTypes);
 
 TYPED_TEST(PowerHalLoaderTest, TestLoadsOnlyOnce) {
diff --git a/services/powermanager/tests/PowerHalWrapperHidlV1_0Test.cpp b/services/powermanager/tests/PowerHalWrapperHidlV1_0Test.cpp
index b54762c..0cd2e22 100644
--- a/services/powermanager/tests/PowerHalWrapperHidlV1_0Test.cpp
+++ b/services/powermanager/tests/PowerHalWrapperHidlV1_0Test.cpp
@@ -87,18 +87,28 @@
 }
 
 TEST_F(PowerHalWrapperHidlV1_0Test, TestSetBoostUnsupported) {
+    EXPECT_CALL(*mMockHal.get(), powerHint(_, _)).Times(0);
+    EXPECT_CALL(*mMockHal.get(), setInteractive(_)).Times(0);
+    EXPECT_CALL(*mMockHal.get(), setFeature(_, _)).Times(0);
+
     auto result = mWrapper->setBoost(Boost::CAMERA_LAUNCH, 10);
     ASSERT_TRUE(result.isUnsupported());
+    result = mWrapper->setBoost(Boost::ML_ACC, 10);
+    ASSERT_TRUE(result.isUnsupported());
+    result = mWrapper->setBoost(Boost::DISPLAY_UPDATE_IMMINENT, 10);
+    ASSERT_TRUE(result.isUnsupported());
 }
 
 TEST_F(PowerHalWrapperHidlV1_0Test, TestSetModeSuccessful) {
     {
         InSequence seq;
-        EXPECT_CALL(*mMockHal.get(), powerHint(Eq(PowerHint::LAUNCH), Eq(1))).Times(Exactly(1));
-        EXPECT_CALL(*mMockHal.get(), powerHint(Eq(PowerHint::LOW_POWER), Eq(0))).Times(Exactly(1));
-        EXPECT_CALL(*mMockHal.get(), powerHint(Eq(PowerHint::SUSTAINED_PERFORMANCE), Eq(1)))
+        EXPECT_CALL(*mMockHal.get(), powerHint(Eq(PowerHint::LAUNCH), Eq(true))).Times(Exactly(1));
+        EXPECT_CALL(*mMockHal.get(), powerHint(Eq(PowerHint::LOW_POWER), Eq(false)))
                 .Times(Exactly(1));
-        EXPECT_CALL(*mMockHal.get(), powerHint(Eq(PowerHint::VR_MODE), Eq(0))).Times(Exactly(1));
+        EXPECT_CALL(*mMockHal.get(), powerHint(Eq(PowerHint::SUSTAINED_PERFORMANCE), Eq(true)))
+                .Times(Exactly(1));
+        EXPECT_CALL(*mMockHal.get(), powerHint(Eq(PowerHint::VR_MODE), Eq(false)))
+                .Times(Exactly(1));
         EXPECT_CALL(*mMockHal.get(), setInteractive(Eq(true))).Times(Exactly(1));
         EXPECT_CALL(*mMockHal.get(),
                     setFeature(Eq(Feature::POWER_FEATURE_DOUBLE_TAP_TO_WAKE), Eq(false)))
@@ -131,6 +141,16 @@
 }
 
 TEST_F(PowerHalWrapperHidlV1_0Test, TestSetModeIgnored) {
+    EXPECT_CALL(*mMockHal.get(), powerHint(_, _)).Times(0);
+    EXPECT_CALL(*mMockHal.get(), setInteractive(_)).Times(0);
+    EXPECT_CALL(*mMockHal.get(), setFeature(_, _)).Times(0);
+
     auto result = mWrapper->setMode(Mode::CAMERA_STREAMING_HIGH, true);
     ASSERT_TRUE(result.isUnsupported());
+    result = mWrapper->setMode(Mode::EXPENSIVE_RENDERING, false);
+    ASSERT_TRUE(result.isUnsupported());
+    result = mWrapper->setMode(Mode::FIXED_PERFORMANCE, true);
+    ASSERT_TRUE(result.isUnsupported());
+    result = mWrapper->setMode(Mode::GAME_LOADING, false);
+    ASSERT_TRUE(result.isUnsupported());
 }
diff --git a/services/powermanager/tests/PowerHalWrapperHidlV1_1Test.cpp b/services/powermanager/tests/PowerHalWrapperHidlV1_1Test.cpp
index d30e8d2..32f84e2 100644
--- a/services/powermanager/tests/PowerHalWrapperHidlV1_1Test.cpp
+++ b/services/powermanager/tests/PowerHalWrapperHidlV1_1Test.cpp
@@ -31,7 +31,6 @@
 using android::hardware::power::V1_0::Feature;
 using android::hardware::power::V1_0::PowerHint;
 using IPowerV1_1 = android::hardware::power::V1_1::IPower;
-using IPowerV1_0 = android::hardware::power::V1_0::IPower;
 
 using namespace android;
 using namespace android::power;
@@ -40,15 +39,6 @@
 
 // -------------------------------------------------------------------------------------------------
 
-class MockIPowerV1_0 : public IPowerV1_0 {
-public:
-    MOCK_METHOD(hardware::Return<void>, setInteractive, (bool interactive), (override));
-    MOCK_METHOD(hardware::Return<void>, powerHint, (PowerHint hint, int32_t data), (override));
-    MOCK_METHOD(hardware::Return<void>, setFeature, (Feature feature, bool activate), (override));
-    MOCK_METHOD(hardware::Return<void>, getPlatformLowPowerStats,
-                (getPlatformLowPowerStats_cb _hidl_cb), (override));
-};
-
 class MockIPowerV1_1 : public IPowerV1_1 {
 public:
     MOCK_METHOD(hardware::Return<void>, setInteractive, (bool interactive), (override));
@@ -69,23 +59,22 @@
 
 protected:
     std::unique_ptr<HalWrapper> mWrapper = nullptr;
-    sp<StrictMock<MockIPowerV1_0>> mMockHalV1_0 = nullptr;
-    sp<StrictMock<MockIPowerV1_1>> mMockHalV1_1 = nullptr;
+    sp<StrictMock<MockIPowerV1_1>> mMockHal = nullptr;
 };
 
 // -------------------------------------------------------------------------------------------------
 
 void PowerHalWrapperHidlV1_1Test::SetUp() {
-    mMockHalV1_0 = new StrictMock<MockIPowerV1_0>();
-    mMockHalV1_1 = new StrictMock<MockIPowerV1_1>();
-    mWrapper = std::make_unique<HidlHalWrapperV1_1>(mMockHalV1_0, mMockHalV1_1);
+    mMockHal = new StrictMock<MockIPowerV1_1>();
+    mWrapper = std::make_unique<HidlHalWrapperV1_1>(mMockHal);
     ASSERT_NE(mWrapper, nullptr);
+    EXPECT_CALL(*mMockHal.get(), powerHint(_, _)).Times(0);
 }
 
 // -------------------------------------------------------------------------------------------------
 
 TEST_F(PowerHalWrapperHidlV1_1Test, TestSetBoostSuccessful) {
-    EXPECT_CALL(*mMockHalV1_1.get(), powerHintAsync(Eq(PowerHint::INTERACTION), Eq(1000)))
+    EXPECT_CALL(*mMockHal.get(), powerHintAsync(Eq(PowerHint::INTERACTION), Eq(1000)))
             .Times(Exactly(1));
 
     auto result = mWrapper->setBoost(Boost::INTERACTION, 1000);
@@ -93,7 +82,7 @@
 }
 
 TEST_F(PowerHalWrapperHidlV1_1Test, TestSetBoostFailed) {
-    EXPECT_CALL(*mMockHalV1_1.get(), powerHintAsync(Eq(PowerHint::INTERACTION), Eq(1000)))
+    EXPECT_CALL(*mMockHal.get(), powerHintAsync(Eq(PowerHint::INTERACTION), Eq(1000)))
             .Times(Exactly(1))
             .WillRepeatedly([](PowerHint, int32_t) {
                 return hardware::Return<void>(hardware::Status::fromExceptionCode(-1));
@@ -104,24 +93,31 @@
 }
 
 TEST_F(PowerHalWrapperHidlV1_1Test, TestSetBoostUnsupported) {
+    EXPECT_CALL(*mMockHal.get(), powerHintAsync(_, _)).Times(0);
+    EXPECT_CALL(*mMockHal.get(), setInteractive(_)).Times(0);
+    EXPECT_CALL(*mMockHal.get(), setFeature(_, _)).Times(0);
+
     auto result = mWrapper->setBoost(Boost::CAMERA_LAUNCH, 10);
     ASSERT_TRUE(result.isUnsupported());
+    result = mWrapper->setBoost(Boost::ML_ACC, 10);
+    ASSERT_TRUE(result.isUnsupported());
+    result = mWrapper->setBoost(Boost::DISPLAY_UPDATE_IMMINENT, 10);
+    ASSERT_TRUE(result.isUnsupported());
 }
 
 TEST_F(PowerHalWrapperHidlV1_1Test, TestSetMode) {
     {
         InSequence seq;
-        EXPECT_CALL(*mMockHalV1_1.get(), powerHintAsync(Eq(PowerHint::LAUNCH), Eq(1)))
+        EXPECT_CALL(*mMockHal.get(), powerHintAsync(Eq(PowerHint::LAUNCH), Eq(true)))
                 .Times(Exactly(1));
-        EXPECT_CALL(*mMockHalV1_1.get(), powerHintAsync(Eq(PowerHint::LOW_POWER), Eq(0)))
+        EXPECT_CALL(*mMockHal.get(), powerHintAsync(Eq(PowerHint::LOW_POWER), Eq(false)))
                 .Times(Exactly(1));
-        EXPECT_CALL(*mMockHalV1_1.get(),
-                    powerHintAsync(Eq(PowerHint::SUSTAINED_PERFORMANCE), Eq(1)))
+        EXPECT_CALL(*mMockHal.get(), powerHintAsync(Eq(PowerHint::SUSTAINED_PERFORMANCE), Eq(true)))
                 .Times(Exactly(1));
-        EXPECT_CALL(*mMockHalV1_1.get(), powerHintAsync(Eq(PowerHint::VR_MODE), Eq(0)))
+        EXPECT_CALL(*mMockHal.get(), powerHintAsync(Eq(PowerHint::VR_MODE), Eq(false)))
                 .Times(Exactly(1));
-        EXPECT_CALL(*mMockHalV1_0.get(), setInteractive(Eq(true))).Times(Exactly(1));
-        EXPECT_CALL(*mMockHalV1_0.get(),
+        EXPECT_CALL(*mMockHal.get(), setInteractive(Eq(true))).Times(Exactly(1));
+        EXPECT_CALL(*mMockHal.get(),
                     setFeature(Eq(Feature::POWER_FEATURE_DOUBLE_TAP_TO_WAKE), Eq(false)))
                 .Times(Exactly(1));
     }
@@ -141,7 +137,7 @@
 }
 
 TEST_F(PowerHalWrapperHidlV1_1Test, TestSetModeFailed) {
-    EXPECT_CALL(*mMockHalV1_1.get(), powerHintAsync(Eq(PowerHint::LAUNCH), Eq(1)))
+    EXPECT_CALL(*mMockHal.get(), powerHintAsync(Eq(PowerHint::LAUNCH), Eq(true)))
             .Times(Exactly(1))
             .WillRepeatedly([](PowerHint, int32_t) {
                 return hardware::Return<void>(hardware::Status::fromExceptionCode(-1));
@@ -152,6 +148,16 @@
 }
 
 TEST_F(PowerHalWrapperHidlV1_1Test, TestSetModeIgnored) {
+    EXPECT_CALL(*mMockHal.get(), powerHintAsync(_, _)).Times(0);
+    EXPECT_CALL(*mMockHal.get(), setInteractive(_)).Times(0);
+    EXPECT_CALL(*mMockHal.get(), setFeature(_, _)).Times(0);
+
     auto result = mWrapper->setMode(Mode::CAMERA_STREAMING_HIGH, true);
     ASSERT_TRUE(result.isUnsupported());
+    result = mWrapper->setMode(Mode::EXPENSIVE_RENDERING, false);
+    ASSERT_TRUE(result.isUnsupported());
+    result = mWrapper->setMode(Mode::FIXED_PERFORMANCE, true);
+    ASSERT_TRUE(result.isUnsupported());
+    result = mWrapper->setMode(Mode::GAME_LOADING, false);
+    ASSERT_TRUE(result.isUnsupported());
 }
diff --git a/services/powermanager/tests/PowerHalWrapperHidlV1_2Test.cpp b/services/powermanager/tests/PowerHalWrapperHidlV1_2Test.cpp
new file mode 100644
index 0000000..cf48409
--- /dev/null
+++ b/services/powermanager/tests/PowerHalWrapperHidlV1_2Test.cpp
@@ -0,0 +1,200 @@
+/*
+ * 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 LOG_TAG "PowerHalWrapperHidlV1_2Test"
+
+#include <android/hardware/power/1.2/IPower.h>
+#include <android/hardware/power/Boost.h>
+#include <android/hardware/power/IPower.h>
+#include <android/hardware/power/Mode.h>
+#include <binder/IServiceManager.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <powermanager/PowerHalWrapper.h>
+#include <utils/Log.h>
+
+using android::hardware::power::Boost;
+using android::hardware::power::Mode;
+using android::hardware::power::V1_0::Feature;
+using PowerHintV1_0 = android::hardware::power::V1_0::PowerHint;
+using PowerHintV1_2 = android::hardware::power::V1_2::PowerHint;
+
+using IPowerV1_2 = android::hardware::power::V1_2::IPower;
+
+using namespace android;
+using namespace android::power;
+using namespace std::chrono_literals;
+using namespace testing;
+
+// -------------------------------------------------------------------------------------------------
+
+class MockIPowerV1_2 : public IPowerV1_2 {
+public:
+    MOCK_METHOD(hardware::Return<void>, setInteractive, (bool interactive), (override));
+    MOCK_METHOD(hardware::Return<void>, powerHint, (PowerHintV1_0 hint, int32_t data), (override));
+    MOCK_METHOD(hardware::Return<void>, setFeature, (Feature feature, bool activate), (override));
+    MOCK_METHOD(hardware::Return<void>, getPlatformLowPowerStats,
+                (getPlatformLowPowerStats_cb _hidl_cb), (override));
+    MOCK_METHOD(hardware::Return<void>, powerHintAsync, (PowerHintV1_0 hint, int32_t data),
+                (override));
+    MOCK_METHOD(hardware::Return<void>, powerHintAsync_1_2, (PowerHintV1_2 hint, int32_t data),
+                (override));
+    MOCK_METHOD(hardware::Return<void>, getSubsystemLowPowerStats,
+                (getSubsystemLowPowerStats_cb _hidl_cb), (override));
+};
+
+// -------------------------------------------------------------------------------------------------
+
+class PowerHalWrapperHidlV1_2Test : public Test {
+public:
+    void SetUp() override;
+
+protected:
+    std::unique_ptr<HalWrapper> mWrapper = nullptr;
+    sp<StrictMock<MockIPowerV1_2>> mMockHal = nullptr;
+};
+
+// -------------------------------------------------------------------------------------------------
+
+void PowerHalWrapperHidlV1_2Test::SetUp() {
+    mMockHal = new StrictMock<MockIPowerV1_2>();
+    mWrapper = std::make_unique<HidlHalWrapperV1_2>(mMockHal);
+    ASSERT_NE(mWrapper, nullptr);
+    EXPECT_CALL(*mMockHal.get(), powerHint(_, _)).Times(0);
+    EXPECT_CALL(*mMockHal.get(), powerHintAsync(_, _)).Times(0);
+}
+
+// -------------------------------------------------------------------------------------------------
+
+TEST_F(PowerHalWrapperHidlV1_2Test, TestSetBoostSuccessful) {
+    {
+        InSequence seq;
+        EXPECT_CALL(*mMockHal.get(), powerHintAsync_1_2(Eq(PowerHintV1_2::INTERACTION), Eq(1000)))
+                .Times(Exactly(1));
+        EXPECT_CALL(*mMockHal.get(), powerHintAsync_1_2(Eq(PowerHintV1_2::CAMERA_SHOT), Eq(500)))
+                .Times(Exactly(1));
+        EXPECT_CALL(*mMockHal.get(), powerHintAsync_1_2(Eq(PowerHintV1_2::CAMERA_LAUNCH), Eq(300)))
+                .Times(Exactly(1));
+    }
+
+    auto result = mWrapper->setBoost(Boost::INTERACTION, 1000);
+    ASSERT_TRUE(result.isOk());
+    result = mWrapper->setBoost(Boost::CAMERA_SHOT, 500);
+    ASSERT_TRUE(result.isOk());
+    result = mWrapper->setBoost(Boost::CAMERA_LAUNCH, 300);
+    ASSERT_TRUE(result.isOk());
+}
+
+TEST_F(PowerHalWrapperHidlV1_2Test, TestSetBoostFailed) {
+    EXPECT_CALL(*mMockHal.get(), powerHintAsync_1_2(Eq(PowerHintV1_2::INTERACTION), Eq(1000)))
+            .Times(Exactly(1))
+            .WillRepeatedly([](PowerHintV1_2, int32_t) {
+                return hardware::Return<void>(hardware::Status::fromExceptionCode(-1));
+            });
+
+    auto result = mWrapper->setBoost(Boost::INTERACTION, 1000);
+    ASSERT_TRUE(result.isFailed());
+}
+
+TEST_F(PowerHalWrapperHidlV1_2Test, TestSetBoostUnsupported) {
+    EXPECT_CALL(*mMockHal.get(), powerHintAsync_1_2(_, _)).Times(0);
+    EXPECT_CALL(*mMockHal.get(), setInteractive(_)).Times(0);
+    EXPECT_CALL(*mMockHal.get(), setFeature(_, _)).Times(0);
+
+    auto result = mWrapper->setBoost(Boost::ML_ACC, 10);
+    ASSERT_TRUE(result.isUnsupported());
+    result = mWrapper->setBoost(Boost::DISPLAY_UPDATE_IMMINENT, 10);
+    ASSERT_TRUE(result.isUnsupported());
+    result = mWrapper->setBoost(Boost::AUDIO_LAUNCH, 10);
+    ASSERT_TRUE(result.isUnsupported());
+}
+
+TEST_F(PowerHalWrapperHidlV1_2Test, TestSetMode) {
+    {
+        InSequence seq;
+        EXPECT_CALL(*mMockHal.get(), powerHintAsync_1_2(Eq(PowerHintV1_2::LAUNCH), Eq(true)))
+                .Times(Exactly(1));
+        EXPECT_CALL(*mMockHal.get(), powerHintAsync_1_2(Eq(PowerHintV1_2::LOW_POWER), Eq(false)))
+                .Times(Exactly(1));
+        EXPECT_CALL(*mMockHal.get(),
+                    powerHintAsync_1_2(Eq(PowerHintV1_2::SUSTAINED_PERFORMANCE), Eq(true)))
+                .Times(Exactly(1));
+        EXPECT_CALL(*mMockHal.get(), powerHintAsync_1_2(Eq(PowerHintV1_2::VR_MODE), Eq(false)))
+                .Times(Exactly(1));
+        EXPECT_CALL(*mMockHal.get(), setInteractive(Eq(true))).Times(Exactly(true));
+        EXPECT_CALL(*mMockHal.get(),
+                    setFeature(Eq(Feature::POWER_FEATURE_DOUBLE_TAP_TO_WAKE), Eq(false)))
+                .Times(Exactly(1));
+        EXPECT_CALL(*mMockHal.get(),
+                    powerHintAsync_1_2(Eq(PowerHintV1_2::CAMERA_STREAMING), Eq(true)))
+                .Times(Exactly(2));
+        EXPECT_CALL(*mMockHal.get(),
+                    powerHintAsync_1_2(Eq(PowerHintV1_2::CAMERA_STREAMING), Eq(false)))
+                .Times(Exactly(2));
+        EXPECT_CALL(*mMockHal.get(),
+                    powerHintAsync_1_2(Eq(PowerHintV1_2::AUDIO_LOW_LATENCY), Eq(true)))
+                .Times(Exactly(1));
+    }
+
+    auto result = mWrapper->setMode(Mode::LAUNCH, true);
+    ASSERT_TRUE(result.isOk());
+    result = mWrapper->setMode(Mode::LOW_POWER, false);
+    ASSERT_TRUE(result.isOk());
+    result = mWrapper->setMode(Mode::SUSTAINED_PERFORMANCE, true);
+    ASSERT_TRUE(result.isOk());
+    result = mWrapper->setMode(Mode::VR, false);
+    ASSERT_TRUE(result.isOk());
+    result = mWrapper->setMode(Mode::INTERACTIVE, true);
+    ASSERT_TRUE(result.isOk());
+    result = mWrapper->setMode(Mode::DOUBLE_TAP_TO_WAKE, false);
+    ASSERT_TRUE(result.isOk());
+    result = mWrapper->setMode(Mode::CAMERA_STREAMING_SECURE, true);
+    ASSERT_TRUE(result.isOk());
+    result = mWrapper->setMode(Mode::CAMERA_STREAMING_LOW, true);
+    ASSERT_TRUE(result.isOk());
+    result = mWrapper->setMode(Mode::CAMERA_STREAMING_MID, false);
+    ASSERT_TRUE(result.isOk());
+    result = mWrapper->setMode(Mode::CAMERA_STREAMING_HIGH, false);
+    ASSERT_TRUE(result.isOk());
+    result = mWrapper->setMode(Mode::AUDIO_STREAMING_LOW_LATENCY, true);
+    ASSERT_TRUE(result.isOk());
+}
+
+TEST_F(PowerHalWrapperHidlV1_2Test, TestSetModeFailed) {
+    EXPECT_CALL(*mMockHal.get(), powerHintAsync_1_2(Eq(PowerHintV1_2::LAUNCH), Eq(1)))
+            .Times(Exactly(1))
+            .WillRepeatedly([](PowerHintV1_2, int32_t) {
+                return hardware::Return<void>(hardware::Status::fromExceptionCode(-1));
+            });
+
+    auto result = mWrapper->setMode(Mode::LAUNCH, 1);
+    ASSERT_TRUE(result.isFailed());
+}
+
+TEST_F(PowerHalWrapperHidlV1_2Test, TestSetModeIgnored) {
+    EXPECT_CALL(*mMockHal.get(), powerHintAsync_1_2(_, _)).Times(0);
+    EXPECT_CALL(*mMockHal.get(), setInteractive(_)).Times(0);
+    EXPECT_CALL(*mMockHal.get(), setFeature(_, _)).Times(0);
+
+    auto result = mWrapper->setMode(Mode::EXPENSIVE_RENDERING, true);
+    ASSERT_TRUE(result.isUnsupported());
+    result = mWrapper->setMode(Mode::DISPLAY_INACTIVE, true);
+    ASSERT_TRUE(result.isUnsupported());
+    result = mWrapper->setMode(Mode::FIXED_PERFORMANCE, true);
+    ASSERT_TRUE(result.isUnsupported());
+    result = mWrapper->setMode(Mode::GAME_LOADING, false);
+    ASSERT_TRUE(result.isUnsupported());
+}
diff --git a/services/powermanager/tests/PowerHalWrapperHidlV1_3Test.cpp b/services/powermanager/tests/PowerHalWrapperHidlV1_3Test.cpp
new file mode 100644
index 0000000..2c48537
--- /dev/null
+++ b/services/powermanager/tests/PowerHalWrapperHidlV1_3Test.cpp
@@ -0,0 +1,210 @@
+/*
+ * 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 LOG_TAG "PowerHalWrapperHidlV1_3Test"
+
+#include <android/hardware/power/1.3/IPower.h>
+#include <android/hardware/power/Boost.h>
+#include <android/hardware/power/IPower.h>
+#include <android/hardware/power/Mode.h>
+#include <binder/IServiceManager.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <powermanager/PowerHalWrapper.h>
+#include <utils/Log.h>
+
+using android::hardware::power::Boost;
+using android::hardware::power::Mode;
+using android::hardware::power::V1_0::Feature;
+using PowerHintV1_0 = android::hardware::power::V1_0::PowerHint;
+using PowerHintV1_2 = android::hardware::power::V1_2::PowerHint;
+using PowerHintV1_3 = android::hardware::power::V1_3::PowerHint;
+
+using IPowerV1_3 = android::hardware::power::V1_3::IPower;
+
+using namespace android;
+using namespace android::power;
+using namespace std::chrono_literals;
+using namespace testing;
+
+// -------------------------------------------------------------------------------------------------
+
+class MockIPowerV1_3 : public IPowerV1_3 {
+public:
+    MOCK_METHOD(hardware::Return<void>, setInteractive, (bool interactive), (override));
+    MOCK_METHOD(hardware::Return<void>, powerHint, (PowerHintV1_0 hint, int32_t data), (override));
+    MOCK_METHOD(hardware::Return<void>, setFeature, (Feature feature, bool activate), (override));
+    MOCK_METHOD(hardware::Return<void>, getPlatformLowPowerStats,
+                (getPlatformLowPowerStats_cb _hidl_cb), (override));
+    MOCK_METHOD(hardware::Return<void>, powerHintAsync, (PowerHintV1_0 hint, int32_t data),
+                (override));
+    MOCK_METHOD(hardware::Return<void>, powerHintAsync_1_2, (PowerHintV1_2 hint, int32_t data),
+                (override));
+    MOCK_METHOD(hardware::Return<void>, powerHintAsync_1_3, (PowerHintV1_3 hint, int32_t data),
+                (override));
+
+    MOCK_METHOD(hardware::Return<void>, getSubsystemLowPowerStats,
+                (getSubsystemLowPowerStats_cb _hidl_cb), (override));
+};
+
+// -------------------------------------------------------------------------------------------------
+
+class PowerHalWrapperHidlV1_3Test : public Test {
+public:
+    void SetUp() override;
+
+protected:
+    std::unique_ptr<HalWrapper> mWrapper = nullptr;
+    sp<StrictMock<MockIPowerV1_3>> mMockHal = nullptr;
+};
+
+// -------------------------------------------------------------------------------------------------
+
+void PowerHalWrapperHidlV1_3Test::SetUp() {
+    mMockHal = new StrictMock<MockIPowerV1_3>();
+    mWrapper = std::make_unique<HidlHalWrapperV1_3>(mMockHal);
+    ASSERT_NE(mWrapper, nullptr);
+    EXPECT_CALL(*mMockHal.get(), powerHint(_, _)).Times(0);
+    EXPECT_CALL(*mMockHal.get(), powerHintAsync(_, _)).Times(0);
+    EXPECT_CALL(*mMockHal.get(), powerHintAsync_1_2(_, _)).Times(0);
+}
+
+// -------------------------------------------------------------------------------------------------
+
+TEST_F(PowerHalWrapperHidlV1_3Test, TestSetBoostSuccessful) {
+    {
+        InSequence seq;
+        EXPECT_CALL(*mMockHal.get(), powerHintAsync_1_3(Eq(PowerHintV1_3::INTERACTION), Eq(1000)))
+                .Times(Exactly(1));
+        EXPECT_CALL(*mMockHal.get(), powerHintAsync_1_3(Eq(PowerHintV1_3::CAMERA_SHOT), Eq(500)))
+                .Times(Exactly(1));
+        EXPECT_CALL(*mMockHal.get(), powerHintAsync_1_3(Eq(PowerHintV1_3::CAMERA_LAUNCH), Eq(300)))
+                .Times(Exactly(1));
+    }
+
+    auto result = mWrapper->setBoost(Boost::INTERACTION, 1000);
+    ASSERT_TRUE(result.isOk());
+    result = mWrapper->setBoost(Boost::CAMERA_SHOT, 500);
+    ASSERT_TRUE(result.isOk());
+    result = mWrapper->setBoost(Boost::CAMERA_LAUNCH, 300);
+    ASSERT_TRUE(result.isOk());
+}
+
+TEST_F(PowerHalWrapperHidlV1_3Test, TestSetBoostFailed) {
+    EXPECT_CALL(*mMockHal.get(), powerHintAsync_1_3(Eq(PowerHintV1_3::INTERACTION), Eq(1000)))
+            .Times(Exactly(1))
+            .WillRepeatedly([](PowerHintV1_3, int32_t) {
+                return hardware::Return<void>(hardware::Status::fromExceptionCode(-1));
+            });
+
+    auto result = mWrapper->setBoost(Boost::INTERACTION, 1000);
+    ASSERT_TRUE(result.isFailed());
+}
+
+TEST_F(PowerHalWrapperHidlV1_3Test, TestSetBoostUnsupported) {
+    EXPECT_CALL(*mMockHal.get(), powerHintAsync_1_3(_, _)).Times(0);
+    EXPECT_CALL(*mMockHal.get(), setInteractive(_)).Times(0);
+    EXPECT_CALL(*mMockHal.get(), setFeature(_, _)).Times(0);
+
+    auto result = mWrapper->setBoost(Boost::ML_ACC, 10);
+    ASSERT_TRUE(result.isUnsupported());
+    result = mWrapper->setBoost(Boost::DISPLAY_UPDATE_IMMINENT, 10);
+    ASSERT_TRUE(result.isUnsupported());
+    result = mWrapper->setBoost(Boost::AUDIO_LAUNCH, 10);
+    ASSERT_TRUE(result.isUnsupported());
+}
+
+TEST_F(PowerHalWrapperHidlV1_3Test, TestSetMode) {
+    {
+        InSequence seq;
+        EXPECT_CALL(*mMockHal.get(), powerHintAsync_1_3(Eq(PowerHintV1_3::LAUNCH), Eq(true)))
+                .Times(Exactly(1));
+        EXPECT_CALL(*mMockHal.get(), powerHintAsync_1_3(Eq(PowerHintV1_3::LOW_POWER), Eq(false)))
+                .Times(Exactly(1));
+        EXPECT_CALL(*mMockHal.get(),
+                    powerHintAsync_1_3(Eq(PowerHintV1_3::SUSTAINED_PERFORMANCE), Eq(true)))
+                .Times(Exactly(1));
+        EXPECT_CALL(*mMockHal.get(), powerHintAsync_1_3(Eq(PowerHintV1_3::VR_MODE), Eq(false)))
+                .Times(Exactly(1));
+        EXPECT_CALL(*mMockHal.get(), setInteractive(Eq(true))).Times(Exactly(true));
+        EXPECT_CALL(*mMockHal.get(),
+                    setFeature(Eq(Feature::POWER_FEATURE_DOUBLE_TAP_TO_WAKE), Eq(false)))
+                .Times(Exactly(1));
+        EXPECT_CALL(*mMockHal.get(),
+                    powerHintAsync_1_3(Eq(PowerHintV1_3::CAMERA_STREAMING), Eq(true)))
+                .Times(Exactly(2));
+        EXPECT_CALL(*mMockHal.get(),
+                    powerHintAsync_1_3(Eq(PowerHintV1_3::CAMERA_STREAMING), Eq(false)))
+                .Times(Exactly(2));
+        EXPECT_CALL(*mMockHal.get(),
+                    powerHintAsync_1_3(Eq(PowerHintV1_3::AUDIO_LOW_LATENCY), Eq(true)))
+                .Times(Exactly(1));
+        EXPECT_CALL(*mMockHal.get(),
+                    powerHintAsync_1_3(Eq(PowerHintV1_3::EXPENSIVE_RENDERING), Eq(false)))
+                .Times(Exactly(1));
+    }
+
+    auto result = mWrapper->setMode(Mode::LAUNCH, true);
+    ASSERT_TRUE(result.isOk());
+    result = mWrapper->setMode(Mode::LOW_POWER, false);
+    ASSERT_TRUE(result.isOk());
+    result = mWrapper->setMode(Mode::SUSTAINED_PERFORMANCE, true);
+    ASSERT_TRUE(result.isOk());
+    result = mWrapper->setMode(Mode::VR, false);
+    ASSERT_TRUE(result.isOk());
+    result = mWrapper->setMode(Mode::INTERACTIVE, true);
+    ASSERT_TRUE(result.isOk());
+    result = mWrapper->setMode(Mode::DOUBLE_TAP_TO_WAKE, false);
+    ASSERT_TRUE(result.isOk());
+    result = mWrapper->setMode(Mode::CAMERA_STREAMING_SECURE, true);
+    ASSERT_TRUE(result.isOk());
+    result = mWrapper->setMode(Mode::CAMERA_STREAMING_LOW, true);
+    ASSERT_TRUE(result.isOk());
+    result = mWrapper->setMode(Mode::CAMERA_STREAMING_MID, false);
+    ASSERT_TRUE(result.isOk());
+    result = mWrapper->setMode(Mode::CAMERA_STREAMING_HIGH, false);
+    ASSERT_TRUE(result.isOk());
+    result = mWrapper->setMode(Mode::AUDIO_STREAMING_LOW_LATENCY, true);
+    ASSERT_TRUE(result.isOk());
+    result = mWrapper->setMode(Mode::EXPENSIVE_RENDERING, false);
+    ASSERT_TRUE(result.isOk());
+}
+
+TEST_F(PowerHalWrapperHidlV1_3Test, TestSetModeFailed) {
+    EXPECT_CALL(*mMockHal.get(), powerHintAsync_1_3(Eq(PowerHintV1_3::LAUNCH), Eq(1)))
+            .Times(Exactly(1))
+            .WillRepeatedly([](PowerHintV1_3, int32_t) {
+                return hardware::Return<void>(hardware::Status::fromExceptionCode(-1));
+            });
+
+    auto result = mWrapper->setMode(Mode::LAUNCH, 1);
+    ASSERT_TRUE(result.isFailed());
+}
+
+TEST_F(PowerHalWrapperHidlV1_3Test, TestSetModeIgnored) {
+    EXPECT_CALL(*mMockHal.get(), powerHintAsync_1_3(_, _)).Times(0);
+    EXPECT_CALL(*mMockHal.get(), setInteractive(_)).Times(0);
+    EXPECT_CALL(*mMockHal.get(), setFeature(_, _)).Times(0);
+
+    auto result = mWrapper->setMode(Mode::GAME, true);
+    ASSERT_TRUE(result.isUnsupported());
+    result = mWrapper->setMode(Mode::DISPLAY_INACTIVE, true);
+    ASSERT_TRUE(result.isUnsupported());
+    result = mWrapper->setMode(Mode::FIXED_PERFORMANCE, true);
+    ASSERT_TRUE(result.isUnsupported());
+    result = mWrapper->setMode(Mode::GAME_LOADING, false);
+    ASSERT_TRUE(result.isUnsupported());
+}
diff --git a/services/sensorservice/AidlSensorHalWrapper.cpp b/services/sensorservice/AidlSensorHalWrapper.cpp
index f67c610..f5b360f 100644
--- a/services/sensorservice/AidlSensorHalWrapper.cpp
+++ b/services/sensorservice/AidlSensorHalWrapper.cpp
@@ -23,6 +23,7 @@
 #include <aidlcommonsupport/NativeHandle.h>
 #include <android-base/logging.h>
 #include <android/binder_manager.h>
+#include <aidl/sensors/convert.h>
 
 using ::aidl::android::hardware::sensors::AdditionalInfo;
 using ::aidl::android::hardware::sensors::DynamicSensorInfo;
@@ -34,469 +35,16 @@
 using ::android::AidlMessageQueue;
 using ::android::hardware::EventFlag;
 using ::android::hardware::sensors::V2_1::implementation::MAX_RECEIVE_BUFFER_EVENT_COUNT;
+using ::android::hardware::sensors::implementation::convertToStatus;
+using ::android::hardware::sensors::implementation::convertToSensor;
+using ::android::hardware::sensors::implementation::convertToSensorEvent;
+using ::android::hardware::sensors::implementation::convertFromSensorEvent;
+
 
 namespace android {
 
 namespace {
 
-status_t convertToStatus(ndk::ScopedAStatus status) {
-    if (status.isOk()) {
-        return OK;
-    } else {
-        switch (status.getExceptionCode()) {
-            case EX_ILLEGAL_ARGUMENT: {
-                return BAD_VALUE;
-            }
-            case EX_SECURITY: {
-                return PERMISSION_DENIED;
-            }
-            case EX_UNSUPPORTED_OPERATION: {
-                return INVALID_OPERATION;
-            }
-            case EX_SERVICE_SPECIFIC: {
-                switch (status.getServiceSpecificError()) {
-                    case ISensors::ERROR_BAD_VALUE: {
-                        return BAD_VALUE;
-                    }
-                    case ISensors::ERROR_NO_MEMORY: {
-                        return NO_MEMORY;
-                    }
-                    default: {
-                        return UNKNOWN_ERROR;
-                    }
-                }
-            }
-            default: {
-                return UNKNOWN_ERROR;
-            }
-        }
-    }
-}
-
-void convertToSensor(const SensorInfo &src, sensor_t *dst) {
-    dst->name = strdup(src.name.c_str());
-    dst->vendor = strdup(src.vendor.c_str());
-    dst->version = src.version;
-    dst->handle = src.sensorHandle;
-    dst->type = (int)src.type;
-    dst->maxRange = src.maxRange;
-    dst->resolution = src.resolution;
-    dst->power = src.power;
-    dst->minDelay = src.minDelayUs;
-    dst->fifoReservedEventCount = src.fifoReservedEventCount;
-    dst->fifoMaxEventCount = src.fifoMaxEventCount;
-    dst->stringType = strdup(src.typeAsString.c_str());
-    dst->requiredPermission = strdup(src.requiredPermission.c_str());
-    dst->maxDelay = src.maxDelayUs;
-    dst->flags = src.flags;
-    dst->reserved[0] = dst->reserved[1] = 0;
-}
-
-void convertToSensorEvent(const Event &src, sensors_event_t *dst) {
-    *dst = {.version = sizeof(sensors_event_t),
-            .sensor = src.sensorHandle,
-            .type = (int32_t)src.sensorType,
-            .reserved0 = 0,
-            .timestamp = src.timestamp};
-
-    switch (src.sensorType) {
-        case SensorType::META_DATA: {
-            // Legacy HALs expect the handle reference in the meta data field.
-            // Copy it over from the handle of the event.
-            dst->meta_data.what = (int32_t)src.payload.get<Event::EventPayload::meta>().what;
-            dst->meta_data.sensor = src.sensorHandle;
-            // Set the sensor handle to 0 to maintain compatibility.
-            dst->sensor = 0;
-            break;
-        }
-
-        case SensorType::ACCELEROMETER:
-        case SensorType::MAGNETIC_FIELD:
-        case SensorType::ORIENTATION:
-        case SensorType::GYROSCOPE:
-        case SensorType::GRAVITY:
-        case SensorType::LINEAR_ACCELERATION: {
-            dst->acceleration.x = src.payload.get<Event::EventPayload::vec3>().x;
-            dst->acceleration.y = src.payload.get<Event::EventPayload::vec3>().y;
-            dst->acceleration.z = src.payload.get<Event::EventPayload::vec3>().z;
-            dst->acceleration.status = (int32_t)src.payload.get<Event::EventPayload::vec3>().status;
-            break;
-        }
-
-        case SensorType::GAME_ROTATION_VECTOR: {
-            dst->data[0] = src.payload.get<Event::EventPayload::vec4>().x;
-            dst->data[1] = src.payload.get<Event::EventPayload::vec4>().y;
-            dst->data[2] = src.payload.get<Event::EventPayload::vec4>().z;
-            dst->data[3] = src.payload.get<Event::EventPayload::vec4>().w;
-            break;
-        }
-
-        case SensorType::ROTATION_VECTOR:
-        case SensorType::GEOMAGNETIC_ROTATION_VECTOR: {
-            dst->data[0] = src.payload.get<Event::EventPayload::data>().values[0];
-            dst->data[1] = src.payload.get<Event::EventPayload::data>().values[1];
-            dst->data[2] = src.payload.get<Event::EventPayload::data>().values[2];
-            dst->data[3] = src.payload.get<Event::EventPayload::data>().values[3];
-            dst->data[4] = src.payload.get<Event::EventPayload::data>().values[4];
-            break;
-        }
-
-        case SensorType::MAGNETIC_FIELD_UNCALIBRATED:
-        case SensorType::GYROSCOPE_UNCALIBRATED:
-        case SensorType::ACCELEROMETER_UNCALIBRATED: {
-            dst->uncalibrated_gyro.x_uncalib = src.payload.get<Event::EventPayload::uncal>().x;
-            dst->uncalibrated_gyro.y_uncalib = src.payload.get<Event::EventPayload::uncal>().y;
-            dst->uncalibrated_gyro.z_uncalib = src.payload.get<Event::EventPayload::uncal>().z;
-            dst->uncalibrated_gyro.x_bias = src.payload.get<Event::EventPayload::uncal>().xBias;
-            dst->uncalibrated_gyro.y_bias = src.payload.get<Event::EventPayload::uncal>().yBias;
-            dst->uncalibrated_gyro.z_bias = src.payload.get<Event::EventPayload::uncal>().zBias;
-            break;
-        }
-
-        case SensorType::HINGE_ANGLE:
-        case SensorType::DEVICE_ORIENTATION:
-        case SensorType::LIGHT:
-        case SensorType::PRESSURE:
-        case SensorType::PROXIMITY:
-        case SensorType::RELATIVE_HUMIDITY:
-        case SensorType::AMBIENT_TEMPERATURE:
-        case SensorType::SIGNIFICANT_MOTION:
-        case SensorType::STEP_DETECTOR:
-        case SensorType::TILT_DETECTOR:
-        case SensorType::WAKE_GESTURE:
-        case SensorType::GLANCE_GESTURE:
-        case SensorType::PICK_UP_GESTURE:
-        case SensorType::WRIST_TILT_GESTURE:
-        case SensorType::STATIONARY_DETECT:
-        case SensorType::MOTION_DETECT:
-        case SensorType::HEART_BEAT:
-        case SensorType::LOW_LATENCY_OFFBODY_DETECT: {
-            dst->data[0] = src.payload.get<Event::EventPayload::scalar>();
-            break;
-        }
-
-        case SensorType::STEP_COUNTER: {
-            dst->u64.step_counter = src.payload.get<Event::EventPayload::stepCount>();
-            break;
-        }
-
-        case SensorType::HEART_RATE: {
-            dst->heart_rate.bpm = src.payload.get<Event::EventPayload::heartRate>().bpm;
-            dst->heart_rate.status =
-                    (int8_t)src.payload.get<Event::EventPayload::heartRate>().status;
-            break;
-        }
-
-        case SensorType::POSE_6DOF: { // 15 floats
-            for (size_t i = 0; i < 15; ++i) {
-                dst->data[i] = src.payload.get<Event::EventPayload::pose6DOF>().values[i];
-            }
-            break;
-        }
-
-        case SensorType::DYNAMIC_SENSOR_META: {
-            dst->dynamic_sensor_meta.connected =
-                    src.payload.get<Event::EventPayload::dynamic>().connected;
-            dst->dynamic_sensor_meta.handle =
-                    src.payload.get<Event::EventPayload::dynamic>().sensorHandle;
-            dst->dynamic_sensor_meta.sensor = NULL; // to be filled in later
-
-            memcpy(dst->dynamic_sensor_meta.uuid,
-                   src.payload.get<Event::EventPayload::dynamic>().uuid.values.data(), 16);
-
-            break;
-        }
-
-        case SensorType::ADDITIONAL_INFO: {
-            const AdditionalInfo &srcInfo = src.payload.get<Event::EventPayload::additional>();
-
-            additional_info_event_t *dstInfo = &dst->additional_info;
-            dstInfo->type = (int32_t)srcInfo.type;
-            dstInfo->serial = srcInfo.serial;
-
-            switch (srcInfo.payload.getTag()) {
-                case AdditionalInfo::AdditionalInfoPayload::Tag::dataInt32: {
-                    const auto &values =
-                            srcInfo.payload.get<AdditionalInfo::AdditionalInfoPayload::dataInt32>()
-                                    .values;
-                    CHECK_EQ(values.size() * sizeof(int32_t), sizeof(dstInfo->data_int32));
-                    memcpy(dstInfo->data_int32, values.data(), sizeof(dstInfo->data_int32));
-                    break;
-                }
-                case AdditionalInfo::AdditionalInfoPayload::Tag::dataFloat: {
-                    const auto &values =
-                            srcInfo.payload.get<AdditionalInfo::AdditionalInfoPayload::dataFloat>()
-                                    .values;
-                    CHECK_EQ(values.size() * sizeof(float), sizeof(dstInfo->data_float));
-                    memcpy(dstInfo->data_float, values.data(), sizeof(dstInfo->data_float));
-                    break;
-                }
-                default: {
-                    ALOGE("Invalid sensor additional info tag: %d", (int)srcInfo.payload.getTag());
-                }
-            }
-            break;
-        }
-
-        case SensorType::HEAD_TRACKER: {
-            const auto &ht = src.payload.get<Event::EventPayload::headTracker>();
-            dst->head_tracker.rx = ht.rx;
-            dst->head_tracker.ry = ht.ry;
-            dst->head_tracker.rz = ht.rz;
-            dst->head_tracker.vx = ht.vx;
-            dst->head_tracker.vy = ht.vy;
-            dst->head_tracker.vz = ht.vz;
-            dst->head_tracker.discontinuity_count = ht.discontinuityCount;
-            break;
-        }
-
-        case SensorType::ACCELEROMETER_LIMITED_AXES:
-        case SensorType::GYROSCOPE_LIMITED_AXES:
-            dst->limited_axes_imu.x = src.payload.get<Event::EventPayload::limitedAxesImu>().x;
-            dst->limited_axes_imu.y = src.payload.get<Event::EventPayload::limitedAxesImu>().y;
-            dst->limited_axes_imu.z = src.payload.get<Event::EventPayload::limitedAxesImu>().z;
-            dst->limited_axes_imu.x_supported =
-                    src.payload.get<Event::EventPayload::limitedAxesImu>().xSupported;
-            dst->limited_axes_imu.y_supported =
-                    src.payload.get<Event::EventPayload::limitedAxesImu>().ySupported;
-            dst->limited_axes_imu.z_supported =
-                    src.payload.get<Event::EventPayload::limitedAxesImu>().zSupported;
-            break;
-
-        case SensorType::ACCELEROMETER_LIMITED_AXES_UNCALIBRATED:
-        case SensorType::GYROSCOPE_LIMITED_AXES_UNCALIBRATED:
-            dst->limited_axes_imu_uncalibrated.x_uncalib =
-                    src.payload.get<Event::EventPayload::limitedAxesImuUncal>().x;
-            dst->limited_axes_imu_uncalibrated.y_uncalib =
-                    src.payload.get<Event::EventPayload::limitedAxesImuUncal>().y;
-            dst->limited_axes_imu_uncalibrated.z_uncalib =
-                    src.payload.get<Event::EventPayload::limitedAxesImuUncal>().z;
-            dst->limited_axes_imu_uncalibrated.x_bias =
-                    src.payload.get<Event::EventPayload::limitedAxesImuUncal>().xBias;
-            dst->limited_axes_imu_uncalibrated.y_bias =
-                    src.payload.get<Event::EventPayload::limitedAxesImuUncal>().yBias;
-            dst->limited_axes_imu_uncalibrated.z_bias =
-                    src.payload.get<Event::EventPayload::limitedAxesImuUncal>().zBias;
-            dst->limited_axes_imu_uncalibrated.x_supported =
-                    src.payload.get<Event::EventPayload::limitedAxesImuUncal>().xSupported;
-            dst->limited_axes_imu_uncalibrated.y_supported =
-                    src.payload.get<Event::EventPayload::limitedAxesImuUncal>().ySupported;
-            dst->limited_axes_imu_uncalibrated.z_supported =
-                    src.payload.get<Event::EventPayload::limitedAxesImuUncal>().zSupported;
-            break;
-
-        case SensorType::HEADING:
-            dst->heading.heading = src.payload.get<Event::EventPayload::heading>().heading;
-            dst->heading.accuracy = src.payload.get<Event::EventPayload::heading>().accuracy;
-            break;
-
-        default: {
-            CHECK_GE((int32_t)src.sensorType, (int32_t)SensorType::DEVICE_PRIVATE_BASE);
-
-            memcpy(dst->data, src.payload.get<Event::EventPayload::data>().values.data(),
-                   16 * sizeof(float));
-            break;
-        }
-    }
-}
-
-void convertFromSensorEvent(const sensors_event_t &src, Event *dst) {
-    *dst = {
-            .timestamp = src.timestamp,
-            .sensorHandle = src.sensor,
-            .sensorType = (SensorType)src.type,
-    };
-
-    switch (dst->sensorType) {
-        case SensorType::META_DATA: {
-            Event::EventPayload::MetaData meta;
-            meta.what = (Event::EventPayload::MetaData::MetaDataEventType)src.meta_data.what;
-            // Legacy HALs contain the handle reference in the meta data field.
-            // Copy that over to the handle of the event. In legacy HALs this
-            // field was expected to be 0.
-            dst->sensorHandle = src.meta_data.sensor;
-            dst->payload.set<Event::EventPayload::Tag::meta>(meta);
-            break;
-        }
-
-        case SensorType::ACCELEROMETER:
-        case SensorType::MAGNETIC_FIELD:
-        case SensorType::ORIENTATION:
-        case SensorType::GYROSCOPE:
-        case SensorType::GRAVITY:
-        case SensorType::LINEAR_ACCELERATION: {
-            Event::EventPayload::Vec3 vec3;
-            vec3.x = src.acceleration.x;
-            vec3.y = src.acceleration.y;
-            vec3.z = src.acceleration.z;
-            vec3.status = (SensorStatus)src.acceleration.status;
-            dst->payload.set<Event::EventPayload::Tag::vec3>(vec3);
-            break;
-        }
-
-        case SensorType::GAME_ROTATION_VECTOR: {
-            Event::EventPayload::Vec4 vec4;
-            vec4.x = src.data[0];
-            vec4.y = src.data[1];
-            vec4.z = src.data[2];
-            vec4.w = src.data[3];
-            dst->payload.set<Event::EventPayload::Tag::vec4>(vec4);
-            break;
-        }
-
-        case SensorType::ROTATION_VECTOR:
-        case SensorType::GEOMAGNETIC_ROTATION_VECTOR: {
-            Event::EventPayload::Data data;
-            memcpy(data.values.data(), src.data, 5 * sizeof(float));
-            dst->payload.set<Event::EventPayload::Tag::data>(data);
-            break;
-        }
-
-        case SensorType::MAGNETIC_FIELD_UNCALIBRATED:
-        case SensorType::GYROSCOPE_UNCALIBRATED:
-        case SensorType::ACCELEROMETER_UNCALIBRATED: {
-            Event::EventPayload::Uncal uncal;
-            uncal.x = src.uncalibrated_gyro.x_uncalib;
-            uncal.y = src.uncalibrated_gyro.y_uncalib;
-            uncal.z = src.uncalibrated_gyro.z_uncalib;
-            uncal.xBias = src.uncalibrated_gyro.x_bias;
-            uncal.yBias = src.uncalibrated_gyro.y_bias;
-            uncal.zBias = src.uncalibrated_gyro.z_bias;
-            dst->payload.set<Event::EventPayload::Tag::uncal>(uncal);
-            break;
-        }
-
-        case SensorType::DEVICE_ORIENTATION:
-        case SensorType::LIGHT:
-        case SensorType::PRESSURE:
-        case SensorType::PROXIMITY:
-        case SensorType::RELATIVE_HUMIDITY:
-        case SensorType::AMBIENT_TEMPERATURE:
-        case SensorType::SIGNIFICANT_MOTION:
-        case SensorType::STEP_DETECTOR:
-        case SensorType::TILT_DETECTOR:
-        case SensorType::WAKE_GESTURE:
-        case SensorType::GLANCE_GESTURE:
-        case SensorType::PICK_UP_GESTURE:
-        case SensorType::WRIST_TILT_GESTURE:
-        case SensorType::STATIONARY_DETECT:
-        case SensorType::MOTION_DETECT:
-        case SensorType::HEART_BEAT:
-        case SensorType::LOW_LATENCY_OFFBODY_DETECT:
-        case SensorType::HINGE_ANGLE: {
-            dst->payload.set<Event::EventPayload::Tag::scalar>((float)src.data[0]);
-            break;
-        }
-
-        case SensorType::STEP_COUNTER: {
-            dst->payload.set<Event::EventPayload::Tag::stepCount>(src.u64.step_counter);
-            break;
-        }
-
-        case SensorType::HEART_RATE: {
-            Event::EventPayload::HeartRate heartRate;
-            heartRate.bpm = src.heart_rate.bpm;
-            heartRate.status = (SensorStatus)src.heart_rate.status;
-            dst->payload.set<Event::EventPayload::Tag::heartRate>(heartRate);
-            break;
-        }
-
-        case SensorType::POSE_6DOF: { // 15 floats
-            Event::EventPayload::Pose6Dof pose6DOF;
-            for (size_t i = 0; i < 15; ++i) {
-                pose6DOF.values[i] = src.data[i];
-            }
-            dst->payload.set<Event::EventPayload::Tag::pose6DOF>(pose6DOF);
-            break;
-        }
-
-        case SensorType::DYNAMIC_SENSOR_META: {
-            DynamicSensorInfo dynamic;
-            dynamic.connected = src.dynamic_sensor_meta.connected;
-            dynamic.sensorHandle = src.dynamic_sensor_meta.handle;
-
-            memcpy(dynamic.uuid.values.data(), src.dynamic_sensor_meta.uuid, 16);
-            dst->payload.set<Event::EventPayload::Tag::dynamic>(dynamic);
-            break;
-        }
-
-        case SensorType::ADDITIONAL_INFO: {
-            AdditionalInfo info;
-            const additional_info_event_t &srcInfo = src.additional_info;
-            info.type = (AdditionalInfo::AdditionalInfoType)srcInfo.type;
-            info.serial = srcInfo.serial;
-
-            AdditionalInfo::AdditionalInfoPayload::Int32Values data;
-            CHECK_EQ(data.values.size() * sizeof(int32_t), sizeof(srcInfo.data_int32));
-            memcpy(data.values.data(), srcInfo.data_int32, sizeof(srcInfo.data_int32));
-            info.payload.set<AdditionalInfo::AdditionalInfoPayload::Tag::dataInt32>(data);
-
-            dst->payload.set<Event::EventPayload::Tag::additional>(info);
-            break;
-        }
-
-        case SensorType::HEAD_TRACKER: {
-            Event::EventPayload::HeadTracker headTracker;
-            headTracker.rx = src.head_tracker.rx;
-            headTracker.ry = src.head_tracker.ry;
-            headTracker.rz = src.head_tracker.rz;
-            headTracker.vx = src.head_tracker.vx;
-            headTracker.vy = src.head_tracker.vy;
-            headTracker.vz = src.head_tracker.vz;
-            headTracker.discontinuityCount = src.head_tracker.discontinuity_count;
-
-            dst->payload.set<Event::EventPayload::Tag::headTracker>(headTracker);
-            break;
-        }
-
-        case SensorType::ACCELEROMETER_LIMITED_AXES:
-        case SensorType::GYROSCOPE_LIMITED_AXES: {
-            Event::EventPayload::LimitedAxesImu limitedAxesImu;
-            limitedAxesImu.x = src.limited_axes_imu.x;
-            limitedAxesImu.y = src.limited_axes_imu.y;
-            limitedAxesImu.z = src.limited_axes_imu.z;
-            limitedAxesImu.xSupported = src.limited_axes_imu.x_supported;
-            limitedAxesImu.ySupported = src.limited_axes_imu.y_supported;
-            limitedAxesImu.zSupported = src.limited_axes_imu.z_supported;
-            dst->payload.set<Event::EventPayload::Tag::limitedAxesImu>(limitedAxesImu);
-            break;
-        }
-
-        case SensorType::ACCELEROMETER_LIMITED_AXES_UNCALIBRATED:
-        case SensorType::GYROSCOPE_LIMITED_AXES_UNCALIBRATED: {
-            Event::EventPayload::LimitedAxesImuUncal limitedAxesImuUncal;
-            limitedAxesImuUncal.x = src.limited_axes_imu_uncalibrated.x_uncalib;
-            limitedAxesImuUncal.y = src.limited_axes_imu_uncalibrated.y_uncalib;
-            limitedAxesImuUncal.z = src.limited_axes_imu_uncalibrated.z_uncalib;
-            limitedAxesImuUncal.xBias = src.limited_axes_imu_uncalibrated.x_bias;
-            limitedAxesImuUncal.yBias = src.limited_axes_imu_uncalibrated.y_bias;
-            limitedAxesImuUncal.zBias = src.limited_axes_imu_uncalibrated.z_bias;
-            limitedAxesImuUncal.xSupported = src.limited_axes_imu_uncalibrated.x_supported;
-            limitedAxesImuUncal.ySupported = src.limited_axes_imu_uncalibrated.y_supported;
-            limitedAxesImuUncal.zSupported = src.limited_axes_imu_uncalibrated.z_supported;
-            dst->payload.set<Event::EventPayload::Tag::limitedAxesImuUncal>(limitedAxesImuUncal);
-            break;
-        }
-
-        case SensorType::HEADING: {
-            Event::EventPayload::Heading heading;
-            heading.heading = src.heading.heading;
-            heading.accuracy = src.heading.accuracy;
-            dst->payload.set<Event::EventPayload::heading>(heading);
-            break;
-        }
-
-        default: {
-            CHECK_GE((int32_t)dst->sensorType, (int32_t)SensorType::DEVICE_PRIVATE_BASE);
-
-            Event::EventPayload::Data data;
-            memcpy(data.values.data(), src.data, 16 * sizeof(float));
-            dst->payload.set<Event::EventPayload::Tag::data>(data);
-            break;
-        }
-    }
-}
-
 void serviceDied(void *cookie) {
     ALOGW("Sensors HAL died, attempting to reconnect.");
     ((AidlSensorHalWrapper *)cookie)->prepareForReconnect();
diff --git a/services/sensorservice/Android.bp b/services/sensorservice/Android.bp
index 5ad4815..0eeb820 100644
--- a/services/sensorservice/Android.bp
+++ b/services/sensorservice/Android.bp
@@ -75,6 +75,7 @@
     static_libs: [
         "libaidlcommonsupport",
         "android.hardware.sensors@1.0-convert",
+        "android.hardware.sensors-V1-convert",
         "android.hardware.sensors-V1-ndk",
     ],
 
diff --git a/services/sensorservice/SensorInterface.cpp b/services/sensorservice/SensorInterface.cpp
index 46f00e8..398cdf9 100644
--- a/services/sensorservice/SensorInterface.cpp
+++ b/services/sensorservice/SensorInterface.cpp
@@ -87,6 +87,42 @@
 
 // ---------------------------------------------------------------------------
 
+RuntimeSensor::RuntimeSensor(const sensor_t& sensor, sp<StateChangeCallback> callback)
+  : BaseSensor(sensor), mCallback(std::move(callback)) {
+}
+
+status_t RuntimeSensor::activate(void*, bool enabled) {
+    if (enabled != mEnabled) {
+        mEnabled = enabled;
+        mCallback->onStateChanged(mEnabled, mSamplingPeriodNs, mBatchReportLatencyNs);
+    }
+    return OK;
+}
+
+status_t RuntimeSensor::batch(void*, int, int, int64_t samplingPeriodNs,
+                              int64_t maxBatchReportLatencyNs) {
+    if (mSamplingPeriodNs != samplingPeriodNs || mBatchReportLatencyNs != maxBatchReportLatencyNs) {
+        mSamplingPeriodNs = samplingPeriodNs;
+        mBatchReportLatencyNs = maxBatchReportLatencyNs;
+        if (mEnabled) {
+            mCallback->onStateChanged(mEnabled, mSamplingPeriodNs, mBatchReportLatencyNs);
+        }
+    }
+    return OK;
+}
+
+status_t RuntimeSensor::setDelay(void*, int, int64_t ns) {
+    if (mSamplingPeriodNs != ns) {
+        mSamplingPeriodNs = ns;
+        if (mEnabled) {
+            mCallback->onStateChanged(mEnabled, mSamplingPeriodNs, mBatchReportLatencyNs);
+        }
+    }
+    return OK;
+}
+
+// ---------------------------------------------------------------------------
+
 ProximitySensor::ProximitySensor(const sensor_t& sensor, SensorService& service)
         : HardwareSensor(sensor), mSensorService(service) {
 }
diff --git a/services/sensorservice/SensorInterface.h b/services/sensorservice/SensorInterface.h
index 5704359..5ee5e12 100644
--- a/services/sensorservice/SensorInterface.h
+++ b/services/sensorservice/SensorInterface.h
@@ -104,6 +104,32 @@
 
 // ---------------------------------------------------------------------------
 
+class RuntimeSensor : public BaseSensor {
+public:
+    static constexpr int DEFAULT_DEVICE_ID = 0;
+
+    class StateChangeCallback : public virtual RefBase {
+      public:
+        virtual void onStateChanged(bool enabled, int64_t samplingPeriodNs,
+                                    int64_t batchReportLatencyNs) = 0;
+    };
+    RuntimeSensor(const sensor_t& sensor, sp<StateChangeCallback> callback);
+    virtual status_t activate(void* ident, bool enabled) override;
+    virtual status_t batch(void* ident, int handle, int flags, int64_t samplingPeriodNs,
+                           int64_t maxBatchReportLatencyNs) override;
+    virtual status_t setDelay(void* ident, int handle, int64_t ns) override;
+    virtual bool process(sensors_event_t*, const sensors_event_t&) { return false; }
+    virtual bool isVirtual() const override { return false; }
+
+private:
+    bool mEnabled = false;
+    int64_t mSamplingPeriodNs = 0;
+    int64_t mBatchReportLatencyNs = 0;
+    sp<StateChangeCallback> mCallback;
+};
+
+// ---------------------------------------------------------------------------
+
 class ProximitySensor : public HardwareSensor {
 public:
     explicit ProximitySensor(const sensor_t& sensor, SensorService& service);
diff --git a/services/sensorservice/SensorList.cpp b/services/sensorservice/SensorList.cpp
index 85ce0f0..6d36b47 100644
--- a/services/sensorservice/SensorList.cpp
+++ b/services/sensorservice/SensorList.cpp
@@ -29,12 +29,12 @@
 const Sensor SensorList::mNonSensor = Sensor("unknown");
 
 bool SensorList::add(
-        int handle, SensorInterface* si, bool isForDebug, bool isVirtual) {
+        int handle, SensorInterface* si, bool isForDebug, bool isVirtual, int deviceId) {
     std::lock_guard<std::mutex> lk(mLock);
     if (handle == si->getSensor().getHandle() &&
         mUsedHandle.insert(handle).second) {
         // will succeed as the mUsedHandle does not have this handle
-        mHandleMap.emplace(handle, Entry(si, isForDebug, isVirtual));
+        mHandleMap.emplace(handle, Entry(si, isForDebug, isVirtual, deviceId));
         return true;
     }
     // handle exist already or handle mismatch
@@ -79,7 +79,8 @@
     Vector<Sensor> sensors;
     forEachEntry(
             [&sensors] (const Entry& e) -> bool {
-                if (!e.isForDebug && !e.si->getSensor().isDynamicSensor()) {
+                if (!e.isForDebug && !e.si->getSensor().isDynamicSensor()
+                    && e.deviceId == RuntimeSensor::DEFAULT_DEVICE_ID) {
                     sensors.add(e.si->getSensor());
                 }
                 return true;
@@ -92,7 +93,8 @@
     Vector<Sensor> sensors;
     forEachEntry(
             [&sensors] (const Entry& e) -> bool {
-                if (!e.si->getSensor().isDynamicSensor()) {
+                if (!e.si->getSensor().isDynamicSensor()
+                    && e.deviceId == RuntimeSensor::DEFAULT_DEVICE_ID) {
                     sensors.add(e.si->getSensor());
                 }
                 return true;
@@ -105,7 +107,8 @@
     Vector<Sensor> sensors;
     forEachEntry(
             [&sensors] (const Entry& e) -> bool {
-                if (!e.isForDebug && e.si->getSensor().isDynamicSensor()) {
+                if (!e.isForDebug && e.si->getSensor().isDynamicSensor()
+                     && e.deviceId == RuntimeSensor::DEFAULT_DEVICE_ID) {
                     sensors.add(e.si->getSensor());
                 }
                 return true;
@@ -118,7 +121,20 @@
     Vector<Sensor> sensors;
     forEachEntry(
             [&sensors] (const Entry& e) -> bool {
-                if (e.isVirtual) {
+                if (e.isVirtual && e.deviceId == RuntimeSensor::DEFAULT_DEVICE_ID) {
+                    sensors.add(e.si->getSensor());
+                }
+                return true;
+            });
+    return sensors;
+}
+
+const Vector<Sensor> SensorList::getRuntimeSensors(int deviceId) const {
+    // lock in forEachEntry
+    Vector<Sensor> sensors;
+    forEachEntry(
+            [&sensors, deviceId] (const Entry& e) -> bool {
+                if (!e.isForDebug && e.deviceId == deviceId) {
                     sensors.add(e.si->getSensor());
                 }
                 return true;
diff --git a/services/sensorservice/SensorList.h b/services/sensorservice/SensorList.h
index 049ae7c..79f6701 100644
--- a/services/sensorservice/SensorList.h
+++ b/services/sensorservice/SensorList.h
@@ -40,14 +40,16 @@
         sp<SensorInterface> si;
         const bool isForDebug;
         const bool isVirtual;
-        Entry(SensorInterface* si_, bool debug_, bool virtual_) :
-            si(si_), isForDebug(debug_), isVirtual(virtual_) {
+        const int deviceId;
+        Entry(SensorInterface* si_, bool debug_, bool virtual_, int deviceId_) :
+            si(si_), isForDebug(debug_), isVirtual(virtual_), deviceId(deviceId_) {
         }
     };
 
     // After SensorInterface * is added into SensorList, it can be assumed that SensorList own the
     // object it pointed to and the object should not be released elsewhere.
-    bool add(int handle, SensorInterface* si, bool isForDebug = false, bool isVirtual = false);
+    bool add(int handle, SensorInterface* si, bool isForDebug = false, bool isVirtual = false,
+             int deviceId = RuntimeSensor::DEFAULT_DEVICE_ID);
 
     // After a handle is removed, the object that SensorInterface * pointing to may get deleted if
     // no more sp<> of the same object exist.
@@ -60,6 +62,7 @@
     const Vector<Sensor> getUserDebugSensors() const;
     const Vector<Sensor> getDynamicSensors() const;
     const Vector<Sensor> getVirtualSensors() const;
+    const Vector<Sensor> getRuntimeSensors(int deviceId) const;
 
     String8 getName(int handle) const;
     String8 getStringType(int handle) const;
diff --git a/services/sensorservice/SensorService.cpp b/services/sensorservice/SensorService.cpp
index 21d6b6b..0c9fef5 100644
--- a/services/sensorservice/SensorService.cpp
+++ b/services/sensorservice/SensorService.cpp
@@ -102,6 +102,33 @@
 static const String16 sLocationHardwarePermission("android.permission.LOCATION_HARDWARE");
 static const String16 sManageSensorsPermission("android.permission.MANAGE_SENSORS");
 
+namespace {
+
+// TODO(b/259227294): Move the sensor ranges to the HAL.
+int32_t nextRuntimeSensorHandle() {
+    static constexpr int32_t kRuntimeHandleBase = 0x5F000000;
+    static constexpr int32_t kRuntimeHandleEnd = 0x5FFFFFFF;
+    static int32_t nextHandle = kRuntimeHandleBase;
+    if (nextHandle == kRuntimeHandleEnd) {
+        return -1;
+    }
+    return nextHandle++;
+}
+
+class RuntimeSensorCallbackProxy : public RuntimeSensor::StateChangeCallback {
+ public:
+    RuntimeSensorCallbackProxy(sp<SensorService::RuntimeSensorStateChangeCallback> callback)
+        : mCallback(std::move(callback)) {}
+    void onStateChanged(bool enabled, int64_t samplingPeriodNs,
+                        int64_t batchReportLatencyNs) override {
+        mCallback->onStateChanged(enabled, samplingPeriodNs, batchReportLatencyNs);
+    }
+ private:
+    sp<SensorService::RuntimeSensorStateChangeCallback> mCallback;
+};
+
+} // namespace
+
 static bool isAutomotive() {
     sp<IServiceManager> serviceManager = defaultServiceManager();
     if (serviceManager.get() == nullptr) {
@@ -137,6 +164,60 @@
     mMicSensorPrivacyPolicy = new MicrophonePrivacyPolicy(this);
 }
 
+int SensorService::registerRuntimeSensor(
+    const sensor_t& sensor, int deviceId, sp<RuntimeSensorStateChangeCallback> callback) {
+    int handle = 0;
+    while (handle == 0 || !mSensors.isNewHandle(handle)) {
+        handle = nextRuntimeSensorHandle();
+        if (handle < 0) {
+            // Ran out of the dedicated range for runtime sensors.
+            return handle;
+        }
+    }
+
+    ALOGI("Registering runtime sensor handle 0x%x, type %d, name %s",
+            handle, sensor.type, sensor.name);
+
+    sp<RuntimeSensor::StateChangeCallback> runtimeSensorCallback(
+        new RuntimeSensorCallbackProxy(std::move(callback)));
+    sensor_t runtimeSensor = sensor;
+    // force the handle to be consistent
+    runtimeSensor.handle = handle;
+    SensorInterface *si = new RuntimeSensor(runtimeSensor, std::move(runtimeSensorCallback));
+
+    Mutex::Autolock _l(mLock);
+    const Sensor& s = registerSensor(si, /* isDebug= */ false, /* isVirtual= */ false, deviceId);
+
+    if (s.getHandle() != handle) {
+        // The registration was unsuccessful.
+        return s.getHandle();
+    }
+    return handle;
+}
+
+status_t SensorService::unregisterRuntimeSensor(int handle) {
+    ALOGI("Unregistering runtime sensor handle 0x%x disconnected", handle);
+    {
+        Mutex::Autolock _l(mLock);
+        if (!unregisterDynamicSensorLocked(handle)) {
+            ALOGE("Runtime sensor release error.");
+            return UNKNOWN_ERROR;
+        }
+    }
+
+    ConnectionSafeAutolock connLock = mConnectionHolder.lock(mLock);
+    for (const sp<SensorEventConnection>& connection : connLock.getActiveConnections()) {
+        connection->removeSensor(handle);
+    }
+    return OK;
+}
+
+status_t SensorService::sendRuntimeSensorEvent(const sensors_event_t& event) {
+    Mutex::Autolock _l(mLock);
+    mRuntimeSensorEventQueue.push(event);
+    return OK;
+}
+
 bool SensorService::initializeHmacKey() {
     int fd = open(SENSOR_SERVICE_HMAC_KEY_FILE, O_RDONLY|O_CLOEXEC);
     if (fd != -1) {
@@ -407,10 +488,11 @@
         && isUidActive(uid) && !isOperationRestrictedLocked(opPackageName);
 }
 
-const Sensor& SensorService::registerSensor(SensorInterface* s, bool isDebug, bool isVirtual) {
+const Sensor& SensorService::registerSensor(SensorInterface* s, bool isDebug, bool isVirtual,
+                                            int deviceId) {
     int handle = s->getSensor().getHandle();
     int type = s->getSensor().getType();
-    if (mSensors.add(handle, s, isDebug, isVirtual)){
+    if (mSensors.add(handle, s, isDebug, isVirtual, deviceId)) {
         mRecentEvent.emplace(handle, new SensorServiceUtil::RecentEventLogger(type));
         return s->getSensor();
     } else {
@@ -1003,6 +1085,7 @@
         recordLastValueLocked(mSensorEventBuffer, count);
 
         // handle virtual sensors
+        bool bufferNeedsSorting = false;
         if (count && vcount) {
             sensors_event_t const * const event = mSensorEventBuffer;
             if (!mActiveVirtualSensors.empty()) {
@@ -1038,12 +1121,37 @@
                     // record the last synthesized values
                     recordLastValueLocked(&mSensorEventBuffer[count], k);
                     count += k;
-                    // sort the buffer by time-stamps
-                    sortEventBuffer(mSensorEventBuffer, count);
+                    bufferNeedsSorting = true;
                 }
             }
         }
 
+        // handle runtime sensors
+        {
+            size_t k = 0;
+            while (!mRuntimeSensorEventQueue.empty()) {
+                if (count + k >= minBufferSize) {
+                    ALOGE("buffer too small to hold all events: count=%zd, k=%zu, size=%zu",
+                          count, k, minBufferSize);
+                    break;
+                }
+                mSensorEventBuffer[count + k] = mRuntimeSensorEventQueue.front();
+                mRuntimeSensorEventQueue.pop();
+                k++;
+            }
+            if (k) {
+                // record the last synthesized values
+                recordLastValueLocked(&mSensorEventBuffer[count], k);
+                count += k;
+                bufferNeedsSorting = true;
+            }
+        }
+
+        if (bufferNeedsSorting) {
+            // sort the buffer by time-stamps
+            sortEventBuffer(mSensorEventBuffer, count);
+        }
+
         // handle backward compatibility for RotationVector sensor
         if (halVersion < SENSORS_DEVICE_API_VERSION_1_0) {
             for (int i = 0; i < count; i++) {
@@ -1342,19 +1450,37 @@
     return accessibleSensorList;
 }
 
+void SensorService::addSensorIfAccessible(const String16& opPackageName, const Sensor& sensor,
+        Vector<Sensor>& accessibleSensorList) {
+    if (canAccessSensor(sensor, "can't see", opPackageName)) {
+        accessibleSensorList.add(sensor);
+    } else if (sensor.getType() != SENSOR_TYPE_HEAD_TRACKER) {
+        ALOGI("Skipped sensor %s because it requires permission %s and app op %" PRId32,
+        sensor.getName().string(), sensor.getRequiredPermission().string(),
+        sensor.getRequiredAppOp());
+    }
+}
+
 Vector<Sensor> SensorService::getDynamicSensorList(const String16& opPackageName) {
     Vector<Sensor> accessibleSensorList;
     mSensors.forEachSensor(
             [this, &opPackageName, &accessibleSensorList] (const Sensor& sensor) -> bool {
                 if (sensor.isDynamicSensor()) {
-                    if (canAccessSensor(sensor, "can't see", opPackageName)) {
-                        accessibleSensorList.add(sensor);
-                    } else if (sensor.getType() != SENSOR_TYPE_HEAD_TRACKER) {
-                        ALOGI("Skipped sensor %s because it requires permission %s and app op %" PRId32,
-                              sensor.getName().string(),
-                              sensor.getRequiredPermission().string(),
-                              sensor.getRequiredAppOp());
-                    }
+                    addSensorIfAccessible(opPackageName, sensor, accessibleSensorList);
+                }
+                return true;
+            });
+    makeUuidsIntoIdsForSensorList(accessibleSensorList);
+    return accessibleSensorList;
+}
+
+Vector<Sensor> SensorService::getRuntimeSensorList(const String16& opPackageName, int deviceId) {
+    Vector<Sensor> accessibleSensorList;
+    mSensors.forEachEntry(
+            [this, &opPackageName, deviceId, &accessibleSensorList] (
+                    const SensorServiceUtil::SensorList::Entry& e) -> bool {
+                if (e.deviceId == deviceId) {
+                    addSensorIfAccessible(opPackageName, e.si->getSensor(), accessibleSensorList);
                 }
                 return true;
             });
diff --git a/services/sensorservice/SensorService.h b/services/sensorservice/SensorService.h
index 4ba3c51..e490398 100644
--- a/services/sensorservice/SensorService.h
+++ b/services/sensorservice/SensorService.h
@@ -42,6 +42,7 @@
 
 #include <stdint.h>
 #include <sys/types.h>
+#include <queue>
 #include <unordered_map>
 #include <unordered_set>
 #include <vector>
@@ -143,6 +144,14 @@
         virtual void onProximityActive(bool isActive) = 0;
     };
 
+    class RuntimeSensorStateChangeCallback : public virtual RefBase {
+    public:
+        // Note that the callback is invoked from an async thread and can interact with the
+        // SensorService directly.
+        virtual void onStateChanged(bool enabled, int64_t samplingPeriodNanos,
+                                    int64_t batchReportLatencyNanos) = 0;
+    };
+
     static char const* getServiceName() ANDROID_API { return "sensorservice"; }
     SensorService() ANDROID_API;
 
@@ -169,6 +178,11 @@
     status_t addProximityActiveListener(const sp<ProximityActiveListener>& callback) ANDROID_API;
     status_t removeProximityActiveListener(const sp<ProximityActiveListener>& callback) ANDROID_API;
 
+    int registerRuntimeSensor(const sensor_t& sensor, int deviceId,
+                              sp<RuntimeSensorStateChangeCallback> callback) ANDROID_API;
+    status_t unregisterRuntimeSensor(int handle) ANDROID_API;
+    status_t sendRuntimeSensorEvent(const sensors_event_t& event) ANDROID_API;
+
     // Returns true if a sensor should be throttled according to our rate-throttling rules.
     static bool isSensorInCappedSet(int sensorType);
 
@@ -346,6 +360,7 @@
     // ISensorServer interface
     virtual Vector<Sensor> getSensorList(const String16& opPackageName);
     virtual Vector<Sensor> getDynamicSensorList(const String16& opPackageName);
+    virtual Vector<Sensor> getRuntimeSensorList(const String16& opPackageName, int deviceId);
     virtual sp<ISensorEventConnection> createSensorEventConnection(
             const String8& packageName,
             int requestedMode, const String16& opPackageName, const String16& attributionTag);
@@ -364,8 +379,9 @@
     bool isWakeUpSensor(int type) const;
     void recordLastValueLocked(sensors_event_t const* buffer, size_t count);
     static void sortEventBuffer(sensors_event_t* buffer, size_t count);
-    const Sensor& registerSensor(SensorInterface* sensor,
-                                 bool isDebug = false, bool isVirtual = false);
+    const Sensor& registerSensor(SensorInterface* sensor, bool isDebug = false,
+                                 bool isVirtual = false,
+                                 int deviceId = RuntimeSensor::DEFAULT_DEVICE_ID);
     const Sensor& registerVirtualSensor(SensorInterface* sensor, bool isDebug = false);
     const Sensor& registerDynamicSensorLocked(SensorInterface* sensor, bool isDebug = false);
     bool unregisterDynamicSensorLocked(int handle);
@@ -375,6 +391,8 @@
             sensors_event_t const* buffer, const int count);
     bool canAccessSensor(const Sensor& sensor, const char* operation,
             const String16& opPackageName);
+    void addSensorIfAccessible(const String16& opPackageName, const Sensor& sensor,
+            Vector<Sensor>& accessibleSensorList);
     static bool hasPermissionForSensor(const Sensor& sensor);
     static int getTargetSdkVersion(const String16& opPackageName);
     static void resetTargetSdkVersionCache(const String16& opPackageName);
@@ -492,6 +510,7 @@
     wp<const SensorEventConnection> * mMapFlushEventsToConnections;
     std::unordered_map<int, SensorServiceUtil::RecentEventLogger*> mRecentEvent;
     Mode mCurrentOperatingMode;
+    std::queue<sensors_event_t> mRuntimeSensorEventQueue;
 
     // true if the head tracker sensor type is currently restricted to system usage only
     // (can only be unrestricted for testing, via shell cmd)
diff --git a/services/sensorservice/aidl/Android.bp b/services/sensorservice/aidl/Android.bp
new file mode 100644
index 0000000..34d1de7
--- /dev/null
+++ b/services/sensorservice/aidl/Android.bp
@@ -0,0 +1,44 @@
+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 {
+    name: "libsensorserviceaidl",
+    srcs: [
+        "EventQueue.cpp",
+        "DirectReportChannel.cpp",
+        "SensorManager.cpp",
+        "utils.cpp",
+    ],
+    host_supported: true,
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
+    header_libs: ["jni_headers"],
+    shared_libs: [
+        "libbase",
+        "libutils",
+        "libcutils",
+        "libbinder_ndk",
+        "libsensor",
+        "android.frameworks.sensorservice-V1-ndk",
+        "android.hardware.sensors-V1-ndk",
+    ],
+    export_include_dirs: [
+        "include/",
+    ],
+    static_libs: [
+        "android.hardware.sensors-V1-convert",
+    ],
+
+    export_header_lib_headers: ["jni_headers"],
+    local_include_dirs: [
+        "include/sensorserviceaidl/",
+    ],
+}
diff --git a/services/sensorservice/aidl/DirectReportChannel.cpp b/services/sensorservice/aidl/DirectReportChannel.cpp
new file mode 100644
index 0000000..cab53c1
--- /dev/null
+++ b/services/sensorservice/aidl/DirectReportChannel.cpp
@@ -0,0 +1,45 @@
+/*
+ * 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 "DirectReportChannel.h"
+
+namespace android {
+namespace frameworks {
+namespace sensorservice {
+namespace implementation {
+
+DirectReportChannel::DirectReportChannel(::android::SensorManager& manager, int channelId)
+      : mManager(manager), mId(channelId) {}
+
+DirectReportChannel::~DirectReportChannel() {
+    mManager.destroyDirectChannel(mId);
+}
+
+ndk::ScopedAStatus DirectReportChannel::configure(
+        int32_t sensorHandle, ::aidl::android::hardware::sensors::ISensors::RateLevel rate,
+        int32_t* _aidl_return) {
+    int token = mManager.configureDirectChannel(mId, sensorHandle, static_cast<int>(rate));
+    if (token <= 0) {
+        return ndk::ScopedAStatus::fromServiceSpecificError(token);
+    }
+    *_aidl_return = token;
+    return ndk::ScopedAStatus::ok();
+}
+
+} // namespace implementation
+} // namespace sensorservice
+} // namespace frameworks
+} // namespace android
diff --git a/services/sensorservice/aidl/DirectReportChannel.h b/services/sensorservice/aidl/DirectReportChannel.h
new file mode 100644
index 0000000..d9ea73d
--- /dev/null
+++ b/services/sensorservice/aidl/DirectReportChannel.h
@@ -0,0 +1,45 @@
+/*
+ * 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
+
+#include <aidl/android/frameworks/sensorservice/BnDirectReportChannel.h>
+#include <aidl/android/hardware/sensors/ISensors.h>
+#include <sensor/SensorManager.h>
+
+namespace android {
+namespace frameworks {
+namespace sensorservice {
+namespace implementation {
+
+class DirectReportChannel final
+      : public ::aidl::android::frameworks::sensorservice::BnDirectReportChannel {
+public:
+    DirectReportChannel(::android::SensorManager& manager, int channelId);
+    ~DirectReportChannel();
+
+    ndk::ScopedAStatus configure(int32_t sensorHandle,
+                                 ::aidl::android::hardware::sensors::ISensors::RateLevel rate,
+                                 int32_t* _aidl_return) override;
+
+private:
+    ::android::SensorManager& mManager;
+    const int mId;
+};
+
+} // namespace implementation
+} // namespace sensorservice
+} // namespace frameworks
+} // namespace android
diff --git a/services/sensorservice/aidl/EventQueue.cpp b/services/sensorservice/aidl/EventQueue.cpp
new file mode 100644
index 0000000..c394709
--- /dev/null
+++ b/services/sensorservice/aidl/EventQueue.cpp
@@ -0,0 +1,86 @@
+/*
+ * 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 "EventQueue.h"
+#include "utils.h"
+
+#include <android-base/logging.h>
+#include <utils/Looper.h>
+
+namespace android {
+namespace frameworks {
+namespace sensorservice {
+namespace implementation {
+
+using ::aidl::android::frameworks::sensorservice::IEventQueueCallback;
+using ::aidl::android::hardware::sensors::Event;
+
+class EventQueueLooperCallback : public ::android::LooperCallback {
+public:
+    EventQueueLooperCallback(sp<::android::SensorEventQueue> queue,
+                             std::shared_ptr<IEventQueueCallback> callback)
+          : mQueue(queue), mCallback(callback) {}
+
+    int handleEvent(int /* fd */, int /* events */, void* /* data */) {
+        ASensorEvent event;
+        ssize_t actual;
+
+        auto internalQueue = mQueue.promote();
+        if (internalQueue == nullptr) {
+            return 1;
+        }
+
+        while ((actual = internalQueue->read(&event, 1)) > 0) {
+            internalQueue->sendAck(&event, actual);
+            ndk::ScopedAStatus ret = mCallback->onEvent(convertEvent(event));
+            if (!ret.isOk()) {
+                LOG(ERROR) << "Failed to envoke EventQueueCallback: " << ret;
+            }
+        }
+
+        return 1; // continue to receive callbacks
+    }
+
+private:
+    wp<::android::SensorEventQueue> mQueue;
+    std::shared_ptr<IEventQueueCallback> mCallback;
+};
+
+EventQueue::EventQueue(std::shared_ptr<IEventQueueCallback> callback, sp<::android::Looper> looper,
+                       sp<::android::SensorEventQueue> internalQueue)
+      : mLooper(looper), mInternalQueue(internalQueue) {
+    mLooper->addFd(internalQueue->getFd(), ALOOPER_POLL_CALLBACK, ALOOPER_EVENT_INPUT,
+                   new EventQueueLooperCallback(internalQueue, callback), nullptr);
+}
+
+EventQueue::~EventQueue() {
+    mLooper->removeFd(mInternalQueue->getFd());
+}
+
+ndk::ScopedAStatus EventQueue::enableSensor(int32_t in_sensorHandle, int32_t in_samplingPeriodUs,
+                                            int64_t in_maxBatchReportLatencyUs) {
+    return convertResult(mInternalQueue->enableSensor(in_sensorHandle, in_samplingPeriodUs,
+                                                      in_maxBatchReportLatencyUs, 0));
+}
+
+ndk::ScopedAStatus EventQueue::disableSensor(int32_t in_sensorHandle) {
+    return convertResult(mInternalQueue->disableSensor(in_sensorHandle));
+}
+
+} // namespace implementation
+} // namespace sensorservice
+} // namespace frameworks
+} // namespace android
diff --git a/services/sensorservice/aidl/EventQueue.h b/services/sensorservice/aidl/EventQueue.h
new file mode 100644
index 0000000..0ae1eba
--- /dev/null
+++ b/services/sensorservice/aidl/EventQueue.h
@@ -0,0 +1,47 @@
+/*
+ * 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
+
+#include "SensorManagerAidl.h"
+
+#include <aidl/android/frameworks/sensorservice/BnEventQueue.h>
+#include <sensor/SensorManager.h>
+
+namespace android {
+namespace frameworks {
+namespace sensorservice {
+namespace implementation {
+
+struct EventQueue final : public aidl::android::frameworks::sensorservice::BnEventQueue {
+    EventQueue(
+            std::shared_ptr<aidl::android::frameworks::sensorservice::IEventQueueCallback> callback,
+            sp<::android::Looper> looper, sp<::android::SensorEventQueue> internalQueue);
+    ~EventQueue();
+
+    ndk::ScopedAStatus enableSensor(int32_t in_sensorHandle, int32_t in_samplingPeriodUs,
+                                    int64_t in_maxBatchReportLatencyUs) override;
+    ndk::ScopedAStatus disableSensor(int32_t sensorHandle) override;
+
+private:
+    friend class EventQueueLooperCallback;
+    sp<::android::Looper> mLooper;
+    sp<::android::SensorEventQueue> mInternalQueue;
+};
+
+} // namespace implementation
+} // namespace sensorservice
+} // namespace frameworks
+} // namespace android
diff --git a/services/sensorservice/aidl/SensorManager.cpp b/services/sensorservice/aidl/SensorManager.cpp
new file mode 100644
index 0000000..9b03344
--- /dev/null
+++ b/services/sensorservice/aidl/SensorManager.cpp
@@ -0,0 +1,254 @@
+/*
+ * 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.
+ */
+
+// LOG_TAG defined via build flag.
+#ifndef LOG_TAG
+#define LOG_TAG "AidlSensorManager"
+#endif
+
+#include "DirectReportChannel.h"
+#include "EventQueue.h"
+#include "SensorManagerAidl.h"
+#include "utils.h"
+
+#include <aidl/android/hardware/sensors/ISensors.h>
+#include <android-base/logging.h>
+#include <android/binder_ibinder.h>
+#include <sched.h>
+
+namespace android {
+namespace frameworks {
+namespace sensorservice {
+namespace implementation {
+
+using ::aidl::android::frameworks::sensorservice::IDirectReportChannel;
+using ::aidl::android::frameworks::sensorservice::IEventQueue;
+using ::aidl::android::frameworks::sensorservice::IEventQueueCallback;
+using ::aidl::android::frameworks::sensorservice::ISensorManager;
+using ::aidl::android::hardware::common::Ashmem;
+using ::aidl::android::hardware::sensors::ISensors;
+using ::aidl::android::hardware::sensors::SensorInfo;
+using ::aidl::android::hardware::sensors::SensorType;
+using ::android::frameworks::sensorservice::implementation::SensorManagerAidl;
+
+static const char* POLL_THREAD_NAME = "aidl_ssvc_poll";
+
+SensorManagerAidl::SensorManagerAidl(JavaVM* vm)
+      : mLooper(new Looper(false)), mStopThread(true), mJavaVm(vm) {}
+SensorManagerAidl::~SensorManagerAidl() {
+    // Stops pollAll inside the thread.
+    std::lock_guard<std::mutex> lock(mThreadMutex);
+
+    mStopThread = true;
+    if (mLooper != nullptr) {
+        mLooper->wake();
+    }
+    if (mPollThread.joinable()) {
+        mPollThread.join();
+    }
+}
+
+ndk::ScopedAStatus createDirectChannel(::android::SensorManager& manager, size_t size, int type,
+                                       const native_handle_t* handle,
+                                       std::shared_ptr<IDirectReportChannel>* chan) {
+    int channelId = manager.createDirectChannel(size, type, handle);
+    if (channelId < 0) {
+        return convertResult(channelId);
+    }
+    if (channelId == 0) {
+        return ndk::ScopedAStatus::fromServiceSpecificError(ISensorManager::RESULT_UNKNOWN_ERROR);
+    }
+    *chan = ndk::SharedRefBase::make<DirectReportChannel>(manager, channelId);
+    return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus SensorManagerAidl::createAshmemDirectChannel(
+        const Ashmem& in_mem, int64_t in_size,
+        std::shared_ptr<IDirectReportChannel>* _aidl_return) {
+    if (in_size > in_mem.size || in_size < ISensors::DIRECT_REPORT_SENSOR_EVENT_TOTAL_LENGTH) {
+        return ndk::ScopedAStatus::fromServiceSpecificError(ISensorManager::RESULT_BAD_VALUE);
+    }
+    native_handle_t* handle = native_handle_create(1, 0);
+    handle->data[0] = dup(in_mem.fd.get());
+
+    auto status = createDirectChannel(getInternalManager(), in_size, SENSOR_DIRECT_MEM_TYPE_ASHMEM,
+                                      handle, _aidl_return);
+    int result = native_handle_close(handle);
+    CHECK(result == 0) << "Failed to close the native_handle_t: " << result;
+    result = native_handle_delete(handle);
+    CHECK(result == 0) << "Failed to delete the native_handle_t: " << result;
+
+    return status;
+}
+
+ndk::ScopedAStatus SensorManagerAidl::createGrallocDirectChannel(
+        const ndk::ScopedFileDescriptor& in_mem, int64_t in_size,
+        std::shared_ptr<IDirectReportChannel>* _aidl_return) {
+    native_handle_t* handle = native_handle_create(1, 0);
+    handle->data[0] = dup(in_mem.get());
+
+    auto status = createDirectChannel(getInternalManager(), in_size, SENSOR_DIRECT_MEM_TYPE_GRALLOC,
+                                      handle, _aidl_return);
+    int result = native_handle_close(handle);
+    CHECK(result == 0) << "Failed to close the native_handle_t: " << result;
+    result = native_handle_delete(handle);
+    CHECK(result == 0) << "Failed to delete the native_handle_t: " << result;
+
+    return status;
+}
+
+ndk::ScopedAStatus SensorManagerAidl::createEventQueue(
+        const std::shared_ptr<IEventQueueCallback>& in_callback,
+        std::shared_ptr<IEventQueue>* _aidl_return) {
+    if (in_callback == nullptr) {
+        return ndk::ScopedAStatus::fromServiceSpecificError(ISensorManager::RESULT_BAD_VALUE);
+    }
+
+    sp<::android::Looper> looper = getLooper();
+    if (looper == nullptr) {
+        LOG(ERROR) << "::android::SensorManagerAidl::createEventQueue cannot initialize looper";
+        return ndk::ScopedAStatus::fromServiceSpecificError(ISensorManager::RESULT_UNKNOWN_ERROR);
+    }
+
+    String8 package(String8::format("aidl_client_pid_%d", AIBinder_getCallingPid()));
+    sp<::android::SensorEventQueue> internalQueue = getInternalManager().createEventQueue(package);
+    if (internalQueue == nullptr) {
+        LOG(ERROR) << "::android::SensorManagerAidl::createEventQueue returns nullptr.";
+        return ndk::ScopedAStatus::fromServiceSpecificError(ISensorManager::RESULT_UNKNOWN_ERROR);
+    }
+
+    *_aidl_return = ndk::SharedRefBase::make<EventQueue>(in_callback, looper, internalQueue);
+
+    return ndk::ScopedAStatus::ok();
+}
+
+SensorInfo convertSensor(Sensor src) {
+    SensorInfo dst;
+    dst.sensorHandle = src.getHandle();
+    dst.name = src.getName();
+    dst.vendor = src.getVendor();
+    dst.version = src.getVersion();
+    dst.type = static_cast<SensorType>(src.getType());
+    dst.typeAsString = src.getStringType();
+    // maxRange uses maxValue because ::android::Sensor wraps the
+    // internal sensor_t in this way.
+    dst.maxRange = src.getMaxValue();
+    dst.resolution = src.getResolution();
+    dst.power = src.getPowerUsage();
+    dst.minDelayUs = src.getMinDelay();
+    dst.fifoReservedEventCount = src.getFifoReservedEventCount();
+    dst.fifoMaxEventCount = src.getFifoMaxEventCount();
+    dst.requiredPermission = src.getRequiredPermission();
+    dst.maxDelayUs = src.getMaxDelay();
+    dst.flags = src.getFlags();
+    return dst;
+}
+
+ndk::ScopedAStatus SensorManagerAidl::getDefaultSensor(SensorType in_type,
+                                                       SensorInfo* _aidl_return) {
+    ::android::Sensor const* sensor =
+            getInternalManager().getDefaultSensor(static_cast<int>(in_type));
+    if (!sensor) {
+        return ndk::ScopedAStatus::fromServiceSpecificError(ISensorManager::RESULT_NOT_EXIST);
+    }
+    *_aidl_return = convertSensor(*sensor);
+    return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus SensorManagerAidl::getSensorList(std::vector<SensorInfo>* _aidl_return) {
+    Sensor const* const* list;
+    _aidl_return->clear();
+    ssize_t count = getInternalManager().getSensorList(&list);
+    if (count < 0 || list == nullptr) {
+        LOG(ERROR) << "SensorMAanger::getSensorList failed with count: " << count;
+        return ndk::ScopedAStatus::fromServiceSpecificError(ISensorManager::RESULT_UNKNOWN_ERROR);
+    }
+    _aidl_return->reserve(static_cast<size_t>(count));
+    for (ssize_t i = 0; i < count; ++i) {
+        _aidl_return->push_back(convertSensor(*list[i]));
+    }
+
+    return ndk::ScopedAStatus::ok();
+}
+
+::android::SensorManager& SensorManagerAidl::getInternalManager() {
+    std::lock_guard<std::mutex> lock(mInternalManagerMutex);
+    if (mInternalManager == nullptr) {
+        mInternalManager = &::android::SensorManager::getInstanceForPackage(
+                String16(ISensorManager::descriptor));
+    }
+    return *mInternalManager;
+}
+
+/* One global looper for all event queues created from this SensorManager. */
+sp<Looper> SensorManagerAidl::getLooper() {
+    std::lock_guard<std::mutex> lock(mThreadMutex);
+
+    if (!mPollThread.joinable()) {
+        // if thread not initialized, start thread
+        mStopThread = false;
+        std::thread pollThread{[&stopThread = mStopThread, looper = mLooper, javaVm = mJavaVm] {
+            struct sched_param p = {};
+            p.sched_priority = 10;
+            if (sched_setscheduler(0 /* current thread*/, SCHED_FIFO, &p) != 0) {
+                LOG(ERROR) << "Could not use SCHED_FIFO for looper thread: " << strerror(errno);
+            }
+
+            // set looper
+            Looper::setForThread(looper);
+
+            // Attach the thread to JavaVM so that pollAll do not crash if the thread
+            // eventually calls into Java.
+            JavaVMAttachArgs args{.version = JNI_VERSION_1_2,
+                                  .name = POLL_THREAD_NAME,
+                                  .group = nullptr};
+            JNIEnv* env;
+            if (javaVm->AttachCurrentThread(&env, &args) != JNI_OK) {
+                LOG(FATAL) << "Cannot attach SensorManager looper thread to Java VM.";
+            }
+
+            LOG(INFO) << POLL_THREAD_NAME << " started.";
+            for (;;) {
+                int pollResult = looper->pollAll(-1 /* timeout */);
+                if (pollResult == Looper::POLL_WAKE) {
+                    if (stopThread == true) {
+                        LOG(INFO) << POLL_THREAD_NAME << ": requested to stop";
+                        break;
+                    } else {
+                        LOG(INFO) << POLL_THREAD_NAME << ": spurious wake up, back to work";
+                    }
+                } else {
+                    LOG(ERROR) << POLL_THREAD_NAME << ": Looper::pollAll returns unexpected "
+                               << pollResult;
+                    break;
+                }
+            }
+
+            if (javaVm->DetachCurrentThread() != JNI_OK) {
+                LOG(ERROR) << "Cannot detach SensorManager looper thread from Java VM.";
+            }
+
+            LOG(INFO) << POLL_THREAD_NAME << " is terminated.";
+        }};
+        mPollThread = std::move(pollThread);
+    }
+    return mLooper;
+}
+
+} // namespace implementation
+} // namespace sensorservice
+} // namespace frameworks
+} // namespace android
diff --git a/services/sensorservice/aidl/fuzzer/Android.bp b/services/sensorservice/aidl/fuzzer/Android.bp
new file mode 100644
index 0000000..0d6e476
--- /dev/null
+++ b/services/sensorservice/aidl/fuzzer/Android.bp
@@ -0,0 +1,52 @@
+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_fuzz {
+    name: "libsensorserviceaidl_fuzzer",
+    defaults: [
+        "service_fuzzer_defaults",
+    ],
+    host_supported: true,
+    static_libs: [
+        "libsensorserviceaidl",
+        "libpermission",
+        "android.frameworks.sensorservice-V1-ndk",
+        "android.hardware.sensors-V1-convert",
+        "android.hardware.sensors-V1-ndk",
+        "android.hardware.common-V2-ndk",
+        "libsensor",
+        "libfakeservicemanager",
+        "libcutils",
+        "liblog",
+    ],
+    srcs: [
+        "fuzzer.cpp",
+    ],
+    fuzz_config: {
+        cc: [
+            "android-sensors@google.com",
+            "devinmoore@google.com",
+        ],
+    },
+    sanitize: {
+        misc_undefined: [
+            "signed-integer-overflow",
+            "unsigned-integer-overflow",
+        ],
+        diag: {
+            misc_undefined: [
+                "signed-integer-overflow",
+                "unsigned-integer-overflow",
+            ],
+        },
+        address: true,
+        integer_overflow: true,
+    },
+
+}
diff --git a/services/sensorservice/aidl/fuzzer/fuzzer.cpp b/services/sensorservice/aidl/fuzzer/fuzzer.cpp
new file mode 100644
index 0000000..1b63d76
--- /dev/null
+++ b/services/sensorservice/aidl/fuzzer/fuzzer.cpp
@@ -0,0 +1,53 @@
+/*
+ * 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 <fuzzbinder/libbinder_ndk_driver.h>
+#include <fuzzer/FuzzedDataProvider.h>
+
+#include <ServiceManager.h>
+#include <android-base/logging.h>
+#include <android/binder_interface_utils.h>
+#include <fuzzbinder/random_binder.h>
+#include <sensorserviceaidl/SensorManagerAidl.h>
+
+using android::fuzzService;
+using android::frameworks::sensorservice::implementation::SensorManagerAidl;
+using ndk::SharedRefBase;
+
+[[clang::no_destroy]] static std::once_flag gSmOnce;
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+    static android::sp<android::ServiceManager> fakeServiceManager = new android::ServiceManager();
+    std::call_once(gSmOnce, [&] { setDefaultServiceManager(fakeServiceManager); });
+    fakeServiceManager->clear();
+
+    FuzzedDataProvider fdp(data, size);
+    android::sp<android::IBinder> binder = android::getRandomBinder(&fdp);
+    if (binder == nullptr) {
+        // Nothing to do if we get a null binder. It will cause SensorManager to
+        // hang while trying to get sensorservice.
+        return 0;
+    }
+
+    CHECK(android::NO_ERROR == fakeServiceManager->addService(android::String16("sensorservice"),
+                                   binder));
+
+    std::shared_ptr<SensorManagerAidl> sensorService =
+            ndk::SharedRefBase::make<SensorManagerAidl>(nullptr);
+
+    fuzzService(sensorService->asBinder().get(), std::move(fdp));
+
+    return 0;
+}
diff --git a/services/sensorservice/aidl/include/sensorserviceaidl/SensorManagerAidl.h b/services/sensorservice/aidl/include/sensorserviceaidl/SensorManagerAidl.h
new file mode 100644
index 0000000..c77ee88
--- /dev/null
+++ b/services/sensorservice/aidl/include/sensorserviceaidl/SensorManagerAidl.h
@@ -0,0 +1,74 @@
+/*
+ * 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
+
+#include <aidl/android/frameworks/sensorservice/BnSensorManager.h>
+#include <jni.h>
+#include <sensor/SensorManager.h>
+#include <utils/Looper.h>
+#include <mutex>
+#include <thread>
+
+namespace android {
+namespace frameworks {
+namespace sensorservice {
+namespace implementation {
+
+class SensorManagerAidl : public ::aidl::android::frameworks::sensorservice::BnSensorManager {
+public:
+    explicit SensorManagerAidl(JavaVM* vm);
+    ~SensorManagerAidl();
+
+    ::ndk::ScopedAStatus createAshmemDirectChannel(
+            const ::aidl::android::hardware::common::Ashmem& in_mem, int64_t in_size,
+            std::shared_ptr<::aidl::android::frameworks::sensorservice::IDirectReportChannel>*
+                    _aidl_return) override;
+    ::ndk::ScopedAStatus createEventQueue(
+            const std::shared_ptr<::aidl::android::frameworks::sensorservice::IEventQueueCallback>&
+                    in_callback,
+            std::shared_ptr<::aidl::android::frameworks::sensorservice::IEventQueue>* _aidl_return)
+            override;
+    ::ndk::ScopedAStatus createGrallocDirectChannel(
+            const ::ndk::ScopedFileDescriptor& in_buffer, int64_t in_size,
+            std::shared_ptr<::aidl::android::frameworks::sensorservice::IDirectReportChannel>*
+                    _aidl_return) override;
+    ::ndk::ScopedAStatus getDefaultSensor(
+            ::aidl::android::hardware::sensors::SensorType in_type,
+            ::aidl::android::hardware::sensors::SensorInfo* _aidl_return) override;
+    ::ndk::ScopedAStatus getSensorList(
+            std::vector<::aidl::android::hardware::sensors::SensorInfo>* _aidl_return) override;
+
+private:
+    // Block until ::android::SensorManager is initialized.
+    ::android::SensorManager& getInternalManager();
+    sp<Looper> getLooper();
+
+    std::mutex mInternalManagerMutex;
+    ::android::SensorManager* mInternalManager = nullptr; // does not own
+    sp<Looper> mLooper;
+
+    volatile bool mStopThread;
+    std::mutex mThreadMutex; // protects mPollThread
+    std::thread mPollThread;
+
+    JavaVM* mJavaVm;
+};
+
+} // namespace implementation
+} // namespace sensorservice
+} // namespace frameworks
+} // namespace android
diff --git a/services/sensorservice/aidl/utils.cpp b/services/sensorservice/aidl/utils.cpp
new file mode 100644
index 0000000..beb38b9
--- /dev/null
+++ b/services/sensorservice/aidl/utils.cpp
@@ -0,0 +1,68 @@
+/*
+ * 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 "utils.h"
+
+#include <aidl/android/frameworks/sensorservice/ISensorManager.h>
+#include <aidl/sensors/convert.h>
+
+namespace android {
+namespace frameworks {
+namespace sensorservice {
+namespace implementation {
+
+ndk::ScopedAStatus convertResult(status_t src) {
+    using ::aidl::android::frameworks::sensorservice::ISensorManager;
+
+    int err = 0;
+    switch (src) {
+        case OK:
+            return ndk::ScopedAStatus::ok();
+        case NAME_NOT_FOUND:
+            err = ISensorManager::RESULT_NOT_EXIST;
+            break;
+        case NO_MEMORY:
+            err = ISensorManager::RESULT_NO_MEMORY;
+            break;
+        case NO_INIT:
+            err = ISensorManager::RESULT_NO_INIT;
+            break;
+        case PERMISSION_DENIED:
+            err = ISensorManager::RESULT_PERMISSION_DENIED;
+            break;
+        case BAD_VALUE:
+            err = ISensorManager::RESULT_BAD_VALUE;
+            break;
+        case INVALID_OPERATION:
+            err = ISensorManager::RESULT_INVALID_OPERATION;
+            break;
+        default:
+            err = ISensorManager::RESULT_UNKNOWN_ERROR;
+    }
+    return ndk::ScopedAStatus::fromServiceSpecificError(err);
+}
+
+::aidl::android::hardware::sensors::Event convertEvent(const ::ASensorEvent& src) {
+    ::aidl::android::hardware::sensors::Event dst;
+    ::android::hardware::sensors::implementation::
+            convertFromASensorEvent(src, &dst);
+    return dst;
+}
+
+} // namespace implementation
+} // namespace sensorservice
+} // namespace frameworks
+} // namespace android
diff --git a/services/sensorservice/aidl/utils.h b/services/sensorservice/aidl/utils.h
new file mode 100644
index 0000000..06ba59e
--- /dev/null
+++ b/services/sensorservice/aidl/utils.h
@@ -0,0 +1,32 @@
+/*
+ * 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
+
+#include <aidl/android/hardware/sensors/Event.h>
+#include <sensor/Sensor.h>
+
+namespace android {
+namespace frameworks {
+namespace sensorservice {
+namespace implementation {
+
+::ndk::ScopedAStatus convertResult(status_t status);
+::aidl::android::hardware::sensors::Event convertEvent(const ::ASensorEvent& event);
+
+} // namespace implementation
+} // namespace sensorservice
+} // namespace frameworks
+} // namespace android
diff --git a/services/sensorservice/hidl/utils.cpp b/services/sensorservice/hidl/utils.cpp
index 2f9e922..5fa594d 100644
--- a/services/sensorservice/hidl/utils.cpp
+++ b/services/sensorservice/hidl/utils.cpp
@@ -76,8 +76,8 @@
 
 ::android::hardware::sensors::V1_0::Event convertEvent(const ::ASensorEvent& src) {
     ::android::hardware::sensors::V1_0::Event dst;
-    ::android::hardware::sensors::V1_0::implementation::convertFromSensorEvent(
-            reinterpret_cast<const sensors_event_t&>(src), &dst);
+    ::android::hardware::sensors::V1_0::implementation::convertFromASensorEvent(
+            src, &dst);
     return dst;
 }
 
diff --git a/services/stats/StatsAidl.cpp b/services/stats/StatsAidl.cpp
index 3de51a4..9e13849 100644
--- a/services/stats/StatsAidl.cpp
+++ b/services/stats/StatsAidl.cpp
@@ -69,6 +69,10 @@
             case VendorAtomValue::repeatedIntValue: {
                 const std::optional<std::vector<int>>& repeatedIntValue =
                         atomValue.get<VendorAtomValue::repeatedIntValue>();
+                if (!repeatedIntValue) {
+                    AStatsEvent_writeInt32Array(event, {}, 0);
+                    break;
+                }
                 AStatsEvent_writeInt32Array(event, repeatedIntValue->data(),
                                             repeatedIntValue->size());
                 break;
@@ -76,6 +80,10 @@
             case VendorAtomValue::repeatedLongValue: {
                 const std::optional<std::vector<int64_t>>& repeatedLongValue =
                         atomValue.get<VendorAtomValue::repeatedLongValue>();
+                if (!repeatedLongValue) {
+                    AStatsEvent_writeInt64Array(event, {}, 0);
+                    break;
+                }
                 AStatsEvent_writeInt64Array(event, repeatedLongValue->data(),
                                             repeatedLongValue->size());
                 break;
@@ -83,6 +91,10 @@
             case VendorAtomValue::repeatedFloatValue: {
                 const std::optional<std::vector<float>>& repeatedFloatValue =
                         atomValue.get<VendorAtomValue::repeatedFloatValue>();
+                if (!repeatedFloatValue) {
+                    AStatsEvent_writeFloatArray(event, {}, 0);
+                    break;
+                }
                 AStatsEvent_writeFloatArray(event, repeatedFloatValue->data(),
                                             repeatedFloatValue->size());
                 break;
@@ -90,12 +102,18 @@
             case VendorAtomValue::repeatedStringValue: {
                 const std::optional<std::vector<std::optional<std::string>>>& repeatedStringValue =
                         atomValue.get<VendorAtomValue::repeatedStringValue>();
+                if (!repeatedStringValue) {
+                    AStatsEvent_writeStringArray(event, {}, 0);
+                    break;
+                }
                 const std::vector<std::optional<std::string>>& repeatedStringVector =
                         *repeatedStringValue;
                 const char* cStringArray[repeatedStringVector.size()];
 
                 for (int i = 0; i < repeatedStringVector.size(); ++i) {
-                    cStringArray[i] = repeatedStringVector[i]->c_str();
+                    cStringArray[i] = repeatedStringVector[i].has_value()
+                            ? repeatedStringVector[i]->c_str()
+                            : "";
                 }
 
                 AStatsEvent_writeStringArray(event, cStringArray, repeatedStringVector.size());
@@ -104,6 +122,10 @@
             case VendorAtomValue::repeatedBoolValue: {
                 const std::optional<std::vector<bool>>& repeatedBoolValue =
                         atomValue.get<VendorAtomValue::repeatedBoolValue>();
+                if (!repeatedBoolValue) {
+                    AStatsEvent_writeBoolArray(event, {}, 0);
+                    break;
+                }
                 const std::vector<bool>& repeatedBoolVector = *repeatedBoolValue;
                 bool boolArray[repeatedBoolValue->size()];
 
@@ -117,7 +139,10 @@
             case VendorAtomValue::byteArrayValue: {
                 const std::optional<std::vector<uint8_t>>& byteArrayValue =
                         atomValue.get<VendorAtomValue::byteArrayValue>();
-
+                if (!byteArrayValue) {
+                    AStatsEvent_writeByteArray(event, {}, 0);
+                    break;
+                }
                 AStatsEvent_writeByteArray(event, byteArrayValue->data(), byteArrayValue->size());
                 break;
             }
diff --git a/services/surfaceflinger/Android.bp b/services/surfaceflinger/Android.bp
index 8a76d7c..b1bd705 100644
--- a/services/surfaceflinger/Android.bp
+++ b/services/surfaceflinger/Android.bp
@@ -49,7 +49,7 @@
         "android.hardware.graphics.composer@2.4",
         "android.hardware.power@1.0",
         "android.hardware.power@1.3",
-        "android.hardware.power-V2-cpp",
+        "android.hardware.power-V4-cpp",
         "libbase",
         "libbinder",
         "libbinder_ndk",
@@ -155,6 +155,11 @@
         "DisplayRenderArea.cpp",
         "Effects/Daltonizer.cpp",
         "EventLog/EventLog.cpp",
+        "FrontEnd/LayerCreationArgs.cpp",
+        "FrontEnd/LayerHandle.cpp",
+        "FrontEnd/LayerLifecycleManager.cpp",
+        "FrontEnd/RequestedLayerState.cpp",
+        "FrontEnd/TransactionHandler.cpp",
         "FlagManager.cpp",
         "FpsReporter.cpp",
         "FrameTracer/FrameTracer.cpp",
@@ -163,6 +168,7 @@
         "HwcSlotGenerator.cpp",
         "WindowInfosListenerInvoker.cpp",
         "Layer.cpp",
+        "LayerFE.cpp",
         "LayerProtoHelper.cpp",
         "LayerRenderArea.cpp",
         "LayerVector.cpp",
@@ -177,7 +183,7 @@
         "Scheduler/LayerHistory.cpp",
         "Scheduler/LayerInfo.cpp",
         "Scheduler/MessageQueue.cpp",
-        "Scheduler/RefreshRateConfigs.cpp",
+        "Scheduler/RefreshRateSelector.cpp",
         "Scheduler/Scheduler.cpp",
         "Scheduler/VSyncDispatchTimerQueue.cpp",
         "Scheduler/VSyncPredictor.cpp",
@@ -185,6 +191,7 @@
         "Scheduler/VsyncConfiguration.cpp",
         "Scheduler/VsyncModulator.cpp",
         "Scheduler/VsyncSchedule.cpp",
+        "ScreenCaptureOutput.cpp",
         "StartPropertySetThread.cpp",
         "SurfaceFlinger.cpp",
         "SurfaceFlingerDefaultFactory.cpp",
@@ -192,7 +199,6 @@
         "Tracing/TransactionTracing.cpp",
         "Tracing/TransactionProtoParser.cpp",
         "TransactionCallbackInvoker.cpp",
-        "TransactionHandler.cpp",
         "TunnelModeEnabledReporter.cpp",
     ],
 }
diff --git a/services/surfaceflinger/Client.cpp b/services/surfaceflinger/Client.cpp
index 30b8759..bdbc79b 100644
--- a/services/surfaceflinger/Client.cpp
+++ b/services/surfaceflinger/Client.cpp
@@ -24,6 +24,8 @@
 #include <gui/AidlStatusUtil.h>
 
 #include "Client.h"
+#include "FrontEnd/LayerCreationArgs.h"
+#include "FrontEnd/LayerHandle.h"
 #include "Layer.h"
 #include "SurfaceFlinger.h"
 
@@ -46,36 +48,6 @@
     return NO_ERROR;
 }
 
-void Client::attachLayer(const sp<IBinder>& handle, const sp<Layer>& layer)
-{
-    Mutex::Autolock _l(mLock);
-    mLayers.add(handle, layer);
-}
-
-void Client::detachLayer(const Layer* layer)
-{
-    Mutex::Autolock _l(mLock);
-    // we do a linear search here, because this doesn't happen often
-    const size_t count = mLayers.size();
-    for (size_t i=0 ; i<count ; i++) {
-        if (mLayers.valueAt(i) == layer) {
-            mLayers.removeItemsAt(i, 1);
-            break;
-        }
-    }
-}
-sp<Layer> Client::getLayerUser(const sp<IBinder>& handle) const
-{
-    Mutex::Autolock _l(mLock);
-    sp<Layer> lbc;
-    wp<Layer> layer(mLayers.valueFor(handle));
-    if (layer != 0) {
-        lbc = layer.promote();
-        ALOGE_IF(lbc==0, "getLayerUser(name=%p) is dead", handle.get());
-    }
-    return lbc;
-}
-
 binder::Status Client::createSurface(const std::string& name, int32_t flags,
                                      const sp<IBinder>& parent, const gui::LayerMetadata& metadata,
                                      gui::CreateSurfaceResult* outResult) {
@@ -83,13 +55,14 @@
     sp<IBinder> handle;
     LayerCreationArgs args(mFlinger.get(), sp<Client>::fromExisting(this), name.c_str(),
                            static_cast<uint32_t>(flags), std::move(metadata));
-    const status_t status = mFlinger->createLayer(args, parent, *outResult);
+    args.parentHandle = parent;
+    const status_t status = mFlinger->createLayer(args, *outResult);
     return binderStatusFromStatusT(status);
 }
 
 binder::Status Client::clearLayerFrameStats(const sp<IBinder>& handle) {
     status_t status;
-    sp<Layer> layer = getLayerUser(handle);
+    sp<Layer> layer = LayerHandle::getLayer(handle);
     if (layer == nullptr) {
         status = NAME_NOT_FOUND;
     } else {
@@ -101,7 +74,7 @@
 
 binder::Status Client::getLayerFrameStats(const sp<IBinder>& handle, gui::FrameStats* outStats) {
     status_t status;
-    sp<Layer> layer = getLayerUser(handle);
+    sp<Layer> layer = LayerHandle::getLayer(handle);
     if (layer == nullptr) {
         status = NAME_NOT_FOUND;
     } else {
diff --git a/services/surfaceflinger/Client.h b/services/surfaceflinger/Client.h
index 02079a3..af410ea 100644
--- a/services/surfaceflinger/Client.h
+++ b/services/surfaceflinger/Client.h
@@ -38,12 +38,6 @@
 
     status_t initCheck() const;
 
-    // protected by SurfaceFlinger::mStateLock
-    void attachLayer(const sp<IBinder>& handle, const sp<Layer>& layer);
-    void detachLayer(const Layer* layer);
-
-    sp<Layer> getLayerUser(const sp<IBinder>& handle) const;
-
 private:
     // ISurfaceComposerClient interface
 
@@ -64,9 +58,6 @@
     // constant
     sp<SurfaceFlinger> mFlinger;
 
-    // protected by mLock
-    DefaultKeyedVector< wp<IBinder>, wp<Layer> > mLayers;
-
     // thread-safe
     mutable Mutex mLock;
 };
diff --git a/services/surfaceflinger/ClientCache.cpp b/services/surfaceflinger/ClientCache.cpp
index cf932a8..2bd8f32 100644
--- a/services/surfaceflinger/ClientCache.cpp
+++ b/services/surfaceflinger/ClientCache.cpp
@@ -22,6 +22,7 @@
 #include <cinttypes>
 
 #include <android-base/stringprintf.h>
+#include <gui/TraceUtils.h>
 #include <renderengine/impl/ExternalTexture.h>
 
 #include "ClientCache.h"
@@ -36,12 +37,12 @@
                             ClientCacheBuffer** outClientCacheBuffer) {
     auto& [processToken, id] = cacheId;
     if (processToken == nullptr) {
-        ALOGE("failed to get buffer, invalid (nullptr) process token");
+        ALOGE_AND_TRACE("ClientCache::getBuffer - invalid (nullptr) process token");
         return false;
     }
     auto it = mBuffers.find(processToken);
     if (it == mBuffers.end()) {
-        ALOGE("failed to get buffer, invalid process token");
+        ALOGE_AND_TRACE("ClientCache::getBuffer - invalid process token");
         return false;
     }
 
@@ -49,7 +50,7 @@
 
     auto bufItr = processBuffers.find(id);
     if (bufItr == processBuffers.end()) {
-        ALOGV("failed to get buffer, invalid buffer id");
+        ALOGE_AND_TRACE("ClientCache::getBuffer - invalid buffer id");
         return false;
     }
 
@@ -58,16 +59,17 @@
     return true;
 }
 
-bool ClientCache::add(const client_cache_t& cacheId, const sp<GraphicBuffer>& buffer) {
+base::expected<std::shared_ptr<renderengine::ExternalTexture>, ClientCache::AddError>
+ClientCache::add(const client_cache_t& cacheId, const sp<GraphicBuffer>& buffer) {
     auto& [processToken, id] = cacheId;
     if (processToken == nullptr) {
-        ALOGE("failed to cache buffer: invalid process token");
-        return false;
+        ALOGE_AND_TRACE("ClientCache::add - invalid (nullptr) process token");
+        return base::unexpected(AddError::Unspecified);
     }
 
     if (!buffer) {
-        ALOGE("failed to cache buffer: invalid buffer");
-        return false;
+        ALOGE_AND_TRACE("ClientCache::add - invalid (nullptr) buffer");
+        return base::unexpected(AddError::Unspecified);
     }
 
     std::lock_guard lock(mMutex);
@@ -79,16 +81,16 @@
     if (it == mBuffers.end()) {
         token = processToken.promote();
         if (!token) {
-            ALOGE("failed to cache buffer: invalid token");
-            return false;
+            ALOGE_AND_TRACE("ClientCache::add - invalid token");
+            return base::unexpected(AddError::Unspecified);
         }
 
         // Only call linkToDeath if not a local binder
         if (token->localBinder() == nullptr) {
             status_t err = token->linkToDeath(mDeathRecipient);
             if (err != NO_ERROR) {
-                ALOGE("failed to cache buffer: could not link to death");
-                return false;
+                ALOGE_AND_TRACE("ClientCache::add - could not link to death");
+                return base::unexpected(AddError::Unspecified);
             }
         }
         auto [itr, success] =
@@ -102,18 +104,18 @@
     auto& processBuffers = it->second.second;
 
     if (processBuffers.size() > BUFFER_CACHE_MAX_SIZE) {
-        ALOGE("failed to cache buffer: cache is full");
-        return false;
+        ALOGE_AND_TRACE("ClientCache::add - cache is full");
+        return base::unexpected(AddError::CacheFull);
     }
 
     LOG_ALWAYS_FATAL_IF(mRenderEngine == nullptr,
                         "Attempted to build the ClientCache before a RenderEngine instance was "
                         "ready!");
-    processBuffers[id].buffer = std::make_shared<
-            renderengine::impl::ExternalTexture>(buffer, *mRenderEngine,
-                                                 renderengine::impl::ExternalTexture::Usage::
-                                                         READABLE);
-    return true;
+
+    return (processBuffers[id].buffer = std::make_shared<
+                    renderengine::impl::ExternalTexture>(buffer, *mRenderEngine,
+                                                         renderengine::impl::ExternalTexture::
+                                                                 Usage::READABLE));
 }
 
 void ClientCache::erase(const client_cache_t& cacheId) {
diff --git a/services/surfaceflinger/ClientCache.h b/services/surfaceflinger/ClientCache.h
index a9b8177..cdeac2b 100644
--- a/services/surfaceflinger/ClientCache.h
+++ b/services/surfaceflinger/ClientCache.h
@@ -37,7 +37,10 @@
 public:
     ClientCache();
 
-    bool add(const client_cache_t& cacheId, const sp<GraphicBuffer>& buffer);
+    enum class AddError { CacheFull, Unspecified };
+
+    base::expected<std::shared_ptr<renderengine::ExternalTexture>, AddError> add(
+            const client_cache_t& cacheId, const sp<GraphicBuffer>& buffer);
     void erase(const client_cache_t& cacheId);
 
     std::shared_ptr<renderengine::ExternalTexture> get(const client_cache_t& cacheId);
diff --git a/services/surfaceflinger/CompositionEngine/Android.bp b/services/surfaceflinger/CompositionEngine/Android.bp
index 0ae8bf9..30d34a5 100644
--- a/services/surfaceflinger/CompositionEngine/Android.bp
+++ b/services/surfaceflinger/CompositionEngine/Android.bp
@@ -25,7 +25,7 @@
         "android.hardware.graphics.composer@2.4",
         "android.hardware.power@1.0",
         "android.hardware.power@1.3",
-        "android.hardware.power-V2-cpp",
+        "android.hardware.power-V4-cpp",
         "libbase",
         "libcutils",
         "libgui",
@@ -140,6 +140,11 @@
         "libgmock",
         "libgtest",
     ],
+    // For some reason, libvulkan isn't picked up from librenderengine
+    // Probably ASAN related?
+    shared_libs: [
+        "libvulkan",
+    ],
     sanitize: {
         // By using the address sanitizer, we not only uncover any issues
         // with the test, but also any issues with the code under test.
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionEngine.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionEngine.h
index 6832ae1..7c10fa5 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionEngine.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionEngine.h
@@ -56,9 +56,9 @@
     virtual void setHwComposer(std::unique_ptr<HWComposer>) = 0;
 
     virtual renderengine::RenderEngine& getRenderEngine() const = 0;
-    virtual void setRenderEngine(std::unique_ptr<renderengine::RenderEngine>) = 0;
+    virtual void setRenderEngine(renderengine::RenderEngine*) = 0;
 
-    virtual TimeStats& getTimeStats() const = 0;
+    virtual TimeStats* getTimeStats() const = 0;
     virtual void setTimeStats(const std::shared_ptr<TimeStats>&) = 0;
 
     virtual bool needsAnotherUpdate() const = 0;
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h
index fe8cad5..608c53a 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h
@@ -118,6 +118,9 @@
 
         // Requested white point of the layer in nits
         const float whitePointNits;
+
+        // True if layers with 170M dataspace should be overridden to sRGB.
+        const bool treat170mAsSrgb;
     };
 
     // A superset of LayerSettings required by RenderEngine to compose a layer
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/CompositionEngine.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/CompositionEngine.h
index dd4dbe9..c699557 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/CompositionEngine.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/CompositionEngine.h
@@ -34,9 +34,9 @@
     void setHwComposer(std::unique_ptr<HWComposer>) override;
 
     renderengine::RenderEngine& getRenderEngine() const override;
-    void setRenderEngine(std::unique_ptr<renderengine::RenderEngine>) override;
+    void setRenderEngine(renderengine::RenderEngine*) override;
 
-    TimeStats& getTimeStats() const override;
+    TimeStats* getTimeStats() const override;
     void setTimeStats(const std::shared_ptr<TimeStats>&) override;
 
     bool needsAnotherUpdate() const override;
@@ -58,7 +58,7 @@
 
 private:
     std::unique_ptr<HWComposer> mHwComposer;
-    std::unique_ptr<renderengine::RenderEngine> mRenderEngine;
+    renderengine::RenderEngine* mRenderEngine;
     std::shared_ptr<TimeStats> mTimeStats;
     bool mNeedsAnotherUpdate = false;
     nsecs_t mRefreshStartTime = 0;
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h
index 23d5570..9ca5da9 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h
@@ -134,9 +134,11 @@
     void applyCompositionStrategy(const std::optional<DeviceRequestedChanges>&) override{};
     bool getSkipColorTransform() const override;
     compositionengine::Output::FrameFences presentAndGetFrameFences() override;
+    virtual renderengine::DisplaySettings generateClientCompositionDisplaySettings() const;
     std::vector<LayerFE::LayerSettings> generateClientCompositionRequests(
           bool supportsProtectedContent, ui::Dataspace outputDataspace,
           std::vector<LayerFE*> &outLayerFEs) override;
+    virtual bool layerNeedsFiltering(const OutputLayer*) const;
     void appendRegionFlashRequests(const Region&, std::vector<LayerFE::LayerSettings>&) override;
     void setExpensiveRenderingExpected(bool enabled) override;
     void setHintSessionGpuFence(std::unique_ptr<FenceTime>&& gpuFence) override;
@@ -153,6 +155,8 @@
 
     bool mustRecompose() const;
 
+    const std::string& getNamePlusId() const { return mNamePlusId; }
+
 private:
     void dirtyEntireOutput();
     void updateCompositionStateForBorder(const compositionengine::CompositionRefreshArgs&);
@@ -163,6 +167,7 @@
             const compositionengine::CompositionRefreshArgs&) const;
 
     std::string mName;
+    std::string mNamePlusId;
 
     std::unique_ptr<compositionengine::DisplayColorProfile> mDisplayColorProfile;
     std::unique_ptr<compositionengine::RenderSurface> mRenderSurface;
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/LayerState.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/LayerState.h
index 5aec7c2..e309442 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/LayerState.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/LayerState.h
@@ -72,6 +72,7 @@
     SolidColor            = 1u << 16,
     BackgroundBlurRadius  = 1u << 17,
     BlurRegions           = 1u << 18,
+    HasProtectedContent   = 1u << 19,
 };
 // clang-format on
 
@@ -245,9 +246,9 @@
 
     ui::Dataspace getDataspace() const { return mOutputDataspace.get(); }
 
-    bool isProtected() const {
-        return getOutputLayer()->getLayerFE().getCompositionState()->hasProtectedContent;
-    }
+    wp<GraphicBuffer> getBuffer() const { return mBuffer.get(); }
+
+    bool isProtected() const { return mIsProtected.get(); }
 
     bool hasSolidColorCompositionType() const {
         return getOutputLayer()->getLayerFE().getCompositionState()->compositionType ==
@@ -482,7 +483,11 @@
                                       return hash;
                                   }};
 
-    static const constexpr size_t kNumNonUniqueFields = 17;
+    OutputLayerState<bool, LayerStateField::HasProtectedContent> mIsProtected{[](auto layer) {
+        return layer->getLayerFE().getCompositionState()->hasProtectedContent;
+    }};
+
+    static const constexpr size_t kNumNonUniqueFields = 18;
 
     std::array<StateInterface*, kNumNonUniqueFields> getNonUniqueFields() {
         std::array<const StateInterface*, kNumNonUniqueFields> constFields =
@@ -501,7 +506,7 @@
                 &mAlpha,        &mLayerMetadata,  &mVisibleRegion,        &mOutputDataspace,
                 &mPixelFormat,  &mColorTransform, &mCompositionType,      &mSidebandStream,
                 &mBuffer,       &mSolidColor,     &mBackgroundBlurRadius, &mBlurRegions,
-                &mFrameNumber,
+                &mFrameNumber,  &mIsProtected,
         };
     }
 };
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/CompositionEngine.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/CompositionEngine.h
index a48cc6f..9b2387b 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/CompositionEngine.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/CompositionEngine.h
@@ -40,9 +40,9 @@
     MOCK_METHOD1(setHwComposer, void(std::unique_ptr<HWComposer>));
 
     MOCK_CONST_METHOD0(getRenderEngine, renderengine::RenderEngine&());
-    MOCK_METHOD1(setRenderEngine, void(std::unique_ptr<renderengine::RenderEngine>));
+    MOCK_METHOD1(setRenderEngine, void(renderengine::RenderEngine*));
 
-    MOCK_CONST_METHOD0(getTimeStats, TimeStats&());
+    MOCK_CONST_METHOD0(getTimeStats, TimeStats*());
     MOCK_METHOD1(setTimeStats, void(const std::shared_ptr<TimeStats>&));
 
     MOCK_CONST_METHOD0(needsAnotherUpdate, bool());
diff --git a/services/surfaceflinger/CompositionEngine/src/CompositionEngine.cpp b/services/surfaceflinger/CompositionEngine/src/CompositionEngine.cpp
index a4e1fff..15fadbc 100644
--- a/services/surfaceflinger/CompositionEngine/src/CompositionEngine.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/CompositionEngine.cpp
@@ -65,15 +65,15 @@
 }
 
 renderengine::RenderEngine& CompositionEngine::getRenderEngine() const {
-    return *mRenderEngine.get();
+    return *mRenderEngine;
 }
 
-void CompositionEngine::setRenderEngine(std::unique_ptr<renderengine::RenderEngine> renderEngine) {
-    mRenderEngine = std::move(renderEngine);
+void CompositionEngine::setRenderEngine(renderengine::RenderEngine* renderEngine) {
+    mRenderEngine = renderEngine;
 }
 
-TimeStats& CompositionEngine::getTimeStats() const {
-    return *mTimeStats.get();
+TimeStats* CompositionEngine::getTimeStats() const {
+    return mTimeStats.get();
 }
 
 void CompositionEngine::setTimeStats(const std::shared_ptr<TimeStats>& timeStats) {
diff --git a/services/surfaceflinger/CompositionEngine/src/Display.cpp b/services/surfaceflinger/CompositionEngine/src/Display.cpp
index 0b69d44..24669c2 100644
--- a/services/surfaceflinger/CompositionEngine/src/Display.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/Display.cpp
@@ -25,6 +25,7 @@
 #include <compositionengine/impl/DumpHelpers.h>
 #include <compositionengine/impl/OutputLayer.h>
 #include <compositionengine/impl/RenderSurface.h>
+#include <gui/TraceUtils.h>
 
 #include <utils/Trace.h>
 
@@ -235,7 +236,7 @@
 
 bool Display::chooseCompositionStrategy(
         std::optional<android::HWComposer::DeviceRequestedChanges>* outChanges) {
-    ATRACE_CALL();
+    ATRACE_FORMAT("%s for %s", __func__, getNamePlusId().c_str());
     ALOGV(__FUNCTION__);
 
     if (mIsDisconnected) {
@@ -248,12 +249,17 @@
         return false;
     }
 
-    const TimePoint startTime = TimePoint::now();
-
     // 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();
+
+    if (isPowerHintSessionEnabled()) {
+        mPowerAdvisor->setRequiresClientComposition(mId, requiresClientComposition);
+    }
+
+    const TimePoint hwcValidateStartTime = TimePoint::now();
+
     if (status_t result =
                 hwc.getDeviceCompositionChanges(*halDisplayId, requiresClientComposition,
                                                 getState().earliestPresentTime,
@@ -266,8 +272,10 @@
     }
 
     if (isPowerHintSessionEnabled()) {
-        mPowerAdvisor->setHwcValidateTiming(mId, startTime, TimePoint::now());
-        mPowerAdvisor->setRequiresClientComposition(mId, requiresClientComposition);
+        mPowerAdvisor->setHwcValidateTiming(mId, hwcValidateStartTime, TimePoint::now());
+        if (auto halDisplayId = HalDisplayId::tryCast(mId)) {
+            mPowerAdvisor->setSkippedValidate(mId, hwc.getValidateSkipped(*halDisplayId));
+        }
     }
 
     return true;
@@ -432,13 +440,6 @@
     }
 
     impl::Output::finishFrame(refreshArgs, std::move(result));
-
-    if (isPowerHintSessionEnabled()) {
-        auto& hwc = getCompositionEngine().getHwComposer();
-        if (auto halDisplayId = HalDisplayId::tryCast(mId)) {
-            mPowerAdvisor->setSkippedValidate(mId, hwc.getValidateSkipped(*halDisplayId));
-        }
-    }
 }
 
 } // namespace android::compositionengine::impl
diff --git a/services/surfaceflinger/CompositionEngine/src/Output.cpp b/services/surfaceflinger/CompositionEngine/src/Output.cpp
index e3f3680..3ee8017 100644
--- a/services/surfaceflinger/CompositionEngine/src/Output.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/Output.cpp
@@ -29,6 +29,7 @@
 #include <compositionengine/impl/OutputLayerCompositionState.h>
 #include <compositionengine/impl/planner/Planner.h>
 #include <ftl/future.h>
+#include <gui/TraceUtils.h>
 
 #include <thread>
 
@@ -116,6 +117,9 @@
 
 void Output::setName(const std::string& name) {
     mName = name;
+    auto displayIdOpt = getDisplayId();
+    mNamePlusId = base::StringPrintf("%s (%s)", mName.c_str(),
+                                     displayIdOpt ? to_string(*displayIdOpt).c_str() : "NA");
 }
 
 void Output::setCompositionEnabled(bool enabled) {
@@ -427,7 +431,7 @@
 }
 
 void Output::present(const compositionengine::CompositionRefreshArgs& refreshArgs) {
-    ATRACE_CALL();
+    ATRACE_FORMAT("%s for %s", __func__, mNamePlusId.c_str());
     ALOGV(__FUNCTION__);
 
     updateColorProfile(refreshArgs);
@@ -900,6 +904,13 @@
     ui::Dataspace bestDataSpace = ui::Dataspace::V0_SRGB;
     *outHdrDataSpace = ui::Dataspace::UNKNOWN;
 
+    // An Output's layers may be stale when it is disabled. As a consequence, the layers returned by
+    // getOutputLayersOrderedByZ may not be in a valid state and it is not safe to access their
+    // properties. Return a default dataspace value in this case.
+    if (!getState().isEnabled) {
+        return ui::Dataspace::V0_SRGB;
+    }
+
     for (const auto* layer : getOutputLayersOrderedByZ()) {
         switch (layer->getLayerFE().getCompositionState()->dataspace) {
             case ui::Dataspace::V0_SCRGB:
@@ -1171,15 +1182,9 @@
         bool needsProtected = std::any_of(layers.begin(), layers.end(), [](auto* layer) {
             return layer->getLayerFE().getCompositionState()->hasProtectedContent;
         });
-        if (needsProtected != renderEngine.isProtected()) {
-            renderEngine.useProtectedContext(needsProtected);
-        }
-        if (needsProtected != mRenderSurface->isProtected() &&
-            needsProtected == renderEngine.isProtected()) {
+        if (needsProtected != mRenderSurface->isProtected()) {
             mRenderSurface->setProtected(needsProtected);
         }
-    } else if (!outputState.isSecure && renderEngine.isProtected()) {
-        renderEngine.useProtectedContext(false);
     }
 }
 
@@ -1225,40 +1230,8 @@
 
     ALOGV("hasClientComposition");
 
-    renderengine::DisplaySettings clientCompositionDisplay;
-    clientCompositionDisplay.physicalDisplay = outputState.framebufferSpace.getContent();
-    clientCompositionDisplay.clip = outputState.layerStackSpace.getContent();
-    clientCompositionDisplay.orientation =
-            ui::Transform::toRotationFlags(outputState.displaySpace.getOrientation());
-    clientCompositionDisplay.outputDataspace = mDisplayColorProfile->hasWideColorGamut()
-            ? outputState.dataspace
-            : ui::Dataspace::UNKNOWN;
-
-    // If we have a valid current display brightness use that, otherwise fall back to the
-    // display's max desired
-    clientCompositionDisplay.currentLuminanceNits = outputState.displayBrightnessNits > 0.f
-            ? outputState.displayBrightnessNits
-            : mDisplayColorProfile->getHdrCapabilities().getDesiredMaxLuminance();
-    clientCompositionDisplay.maxLuminance =
-            mDisplayColorProfile->getHdrCapabilities().getDesiredMaxLuminance();
-    clientCompositionDisplay.targetLuminanceNits =
-            outputState.clientTargetBrightness * outputState.displayBrightnessNits;
-    clientCompositionDisplay.dimmingStage = outputState.clientTargetDimmingStage;
-    clientCompositionDisplay.renderIntent =
-            static_cast<aidl::android::hardware::graphics::composer3::RenderIntent>(
-                    outputState.renderIntent);
-
-    // Compute the global color transform matrix.
-    clientCompositionDisplay.colorTransform = outputState.colorTransformMatrix;
-    for (auto& info : outputState.borderInfoList) {
-        renderengine::BorderRenderInfo borderInfo;
-        borderInfo.width = info.width;
-        borderInfo.color = info.color;
-        borderInfo.combinedRegion = info.combinedRegion;
-        clientCompositionDisplay.borderInfoList.emplace_back(std::move(borderInfo));
-    }
-    clientCompositionDisplay.deviceHandlesColorTransform =
-            outputState.usesDeviceComposition || getSkipColorTransform();
+    renderengine::DisplaySettings clientCompositionDisplay =
+            generateClientCompositionDisplaySettings();
 
     // Generate the client composition requests for the layers on this output.
     auto& renderEngine = getCompositionEngine().getRenderEngine();
@@ -1333,10 +1306,13 @@
 
     const auto fence = std::move(fenceResult).value_or(Fence::NO_FENCE);
 
-    if (auto& timeStats = getCompositionEngine().getTimeStats(); fence->isValid()) {
-        timeStats.recordRenderEngineDuration(renderEngineStart, std::make_shared<FenceTime>(fence));
-    } else {
-        timeStats.recordRenderEngineDuration(renderEngineStart, systemTime());
+    if (auto timeStats = getCompositionEngine().getTimeStats()) {
+        if (fence->isValid()) {
+            timeStats->recordRenderEngineDuration(renderEngineStart,
+                                                  std::make_shared<FenceTime>(fence));
+        } else {
+            timeStats->recordRenderEngineDuration(renderEngineStart, systemTime());
+        }
     }
 
     for (auto* clientComposedLayer : clientCompositionLayersFE) {
@@ -1346,6 +1322,47 @@
     return base::unique_fd(fence->dup());
 }
 
+renderengine::DisplaySettings Output::generateClientCompositionDisplaySettings() const {
+    const auto& outputState = getState();
+
+    renderengine::DisplaySettings clientCompositionDisplay;
+    clientCompositionDisplay.namePlusId = mNamePlusId;
+    clientCompositionDisplay.physicalDisplay = outputState.framebufferSpace.getContent();
+    clientCompositionDisplay.clip = outputState.layerStackSpace.getContent();
+    clientCompositionDisplay.orientation =
+            ui::Transform::toRotationFlags(outputState.displaySpace.getOrientation());
+    clientCompositionDisplay.outputDataspace = mDisplayColorProfile->hasWideColorGamut()
+            ? outputState.dataspace
+            : ui::Dataspace::UNKNOWN;
+
+    // If we have a valid current display brightness use that, otherwise fall back to the
+    // display's max desired
+    clientCompositionDisplay.currentLuminanceNits = outputState.displayBrightnessNits > 0.f
+            ? outputState.displayBrightnessNits
+            : mDisplayColorProfile->getHdrCapabilities().getDesiredMaxLuminance();
+    clientCompositionDisplay.maxLuminance =
+            mDisplayColorProfile->getHdrCapabilities().getDesiredMaxLuminance();
+    clientCompositionDisplay.targetLuminanceNits =
+            outputState.clientTargetBrightness * outputState.displayBrightnessNits;
+    clientCompositionDisplay.dimmingStage = outputState.clientTargetDimmingStage;
+    clientCompositionDisplay.renderIntent =
+            static_cast<aidl::android::hardware::graphics::composer3::RenderIntent>(
+                    outputState.renderIntent);
+
+    // Compute the global color transform matrix.
+    clientCompositionDisplay.colorTransform = outputState.colorTransformMatrix;
+    for (auto& info : outputState.borderInfoList) {
+        renderengine::BorderRenderInfo borderInfo;
+        borderInfo.width = info.width;
+        borderInfo.color = info.color;
+        borderInfo.combinedRegion = info.combinedRegion;
+        clientCompositionDisplay.borderInfoList.emplace_back(std::move(borderInfo));
+    }
+    clientCompositionDisplay.deviceHandlesColorTransform =
+            outputState.usesDeviceComposition || getSkipColorTransform();
+    return clientCompositionDisplay;
+}
+
 std::vector<LayerFE::LayerSettings> Output::generateClientCompositionRequests(
       bool supportsProtectedContent, ui::Dataspace outputDataspace, std::vector<LayerFE*>& outLayerFEs) {
     std::vector<LayerFE::LayerSettings> clientCompositionLayers;
@@ -1411,7 +1428,7 @@
                                              Enabled);
                 compositionengine::LayerFE::ClientCompositionTargetSettings
                         targetSettings{.clip = clip,
-                                       .needsFiltering = layer->needsFiltering() ||
+                                       .needsFiltering = layerNeedsFiltering(layer) ||
                                                outputState.needsFiltering,
                                        .isSecure = outputState.isSecure,
                                        .supportsProtectedContent = supportsProtectedContent,
@@ -1420,7 +1437,8 @@
                                        .realContentIsVisible = realContentIsVisible,
                                        .clearContent = !clientComposition,
                                        .blurSetting = blurSetting,
-                                       .whitePointNits = layerState.whitePointNits};
+                                       .whitePointNits = layerState.whitePointNits,
+                                       .treat170mAsSrgb = outputState.treat170mAsSrgb};
                 if (auto clientCompositionSettings =
                             layerFE.prepareClientComposition(targetSettings)) {
                     clientCompositionLayers.push_back(std::move(*clientCompositionSettings));
@@ -1441,6 +1459,10 @@
     return clientCompositionLayers;
 }
 
+bool Output::layerNeedsFiltering(const compositionengine::OutputLayer* layer) const {
+    return layer->needsFiltering();
+}
+
 void Output::appendRegionFlashRequests(
         const Region& flashRegion, std::vector<LayerFE::LayerSettings>& clientCompositionLayers) {
     if (flashRegion.isEmpty()) {
@@ -1471,7 +1493,7 @@
 }
 
 void Output::postFramebuffer() {
-    ATRACE_CALL();
+    ATRACE_FORMAT("%s for %s", __func__, mNamePlusId.c_str());
     ALOGV(__FUNCTION__);
 
     if (!getState().isEnabled) {
diff --git a/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp b/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp
index 0731d48..ed9a88d 100644
--- a/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp
@@ -411,8 +411,8 @@
 
     if (mLayers.size() == 1) {
         base::StringAppendF(&result, "    Layer [%s]\n", mLayers[0].getName().c_str());
-        if (auto* buffer = mLayers[0].getBuffer().get()) {
-            base::StringAppendF(&result, "    Buffer %p", buffer);
+        if (const sp<GraphicBuffer> buffer = mLayers[0].getState()->getBuffer().promote()) {
+            base::StringAppendF(&result, "    Buffer %p", buffer.get());
             base::StringAppendF(&result, "    Format %s",
                                 decodePixelFormat(buffer->getPixelFormat()).c_str());
         }
@@ -422,8 +422,8 @@
         result.append("    Cached set of:\n");
         for (const Layer& layer : mLayers) {
             base::StringAppendF(&result, "      Layer [%s]\n", layer.getName().c_str());
-            if (auto* buffer = layer.getBuffer().get()) {
-                base::StringAppendF(&result, "       Buffer %p", buffer);
+            if (const sp<GraphicBuffer> buffer = layer.getState()->getBuffer().promote()) {
+                base::StringAppendF(&result, "       Buffer %p", buffer.get());
                 base::StringAppendF(&result, "    Format[%s]",
                                     decodePixelFormat(buffer->getPixelFormat()).c_str());
             }
diff --git a/services/surfaceflinger/CompositionEngine/tests/CompositionEngineTest.cpp b/services/surfaceflinger/CompositionEngine/tests/CompositionEngineTest.cpp
index b570979..60ed660 100644
--- a/services/surfaceflinger/CompositionEngine/tests/CompositionEngineTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/CompositionEngineTest.cpp
@@ -62,17 +62,16 @@
 }
 
 TEST_F(CompositionEngineTest, canSetRenderEngine) {
-    renderengine::mock::RenderEngine* renderEngine =
-            new StrictMock<renderengine::mock::RenderEngine>();
-    mEngine.setRenderEngine(std::unique_ptr<renderengine::RenderEngine>(renderEngine));
+    auto renderEngine = std::make_unique<StrictMock<renderengine::mock::RenderEngine>>();
+    mEngine.setRenderEngine(renderEngine.get());
 
-    EXPECT_EQ(renderEngine, &mEngine.getRenderEngine());
+    EXPECT_EQ(renderEngine.get(), &mEngine.getRenderEngine());
 }
 
 TEST_F(CompositionEngineTest, canSetTimeStats) {
     mEngine.setTimeStats(mTimeStats);
 
-    EXPECT_EQ(mTimeStats.get(), &mEngine.getTimeStats());
+    EXPECT_EQ(mTimeStats.get(), mEngine.getTimeStats());
 }
 
 /*
diff --git a/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h b/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h
index 9102139..33caa7a 100644
--- a/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h
+++ b/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h
@@ -139,6 +139,8 @@
     MOCK_METHOD(Hwc2::AidlTransform, getPhysicalDisplayOrientation, (PhysicalDisplayId),
                 (const, override));
     MOCK_METHOD(bool, getValidateSkipped, (HalDisplayId), (const, override));
+    MOCK_METHOD(const aidl::android::hardware::graphics::composer3::OverlayProperties&,
+                getOverlaySupport, (), (const, override));
 };
 
 } // namespace mock
diff --git a/services/surfaceflinger/CompositionEngine/tests/MockPowerAdvisor.h b/services/surfaceflinger/CompositionEngine/tests/MockPowerAdvisor.h
index e220541..c555b39 100644
--- a/services/surfaceflinger/CompositionEngine/tests/MockPowerAdvisor.h
+++ b/services/surfaceflinger/CompositionEngine/tests/MockPowerAdvisor.h
@@ -34,7 +34,7 @@
     MOCK_METHOD(void, setExpensiveRenderingExpected, (DisplayId displayId, bool expected),
                 (override));
     MOCK_METHOD(bool, isUsingExpensiveRendering, (), (override));
-    MOCK_METHOD(void, notifyDisplayUpdateImminent, (), (override));
+    MOCK_METHOD(void, notifyDisplayUpdateImminentAndCpuReset, (), (override));
     MOCK_METHOD(bool, usePowerHintSession, (), (override));
     MOCK_METHOD(bool, supportsPowerHintSession, (), (override));
     MOCK_METHOD(bool, isPowerHintSessionRunning, (), (override));
diff --git a/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
index eb209e9..2109987 100644
--- a/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
@@ -2081,6 +2081,7 @@
         mOutput.setDisplayColorProfileForTest(
                 std::unique_ptr<DisplayColorProfile>(mDisplayColorProfile));
         mOutput.setRenderSurfaceForTest(std::unique_ptr<RenderSurface>(mRenderSurface));
+        mOutput.editState().isEnabled = true;
 
         EXPECT_CALL(mOutput, getOutputLayerOrderedByZByIndex(0))
                 .WillRepeatedly(Return(&mLayer1.mOutputLayer));
@@ -3331,8 +3332,7 @@
 
         EXPECT_CALL(mOutput, getCompositionEngine()).WillRepeatedly(ReturnRef(mCompositionEngine));
         EXPECT_CALL(mCompositionEngine, getRenderEngine()).WillRepeatedly(ReturnRef(mRenderEngine));
-        EXPECT_CALL(mCompositionEngine, getTimeStats())
-                .WillRepeatedly(ReturnRef(*mTimeStats.get()));
+        EXPECT_CALL(mCompositionEngine, getTimeStats()).WillRepeatedly(Return(mTimeStats.get()));
         EXPECT_CALL(*mDisplayColorProfile, getHdrCapabilities())
                 .WillRepeatedly(ReturnRef(kHdrCapabilities));
     }
@@ -4009,39 +4009,11 @@
     Layer mLayer2;
 };
 
-TEST_F(OutputComposeSurfacesTest_HandlesProtectedContent, ifDisplayIsNotSecure) {
-    mOutput.mState.isSecure = false;
-    mLayer2.mLayerFEState.hasProtectedContent = true;
-    EXPECT_CALL(mRenderEngine, supportsProtectedContent()).WillRepeatedly(Return(true));
-    EXPECT_CALL(mRenderEngine, isProtected).WillOnce(Return(true));
-    EXPECT_CALL(mRenderEngine, useProtectedContext(false));
-
-    base::unique_fd fd;
-    std::shared_ptr<renderengine::ExternalTexture> tex;
-    mOutput.updateProtectedContentState();
-    mOutput.dequeueRenderBuffer(&fd, &tex);
-    mOutput.composeSurfaces(kDebugRegion, kDefaultRefreshArgs, tex, fd);
-}
-
-TEST_F(OutputComposeSurfacesTest_HandlesProtectedContent, ifRenderEngineDoesNotSupportIt) {
-    mOutput.mState.isSecure = true;
-    mLayer2.mLayerFEState.hasProtectedContent = true;
-    EXPECT_CALL(mRenderEngine, supportsProtectedContent()).WillRepeatedly(Return(false));
-
-    base::unique_fd fd;
-    std::shared_ptr<renderengine::ExternalTexture> tex;
-    mOutput.updateProtectedContentState();
-    mOutput.dequeueRenderBuffer(&fd, &tex);
-    mOutput.composeSurfaces(kDebugRegion, kDefaultRefreshArgs, tex, fd);
-}
-
 TEST_F(OutputComposeSurfacesTest_HandlesProtectedContent, ifNoProtectedContentLayers) {
     mOutput.mState.isSecure = true;
     mLayer2.mLayerFEState.hasProtectedContent = false;
     EXPECT_CALL(mRenderEngine, supportsProtectedContent()).WillRepeatedly(Return(true));
-    EXPECT_CALL(mRenderEngine, isProtected).WillOnce(Return(true)).WillOnce(Return(false));
     EXPECT_CALL(*mRenderSurface, isProtected).WillOnce(Return(true));
-    EXPECT_CALL(mRenderEngine, useProtectedContext(false));
     EXPECT_CALL(*mRenderSurface, setProtected(false));
 
     base::unique_fd fd;
@@ -4059,10 +4031,7 @@
     // For this test, we also check the call order of key functions.
     InSequence seq;
 
-    EXPECT_CALL(mRenderEngine, isProtected).WillOnce(Return(false));
-    EXPECT_CALL(mRenderEngine, useProtectedContext(true));
     EXPECT_CALL(*mRenderSurface, isProtected).WillOnce(Return(false));
-    EXPECT_CALL(mRenderEngine, isProtected).WillOnce(Return(true));
     EXPECT_CALL(*mRenderSurface, setProtected(true));
     // Must happen after setting the protected content state.
     EXPECT_CALL(*mRenderSurface, dequeueBuffer(_)).WillRepeatedly(Return(mOutputBuffer));
@@ -4080,7 +4049,6 @@
     mOutput.mState.isSecure = true;
     mLayer2.mLayerFEState.hasProtectedContent = true;
     EXPECT_CALL(mRenderEngine, supportsProtectedContent()).WillRepeatedly(Return(true));
-    EXPECT_CALL(mRenderEngine, isProtected).WillOnce(Return(true));
     EXPECT_CALL(*mRenderSurface, isProtected).WillOnce(Return(true));
 
     base::unique_fd fd;
@@ -4090,43 +4058,11 @@
     mOutput.composeSurfaces(kDebugRegion, kDefaultRefreshArgs, tex, fd);
 }
 
-TEST_F(OutputComposeSurfacesTest_HandlesProtectedContent, ifFailsToEnableInRenderEngine) {
-    mOutput.mState.isSecure = true;
-    mLayer2.mLayerFEState.hasProtectedContent = true;
-    EXPECT_CALL(mRenderEngine, supportsProtectedContent()).WillRepeatedly(Return(true));
-    EXPECT_CALL(mRenderEngine, isProtected).WillOnce(Return(false)).WillOnce(Return(false));
-    EXPECT_CALL(*mRenderSurface, isProtected).WillOnce(Return(false));
-    EXPECT_CALL(mRenderEngine, useProtectedContext(true));
-
-    base::unique_fd fd;
-    std::shared_ptr<renderengine::ExternalTexture> tex;
-    mOutput.updateProtectedContentState();
-    mOutput.dequeueRenderBuffer(&fd, &tex);
-    mOutput.composeSurfaces(kDebugRegion, kDefaultRefreshArgs, tex, fd);
-}
-
-TEST_F(OutputComposeSurfacesTest_HandlesProtectedContent, ifAlreadyEnabledInRenderEngine) {
-    mOutput.mState.isSecure = true;
-    mLayer2.mLayerFEState.hasProtectedContent = true;
-    EXPECT_CALL(mRenderEngine, supportsProtectedContent()).WillRepeatedly(Return(true));
-    EXPECT_CALL(mRenderEngine, isProtected).WillOnce(Return(true)).WillOnce(Return(true));
-    EXPECT_CALL(*mRenderSurface, isProtected).WillOnce(Return(false));
-    EXPECT_CALL(*mRenderSurface, setProtected(true));
-
-    base::unique_fd fd;
-    std::shared_ptr<renderengine::ExternalTexture> tex;
-    mOutput.updateProtectedContentState();
-    mOutput.dequeueRenderBuffer(&fd, &tex);
-    mOutput.composeSurfaces(kDebugRegion, kDefaultRefreshArgs, tex, fd);
-}
-
 TEST_F(OutputComposeSurfacesTest_HandlesProtectedContent, ifAlreadyEnabledInRenderSurface) {
     mOutput.mState.isSecure = true;
     mLayer2.mLayerFEState.hasProtectedContent = true;
     EXPECT_CALL(mRenderEngine, supportsProtectedContent()).WillRepeatedly(Return(true));
-    EXPECT_CALL(mRenderEngine, isProtected).WillOnce(Return(false));
     EXPECT_CALL(*mRenderSurface, isProtected).WillOnce(Return(true));
-    EXPECT_CALL(mRenderEngine, useProtectedContext(true));
 
     base::unique_fd fd;
     std::shared_ptr<renderengine::ExternalTexture> tex;
@@ -4464,6 +4400,7 @@
             true /* clearContent */,
             compositionengine::LayerFE::ClientCompositionTargetSettings::BlurSetting::Enabled,
             kLayerWhitePointNits,
+            false /* treat170mAsSrgb */,
     };
     compositionengine::LayerFE::ClientCompositionTargetSettings layer2TargetSettings{
             Region(kDisplayFrame),
@@ -4476,6 +4413,7 @@
             false /* clearContent */,
             compositionengine::LayerFE::ClientCompositionTargetSettings::BlurSetting::Enabled,
             kLayerWhitePointNits,
+            false /* treat170mAsSrgb */,
     };
 
     LayerFE::LayerSettings mBlackoutSettings = mLayers[1].mLayerSettings;
@@ -4516,6 +4454,7 @@
             false /* clearContent */,
             compositionengine::LayerFE::ClientCompositionTargetSettings::BlurSetting::Enabled,
             kLayerWhitePointNits,
+            false /* treat170mAsSrgb */,
     };
     compositionengine::LayerFE::ClientCompositionTargetSettings layer1TargetSettings{
             Region(Rect(0, 0, 30, 30)),
@@ -4528,6 +4467,7 @@
             false /* clearContent */,
             compositionengine::LayerFE::ClientCompositionTargetSettings::BlurSetting::Enabled,
             kLayerWhitePointNits,
+            false /* treat170mAsSrgb */,
     };
     compositionengine::LayerFE::ClientCompositionTargetSettings layer2TargetSettings{
             Region(Rect(0, 0, 40, 201)),
@@ -4540,6 +4480,7 @@
             false /* clearContent */,
             compositionengine::LayerFE::ClientCompositionTargetSettings::BlurSetting::Enabled,
             kLayerWhitePointNits,
+            false /* treat170mAsSrgb */,
     };
 
     EXPECT_CALL(*mLayers[0].mLayerFE, prepareClientComposition(Eq(ByRef(layer0TargetSettings))))
@@ -4570,6 +4511,7 @@
             false /* clearContent */,
             compositionengine::LayerFE::ClientCompositionTargetSettings::BlurSetting::Enabled,
             kLayerWhitePointNits,
+            false /* treat170mAsSrgb */,
     };
     compositionengine::LayerFE::ClientCompositionTargetSettings layer1TargetSettings{
             Region(kDisplayFrame),
@@ -4582,6 +4524,7 @@
             false /* clearContent */,
             compositionengine::LayerFE::ClientCompositionTargetSettings::BlurSetting::Enabled,
             kLayerWhitePointNits,
+            false /* treat170mAsSrgb */,
     };
     compositionengine::LayerFE::ClientCompositionTargetSettings layer2TargetSettings{
             Region(kDisplayFrame),
@@ -4594,6 +4537,7 @@
             false /* clearContent */,
             compositionengine::LayerFE::ClientCompositionTargetSettings::BlurSetting::Enabled,
             kLayerWhitePointNits,
+            false /* treat170mAsSrgb */,
     };
 
     EXPECT_CALL(*mLayers[0].mLayerFE, prepareClientComposition(Eq(ByRef(layer0TargetSettings))))
@@ -4624,6 +4568,7 @@
             false /* clearContent */,
             compositionengine::LayerFE::ClientCompositionTargetSettings::BlurSetting::Enabled,
             kLayerWhitePointNits,
+            false /* treat170mAsSrgb */,
     };
     compositionengine::LayerFE::ClientCompositionTargetSettings layer1TargetSettings{
             Region(kDisplayFrame),
@@ -4636,6 +4581,7 @@
             false /* clearContent */,
             compositionengine::LayerFE::ClientCompositionTargetSettings::BlurSetting::Enabled,
             kLayerWhitePointNits,
+            false /* treat170mAsSrgb */,
     };
     compositionengine::LayerFE::ClientCompositionTargetSettings layer2TargetSettings{
             Region(kDisplayFrame),
@@ -4648,6 +4594,7 @@
             false /* clearContent */,
             compositionengine::LayerFE::ClientCompositionTargetSettings::BlurSetting::Enabled,
             kLayerWhitePointNits,
+            false /* treat170mAsSrgb */,
     };
 
     EXPECT_CALL(*mLayers[0].mLayerFE, prepareClientComposition(Eq(ByRef(layer0TargetSettings))))
@@ -4677,6 +4624,7 @@
             false /* clearContent */,
             compositionengine::LayerFE::ClientCompositionTargetSettings::BlurSetting::Enabled,
             kLayerWhitePointNits,
+            false /* treat170mAsSrgb */,
     };
     compositionengine::LayerFE::ClientCompositionTargetSettings layer1TargetSettings{
             Region(kDisplayFrame),
@@ -4689,6 +4637,7 @@
             false /* clearContent */,
             compositionengine::LayerFE::ClientCompositionTargetSettings::BlurSetting::Enabled,
             kLayerWhitePointNits,
+            false /* treat170mAsSrgb */,
     };
     compositionengine::LayerFE::ClientCompositionTargetSettings layer2TargetSettings{
             Region(kDisplayFrame),
@@ -4701,6 +4650,7 @@
             false /* clearContent */,
             compositionengine::LayerFE::ClientCompositionTargetSettings::BlurSetting::Enabled,
             kLayerWhitePointNits,
+            false /* treat170mAsSrgb */,
     };
 
     EXPECT_CALL(*mLayers[0].mLayerFE, prepareClientComposition(Eq(ByRef(layer0TargetSettings))))
@@ -4728,6 +4678,7 @@
             false /* clearContent */,
             compositionengine::LayerFE::ClientCompositionTargetSettings::BlurSetting::Enabled,
             kLayerWhitePointNits,
+            false /* treat170mAsSrgb */,
     };
     compositionengine::LayerFE::ClientCompositionTargetSettings layer1TargetSettings{
             Region(kDisplayFrame),
@@ -4740,6 +4691,7 @@
             false /* clearContent */,
             compositionengine::LayerFE::ClientCompositionTargetSettings::BlurSetting::Enabled,
             kLayerWhitePointNits,
+            false /* treat170mAsSrgb */,
     };
     compositionengine::LayerFE::ClientCompositionTargetSettings layer2TargetSettings{
             Region(kDisplayFrame),
@@ -4752,6 +4704,7 @@
             false /* clearContent */,
             compositionengine::LayerFE::ClientCompositionTargetSettings::BlurSetting::Enabled,
             kLayerWhitePointNits,
+            false /* treat170mAsSrgb */,
     };
 
     EXPECT_CALL(*mLayers[0].mLayerFE, prepareClientComposition(Eq(ByRef(layer0TargetSettings))))
@@ -4936,6 +4889,7 @@
             false /* clearContent */,
             compositionengine::LayerFE::ClientCompositionTargetSettings::BlurSetting::Enabled,
             kLayerWhitePointNits,
+            false /* treat170mAsSrgb */,
     };
 
     EXPECT_CALL(leftLayer.mOutputLayer, requiresClientComposition()).WillRepeatedly(Return(true));
@@ -4954,6 +4908,7 @@
             false /* clearContent */,
             compositionengine::LayerFE::ClientCompositionTargetSettings::BlurSetting::Enabled,
             kLayerWhitePointNits,
+            false /* treat170mAsSrgb */,
     };
 
     EXPECT_CALL(rightLayer.mOutputLayer, requiresClientComposition()).WillRepeatedly(Return(true));
@@ -4987,6 +4942,7 @@
             false /* clearContent */,
             compositionengine::LayerFE::ClientCompositionTargetSettings::BlurSetting::Enabled,
             kLayerWhitePointNits,
+            false /* treat170mAsSrgb */,
     };
 
     LayerFE::LayerSettings mShadowSettings;
@@ -5029,6 +4985,7 @@
             false /* clearContent */,
             compositionengine::LayerFE::ClientCompositionTargetSettings::BlurSetting::Enabled,
             kLayerWhitePointNits,
+            false /* treat170mAsSrgb */,
     };
 
     EXPECT_CALL(mLayers[0].mOutputLayer, requiresClientComposition()).WillOnce(Return(false));
diff --git a/services/surfaceflinger/Display/DisplayMap.h b/services/surfaceflinger/Display/DisplayMap.h
index baf0da9..0d59706 100644
--- a/services/surfaceflinger/Display/DisplayMap.h
+++ b/services/surfaceflinger/Display/DisplayMap.h
@@ -17,6 +17,7 @@
 #pragma once
 
 #include <ftl/small_map.h>
+#include <ftl/small_vector.h>
 
 namespace android::display {
 
@@ -28,4 +29,7 @@
 template <typename Key, typename Value>
 using PhysicalDisplayMap = ftl::SmallMap<Key, Value, 3>;
 
+template <typename T>
+using PhysicalDisplayVector = ftl::SmallVector<T, 3>;
+
 } // namespace android::display
diff --git a/services/surfaceflinger/Display/DisplayModeRequest.h b/services/surfaceflinger/Display/DisplayModeRequest.h
new file mode 100644
index 0000000..d07cdf5
--- /dev/null
+++ b/services/surfaceflinger/Display/DisplayModeRequest.h
@@ -0,0 +1,36 @@
+/*
+ * 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 <ftl/non_null.h>
+
+#include <scheduler/FrameRateMode.h>
+
+namespace android::display {
+
+struct DisplayModeRequest {
+    scheduler::FrameRateMode mode;
+
+    // Whether to emit DisplayEventReceiver::DISPLAY_EVENT_MODE_CHANGE.
+    bool emitEvent = false;
+};
+
+inline bool operator==(const DisplayModeRequest& lhs, const DisplayModeRequest& rhs) {
+    return lhs.mode == rhs.mode && lhs.emitEvent == rhs.emitEvent;
+}
+
+} // namespace android::display
diff --git a/services/surfaceflinger/DisplayDevice.cpp b/services/surfaceflinger/DisplayDevice.cpp
index c63d57f..46b857b 100644
--- a/services/surfaceflinger/DisplayDevice.cpp
+++ b/services/surfaceflinger/DisplayDevice.cpp
@@ -41,6 +41,7 @@
 
 #include "Display/DisplaySnapshot.h"
 #include "DisplayDevice.h"
+#include "FrontEnd/DisplayInfo.h"
 #include "Layer.h"
 #include "RefreshRateOverlay.h"
 #include "SurfaceFlinger.h"
@@ -67,9 +68,10 @@
         mCompositionDisplay{args.compositionDisplay},
         mActiveModeFPSTrace("ActiveModeFPS -" + to_string(getId())),
         mActiveModeFPSHwcTrace("ActiveModeFPS_HWC -" + to_string(getId())),
+        mRenderFrameRateFPSTrace("RenderRateFPS -" + to_string(getId())),
         mPhysicalOrientation(args.physicalOrientation),
         mIsPrimary(args.isPrimary),
-        mRefreshRateConfigs(std::move(args.refreshRateConfigs)) {
+        mRefreshRateSelector(std::move(args.refreshRateSelector)) {
     mCompositionDisplay->editState().isSecure = args.isSecure;
     mCompositionDisplay->createRenderSurface(
             compositionengine::RenderSurfaceCreationArgsBuilder()
@@ -103,7 +105,9 @@
 
     mCompositionDisplay->getRenderSurface()->initialize();
 
-    if (args.initialPowerMode.has_value()) setPowerMode(args.initialPowerMode.value());
+    if (const auto powerModeOpt = args.initialPowerMode) {
+        setPowerMode(*powerModeOpt);
+    }
 
     // initialize the display orientation transform.
     setProjection(ui::ROTATION_0, Rect::INVALID_RECT, Rect::INVALID_RECT);
@@ -131,7 +135,7 @@
     }
 }
 
-auto DisplayDevice::getInputInfo() const -> InputInfo {
+auto DisplayDevice::getFrontEndInfo() const -> frontend::DisplayInfo {
     gui::DisplayInfo info;
     info.displayId = getLayerStack().id;
 
@@ -160,7 +164,9 @@
     return {.info = info,
             .transform = displayTransform,
             .receivesInput = receivesInput(),
-            .isSecure = isSecure()};
+            .isSecure = isSecure(),
+            .isPrimary = isPrimary(),
+            .rotationFlags = ui::Transform::toRotationFlags(mOrientation)};
 }
 
 void DisplayDevice::setPowerMode(hal::PowerMode mode) {
@@ -175,8 +181,7 @@
 
     mPowerMode = mode;
 
-    getCompositionDisplay()->setCompositionEnabled(mPowerMode.has_value() &&
-                                                   *mPowerMode != hal::PowerMode::OFF);
+    getCompositionDisplay()->setCompositionEnabled(isPoweredOn());
 }
 
 void DisplayDevice::enableLayerCaching(bool enable) {
@@ -191,35 +196,32 @@
     return mPowerMode && *mPowerMode != hal::PowerMode::OFF;
 }
 
-void DisplayDevice::setActiveMode(DisplayModeId modeId, const display::DisplaySnapshot& snapshot) {
-    const auto fpsOpt = snapshot.displayModes().get(modeId).transform(
-            [](const DisplayModePtr& mode) { return mode->getFps(); });
+void DisplayDevice::setActiveMode(DisplayModeId modeId, Fps displayFps, Fps renderFps) {
+    ATRACE_INT(mActiveModeFPSTrace.c_str(), displayFps.getIntValue());
+    ATRACE_INT(mRenderFrameRateFPSTrace.c_str(), renderFps.getIntValue());
 
-    LOG_ALWAYS_FATAL_IF(!fpsOpt, "Unknown mode");
-    const Fps fps = *fpsOpt;
-
-    ATRACE_INT(mActiveModeFPSTrace.c_str(), fps.getIntValue());
-
-    mRefreshRateConfigs->setActiveModeId(modeId);
+    mRefreshRateSelector->setActiveMode(modeId, renderFps);
 
     if (mRefreshRateOverlay) {
-        mRefreshRateOverlay->changeRefreshRate(fps);
+        mRefreshRateOverlay->changeRefreshRate(displayFps, renderFps);
     }
 }
 
 status_t DisplayDevice::initiateModeChange(const ActiveModeInfo& info,
                                            const hal::VsyncPeriodChangeConstraints& constraints,
                                            hal::VsyncPeriodChangeTimeline* outTimeline) {
-    if (!info.mode || info.mode->getPhysicalDisplayId() != getPhysicalId()) {
+    if (!info.modeOpt || info.modeOpt->modePtr->getPhysicalDisplayId() != getPhysicalId()) {
         ALOGE("Trying to initiate a mode change to invalid mode %s on display %s",
-              info.mode ? std::to_string(info.mode->getId().value()).c_str() : "null",
+              info.modeOpt ? std::to_string(info.modeOpt->modePtr->getId().value()).c_str()
+                           : "null",
               to_string(getId()).c_str());
         return BAD_VALUE;
     }
     mUpcomingActiveMode = info;
-    ATRACE_INT(mActiveModeFPSHwcTrace.c_str(), info.mode->getFps().getIntValue());
-    return mHwComposer.setActiveModeWithConstraints(getPhysicalId(), info.mode->getHwcId(),
-                                                    constraints, outTimeline);
+    ATRACE_INT(mActiveModeFPSHwcTrace.c_str(), info.modeOpt->modePtr->getFps().getIntValue());
+    return mHwComposer.setActiveModeWithConstraints(getPhysicalId(),
+                                                    info.modeOpt->modePtr->getHwcId(), constraints,
+                                                    outTimeline);
 }
 
 nsecs_t DisplayDevice::getVsyncPeriodFromHWC() const {
@@ -234,7 +236,7 @@
         return vsyncPeriod;
     }
 
-    return refreshRateConfigs().getActiveModePtr()->getVsyncPeriod();
+    return refreshRateSelector().getActiveMode().modePtr->getVsyncPeriod();
 }
 
 ui::Dataspace DisplayDevice::getCompositionDataSpace() const {
@@ -313,30 +315,14 @@
     return sPrimaryDisplayRotationFlags;
 }
 
-std::string DisplayDevice::getDebugName() const {
-    using namespace std::string_literals;
-
-    std::string name = "Display "s + to_string(getId()) + " ("s;
-
-    name += isVirtual() ? "virtual"s : "physical"s;
-
-    if (isPrimary()) {
-        name += ", primary"s;
-    }
-
-    return name + ", \""s + mDisplayName + "\")"s;
-}
-
 void DisplayDevice::dump(utils::Dumper& dumper) const {
     using namespace std::string_view_literals;
 
-    dumper.dump({}, getDebugName());
-
-    utils::Dumper::Indent indent(dumper);
+    dumper.dump("name"sv, '"' + mDisplayName + '"');
     dumper.dump("powerMode"sv, mPowerMode);
 
-    if (mRefreshRateConfigs) {
-        mRefreshRateConfigs->dump(dumper);
+    if (mRefreshRateSelector) {
+        mRefreshRateSelector->dump(dumper);
     }
 }
 
@@ -424,26 +410,35 @@
                            capabilities.getDesiredMinLuminance());
 }
 
-void DisplayDevice::enableRefreshRateOverlay(bool enable, bool showSpinnner) {
+void DisplayDevice::enableRefreshRateOverlay(bool enable, bool showSpinner, bool showRenderRate) {
     if (!enable) {
         mRefreshRateOverlay.reset();
         return;
     }
 
-    const auto fpsRange = mRefreshRateConfigs->getSupportedRefreshRateRange();
-    mRefreshRateOverlay = std::make_unique<RefreshRateOverlay>(fpsRange, showSpinnner);
+    ftl::Flags<RefreshRateOverlay::Features> features;
+    if (showSpinner) {
+        features |= RefreshRateOverlay::Features::Spinner;
+    }
+
+    if (showRenderRate) {
+        features |= RefreshRateOverlay::Features::RenderRate;
+    }
+
+    const auto fpsRange = mRefreshRateSelector->getSupportedRefreshRateRange();
+    mRefreshRateOverlay = std::make_unique<RefreshRateOverlay>(fpsRange, features);
     mRefreshRateOverlay->setLayerStack(getLayerStack());
     mRefreshRateOverlay->setViewport(getSize());
-    mRefreshRateOverlay->changeRefreshRate(getActiveMode().getFps());
+    mRefreshRateOverlay->changeRefreshRate(getActiveMode().modePtr->getFps(), getActiveMode().fps);
 }
 
 bool DisplayDevice::onKernelTimerChanged(std::optional<DisplayModeId> desiredModeId,
                                          bool timerExpired) {
-    if (mRefreshRateConfigs && mRefreshRateOverlay) {
-        const auto newRefreshRate =
-                mRefreshRateConfigs->onKernelTimerChanged(desiredModeId, timerExpired);
-        if (newRefreshRate) {
-            mRefreshRateOverlay->changeRefreshRate(*newRefreshRate);
+    if (mRefreshRateSelector && mRefreshRateOverlay) {
+        const auto newMode =
+                mRefreshRateSelector->onKernelTimerChanged(desiredModeId, timerExpired);
+        if (newMode) {
+            mRefreshRateOverlay->changeRefreshRate(newMode->modePtr->getFps(), newMode->fps);
             return true;
         }
     }
@@ -457,13 +452,14 @@
     }
 }
 
-bool DisplayDevice::setDesiredActiveMode(const ActiveModeInfo& info) {
+auto DisplayDevice::setDesiredActiveMode(const ActiveModeInfo& info) -> DesiredActiveModeAction {
     ATRACE_CALL();
 
-    LOG_ALWAYS_FATAL_IF(!info.mode, "desired mode not provided");
-    LOG_ALWAYS_FATAL_IF(getPhysicalId() != info.mode->getPhysicalDisplayId(), "DisplayId mismatch");
+    LOG_ALWAYS_FATAL_IF(!info.modeOpt, "desired mode not provided");
+    LOG_ALWAYS_FATAL_IF(getPhysicalId() != info.modeOpt->modePtr->getPhysicalDisplayId(),
+                        "DisplayId mismatch");
 
-    ALOGV("%s(%s)", __func__, to_string(*info.mode).c_str());
+    ALOGV("%s(%s)", __func__, to_string(*info.modeOpt->modePtr).c_str());
 
     std::scoped_lock lock(mActiveModeLock);
     if (mDesiredActiveModeChanged) {
@@ -471,18 +467,25 @@
         const auto prevConfig = mDesiredActiveMode.event;
         mDesiredActiveMode = info;
         mDesiredActiveMode.event = mDesiredActiveMode.event | prevConfig;
-        return false;
+        return DesiredActiveModeAction::None;
     }
 
+    const auto& desiredMode = *info.modeOpt->modePtr;
+
     // Check if we are already at the desired mode
-    if (refreshRateConfigs().getActiveModePtr()->getId() == info.mode->getId()) {
-        return false;
+    if (refreshRateSelector().getActiveMode().modePtr->getId() == desiredMode.getId()) {
+        if (refreshRateSelector().getActiveMode() == info.modeOpt) {
+            return DesiredActiveModeAction::None;
+        }
+
+        setActiveMode(desiredMode.getId(), desiredMode.getFps(), info.modeOpt->fps);
+        return DesiredActiveModeAction::InitiateRenderRateSwitch;
     }
 
     // Initiate a mode change.
     mDesiredActiveModeChanged = true;
     mDesiredActiveMode = info;
-    return true;
+    return DesiredActiveModeAction::InitiateDisplayModeSwitch;
 }
 
 std::optional<DisplayDevice::ActiveModeInfo> DisplayDevice::getDesiredActiveMode() const {
diff --git a/services/surfaceflinger/DisplayDevice.h b/services/surfaceflinger/DisplayDevice.h
index 06a812b..8f9b2a1 100644
--- a/services/surfaceflinger/DisplayDevice.h
+++ b/services/surfaceflinger/DisplayDevice.h
@@ -41,14 +41,15 @@
 #include <utils/RefBase.h>
 #include <utils/Timers.h>
 
+#include "Display/DisplayModeRequest.h"
 #include "DisplayHardware/DisplayMode.h"
 #include "DisplayHardware/Hal.h"
 #include "DisplayHardware/PowerAdvisor.h"
-#include "Scheduler/RefreshRateConfigs.h"
+#include "FrontEnd/DisplayInfo.h"
+#include "Scheduler/RefreshRateSelector.h"
 #include "ThreadContext.h"
 #include "TracedOrdinal.h"
 #include "Utils/Dumper.h"
-
 namespace android {
 
 class Fence;
@@ -166,14 +167,7 @@
     void setDisplayName(const std::string& displayName);
     const std::string& getDisplayName() const { return mDisplayName; }
 
-    struct InputInfo {
-        gui::DisplayInfo info;
-        ui::Transform transform;
-        bool receivesInput;
-        bool isSecure;
-    };
-
-    InputInfo getInputInfo() const;
+    surfaceflinger::frontend::DisplayInfo getFrontEndInfo() const;
 
     /* ------------------------------------------------------------------------
      * Display power mode management.
@@ -190,46 +184,60 @@
     /* ------------------------------------------------------------------------
      * Display mode management.
      */
+
+    // TODO(b/241285876): Replace ActiveModeInfo and DisplayModeEvent with DisplayModeRequest.
     struct ActiveModeInfo {
-        DisplayModePtr mode;
-        scheduler::DisplayModeEvent event = scheduler::DisplayModeEvent::None;
+        using Event = scheduler::DisplayModeEvent;
+
+        ActiveModeInfo() = default;
+        ActiveModeInfo(scheduler::FrameRateMode mode, Event event)
+              : modeOpt(std::move(mode)), event(event) {}
+
+        explicit ActiveModeInfo(display::DisplayModeRequest&& request)
+              : ActiveModeInfo(std::move(request.mode),
+                               request.emitEvent ? Event::Changed : Event::None) {}
+
+        ftl::Optional<scheduler::FrameRateMode> modeOpt;
+        Event event = Event::None;
 
         bool operator!=(const ActiveModeInfo& other) const {
-            return mode != other.mode || event != other.event;
+            return modeOpt != other.modeOpt || event != other.event;
         }
     };
 
-    bool setDesiredActiveMode(const ActiveModeInfo&) EXCLUDES(mActiveModeLock);
+    enum class DesiredActiveModeAction {
+        None,
+        InitiateDisplayModeSwitch,
+        InitiateRenderRateSwitch
+    };
+    DesiredActiveModeAction setDesiredActiveMode(const ActiveModeInfo&) EXCLUDES(mActiveModeLock);
     std::optional<ActiveModeInfo> getDesiredActiveMode() const EXCLUDES(mActiveModeLock);
     void clearDesiredActiveModeState() EXCLUDES(mActiveModeLock);
     ActiveModeInfo getUpcomingActiveMode() const REQUIRES(kMainThreadContext) {
         return mUpcomingActiveMode;
     }
 
-    const DisplayMode& getActiveMode() const REQUIRES(kMainThreadContext) {
-        return mRefreshRateConfigs->getActiveMode();
+    scheduler::FrameRateMode getActiveMode() const REQUIRES(kMainThreadContext) {
+        return mRefreshRateSelector->getActiveMode();
     }
 
-    // Precondition: DisplaySnapshot must contain a mode with DisplayModeId.
-    void setActiveMode(DisplayModeId, const display::DisplaySnapshot&) REQUIRES(kMainThreadContext);
+    void setActiveMode(DisplayModeId, Fps displayFps, Fps renderFps);
 
     status_t initiateModeChange(const ActiveModeInfo&,
                                 const hal::VsyncPeriodChangeConstraints& constraints,
                                 hal::VsyncPeriodChangeTimeline* outTimeline)
             REQUIRES(kMainThreadContext);
 
-    // Returns the refresh rate configs for this display.
-    scheduler::RefreshRateConfigs& refreshRateConfigs() const { return *mRefreshRateConfigs; }
+    scheduler::RefreshRateSelector& refreshRateSelector() const { return *mRefreshRateSelector; }
 
-    // Returns a shared pointer to the refresh rate configs for this display.
-    // Clients can store this refresh rate configs and use it even if the DisplayDevice
-    // is destroyed.
-    std::shared_ptr<scheduler::RefreshRateConfigs> holdRefreshRateConfigs() const {
-        return mRefreshRateConfigs;
+    // Extends the lifetime of the RefreshRateSelector, so it can outlive this DisplayDevice.
+    std::shared_ptr<scheduler::RefreshRateSelector> holdRefreshRateSelector() const {
+        return mRefreshRateSelector;
     }
 
     // Enables an overlay to be displayed with the current refresh rate
-    void enableRefreshRateOverlay(bool enable, bool showSpinner) REQUIRES(kMainThreadContext);
+    void enableRefreshRateOverlay(bool enable, bool showSpinner, bool showRenderRate)
+            REQUIRES(kMainThreadContext);
     bool isRefreshRateOverlayEnabled() const { return mRefreshRateOverlay != nullptr; }
     bool onKernelTimerChanged(std::optional<DisplayModeId>, bool timerExpired);
     void animateRefreshRateOverlay();
@@ -239,10 +247,6 @@
     // release HWC resources (if any) for removable displays
     void disconnect();
 
-    /* ------------------------------------------------------------------------
-     * Debugging
-     */
-    std::string getDebugName() const;
     void dump(utils::Dumper&) const;
 
 private:
@@ -256,6 +260,7 @@
     std::string mDisplayName;
     std::string mActiveModeFPSTrace;
     std::string mActiveModeFPSHwcTrace;
+    std::string mRenderFrameRateFPSTrace;
 
     const ui::Rotation mPhysicalOrientation;
     ui::Rotation mOrientation = ui::ROTATION_0;
@@ -275,7 +280,7 @@
 
     std::vector<ui::Hdr> mOverrideHdrTypes;
 
-    std::shared_ptr<scheduler::RefreshRateConfigs> mRefreshRateConfigs;
+    std::shared_ptr<scheduler::RefreshRateSelector> mRefreshRateSelector;
     std::unique_ptr<RefreshRateOverlay> mRefreshRateOverlay;
 
     mutable std::mutex mActiveModeLock;
@@ -325,7 +330,7 @@
     HWComposer& hwComposer;
     const wp<IBinder> displayToken;
     const std::shared_ptr<compositionengine::Display> compositionDisplay;
-    std::shared_ptr<scheduler::RefreshRateConfigs> refreshRateConfigs;
+    std::shared_ptr<scheduler::RefreshRateSelector> refreshRateSelector;
 
     int32_t sequenceId{0};
     bool isSecure{false};
diff --git a/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp b/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp
index 3651231..800e36d 100644
--- a/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp
+++ b/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp
@@ -55,6 +55,7 @@
 using AidlDisplayAttribute = aidl::android::hardware::graphics::composer3::DisplayAttribute;
 using AidlDisplayCapability = aidl::android::hardware::graphics::composer3::DisplayCapability;
 using AidlHdrCapabilities = aidl::android::hardware::graphics::composer3::HdrCapabilities;
+using AidlOverlayProperties = aidl::android::hardware::graphics::composer3::OverlayProperties;
 using AidlPerFrameMetadata = aidl::android::hardware::graphics::composer3::PerFrameMetadata;
 using AidlPerFrameMetadataKey = aidl::android::hardware::graphics::composer3::PerFrameMetadataKey;
 using AidlPerFrameMetadataBlob = aidl::android::hardware::graphics::composer3::PerFrameMetadataBlob;
@@ -229,6 +230,8 @@
         return;
     }
 
+    addReader(translate<Display>(kSingleReaderKey));
+
     ALOGI("Loaded AIDL composer3 HAL service");
 }
 
@@ -297,12 +300,19 @@
     }
 }
 
-void AidlComposer::resetCommands() {
-    mWriter.reset();
+void AidlComposer::resetCommands(Display display) {
+    mMutex.lock_shared();
+    if (auto writer = getWriter(display)) {
+        writer->get().reset();
+    }
+    mMutex.unlock_shared();
 }
 
-Error AidlComposer::executeCommands() {
-    return execute();
+Error AidlComposer::executeCommands(Display display) {
+    mMutex.lock_shared();
+    auto error = execute(display);
+    mMutex.unlock_shared();
+    return error;
 }
 
 uint32_t AidlComposer::getMaxVirtualDisplayCount() {
@@ -333,6 +343,7 @@
 
     *outDisplay = translate<Display>(virtualDisplay.display);
     *format = static_cast<PixelFormat>(virtualDisplay.format);
+    addDisplay(translate<Display>(virtualDisplay.display));
     return Error::NONE;
 }
 
@@ -342,12 +353,20 @@
         ALOGE("destroyVirtualDisplay failed %s", status.getDescription().c_str());
         return static_cast<Error>(status.getServiceSpecificError());
     }
+    removeDisplay(display);
     return Error::NONE;
 }
 
 Error AidlComposer::acceptDisplayChanges(Display display) {
-    mWriter.acceptDisplayChanges(translate<int64_t>(display));
-    return Error::NONE;
+    Error error = Error::NONE;
+    mMutex.lock_shared();
+    if (auto writer = getWriter(display)) {
+        writer->get().acceptDisplayChanges(translate<int64_t>(display));
+    } else {
+        error = Error::BAD_DISPLAY;
+    }
+    mMutex.unlock_shared();
+    return error;
 }
 
 Error AidlComposer::createLayer(Display display, Layer* outLayer) {
@@ -387,7 +406,17 @@
 Error AidlComposer::getChangedCompositionTypes(
         Display display, std::vector<Layer>* outLayers,
         std::vector<aidl::android::hardware::graphics::composer3::Composition>* outTypes) {
-    const auto changedLayers = mReader.takeChangedCompositionTypes(translate<int64_t>(display));
+    std::vector<ChangedCompositionLayer> changedLayers;
+    Error error = Error::NONE;
+    {
+        mMutex.lock_shared();
+        if (auto reader = getReader(display)) {
+            changedLayers = reader->get().takeChangedCompositionTypes(translate<int64_t>(display));
+        } else {
+            error = Error::BAD_DISPLAY;
+        }
+        mMutex.unlock_shared();
+    }
     outLayers->reserve(changedLayers.size());
     outTypes->reserve(changedLayers.size());
 
@@ -395,7 +424,7 @@
         outLayers->emplace_back(translate<Layer>(layer.layer));
         outTypes->emplace_back(layer.composition);
     }
-    return Error::NONE;
+    return error;
 }
 
 Error AidlComposer::getColorModes(Display display, std::vector<ColorMode>* outModes) {
@@ -447,7 +476,17 @@
 Error AidlComposer::getDisplayRequests(Display display, uint32_t* outDisplayRequestMask,
                                        std::vector<Layer>* outLayers,
                                        std::vector<uint32_t>* outLayerRequestMasks) {
-    const auto displayRequests = mReader.takeDisplayRequests(translate<int64_t>(display));
+    Error error = Error::NONE;
+    DisplayRequest displayRequests;
+    {
+        mMutex.lock_shared();
+        if (auto reader = getReader(display)) {
+            displayRequests = reader->get().takeDisplayRequests(translate<int64_t>(display));
+        } else {
+            error = Error::BAD_DISPLAY;
+        }
+        mMutex.unlock_shared();
+    }
     *outDisplayRequestMask = translate<uint32_t>(displayRequests.mask);
     outLayers->reserve(displayRequests.layerRequests.size());
     outLayerRequestMasks->reserve(displayRequests.layerRequests.size());
@@ -456,7 +495,7 @@
         outLayers->emplace_back(translate<Layer>(layer.layer));
         outLayerRequestMasks->emplace_back(translate<uint32_t>(layer.mask));
     }
-    return Error::NONE;
+    return error;
 }
 
 Error AidlComposer::getDozeSupport(Display display, bool* outSupport) {
@@ -496,16 +535,35 @@
         return static_cast<Error>(status.getServiceSpecificError());
     }
 
-    *outTypes = translate<Hdr>(capabilities.types);
+    *outTypes = capabilities.types;
     *outMaxLuminance = capabilities.maxLuminance;
     *outMaxAverageLuminance = capabilities.maxAverageLuminance;
     *outMinLuminance = capabilities.minLuminance;
     return Error::NONE;
 }
 
+Error AidlComposer::getOverlaySupport(AidlOverlayProperties* outProperties) {
+    const auto status = mAidlComposerClient->getOverlaySupport(outProperties);
+    if (!status.isOk()) {
+        ALOGE("getOverlaySupport failed %s", status.getDescription().c_str());
+        return static_cast<Error>(status.getServiceSpecificError());
+    }
+    return Error::NONE;
+}
+
 Error AidlComposer::getReleaseFences(Display display, std::vector<Layer>* outLayers,
                                      std::vector<int>* outReleaseFences) {
-    auto fences = mReader.takeReleaseFences(translate<int64_t>(display));
+    Error error = Error::NONE;
+    std::vector<ReleaseFences::Layer> fences;
+    {
+        mMutex.lock_shared();
+        if (auto reader = getReader(display)) {
+            fences = reader->get().takeReleaseFences(translate<int64_t>(display));
+        } else {
+            error = Error::BAD_DISPLAY;
+        }
+        mMutex.unlock_shared();
+    }
     outLayers->reserve(fences.size());
     outReleaseFences->reserve(fences.size());
 
@@ -516,19 +574,29 @@
         *fence.fence.getR() = -1;
         outReleaseFences->emplace_back(fenceOwner);
     }
-    return Error::NONE;
+    return error;
 }
 
 Error AidlComposer::presentDisplay(Display display, int* outPresentFence) {
     ATRACE_NAME("HwcPresentDisplay");
-    mWriter.presentDisplay(translate<int64_t>(display));
+    Error error = Error::NONE;
+    mMutex.lock_shared();
+    auto writer = getWriter(display);
+    auto reader = getReader(display);
+    if (writer && reader) {
+        writer->get().presentDisplay(translate<int64_t>(display));
+        error = execute(display);
+    } else {
+        error = Error::BAD_DISPLAY;
+    }
 
-    Error error = execute();
     if (error != Error::NONE) {
+        mMutex.unlock_shared();
         return error;
     }
 
-    auto fence = mReader.takePresentFence(translate<int64_t>(display));
+    auto fence = reader->get().takePresentFence(translate<int64_t>(display));
+    mMutex.unlock_shared();
     // take ownership
     *outPresentFence = fence.get();
     *fence.getR() = -1;
@@ -553,11 +621,19 @@
         handle = target->getNativeBuffer()->handle;
     }
 
-    mWriter.setClientTarget(translate<int64_t>(display), slot, handle, acquireFence,
-                            translate<aidl::android::hardware::graphics::common::Dataspace>(
-                                    dataspace),
-                            translate<AidlRect>(damage));
-    return Error::NONE;
+    Error error = Error::NONE;
+    mMutex.lock_shared();
+    if (auto writer = getWriter(display)) {
+        writer->get()
+                .setClientTarget(translate<int64_t>(display), slot, handle, acquireFence,
+                                 translate<aidl::android::hardware::graphics::common::Dataspace>(
+                                         dataspace),
+                                 translate<AidlRect>(damage));
+    } else {
+        error = Error::BAD_DISPLAY;
+    }
+    mMutex.unlock_shared();
+    return error;
 }
 
 Error AidlComposer::setColorMode(Display display, ColorMode mode, RenderIntent renderIntent) {
@@ -573,14 +649,28 @@
 }
 
 Error AidlComposer::setColorTransform(Display display, const float* matrix) {
-    mWriter.setColorTransform(translate<int64_t>(display), matrix);
-    return Error::NONE;
+    auto error = Error::NONE;
+    mMutex.lock_shared();
+    if (auto writer = getWriter(display)) {
+        writer->get().setColorTransform(translate<int64_t>(display), matrix);
+    } else {
+        error = Error::BAD_DISPLAY;
+    }
+    mMutex.unlock_shared();
+    return error;
 }
 
 Error AidlComposer::setOutputBuffer(Display display, const native_handle_t* buffer,
                                     int releaseFence) {
-    mWriter.setOutputBuffer(translate<int64_t>(display), 0, buffer, dup(releaseFence));
-    return Error::NONE;
+    auto error = Error::NONE;
+    mMutex.lock_shared();
+    if (auto writer = getWriter(display)) {
+        writer->get().setOutputBuffer(translate<int64_t>(display), 0, buffer, dup(releaseFence));
+    } else {
+        error = Error::BAD_DISPLAY;
+    }
+    mMutex.unlock_shared();
+    return error;
 }
 
 Error AidlComposer::setPowerMode(Display display, IComposerClient::PowerMode mode) {
@@ -618,16 +708,26 @@
 Error AidlComposer::validateDisplay(Display display, nsecs_t expectedPresentTime,
                                     uint32_t* outNumTypes, uint32_t* outNumRequests) {
     ATRACE_NAME("HwcValidateDisplay");
-    mWriter.validateDisplay(translate<int64_t>(display),
-                            ClockMonotonicTimestamp{expectedPresentTime});
+    const auto displayId = translate<int64_t>(display);
+    Error error = Error::NONE;
+    mMutex.lock_shared();
+    auto writer = getWriter(display);
+    auto reader = getReader(display);
+    if (writer && reader) {
+        writer->get().validateDisplay(displayId, ClockMonotonicTimestamp{expectedPresentTime});
+        error = execute(display);
+    } else {
+        error = Error::BAD_DISPLAY;
+    }
 
-    Error error = execute();
     if (error != Error::NONE) {
+        mMutex.unlock_shared();
         return error;
     }
 
-    mReader.hasChanges(translate<int64_t>(display), outNumTypes, outNumRequests);
+    reader->get().hasChanges(displayId, outNumTypes, outNumRequests);
 
+    mMutex.unlock_shared();
     return Error::NONE;
 }
 
@@ -635,39 +735,59 @@
                                              uint32_t* outNumTypes, uint32_t* outNumRequests,
                                              int* outPresentFence, uint32_t* state) {
     ATRACE_NAME("HwcPresentOrValidateDisplay");
-    mWriter.presentOrvalidateDisplay(translate<int64_t>(display),
-                                     ClockMonotonicTimestamp{expectedPresentTime});
+    const auto displayId = translate<int64_t>(display);
+    Error error = Error::NONE;
+    mMutex.lock_shared();
+    auto writer = getWriter(display);
+    auto reader = getReader(display);
+    if (writer && reader) {
+        writer->get().presentOrvalidateDisplay(displayId,
+                                               ClockMonotonicTimestamp{expectedPresentTime});
+        error = execute(display);
+    } else {
+        error = Error::BAD_DISPLAY;
+    }
 
-    Error error = execute();
     if (error != Error::NONE) {
+        mMutex.unlock_shared();
         return error;
     }
 
-    const auto result = mReader.takePresentOrValidateStage(translate<int64_t>(display));
+    const auto result = reader->get().takePresentOrValidateStage(displayId);
     if (!result.has_value()) {
         *state = translate<uint32_t>(-1);
+        mMutex.unlock_shared();
         return Error::NO_RESOURCES;
     }
 
     *state = translate<uint32_t>(*result);
 
     if (*result == PresentOrValidate::Result::Presented) {
-        auto fence = mReader.takePresentFence(translate<int64_t>(display));
+        auto fence = reader->get().takePresentFence(displayId);
         // take ownership
         *outPresentFence = fence.get();
         *fence.getR() = -1;
     }
 
     if (*result == PresentOrValidate::Result::Validated) {
-        mReader.hasChanges(translate<int64_t>(display), outNumTypes, outNumRequests);
+        reader->get().hasChanges(displayId, outNumTypes, outNumRequests);
     }
 
+    mMutex.unlock_shared();
     return Error::NONE;
 }
 
 Error AidlComposer::setCursorPosition(Display display, Layer layer, int32_t x, int32_t y) {
-    mWriter.setLayerCursorPosition(translate<int64_t>(display), translate<int64_t>(layer), x, y);
-    return Error::NONE;
+    Error error = Error::NONE;
+    mMutex.lock_shared();
+    if (auto writer = getWriter(display)) {
+        writer->get().setLayerCursorPosition(translate<int64_t>(display), translate<int64_t>(layer),
+                                             x, y);
+    } else {
+        error = Error::BAD_DISPLAY;
+    }
+    mMutex.unlock_shared();
+    return error;
 }
 
 Error AidlComposer::setLayerBuffer(Display display, Layer layer, uint32_t slot,
@@ -677,90 +797,190 @@
         handle = buffer->getNativeBuffer()->handle;
     }
 
-    mWriter.setLayerBuffer(translate<int64_t>(display), translate<int64_t>(layer), slot, handle,
-                           acquireFence);
-    return Error::NONE;
+    Error error = Error::NONE;
+    mMutex.lock_shared();
+    if (auto writer = getWriter(display)) {
+        writer->get().setLayerBuffer(translate<int64_t>(display), translate<int64_t>(layer), slot,
+                                     handle, acquireFence);
+    } else {
+        error = Error::BAD_DISPLAY;
+    }
+    mMutex.unlock_shared();
+    return error;
 }
 
 Error AidlComposer::setLayerSurfaceDamage(Display display, Layer layer,
                                           const std::vector<IComposerClient::Rect>& damage) {
-    mWriter.setLayerSurfaceDamage(translate<int64_t>(display), translate<int64_t>(layer),
-                                  translate<AidlRect>(damage));
-    return Error::NONE;
+    Error error = Error::NONE;
+    mMutex.lock_shared();
+    if (auto writer = getWriter(display)) {
+        writer->get().setLayerSurfaceDamage(translate<int64_t>(display), translate<int64_t>(layer),
+                                            translate<AidlRect>(damage));
+    } else {
+        error = Error::BAD_DISPLAY;
+    }
+    mMutex.unlock_shared();
+    return error;
 }
 
 Error AidlComposer::setLayerBlendMode(Display display, Layer layer,
                                       IComposerClient::BlendMode mode) {
-    mWriter.setLayerBlendMode(translate<int64_t>(display), translate<int64_t>(layer),
-                              translate<BlendMode>(mode));
-    return Error::NONE;
+    Error error = Error::NONE;
+    mMutex.lock_shared();
+    if (auto writer = getWriter(display)) {
+        writer->get().setLayerBlendMode(translate<int64_t>(display), translate<int64_t>(layer),
+                                        translate<BlendMode>(mode));
+    } else {
+        error = Error::BAD_DISPLAY;
+    }
+    mMutex.unlock_shared();
+    return error;
 }
 
 Error AidlComposer::setLayerColor(Display display, Layer layer, const Color& color) {
-    mWriter.setLayerColor(translate<int64_t>(display), translate<int64_t>(layer), color);
-    return Error::NONE;
+    Error error = Error::NONE;
+    mMutex.lock_shared();
+    if (auto writer = getWriter(display)) {
+        writer->get().setLayerColor(translate<int64_t>(display), translate<int64_t>(layer), color);
+    } else {
+        error = Error::BAD_DISPLAY;
+    }
+    mMutex.unlock_shared();
+    return error;
 }
 
 Error AidlComposer::setLayerCompositionType(
         Display display, Layer layer,
         aidl::android::hardware::graphics::composer3::Composition type) {
-    mWriter.setLayerCompositionType(translate<int64_t>(display), translate<int64_t>(layer), type);
-    return Error::NONE;
+    Error error = Error::NONE;
+    mMutex.lock_shared();
+    if (auto writer = getWriter(display)) {
+        writer->get().setLayerCompositionType(translate<int64_t>(display),
+                                              translate<int64_t>(layer), type);
+    } else {
+        error = Error::BAD_DISPLAY;
+    }
+    mMutex.unlock_shared();
+    return error;
 }
 
 Error AidlComposer::setLayerDataspace(Display display, Layer layer, Dataspace dataspace) {
-    mWriter.setLayerDataspace(translate<int64_t>(display), translate<int64_t>(layer),
-                              translate<AidlDataspace>(dataspace));
-    return Error::NONE;
+    Error error = Error::NONE;
+    mMutex.lock_shared();
+    if (auto writer = getWriter(display)) {
+        writer->get().setLayerDataspace(translate<int64_t>(display), translate<int64_t>(layer),
+                                        translate<AidlDataspace>(dataspace));
+    } else {
+        error = Error::BAD_DISPLAY;
+    }
+    mMutex.unlock_shared();
+    return error;
 }
 
 Error AidlComposer::setLayerDisplayFrame(Display display, Layer layer,
                                          const IComposerClient::Rect& frame) {
-    mWriter.setLayerDisplayFrame(translate<int64_t>(display), translate<int64_t>(layer),
-                                 translate<AidlRect>(frame));
-    return Error::NONE;
+    Error error = Error::NONE;
+    mMutex.lock_shared();
+    if (auto writer = getWriter(display)) {
+        writer->get().setLayerDisplayFrame(translate<int64_t>(display), translate<int64_t>(layer),
+                                           translate<AidlRect>(frame));
+    } else {
+        error = Error::BAD_DISPLAY;
+    }
+    mMutex.unlock_shared();
+    return error;
 }
 
 Error AidlComposer::setLayerPlaneAlpha(Display display, Layer layer, float alpha) {
-    mWriter.setLayerPlaneAlpha(translate<int64_t>(display), translate<int64_t>(layer), alpha);
-    return Error::NONE;
+    Error error = Error::NONE;
+    mMutex.lock_shared();
+    if (auto writer = getWriter(display)) {
+        writer->get().setLayerPlaneAlpha(translate<int64_t>(display), translate<int64_t>(layer),
+                                         alpha);
+    } else {
+        error = Error::BAD_DISPLAY;
+    }
+    mMutex.unlock_shared();
+    return error;
 }
 
 Error AidlComposer::setLayerSidebandStream(Display display, Layer layer,
                                            const native_handle_t* stream) {
-    mWriter.setLayerSidebandStream(translate<int64_t>(display), translate<int64_t>(layer), stream);
-    return Error::NONE;
+    Error error = Error::NONE;
+    mMutex.lock_shared();
+    if (auto writer = getWriter(display)) {
+        writer->get().setLayerSidebandStream(translate<int64_t>(display), translate<int64_t>(layer),
+                                             stream);
+    } else {
+        error = Error::BAD_DISPLAY;
+    }
+    mMutex.unlock_shared();
+    return error;
 }
 
 Error AidlComposer::setLayerSourceCrop(Display display, Layer layer,
                                        const IComposerClient::FRect& crop) {
-    mWriter.setLayerSourceCrop(translate<int64_t>(display), translate<int64_t>(layer),
-                               translate<AidlFRect>(crop));
-    return Error::NONE;
+    Error error = Error::NONE;
+    mMutex.lock_shared();
+    if (auto writer = getWriter(display)) {
+        writer->get().setLayerSourceCrop(translate<int64_t>(display), translate<int64_t>(layer),
+                                         translate<AidlFRect>(crop));
+    } else {
+        error = Error::BAD_DISPLAY;
+    }
+    mMutex.unlock_shared();
+    return error;
 }
 
 Error AidlComposer::setLayerTransform(Display display, Layer layer, Transform transform) {
-    mWriter.setLayerTransform(translate<int64_t>(display), translate<int64_t>(layer),
-                              translate<AidlTransform>(transform));
-    return Error::NONE;
+    Error error = Error::NONE;
+    mMutex.lock_shared();
+    if (auto writer = getWriter(display)) {
+        writer->get().setLayerTransform(translate<int64_t>(display), translate<int64_t>(layer),
+                                        translate<AidlTransform>(transform));
+    } else {
+        error = Error::BAD_DISPLAY;
+    }
+    mMutex.unlock_shared();
+    return error;
 }
 
 Error AidlComposer::setLayerVisibleRegion(Display display, Layer layer,
                                           const std::vector<IComposerClient::Rect>& visible) {
-    mWriter.setLayerVisibleRegion(translate<int64_t>(display), translate<int64_t>(layer),
-                                  translate<AidlRect>(visible));
-    return Error::NONE;
+    Error error = Error::NONE;
+    mMutex.lock_shared();
+    if (auto writer = getWriter(display)) {
+        writer->get().setLayerVisibleRegion(translate<int64_t>(display), translate<int64_t>(layer),
+                                            translate<AidlRect>(visible));
+    } else {
+        error = Error::BAD_DISPLAY;
+    }
+    mMutex.unlock_shared();
+    return error;
 }
 
 Error AidlComposer::setLayerZOrder(Display display, Layer layer, uint32_t z) {
-    mWriter.setLayerZOrder(translate<int64_t>(display), translate<int64_t>(layer), z);
-    return Error::NONE;
+    Error error = Error::NONE;
+    mMutex.lock_shared();
+    if (auto writer = getWriter(display)) {
+        writer->get().setLayerZOrder(translate<int64_t>(display), translate<int64_t>(layer), z);
+    } else {
+        error = Error::BAD_DISPLAY;
+    }
+    mMutex.unlock_shared();
+    return error;
 }
 
-Error AidlComposer::execute() {
-    const auto& commands = mWriter.getPendingCommands();
+Error AidlComposer::execute(Display display) {
+    auto writer = getWriter(display);
+    auto reader = getReader(display);
+    if (!writer || !reader) {
+        return Error::BAD_DISPLAY;
+    }
+
+    const auto& commands = writer->get().getPendingCommands();
     if (commands.empty()) {
-        mWriter.reset();
+        writer->get().reset();
         return Error::NONE;
     }
 
@@ -772,9 +992,9 @@
             return static_cast<Error>(status.getServiceSpecificError());
         }
 
-        mReader.parse(std::move(results));
+        reader->get().parse(std::move(results));
     }
-    const auto commandErrors = mReader.takeErrors();
+    const auto commandErrors = reader->get().takeErrors();
     Error error = Error::NONE;
     for (const auto& cmdErr : commandErrors) {
         const auto index = static_cast<size_t>(cmdErr.commandIndex);
@@ -792,7 +1012,7 @@
         }
     }
 
-    mWriter.reset();
+    writer->get().reset();
 
     return error;
 }
@@ -800,9 +1020,17 @@
 Error AidlComposer::setLayerPerFrameMetadata(
         Display display, Layer layer,
         const std::vector<IComposerClient::PerFrameMetadata>& perFrameMetadatas) {
-    mWriter.setLayerPerFrameMetadata(translate<int64_t>(display), translate<int64_t>(layer),
-                                     translate<AidlPerFrameMetadata>(perFrameMetadatas));
-    return Error::NONE;
+    Error error = Error::NONE;
+    mMutex.lock_shared();
+    if (auto writer = getWriter(display)) {
+        writer->get().setLayerPerFrameMetadata(translate<int64_t>(display),
+                                               translate<int64_t>(layer),
+                                               translate<AidlPerFrameMetadata>(perFrameMetadatas));
+    } else {
+        error = Error::BAD_DISPLAY;
+    }
+    mMutex.unlock_shared();
+    return error;
 }
 
 std::vector<IComposerClient::PerFrameMetadataKey> AidlComposer::getPerFrameMetadataKeys(
@@ -862,8 +1090,16 @@
 }
 
 Error AidlComposer::setLayerColorTransform(Display display, Layer layer, const float* matrix) {
-    mWriter.setLayerColorTransform(translate<int64_t>(display), translate<int64_t>(layer), matrix);
-    return Error::NONE;
+    Error error = Error::NONE;
+    mMutex.lock_shared();
+    if (auto writer = getWriter(display)) {
+        writer->get().setLayerColorTransform(translate<int64_t>(display), translate<int64_t>(layer),
+                                             matrix);
+    } else {
+        error = Error::BAD_DISPLAY;
+    }
+    mMutex.unlock_shared();
+    return error;
 }
 
 Error AidlComposer::getDisplayedContentSamplingAttributes(Display display, PixelFormat* outFormat,
@@ -926,20 +1162,36 @@
 Error AidlComposer::setLayerPerFrameMetadataBlobs(
         Display display, Layer layer,
         const std::vector<IComposerClient::PerFrameMetadataBlob>& metadata) {
-    mWriter.setLayerPerFrameMetadataBlobs(translate<int64_t>(display), translate<int64_t>(layer),
-                                          translate<AidlPerFrameMetadataBlob>(metadata));
-    return Error::NONE;
+    Error error = Error::NONE;
+    mMutex.lock_shared();
+    if (auto writer = getWriter(display)) {
+        writer->get().setLayerPerFrameMetadataBlobs(translate<int64_t>(display),
+                                                    translate<int64_t>(layer),
+                                                    translate<AidlPerFrameMetadataBlob>(metadata));
+    } else {
+        error = Error::BAD_DISPLAY;
+    }
+    mMutex.unlock_shared();
+    return error;
 }
 
 Error AidlComposer::setDisplayBrightness(Display display, float brightness, float brightnessNits,
                                          const DisplayBrightnessOptions& options) {
-    mWriter.setDisplayBrightness(translate<int64_t>(display), brightness, brightnessNits);
+    Error error = Error::NONE;
+    mMutex.lock_shared();
+    if (auto writer = getWriter(display)) {
+        writer->get().setDisplayBrightness(translate<int64_t>(display), brightness, brightnessNits);
 
-    if (options.applyImmediately) {
-        return execute();
+        if (options.applyImmediately) {
+            error = execute(display);
+            mMutex.unlock_shared();
+            return error;
+        }
+    } else {
+        error = Error::BAD_DISPLAY;
     }
-
-    return Error::NONE;
+    mMutex.unlock_shared();
+    return error;
 }
 
 Error AidlComposer::getDisplayCapabilities(Display display,
@@ -1079,20 +1331,43 @@
 
 Error AidlComposer::getClientTargetProperty(
         Display display, ClientTargetPropertyWithBrightness* outClientTargetProperty) {
-    *outClientTargetProperty = mReader.takeClientTargetProperty(translate<int64_t>(display));
-    return Error::NONE;
+    Error error = Error::NONE;
+    mMutex.lock_shared();
+    if (auto reader = getReader(display)) {
+        *outClientTargetProperty =
+                reader->get().takeClientTargetProperty(translate<int64_t>(display));
+    } else {
+        error = Error::BAD_DISPLAY;
+    }
+    mMutex.unlock_shared();
+    return error;
 }
 
 Error AidlComposer::setLayerBrightness(Display display, Layer layer, float brightness) {
-    mWriter.setLayerBrightness(translate<int64_t>(display), translate<int64_t>(layer), brightness);
-    return Error::NONE;
+    Error error = Error::NONE;
+    mMutex.lock_shared();
+    if (auto writer = getWriter(display)) {
+        writer->get().setLayerBrightness(translate<int64_t>(display), translate<int64_t>(layer),
+                                         brightness);
+    } else {
+        error = Error::BAD_DISPLAY;
+    }
+    mMutex.unlock_shared();
+    return error;
 }
 
 Error AidlComposer::setLayerBlockingRegion(Display display, Layer layer,
                                            const std::vector<IComposerClient::Rect>& blocking) {
-    mWriter.setLayerBlockingRegion(translate<int64_t>(display), translate<int64_t>(layer),
-                                   translate<AidlRect>(blocking));
-    return Error::NONE;
+    Error error = Error::NONE;
+    mMutex.lock_shared();
+    if (auto writer = getWriter(display)) {
+        writer->get().setLayerBlockingRegion(translate<int64_t>(display), translate<int64_t>(layer),
+                                             translate<AidlRect>(blocking));
+    } else {
+        error = Error::BAD_DISPLAY;
+    }
+    mMutex.unlock_shared();
+    return error;
 }
 
 Error AidlComposer::getDisplayDecorationSupport(Display display,
@@ -1130,5 +1405,88 @@
     return Error::NONE;
 }
 
+ftl::Optional<std::reference_wrapper<ComposerClientWriter>> AidlComposer::getWriter(Display display)
+        REQUIRES_SHARED(mMutex) {
+    return mWriters.get(display);
+}
+
+ftl::Optional<std::reference_wrapper<ComposerClientReader>> AidlComposer::getReader(Display display)
+        REQUIRES_SHARED(mMutex) {
+    if (mSingleReader) {
+        display = translate<Display>(kSingleReaderKey);
+    }
+    return mReaders.get(display);
+}
+
+void AidlComposer::removeDisplay(Display display) {
+    mMutex.lock();
+    bool wasErased = mWriters.erase(display);
+    ALOGW_IF(!wasErased,
+             "Attempting to remove writer for display %" PRId64 " which is not connected",
+             translate<int64_t>(display));
+    if (!mSingleReader) {
+        removeReader(display);
+    }
+    mMutex.unlock();
+}
+
+void AidlComposer::onHotplugDisconnect(Display display) {
+    removeDisplay(display);
+}
+
+bool AidlComposer::hasMultiThreadedPresentSupport(Display display) {
+    const auto displayId = translate<int64_t>(display);
+    std::vector<AidlDisplayCapability> capabilities;
+    const auto status = mAidlComposerClient->getDisplayCapabilities(displayId, &capabilities);
+    if (!status.isOk()) {
+        ALOGE("getDisplayCapabilities failed %s", status.getDescription().c_str());
+        return false;
+    }
+    return std::find(capabilities.begin(), capabilities.end(),
+                     AidlDisplayCapability::MULTI_THREADED_PRESENT) != capabilities.end();
+}
+
+void AidlComposer::addReader(Display display) {
+    const auto displayId = translate<int64_t>(display);
+    std::optional<int64_t> displayOpt;
+    if (displayId != kSingleReaderKey) {
+        displayOpt.emplace(displayId);
+    }
+    auto [it, added] = mReaders.try_emplace(display, std::move(displayOpt));
+    ALOGW_IF(!added, "Attempting to add writer for display %" PRId64 " which is already connected",
+             displayId);
+}
+
+void AidlComposer::removeReader(Display display) {
+    bool wasErased = mReaders.erase(display);
+    ALOGW_IF(!wasErased,
+             "Attempting to remove reader for display %" PRId64 " which is not connected",
+             translate<int64_t>(display));
+}
+
+void AidlComposer::addDisplay(Display display) {
+    const auto displayId = translate<int64_t>(display);
+    mMutex.lock();
+    auto [it, added] = mWriters.try_emplace(display, displayId);
+    ALOGW_IF(!added, "Attempting to add writer for display %" PRId64 " which is already connected",
+             displayId);
+    if (mSingleReader) {
+        if (hasMultiThreadedPresentSupport(display)) {
+            mSingleReader = false;
+            removeReader(translate<Display>(kSingleReaderKey));
+            // Note that this includes the new display.
+            for (const auto& [existingDisplay, _] : mWriters) {
+                addReader(existingDisplay);
+            }
+        }
+    } else {
+        addReader(display);
+    }
+    mMutex.unlock();
+}
+
+void AidlComposer::onHotplugConnect(Display display) {
+    addDisplay(display);
+}
 } // namespace Hwc2
 } // namespace android
diff --git a/services/surfaceflinger/DisplayHardware/AidlComposerHal.h b/services/surfaceflinger/DisplayHardware/AidlComposerHal.h
index 18d2242..2a043fd 100644
--- a/services/surfaceflinger/DisplayHardware/AidlComposerHal.h
+++ b/services/surfaceflinger/DisplayHardware/AidlComposerHal.h
@@ -17,10 +17,12 @@
 #pragma once
 
 #include "ComposerHal.h"
+#include <ftl/shared_mutex.h>
+#include <ftl/small_map.h>
 
+#include <functional>
 #include <optional>
 #include <string>
-#include <unordered_map>
 #include <utility>
 #include <vector>
 
@@ -48,6 +50,7 @@
 using aidl::android::hardware::graphics::common::DisplayDecorationSupport;
 using aidl::android::hardware::graphics::composer3::ComposerClientReader;
 using aidl::android::hardware::graphics::composer3::ComposerClientWriter;
+using aidl::android::hardware::graphics::composer3::OverlayProperties;
 
 class AidlIComposerCallbackWrapper;
 
@@ -69,10 +72,10 @@
 
     // Reset all pending commands in the command buffer. Useful if you want to
     // skip a frame but have already queued some commands.
-    void resetCommands() override;
+    void resetCommands(Display) override;
 
     // Explicitly flush all pending commands in the command buffer.
-    Error executeCommands() override;
+    Error executeCommands(Display) override;
 
     uint32_t getMaxVirtualDisplayCount() override;
     Error createVirtualDisplay(uint32_t width, uint32_t height, PixelFormat* format,
@@ -101,8 +104,9 @@
 
     Error getDozeSupport(Display display, bool* outSupport) override;
     Error hasDisplayIdleTimerCapability(Display display, bool* outSupport) override;
-    Error getHdrCapabilities(Display display, std::vector<Hdr>* outTypes, float* outMaxLuminance,
+    Error getHdrCapabilities(Display display, std::vector<Hdr>* outHdrTypes, float* outMaxLuminance,
                              float* outMaxAverageLuminance, float* outMinLuminance) override;
+    Error getOverlaySupport(OverlayProperties* outProperties) override;
 
     Error getReleaseFences(Display display, std::vector<Layer>* outLayers,
                            std::vector<int>* outReleaseFences) override;
@@ -226,16 +230,29 @@
 
     Error getPhysicalDisplayOrientation(Display displayId,
                                         AidlTransform* outDisplayOrientation) override;
+    void onHotplugConnect(Display) override;
+    void onHotplugDisconnect(Display) override;
 
 private:
     // Many public functions above simply write a command into the command
     // queue to batch the calls.  validateDisplay and presentDisplay will call
     // this function to execute the command queue.
-    Error execute();
+    Error execute(Display) REQUIRES_SHARED(mMutex);
 
     // returns the default instance name for the given service
     static std::string instance(const std::string& serviceName);
 
+    ftl::Optional<std::reference_wrapper<ComposerClientWriter>> getWriter(Display)
+            REQUIRES_SHARED(mMutex);
+    ftl::Optional<std::reference_wrapper<ComposerClientReader>> getReader(Display)
+            REQUIRES_SHARED(mMutex);
+    void addDisplay(Display) EXCLUDES(mMutex);
+    void removeDisplay(Display) EXCLUDES(mMutex);
+    void addReader(Display) REQUIRES(mMutex);
+    void removeReader(Display) REQUIRES(mMutex);
+
+    bool hasMultiThreadedPresentSupport(Display);
+
     // 64KiB minus a small space for metadata such as read/write pointers
     static constexpr size_t kWriterInitialSize = 64 * 1024 / sizeof(uint32_t) - 16;
     // Max number of buffers that may be cached for a given layer
@@ -243,8 +260,25 @@
     // 1. Tightly coupling this cache to the max size of BufferQueue
     // 2. Adding an additional slot for the layer caching feature in SurfaceFlinger (see: Planner.h)
     static const constexpr uint32_t kMaxLayerBufferCount = BufferQueue::NUM_BUFFER_SLOTS + 1;
-    ComposerClientWriter mWriter;
-    ComposerClientReader mReader;
+
+    // Without DisplayCapability::MULTI_THREADED_PRESENT, we use a single reader
+    // for all displays. With the capability, we use a separate reader for each
+    // display.
+    bool mSingleReader = true;
+    // Invalid displayId used as a key to mReaders when mSingleReader is true.
+    static constexpr int64_t kSingleReaderKey = 0;
+
+    // TODO (b/256881188): Use display::PhysicalDisplayMap instead of hard-coded `3`
+    ftl::SmallMap<Display, ComposerClientWriter, 3> mWriters GUARDED_BY(mMutex);
+    ftl::SmallMap<Display, ComposerClientReader, 3> mReaders GUARDED_BY(mMutex);
+    // Protect access to mWriters and mReaders with a shared_mutex. Adding and
+    // removing a display require exclusive access, since the iterator or the
+    // writer/reader may be invalidated. Other calls need shared access while
+    // using the writer/reader, so they can use their display's writer/reader
+    // without it being deleted or the iterator being invalidated.
+    // TODO (b/257958323): Use std::shared_mutex and RAII once they support
+    // threading annotations.
+    ftl::SharedMutex mMutex;
 
     // Aidl interface
     using AidlIComposer = aidl::android::hardware::graphics::composer3::IComposer;
diff --git a/services/surfaceflinger/DisplayHardware/ComposerHal.h b/services/surfaceflinger/DisplayHardware/ComposerHal.h
index d266d94..ec23935 100644
--- a/services/surfaceflinger/DisplayHardware/ComposerHal.h
+++ b/services/surfaceflinger/DisplayHardware/ComposerHal.h
@@ -38,6 +38,7 @@
 #include <aidl/android/hardware/graphics/composer3/Composition.h>
 #include <aidl/android/hardware/graphics/composer3/DisplayCapability.h>
 #include <aidl/android/hardware/graphics/composer3/IComposerCallback.h>
+#include <aidl/android/hardware/graphics/composer3/OverlayProperties.h>
 
 #include <aidl/android/hardware/graphics/common/Transform.h>
 #include <optional>
@@ -65,7 +66,6 @@
 using types::V1_1::RenderIntent;
 using types::V1_2::ColorMode;
 using types::V1_2::Dataspace;
-using types::V1_2::Hdr;
 using types::V1_2::PixelFormat;
 
 using V2_1::Config;
@@ -83,6 +83,7 @@
 using PerFrameMetadataKey = IComposerClient::PerFrameMetadataKey;
 using PerFrameMetadataBlob = IComposerClient::PerFrameMetadataBlob;
 using AidlTransform = ::aidl::android::hardware::graphics::common::Transform;
+using aidl::android::hardware::graphics::common::Hdr;
 
 class Composer {
 public:
@@ -109,10 +110,10 @@
 
     // Reset all pending commands in the command buffer. Useful if you want to
     // skip a frame but have already queued some commands.
-    virtual void resetCommands() = 0;
+    virtual void resetCommands(Display) = 0;
 
     // Explicitly flush all pending commands in the command buffer.
-    virtual Error executeCommands() = 0;
+    virtual Error executeCommands(Display) = 0;
 
     virtual uint32_t getMaxVirtualDisplayCount() = 0;
     virtual Error createVirtualDisplay(uint32_t width, uint32_t height, PixelFormat*,
@@ -139,7 +140,7 @@
 
     virtual Error getDozeSupport(Display display, bool* outSupport) = 0;
     virtual Error hasDisplayIdleTimerCapability(Display display, bool* outSupport) = 0;
-    virtual Error getHdrCapabilities(Display display, std::vector<Hdr>* outTypes,
+    virtual Error getHdrCapabilities(Display display, std::vector<Hdr>* outHdrTypes,
                                      float* outMaxLuminance, float* outMaxAverageLuminance,
                                      float* outMinLuminance) = 0;
 
@@ -281,6 +282,9 @@
     virtual Error setIdleTimerEnabled(Display displayId, std::chrono::milliseconds timeout) = 0;
     virtual Error getPhysicalDisplayOrientation(Display displayId,
                                                 AidlTransform* outDisplayOrientation) = 0;
+    virtual Error getOverlaySupport(V3_0::OverlayProperties* outProperties) = 0;
+    virtual void onHotplugConnect(Display) = 0;
+    virtual void onHotplugDisconnect(Display) = 0;
 };
 
 } // namespace Hwc2
diff --git a/services/surfaceflinger/DisplayHardware/HWC2.cpp b/services/surfaceflinger/DisplayHardware/HWC2.cpp
index 16d3984..d0126d0 100644
--- a/services/surfaceflinger/DisplayHardware/HWC2.cpp
+++ b/services/surfaceflinger/DisplayHardware/HWC2.cpp
@@ -40,6 +40,7 @@
 using aidl::android::hardware::graphics::composer3::Composition;
 using AidlCapability = aidl::android::hardware::graphics::composer3::Capability;
 using aidl::android::hardware::graphics::composer3::DisplayCapability;
+using aidl::android::hardware::graphics::composer3::OverlayProperties;
 
 namespace android {
 
@@ -319,20 +320,25 @@
     float maxLuminance = -1.0f;
     float maxAverageLuminance = -1.0f;
     float minLuminance = -1.0f;
-    std::vector<Hwc2::Hdr> types;
-    auto intError = mComposer.getHdrCapabilities(mId, &types,
-            &maxLuminance, &maxAverageLuminance, &minLuminance);
+    std::vector<Hwc2::Hdr> hdrTypes;
+    auto intError = mComposer.getHdrCapabilities(mId, &hdrTypes, &maxLuminance,
+                                                 &maxAverageLuminance, &minLuminance);
     auto error = static_cast<HWC2::Error>(intError);
 
     if (error != Error::NONE) {
         return error;
     }
 
-    *outCapabilities = HdrCapabilities(std::move(types),
-            maxLuminance, maxAverageLuminance, minLuminance);
+    *outCapabilities =
+            HdrCapabilities(std::move(hdrTypes), maxLuminance, maxAverageLuminance, minLuminance);
     return Error::NONE;
 }
 
+Error Display::getOverlaySupport(OverlayProperties* outProperties) const {
+    auto intError = mComposer.getOverlaySupport(outProperties);
+    return static_cast<Error>(intError);
+}
+
 Error Display::getDisplayedContentSamplingAttributes(hal::PixelFormat* outFormat,
                                                      Dataspace* outDataspace,
                                                      uint8_t* outComponentMask) const {
diff --git a/services/surfaceflinger/DisplayHardware/HWC2.h b/services/surfaceflinger/DisplayHardware/HWC2.h
index 96399e2..4971d19 100644
--- a/services/surfaceflinger/DisplayHardware/HWC2.h
+++ b/services/surfaceflinger/DisplayHardware/HWC2.h
@@ -43,6 +43,7 @@
 #include <aidl/android/hardware/graphics/composer3/Color.h>
 #include <aidl/android/hardware/graphics/composer3/Composition.h>
 #include <aidl/android/hardware/graphics/composer3/DisplayCapability.h>
+#include <aidl/android/hardware/graphics/composer3/OverlayProperties.h>
 
 namespace android {
 
@@ -117,6 +118,9 @@
     [[nodiscard]] virtual hal::Error supportsDoze(bool* outSupport) const = 0;
     [[nodiscard]] virtual hal::Error getHdrCapabilities(
             android::HdrCapabilities* outCapabilities) const = 0;
+    [[nodiscard]] virtual hal::Error getOverlaySupport(
+            aidl::android::hardware::graphics::composer3::OverlayProperties* outProperties)
+            const = 0;
     [[nodiscard]] virtual hal::Error getDisplayedContentSamplingAttributes(
             hal::PixelFormat* outFormat, hal::Dataspace* outDataspace,
             uint8_t* outComponentMask) const = 0;
@@ -204,6 +208,8 @@
     hal::Error getConnectionType(ui::DisplayConnectionType*) const override;
     hal::Error supportsDoze(bool* outSupport) const override EXCLUDES(mDisplayCapabilitiesMutex);
     hal::Error getHdrCapabilities(android::HdrCapabilities* outCapabilities) const override;
+    hal::Error getOverlaySupport(aidl::android::hardware::graphics::composer3::OverlayProperties*
+                                         outProperties) const override;
     hal::Error getDisplayedContentSamplingAttributes(hal::PixelFormat* outFormat,
                                                      hal::Dataspace* outDataspace,
                                                      uint8_t* outComponentMask) const override;
diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.cpp b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
index 0a4ad97..10fde2a 100644
--- a/services/surfaceflinger/DisplayHardware/HWComposer.cpp
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
@@ -96,6 +96,7 @@
 void HWComposer::setCallback(HWC2::ComposerCallback& callback) {
     loadCapabilities();
     loadLayerMetadataSupport();
+    loadOverlayProperties();
 
     if (mRegisteredCallback) {
         ALOGW("Callback already registered. Ignored extra registration attempt.");
@@ -513,7 +514,7 @@
 
     if (displayData.validateWasSkipped) {
         // explicitly flush all pending commands
-        auto error = static_cast<hal::Error>(mComposer->executeCommands());
+        auto error = static_cast<hal::Error>(mComposer->executeCommands(hwcDisplay->getId()));
         RETURN_IF_HWC_ERROR_FOR("executeCommands", error, displayId, UNKNOWN_ERROR);
         RETURN_IF_HWC_ERROR_FOR("present", displayData.presentError, displayId, UNKNOWN_ERROR);
         return NO_ERROR;
@@ -652,6 +653,11 @@
     return NO_ERROR;
 }
 
+const aidl::android::hardware::graphics::composer3::OverlayProperties&
+HWComposer::getOverlaySupport() const {
+    return mOverlayProperties;
+}
+
 int32_t HWComposer::getSupportedPerFrameMetadata(HalDisplayId displayId) const {
     RETURN_IF_INVALID_DISPLAY(displayId, 0);
     return mDisplayData.at(displayId).hwcDisplay->getSupportedPerFrameMetadata();
@@ -928,6 +934,8 @@
                                                                : "Secondary display",
                                              .deviceProductInfo = std::nullopt};
         }();
+
+        mComposer->onHotplugConnect(hwcDisplayId);
     }
 
     if (!isConnected(info->id)) {
@@ -955,6 +963,7 @@
     // The display will later be destroyed by a call to HWComposer::disconnectDisplay. For now, mark
     // it as disconnected.
     mDisplayData.at(*displayId).hwcDisplay->setConnected(false);
+    mComposer->onHotplugDisconnect(hwcDisplayId);
     return DisplayIdentificationInfo{.id = *displayId};
 }
 
@@ -966,6 +975,10 @@
     }
 }
 
+void HWComposer::loadOverlayProperties() {
+    mComposer->getOverlaySupport(&mOverlayProperties);
+}
+
 status_t HWComposer::setIdleTimerEnabled(PhysicalDisplayId displayId,
                                          std::chrono::milliseconds timeout) {
     ATRACE_CALL();
diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.h b/services/surfaceflinger/DisplayHardware/HWComposer.h
index 6c43d8b..78d4a68 100644
--- a/services/surfaceflinger/DisplayHardware/HWComposer.h
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.h
@@ -48,6 +48,7 @@
 #include <aidl/android/hardware/graphics/composer3/ClientTargetPropertyWithBrightness.h>
 #include <aidl/android/hardware/graphics/composer3/Composition.h>
 #include <aidl/android/hardware/graphics/composer3/DisplayCapability.h>
+#include <aidl/android/hardware/graphics/composer3/OverlayProperties.h>
 
 namespace android {
 
@@ -178,6 +179,9 @@
     // Fetches the HDR capabilities of the given display
     virtual status_t getHdrCapabilities(HalDisplayId, HdrCapabilities* outCapabilities) = 0;
 
+    virtual const aidl::android::hardware::graphics::composer3::OverlayProperties&
+    getOverlaySupport() const = 0;
+
     virtual int32_t getSupportedPerFrameMetadata(HalDisplayId) const = 0;
 
     // Returns the available RenderIntent of the given display.
@@ -362,6 +366,9 @@
     // Fetches the HDR capabilities of the given display
     status_t getHdrCapabilities(HalDisplayId, HdrCapabilities* outCapabilities) override;
 
+    const aidl::android::hardware::graphics::composer3::OverlayProperties& getOverlaySupport()
+            const override;
+
     int32_t getSupportedPerFrameMetadata(HalDisplayId) const override;
 
     // Returns the available RenderIntent of the given display.
@@ -482,11 +489,13 @@
 
     void loadCapabilities();
     void loadLayerMetadataSupport();
+    void loadOverlayProperties();
 
     std::unordered_map<HalDisplayId, DisplayData> mDisplayData;
 
     std::unique_ptr<android::Hwc2::Composer> mComposer;
     std::unordered_set<aidl::android::hardware::graphics::composer3::Capability> mCapabilities;
+    aidl::android::hardware::graphics::composer3::OverlayProperties mOverlayProperties;
     std::unordered_map<std::string, bool> mSupportedLayerGenericMetadata;
     bool mRegisteredCallback = false;
 
diff --git a/services/surfaceflinger/DisplayHardware/Hal.h b/services/surfaceflinger/DisplayHardware/Hal.h
index 33a7bca..537d545 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/Hdr.h>
 #include <aidl/android/hardware/graphics/composer3/Composition.h>
 #include <aidl/android/hardware/graphics/composer3/DisplayCapability.h>
 
@@ -39,7 +40,6 @@
 using types::V1_1::RenderIntent;
 using types::V1_2::ColorMode;
 using types::V1_2::Dataspace;
-using types::V1_2::Hdr;
 using types::V1_2::PixelFormat;
 
 using V2_1::Error;
@@ -69,6 +69,7 @@
 using PowerMode = IComposerClient::PowerMode;
 using Vsync = IComposerClient::Vsync;
 using VsyncPeriodChangeConstraints = IComposerClient::VsyncPeriodChangeConstraints;
+using Hdr = aidl::android::hardware::graphics::common::Hdr;
 
 } // namespace hardware::graphics::composer::hal
 
@@ -178,7 +179,8 @@
 }
 
 // For utils::Dumper ADL.
-namespace hardware::graphics::composer::V2_2 {
+namespace hardware::graphics::composer {
+namespace V2_2 {
 
 inline std::string to_string(hardware::graphics::composer::hal::PowerMode mode) {
     switch (mode) {
@@ -197,7 +199,9 @@
     }
 }
 
-} // namespace hardware::graphics::composer::V2_2
+} // namespace V2_2
+
+namespace V2_1 {
 
 inline std::string to_string(hardware::graphics::composer::hal::Vsync vsync) {
     switch (vsync) {
@@ -210,4 +214,6 @@
     }
 }
 
+} // namespace V2_1
+} // namespace hardware::graphics::composer
 } // namespace android
diff --git a/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp b/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp
index 2597ae6..b607df0 100644
--- a/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp
+++ b/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp
@@ -40,6 +40,7 @@
 using aidl::android::hardware::graphics::composer3::ClientTargetPropertyWithBrightness;
 using aidl::android::hardware::graphics::composer3::DimmingStage;
 using aidl::android::hardware::graphics::composer3::DisplayCapability;
+using aidl::android::hardware::graphics::composer3::OverlayProperties;
 
 namespace android {
 
@@ -272,11 +273,11 @@
     }
 }
 
-void HidlComposer::resetCommands() {
+void HidlComposer::resetCommands(Display) {
     mWriter.reset();
 }
 
-Error HidlComposer::executeCommands() {
+Error HidlComposer::executeCommands(Display) {
     return execute();
 }
 
@@ -495,13 +496,13 @@
                      "OptionalFeature::KernelIdleTimer is not supported on HIDL");
 }
 
-Error HidlComposer::getHdrCapabilities(Display display, std::vector<Hdr>* outTypes,
+Error HidlComposer::getHdrCapabilities(Display display, std::vector<Hdr>* outHdrTypes,
                                        float* outMaxLuminance, float* outMaxAverageLuminance,
                                        float* outMinLuminance) {
     Error error = kDefaultError;
     if (mClient_2_3) {
         mClient_2_3->getHdrCapabilities_2_3(display,
-                                            [&](const auto& tmpError, const auto& tmpTypes,
+                                            [&](const auto& tmpError, const auto& tmpHdrTypes,
                                                 const auto& tmpMaxLuminance,
                                                 const auto& tmpMaxAverageLuminance,
                                                 const auto& tmpMinLuminance) {
@@ -509,15 +510,15 @@
                                                 if (error != Error::NONE) {
                                                     return;
                                                 }
+                                                *outHdrTypes = translate<ui::Hdr>(tmpHdrTypes);
 
-                                                *outTypes = tmpTypes;
                                                 *outMaxLuminance = tmpMaxLuminance;
                                                 *outMaxAverageLuminance = tmpMaxAverageLuminance;
                                                 *outMinLuminance = tmpMinLuminance;
                                             });
     } else {
         mClient->getHdrCapabilities(display,
-                                    [&](const auto& tmpError, const auto& tmpTypes,
+                                    [&](const auto& tmpError, const auto& tmpHdrTypes,
                                         const auto& tmpMaxLuminance,
                                         const auto& tmpMaxAverageLuminance,
                                         const auto& tmpMinLuminance) {
@@ -525,11 +526,7 @@
                                         if (error != Error::NONE) {
                                             return;
                                         }
-
-                                        outTypes->clear();
-                                        for (auto type : tmpTypes) {
-                                            outTypes->push_back(static_cast<Hdr>(type));
-                                        }
+                                        *outHdrTypes = translate<ui::Hdr>(tmpHdrTypes);
 
                                         *outMaxLuminance = tmpMaxLuminance;
                                         *outMaxAverageLuminance = tmpMaxAverageLuminance;
@@ -540,6 +537,10 @@
     return error;
 }
 
+Error HidlComposer::getOverlaySupport(OverlayProperties* /*outProperties*/) {
+    return Error::NONE;
+}
+
 Error HidlComposer::getReleaseFences(Display display, std::vector<Layer>* outLayers,
                                      std::vector<int>* outReleaseFences) {
     mReader.takeReleaseFences(display, outLayers, outReleaseFences);
@@ -1352,6 +1353,9 @@
     registerCallback(sp<ComposerCallbackBridge>::make(callback, vsyncSwitchingSupported));
 }
 
+void HidlComposer::onHotplugConnect(Display) {}
+void HidlComposer::onHotplugDisconnect(Display) {}
+
 CommandReader::~CommandReader() {
     resetData();
 }
diff --git a/services/surfaceflinger/DisplayHardware/HidlComposerHal.h b/services/surfaceflinger/DisplayHardware/HidlComposerHal.h
index d0d3c2e..3602bbb 100644
--- a/services/surfaceflinger/DisplayHardware/HidlComposerHal.h
+++ b/services/surfaceflinger/DisplayHardware/HidlComposerHal.h
@@ -56,7 +56,6 @@
 using types::V1_1::RenderIntent;
 using types::V1_2::ColorMode;
 using types::V1_2::Dataspace;
-using types::V1_2::Hdr;
 using types::V1_2::PixelFormat;
 
 using V2_1::Config;
@@ -177,10 +176,10 @@
 
     // Reset all pending commands in the command buffer. Useful if you want to
     // skip a frame but have already queued some commands.
-    void resetCommands() override;
+    void resetCommands(Display) override;
 
     // Explicitly flush all pending commands in the command buffer.
-    Error executeCommands() override;
+    Error executeCommands(Display) override;
 
     uint32_t getMaxVirtualDisplayCount() override;
     Error createVirtualDisplay(uint32_t width, uint32_t height, PixelFormat* format,
@@ -209,8 +208,10 @@
 
     Error getDozeSupport(Display display, bool* outSupport) override;
     Error hasDisplayIdleTimerCapability(Display display, bool* outSupport) override;
-    Error getHdrCapabilities(Display display, std::vector<Hdr>* outTypes, float* outMaxLuminance,
+    Error getHdrCapabilities(Display display, std::vector<Hdr>* outHdrTypes, float* outMaxLuminance,
                              float* outMaxAverageLuminance, float* outMinLuminance) override;
+    Error getOverlaySupport(aidl::android::hardware::graphics::composer3::OverlayProperties*
+                                    outProperties) override;
 
     Error getReleaseFences(Display display, std::vector<Layer>* outLayers,
                            std::vector<int>* outReleaseFences) override;
@@ -337,6 +338,8 @@
 
     Error getPhysicalDisplayOrientation(Display displayId,
                                         AidlTransform* outDisplayOrientation) override;
+    void onHotplugConnect(Display) override;
+    void onHotplugDisconnect(Display) override;
 
 private:
     class CommandWriter : public CommandWriterBase {
diff --git a/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp b/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp
index cb2c8c5..f05223c 100644
--- a/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp
+++ b/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp
@@ -57,6 +57,7 @@
 using android::hardware::power::IPower;
 using android::hardware::power::IPowerHintSession;
 using android::hardware::power::Mode;
+using android::hardware::power::SessionHint;
 using android::hardware::power::WorkDuration;
 
 PowerAdvisor::~PowerAdvisor() = default;
@@ -140,7 +141,7 @@
     }
 }
 
-void PowerAdvisor::notifyDisplayUpdateImminent() {
+void PowerAdvisor::notifyDisplayUpdateImminentAndCpuReset() {
     // Only start sending this notification once the system has booted so we don't introduce an
     // early-boot dependency on Power HAL
     if (!mBootFinished.load()) {
@@ -154,7 +155,7 @@
             return;
         }
 
-        if (!halWrapper->notifyDisplayUpdateImminent()) {
+        if (!halWrapper->notifyDisplayUpdateImminentAndCpuReset()) {
             // The HAL has become unavailable; attempt to reconnect later
             mReconnectPowerHal = true;
             return;
@@ -599,7 +600,7 @@
         return ret.isOk();
     }
 
-    bool notifyDisplayUpdateImminent() override {
+    bool notifyDisplayUpdateImminentAndCpuReset() override {
         // Power HAL 1.x doesn't have a notification for this
         ALOGV("HIDL notifyUpdateImminent received but can't send");
         return true;
@@ -675,8 +676,12 @@
     return ret.isOk();
 }
 
-bool AidlPowerHalWrapper::notifyDisplayUpdateImminent() {
-    ALOGV("AIDL notifyDisplayUpdateImminent");
+bool AidlPowerHalWrapper::notifyDisplayUpdateImminentAndCpuReset() {
+    ALOGV("AIDL notifyDisplayUpdateImminentAndCpuReset");
+    if (isPowerHintSessionRunning()) {
+        mPowerHintSession->sendHint(SessionHint::CPU_LOAD_RESET);
+    }
+
     if (!mHasDisplayUpdateImminent) {
         ALOGV("Skipped sending DISPLAY_UPDATE_IMMINENT because HAL doesn't support it");
         return true;
diff --git a/services/surfaceflinger/DisplayHardware/PowerAdvisor.h b/services/surfaceflinger/DisplayHardware/PowerAdvisor.h
index 1c9d123..d45e7cb 100644
--- a/services/surfaceflinger/DisplayHardware/PowerAdvisor.h
+++ b/services/surfaceflinger/DisplayHardware/PowerAdvisor.h
@@ -48,7 +48,7 @@
     virtual void onBootFinished() = 0;
     virtual void setExpensiveRenderingExpected(DisplayId displayId, bool expected) = 0;
     virtual bool isUsingExpensiveRendering() = 0;
-    virtual void notifyDisplayUpdateImminent() = 0;
+    virtual void notifyDisplayUpdateImminentAndCpuReset() = 0;
     // Checks both if it supports and if it's enabled
     virtual bool usePowerHintSession() = 0;
     virtual bool supportsPowerHintSession() = 0;
@@ -106,7 +106,7 @@
         virtual ~HalWrapper() = default;
 
         virtual bool setExpensiveRendering(bool enabled) = 0;
-        virtual bool notifyDisplayUpdateImminent() = 0;
+        virtual bool notifyDisplayUpdateImminentAndCpuReset() = 0;
         virtual bool supportsPowerHintSession() = 0;
         virtual bool isPowerHintSessionRunning() = 0;
         virtual void restartPowerHintSession() = 0;
@@ -126,7 +126,7 @@
     void onBootFinished() override;
     void setExpensiveRenderingExpected(DisplayId displayId, bool expected) override;
     bool isUsingExpensiveRendering() override { return mNotifiedExpensiveRendering; };
-    void notifyDisplayUpdateImminent() override;
+    void notifyDisplayUpdateImminentAndCpuReset() override;
     bool usePowerHintSession() override;
     bool supportsPowerHintSession() override;
     bool isPowerHintSessionRunning() override;
@@ -289,7 +289,7 @@
     static std::unique_ptr<HalWrapper> connect();
 
     bool setExpensiveRendering(bool enabled) override;
-    bool notifyDisplayUpdateImminent() override;
+    bool notifyDisplayUpdateImminentAndCpuReset() override;
     bool supportsPowerHintSession() override;
     bool isPowerHintSessionRunning() override;
     void restartPowerHintSession() override;
diff --git a/services/surfaceflinger/FrontEnd/DisplayInfo.h b/services/surfaceflinger/FrontEnd/DisplayInfo.h
new file mode 100644
index 0000000..0c7b24a
--- /dev/null
+++ b/services/surfaceflinger/FrontEnd/DisplayInfo.h
@@ -0,0 +1,34 @@
+/*
+ * 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 <gui/DisplayInfo.h>
+
+namespace android::surfaceflinger::frontend {
+
+// Display information needed to populate input and calculate layer geometry.
+struct DisplayInfo {
+    gui::DisplayInfo info;
+    ui::Transform transform;
+    bool receivesInput;
+    bool isSecure;
+    // TODO(b/238781169) can eliminate once sPrimaryDisplayRotationFlags is removed.
+    bool isPrimary;
+    ui::Transform::RotationFlags rotationFlags;
+};
+
+} // namespace android::surfaceflinger::frontend
diff --git a/services/surfaceflinger/FrontEnd/LayerCreationArgs.cpp b/services/surfaceflinger/FrontEnd/LayerCreationArgs.cpp
new file mode 100644
index 0000000..6d492c0
--- /dev/null
+++ b/services/surfaceflinger/FrontEnd/LayerCreationArgs.cpp
@@ -0,0 +1,62 @@
+/*
+ * 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 "LayerCreationArgs.h"
+#include <binder/IPCThreadState.h>
+#include <private/android_filesystem_config.h>
+#include "Client.h"
+#include "gui/LayerMetadata.h"
+
+namespace android::surfaceflinger {
+
+std::atomic<uint32_t> LayerCreationArgs::sSequence{1};
+
+LayerCreationArgs::LayerCreationArgs(SurfaceFlinger* flinger, sp<Client> client, std::string name,
+                                     uint32_t flags, gui::LayerMetadata metadataArg,
+                                     std::optional<uint32_t> id)
+      : flinger(flinger),
+        client(std::move(client)),
+        name(std::move(name)),
+        flags(flags),
+        metadata(std::move(metadataArg)) {
+    IPCThreadState* ipc = IPCThreadState::self();
+    ownerPid = ipc->getCallingPid();
+    uid_t callingUid = ipc->getCallingUid();
+    metadata.setInt32(gui::METADATA_CALLING_UID, static_cast<int32_t>(callingUid));
+    ownerUid = callingUid;
+    if (ownerUid == AID_GRAPHICS || ownerUid == AID_SYSTEM) {
+        // System can override the calling UID/PID since it can create layers on behalf of apps.
+        ownerPid = metadata.getInt32(gui::METADATA_OWNER_PID, ownerPid);
+        ownerUid = static_cast<uid_t>(
+                metadata.getInt32(gui::METADATA_OWNER_UID, static_cast<int32_t>(ownerUid)));
+    }
+
+    if (id) {
+        sequence = *id;
+        sSequence = *id + 1;
+    } else {
+        sequence = sSequence++;
+        if (sequence == UNASSIGNED_LAYER_ID) {
+            ALOGW("Layer sequence id rolled over.");
+            sequence = sSequence++;
+        }
+    }
+}
+
+LayerCreationArgs::LayerCreationArgs(const LayerCreationArgs& args)
+      : LayerCreationArgs(args.flinger, args.client, args.name, args.flags, args.metadata) {}
+
+} // namespace android::surfaceflinger
diff --git a/services/surfaceflinger/FrontEnd/LayerCreationArgs.h b/services/surfaceflinger/FrontEnd/LayerCreationArgs.h
new file mode 100644
index 0000000..7b5a157
--- /dev/null
+++ b/services/surfaceflinger/FrontEnd/LayerCreationArgs.h
@@ -0,0 +1,56 @@
+/*
+ * 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 <binder/Binder.h>
+#include <gui/LayerMetadata.h>
+#include <utils/StrongPointer.h>
+#include <cstdint>
+#include <limits>
+#include <optional>
+constexpr uint32_t UNASSIGNED_LAYER_ID = std::numeric_limits<uint32_t>::max();
+
+namespace android {
+class SurfaceFlinger;
+class Client;
+} // namespace android
+
+namespace android::surfaceflinger {
+
+struct LayerCreationArgs {
+    static std::atomic<uint32_t> sSequence;
+
+    LayerCreationArgs(android::SurfaceFlinger*, sp<android::Client>, std::string name,
+                      uint32_t flags, gui::LayerMetadata,
+                      std::optional<uint32_t> id = std::nullopt);
+    LayerCreationArgs(const LayerCreationArgs&);
+
+    android::SurfaceFlinger* flinger;
+    sp<android::Client> client;
+    std::string name;
+    uint32_t flags; // ISurfaceComposerClient flags
+    gui::LayerMetadata metadata;
+    pid_t ownerPid;
+    uid_t ownerUid;
+    uint32_t textureName;
+    uint32_t sequence;
+    bool addToRoot = true;
+    wp<IBinder> parentHandle = nullptr;
+    wp<IBinder> mirrorLayerHandle = nullptr;
+};
+
+} // namespace android::surfaceflinger
diff --git a/services/surfaceflinger/FrontEnd/LayerHandle.cpp b/services/surfaceflinger/FrontEnd/LayerHandle.cpp
new file mode 100644
index 0000000..75e4e3a
--- /dev/null
+++ b/services/surfaceflinger/FrontEnd/LayerHandle.cpp
@@ -0,0 +1,62 @@
+/*
+ * 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 "LayerHandle.h"
+#include <cstdint>
+#include "Layer.h"
+#include "LayerCreationArgs.h"
+#include "SurfaceFlinger.h"
+
+namespace android::surfaceflinger {
+
+LayerHandle::LayerHandle(const sp<android::SurfaceFlinger>& flinger,
+                         const sp<android::Layer>& layer)
+      : mFlinger(flinger), mLayer(layer), mLayerId(static_cast<uint32_t>(layer->getSequence())) {}
+
+LayerHandle::~LayerHandle() {
+    if (mFlinger) {
+        mFlinger->onHandleDestroyed(this, mLayer, mLayerId);
+    }
+}
+
+const String16 LayerHandle::kDescriptor = String16("android.Layer.LayerHandle");
+
+sp<LayerHandle> LayerHandle::fromIBinder(const sp<IBinder>& binder) {
+    if (binder == nullptr) {
+        return nullptr;
+    }
+
+    BBinder* b = binder->localBinder();
+    if (b == nullptr || b->getInterfaceDescriptor() != LayerHandle::kDescriptor) {
+        ALOGD("handle does not have a valid descriptor");
+        return nullptr;
+    }
+
+    // We can safely cast this binder since its local and we verified its interface descriptor.
+    return sp<LayerHandle>::cast(binder);
+}
+
+sp<android::Layer> LayerHandle::getLayer(const sp<IBinder>& binder) {
+    sp<LayerHandle> handle = LayerHandle::fromIBinder(binder);
+    return handle ? handle->mLayer : nullptr;
+}
+
+uint32_t LayerHandle::getLayerId(const sp<IBinder>& binder) {
+    sp<LayerHandle> handle = LayerHandle::fromIBinder(binder);
+    return handle ? handle->mLayerId : UNASSIGNED_LAYER_ID;
+}
+
+} // namespace android::surfaceflinger
diff --git a/services/surfaceflinger/FrontEnd/LayerHandle.h b/services/surfaceflinger/FrontEnd/LayerHandle.h
new file mode 100644
index 0000000..5d0f783
--- /dev/null
+++ b/services/surfaceflinger/FrontEnd/LayerHandle.h
@@ -0,0 +1,58 @@
+/*
+ * 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 <binder/Binder.h>
+#include <utils/StrongPointer.h>
+
+namespace android {
+class SurfaceFlinger;
+class Layer;
+} // namespace android
+
+namespace android::surfaceflinger {
+
+/*
+ * The layer handle is just a BBinder object passed to the client
+ * (remote process) -- we don't keep any reference on our side such that
+ * the dtor is called when the remote side let go of its reference.
+ *
+ * ~LayerHandle ensures that mFlinger->onLayerDestroyed() is called for
+ * this layer when the handle is destroyed.
+ */
+class LayerHandle : public BBinder {
+public:
+    LayerHandle(const sp<android::SurfaceFlinger>& flinger, const sp<android::Layer>& layer);
+    // for testing
+    LayerHandle(uint32_t layerId) : mFlinger(nullptr), mLayer(nullptr), mLayerId(layerId) {}
+    ~LayerHandle();
+
+    // Static functions to access the layer and layer id safely from an incoming binder.
+    static sp<LayerHandle> fromIBinder(const sp<IBinder>& handle);
+    static sp<android::Layer> getLayer(const sp<IBinder>& handle);
+    static uint32_t getLayerId(const sp<IBinder>& handle);
+    static const String16 kDescriptor;
+
+    const String16& getInterfaceDescriptor() const override { return kDescriptor; }
+
+private:
+    sp<android::SurfaceFlinger> mFlinger;
+    sp<android::Layer> mLayer;
+    const uint32_t mLayerId;
+};
+
+} // namespace android::surfaceflinger
diff --git a/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp b/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp
new file mode 100644
index 0000000..7afa144
--- /dev/null
+++ b/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp
@@ -0,0 +1,321 @@
+/*
+ * 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.
+ */
+
+#define ATRACE_TAG ATRACE_TAG_GRAPHICS
+
+#undef LOG_TAG
+#define LOG_TAG "LayerLifecycleManager"
+
+#include "LayerLifecycleManager.h"
+#include "Layer.h" // temporarily needed for LayerHandle
+#include "LayerHandle.h"
+#include "SwapErase.h"
+
+namespace android::surfaceflinger::frontend {
+
+using namespace ftl::flag_operators;
+
+void LayerLifecycleManager::addLayers(std::vector<std::unique_ptr<RequestedLayerState>> newLayers) {
+    if (newLayers.empty()) {
+        return;
+    }
+
+    mGlobalChanges |= RequestedLayerState::Changes::Hierarchy;
+    for (auto& newLayer : newLayers) {
+        RequestedLayerState& layer = *newLayer.get();
+        auto [it, inserted] = mIdToLayer.try_emplace(layer.id, References{.owner = layer});
+        if (!inserted) {
+            LOG_ALWAYS_FATAL("Duplicate layer id %d found. Existing layer: %s", layer.id,
+                             it->second.owner.getDebugString().c_str());
+        }
+
+        linkLayer(layer.parentId, layer.id);
+        linkLayer(layer.relativeParentId, layer.id);
+        linkLayer(layer.mirrorId, layer.id);
+        linkLayer(layer.touchCropId, layer.id);
+
+        mLayers.emplace_back(std::move(newLayer));
+    }
+}
+
+void LayerLifecycleManager::onHandlesDestroyed(const std::vector<uint32_t>& destroyedHandles) {
+    std::vector<uint32_t> layersToBeDestroyed;
+    for (const auto& layerId : destroyedHandles) {
+        auto it = mIdToLayer.find(layerId);
+        if (it == mIdToLayer.end()) {
+            LOG_ALWAYS_FATAL("%s Layerid not found %d", __func__, layerId);
+            continue;
+        }
+        RequestedLayerState& layer = it->second.owner;
+        layer.handleAlive = false;
+        if (!layer.canBeDestroyed()) {
+            continue;
+        }
+        layer.changes |= RequestedLayerState::Changes::Destroyed;
+        layersToBeDestroyed.emplace_back(layerId);
+    }
+
+    if (layersToBeDestroyed.empty()) {
+        return;
+    }
+
+    mGlobalChanges |= RequestedLayerState::Changes::Hierarchy;
+    for (size_t i = 0; i < layersToBeDestroyed.size(); i++) {
+        uint32_t layerId = layersToBeDestroyed[i];
+        auto it = mIdToLayer.find(layerId);
+        if (it == mIdToLayer.end()) {
+            LOG_ALWAYS_FATAL("%s Layer with id %d not found", __func__, layerId);
+            continue;
+        }
+
+        RequestedLayerState& layer = it->second.owner;
+
+        unlinkLayer(layer.parentId, layer.id);
+        unlinkLayer(layer.relativeParentId, layer.id);
+        unlinkLayer(layer.mirrorId, layer.id);
+        unlinkLayer(layer.touchCropId, layer.id);
+
+        auto& references = it->second.references;
+        for (uint32_t linkedLayerId : references) {
+            RequestedLayerState* linkedLayer = getLayerFromId(linkedLayerId);
+            if (!linkedLayer) {
+                LOG_ALWAYS_FATAL("%s Layerid reference %d not found for %d", __func__,
+                                 linkedLayerId, layer.id);
+                continue;
+            };
+            if (linkedLayer->parentId == layer.id) {
+                linkedLayer->parentId = UNASSIGNED_LAYER_ID;
+                if (linkedLayer->canBeDestroyed()) {
+                    linkedLayer->changes |= RequestedLayerState::Changes::Destroyed;
+                    layersToBeDestroyed.emplace_back(linkedLayer->id);
+                }
+            }
+            if (linkedLayer->relativeParentId == layer.id) {
+                linkedLayer->relativeParentId = UNASSIGNED_LAYER_ID;
+            }
+            if (linkedLayer->mirrorId == layer.id) {
+                linkedLayer->mirrorId = UNASSIGNED_LAYER_ID;
+            }
+            if (linkedLayer->touchCropId == layer.id) {
+                linkedLayer->touchCropId = UNASSIGNED_LAYER_ID;
+            }
+        }
+        mIdToLayer.erase(it);
+    }
+
+    auto it = mLayers.begin();
+    while (it != mLayers.end()) {
+        RequestedLayerState* layer = it->get();
+        if (layer->changes.test(RequestedLayerState::Changes::Destroyed)) {
+            ALOGV("%s destroyed layer %s", __func__, layer->getDebugStringShort().c_str());
+            std::iter_swap(it, mLayers.end() - 1);
+            mDestroyedLayers.emplace_back(std::move(mLayers.back()));
+            if (it == mLayers.end() - 1) {
+                it = mLayers.erase(mLayers.end() - 1);
+            } else {
+                mLayers.erase(mLayers.end() - 1);
+            }
+        } else {
+            it++;
+        }
+    }
+}
+
+void LayerLifecycleManager::applyTransactions(const std::vector<TransactionState>& transactions) {
+    for (const auto& transaction : transactions) {
+        for (const auto& resolvedComposerState : transaction.states) {
+            const auto& clientState = resolvedComposerState.state;
+            uint32_t layerId = LayerHandle::getLayerId(clientState.surface);
+            if (layerId == UNASSIGNED_LAYER_ID) {
+                ALOGW("%s Handle %p is not valid", __func__, clientState.surface.get());
+                continue;
+            }
+
+            RequestedLayerState* layer = getLayerFromId(layerId);
+            if (layer == nullptr) {
+                LOG_ALWAYS_FATAL("%s Layer with handle %p (layerid=%d) not found", __func__,
+                                 clientState.surface.get(), layerId);
+                continue;
+            }
+
+            if (!layer->handleAlive) {
+                LOG_ALWAYS_FATAL("%s Layer's handle %p (layerid=%d) is not alive. Possible out of "
+                                 "order LayerLifecycleManager updates",
+                                 __func__, clientState.surface.get(), layerId);
+                continue;
+            }
+
+            uint32_t oldParentId = layer->parentId;
+            uint32_t oldRelativeParentId = layer->relativeParentId;
+            uint32_t oldTouchCropId = layer->touchCropId;
+            layer->merge(resolvedComposerState);
+
+            if (layer->what & layer_state_t::eBackgroundColorChanged) {
+                if (layer->bgColorLayerId == UNASSIGNED_LAYER_ID && layer->bgColorAlpha != 0) {
+                    LayerCreationArgs backgroundLayerArgs{nullptr,
+                                                          nullptr,
+                                                          layer->name + "BackgroundColorLayer",
+                                                          ISurfaceComposerClient::eFXSurfaceEffect,
+                                                          {}};
+                    std::vector<std::unique_ptr<RequestedLayerState>> newLayers;
+                    newLayers.emplace_back(
+                            std::make_unique<RequestedLayerState>(backgroundLayerArgs));
+                    RequestedLayerState* backgroundLayer = newLayers.back().get();
+                    backgroundLayer->handleAlive = false;
+                    backgroundLayer->parentId = layer->id;
+                    backgroundLayer->z = std::numeric_limits<int32_t>::min();
+                    backgroundLayer->color.rgb = layer->color.rgb;
+                    backgroundLayer->color.a = layer->bgColorAlpha;
+                    backgroundLayer->dataspace = layer->bgColorDataspace;
+
+                    layer->bgColorLayerId = backgroundLayer->id;
+                    addLayers({std::move(newLayers)});
+                } else if (layer->bgColorLayerId != UNASSIGNED_LAYER_ID &&
+                           layer->bgColorAlpha == 0) {
+                    RequestedLayerState* bgColorLayer = getLayerFromId(layer->bgColorLayerId);
+                    bgColorLayer->parentId = UNASSIGNED_LAYER_ID;
+                    onHandlesDestroyed({layer->bgColorLayerId});
+                } else if (layer->bgColorLayerId != UNASSIGNED_LAYER_ID) {
+                    RequestedLayerState* bgColorLayer = getLayerFromId(layer->bgColorLayerId);
+                    bgColorLayer->color.rgb = layer->color.rgb;
+                    bgColorLayer->color.a = layer->bgColorAlpha;
+                    bgColorLayer->dataspace = layer->bgColorDataspace;
+                    mGlobalChanges |= RequestedLayerState::Changes::Content;
+                }
+            }
+
+            if (oldParentId != layer->parentId) {
+                unlinkLayer(oldParentId, layer->id);
+                linkLayer(layer->parentId, layer->id);
+            }
+            if (oldRelativeParentId != layer->relativeParentId) {
+                unlinkLayer(oldRelativeParentId, layer->id);
+                linkLayer(layer->relativeParentId, layer->id);
+            }
+            if (oldTouchCropId != layer->touchCropId) {
+                unlinkLayer(oldTouchCropId, layer->id);
+                linkLayer(layer->touchCropId, layer->id);
+            }
+
+            mGlobalChanges |= layer->changes &
+                    (RequestedLayerState::Changes::Hierarchy |
+                     RequestedLayerState::Changes::Geometry |
+                     RequestedLayerState::Changes::Content);
+        }
+    }
+}
+
+void LayerLifecycleManager::commitChanges() {
+    for (auto& layer : mLayers) {
+        if (layer->changes.test(RequestedLayerState::Changes::Created)) {
+            for (auto listener : mListeners) {
+                listener->onLayerAdded(*layer);
+            }
+        }
+        layer->what = 0;
+        layer->changes.clear();
+    }
+
+    for (auto& destroyedLayer : mDestroyedLayers) {
+        if (destroyedLayer->changes.test(RequestedLayerState::Changes::Created)) {
+            for (auto listener : mListeners) {
+                listener->onLayerAdded(*destroyedLayer);
+            }
+        }
+
+        for (auto listener : mListeners) {
+            listener->onLayerDestroyed(*destroyedLayer);
+        }
+    }
+    mDestroyedLayers.clear();
+    mGlobalChanges.clear();
+}
+
+void LayerLifecycleManager::addLifecycleListener(std::shared_ptr<ILifecycleListener> listener) {
+    mListeners.emplace_back(std::move(listener));
+}
+
+void LayerLifecycleManager::removeLifecycleListener(std::shared_ptr<ILifecycleListener> listener) {
+    swapErase(mListeners, listener);
+}
+
+const std::vector<std::unique_ptr<RequestedLayerState>>& LayerLifecycleManager::getLayers() const {
+    return mLayers;
+}
+
+const std::vector<std::unique_ptr<RequestedLayerState>>& LayerLifecycleManager::getDestroyedLayers()
+        const {
+    return mDestroyedLayers;
+}
+
+const ftl::Flags<RequestedLayerState::Changes> LayerLifecycleManager::getGlobalChanges() const {
+    return mGlobalChanges;
+}
+
+RequestedLayerState* LayerLifecycleManager::getLayerFromId(uint32_t id) {
+    if (id == UNASSIGNED_LAYER_ID) {
+        return nullptr;
+    }
+    auto it = mIdToLayer.find(id);
+    if (it == mIdToLayer.end()) {
+        return nullptr;
+    }
+    return &it->second.owner;
+}
+
+std::vector<uint32_t>* LayerLifecycleManager::getLinkedLayersFromId(uint32_t id) {
+    if (id == UNASSIGNED_LAYER_ID) {
+        return nullptr;
+    }
+    auto it = mIdToLayer.find(id);
+    if (it == mIdToLayer.end()) {
+        return nullptr;
+    }
+    return &it->second.references;
+}
+
+void LayerLifecycleManager::linkLayer(uint32_t layerId, uint32_t layerToLink) {
+    if (layerToLink && layerId != UNASSIGNED_LAYER_ID) {
+        std::vector<uint32_t>* linkedLayers = getLinkedLayersFromId(layerId);
+        if (!linkedLayers) {
+            LOG_ALWAYS_FATAL("Could not find layer id %d to link %d", layerId, layerToLink);
+            return;
+        }
+        linkedLayers->emplace_back(layerToLink);
+    }
+}
+
+void LayerLifecycleManager::unlinkLayer(uint32_t& inOutLayerId, uint32_t linkedLayer) {
+    uint32_t layerId = inOutLayerId;
+    inOutLayerId = UNASSIGNED_LAYER_ID;
+
+    std::vector<uint32_t>* linkedLayers = getLinkedLayersFromId(layerId);
+    if (!linkedLayers) {
+        return;
+    }
+    swapErase(*linkedLayers, linkedLayer);
+}
+
+std::string LayerLifecycleManager::References::getDebugString() const {
+    std::string debugInfo = owner.name + "[" + std::to_string(owner.id) + "] refs:";
+    std::for_each(references.begin(), references.end(),
+                  [&debugInfo = debugInfo](const uint32_t& reference) mutable {
+                      debugInfo += std::to_string(reference) + ",";
+                  });
+    return debugInfo;
+}
+
+} // namespace android::surfaceflinger::frontend
diff --git a/services/surfaceflinger/FrontEnd/LayerLifecycleManager.h b/services/surfaceflinger/FrontEnd/LayerLifecycleManager.h
new file mode 100644
index 0000000..ad70d3f
--- /dev/null
+++ b/services/surfaceflinger/FrontEnd/LayerLifecycleManager.h
@@ -0,0 +1,95 @@
+/*
+ * 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 "RequestedLayerState.h"
+#include "TransactionState.h"
+
+namespace android::surfaceflinger::frontend {
+
+// Owns a collection of RequestedLayerStates and manages their lifecycle
+// and state changes.
+//
+// RequestedLayerStates are tracked and destroyed if they have no parent and
+// no handle left to keep them alive. The handle does not keep a reference to
+// the RequestedLayerState but a layer id associated with the RequestedLayerState.
+// If the handle is destroyed and the RequestedLayerState does not have a parent,
+// the LayerLifecycleManager destroys the RequestedLayerState.
+//
+// Threading: This class is not thread safe, it requires external synchronization.
+//
+// Typical usage: Input states (new layers, transactions, destroyed layer handles)
+// are collected in the background passed into the LayerLifecycleManager to update
+// layer lifecycle and layer state at start of composition.
+class LayerLifecycleManager {
+public:
+    // External state changes should be updated in the following order:
+    void addLayers(std::vector<std::unique_ptr<RequestedLayerState>>);
+    void applyTransactions(const std::vector<TransactionState>&);
+    void onHandlesDestroyed(const std::vector<uint32_t>&);
+
+    // Destroys RequestedLayerStates that are marked to be destroyed. Invokes all
+    // ILifecycleListener callbacks and clears any change flags from previous state
+    // updates. This function should be called outside the hot path since it's not
+    // critical to composition.
+    void commitChanges();
+
+    class ILifecycleListener {
+    public:
+        virtual ~ILifecycleListener() = default;
+        // Called on commitChanges when a layer is added. The callback includes
+        // the layer state the client was created with as well as any state updates
+        // until changes were committed.
+        virtual void onLayerAdded(const RequestedLayerState&) = 0;
+        // Called on commitChanges when a layer has been destroyed. The callback
+        // includes the final state before the layer was destroyed.
+        virtual void onLayerDestroyed(const RequestedLayerState&) = 0;
+    };
+    void addLifecycleListener(std::shared_ptr<ILifecycleListener>);
+    void removeLifecycleListener(std::shared_ptr<ILifecycleListener>);
+    const std::vector<std::unique_ptr<RequestedLayerState>>& getLayers() const;
+    const std::vector<std::unique_ptr<RequestedLayerState>>& getDestroyedLayers() const;
+    const ftl::Flags<RequestedLayerState::Changes> getGlobalChanges() const;
+
+private:
+    friend class LayerLifecycleManagerTest;
+    friend class HierarchyBuilderTest;
+    friend class android::SurfaceFlinger;
+
+    RequestedLayerState* getLayerFromId(uint32_t);
+    std::vector<uint32_t>* getLinkedLayersFromId(uint32_t);
+    void linkLayer(uint32_t layerId, uint32_t layerToLink);
+    void unlinkLayer(uint32_t& inOutLayerId, uint32_t linkedLayer);
+
+    struct References {
+        // Lifetime tied to mLayers
+        RequestedLayerState& owner;
+        std::vector<uint32_t> references;
+        std::string getDebugString() const;
+    };
+    std::unordered_map<uint32_t, References> mIdToLayer;
+    // Listeners are invoked once changes are committed.
+    std::vector<std::shared_ptr<ILifecycleListener>> mListeners;
+
+    // Aggregation of changes since last commit.
+    ftl::Flags<RequestedLayerState::Changes> mGlobalChanges;
+    std::vector<std::unique_ptr<RequestedLayerState>> mLayers;
+    // Layers pending destruction. Layers will be destroyed once changes are committed.
+    std::vector<std::unique_ptr<RequestedLayerState>> mDestroyedLayers;
+};
+
+} // namespace android::surfaceflinger::frontend
diff --git a/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp
new file mode 100644
index 0000000..45058d9
--- /dev/null
+++ b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp
@@ -0,0 +1,358 @@
+/*
+ * 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 "FrontEnd/LayerCreationArgs.h"
+#define ATRACE_TAG ATRACE_TAG_GRAPHICS
+#undef LOG_TAG
+#define LOG_TAG "RequestedLayerState"
+
+#include <private/android_filesystem_config.h>
+#include <sys/types.h>
+
+#include "Layer.h"
+#include "LayerHandle.h"
+#include "RequestedLayerState.h"
+
+namespace android::surfaceflinger::frontend {
+using ftl::Flags;
+using namespace ftl::flag_operators;
+
+namespace {
+uint32_t getLayerIdFromSurfaceControl(sp<SurfaceControl> surfaceControl) {
+    if (!surfaceControl) {
+        return UNASSIGNED_LAYER_ID;
+    }
+
+    return LayerHandle::getLayerId(surfaceControl->getHandle());
+}
+
+std::string layerIdToString(uint32_t layerId) {
+    return layerId == UNASSIGNED_LAYER_ID ? std::to_string(layerId) : "none";
+}
+
+} // namespace
+
+RequestedLayerState::RequestedLayerState(const LayerCreationArgs& args)
+      : id(args.sequence),
+        name(args.name),
+        canBeRoot(args.addToRoot),
+        layerCreationFlags(args.flags),
+        textureName(args.textureName),
+        ownerUid(args.ownerUid),
+        ownerPid(args.ownerPid) {
+    layerId = static_cast<int32_t>(args.sequence);
+    changes |= RequestedLayerState::Changes::Created;
+    metadata.merge(args.metadata);
+    changes |= RequestedLayerState::Changes::Metadata;
+    handleAlive = true;
+    parentId = LayerHandle::getLayerId(args.parentHandle.promote());
+    mirrorId = LayerHandle::getLayerId(args.mirrorLayerHandle.promote());
+    if (mirrorId != UNASSIGNED_LAYER_ID) {
+        changes |= RequestedLayerState::Changes::Mirror;
+    }
+
+    flags = 0;
+    if (args.flags & ISurfaceComposerClient::eHidden) flags |= layer_state_t::eLayerHidden;
+    if (args.flags & ISurfaceComposerClient::eOpaque) flags |= layer_state_t::eLayerOpaque;
+    if (args.flags & ISurfaceComposerClient::eSecure) flags |= layer_state_t::eLayerSecure;
+    if (args.flags & ISurfaceComposerClient::eSkipScreenshot) {
+        flags |= layer_state_t::eLayerSkipScreenshot;
+    }
+    premultipliedAlpha = !(args.flags & ISurfaceComposerClient::eNonPremultiplied);
+    potentialCursor = args.flags & ISurfaceComposerClient::eCursorWindow;
+    protectedByApp = args.flags & ISurfaceComposerClient::eProtectedByApp;
+    if (args.flags & ISurfaceComposerClient::eNoColorFill) {
+        // Set an invalid color so there is no color fill.
+        // (b/259981098) use an explicit flag instead of relying on invalid values.
+        color.r = -1.0_hf;
+        color.g = -1.0_hf;
+        color.b = -1.0_hf;
+    } else {
+        color.rgb = {0.0_hf, 0.0_hf, 0.0_hf};
+    }
+    color.a = 1.0f;
+
+    crop.makeInvalid();
+    z = 0;
+    layerStack = ui::DEFAULT_LAYER_STACK;
+    transformToDisplayInverse = false;
+    dataspace = ui::Dataspace::UNKNOWN;
+    dataspaceRequested = false;
+    hdrMetadata.validTypes = 0;
+    surfaceDamageRegion = Region::INVALID_REGION;
+    cornerRadius = 0.0f;
+    backgroundBlurRadius = 0;
+    api = -1;
+    hasColorTransform = false;
+    bufferTransform = 0;
+    requestedTransform.reset();
+    bufferData = std::make_shared<BufferData>();
+    bufferData->frameNumber = 0;
+    bufferData->acquireFence = sp<Fence>::make(-1);
+    acquireFenceTime = std::make_shared<FenceTime>(bufferData->acquireFence);
+    colorSpaceAgnostic = false;
+    frameRateSelectionPriority = Layer::PRIORITY_UNSET;
+    shadowRadius = 0.f;
+    fixedTransformHint = ui::Transform::ROT_INVALID;
+    destinationFrame.makeInvalid();
+    isTrustedOverlay = false;
+    dropInputMode = gui::DropInputMode::NONE;
+    dimmingEnabled = true;
+    defaultFrameRateCompatibility =
+            static_cast<int8_t>(scheduler::LayerInfo::FrameRateCompatibility::Default);
+    dataspace = ui::Dataspace::V0_SRGB;
+}
+
+void RequestedLayerState::merge(const ResolvedComposerState& resolvedComposerState) {
+    bool oldFlags = flags;
+    Rect oldBufferSize = getBufferSize();
+    const layer_state_t& clientState = resolvedComposerState.state;
+
+    uint64_t clientChanges = what | layer_state_t::diff(clientState);
+    layer_state_t::merge(clientState);
+    what = clientChanges;
+
+    if (clientState.what & layer_state_t::eFlagsChanged) {
+        if ((oldFlags ^ flags) & layer_state_t::eLayerHidden) {
+            changes |= RequestedLayerState::Changes::Visibility;
+        }
+        if ((oldFlags ^ flags) & layer_state_t::eIgnoreDestinationFrame) {
+            changes |= RequestedLayerState::Changes::Geometry;
+        }
+    }
+    if (clientState.what & layer_state_t::eBufferChanged && oldBufferSize != getBufferSize()) {
+        changes |= RequestedLayerState::Changes::Geometry;
+    }
+    if (clientChanges & layer_state_t::HIERARCHY_CHANGES)
+        changes |= RequestedLayerState::Changes::Hierarchy;
+    if (clientChanges & layer_state_t::CONTENT_CHANGES)
+        changes |= RequestedLayerState::Changes::Content;
+    if (clientChanges & layer_state_t::GEOMETRY_CHANGES)
+        changes |= RequestedLayerState::Changes::Geometry;
+
+    if (clientState.what & layer_state_t::eColorTransformChanged) {
+        static const mat4 identityMatrix = mat4();
+        hasColorTransform = colorTransform != identityMatrix;
+    }
+    if (clientState.what & layer_state_t::eLayerChanged) {
+        changes |= RequestedLayerState::Changes::Z;
+    }
+    if (clientState.what & layer_state_t::eReparent) {
+        changes |= RequestedLayerState::Changes::Parent;
+        parentId = getLayerIdFromSurfaceControl(clientState.parentSurfaceControlForChild);
+        parentSurfaceControlForChild = nullptr;
+    }
+    if (clientState.what & layer_state_t::eRelativeLayerChanged) {
+        changes |= RequestedLayerState::Changes::RelativeParent;
+        relativeParentId = getLayerIdFromSurfaceControl(clientState.relativeLayerSurfaceControl);
+        isRelativeOf = true;
+        relativeLayerSurfaceControl = nullptr;
+    }
+    if ((clientState.what & layer_state_t::eLayerChanged ||
+         (clientState.what & layer_state_t::eReparent && parentId == UNASSIGNED_LAYER_ID)) &&
+        isRelativeOf) {
+        // clear out relz data
+        relativeParentId = UNASSIGNED_LAYER_ID;
+        isRelativeOf = false;
+        changes |= RequestedLayerState::Changes::RelativeParent;
+    }
+    if (clientState.what & layer_state_t::eReparent && parentId == relativeParentId) {
+        // provide a hint that we are are now a direct child and not a relative child.
+        changes |= RequestedLayerState::Changes::RelativeParent;
+    }
+    if (clientState.what & layer_state_t::eInputInfoChanged) {
+        wp<IBinder>& touchableRegionCropHandle =
+                windowInfoHandle->editInfo()->touchableRegionCropHandle;
+        touchCropId = LayerHandle::getLayerId(touchableRegionCropHandle.promote());
+        changes |= RequestedLayerState::Changes::Input;
+        touchableRegionCropHandle.clear();
+    }
+    if (clientState.what & layer_state_t::eStretchChanged) {
+        stretchEffect.sanitize();
+    }
+
+    if (clientState.what & layer_state_t::eHasListenerCallbacksChanged) {
+        // TODO(b/238781169) handle callbacks
+    }
+
+    if (clientState.what & layer_state_t::eBufferChanged) {
+        externalTexture = resolvedComposerState.externalTexture;
+        hwcBufferSlot = resolvedComposerState.hwcBufferSlot;
+    }
+
+    if (clientState.what & layer_state_t::ePositionChanged) {
+        requestedTransform.set(x, y);
+    }
+
+    if (clientState.what & layer_state_t::eMatrixChanged) {
+        requestedTransform.set(matrix.dsdx, matrix.dtdy, matrix.dtdx, matrix.dsdy);
+    }
+}
+
+ui::Transform RequestedLayerState::getTransform() const {
+    if ((flags & layer_state_t::eIgnoreDestinationFrame) || destinationFrame.isEmpty()) {
+        // If destination frame is not set, use the requested transform set via
+        // Transaction::setPosition and Transaction::setMatrix.
+        return requestedTransform;
+    }
+
+    Rect destRect = destinationFrame;
+    int32_t destW = destRect.width();
+    int32_t destH = destRect.height();
+    if (destRect.left < 0) {
+        destRect.left = 0;
+        destRect.right = destW;
+    }
+    if (destRect.top < 0) {
+        destRect.top = 0;
+        destRect.bottom = destH;
+    }
+
+    if (!externalTexture) {
+        ui::Transform transform;
+        transform.set(static_cast<float>(destRect.left), static_cast<float>(destRect.top));
+        return transform;
+    }
+
+    uint32_t bufferWidth = externalTexture->getWidth();
+    uint32_t bufferHeight = externalTexture->getHeight();
+    // Undo any transformations on the buffer.
+    if (bufferTransform & ui::Transform::ROT_90) {
+        std::swap(bufferWidth, bufferHeight);
+    }
+    // TODO(b/238781169) remove dep
+    uint32_t invTransform = DisplayDevice::getPrimaryDisplayRotationFlags();
+    if (transformToDisplayInverse) {
+        if (invTransform & ui::Transform::ROT_90) {
+            std::swap(bufferWidth, bufferHeight);
+        }
+    }
+
+    float sx = static_cast<float>(destW) / static_cast<float>(bufferWidth);
+    float sy = static_cast<float>(destH) / static_cast<float>(bufferHeight);
+    ui::Transform transform;
+    transform.set(sx, 0, 0, sy);
+    transform.set(static_cast<float>(destRect.left), static_cast<float>(destRect.top));
+    return transform;
+}
+
+std::string RequestedLayerState::getDebugString() const {
+    return "[" + std::to_string(id) + "]" + name + ",parent=" + layerIdToString(parentId) +
+            ",relativeParent=" + layerIdToString(relativeParentId) +
+            ",isRelativeOf=" + std::to_string(isRelativeOf) +
+            ",mirrorId=" + layerIdToString(mirrorId) +
+            ",handleAlive=" + std::to_string(handleAlive);
+}
+
+std::string RequestedLayerState::getDebugStringShort() const {
+    return "[" + std::to_string(id) + "]" + name;
+}
+
+bool RequestedLayerState::canBeDestroyed() const {
+    return !handleAlive && parentId == UNASSIGNED_LAYER_ID;
+}
+bool RequestedLayerState::isRoot() const {
+    return canBeRoot && parentId == UNASSIGNED_LAYER_ID;
+}
+bool RequestedLayerState::isHiddenByPolicy() const {
+    return (flags & layer_state_t::eLayerHidden) == layer_state_t::eLayerHidden;
+};
+half4 RequestedLayerState::getColor() const {
+    if ((sidebandStream != nullptr) || (externalTexture != nullptr)) {
+        return {0._hf, 0._hf, 0._hf, color.a};
+    }
+    return color;
+}
+Rect RequestedLayerState::getBufferSize() const {
+    // for buffer state layers we use the display frame size as the buffer size.
+    if (!externalTexture) {
+        return Rect::INVALID_RECT;
+    }
+
+    uint32_t bufWidth = externalTexture->getWidth();
+    uint32_t bufHeight = externalTexture->getHeight();
+
+    // Undo any transformations on the buffer and return the result.
+    if (bufferTransform & ui::Transform::ROT_90) {
+        std::swap(bufWidth, bufHeight);
+    }
+
+    if (transformToDisplayInverse) {
+        // TODO(b/238781169) pass in display metrics (would be useful for input info as well
+        uint32_t invTransform = DisplayDevice::getPrimaryDisplayRotationFlags();
+        if (invTransform & ui::Transform::ROT_90) {
+            std::swap(bufWidth, bufHeight);
+        }
+    }
+
+    return Rect(0, 0, static_cast<int32_t>(bufWidth), static_cast<int32_t>(bufHeight));
+}
+
+Rect RequestedLayerState::getCroppedBufferSize() const {
+    Rect size = getBufferSize();
+    if (!crop.isEmpty() && size.isValid()) {
+        size.intersect(crop, &size);
+    } else if (!crop.isEmpty()) {
+        size = crop;
+    }
+    return size;
+}
+
+Rect RequestedLayerState::getBufferCrop() const {
+    // this is the crop rectangle that applies to the buffer
+    // itself (as opposed to the window)
+    if (!bufferCrop.isEmpty()) {
+        // if the buffer crop is defined, we use that
+        return bufferCrop;
+    } else if (externalTexture != nullptr) {
+        // otherwise we use the whole buffer
+        return externalTexture->getBounds();
+    } else {
+        // if we don't have a buffer yet, we use an empty/invalid crop
+        return Rect();
+    }
+}
+
+aidl::android::hardware::graphics::composer3::Composition RequestedLayerState::getCompositionType()
+        const {
+    using aidl::android::hardware::graphics::composer3::Composition;
+    // TODO(b/238781169) check about sidestream ready flag
+    if (sidebandStream.get()) {
+        return Composition::SIDEBAND;
+    }
+    if (!externalTexture) {
+        return Composition::SOLID_COLOR;
+    }
+    if (flags & layer_state_t::eLayerIsDisplayDecoration) {
+        return Composition::DISPLAY_DECORATION;
+    }
+    if (potentialCursor) {
+        return Composition::CURSOR;
+    }
+    return Composition::DEVICE;
+}
+
+Rect RequestedLayerState::reduce(const Rect& win, const Region& exclude) {
+    if (CC_LIKELY(exclude.isEmpty())) {
+        return win;
+    }
+    if (exclude.isRect()) {
+        return win.reduce(exclude.getBounds());
+    }
+    return Region(win).subtract(exclude).getBounds();
+}
+
+} // namespace android::surfaceflinger::frontend
diff --git a/services/surfaceflinger/FrontEnd/RequestedLayerState.h b/services/surfaceflinger/FrontEnd/RequestedLayerState.h
new file mode 100644
index 0000000..0ddf5e2
--- /dev/null
+++ b/services/surfaceflinger/FrontEnd/RequestedLayerState.h
@@ -0,0 +1,102 @@
+/*
+ * 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 <aidl/android/hardware/graphics/composer3/Composition.h>
+#include <ftl/flags.h>
+#include <gui/LayerState.h>
+#include <renderengine/ExternalTexture.h>
+
+#include "LayerCreationArgs.h"
+#include "TransactionState.h"
+
+namespace android::surfaceflinger::frontend {
+
+// Stores client requested states for a layer.
+// This struct does not store any other states or states pertaining to
+// other layers. Links to other layers that are part of the client
+// requested state such as parent are translated to layer id so
+// we can avoid extending the lifetime of layer handles.
+struct RequestedLayerState : layer_state_t {
+    // Changes in state after merging with new state. This includes additional state
+    // changes found in layer_state_t::what.
+    enum class Changes : uint32_t {
+        Created = 1u << 0,
+        Destroyed = 1u << 1,
+        Hierarchy = 1u << 2,
+        Geometry = 1u << 3,
+        Content = 1u << 4,
+        Input = 1u << 5,
+        Z = 1u << 6,
+        Mirror = 1u << 7,
+        Parent = 1u << 8,
+        RelativeParent = 1u << 9,
+        Metadata = 1u << 10,
+        Visibility = 1u << 11,
+    };
+    static Rect reduce(const Rect& win, const Region& exclude);
+    RequestedLayerState(const LayerCreationArgs&);
+    void merge(const ResolvedComposerState&);
+    ui::Transform getTransform() const;
+    bool canBeDestroyed() const;
+    bool isRoot() const;
+    bool isHiddenByPolicy() const;
+    half4 getColor() const;
+    Rect getBufferSize() const;
+    Rect getCroppedBufferSize() const;
+    Rect getBufferCrop() const;
+    std::string getDebugString() const;
+    std::string getDebugStringShort() const;
+    aidl::android::hardware::graphics::composer3::Composition getCompositionType() const;
+
+    // Layer serial number.  This gives layers an explicit ordering, so we
+    // have a stable sort order when their layer stack and Z-order are
+    // the same.
+    const uint32_t id;
+    const std::string name;
+    const bool canBeRoot = false;
+    const uint32_t layerCreationFlags;
+    const uint32_t textureName;
+    // The owner of the layer. If created from a non system process, it will be the calling uid.
+    // If created from a system process, the value can be passed in.
+    const uid_t ownerUid;
+    // The owner pid of the layer. If created from a non system process, it will be the calling pid.
+    // If created from a system process, the value can be passed in.
+    const pid_t ownerPid;
+    bool dataspaceRequested;
+    bool hasColorTransform;
+    bool premultipliedAlpha{true};
+    // This layer can be a cursor on some displays.
+    bool potentialCursor{false};
+    bool protectedByApp{false}; // application requires protected path to external sink
+    ui::Transform requestedTransform;
+    std::shared_ptr<FenceTime> acquireFenceTime;
+    std::shared_ptr<renderengine::ExternalTexture> externalTexture;
+    int hwcBufferSlot = 0;
+
+    // book keeping states
+    bool handleAlive = true;
+    bool isRelativeOf = false;
+    uint32_t parentId = UNASSIGNED_LAYER_ID;
+    uint32_t relativeParentId = UNASSIGNED_LAYER_ID;
+    uint32_t mirrorId = UNASSIGNED_LAYER_ID;
+    uint32_t touchCropId = UNASSIGNED_LAYER_ID;
+    uint32_t bgColorLayerId = UNASSIGNED_LAYER_ID;
+    ftl::Flags<RequestedLayerState::Changes> changes;
+};
+
+} // namespace android::surfaceflinger::frontend
diff --git a/services/surfaceflinger/FrontEnd/SwapErase.h b/services/surfaceflinger/FrontEnd/SwapErase.h
new file mode 100644
index 0000000..f672f99
--- /dev/null
+++ b/services/surfaceflinger/FrontEnd/SwapErase.h
@@ -0,0 +1,45 @@
+/*
+ * 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 <vector>
+
+namespace android::surfaceflinger::frontend {
+// Erases the first element in vec that matches value. This is a more optimal way to
+// remove an element from a vector that avoids relocating all the elements after the one
+// that is erased.
+template <typename T>
+void swapErase(std::vector<T>& vec, const T& value) {
+    auto it = std::find(vec.begin(), vec.end(), value);
+    if (it != vec.end()) {
+        std::iter_swap(it, vec.end() - 1);
+        vec.erase(vec.end() - 1);
+    }
+}
+
+// Similar to swapErase(std::vector<T>& vec, const T& value) but erases the first element
+// that returns true for predicate.
+template <typename T, class P>
+void swapErase(std::vector<T>& vec, P predicate) {
+    auto it = std::find_if(vec.begin(), vec.end(), predicate);
+    if (it != vec.end()) {
+        std::iter_swap(it, vec.end() - 1);
+        vec.erase(vec.end() - 1);
+    }
+}
+
+} // namespace android::surfaceflinger::frontend
diff --git a/services/surfaceflinger/TransactionHandler.cpp b/services/surfaceflinger/FrontEnd/TransactionHandler.cpp
similarity index 94%
rename from services/surfaceflinger/TransactionHandler.cpp
rename to services/surfaceflinger/FrontEnd/TransactionHandler.cpp
index 6c6a487..8629671 100644
--- a/services/surfaceflinger/TransactionHandler.cpp
+++ b/services/surfaceflinger/FrontEnd/TransactionHandler.cpp
@@ -24,7 +24,7 @@
 
 #include "TransactionHandler.h"
 
-namespace android {
+namespace android::surfaceflinger::frontend {
 
 void TransactionHandler::queueTransaction(TransactionState&& state) {
     mLocklessTransactionQueue.push(std::move(state));
@@ -168,15 +168,16 @@
     return !mPendingTransactionQueues.empty() || !mLocklessTransactionQueue.isEmpty();
 }
 
-void TransactionHandler::onTransactionQueueStalled(const TransactionState& transaction,
-                                                   sp<ITransactionCompletedListener>& listener) {
-    if (std::find(mStalledTransactions.begin(), mStalledTransactions.end(), transaction.id) !=
+void TransactionHandler::onTransactionQueueStalled(uint64_t transactionId,
+                                                   sp<ITransactionCompletedListener>& listener,
+                                                   const std::string& reason) {
+    if (std::find(mStalledTransactions.begin(), mStalledTransactions.end(), transactionId) !=
         mStalledTransactions.end()) {
         return;
     }
 
-    mStalledTransactions.push_back(transaction.id);
-    listener->onTransactionQueueStalled();
+    mStalledTransactions.push_back(transactionId);
+    listener->onTransactionQueueStalled(String8(reason.c_str()));
 }
 
 void TransactionHandler::removeFromStalledTransactions(uint64_t id) {
@@ -185,4 +186,4 @@
         mStalledTransactions.erase(it);
     }
 }
-} // namespace android
\ No newline at end of file
+} // namespace android::surfaceflinger::frontend
diff --git a/services/surfaceflinger/TransactionHandler.h b/services/surfaceflinger/FrontEnd/TransactionHandler.h
similarity index 88%
rename from services/surfaceflinger/TransactionHandler.h
rename to services/surfaceflinger/FrontEnd/TransactionHandler.h
index 237b48d..a06b870 100644
--- a/services/surfaceflinger/TransactionHandler.h
+++ b/services/surfaceflinger/FrontEnd/TransactionHandler.h
@@ -18,9 +18,6 @@
 
 #include <semaphore.h>
 #include <cstdint>
-#include <mutex>
-#include <queue>
-#include <thread>
 #include <vector>
 
 #include <LocklessQueue.h>
@@ -30,6 +27,10 @@
 #include <ftl/small_vector.h>
 
 namespace android {
+
+class TestableSurfaceFlinger;
+namespace surfaceflinger::frontend {
+
 class TransactionHandler {
 public:
     struct TransactionFlushState {
@@ -54,12 +55,13 @@
     std::vector<TransactionState> flushTransactions();
     void addTransactionReadyFilter(TransactionFilter&&);
     void queueTransaction(TransactionState&&);
-    void onTransactionQueueStalled(const TransactionState&, sp<ITransactionCompletedListener>&);
+    void onTransactionQueueStalled(uint64_t transactionId, sp<ITransactionCompletedListener>&,
+                                   const std::string& reason);
     void removeFromStalledTransactions(uint64_t transactionId);
 
 private:
     // For unit tests
-    friend class TestableSurfaceFlinger;
+    friend class ::android::TestableSurfaceFlinger;
 
     int flushPendingTransactionQueues(std::vector<TransactionState>&, TransactionFlushState&);
     TransactionReadiness applyFilters(TransactionFlushState&);
@@ -70,5 +72,5 @@
     ftl::SmallVector<TransactionFilter, 2> mTransactionReadyFilters;
     std::vector<uint64_t> mStalledTransactions;
 };
-
+} // namespace surfaceflinger::frontend
 } // namespace android
diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp
index 410e438..0017af0 100644
--- a/services/surfaceflinger/Layer.cpp
+++ b/services/surfaceflinger/Layer.cpp
@@ -40,7 +40,6 @@
 #include <ftl/enum.h>
 #include <ftl/fake_guard.h>
 #include <gui/BufferItem.h>
-#include <gui/GLConsumer.h>
 #include <gui/LayerDebugInfo.h>
 #include <gui/Surface.h>
 #include <gui/TraceUtils.h>
@@ -63,12 +62,15 @@
 
 #include <algorithm>
 #include <mutex>
+#include <optional>
 #include <sstream>
 
 #include "DisplayDevice.h"
 #include "DisplayHardware/HWComposer.h"
 #include "FrameTimeline.h"
 #include "FrameTracer/FrameTracer.h"
+#include "FrontEnd/LayerCreationArgs.h"
+#include "FrontEnd/LayerHandle.h"
 #include "LayerProtoHelper.h"
 #include "SurfaceFlinger.h"
 #include "TimeStats/TimeStats.h"
@@ -81,28 +83,8 @@
 namespace {
 constexpr int kDumpTableRowLength = 159;
 
-static constexpr float defaultMaxLuminance = 1000.0;
-
 const ui::Transform kIdentityTransform;
 
-constexpr mat4 inverseOrientation(uint32_t transform) {
-    const mat4 flipH(-1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1);
-    const mat4 flipV(1, 0, 0, 0, 0, -1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1);
-    const mat4 rot90(0, 1, 0, 0, -1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1);
-    mat4 tr;
-
-    if (transform & NATIVE_WINDOW_TRANSFORM_ROT_90) {
-        tr = tr * rot90;
-    }
-    if (transform & NATIVE_WINDOW_TRANSFORM_FLIP_H) {
-        tr = tr * flipH;
-    }
-    if (transform & NATIVE_WINDOW_TRANSFORM_FLIP_V) {
-        tr = tr * flipV;
-    }
-    return inverse(tr);
-}
-
 bool assignTransform(ui::Transform* dst, ui::Transform& from) {
     if (*dst == from) {
         return false;
@@ -152,10 +134,8 @@
 
 using PresentState = frametimeline::SurfaceFrame::PresentState;
 
-std::atomic<int32_t> Layer::sSequence{1};
-
 Layer::Layer(const LayerCreationArgs& args)
-      : sequence(args.sequence.value_or(sSequence++)),
+      : sequence(args.sequence),
         mFlinger(sp<SurfaceFlinger>::fromExisting(args.flinger)),
         mName(base::StringPrintf("%s#%d", args.name.c_str(), sequence)),
         mClientRef(args.client),
@@ -164,7 +144,8 @@
         mLayerCreationFlags(args.flags),
         mBorderEnabled(false),
         mTextureName(args.textureName),
-        mHwcSlotGenerator(sp<HwcSlotGenerator>::make()) {
+        mHwcSlotGenerator(sp<HwcSlotGenerator>::make()),
+        mLayerFE(args.flinger->getFactory().createLayerFE(mName)) {
     ALOGV("Creating Layer %s", getDebugName());
 
     uint32_t layerFlags = 0;
@@ -173,9 +154,6 @@
     if (args.flags & ISurfaceComposerClient::eSecure) layerFlags |= layer_state_t::eLayerSecure;
     if (args.flags & ISurfaceComposerClient::eSkipScreenshot)
         layerFlags |= layer_state_t::eLayerSkipScreenshot;
-    if (args.sequence) {
-        sSequence = *args.sequence + 1;
-    }
     mDrawingState.flags = layerFlags;
     mDrawingState.crop.makeInvalid();
     mDrawingState.z = 0;
@@ -217,21 +195,10 @@
         mDrawingState.color.b = -1.0_hf;
     }
 
-    mFrameTracker.setDisplayRefreshPeriod(
-            args.flinger->mScheduler->getVsyncPeriodFromRefreshRateConfigs());
+    mFrameTracker.setDisplayRefreshPeriod(args.flinger->mScheduler->getLeaderVsyncPeriod());
 
-    mCallingPid = args.callingPid;
-    mCallingUid = args.callingUid;
-
-    if (mCallingUid == AID_GRAPHICS || mCallingUid == AID_SYSTEM) {
-        // If the system didn't send an ownerUid, use the callingUid for the ownerUid.
-        mOwnerUid = args.metadata.getInt32(gui::METADATA_OWNER_UID, mCallingUid);
-        mOwnerPid = args.metadata.getInt32(gui::METADATA_OWNER_PID, mCallingPid);
-    } else {
-        // A create layer request from a non system request cannot specify the owner uid
-        mOwnerUid = mCallingUid;
-        mOwnerPid = mCallingPid;
-    }
+    mOwnerUid = args.ownerUid;
+    mOwnerPid = args.ownerPid;
 
     mPremultipliedAlpha = !(args.flags & ISurfaceComposerClient::eNonPremultiplied);
     mPotentialCursor = args.flags & ISurfaceComposerClient::eCursorWindow;
@@ -272,11 +239,6 @@
     mFlinger->mTimeStats->onDestroy(layerId);
     mFlinger->mFrameTracer->onDestroy(layerId);
 
-    sp<Client> c(mClientRef.promote());
-    if (c != 0) {
-        c->detachLayer(this);
-    }
-
     mFrameTracker.logAndResetStats(mName);
     mFlinger->onLayerDestroyed(this);
 
@@ -288,18 +250,6 @@
     }
 }
 
-LayerCreationArgs::LayerCreationArgs(SurfaceFlinger* flinger, sp<Client> client, std::string name,
-                                     uint32_t flags, LayerMetadata metadata)
-      : flinger(flinger),
-        client(std::move(client)),
-        name(std::move(name)),
-        flags(flags),
-        metadata(std::move(metadata)) {
-    IPCThreadState* ipc = IPCThreadState::self();
-    callingPid = ipc->getCallingPid();
-    callingUid = ipc->getCallingUid();
-}
-
 // ---------------------------------------------------------------------------
 // callbacks
 // ---------------------------------------------------------------------------
@@ -377,7 +327,7 @@
         return nullptr;
     }
     mGetHandleCalled = true;
-    return sp<Handle>::make(mFlinger, sp<Layer>::fromExisting(this));
+    return sp<LayerHandle>::make(mFlinger, sp<Layer>::fromExisting(this));
 }
 
 // ---------------------------------------------------------------------------
@@ -521,7 +471,7 @@
     snapshot->geomLayerTransform = getTransform();
     snapshot->geomInverseLayerTransform = snapshot->geomLayerTransform.inverse();
     snapshot->transparentRegionHint = getActiveTransparentRegion(drawingState);
-
+    snapshot->blurRegionTransform = getActiveTransform(drawingState).inverse();
     snapshot->blendMode = static_cast<Hwc2::IComposerClient::BlendMode>(blendMode);
     snapshot->alpha = alpha;
     snapshot->backgroundBlurRadius = drawingState.backgroundBlurRadius;
@@ -652,12 +602,6 @@
     snapshot->cursorFrame = frame;
 }
 
-sp<compositionengine::LayerFE> Layer::asLayerFE() const {
-    compositionengine::LayerFE* layerFE = const_cast<compositionengine::LayerFE*>(
-            static_cast<const compositionengine::LayerFE*>(this));
-    return sp<compositionengine::LayerFE>::fromExisting(layerFE);
-}
-
 const char* Layer::getDebugName() const {
     return mName.c_str();
 }
@@ -666,237 +610,6 @@
 // drawing...
 // ---------------------------------------------------------------------------
 
-std::optional<compositionengine::LayerFE::LayerSettings> Layer::prepareClientComposition(
-        compositionengine::LayerFE::ClientCompositionTargetSettings& targetSettings) const {
-    std::optional<compositionengine::LayerFE::LayerSettings> layerSettings =
-            prepareClientCompositionInternal(targetSettings);
-    // Nothing to render.
-    if (!layerSettings) {
-        return {};
-    }
-
-    // HWC requests to clear this layer.
-    if (targetSettings.clearContent) {
-        prepareClearClientComposition(*layerSettings, false /* blackout */);
-        return layerSettings;
-    }
-
-    // set the shadow for the layer if needed
-    prepareShadowClientComposition(*layerSettings, targetSettings.viewport);
-
-    return layerSettings;
-}
-
-std::optional<compositionengine::LayerFE::LayerSettings> Layer::prepareClientCompositionInternal(
-        compositionengine::LayerFE::ClientCompositionTargetSettings& targetSettings) const {
-    ATRACE_CALL();
-
-    const auto* snapshot = getLayerSnapshot();
-    if (!snapshot) {
-        return {};
-    }
-
-    compositionengine::LayerFE::LayerSettings layerSettings;
-    layerSettings.geometry.boundaries =
-            reduce(snapshot->geomLayerBounds, snapshot->transparentRegionHint);
-    layerSettings.geometry.positionTransform = snapshot->geomLayerTransform.asMatrix4();
-
-    // skip drawing content if the targetSettings indicate the content will be occluded
-    const bool drawContent = targetSettings.realContentIsVisible || targetSettings.clearContent;
-    layerSettings.skipContentDraw = !drawContent;
-
-    if (hasColorTransform()) {
-        layerSettings.colorTransform = snapshot->colorTransform;
-    }
-
-    const auto& roundedCornerState = snapshot->roundedCorner;
-    layerSettings.geometry.roundedCornersRadius = roundedCornerState.radius;
-    layerSettings.geometry.roundedCornersCrop = roundedCornerState.cropRect;
-
-    layerSettings.alpha = snapshot->alpha;
-    layerSettings.sourceDataspace = snapshot->dataspace;
-
-    // Override the dataspace transfer from 170M to sRGB if the device configuration requests this.
-    // We do this here instead of in buffer info so that dumpsys can still report layers that are
-    // using the 170M transfer.
-    if (mFlinger->mTreat170mAsSrgb &&
-        (layerSettings.sourceDataspace & HAL_DATASPACE_TRANSFER_MASK) ==
-                HAL_DATASPACE_TRANSFER_SMPTE_170M) {
-        layerSettings.sourceDataspace = static_cast<ui::Dataspace>(
-                (layerSettings.sourceDataspace & HAL_DATASPACE_STANDARD_MASK) |
-                (layerSettings.sourceDataspace & HAL_DATASPACE_RANGE_MASK) |
-                HAL_DATASPACE_TRANSFER_SRGB);
-    }
-
-    layerSettings.whitePointNits = targetSettings.whitePointNits;
-    switch (targetSettings.blurSetting) {
-        case LayerFE::ClientCompositionTargetSettings::BlurSetting::Enabled:
-            layerSettings.backgroundBlurRadius = snapshot->backgroundBlurRadius;
-            layerSettings.blurRegions = snapshot->blurRegions;
-            layerSettings.blurRegionTransform = snapshot->geomInverseLayerTransform.asMatrix4();
-            break;
-        case LayerFE::ClientCompositionTargetSettings::BlurSetting::BackgroundBlurOnly:
-            layerSettings.backgroundBlurRadius = snapshot->backgroundBlurRadius;
-            break;
-        case LayerFE::ClientCompositionTargetSettings::BlurSetting::BlurRegionsOnly:
-            layerSettings.blurRegions = snapshot->blurRegions;
-            layerSettings.blurRegionTransform = snapshot->geomInverseLayerTransform.asMatrix4();
-            break;
-        case LayerFE::ClientCompositionTargetSettings::BlurSetting::Disabled:
-        default:
-            break;
-    }
-    layerSettings.stretchEffect = snapshot->stretchEffect;
-    // Record the name of the layer for debugging further down the stack.
-    layerSettings.name = snapshot->name;
-
-    if (hasEffect() && !hasBufferOrSidebandStream()) {
-        prepareEffectsClientComposition(layerSettings, targetSettings);
-        return layerSettings;
-    }
-
-    prepareBufferStateClientComposition(layerSettings, targetSettings);
-    return layerSettings;
-}
-
-void Layer::prepareClearClientComposition(LayerFE::LayerSettings& layerSettings,
-                                          bool blackout) const {
-    layerSettings.source.buffer.buffer = nullptr;
-    layerSettings.source.solidColor = half3(0.0, 0.0, 0.0);
-    layerSettings.disableBlending = true;
-    layerSettings.bufferId = 0;
-    layerSettings.frameNumber = 0;
-
-    // If layer is blacked out, force alpha to 1 so that we draw a black color layer.
-    layerSettings.alpha = blackout ? 1.0f : 0.0f;
-    layerSettings.name = getLayerSnapshot()->name;
-}
-
-void Layer::prepareEffectsClientComposition(
-        compositionengine::LayerFE::LayerSettings& layerSettings,
-        compositionengine::LayerFE::ClientCompositionTargetSettings& targetSettings) const {
-    // If fill bounds are occluded or the fill color is invalid skip the fill settings.
-    if (targetSettings.realContentIsVisible && fillsColor()) {
-        // Set color for color fill settings.
-        layerSettings.source.solidColor = getColor().rgb;
-    } else if (hasBlur() || drawShadows()) {
-        layerSettings.skipContentDraw = true;
-    }
-}
-
-void Layer::prepareBufferStateClientComposition(
-        compositionengine::LayerFE::LayerSettings& layerSettings,
-        compositionengine::LayerFE::ClientCompositionTargetSettings& targetSettings) const {
-    ATRACE_CALL();
-    const auto* snapshot = getLayerSnapshot();
-    if (CC_UNLIKELY(!snapshot->externalTexture)) {
-        // If there is no buffer for the layer or we have sidebandstream where there is no
-        // activeBuffer, then we need to return LayerSettings.
-        return;
-    }
-    const bool blackOutLayer =
-            (snapshot->hasProtectedContent && !targetSettings.supportsProtectedContent) ||
-            ((snapshot->isSecure || snapshot->hasProtectedContent) && !targetSettings.isSecure);
-    const bool bufferCanBeUsedAsHwTexture =
-            snapshot->externalTexture->getUsage() & GraphicBuffer::USAGE_HW_TEXTURE;
-    if (blackOutLayer || !bufferCanBeUsedAsHwTexture) {
-        ALOGE_IF(!bufferCanBeUsedAsHwTexture, "%s is blacked out as buffer is not gpu readable",
-                 snapshot->name.c_str());
-        prepareClearClientComposition(layerSettings, true /* blackout */);
-        return;
-    }
-
-    layerSettings.source.buffer.buffer = snapshot->externalTexture;
-    layerSettings.source.buffer.isOpaque = snapshot->contentOpaque;
-    layerSettings.source.buffer.fence = snapshot->acquireFence;
-    layerSettings.source.buffer.textureName = snapshot->textureName;
-    layerSettings.source.buffer.usePremultipliedAlpha = snapshot->premultipliedAlpha;
-    layerSettings.source.buffer.isY410BT2020 = snapshot->isHdrY410;
-    bool hasSmpte2086 = snapshot->hdrMetadata.validTypes & HdrMetadata::SMPTE2086;
-    bool hasCta861_3 = snapshot->hdrMetadata.validTypes & HdrMetadata::CTA861_3;
-    float maxLuminance = 0.f;
-    if (hasSmpte2086 && hasCta861_3) {
-        maxLuminance = std::min(snapshot->hdrMetadata.smpte2086.maxLuminance,
-                                snapshot->hdrMetadata.cta8613.maxContentLightLevel);
-    } else if (hasSmpte2086) {
-        maxLuminance = snapshot->hdrMetadata.smpte2086.maxLuminance;
-    } else if (hasCta861_3) {
-        maxLuminance = snapshot->hdrMetadata.cta8613.maxContentLightLevel;
-    } else {
-        switch (layerSettings.sourceDataspace & HAL_DATASPACE_TRANSFER_MASK) {
-            case HAL_DATASPACE_TRANSFER_ST2084:
-            case HAL_DATASPACE_TRANSFER_HLG:
-                // Behavior-match previous releases for HDR content
-                maxLuminance = defaultMaxLuminance;
-                break;
-        }
-    }
-    layerSettings.source.buffer.maxLuminanceNits = maxLuminance;
-    layerSettings.frameNumber = snapshot->frameNumber;
-    layerSettings.bufferId = snapshot->externalTexture->getId();
-
-    const bool useFiltering = targetSettings.needsFiltering ||
-            snapshot->geomLayerTransform.needsBilinearFiltering() || snapshot->bufferNeedsFiltering;
-
-    // Query the texture matrix given our current filtering mode.
-    float textureMatrix[16];
-    getDrawingTransformMatrix(useFiltering, textureMatrix);
-
-    if (snapshot->geomBufferUsesDisplayInverseTransform) {
-        /*
-         * the code below applies the primary display's inverse transform to
-         * the texture transform
-         */
-        uint32_t transform = DisplayDevice::getPrimaryDisplayRotationFlags();
-        mat4 tr = inverseOrientation(transform);
-
-        /**
-         * TODO(b/36727915): This is basically a hack.
-         *
-         * Ensure that regardless of the parent transformation,
-         * this buffer is always transformed from native display
-         * orientation to display orientation. For example, in the case
-         * of a camera where the buffer remains in native orientation,
-         * we want the pixels to always be upright.
-         */
-        const auto parentTransform = snapshot->transform;
-        tr = tr * inverseOrientation(parentTransform.getOrientation());
-
-        // and finally apply it to the original texture matrix
-        const mat4 texTransform(mat4(static_cast<const float*>(textureMatrix)) * tr);
-        memcpy(textureMatrix, texTransform.asArray(), sizeof(textureMatrix));
-    }
-
-    const Rect win{layerSettings.geometry.boundaries};
-    float bufferWidth = snapshot->bufferSize.getWidth();
-    float bufferHeight = snapshot->bufferSize.getHeight();
-
-    // Layers can have a "buffer size" of [0, 0, -1, -1] when no display frame has
-    // been set and there is no parent layer bounds. In that case, the scale is meaningless so
-    // ignore them.
-    if (!snapshot->bufferSize.isValid()) {
-        bufferWidth = float(win.right) - float(win.left);
-        bufferHeight = float(win.bottom) - float(win.top);
-    }
-
-    const float scaleHeight = (float(win.bottom) - float(win.top)) / bufferHeight;
-    const float scaleWidth = (float(win.right) - float(win.left)) / bufferWidth;
-    const float translateY = float(win.top) / bufferHeight;
-    const float translateX = float(win.left) / bufferWidth;
-
-    // Flip y-coordinates because GLConsumer expects OpenGL convention.
-    mat4 tr = mat4::translate(vec4(.5f, .5f, 0.f, 1.f)) * mat4::scale(vec4(1.f, -1.f, 1.f, 1.f)) *
-            mat4::translate(vec4(-.5f, -.5f, 0.f, 1.f)) *
-            mat4::translate(vec4(translateX, translateY, 0.f, 1.f)) *
-            mat4::scale(vec4(scaleWidth, scaleHeight, 1.0f, 1.0f));
-
-    layerSettings.source.buffer.useTextureFiltering = useFiltering;
-    layerSettings.source.buffer.textureTransform =
-            mat4(static_cast<const float*>(textureMatrix)) * tr;
-
-    return;
-}
-
 aidl::android::hardware::graphics::composer3::Composition Layer::getCompositionType(
         const DisplayDevice& display) const {
     const auto outputLayer = findOutputLayerForDisplay(&display);
@@ -1056,7 +769,7 @@
 }
 
 bool Layer::setRelativeLayer(const sp<IBinder>& relativeToHandle, int32_t relativeZ) {
-    sp<Layer> relative = fromHandle(relativeToHandle).promote();
+    sp<Layer> relative = LayerHandle::getLayer(relativeToHandle);
     if (relative == nullptr) {
         return false;
     }
@@ -1296,8 +1009,10 @@
     return priority == PRIORITY_FOCUSED_WITH_MODE || priority == PRIORITY_FOCUSED_WITHOUT_MODE;
 };
 
-ui::LayerStack Layer::getLayerStack() const {
-    if (const auto parent = mDrawingParent.promote()) {
+ui::LayerStack Layer::getLayerStack(LayerVector::StateSet state) const {
+    bool useDrawing = state == LayerVector::StateSet::Drawing;
+    const auto parent = useDrawing ? mDrawingParent.promote() : mCurrentParent.promote();
+    if (parent) {
         return parent->getLayerStack();
     }
     return getDrawingState().layerStack;
@@ -1408,7 +1123,7 @@
 
     // We return whether this layer ot its children has a vote. We ignore ExactOrMultiple votes for
     // the same reason we are allowing touch boost for those layers. See
-    // RefreshRateConfigs::getBestRefreshRate for more details.
+    // RefreshRateSelector::rankFrameRates for details.
     const auto layerVotedWithDefaultCompatibility =
             frameRate.rate.isValid() && frameRate.type == FrameRateCompatibility::Default;
     const auto layerVotedWithNoVote = frameRate.type == FrameRateCompatibility::NoVote;
@@ -1551,7 +1266,6 @@
     if (fps) {
         surfaceFrame->setRenderRate(*fps);
     }
-    // TODO(b/178542907): Implement onSurfaceFrameCreated for BQLayer as well.
     onSurfaceFrameCreated(surfaceFrame);
     return surfaceFrame;
 }
@@ -1613,6 +1327,10 @@
     return usage;
 }
 
+void Layer::skipReportingTransformHint() {
+    mSkipReportingTransformHint = true;
+}
+
 void Layer::updateTransformHint(ui::Transform::RotationFlags transformHint) {
     if (mFlinger->mDebugDisableTransformHint || transformHint & ui::Transform::ROT_INVALID) {
         transformHint = ui::Transform::ROT_0;
@@ -1755,8 +1473,8 @@
 }
 
 void Layer::dumpCallingUidPid(std::string& result) const {
-    StringAppendF(&result, "Layer %s (%s) callingPid:%d callingUid:%d ownerUid:%d\n",
-                  getName().c_str(), getType(), mCallingPid, mCallingUid, mOwnerUid);
+    StringAppendF(&result, "Layer %s (%s) ownerPid:%d ownerUid:%d\n", getName().c_str(), getType(),
+                  mOwnerPid, mOwnerUid);
 }
 
 void Layer::onDisconnect() {
@@ -1822,7 +1540,7 @@
 bool Layer::reparent(const sp<IBinder>& newParentHandle) {
     sp<Layer> newParent;
     if (newParentHandle != nullptr) {
-        newParent = fromHandle(newParentHandle).promote();
+        newParent = LayerHandle::getLayer(newParentHandle);
         if (newParent == nullptr) {
             ALOGE("Unable to promote Layer handle");
             return false;
@@ -2139,7 +1857,7 @@
     return regionsCopy;
 }
 
-Layer::RoundedCornerState Layer::getRoundedCornerState() const {
+RoundedCornerState Layer::getRoundedCornerState() const {
     // Get parent settings
     RoundedCornerState parentSettings;
     const auto& parent = mDrawingParent.promote();
@@ -2180,21 +1898,6 @@
     return {};
 }
 
-void Layer::prepareShadowClientComposition(LayerFE::LayerSettings& caster,
-                                           const Rect& layerStackRect) const {
-    const auto* snapshot = getLayerSnapshot();
-    renderengine::ShadowSettings state = snapshot->shadowSettings;
-    if (state.length <= 0.f || (state.ambientColor.a <= 0.f && state.spotColor.a <= 0.f)) {
-        return;
-    }
-
-    // Shift the spot light x-position to the middle of the display and then
-    // offset it by casting layer's screen pos.
-    state.lightPos.x = (layerStackRect.width() / 2.f) - snapshot->transformedBounds.left;
-    state.lightPos.y -= snapshot->transformedBounds.top;
-    caster.shadow = state;
-}
-
 bool Layer::findInHierarchy(const sp<Layer>& l) {
     if (l == this) {
         return true;
@@ -2230,7 +1933,8 @@
 
 void Layer::setInputInfo(const WindowInfo& info) {
     mDrawingState.inputInfo = info;
-    mDrawingState.touchableRegionCrop = fromHandle(info.touchableRegionCropHandle.promote());
+    mDrawingState.touchableRegionCrop =
+            LayerHandle::getLayer(info.touchableRegionCropHandle.promote());
     mDrawingState.modified = true;
     mFlinger->mUpdateInputInfo = true;
     setTransactionFlags(eTransactionNeeded);
@@ -2877,23 +2581,6 @@
     mFlinger->mNumClones++;
 }
 
-const String16 Layer::Handle::kDescriptor = String16("android.Layer.Handle");
-
-wp<Layer> Layer::fromHandle(const sp<IBinder>& handleBinder) {
-    if (handleBinder == nullptr) {
-        return nullptr;
-    }
-
-    BBinder* b = handleBinder->localBinder();
-    if (b == nullptr || b->getInterfaceDescriptor() != Handle::kDescriptor) {
-        return nullptr;
-    }
-
-    // We can safely cast this binder since its local and we verified its interface descriptor.
-    sp<Handle> handle = sp<Handle>::cast(handleBinder);
-    return handle->owner;
-}
-
 bool Layer::setDropInputMode(gui::DropInputMode mode) {
     if (mDrawingState.dropInputMode == mode) {
         return false;
@@ -2973,6 +2660,10 @@
 
 void Layer::onSurfaceFrameCreated(
         const std::shared_ptr<frametimeline::SurfaceFrame>& surfaceFrame) {
+    if (!hasBufferOrSidebandStreamInDrawing()) {
+        return;
+    }
+
     while (mPendingJankClassifications.size() >= kPendingClassificationMaxSurfaceFrames) {
         // Too many SurfaceFrames pending classification. The front of the deque is probably not
         // tracked by FrameTimeline and will never be presented. This will only result in a memory
@@ -2988,7 +2679,9 @@
 
 void Layer::releasePendingBuffer(nsecs_t dequeueReadyTime) {
     for (const auto& handle : mDrawingState.callbackHandles) {
-        handle->transformHint = mTransformHint;
+        handle->transformHint = mSkipReportingTransformHint
+                ? std::nullopt
+                : std::make_optional<uint32_t>(mTransformHint);
         handle->dequeueReadyTime = dequeueReadyTime;
         handle->currentMaxAcquiredBufferCount =
                 mFlinger->getMaxAcquiredBufferCountForCurrentRefreshRate(mOwnerUid);
@@ -3147,7 +2840,7 @@
 bool Layer::setBuffer(std::shared_ptr<renderengine::ExternalTexture>& buffer,
                       const BufferData& bufferData, nsecs_t postTime, nsecs_t desiredPresentTime,
                       bool isAutoTimestamp, std::optional<nsecs_t> dequeueTime,
-                      const FrameTimelineInfo& info) {
+                      const FrameTimelineInfo& info, int hwcBufferSlot) {
     ATRACE_FORMAT("setBuffer %s - hasBuffer=%s", getDebugName(), (buffer ? "true" : "false"));
     if (!buffer) {
         return false;
@@ -3193,7 +2886,7 @@
     mDrawingState.releaseBufferListener = bufferData.releaseBufferListener;
     mDrawingState.buffer = std::move(buffer);
     mDrawingState.clientCacheId = bufferData.cachedBuffer;
-
+    mDrawingState.hwcBufferSlot = hwcBufferSlot;
     mDrawingState.acquireFence = bufferData.flags.test(BufferData::BufferDataChange::fenceChanged)
             ? bufferData.acquireFence
             : Fence::NO_FENCE;
@@ -3377,7 +3070,7 @@
     return fenceSignaled;
 }
 
-bool Layer::onPreComposition(nsecs_t refreshStartTime, bool /* updatingOutputGeometryThisFrame */) {
+bool Layer::onPreComposition(nsecs_t refreshStartTime) {
     for (const auto& handle : mDrawingState.callbackHandles) {
         handle->refreshStartTime = refreshStartTime;
     }
@@ -3492,7 +3185,7 @@
     mBufferInfo.mHdrMetadata = mDrawingState.hdrMetadata;
     mBufferInfo.mApi = mDrawingState.api;
     mBufferInfo.mTransformToDisplayInverse = mDrawingState.transformToDisplayInverse;
-    mBufferInfo.mBufferSlot = mHwcSlotGenerator->getHwcCacheSlot(mDrawingState.clientCacheId);
+    mBufferInfo.mBufferSlot = mDrawingState.hwcBufferSlot;
 }
 
 Rect Layer::computeBufferCrop(const State& s) {
@@ -3511,7 +3204,6 @@
     LayerCreationArgs args(mFlinger.get(), nullptr, mName + " (Mirror)", 0, LayerMetadata());
     args.textureName = mTextureName;
     sp<Layer> layer = mFlinger->getFactory().createBufferStateLayer(args);
-    layer->mHwcSlotGenerator = mHwcSlotGenerator;
     layer->setInitialValuesForClone(sp<Layer>::fromExisting(this));
     return layer;
 }
@@ -3633,7 +3325,7 @@
     }
 
     if (s.what & layer_state_t::eAlphaChanged) {
-        if (mDrawingState.color.a != s.alpha) {
+        if (mDrawingState.color.a != s.color.a) {
             ALOGV("%s: false [eAlphaChanged changed]", __func__);
             return false;
         }
@@ -3677,9 +3369,9 @@
         }
     }
 
-    if (s.what & layer_state_t::eTransformChanged) {
-        if (mDrawingState.bufferTransform != s.transform) {
-            ALOGV("%s: false [eTransformChanged changed]", __func__);
+    if (s.what & layer_state_t::eBufferTransformChanged) {
+        if (mDrawingState.bufferTransform != s.bufferTransform) {
+            ALOGV("%s: false [eBufferTransformChanged changed]", __func__);
             return false;
         }
     }
@@ -3788,26 +3480,29 @@
             mBufferInfo.mPixelFormat == HAL_PIXEL_FORMAT_RGBA_1010102);
 }
 
-sp<compositionengine::LayerFE> Layer::getCompositionEngineLayerFE() const {
+sp<LayerFE> Layer::getCompositionEngineLayerFE() const {
     // There's no need to get a CE Layer if the layer isn't going to draw anything.
-    if (hasSomethingToDraw()) {
-        return asLayerFE();
-    } else {
-        return nullptr;
-    }
+    return hasSomethingToDraw() ? mLayerFE : nullptr;
 }
 
-const Layer::LayerSnapshot* Layer::getLayerSnapshot() const {
+const LayerSnapshot* Layer::getLayerSnapshot() const {
     return mSnapshot.get();
 }
 
-Layer::LayerSnapshot* Layer::editLayerSnapshot() {
+LayerSnapshot* Layer::editLayerSnapshot() {
     return mSnapshot.get();
 }
+
 const compositionengine::LayerFECompositionState* Layer::getCompositionState() const {
     return mSnapshot.get();
 }
 
+sp<LayerFE> Layer::copyCompositionEngineLayerFE() const {
+    auto result = mFlinger->getFactory().createLayerFE(mLayerFE->getDebugName());
+    result->mSnapshot = std::make_unique<LayerSnapshot>(*mSnapshot);
+    return result;
+}
+
 void Layer::useSurfaceDamage() {
     if (mFlinger->mForceFullDamage) {
         surfaceDamageRegion = Region::INVALID_REGION;
@@ -3902,7 +3597,7 @@
     }
 
     if (display) {
-        const Fps refreshRate = display->refreshRateConfigs().getActiveModePtr()->getFps();
+        const Fps refreshRate = display->refreshRateSelector().getActiveMode().fps;
         const std::optional<Fps> renderRate =
                 mFlinger->mScheduler->getFrameRateOverride(getOwnerUid());
 
@@ -4154,22 +3849,12 @@
     return mBufferInfo.mBuffer ? mBufferInfo.mBuffer->getBuffer() : nullptr;
 }
 
-void Layer::getDrawingTransformMatrix(bool filteringEnabled, float outMatrix[16]) const {
-    sp<GraphicBuffer> buffer = getBuffer();
-    if (!buffer) {
-        ALOGE("Buffer should not be null!");
-        return;
-    }
-    GLConsumer::computeTransformMatrix(outMatrix, buffer->getWidth(), buffer->getHeight(),
-                                       buffer->getPixelFormat(), mBufferInfo.mCrop,
-                                       mBufferInfo.mTransform, filteringEnabled);
-}
-
 void Layer::setTransformHint(ui::Transform::RotationFlags displayTransformHint) {
     mTransformHint = getFixedTransformHint();
     if (mTransformHint == ui::Transform::ROT_INVALID) {
         mTransformHint = displayTransformHint;
     }
+    mSkipReportingTransformHint = false;
 }
 
 const std::shared_ptr<renderengine::ExternalTexture>& Layer::getExternalTexture() const {
@@ -4177,15 +3862,12 @@
 }
 
 bool Layer::setColor(const half3& color) {
-    if (mDrawingState.color.r == color.r && mDrawingState.color.g == color.g &&
-        mDrawingState.color.b == color.b) {
+    if (mDrawingState.color.rgb == color) {
         return false;
     }
 
     mDrawingState.sequence++;
-    mDrawingState.color.r = color.r;
-    mDrawingState.color.g = color.g;
-    mDrawingState.color.b = color.b;
+    mDrawingState.color.rgb = color;
     mDrawingState.modified = true;
     setTransactionFlags(eTransactionNeeded);
     return true;
@@ -4243,9 +3925,17 @@
     }
     snapshot->bufferSize = getBufferSize(mDrawingState);
     snapshot->externalTexture = mBufferInfo.mBuffer;
+    snapshot->hasReadyFrame = hasReadyFrame();
     preparePerFrameCompositionState();
 }
 
+void Layer::updateChildrenSnapshots(bool updateGeometry) {
+    for (const sp<Layer>& child : mDrawingChildren) {
+        child->updateSnapshot(updateGeometry);
+        child->updateChildrenSnapshots(updateGeometry);
+    }
+}
+
 void Layer::updateMetadataSnapshot(const LayerMetadata& parentMetadata) {
     mSnapshot->layerMetadata = parentMetadata;
     mSnapshot->layerMetadata.merge(mDrawingState.metadata);
@@ -4278,6 +3968,32 @@
     }
 }
 
+int Layer::getHwcCacheSlot(const client_cache_t& clientCacheId) {
+    return mHwcSlotGenerator->getHwcCacheSlot(clientCacheId);
+}
+
+LayerSnapshotGuard::LayerSnapshotGuard(Layer* layer) : mLayer(layer) {
+    if (mLayer) {
+        mLayer->mLayerFE->mSnapshot = std::move(mLayer->mSnapshot);
+    }
+}
+
+LayerSnapshotGuard::~LayerSnapshotGuard() {
+    if (mLayer) {
+        mLayer->mSnapshot = std::move(mLayer->mLayerFE->mSnapshot);
+    }
+}
+
+LayerSnapshotGuard::LayerSnapshotGuard(LayerSnapshotGuard&& other) : mLayer(other.mLayer) {
+    other.mLayer = nullptr;
+}
+
+LayerSnapshotGuard& LayerSnapshotGuard::operator=(LayerSnapshotGuard&& other) {
+    mLayer = other.mLayer;
+    other.mLayer = nullptr;
+    return *this;
+}
+
 // ---------------------------------------------------------------------------
 
 std::ostream& operator<<(std::ostream& stream, const Layer::FrameRate& rate) {
diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h
index 8ace812..f743896 100644
--- a/services/surfaceflinger/Layer.h
+++ b/services/surfaceflinger/Layer.h
@@ -52,6 +52,7 @@
 #include "DisplayHardware/HWComposer.h"
 #include "FrameTracker.h"
 #include "HwcSlotGenerator.h"
+#include "LayerFE.h"
 #include "LayerVector.h"
 #include "Scheduler/LayerInfo.h"
 #include "SurfaceFlinger.h"
@@ -81,24 +82,8 @@
 class SurfaceFrame;
 } // namespace frametimeline
 
-struct LayerCreationArgs {
-    LayerCreationArgs(SurfaceFlinger*, sp<Client>, std::string name, uint32_t flags, LayerMetadata);
-
-    SurfaceFlinger* flinger;
-    const sp<Client> client;
-    std::string name;
-    uint32_t flags;
-    LayerMetadata metadata;
-
-    pid_t callingPid;
-    uid_t callingUid;
-    uint32_t textureName;
-    std::optional<uint32_t> sequence = std::nullopt;
-    bool addToRoot = true;
-};
-
-class Layer : public virtual RefBase, compositionengine::LayerFE {
-    static std::atomic<int32_t> sSequence;
+class Layer : public virtual RefBase {
+public:
     // The following constants represent priority of the window. SF uses this information when
     // deciding which window has a priority when deciding about the refresh rate of the screen.
     // Priority 0 is considered the highest priority. -1 means that the priority is unset.
@@ -110,7 +95,6 @@
     // Windows that are not in focus, but voted for a specific mode ID.
     static constexpr int32_t PRIORITY_NOT_FOCUSED_WITH_MODE = 2;
 
-public:
     enum { // flags for doTransaction()
         eDontUpdateGeometryState = 0x00000001,
         eVisibleRegion = 0x00000002,
@@ -129,43 +113,6 @@
         inline bool operator!=(const Geometry& rhs) const { return !operator==(rhs); }
     };
 
-    struct RoundedCornerState {
-        RoundedCornerState() = default;
-        RoundedCornerState(const FloatRect& cropRect, const vec2& radius)
-              : cropRect(cropRect), radius(radius) {}
-
-        // Rounded rectangle in local layer coordinate space.
-        FloatRect cropRect = FloatRect();
-        // Radius of the rounded rectangle.
-        vec2 radius;
-        bool hasRoundedCorners() const { return radius.x > 0.0f && radius.y > 0.0f; }
-    };
-
-    // LayerSnapshot stores Layer state used by Composition Engine and Render Engine. Composition
-    // Engine uses a pointer to LayerSnapshot (as LayerFECompositionState*) and the LayerSettings
-    // passed to Render Engine are created using properties stored on this struct.
-    //
-    // TODO(b/238781169) Implement LayerFE as a separate subclass. Migrate LayerSnapshot to that
-    // LayerFE subclass.
-    struct LayerSnapshot : public compositionengine::LayerFECompositionState {
-        int32_t sequence;
-        std::string name;
-        uint32_t textureName;
-        bool contentOpaque;
-        RoundedCornerState roundedCorner;
-        StretchEffect stretchEffect;
-        FloatRect transformedBounds;
-        renderengine::ShadowSettings shadowSettings;
-        bool premultipliedAlpha;
-        bool isHdrY410;
-        bool bufferNeedsFiltering;
-        ui::Transform transform;
-        Rect bufferSize;
-        std::shared_ptr<renderengine::ExternalTexture> externalTexture;
-        LayerMetadata layerMetadata;
-        LayerMetadata relativeLayerMetadata;
-    };
-
     using FrameRate = scheduler::LayerInfo::FrameRate;
     using FrameRateCompatibility = scheduler::LayerInfo::FrameRateCompatibility;
 
@@ -199,6 +146,7 @@
         bool transformToDisplayInverse;
         Region transparentRegionHint;
         std::shared_ptr<renderengine::ExternalTexture> buffer;
+        int hwcBufferSlot;
         client_cache_t clientCacheId;
         sp<Fence> acquireFence;
         std::shared_ptr<FenceTime> acquireFenceTime;
@@ -279,46 +227,6 @@
         bool dimmingEnabled = true;
     };
 
-    /*
-     * Trivial class, used to ensure that mFlinger->onLayerDestroyed(mLayer)
-     * is called.
-     */
-    class LayerCleaner {
-        sp<SurfaceFlinger> mFlinger;
-        sp<Layer> mLayer;
-        BBinder* mHandle;
-
-    protected:
-        ~LayerCleaner() {
-            // destroy client resources
-            mFlinger->onHandleDestroyed(mHandle, mLayer);
-        }
-
-    public:
-        LayerCleaner(const sp<SurfaceFlinger>& flinger, const sp<Layer>& layer, BBinder* handle)
-              : mFlinger(flinger), mLayer(layer), mHandle(handle) {}
-    };
-
-    /*
-     * The layer handle is just a BBinder object passed to the client
-     * (remote process) -- we don't keep any reference on our side such that
-     * the dtor is called when the remote side let go of its reference.
-     *
-     * LayerCleaner ensures that mFlinger->onLayerDestroyed() is called for
-     * this layer when the handle is destroyed.
-     */
-    class Handle : public BBinder, public LayerCleaner {
-    public:
-        Handle(const sp<SurfaceFlinger>& flinger, const sp<Layer>& layer)
-              : LayerCleaner(flinger, layer, this), owner(layer) {}
-        const String16& getInterfaceDescriptor() const override { return kDescriptor; }
-
-        static const String16 kDescriptor;
-        wp<Layer> owner;
-    };
-
-    static wp<Layer> fromHandle(const sp<IBinder>& handle);
-
     explicit Layer(const LayerCreationArgs& args);
     virtual ~Layer();
 
@@ -373,7 +281,9 @@
     virtual bool setTrustedOverlay(bool);
     virtual bool setFlags(uint32_t flags, uint32_t mask);
     virtual bool setLayerStack(ui::LayerStack);
-    virtual ui::LayerStack getLayerStack() const;
+    virtual ui::LayerStack getLayerStack(
+            LayerVector::StateSet state = LayerVector::StateSet::Drawing) const;
+
     virtual bool setMetadata(const LayerMetadata& data);
     virtual void setChildrenDrawingParent(const sp<Layer>&);
     virtual bool reparent(const sp<IBinder>& newParentHandle);
@@ -388,7 +298,8 @@
     bool setBuffer(std::shared_ptr<renderengine::ExternalTexture>& /* buffer */,
                    const BufferData& /* bufferData */, nsecs_t /* postTime */,
                    nsecs_t /*desiredPresentTime*/, bool /*isAutoTimestamp*/,
-                   std::optional<nsecs_t> /* dequeueTime */, const FrameTimelineInfo& /*info*/);
+                   std::optional<nsecs_t> /* dequeueTime */, const FrameTimelineInfo& /*info*/,
+                   int /* hwcBufferSlot */);
     bool setDataspace(ui::Dataspace /*dataspace*/);
     bool setHdrMetadata(const HdrMetadata& /*hdrMetadata*/);
     bool setSurfaceDamageRegion(const Region& /*surfaceDamage*/);
@@ -413,7 +324,8 @@
     ui::Dataspace getDataSpace() const;
     ui::Dataspace getRequestedDataSpace() const;
 
-    virtual sp<compositionengine::LayerFE> getCompositionEngineLayerFE() const;
+    virtual sp<LayerFE> getCompositionEngineLayerFE() const;
+    virtual sp<LayerFE> copyCompositionEngineLayerFE() const;
 
     const LayerSnapshot* getLayerSnapshot() const;
     LayerSnapshot* editLayerSnapshot();
@@ -557,7 +469,7 @@
     // corner crop does not intersect with its own rounded corner crop.
     virtual RoundedCornerState getRoundedCornerState() const;
 
-    bool hasRoundedCorners() const override { return getRoundedCornerState().hasRoundedCorners(); }
+    bool hasRoundedCorners() const { return getRoundedCornerState().hasRoundedCorners(); }
 
     PixelFormat getPixelFormat() const;
     /**
@@ -598,24 +510,15 @@
     // implements compositionengine::LayerFE
     const compositionengine::LayerFECompositionState* getCompositionState() const;
     bool fenceHasSignaled() const;
-    // Called before composition. updatingOutputGeometryThisFrame is used by ARC++'s Layer subclass.
-    bool onPreComposition(nsecs_t refreshStartTime, bool updatingOutputGeometryThisFrame);
-    std::optional<compositionengine::LayerFE::LayerSettings> prepareClientComposition(
-            compositionengine::LayerFE::ClientCompositionTargetSettings&) const override;
+    bool onPreComposition(nsecs_t refreshStartTime);
     void onLayerDisplayed(ftl::SharedFuture<FenceResult>);
 
-    void setWasClientComposed(const sp<Fence>& fence) override {
+    void setWasClientComposed(const sp<Fence>& fence) {
         mLastClientCompositionFence = fence;
         mClearClientCompositionFenceOnLayerDisplayed = false;
     }
 
-    const LayerMetadata* getMetadata() const override { return &mSnapshot->layerMetadata; }
-
-    const LayerMetadata* getRelativeMetadata() const override {
-        return &mSnapshot->relativeLayerMetadata;
-    }
-
-    const char* getDebugName() const override;
+    const char* getDebugName() const;
 
     bool setShadowRadius(float shadowRadius);
 
@@ -640,7 +543,7 @@
     // Compute bounds for the layer and cache the results.
     void computeBounds(FloatRect parentBounds, ui::Transform parentTransform, float shadowRadius);
 
-    int32_t getSequence() const override { return sequence; }
+    int32_t getSequence() const { return sequence; }
 
     // For tracing.
     // TODO: Replace with raw buffer id from buffer metadata when that becomes available.
@@ -723,7 +626,7 @@
      * Sets display transform hint on BufferLayerConsumer.
      */
     void updateTransformHint(ui::Transform::RotationFlags);
-
+    void skipReportingTransformHint();
     inline const State& getDrawingState() const { return mDrawingState; }
     inline State& getDrawingState() { return mDrawingState; }
 
@@ -902,7 +805,7 @@
 
     // Updates the LayerSnapshot. This must be called prior to sending layer data to
     // CompositionEngine or RenderEngine (i.e. before calling CompositionEngine::present or
-    // Layer::prepareClientComposition).
+    // LayerFE::prepareClientComposition).
     //
     // TODO(b/238781169) Remove direct calls to RenderEngine::drawLayers that don't go through
     // CompositionEngine to create a single path for composing layers.
@@ -910,6 +813,7 @@
     void updateMetadataSnapshot(const LayerMetadata& parentMetadata);
     void updateRelativeMetadataSnapshot(const LayerMetadata& relativeLayerMetadata,
                                         std::unordered_set<Layer*>& visited);
+    int getHwcCacheSlot(const client_cache_t& clientCacheId);
 
 protected:
     // For unit tests
@@ -928,7 +832,6 @@
     void gatherBufferInfo();
     void onSurfaceFrameCreated(const std::shared_ptr<frametimeline::SurfaceFrame>&);
 
-    sp<compositionengine::LayerFE> asLayerFE() const;
     sp<Layer> getClonedFrom() { return mClonedFrom != nullptr ? mClonedFrom.promote() : nullptr; }
     bool isClone() { return mClonedFrom != nullptr; }
     bool isClonedFromAlive() { return getClonedFrom() != nullptr; }
@@ -941,12 +844,6 @@
     void addChildToDrawing(const sp<Layer>&);
     void updateClonedInputInfo(const std::map<sp<Layer>, sp<Layer>>& clonedLayersMap);
 
-    // Modifies the passed in layer settings to clear the contents. If the blackout flag is set,
-    // the settings clears the content with a solid black fill.
-    void prepareClearClientComposition(LayerFE::LayerSettings&, bool blackout) const;
-    void prepareShadowClientComposition(LayerFE::LayerSettings& caster,
-                                        const Rect& layerStackRect) const;
-
     void prepareBasicGeometryCompositionState();
     void prepareGeometryCompositionState();
     void prepareCursorCompositionState();
@@ -1102,10 +999,6 @@
     // Fills in the frame and transform info for the gui::WindowInfo.
     void fillInputFrameInfo(gui::WindowInfo&, const ui::Transform& screenToDisplay);
 
-    // Computes the transform matrix using the setFilteringEnabled to determine whether the
-    // transform matrix should be computed for use with bilinear filtering.
-    void getDrawingTransformMatrix(bool filteringEnabled, float outMatrix[16]) const;
-
     inline void tracePendingBufferCount(int32_t pendingBuffers);
 
     // Latch sideband stream and returns true if the dirty region should be updated.
@@ -1129,8 +1022,6 @@
                                    const sp<Fence>& releaseFence,
                                    uint32_t currentMaxAcquiredBufferCount);
 
-    std::optional<compositionengine::LayerFE::LayerSettings> prepareClientCompositionInternal(
-            compositionengine::LayerFE::ClientCompositionTargetSettings&) const;
     // Returns true if there is a valid color to fill.
     bool fillsColor() const;
     // Returns true if this layer has a blur value.
@@ -1140,13 +1031,13 @@
         return ((mSidebandStream != nullptr) || (mBufferInfo.mBuffer != nullptr));
     }
 
+    bool hasBufferOrSidebandStreamInDrawing() const {
+        return ((mDrawingState.sidebandStream != nullptr) || (mDrawingState.buffer != nullptr));
+    }
+
     bool hasSomethingToDraw() const { return hasEffect() || hasBufferOrSidebandStream(); }
-    void prepareBufferStateClientComposition(
-            compositionengine::LayerFE::LayerSettings&,
-            compositionengine::LayerFE::ClientCompositionTargetSettings&) const;
-    void prepareEffectsClientComposition(
-            compositionengine::LayerFE::LayerSettings&,
-            compositionengine::LayerFE::ClientCompositionTargetSettings&) const;
+
+    void updateChildrenSnapshots(bool updateGeometry);
 
     // Cached properties computed from drawing state
     // Effective transform taking into account parent transforms and any parent scaling, which is
@@ -1166,11 +1057,6 @@
 
     bool mGetHandleCalled = false;
 
-    // Tracks the process and user id of the caller when creating this layer
-    // to help debugging.
-    pid_t mCallingPid;
-    uid_t mCallingUid;
-
     // The current layer is a clone of mClonedFrom. This means that this layer will update it's
     // properties based on mClonedFrom. When mClonedFrom latches a new buffer for BufferLayers,
     // this layer will update it's buffer. When mClonedFrom updates it's drawing state, children,
@@ -1205,6 +1091,7 @@
     // Transform hint provided to the producer. This must be accessed holding
     // the mStateLock.
     ui::Transform::RotationFlags mTransformHint = ui::Transform::ROT_0;
+    bool mSkipReportingTransformHint = true;
 
     ReleaseCallbackId mPreviousReleaseCallbackId = ReleaseCallbackId::INVALID_ID;
     uint64_t mPreviousReleasedFrameNumber = 0;
@@ -1219,7 +1106,7 @@
 
     std::deque<std::shared_ptr<android::frametimeline::SurfaceFrame>> mPendingJankClassifications;
     // An upper bound on the number of SurfaceFrames in the pending classifications deque.
-    static constexpr int kPendingClassificationMaxSurfaceFrames = 25;
+    static constexpr int kPendingClassificationMaxSurfaceFrames = 50;
 
     const std::string mBlastTransactionName{"BufferTX - " + mName};
     // This integer is incremented everytime a buffer arrives at the server for this layer,
@@ -1239,7 +1126,33 @@
 
     sp<HwcSlotGenerator> mHwcSlotGenerator;
 
+    sp<LayerFE> mLayerFE;
     std::unique_ptr<LayerSnapshot> mSnapshot = std::make_unique<LayerSnapshot>();
+
+    friend class LayerSnapshotGuard;
+};
+
+// LayerSnapshotGuard manages the movement of LayerSnapshot between a Layer and its corresponding
+// LayerFE. This class must be used whenever LayerFEs are passed to CompositionEngine. Instances of
+// LayerSnapshotGuard should only be constructed on the main thread and should not be moved outside
+// the main thread.
+//
+// Moving the snapshot instead of sharing common state prevents use of LayerFE outside the main
+// thread by making errors obvious (i.e. use outside the main thread results in SEGFAULTs due to
+// nullptr dereference).
+class LayerSnapshotGuard {
+public:
+    LayerSnapshotGuard(Layer* layer) REQUIRES(kMainThreadContext);
+    ~LayerSnapshotGuard() REQUIRES(kMainThreadContext);
+
+    LayerSnapshotGuard(const LayerSnapshotGuard&) = delete;
+    LayerSnapshotGuard& operator=(const LayerSnapshotGuard&) = delete;
+
+    LayerSnapshotGuard(LayerSnapshotGuard&& other) REQUIRES(kMainThreadContext);
+    LayerSnapshotGuard& operator=(LayerSnapshotGuard&& other) REQUIRES(kMainThreadContext);
+
+private:
+    Layer* mLayer;
 };
 
 std::ostream& operator<<(std::ostream& stream, const Layer::FrameRate& rate);
diff --git a/services/surfaceflinger/LayerFE.cpp b/services/surfaceflinger/LayerFE.cpp
new file mode 100644
index 0000000..363adc6
--- /dev/null
+++ b/services/surfaceflinger/LayerFE.cpp
@@ -0,0 +1,386 @@
+/*
+ * 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.
+ */
+
+// #define LOG_NDEBUG 0
+#undef LOG_TAG
+#define LOG_TAG "LayerFE"
+#define ATRACE_TAG ATRACE_TAG_GRAPHICS
+
+#include <gui/GLConsumer.h>
+#include <gui/TraceUtils.h>
+#include <math/vec3.h>
+#include <system/window.h>
+#include <utils/Log.h>
+
+#include "DisplayDevice.h"
+#include "LayerFE.h"
+
+namespace android {
+
+namespace {
+constexpr float defaultMaxLuminance = 1000.0;
+
+constexpr mat4 inverseOrientation(uint32_t transform) {
+    const mat4 flipH(-1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1);
+    const mat4 flipV(1, 0, 0, 0, 0, -1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1);
+    const mat4 rot90(0, 1, 0, 0, -1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1);
+    mat4 tr;
+
+    if (transform & NATIVE_WINDOW_TRANSFORM_ROT_90) {
+        tr = tr * rot90;
+    }
+    if (transform & NATIVE_WINDOW_TRANSFORM_FLIP_H) {
+        tr = tr * flipH;
+    }
+    if (transform & NATIVE_WINDOW_TRANSFORM_FLIP_V) {
+        tr = tr * flipV;
+    }
+    return inverse(tr);
+}
+
+FloatRect reduce(const FloatRect& win, const Region& exclude) {
+    if (CC_LIKELY(exclude.isEmpty())) {
+        return win;
+    }
+    // Convert through Rect (by rounding) for lack of FloatRegion
+    return Region(Rect{win}).subtract(exclude).getBounds().toFloatRect();
+}
+
+// Computes the transform matrix using the setFilteringEnabled to determine whether the
+// transform matrix should be computed for use with bilinear filtering.
+void getDrawingTransformMatrix(const std::shared_ptr<renderengine::ExternalTexture>& buffer,
+                               Rect bufferCrop, uint32_t bufferTransform, bool filteringEnabled,
+                               float outMatrix[16]) {
+    if (!buffer) {
+        ALOGE("Buffer should not be null!");
+        return;
+    }
+    GLConsumer::computeTransformMatrix(outMatrix, static_cast<float>(buffer->getWidth()),
+                                       static_cast<float>(buffer->getHeight()),
+                                       buffer->getPixelFormat(), bufferCrop, bufferTransform,
+                                       filteringEnabled);
+}
+
+} // namespace
+
+LayerFE::LayerFE(const std::string& name) : mName(name) {}
+
+const compositionengine::LayerFECompositionState* LayerFE::getCompositionState() const {
+    return mSnapshot.get();
+}
+
+bool LayerFE::onPreComposition(nsecs_t refreshStartTime, bool) {
+    mCompositionResult.refreshStartTime = refreshStartTime;
+    return mSnapshot->hasReadyFrame;
+}
+
+std::optional<compositionengine::LayerFE::LayerSettings> LayerFE::prepareClientComposition(
+        compositionengine::LayerFE::ClientCompositionTargetSettings& targetSettings) const {
+    std::optional<compositionengine::LayerFE::LayerSettings> layerSettings =
+            prepareClientCompositionInternal(targetSettings);
+    // Nothing to render.
+    if (!layerSettings) {
+        return {};
+    }
+
+    // HWC requests to clear this layer.
+    if (targetSettings.clearContent) {
+        prepareClearClientComposition(*layerSettings, false /* blackout */);
+        return layerSettings;
+    }
+
+    // set the shadow for the layer if needed
+    prepareShadowClientComposition(*layerSettings, targetSettings.viewport);
+
+    return layerSettings;
+}
+
+std::optional<compositionengine::LayerFE::LayerSettings> LayerFE::prepareClientCompositionInternal(
+        compositionengine::LayerFE::ClientCompositionTargetSettings& targetSettings) const {
+    ATRACE_CALL();
+    compositionengine::LayerFE::LayerSettings layerSettings;
+    layerSettings.geometry.boundaries =
+            reduce(mSnapshot->geomLayerBounds, mSnapshot->transparentRegionHint);
+    layerSettings.geometry.positionTransform = mSnapshot->geomLayerTransform.asMatrix4();
+
+    // skip drawing content if the targetSettings indicate the content will be occluded
+    const bool drawContent = targetSettings.realContentIsVisible || targetSettings.clearContent;
+    layerSettings.skipContentDraw = !drawContent;
+
+    if (!mSnapshot->colorTransformIsIdentity) {
+        layerSettings.colorTransform = mSnapshot->colorTransform;
+    }
+
+    const auto& roundedCornerState = mSnapshot->roundedCorner;
+    layerSettings.geometry.roundedCornersRadius = roundedCornerState.radius;
+    layerSettings.geometry.roundedCornersCrop = roundedCornerState.cropRect;
+
+    layerSettings.alpha = mSnapshot->alpha;
+    layerSettings.sourceDataspace = mSnapshot->dataspace;
+
+    // Override the dataspace transfer from 170M to sRGB if the device configuration requests this.
+    // We do this here instead of in buffer info so that dumpsys can still report layers that are
+    // using the 170M transfer.
+    if (targetSettings.treat170mAsSrgb &&
+        (layerSettings.sourceDataspace & HAL_DATASPACE_TRANSFER_MASK) ==
+                HAL_DATASPACE_TRANSFER_SMPTE_170M) {
+        layerSettings.sourceDataspace = static_cast<ui::Dataspace>(
+                (layerSettings.sourceDataspace & HAL_DATASPACE_STANDARD_MASK) |
+                (layerSettings.sourceDataspace & HAL_DATASPACE_RANGE_MASK) |
+                HAL_DATASPACE_TRANSFER_SRGB);
+    }
+
+    layerSettings.whitePointNits = targetSettings.whitePointNits;
+    switch (targetSettings.blurSetting) {
+        case LayerFE::ClientCompositionTargetSettings::BlurSetting::Enabled:
+            layerSettings.backgroundBlurRadius = mSnapshot->backgroundBlurRadius;
+            layerSettings.blurRegions = mSnapshot->blurRegions;
+            layerSettings.blurRegionTransform = mSnapshot->blurRegionTransform.asMatrix4();
+            break;
+        case LayerFE::ClientCompositionTargetSettings::BlurSetting::BackgroundBlurOnly:
+            layerSettings.backgroundBlurRadius = mSnapshot->backgroundBlurRadius;
+            break;
+        case LayerFE::ClientCompositionTargetSettings::BlurSetting::BlurRegionsOnly:
+            layerSettings.blurRegions = mSnapshot->blurRegions;
+            layerSettings.blurRegionTransform = mSnapshot->blurRegionTransform.asMatrix4();
+            break;
+        case LayerFE::ClientCompositionTargetSettings::BlurSetting::Disabled:
+        default:
+            break;
+    }
+    layerSettings.stretchEffect = mSnapshot->stretchEffect;
+    // Record the name of the layer for debugging further down the stack.
+    layerSettings.name = mSnapshot->name;
+
+    if (hasEffect() && !hasBufferOrSidebandStream()) {
+        prepareEffectsClientComposition(layerSettings, targetSettings);
+        return layerSettings;
+    }
+
+    prepareBufferStateClientComposition(layerSettings, targetSettings);
+    return layerSettings;
+}
+
+void LayerFE::prepareClearClientComposition(LayerFE::LayerSettings& layerSettings,
+                                            bool blackout) const {
+    layerSettings.source.buffer.buffer = nullptr;
+    layerSettings.source.solidColor = half3(0.0f, 0.0f, 0.0f);
+    layerSettings.disableBlending = true;
+    layerSettings.bufferId = 0;
+    layerSettings.frameNumber = 0;
+
+    // If layer is blacked out, force alpha to 1 so that we draw a black color layer.
+    layerSettings.alpha = blackout ? 1.0f : 0.0f;
+    layerSettings.name = mSnapshot->name;
+}
+
+void LayerFE::prepareEffectsClientComposition(
+        compositionengine::LayerFE::LayerSettings& layerSettings,
+        compositionengine::LayerFE::ClientCompositionTargetSettings& targetSettings) const {
+    // If fill bounds are occluded or the fill color is invalid skip the fill settings.
+    if (targetSettings.realContentIsVisible && fillsColor()) {
+        // Set color for color fill settings.
+        layerSettings.source.solidColor = mSnapshot->color.rgb;
+    } else if (hasBlur() || drawShadows()) {
+        layerSettings.skipContentDraw = true;
+    }
+}
+
+void LayerFE::prepareBufferStateClientComposition(
+        compositionengine::LayerFE::LayerSettings& layerSettings,
+        compositionengine::LayerFE::ClientCompositionTargetSettings& targetSettings) const {
+    ATRACE_CALL();
+    if (CC_UNLIKELY(!mSnapshot->externalTexture)) {
+        // If there is no buffer for the layer or we have sidebandstream where there is no
+        // activeBuffer, then we need to return LayerSettings.
+        return;
+    }
+    const bool blackOutLayer =
+            (mSnapshot->hasProtectedContent && !targetSettings.supportsProtectedContent) ||
+            ((mSnapshot->isSecure || mSnapshot->hasProtectedContent) && !targetSettings.isSecure);
+    const bool bufferCanBeUsedAsHwTexture =
+            mSnapshot->externalTexture->getUsage() & GraphicBuffer::USAGE_HW_TEXTURE;
+    if (blackOutLayer || !bufferCanBeUsedAsHwTexture) {
+        ALOGE_IF(!bufferCanBeUsedAsHwTexture, "%s is blacked out as buffer is not gpu readable",
+                 mSnapshot->name.c_str());
+        prepareClearClientComposition(layerSettings, true /* blackout */);
+        return;
+    }
+
+    layerSettings.source.buffer.buffer = mSnapshot->externalTexture;
+    layerSettings.source.buffer.isOpaque = mSnapshot->contentOpaque;
+    layerSettings.source.buffer.fence = mSnapshot->acquireFence;
+    layerSettings.source.buffer.textureName = mSnapshot->textureName;
+    layerSettings.source.buffer.usePremultipliedAlpha = mSnapshot->premultipliedAlpha;
+    layerSettings.source.buffer.isY410BT2020 = mSnapshot->isHdrY410;
+    bool hasSmpte2086 = mSnapshot->hdrMetadata.validTypes & HdrMetadata::SMPTE2086;
+    bool hasCta861_3 = mSnapshot->hdrMetadata.validTypes & HdrMetadata::CTA861_3;
+    float maxLuminance = 0.f;
+    if (hasSmpte2086 && hasCta861_3) {
+        maxLuminance = std::min(mSnapshot->hdrMetadata.smpte2086.maxLuminance,
+                                mSnapshot->hdrMetadata.cta8613.maxContentLightLevel);
+    } else if (hasSmpte2086) {
+        maxLuminance = mSnapshot->hdrMetadata.smpte2086.maxLuminance;
+    } else if (hasCta861_3) {
+        maxLuminance = mSnapshot->hdrMetadata.cta8613.maxContentLightLevel;
+    } else {
+        switch (layerSettings.sourceDataspace & HAL_DATASPACE_TRANSFER_MASK) {
+            case HAL_DATASPACE_TRANSFER_ST2084:
+            case HAL_DATASPACE_TRANSFER_HLG:
+                // Behavior-match previous releases for HDR content
+                maxLuminance = defaultMaxLuminance;
+                break;
+        }
+    }
+    layerSettings.source.buffer.maxLuminanceNits = maxLuminance;
+    layerSettings.frameNumber = mSnapshot->frameNumber;
+    layerSettings.bufferId = mSnapshot->externalTexture->getId();
+
+    const bool useFiltering = targetSettings.needsFiltering ||
+            mSnapshot->geomLayerTransform.needsBilinearFiltering() ||
+            mSnapshot->bufferNeedsFiltering;
+
+    // Query the texture matrix given our current filtering mode.
+    float textureMatrix[16];
+    getDrawingTransformMatrix(layerSettings.source.buffer.buffer, mSnapshot->geomContentCrop,
+                              mSnapshot->geomBufferTransform, useFiltering, textureMatrix);
+
+    if (mSnapshot->geomBufferUsesDisplayInverseTransform) {
+        /*
+         * the code below applies the primary display's inverse transform to
+         * the texture transform
+         */
+        uint32_t transform = DisplayDevice::getPrimaryDisplayRotationFlags();
+        mat4 tr = inverseOrientation(transform);
+
+        /**
+         * TODO(b/36727915): This is basically a hack.
+         *
+         * Ensure that regardless of the parent transformation,
+         * this buffer is always transformed from native display
+         * orientation to display orientation. For example, in the case
+         * of a camera where the buffer remains in native orientation,
+         * we want the pixels to always be upright.
+         */
+        const auto parentTransform = mSnapshot->transform;
+        tr = tr * inverseOrientation(parentTransform.getOrientation());
+
+        // and finally apply it to the original texture matrix
+        const mat4 texTransform(mat4(static_cast<const float*>(textureMatrix)) * tr);
+        memcpy(textureMatrix, texTransform.asArray(), sizeof(textureMatrix));
+    }
+
+    const Rect win{layerSettings.geometry.boundaries};
+    float bufferWidth = static_cast<float>(mSnapshot->bufferSize.getWidth());
+    float bufferHeight = static_cast<float>(mSnapshot->bufferSize.getHeight());
+
+    // Layers can have a "buffer size" of [0, 0, -1, -1] when no display frame has
+    // been set and there is no parent layer bounds. In that case, the scale is meaningless so
+    // ignore them.
+    if (!mSnapshot->bufferSize.isValid()) {
+        bufferWidth = float(win.right) - float(win.left);
+        bufferHeight = float(win.bottom) - float(win.top);
+    }
+
+    const float scaleHeight = (float(win.bottom) - float(win.top)) / bufferHeight;
+    const float scaleWidth = (float(win.right) - float(win.left)) / bufferWidth;
+    const float translateY = float(win.top) / bufferHeight;
+    const float translateX = float(win.left) / bufferWidth;
+
+    // Flip y-coordinates because GLConsumer expects OpenGL convention.
+    mat4 tr = mat4::translate(vec4(.5f, .5f, 0.f, 1.f)) * mat4::scale(vec4(1.f, -1.f, 1.f, 1.f)) *
+            mat4::translate(vec4(-.5f, -.5f, 0.f, 1.f)) *
+            mat4::translate(vec4(translateX, translateY, 0.f, 1.f)) *
+            mat4::scale(vec4(scaleWidth, scaleHeight, 1.0f, 1.0f));
+
+    layerSettings.source.buffer.useTextureFiltering = useFiltering;
+    layerSettings.source.buffer.textureTransform =
+            mat4(static_cast<const float*>(textureMatrix)) * tr;
+
+    return;
+}
+
+void LayerFE::prepareShadowClientComposition(LayerFE::LayerSettings& caster,
+                                             const Rect& layerStackRect) const {
+    renderengine::ShadowSettings state = mSnapshot->shadowSettings;
+    if (state.length <= 0.f || (state.ambientColor.a <= 0.f && state.spotColor.a <= 0.f)) {
+        return;
+    }
+
+    // Shift the spot light x-position to the middle of the display and then
+    // offset it by casting layer's screen pos.
+    state.lightPos.x =
+            (static_cast<float>(layerStackRect.width()) / 2.f) - mSnapshot->transformedBounds.left;
+    state.lightPos.y -= mSnapshot->transformedBounds.top;
+    caster.shadow = state;
+}
+
+void LayerFE::onLayerDisplayed(ftl::SharedFuture<FenceResult> futureFenceResult) {
+    mCompositionResult.releaseFences.emplace_back(std::move(futureFenceResult));
+}
+
+CompositionResult&& LayerFE::stealCompositionResult() {
+    return std::move(mCompositionResult);
+}
+
+const char* LayerFE::getDebugName() const {
+    return mName.c_str();
+}
+
+const LayerMetadata* LayerFE::getMetadata() const {
+    return &mSnapshot->layerMetadata;
+}
+
+const LayerMetadata* LayerFE::getRelativeMetadata() const {
+    return &mSnapshot->relativeLayerMetadata;
+}
+
+int32_t LayerFE::getSequence() const {
+    return mSnapshot->sequence;
+}
+
+bool LayerFE::hasRoundedCorners() const {
+    return mSnapshot->roundedCorner.hasRoundedCorners();
+}
+
+void LayerFE::setWasClientComposed(const sp<Fence>& fence) {
+    mCompositionResult.lastClientCompositionFence = fence;
+}
+
+bool LayerFE::hasBufferOrSidebandStream() const {
+    return mSnapshot->externalTexture || mSnapshot->sidebandStream;
+}
+
+bool LayerFE::fillsColor() const {
+    return mSnapshot->color.r >= 0.0_hf && mSnapshot->color.g >= 0.0_hf &&
+            mSnapshot->color.b >= 0.0_hf;
+}
+
+bool LayerFE::hasBlur() const {
+    return mSnapshot->backgroundBlurRadius > 0 || mSnapshot->blurRegions.size() > 0;
+}
+
+bool LayerFE::drawShadows() const {
+    return mSnapshot->shadowSettings.length > 0.f &&
+            (mSnapshot->shadowSettings.ambientColor.a > 0 ||
+             mSnapshot->shadowSettings.spotColor.a > 0);
+};
+
+const sp<GraphicBuffer> LayerFE::getBuffer() const {
+    return mSnapshot->externalTexture ? mSnapshot->externalTexture->getBuffer() : nullptr;
+}
+
+} // namespace android
diff --git a/services/surfaceflinger/LayerFE.h b/services/surfaceflinger/LayerFE.h
new file mode 100644
index 0000000..822bcb7
--- /dev/null
+++ b/services/surfaceflinger/LayerFE.h
@@ -0,0 +1,119 @@
+/*
+ * 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 <gui/LayerMetadata.h>
+
+#include "compositionengine/LayerFE.h"
+#include "compositionengine/LayerFECompositionState.h"
+#include "renderengine/LayerSettings.h"
+
+namespace android {
+struct RoundedCornerState {
+    RoundedCornerState() = default;
+    RoundedCornerState(const FloatRect& cropRect, const vec2& radius)
+          : cropRect(cropRect), radius(radius) {}
+
+    // Rounded rectangle in local layer coordinate space.
+    FloatRect cropRect = FloatRect();
+    // Radius of the rounded rectangle.
+    vec2 radius;
+    bool hasRoundedCorners() const { return radius.x > 0.0f && radius.y > 0.0f; }
+};
+
+// LayerSnapshot stores Layer state used by CompositionEngine and RenderEngine. Composition
+// Engine uses a pointer to LayerSnapshot (as LayerFECompositionState*) and the LayerSettings
+// passed to Render Engine are created using properties stored on this struct.
+struct LayerSnapshot : public compositionengine::LayerFECompositionState {
+    int32_t sequence;
+    std::string name;
+    uint32_t textureName;
+    bool contentOpaque;
+    RoundedCornerState roundedCorner;
+    StretchEffect stretchEffect;
+    FloatRect transformedBounds;
+    renderengine::ShadowSettings shadowSettings;
+    bool premultipliedAlpha;
+    bool isHdrY410;
+    bool bufferNeedsFiltering;
+    ui::Transform transform;
+    Rect bufferSize;
+    std::shared_ptr<renderengine::ExternalTexture> externalTexture;
+    gui::LayerMetadata layerMetadata;
+    gui::LayerMetadata relativeLayerMetadata;
+    bool contentDirty;
+    bool hasReadyFrame;
+    ui::Transform blurRegionTransform;
+};
+
+struct CompositionResult {
+    // TODO(b/238781169) update CE to no longer pass refreshStartTime to LayerFE::onPreComposition
+    // and remove this field.
+    nsecs_t refreshStartTime = 0;
+    std::vector<ftl::SharedFuture<FenceResult>> releaseFences;
+    sp<Fence> lastClientCompositionFence = nullptr;
+};
+
+class LayerFE : public virtual RefBase, public virtual compositionengine::LayerFE {
+public:
+    LayerFE(const std::string& name);
+
+    // compositionengine::LayerFE overrides
+    const compositionengine::LayerFECompositionState* getCompositionState() const override;
+    bool onPreComposition(nsecs_t refreshStartTime, bool updatingOutputGeometryThisFrame) override;
+    void onLayerDisplayed(ftl::SharedFuture<FenceResult>) override;
+    const char* getDebugName() const override;
+    int32_t getSequence() const override;
+    bool hasRoundedCorners() const override;
+    void setWasClientComposed(const sp<Fence>&) override;
+    const gui::LayerMetadata* getMetadata() const override;
+    const gui::LayerMetadata* getRelativeMetadata() const override;
+    std::optional<compositionengine::LayerFE::LayerSettings> prepareClientComposition(
+            compositionengine::LayerFE::ClientCompositionTargetSettings&) const;
+    CompositionResult&& stealCompositionResult();
+
+    std::unique_ptr<LayerSnapshot> mSnapshot;
+
+private:
+    std::optional<compositionengine::LayerFE::LayerSettings> prepareClientCompositionInternal(
+            compositionengine::LayerFE::ClientCompositionTargetSettings&) const;
+    // Modifies the passed in layer settings to clear the contents. If the blackout flag is set,
+    // the settings clears the content with a solid black fill.
+    void prepareClearClientComposition(LayerFE::LayerSettings&, bool blackout) const;
+    void prepareShadowClientComposition(LayerFE::LayerSettings& caster,
+                                        const Rect& layerStackRect) const;
+    void prepareBufferStateClientComposition(
+            compositionengine::LayerFE::LayerSettings&,
+            compositionengine::LayerFE::ClientCompositionTargetSettings&) const;
+    void prepareEffectsClientComposition(
+            compositionengine::LayerFE::LayerSettings&,
+            compositionengine::LayerFE::ClientCompositionTargetSettings&) const;
+
+    bool hasEffect() const { return fillsColor() || drawShadows() || hasBlur(); }
+    bool hasBufferOrSidebandStream() const;
+
+    bool fillsColor() const;
+    bool hasBlur() const;
+    bool drawShadows() const;
+
+    const sp<GraphicBuffer> getBuffer() const;
+
+    CompositionResult mCompositionResult;
+    std::string mName;
+};
+
+} // namespace android
diff --git a/services/surfaceflinger/LayerRenderArea.cpp b/services/surfaceflinger/LayerRenderArea.cpp
index 6bc7dc1..554fae4 100644
--- a/services/surfaceflinger/LayerRenderArea.cpp
+++ b/services/surfaceflinger/LayerRenderArea.cpp
@@ -18,6 +18,7 @@
 #include <ui/Transform.h>
 
 #include "DisplayDevice.h"
+#include "FrontEnd/LayerCreationArgs.h"
 #include "Layer.h"
 #include "LayerRenderArea.h"
 #include "SurfaceFlinger.h"
@@ -30,6 +31,7 @@
         // Compute and cache the bounds for the new parent layer.
         newParent->computeBounds(drawingBounds.toFloatRect(), ui::Transform(),
             0.f /* shadowRadius */);
+        newParent->updateSnapshot(true /* updateGeometry */);
         oldParent->setChildrenDrawingParent(newParent);
 };
 
diff --git a/services/surfaceflinger/RefreshRateOverlay.cpp b/services/surfaceflinger/RefreshRateOverlay.cpp
index 9b19afb..7aa7e17 100644
--- a/services/surfaceflinger/RefreshRateOverlay.cpp
+++ b/services/surfaceflinger/RefreshRateOverlay.cpp
@@ -42,8 +42,8 @@
 constexpr int kDigitHeight = 100;
 constexpr int kDigitSpace = 16;
 
-// Layout is digit, space, digit, space, digit, space, spinner.
-constexpr int kBufferWidth = 4 * kDigitWidth + 3 * kDigitSpace;
+constexpr int kMaxDigits = /*displayFps*/ 3 + /*renderFps*/ 3 + /*spinner*/ 1;
+constexpr int kBufferWidth = kMaxDigits * kDigitWidth + (kMaxDigits - 1) * kDigitSpace;
 constexpr int kBufferHeight = kDigitHeight;
 
 SurfaceComposerClient::Transaction createTransaction(const sp<SurfaceControl>& surface) {
@@ -121,16 +121,10 @@
         drawSegment(Segment::Bottom, left, color, canvas);
 }
 
-auto RefreshRateOverlay::SevenSegmentDrawer::draw(int number, SkColor color,
+auto RefreshRateOverlay::SevenSegmentDrawer::draw(int displayFps, int renderFps, SkColor color,
                                                   ui::Transform::RotationFlags rotation,
-                                                  bool showSpinner) -> Buffers {
-    if (number < 0 || number > 1000) return {};
-
-    const auto hundreds = number / 100;
-    const auto tens = (number / 10) % 10;
-    const auto ones = number % 10;
-
-    const size_t loopCount = showSpinner ? 6 : 1;
+                                                  ftl::Flags<Features> features) -> Buffers {
+    const size_t loopCount = features.test(Features::Spinner) ? 6 : 1;
 
     Buffers buffers;
     buffers.reserve(loopCount);
@@ -169,20 +163,9 @@
         canvas->setMatrix(canvasTransform);
 
         int left = 0;
-        if (hundreds != 0) {
-            drawDigit(hundreds, left, color, *canvas);
-        }
-        left += kDigitWidth + kDigitSpace;
-
-        if (tens != 0) {
-            drawDigit(tens, left, color, *canvas);
-        }
-        left += kDigitWidth + kDigitSpace;
-
-        drawDigit(ones, left, color, *canvas);
-        left += kDigitWidth + kDigitSpace;
-
-        if (showSpinner) {
+        drawNumber(displayFps, left, color, *canvas);
+        left += 3 * (kDigitWidth + kDigitSpace);
+        if (features.test(Features::Spinner)) {
             switch (i) {
                 case 0:
                     drawSegment(Segment::Upper, left, color, *canvas);
@@ -205,6 +188,13 @@
             }
         }
 
+        left += kDigitWidth + kDigitSpace;
+
+        if (features.test(Features::RenderRate)) {
+            drawNumber(renderFps, left, color, *canvas);
+        }
+        left += 3 * (kDigitWidth + kDigitSpace);
+
         void* pixels = nullptr;
         buffer->lock(GRALLOC_USAGE_SW_WRITE_RARELY, reinterpret_cast<void**>(&pixels));
 
@@ -219,6 +209,23 @@
     return buffers;
 }
 
+void RefreshRateOverlay::SevenSegmentDrawer::drawNumber(int number, int left, SkColor color,
+                                                        SkCanvas& canvas) {
+    if (number < 0 || number >= 1000) return;
+
+    if (number >= 100) {
+        drawDigit(number / 100, left, color, canvas);
+    }
+    left += kDigitWidth + kDigitSpace;
+
+    if (number >= 10) {
+        drawDigit((number / 10) % 10, left, color, canvas);
+    }
+    left += kDigitWidth + kDigitSpace;
+
+    drawDigit(number % 10, left, color, canvas);
+}
+
 std::unique_ptr<SurfaceControlHolder> createSurfaceControlHolder() {
     sp<SurfaceControl> surfaceControl =
             SurfaceComposerClient::getDefault()
@@ -228,10 +235,8 @@
     return std::make_unique<SurfaceControlHolder>(std::move(surfaceControl));
 }
 
-RefreshRateOverlay::RefreshRateOverlay(FpsRange fpsRange, bool showSpinner)
-      : mFpsRange(fpsRange),
-        mShowSpinner(showSpinner),
-        mSurfaceControl(createSurfaceControlHolder()) {
+RefreshRateOverlay::RefreshRateOverlay(FpsRange fpsRange, ftl::Flags<Features> features)
+      : mFpsRange(fpsRange), mFeatures(features), mSurfaceControl(createSurfaceControlHolder()) {
     if (!mSurfaceControl) {
         ALOGE("%s: Failed to create buffer state layer", __func__);
         return;
@@ -243,10 +248,15 @@
             .apply();
 }
 
-auto RefreshRateOverlay::getOrCreateBuffers(Fps fps) -> const Buffers& {
+auto RefreshRateOverlay::getOrCreateBuffers(Fps displayFps, Fps renderFps) -> const Buffers& {
     static const Buffers kNoBuffers;
     if (!mSurfaceControl) return kNoBuffers;
 
+    // avoid caching different render rates if RenderRate is anyway not visible
+    if (!mFeatures.test(Features::RenderRate)) {
+        renderFps = 0_Hz;
+    }
+
     const auto transformHint =
             static_cast<ui::Transform::RotationFlags>(mSurfaceControl->get()->getTransformHint());
 
@@ -266,17 +276,20 @@
             .setTransform(mSurfaceControl->get(), transform)
             .apply();
 
-    BufferCache::const_iterator it = mBufferCache.find({fps.getIntValue(), transformHint});
+    BufferCache::const_iterator it =
+            mBufferCache.find({displayFps.getIntValue(), renderFps.getIntValue(), transformHint});
     if (it == mBufferCache.end()) {
         const int minFps = mFpsRange.min.getIntValue();
         const int maxFps = mFpsRange.max.getIntValue();
 
-        // Clamp to the range. The current fps may be outside of this range if the display has
-        // changed its set of supported refresh rates.
-        const int intFps = std::clamp(fps.getIntValue(), minFps, maxFps);
+        // Clamp to the range. The current displayFps may be outside of this range if the display
+        // has changed its set of supported refresh rates.
+        const int displayIntFps = std::clamp(displayFps.getIntValue(), minFps, maxFps);
+        const int renderIntFps = renderFps.getIntValue();
 
         // Ensure non-zero range to avoid division by zero.
-        const float fpsScale = static_cast<float>(intFps - minFps) / std::max(1, maxFps - minFps);
+        const float fpsScale =
+                static_cast<float>(displayIntFps - minFps) / std::max(1, maxFps - minFps);
 
         constexpr SkColor kMinFpsColor = SK_ColorRED;
         constexpr SkColor kMaxFpsColor = SK_ColorGREEN;
@@ -292,8 +305,11 @@
 
         const SkColor color = colorBase.toSkColor();
 
-        auto buffers = SevenSegmentDrawer::draw(intFps, color, transformHint, mShowSpinner);
-        it = mBufferCache.try_emplace({intFps, transformHint}, std::move(buffers)).first;
+        auto buffers = SevenSegmentDrawer::draw(displayIntFps, renderIntFps, color, transformHint,
+                                                mFeatures);
+        it = mBufferCache
+                     .try_emplace({displayIntFps, renderIntFps, transformHint}, std::move(buffers))
+                     .first;
     }
 
     return it->second;
@@ -303,7 +319,7 @@
     constexpr int32_t kMaxWidth = 1000;
     const auto width = std::min({kMaxWidth, viewport.width, viewport.height});
     const auto height = 2 * width;
-    Rect frame((3 * width) >> 4, height >> 5);
+    Rect frame((5 * width) >> 4, height >> 5);
     frame.offsetBy(width >> 5, height >> 4);
 
     createTransaction(mSurfaceControl->get())
@@ -317,16 +333,17 @@
     createTransaction(mSurfaceControl->get()).setLayerStack(mSurfaceControl->get(), stack).apply();
 }
 
-void RefreshRateOverlay::changeRefreshRate(Fps fps) {
-    mCurrentFps = fps;
-    const auto buffer = getOrCreateBuffers(fps)[mFrame];
+void RefreshRateOverlay::changeRefreshRate(Fps displayFps, Fps renderFps) {
+    mDisplayFps = displayFps;
+    mRenderFps = renderFps;
+    const auto buffer = getOrCreateBuffers(displayFps, renderFps)[mFrame];
     createTransaction(mSurfaceControl->get()).setBuffer(mSurfaceControl->get(), buffer).apply();
 }
 
 void RefreshRateOverlay::animate() {
-    if (!mShowSpinner || !mCurrentFps) return;
+    if (!mFeatures.test(Features::Spinner) || !mDisplayFps) return;
 
-    const auto& buffers = getOrCreateBuffers(*mCurrentFps);
+    const auto& buffers = getOrCreateBuffers(*mDisplayFps, *mRenderFps);
     mFrame = (mFrame + 1) % buffers.size();
     const auto buffer = buffers[mFrame];
     createTransaction(mSurfaceControl->get()).setBuffer(mSurfaceControl->get(), buffer).apply();
diff --git a/services/surfaceflinger/RefreshRateOverlay.h b/services/surfaceflinger/RefreshRateOverlay.h
index a2966e6..d6f828f 100644
--- a/services/surfaceflinger/RefreshRateOverlay.h
+++ b/services/surfaceflinger/RefreshRateOverlay.h
@@ -19,6 +19,7 @@
 #include <SkColor.h>
 #include <vector>
 
+#include <ftl/flags.h>
 #include <ftl/small_map.h>
 #include <ui/LayerStack.h>
 #include <ui/Size.h>
@@ -50,11 +51,16 @@
 
 class RefreshRateOverlay {
 public:
-    RefreshRateOverlay(FpsRange, bool showSpinner);
+    enum class Features {
+        Spinner = 1 << 0,
+        RenderRate = 1 << 1,
+    };
+
+    RefreshRateOverlay(FpsRange, ftl::Flags<Features>);
 
     void setLayerStack(ui::LayerStack);
     void setViewport(ui::Size);
-    void changeRefreshRate(Fps);
+    void changeRefreshRate(Fps, Fps);
     void animate();
 
 private:
@@ -62,32 +68,39 @@
 
     class SevenSegmentDrawer {
     public:
-        static Buffers draw(int number, SkColor, ui::Transform::RotationFlags, bool showSpinner);
+        static Buffers draw(int displayFps, int renderFps, SkColor, ui::Transform::RotationFlags,
+                            ftl::Flags<Features>);
 
     private:
         enum class Segment { Upper, UpperLeft, UpperRight, Middle, LowerLeft, LowerRight, Bottom };
 
         static void drawSegment(Segment, int left, SkColor, SkCanvas&);
         static void drawDigit(int digit, int left, SkColor, SkCanvas&);
+        static void drawNumber(int number, int left, SkColor, SkCanvas&);
     };
 
-    const Buffers& getOrCreateBuffers(Fps);
+    const Buffers& getOrCreateBuffers(Fps, Fps);
 
     struct Key {
-        int fps;
+        int displayFps;
+        int renderFps;
         ui::Transform::RotationFlags flags;
 
-        bool operator==(Key other) const { return fps == other.fps && flags == other.flags; }
+        bool operator==(Key other) const {
+            return displayFps == other.displayFps && renderFps == other.renderFps &&
+                    flags == other.flags;
+        }
     };
 
     using BufferCache = ftl::SmallMap<Key, Buffers, 9>;
     BufferCache mBufferCache;
 
-    std::optional<Fps> mCurrentFps;
+    std::optional<Fps> mDisplayFps;
+    std::optional<Fps> mRenderFps;
     size_t mFrame = 0;
 
     const FpsRange mFpsRange; // For color interpolation.
-    const bool mShowSpinner;
+    const ftl::Flags<Features> mFeatures;
 
     const std::unique_ptr<SurfaceControlHolder> mSurfaceControl;
 };
diff --git a/services/surfaceflinger/RegionSamplingThread.cpp b/services/surfaceflinger/RegionSamplingThread.cpp
index 8dd3b0f..6e64e0a 100644
--- a/services/surfaceflinger/RegionSamplingThread.cpp
+++ b/services/surfaceflinger/RegionSamplingThread.cpp
@@ -40,6 +40,7 @@
 
 #include "DisplayDevice.h"
 #include "DisplayRenderArea.h"
+#include "FrontEnd/LayerCreationArgs.h"
 #include "Layer.h"
 #include "Scheduler/VsyncController.h"
 #include "SurfaceFlinger.h"
@@ -129,12 +130,12 @@
     }
 }
 
-void RegionSamplingThread::addListener(const Rect& samplingArea, const wp<Layer>& stopLayer,
+void RegionSamplingThread::addListener(const Rect& samplingArea, uint32_t stopLayerId,
                                        const sp<IRegionSamplingListener>& listener) {
     sp<IBinder> asBinder = IInterface::asBinder(listener);
     asBinder->linkToDeath(sp<DeathRecipient>::fromExisting(this));
     std::lock_guard lock(mSamplingMutex);
-    mDescriptors.emplace(wp<IBinder>(asBinder), Descriptor{samplingArea, stopLayer, listener});
+    mDescriptors.emplace(wp<IBinder>(asBinder), Descriptor{samplingArea, stopLayerId, listener});
 }
 
 void RegionSamplingThread::removeListener(const sp<IRegionSamplingListener>& listener) {
@@ -291,8 +292,8 @@
             if (stopLayerFound) return;
 
             // Likewise if we just found a stop layer, set the flag and abort
-            for (const auto& [area, stopLayer, listener] : descriptors) {
-                if (layer == stopLayer.promote().get()) {
+            for (const auto& [area, stopLayerId, listener] : descriptors) {
+                if (stopLayerId != UNASSIGNED_LAYER_ID && layer->getSequence() == stopLayerId) {
                     stopLayerFound = true;
                     return;
                 }
diff --git a/services/surfaceflinger/RegionSamplingThread.h b/services/surfaceflinger/RegionSamplingThread.h
index 686b4b1..b62b15c 100644
--- a/services/surfaceflinger/RegionSamplingThread.h
+++ b/services/surfaceflinger/RegionSamplingThread.h
@@ -26,6 +26,7 @@
 
 #include <chrono>
 #include <condition_variable>
+#include <cstdint>
 #include <mutex>
 #include <thread>
 #include <unordered_map>
@@ -73,7 +74,7 @@
 
     // Add a listener to receive luma notifications. The luma reported via listener will
     // report the median luma for the layers under the stopLayerHandle, in the samplingArea region.
-    void addListener(const Rect& samplingArea, const wp<Layer>& stopLayer,
+    void addListener(const Rect& samplingArea, uint32_t stopLayerId,
                      const sp<IRegionSamplingListener>& listener);
     // Remove the listener to stop receiving median luma notifications.
     void removeListener(const sp<IRegionSamplingListener>& listener);
@@ -87,7 +88,7 @@
 private:
     struct Descriptor {
         Rect area = Rect::EMPTY_RECT;
-        wp<Layer> stopLayer;
+        uint32_t stopLayerId;
         sp<IRegionSamplingListener> listener;
     };
 
diff --git a/services/surfaceflinger/Scheduler/EventThread.cpp b/services/surfaceflinger/Scheduler/EventThread.cpp
index a6cd47b..008d8c4 100644
--- a/services/surfaceflinger/Scheduler/EventThread.cpp
+++ b/services/surfaceflinger/Scheduler/EventThread.cpp
@@ -124,12 +124,12 @@
     return event;
 }
 
-DisplayEventReceiver::Event makeModeChanged(DisplayModePtr mode) {
+DisplayEventReceiver::Event makeModeChanged(const scheduler::FrameRateMode& mode) {
     DisplayEventReceiver::Event event;
-    event.header = {DisplayEventReceiver::DISPLAY_EVENT_MODE_CHANGE, mode->getPhysicalDisplayId(),
-                    systemTime()};
-    event.modeChange.modeId = mode->getId().value();
-    event.modeChange.vsyncPeriod = mode->getVsyncPeriod();
+    event.header = {DisplayEventReceiver::DISPLAY_EVENT_MODE_CHANGE,
+                    mode.modePtr->getPhysicalDisplayId(), systemTime()};
+    event.modeChange.modeId = mode.modePtr->getId().value();
+    event.modeChange.vsyncPeriod = mode.fps.getPeriodNsecs();
     return event;
 }
 
@@ -405,7 +405,7 @@
     mCondition.notify_all();
 }
 
-void EventThread::onModeChanged(DisplayModePtr mode) {
+void EventThread::onModeChanged(const scheduler::FrameRateMode& mode) {
     std::lock_guard<std::mutex> lock(mMutex);
 
     mPendingEvents.push_back(makeModeChanged(mode));
@@ -668,6 +668,7 @@
             StringAppendF(&result, "    %s\n", toString(*connection).c_str());
         }
     }
+    result += '\n';
 }
 
 const char* EventThread::toCString(State state) {
diff --git a/services/surfaceflinger/Scheduler/EventThread.h b/services/surfaceflinger/Scheduler/EventThread.h
index 7a5a348..43c3598 100644
--- a/services/surfaceflinger/Scheduler/EventThread.h
+++ b/services/surfaceflinger/Scheduler/EventThread.h
@@ -23,6 +23,7 @@
 #include <sys/types.h>
 #include <utils/Errors.h>
 
+#include <scheduler/FrameRateMode.h>
 #include <condition_variable>
 #include <cstdint>
 #include <deque>
@@ -134,7 +135,7 @@
     virtual void onHotplugReceived(PhysicalDisplayId displayId, bool connected) = 0;
 
     // called when SF changes the active mode and apps needs to be notified about the change
-    virtual void onModeChanged(DisplayModePtr) = 0;
+    virtual void onModeChanged(const scheduler::FrameRateMode&) = 0;
 
     // called when SF updates the Frame Rate Override list
     virtual void onFrameRateOverridesChanged(PhysicalDisplayId displayId,
@@ -185,7 +186,7 @@
 
     void onHotplugReceived(PhysicalDisplayId displayId, bool connected) override;
 
-    void onModeChanged(DisplayModePtr) override;
+    void onModeChanged(const scheduler::FrameRateMode&) override;
 
     void onFrameRateOverridesChanged(PhysicalDisplayId displayId,
                                      std::vector<FrameRateOverride> overrides) override;
diff --git a/services/surfaceflinger/Scheduler/FrameRateOverrideMappings.cpp b/services/surfaceflinger/Scheduler/FrameRateOverrideMappings.cpp
index c233455..cb9bfe9 100644
--- a/services/surfaceflinger/Scheduler/FrameRateOverrideMappings.cpp
+++ b/services/surfaceflinger/Scheduler/FrameRateOverrideMappings.cpp
@@ -54,10 +54,9 @@
 std::vector<FrameRateOverride> FrameRateOverrideMappings::getAllFrameRateOverrides(
         bool supportsFrameRateOverrideByContent) {
     std::lock_guard lock(mFrameRateOverridesLock);
+
     std::vector<FrameRateOverride> overrides;
-    overrides.reserve(std::max({mFrameRateOverridesFromGameManager.size(),
-                                mFrameRateOverridesFromBackdoor.size(),
-                                mFrameRateOverridesByContent.size()}));
+    overrides.reserve(maxOverridesCount());
 
     for (const auto& [uid, frameRate] : mFrameRateOverridesFromBackdoor) {
         overrides.emplace_back(FrameRateOverride{uid, frameRate.getValue()});
@@ -83,28 +82,34 @@
     return overrides;
 }
 
-void FrameRateOverrideMappings::dump(std::string& result) const {
-    using base::StringAppendF;
+void FrameRateOverrideMappings::dump(utils::Dumper& dumper) const {
+    using namespace std::string_view_literals;
 
     std::lock_guard lock(mFrameRateOverridesLock);
 
-    StringAppendF(&result, "Frame Rate Overrides (backdoor): {");
-    for (const auto& [uid, frameRate] : mFrameRateOverridesFromBackdoor) {
-        StringAppendF(&result, "[uid: %d frameRate: %s], ", uid, to_string(frameRate).c_str());
-    }
-    StringAppendF(&result, "}\n");
+    const bool hasOverrides = maxOverridesCount() > 0;
+    dumper.dump("FrameRateOverrides"sv, hasOverrides ? ""sv : "none"sv);
 
-    StringAppendF(&result, "Frame Rate Overrides (GameManager): {");
-    for (const auto& [uid, frameRate] : mFrameRateOverridesFromGameManager) {
-        StringAppendF(&result, "[uid: %d frameRate: %s], ", uid, to_string(frameRate).c_str());
-    }
-    StringAppendF(&result, "}\n");
+    if (!hasOverrides) return;
 
-    StringAppendF(&result, "Frame Rate Overrides (setFrameRate): {");
-    for (const auto& [uid, frameRate] : mFrameRateOverridesByContent) {
-        StringAppendF(&result, "[uid: %d frameRate: %s], ", uid, to_string(frameRate).c_str());
+    dump(dumper, "setFrameRate"sv, mFrameRateOverridesByContent);
+    dump(dumper, "GameManager"sv, mFrameRateOverridesFromGameManager);
+    dump(dumper, "Backdoor"sv, mFrameRateOverridesFromBackdoor);
+}
+
+void FrameRateOverrideMappings::dump(utils::Dumper& dumper, std::string_view name,
+                                     const UidToFrameRateOverride& overrides) const {
+    if (overrides.empty()) return;
+
+    utils::Dumper::Indent indent(dumper);
+    dumper.dump(name);
+    {
+        utils::Dumper::Indent indent(dumper);
+        for (const auto& [uid, frameRate] : overrides) {
+            using namespace std::string_view_literals;
+            dumper.dump("(uid, frameRate)"sv, uid, frameRate);
+        }
     }
-    StringAppendF(&result, "}\n");
 }
 
 bool FrameRateOverrideMappings::updateFrameRateOverridesByContent(
diff --git a/services/surfaceflinger/Scheduler/FrameRateOverrideMappings.h b/services/surfaceflinger/Scheduler/FrameRateOverrideMappings.h
index 4185a4c..da0f276 100644
--- a/services/surfaceflinger/Scheduler/FrameRateOverrideMappings.h
+++ b/services/surfaceflinger/Scheduler/FrameRateOverrideMappings.h
@@ -23,7 +23,10 @@
 #include <map>
 #include <optional>
 
+#include "Utils/Dumper.h"
+
 namespace android::scheduler {
+
 class FrameRateOverrideMappings {
     using FrameRateOverride = DisplayEventReceiver::Event::FrameRateOverride;
     using UidToFrameRateOverride = std::map<uid_t, Fps>;
@@ -34,7 +37,6 @@
             EXCLUDES(mFrameRateOverridesLock);
     std::vector<FrameRateOverride> getAllFrameRateOverrides(bool supportsFrameRateOverrideByContent)
             EXCLUDES(mFrameRateOverridesLock);
-    void dump(std::string& result) const;
     bool updateFrameRateOverridesByContent(const UidToFrameRateOverride& frameRateOverrides)
             EXCLUDES(mFrameRateOverridesLock);
     void setGameModeRefreshRateForUid(FrameRateOverride frameRateOverride)
@@ -42,7 +44,17 @@
     void setPreferredRefreshRateForUid(FrameRateOverride frameRateOverride)
             EXCLUDES(mFrameRateOverridesLock);
 
+    void dump(utils::Dumper&) const;
+
 private:
+    size_t maxOverridesCount() const REQUIRES(mFrameRateOverridesLock) {
+        return std::max({mFrameRateOverridesByContent.size(),
+                         mFrameRateOverridesFromGameManager.size(),
+                         mFrameRateOverridesFromBackdoor.size()});
+    }
+
+    void dump(utils::Dumper&, std::string_view name, const UidToFrameRateOverride&) const;
+
     // The frame rate override lists need their own mutex as they are being read
     // by SurfaceFlinger, Scheduler and EventThread (as a callback) to prevent deadlocks
     mutable std::mutex mFrameRateOverridesLock;
@@ -53,4 +65,5 @@
     UidToFrameRateOverride mFrameRateOverridesFromBackdoor GUARDED_BY(mFrameRateOverridesLock);
     UidToFrameRateOverride mFrameRateOverridesFromGameManager GUARDED_BY(mFrameRateOverridesLock);
 };
+
 } // namespace android::scheduler
diff --git a/services/surfaceflinger/Scheduler/LayerHistory.cpp b/services/surfaceflinger/Scheduler/LayerHistory.cpp
index ae111c3..7c9cedfa 100644
--- a/services/surfaceflinger/Scheduler/LayerHistory.cpp
+++ b/services/surfaceflinger/Scheduler/LayerHistory.cpp
@@ -164,7 +164,7 @@
             getVoteType(layer->getDefaultFrameRateCompatibility(), contentDetectionEnabled));
 }
 
-auto LayerHistory::summarize(const RefreshRateConfigs& configs, nsecs_t now) -> Summary {
+auto LayerHistory::summarize(const RefreshRateSelector& selector, nsecs_t now) -> Summary {
     Summary summary;
 
     std::lock_guard lock(mLock);
@@ -178,7 +178,7 @@
         ALOGV("%s has priority: %d %s focused", info->getName().c_str(), frameRateSelectionPriority,
               layerFocused ? "" : "not");
 
-        const auto vote = info->getRefreshRateVote(configs, now);
+        const auto vote = info->getRefreshRateVote(selector, now);
         // Skip NoVote layer as those don't have any requirements
         if (vote.type == LayerVoteType::NoVote) {
             continue;
@@ -275,7 +275,7 @@
 
 std::string LayerHistory::dump() const {
     std::lock_guard lock(mLock);
-    return base::StringPrintf("LayerHistory{size=%zu, active=%zu}",
+    return base::StringPrintf("{size=%zu, active=%zu}",
                               mActiveLayerInfos.size() + mInactiveLayerInfos.size(),
                               mActiveLayerInfos.size());
 }
diff --git a/services/surfaceflinger/Scheduler/LayerHistory.h b/services/surfaceflinger/Scheduler/LayerHistory.h
index 12bec8d..5022906 100644
--- a/services/surfaceflinger/Scheduler/LayerHistory.h
+++ b/services/surfaceflinger/Scheduler/LayerHistory.h
@@ -27,7 +27,7 @@
 #include <utility>
 #include <vector>
 
-#include "RefreshRateConfigs.h"
+#include "RefreshRateSelector.h"
 
 namespace android {
 
@@ -39,7 +39,7 @@
 
 class LayerHistory {
 public:
-    using LayerVoteType = RefreshRateConfigs::LayerVoteType;
+    using LayerVoteType = RefreshRateSelector::LayerVoteType;
 
     LayerHistory();
     ~LayerHistory();
@@ -67,10 +67,10 @@
     // does not set a preference for refresh rate.
     void setDefaultFrameRateCompatibility(Layer*, bool contentDetectionEnabled);
 
-    using Summary = std::vector<RefreshRateConfigs::LayerRequirement>;
+    using Summary = std::vector<RefreshRateSelector::LayerRequirement>;
 
     // Rebuilds sets of active/inactive layers, and accumulates stats for active layers.
-    Summary summarize(const RefreshRateConfigs&, nsecs_t now);
+    Summary summarize(const RefreshRateSelector&, nsecs_t now);
 
     void clear();
 
diff --git a/services/surfaceflinger/Scheduler/LayerInfo.cpp b/services/surfaceflinger/Scheduler/LayerInfo.cpp
index 943615c..7247e4b 100644
--- a/services/surfaceflinger/Scheduler/LayerInfo.cpp
+++ b/services/surfaceflinger/Scheduler/LayerInfo.cpp
@@ -187,8 +187,8 @@
     return static_cast<nsecs_t>(averageFrameTime);
 }
 
-std::optional<Fps> LayerInfo::calculateRefreshRateIfPossible(
-        const RefreshRateConfigs& refreshRateConfigs, nsecs_t now) {
+std::optional<Fps> LayerInfo::calculateRefreshRateIfPossible(const RefreshRateSelector& selector,
+                                                             nsecs_t now) {
     static constexpr float MARGIN = 1.0f; // 1Hz
     if (!hasEnoughDataForHeuristic()) {
         ALOGV("Not enough data");
@@ -199,7 +199,7 @@
         const auto refreshRate = Fps::fromPeriodNsecs(*averageFrameTime);
         const bool refreshRateConsistent = mRefreshRateHistory.add(refreshRate, now);
         if (refreshRateConsistent) {
-            const auto knownRefreshRate = refreshRateConfigs.findClosestKnownFrameRate(refreshRate);
+            const auto knownRefreshRate = selector.findClosestKnownFrameRate(refreshRate);
             using fps_approx_ops::operator!=;
 
             // To avoid oscillation, use the last calculated refresh rate if it is close enough.
@@ -222,7 +222,7 @@
                                                : std::nullopt;
 }
 
-LayerInfo::LayerVote LayerInfo::getRefreshRateVote(const RefreshRateConfigs& refreshRateConfigs,
+LayerInfo::LayerVote LayerInfo::getRefreshRateVote(const RefreshRateSelector& selector,
                                                    nsecs_t now) {
     if (mLayerVote.type != LayerHistory::LayerVoteType::Heuristic) {
         ALOGV("%s voted %d ", mName.c_str(), static_cast<int>(mLayerVote.type));
@@ -250,7 +250,7 @@
         clearHistory(now);
     }
 
-    auto refreshRate = calculateRefreshRateIfPossible(refreshRateConfigs, now);
+    auto refreshRate = calculateRefreshRateIfPossible(selector, now);
     if (refreshRate.has_value()) {
         ALOGV("%s calculated refresh rate: %s", mName.c_str(), to_string(*refreshRate).c_str());
         return {LayerHistory::LayerVoteType::Heuristic, refreshRate.value()};
diff --git a/services/surfaceflinger/Scheduler/LayerInfo.h b/services/surfaceflinger/Scheduler/LayerInfo.h
index 28cb24a..a5ffbbe 100644
--- a/services/surfaceflinger/Scheduler/LayerInfo.h
+++ b/services/surfaceflinger/Scheduler/LayerInfo.h
@@ -28,7 +28,7 @@
 #include <scheduler/Seamlessness.h>
 
 #include "LayerHistory.h"
-#include "RefreshRateConfigs.h"
+#include "RefreshRateSelector.h"
 
 namespace android {
 
@@ -162,7 +162,7 @@
 
     uid_t getOwnerUid() const { return mOwnerUid; }
 
-    LayerVote getRefreshRateVote(const RefreshRateConfigs&, nsecs_t now);
+    LayerVote getRefreshRateVote(const RefreshRateSelector&, nsecs_t now);
 
     // Return the last updated time. If the present time is farther in the future than the
     // updated time, the updated time is the present time.
@@ -261,7 +261,7 @@
     bool isFrequent(nsecs_t now) const;
     bool isAnimating(nsecs_t now) const;
     bool hasEnoughDataForHeuristic() const;
-    std::optional<Fps> calculateRefreshRateIfPossible(const RefreshRateConfigs&, nsecs_t now);
+    std::optional<Fps> calculateRefreshRateIfPossible(const RefreshRateSelector&, nsecs_t now);
     std::optional<nsecs_t> calculateAverageFrameTime() const;
     bool isFrameTimeValid(const FrameTimeData&) const;
 
diff --git a/services/surfaceflinger/Scheduler/OneShotTimer.cpp b/services/surfaceflinger/Scheduler/OneShotTimer.cpp
index 3c8dc64..cd45bfd 100644
--- a/services/surfaceflinger/Scheduler/OneShotTimer.cpp
+++ b/services/surfaceflinger/Scheduler/OneShotTimer.cpp
@@ -179,11 +179,5 @@
     }
 }
 
-std::string OneShotTimer::dump() const {
-    std::ostringstream stream;
-    stream << mInterval.count() << " ms";
-    return stream.str();
-}
-
 } // namespace scheduler
 } // namespace android
diff --git a/services/surfaceflinger/Scheduler/OneShotTimer.h b/services/surfaceflinger/Scheduler/OneShotTimer.h
index 2017c31..f95646c 100644
--- a/services/surfaceflinger/Scheduler/OneShotTimer.h
+++ b/services/surfaceflinger/Scheduler/OneShotTimer.h
@@ -23,6 +23,7 @@
 #include "../Clock.h"
 
 #include <android-base/thread_annotations.h>
+#include <scheduler/Time.h>
 
 namespace android {
 namespace scheduler {
@@ -42,6 +43,8 @@
                  std::unique_ptr<Clock> clock = std::make_unique<SteadyClock>());
     ~OneShotTimer();
 
+    Duration interval() const { return mInterval; }
+
     // Initializes and turns on the idle timer.
     void start();
     // Stops the idle timer and any held resources.
@@ -49,8 +52,6 @@
     // Resets the wakeup time and fires the reset callback.
     void reset();
 
-    std::string dump() const;
-
 private:
     // Enum to track in what state is the timer.
     enum class TimerState {
diff --git a/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp b/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp
deleted file mode 100644
index 30483a2..0000000
--- a/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp
+++ /dev/null
@@ -1,1162 +0,0 @@
-/*
- * Copyright 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.
- */
-
-// #define LOG_NDEBUG 0
-#define ATRACE_TAG ATRACE_TAG_GRAPHICS
-
-// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wextra"
-
-#include <chrono>
-#include <cmath>
-
-#include <android-base/properties.h>
-#include <android-base/stringprintf.h>
-#include <ftl/enum.h>
-#include <ftl/fake_guard.h>
-#include <ftl/match.h>
-#include <utils/Trace.h>
-
-#include "../SurfaceFlingerProperties.h"
-#include "RefreshRateConfigs.h"
-
-#undef LOG_TAG
-#define LOG_TAG "RefreshRateConfigs"
-
-namespace android::scheduler {
-namespace {
-
-struct RefreshRateScore {
-    DisplayModeIterator modeIt;
-    float overallScore;
-    struct {
-        float modeBelowThreshold;
-        float modeAboveThreshold;
-    } fixedRateBelowThresholdLayersScore;
-};
-
-constexpr RefreshRateConfigs::GlobalSignals kNoSignals;
-
-std::string formatLayerInfo(const RefreshRateConfigs::LayerRequirement& layer, float weight) {
-    return base::StringPrintf("%s (type=%s, weight=%.2f, seamlessness=%s) %s", layer.name.c_str(),
-                              ftl::enum_string(layer.vote).c_str(), weight,
-                              ftl::enum_string(layer.seamlessness).c_str(),
-                              to_string(layer.desiredRefreshRate).c_str());
-}
-
-std::vector<Fps> constructKnownFrameRates(const DisplayModes& modes) {
-    std::vector<Fps> knownFrameRates = {24_Hz, 30_Hz, 45_Hz, 60_Hz, 72_Hz};
-    knownFrameRates.reserve(knownFrameRates.size() + modes.size());
-
-    // Add all supported refresh rates.
-    for (const auto& [id, mode] : modes) {
-        knownFrameRates.push_back(mode->getFps());
-    }
-
-    // Sort and remove duplicates.
-    std::sort(knownFrameRates.begin(), knownFrameRates.end(), isStrictlyLess);
-    knownFrameRates.erase(std::unique(knownFrameRates.begin(), knownFrameRates.end(),
-                                      isApproxEqual),
-                          knownFrameRates.end());
-    return knownFrameRates;
-}
-
-// The Filter is a `bool(const DisplayMode&)` predicate.
-template <typename Filter>
-std::vector<DisplayModeIterator> sortByRefreshRate(const DisplayModes& modes, Filter&& filter) {
-    std::vector<DisplayModeIterator> sortedModes;
-    sortedModes.reserve(modes.size());
-
-    for (auto it = modes.begin(); it != modes.end(); ++it) {
-        const auto& [id, mode] = *it;
-
-        if (filter(*mode)) {
-            ALOGV("%s: including mode %d", __func__, id.value());
-            sortedModes.push_back(it);
-        }
-    }
-
-    std::sort(sortedModes.begin(), sortedModes.end(), [](auto it1, auto it2) {
-        const auto& mode1 = it1->second;
-        const auto& mode2 = it2->second;
-
-        if (mode1->getVsyncPeriod() == mode2->getVsyncPeriod()) {
-            return mode1->getGroup() > mode2->getGroup();
-        }
-
-        return mode1->getVsyncPeriod() > mode2->getVsyncPeriod();
-    });
-
-    return sortedModes;
-}
-
-bool canModesSupportFrameRateOverride(const std::vector<DisplayModeIterator>& sortedModes) {
-    for (const auto it1 : sortedModes) {
-        const auto& mode1 = it1->second;
-        for (const auto it2 : sortedModes) {
-            const auto& mode2 = it2->second;
-
-            if (RefreshRateConfigs::getFrameRateDivisor(mode1->getFps(), mode2->getFps()) >= 2) {
-                return true;
-            }
-        }
-    }
-    return false;
-}
-
-std::string toString(const RefreshRateConfigs::PolicyVariant& policy) {
-    using namespace std::string_literals;
-
-    return ftl::match(
-            policy,
-            [](const RefreshRateConfigs::DisplayManagerPolicy& policy) {
-                return "DisplayManagerPolicy"s + policy.toString();
-            },
-            [](const RefreshRateConfigs::OverridePolicy& policy) {
-                return "OverridePolicy"s + policy.toString();
-            },
-            [](RefreshRateConfigs::NoOverridePolicy) { return "NoOverridePolicy"s; });
-}
-
-} // namespace
-
-struct RefreshRateConfigs::RefreshRateScoreComparator {
-    bool operator()(const RefreshRateScore& lhs, const RefreshRateScore& rhs) const {
-        const auto& [modeIt, overallScore, _] = lhs;
-
-        std::string name = to_string(modeIt->second->getFps());
-        ALOGV("%s sorting scores %.2f", name.c_str(), overallScore);
-
-        ATRACE_INT(name.c_str(), static_cast<int>(std::round(overallScore * 100)));
-
-        constexpr float kEpsilon = 0.0001f;
-        if (std::abs(overallScore - rhs.overallScore) > kEpsilon) {
-            return overallScore > rhs.overallScore;
-        }
-
-        // If overallScore tie we will pick the higher refresh rate if
-        // high refresh rate is the priority else the lower refresh rate.
-        if (refreshRateOrder == RefreshRateOrder::Descending) {
-            using fps_approx_ops::operator>;
-            return modeIt->second->getFps() > rhs.modeIt->second->getFps();
-        } else {
-            using fps_approx_ops::operator<;
-            return modeIt->second->getFps() < rhs.modeIt->second->getFps();
-        }
-    }
-
-    const RefreshRateOrder refreshRateOrder;
-};
-
-std::string RefreshRateConfigs::Policy::toString() const {
-    return base::StringPrintf("{defaultModeId=%d, allowGroupSwitching=%s"
-                              ", primaryRange=%s, appRequestRange=%s}",
-                              defaultMode.value(), allowGroupSwitching ? "true" : "false",
-                              to_string(primaryRange).c_str(), to_string(appRequestRange).c_str());
-}
-
-std::pair<nsecs_t, nsecs_t> RefreshRateConfigs::getDisplayFrames(nsecs_t layerPeriod,
-                                                                 nsecs_t displayPeriod) const {
-    auto [quotient, remainder] = std::div(layerPeriod, displayPeriod);
-    if (remainder <= MARGIN_FOR_PERIOD_CALCULATION ||
-        std::abs(remainder - displayPeriod) <= MARGIN_FOR_PERIOD_CALCULATION) {
-        quotient++;
-        remainder = 0;
-    }
-
-    return {quotient, remainder};
-}
-
-float RefreshRateConfigs::calculateNonExactMatchingLayerScoreLocked(const LayerRequirement& layer,
-                                                                    Fps refreshRate) const {
-    constexpr float kScoreForFractionalPairs = .8f;
-
-    const auto displayPeriod = refreshRate.getPeriodNsecs();
-    const auto layerPeriod = layer.desiredRefreshRate.getPeriodNsecs();
-    if (layer.vote == LayerVoteType::ExplicitDefault) {
-        // Find the actual rate the layer will render, assuming
-        // that layerPeriod is the minimal period to render a frame.
-        // For example if layerPeriod is 20ms and displayPeriod is 16ms,
-        // then the actualLayerPeriod will be 32ms, because it is the
-        // smallest multiple of the display period which is >= layerPeriod.
-        auto actualLayerPeriod = displayPeriod;
-        int multiplier = 1;
-        while (layerPeriod > actualLayerPeriod + MARGIN_FOR_PERIOD_CALCULATION) {
-            multiplier++;
-            actualLayerPeriod = displayPeriod * multiplier;
-        }
-
-        // Because of the threshold we used above it's possible that score is slightly
-        // above 1.
-        return std::min(1.0f,
-                        static_cast<float>(layerPeriod) / static_cast<float>(actualLayerPeriod));
-    }
-
-    if (layer.vote == LayerVoteType::ExplicitExactOrMultiple ||
-        layer.vote == LayerVoteType::Heuristic) {
-        if (isFractionalPairOrMultiple(refreshRate, layer.desiredRefreshRate)) {
-            return kScoreForFractionalPairs;
-        }
-
-        // Calculate how many display vsyncs we need to present a single frame for this
-        // layer
-        const auto [displayFramesQuotient, displayFramesRemainder] =
-                getDisplayFrames(layerPeriod, displayPeriod);
-        static constexpr size_t MAX_FRAMES_TO_FIT = 10; // Stop calculating when score < 0.1
-        if (displayFramesRemainder == 0) {
-            // Layer desired refresh rate matches the display rate.
-            return 1.0f;
-        }
-
-        if (displayFramesQuotient == 0) {
-            // Layer desired refresh rate is higher than the display rate.
-            return (static_cast<float>(layerPeriod) / static_cast<float>(displayPeriod)) *
-                    (1.0f / (MAX_FRAMES_TO_FIT + 1));
-        }
-
-        // Layer desired refresh rate is lower than the display rate. Check how well it fits
-        // the cadence.
-        auto diff = std::abs(displayFramesRemainder - (displayPeriod - displayFramesRemainder));
-        int iter = 2;
-        while (diff > MARGIN_FOR_PERIOD_CALCULATION && iter < MAX_FRAMES_TO_FIT) {
-            diff = diff - (displayPeriod - diff);
-            iter++;
-        }
-
-        return (1.0f / iter);
-    }
-
-    return 0;
-}
-
-float RefreshRateConfigs::calculateRefreshRateScoreForFps(Fps refreshRate) const {
-    const float ratio =
-            refreshRate.getValue() / mAppRequestRefreshRates.back()->second->getFps().getValue();
-    // Use ratio^2 to get a lower score the more we get further from peak
-    return ratio * ratio;
-}
-
-float RefreshRateConfigs::calculateLayerScoreLocked(const LayerRequirement& layer, Fps refreshRate,
-                                                    bool isSeamlessSwitch) const {
-    // Slightly prefer seamless switches.
-    constexpr float kSeamedSwitchPenalty = 0.95f;
-    const float seamlessness = isSeamlessSwitch ? 1.0f : kSeamedSwitchPenalty;
-
-    // If the layer wants Max, give higher score to the higher refresh rate
-    if (layer.vote == LayerVoteType::Max) {
-        return calculateRefreshRateScoreForFps(refreshRate);
-    }
-
-    if (layer.vote == LayerVoteType::ExplicitExact) {
-        const int divisor = getFrameRateDivisor(refreshRate, layer.desiredRefreshRate);
-        if (mSupportsFrameRateOverrideByContent) {
-            // Since we support frame rate override, allow refresh rates which are
-            // multiples of the layer's request, as those apps would be throttled
-            // down to run at the desired refresh rate.
-            return divisor > 0;
-        }
-
-        return divisor == 1;
-    }
-
-    // If the layer frame rate is a divisor of the refresh rate it should score
-    // the highest score.
-    if (getFrameRateDivisor(refreshRate, layer.desiredRefreshRate) > 0) {
-        return 1.0f * seamlessness;
-    }
-
-    // The layer frame rate is not a divisor of the refresh rate,
-    // there is a small penalty attached to the score to favor the frame rates
-    // the exactly matches the display refresh rate or a multiple.
-    constexpr float kNonExactMatchingPenalty = 0.95f;
-    return calculateNonExactMatchingLayerScoreLocked(layer, refreshRate) * seamlessness *
-            kNonExactMatchingPenalty;
-}
-
-auto RefreshRateConfigs::getRankedRefreshRates(const std::vector<LayerRequirement>& layers,
-                                               GlobalSignals signals) const
-        -> std::pair<std::vector<RefreshRateRanking>, GlobalSignals> {
-    std::lock_guard lock(mLock);
-
-    if (mGetRankedRefreshRatesCache &&
-        mGetRankedRefreshRatesCache->arguments == std::make_pair(layers, signals)) {
-        return mGetRankedRefreshRatesCache->result;
-    }
-
-    const auto result = getRankedRefreshRatesLocked(layers, signals);
-    mGetRankedRefreshRatesCache = GetRankedRefreshRatesCache{{layers, signals}, result};
-    return result;
-}
-
-auto RefreshRateConfigs::getRankedRefreshRatesLocked(const std::vector<LayerRequirement>& layers,
-                                                     GlobalSignals signals) const
-        -> std::pair<std::vector<RefreshRateRanking>, GlobalSignals> {
-    using namespace fps_approx_ops;
-    ATRACE_CALL();
-    ALOGV("%s: %zu layers", __func__, layers.size());
-
-    const auto& activeMode = *getActiveModeItLocked()->second;
-
-    // Keep the display at max refresh rate for the duration of powering on the display.
-    if (signals.powerOnImminent) {
-        ALOGV("Power On Imminent");
-        return {getRefreshRatesByPolicyLocked(activeMode.getGroup(), RefreshRateOrder::Descending,
-                                              /*preferredDisplayModeOpt*/ std::nullopt),
-                GlobalSignals{.powerOnImminent = true}};
-    }
-
-    int noVoteLayers = 0;
-    int minVoteLayers = 0;
-    int maxVoteLayers = 0;
-    int explicitDefaultVoteLayers = 0;
-    int explicitExactOrMultipleVoteLayers = 0;
-    int explicitExact = 0;
-    int seamedFocusedLayers = 0;
-
-    for (const auto& layer : layers) {
-        switch (layer.vote) {
-            case LayerVoteType::NoVote:
-                noVoteLayers++;
-                break;
-            case LayerVoteType::Min:
-                minVoteLayers++;
-                break;
-            case LayerVoteType::Max:
-                maxVoteLayers++;
-                break;
-            case LayerVoteType::ExplicitDefault:
-                explicitDefaultVoteLayers++;
-                break;
-            case LayerVoteType::ExplicitExactOrMultiple:
-                explicitExactOrMultipleVoteLayers++;
-                break;
-            case LayerVoteType::ExplicitExact:
-                explicitExact++;
-                break;
-            case LayerVoteType::Heuristic:
-                break;
-        }
-
-        if (layer.seamlessness == Seamlessness::SeamedAndSeamless && layer.focused) {
-            seamedFocusedLayers++;
-        }
-    }
-
-    const bool hasExplicitVoteLayers = explicitDefaultVoteLayers > 0 ||
-            explicitExactOrMultipleVoteLayers > 0 || explicitExact > 0;
-
-    const Policy* policy = getCurrentPolicyLocked();
-    const auto& defaultMode = mDisplayModes.get(policy->defaultMode)->get();
-
-    // If the default mode group is different from the group of current mode,
-    // this means a layer requesting a seamed mode switch just disappeared and
-    // we should switch back to the default group.
-    // However if a seamed layer is still present we anchor around the group
-    // of the current mode, in order to prevent unnecessary seamed mode switches
-    // (e.g. when pausing a video playback).
-    const auto anchorGroup =
-            seamedFocusedLayers > 0 ? activeMode.getGroup() : defaultMode->getGroup();
-
-    // Consider the touch event if there are no Explicit* layers. Otherwise wait until after we've
-    // selected a refresh rate to see if we should apply touch boost.
-    if (signals.touch && !hasExplicitVoteLayers) {
-        ALOGV("Touch Boost");
-        return {getRefreshRatesByPolicyLocked(anchorGroup, RefreshRateOrder::Descending,
-                                              /*preferredDisplayModeOpt*/ std::nullopt),
-                GlobalSignals{.touch = true}};
-    }
-
-    // If the primary range consists of a single refresh rate then we can only
-    // move out the of range if layers explicitly request a different refresh
-    // rate.
-    const bool primaryRangeIsSingleRate =
-            isApproxEqual(policy->primaryRange.min, policy->primaryRange.max);
-
-    if (!signals.touch && signals.idle && !(primaryRangeIsSingleRate && hasExplicitVoteLayers)) {
-        ALOGV("Idle");
-        return {getRefreshRatesByPolicyLocked(activeMode.getGroup(), RefreshRateOrder::Ascending,
-                                              /*preferredDisplayModeOpt*/ std::nullopt),
-                GlobalSignals{.idle = true}};
-    }
-
-    if (layers.empty() || noVoteLayers == layers.size()) {
-        ALOGV("No layers with votes");
-        return {getRefreshRatesByPolicyLocked(anchorGroup, RefreshRateOrder::Descending,
-                                              /*preferredDisplayModeOpt*/ std::nullopt),
-                kNoSignals};
-    }
-
-    // Only if all layers want Min we should return Min
-    if (noVoteLayers + minVoteLayers == layers.size()) {
-        ALOGV("All layers Min");
-        return {getRefreshRatesByPolicyLocked(activeMode.getGroup(), RefreshRateOrder::Ascending,
-                                              /*preferredDisplayModeOpt*/ std::nullopt),
-                kNoSignals};
-    }
-
-    // Find the best refresh rate based on score
-    std::vector<RefreshRateScore> scores;
-    scores.reserve(mAppRequestRefreshRates.size());
-
-    for (const DisplayModeIterator modeIt : mAppRequestRefreshRates) {
-        scores.emplace_back(RefreshRateScore{modeIt, 0.0f});
-    }
-
-    for (const auto& layer : layers) {
-        ALOGV("Calculating score for %s (%s, weight %.2f, desired %.2f) ", layer.name.c_str(),
-              ftl::enum_string(layer.vote).c_str(), layer.weight,
-              layer.desiredRefreshRate.getValue());
-        if (layer.vote == LayerVoteType::NoVote || layer.vote == LayerVoteType::Min) {
-            continue;
-        }
-
-        const auto weight = layer.weight;
-
-        for (auto& [modeIt, overallScore, fixedRateBelowThresholdLayersScore] : scores) {
-            const auto& [id, mode] = *modeIt;
-            const bool isSeamlessSwitch = mode->getGroup() == activeMode.getGroup();
-
-            if (layer.seamlessness == Seamlessness::OnlySeamless && !isSeamlessSwitch) {
-                ALOGV("%s ignores %s to avoid non-seamless switch. Current mode = %s",
-                      formatLayerInfo(layer, weight).c_str(), to_string(*mode).c_str(),
-                      to_string(activeMode).c_str());
-                continue;
-            }
-
-            if (layer.seamlessness == Seamlessness::SeamedAndSeamless && !isSeamlessSwitch &&
-                !layer.focused) {
-                ALOGV("%s ignores %s because it's not focused and the switch is going to be seamed."
-                      " Current mode = %s",
-                      formatLayerInfo(layer, weight).c_str(), to_string(*mode).c_str(),
-                      to_string(activeMode).c_str());
-                continue;
-            }
-
-            // Layers with default seamlessness vote for the current mode group if
-            // there are layers with seamlessness=SeamedAndSeamless and for the default
-            // mode group otherwise. In second case, if the current mode group is different
-            // from the default, this means a layer with seamlessness=SeamedAndSeamless has just
-            // disappeared.
-            const bool isInPolicyForDefault = mode->getGroup() == anchorGroup;
-            if (layer.seamlessness == Seamlessness::Default && !isInPolicyForDefault) {
-                ALOGV("%s ignores %s. Current mode = %s", formatLayerInfo(layer, weight).c_str(),
-                      to_string(*mode).c_str(), to_string(activeMode).c_str());
-                continue;
-            }
-
-            const bool inPrimaryRange = policy->primaryRange.includes(mode->getFps());
-            if ((primaryRangeIsSingleRate || !inPrimaryRange) &&
-                !(layer.focused &&
-                  (layer.vote == LayerVoteType::ExplicitDefault ||
-                   layer.vote == LayerVoteType::ExplicitExact))) {
-                // Only focused layers with ExplicitDefault frame rate settings are allowed to score
-                // refresh rates outside the primary range.
-                continue;
-            }
-
-            const float layerScore =
-                    calculateLayerScoreLocked(layer, mode->getFps(), isSeamlessSwitch);
-            const float weightedLayerScore = weight * layerScore;
-
-            // Layer with fixed source has a special consideration which depends on the
-            // mConfig.frameRateMultipleThreshold. We don't want these layers to score
-            // refresh rates above the threshold, but we also don't want to favor the lower
-            // ones by having a greater number of layers scoring them. Instead, we calculate
-            // the score independently for these layers and later decide which
-            // refresh rates to add it. For example, desired 24 fps with 120 Hz threshold should not
-            // score 120 Hz, but desired 60 fps should contribute to the score.
-            const bool fixedSourceLayer = [](LayerVoteType vote) {
-                switch (vote) {
-                    case LayerVoteType::ExplicitExactOrMultiple:
-                    case LayerVoteType::Heuristic:
-                        return true;
-                    case LayerVoteType::NoVote:
-                    case LayerVoteType::Min:
-                    case LayerVoteType::Max:
-                    case LayerVoteType::ExplicitDefault:
-                    case LayerVoteType::ExplicitExact:
-                        return false;
-                }
-            }(layer.vote);
-            const bool layerBelowThreshold = mConfig.frameRateMultipleThreshold != 0 &&
-                    layer.desiredRefreshRate <
-                            Fps::fromValue(mConfig.frameRateMultipleThreshold / 2);
-            if (fixedSourceLayer && layerBelowThreshold) {
-                const bool modeAboveThreshold =
-                        mode->getFps() >= Fps::fromValue(mConfig.frameRateMultipleThreshold);
-                if (modeAboveThreshold) {
-                    ALOGV("%s gives %s fixed source (above threshold) score of %.4f",
-                          formatLayerInfo(layer, weight).c_str(), to_string(mode->getFps()).c_str(),
-                          layerScore);
-                    fixedRateBelowThresholdLayersScore.modeAboveThreshold += weightedLayerScore;
-                } else {
-                    ALOGV("%s gives %s fixed source (below threshold) score of %.4f",
-                          formatLayerInfo(layer, weight).c_str(), to_string(mode->getFps()).c_str(),
-                          layerScore);
-                    fixedRateBelowThresholdLayersScore.modeBelowThreshold += weightedLayerScore;
-                }
-            } else {
-                ALOGV("%s gives %s score of %.4f", formatLayerInfo(layer, weight).c_str(),
-                      to_string(mode->getFps()).c_str(), layerScore);
-                overallScore += weightedLayerScore;
-            }
-        }
-    }
-
-    // We want to find the best refresh rate without the fixed source layers,
-    // so we could know whether we should add the modeAboveThreshold scores or not.
-    // If the best refresh rate is already above the threshold, it means that
-    // some non-fixed source layers already scored it, so we can just add the score
-    // for all fixed source layers, even the ones that are above the threshold.
-    const bool maxScoreAboveThreshold = [&] {
-        if (mConfig.frameRateMultipleThreshold == 0 || scores.empty()) {
-            return false;
-        }
-
-        const auto maxScoreIt =
-                std::max_element(scores.begin(), scores.end(),
-                                 [](RefreshRateScore max, RefreshRateScore current) {
-                                     const auto& [modeIt, overallScore, _] = current;
-                                     return overallScore > max.overallScore;
-                                 });
-        ALOGV("%s is the best refresh rate without fixed source layers. It is %s the threshold for "
-              "refresh rate multiples",
-              to_string(maxScoreIt->modeIt->second->getFps()).c_str(),
-              maxScoreAboveThreshold ? "above" : "below");
-        return maxScoreIt->modeIt->second->getFps() >=
-                Fps::fromValue(mConfig.frameRateMultipleThreshold);
-    }();
-
-    // Now we can add the fixed rate layers score
-    for (auto& [modeIt, overallScore, fixedRateBelowThresholdLayersScore] : scores) {
-        overallScore += fixedRateBelowThresholdLayersScore.modeBelowThreshold;
-        if (maxScoreAboveThreshold) {
-            overallScore += fixedRateBelowThresholdLayersScore.modeAboveThreshold;
-        }
-        ALOGV("%s adjusted overallScore is %.4f", to_string(modeIt->second->getFps()).c_str(),
-              overallScore);
-    }
-
-    // Now that we scored all the refresh rates we need to pick the one that got the highest
-    // overallScore. Sort the scores based on their overallScore in descending order of priority.
-    const RefreshRateOrder refreshRateOrder =
-            maxVoteLayers > 0 ? RefreshRateOrder::Descending : RefreshRateOrder::Ascending;
-    std::sort(scores.begin(), scores.end(),
-              RefreshRateScoreComparator{.refreshRateOrder = refreshRateOrder});
-    std::vector<RefreshRateRanking> rankedRefreshRates;
-    rankedRefreshRates.reserve(scores.size());
-
-    std::transform(scores.begin(), scores.end(), back_inserter(rankedRefreshRates),
-                   [](const RefreshRateScore& score) {
-                       return RefreshRateRanking{score.modeIt->second, score.overallScore};
-                   });
-
-    const bool noLayerScore = std::all_of(scores.begin(), scores.end(), [](RefreshRateScore score) {
-        return score.overallScore == 0;
-    });
-
-    if (primaryRangeIsSingleRate) {
-        // If we never scored any layers, then choose the rate from the primary
-        // range instead of picking a random score from the app range.
-        if (noLayerScore) {
-            ALOGV("Layers not scored");
-            return {getRefreshRatesByPolicyLocked(anchorGroup, RefreshRateOrder::Descending,
-                                                  /*preferredDisplayModeOpt*/ std::nullopt),
-                    kNoSignals};
-        } else {
-            return {rankedRefreshRates, kNoSignals};
-        }
-    }
-
-    // Consider the touch event if there are no ExplicitDefault layers. ExplicitDefault are mostly
-    // interactive (as opposed to ExplicitExactOrMultiple) and therefore if those posted an explicit
-    // vote we should not change it if we get a touch event. Only apply touch boost if it will
-    // actually increase the refresh rate over the normal selection.
-    const bool touchBoostForExplicitExact = [&] {
-        if (mSupportsFrameRateOverrideByContent) {
-            // Enable touch boost if there are other layers besides exact
-            return explicitExact + noVoteLayers != layers.size();
-        } else {
-            // Enable touch boost if there are no exact layers
-            return explicitExact == 0;
-        }
-    }();
-
-    const auto& touchRefreshRates =
-            getRefreshRatesByPolicyLocked(anchorGroup, RefreshRateOrder::Descending,
-                                          /*preferredDisplayModeOpt*/ std::nullopt);
-    using fps_approx_ops::operator<;
-
-    if (signals.touch && explicitDefaultVoteLayers == 0 && touchBoostForExplicitExact &&
-        scores.front().modeIt->second->getFps() <
-                touchRefreshRates.front().displayModePtr->getFps()) {
-        ALOGV("Touch Boost");
-        return {touchRefreshRates, GlobalSignals{.touch = true}};
-    }
-
-    // If we never scored any layers, and we don't favor high refresh rates, prefer to stay with the
-    // current config
-    if (noLayerScore && refreshRateOrder == RefreshRateOrder::Ascending) {
-        const auto preferredDisplayMode = activeMode.getId();
-        return {getRefreshRatesByPolicyLocked(anchorGroup, RefreshRateOrder::Ascending,
-                                              preferredDisplayMode),
-                kNoSignals};
-    }
-
-    return {rankedRefreshRates, kNoSignals};
-}
-
-std::unordered_map<uid_t, std::vector<const RefreshRateConfigs::LayerRequirement*>>
-groupLayersByUid(const std::vector<RefreshRateConfigs::LayerRequirement>& layers) {
-    std::unordered_map<uid_t, std::vector<const RefreshRateConfigs::LayerRequirement*>> layersByUid;
-    for (const auto& layer : layers) {
-        auto iter = layersByUid.emplace(layer.ownerUid,
-                                        std::vector<const RefreshRateConfigs::LayerRequirement*>());
-        auto& layersWithSameUid = iter.first->second;
-        layersWithSameUid.push_back(&layer);
-    }
-
-    // Remove uids that can't have a frame rate override
-    for (auto iter = layersByUid.begin(); iter != layersByUid.end();) {
-        const auto& layersWithSameUid = iter->second;
-        bool skipUid = false;
-        for (const auto& layer : layersWithSameUid) {
-            if (layer->vote == RefreshRateConfigs::LayerVoteType::Max ||
-                layer->vote == RefreshRateConfigs::LayerVoteType::Heuristic) {
-                skipUid = true;
-                break;
-            }
-        }
-        if (skipUid) {
-            iter = layersByUid.erase(iter);
-        } else {
-            ++iter;
-        }
-    }
-
-    return layersByUid;
-}
-
-RefreshRateConfigs::UidToFrameRateOverride RefreshRateConfigs::getFrameRateOverrides(
-        const std::vector<LayerRequirement>& layers, Fps displayRefreshRate,
-        GlobalSignals globalSignals) const {
-    ATRACE_CALL();
-
-    ALOGV("%s: %zu layers", __func__, layers.size());
-
-    std::lock_guard lock(mLock);
-
-    std::vector<RefreshRateScore> scores;
-    scores.reserve(mDisplayModes.size());
-
-    for (auto it = mDisplayModes.begin(); it != mDisplayModes.end(); ++it) {
-        scores.emplace_back(RefreshRateScore{it, 0.0f});
-    }
-
-    std::sort(scores.begin(), scores.end(), [](const auto& lhs, const auto& rhs) {
-        const auto& mode1 = lhs.modeIt->second;
-        const auto& mode2 = rhs.modeIt->second;
-        return isStrictlyLess(mode1->getFps(), mode2->getFps());
-    });
-
-    std::unordered_map<uid_t, std::vector<const LayerRequirement*>> layersByUid =
-            groupLayersByUid(layers);
-    UidToFrameRateOverride frameRateOverrides;
-    for (const auto& [uid, layersWithSameUid] : layersByUid) {
-        // Layers with ExplicitExactOrMultiple expect touch boost
-        const bool hasExplicitExactOrMultiple =
-                std::any_of(layersWithSameUid.cbegin(), layersWithSameUid.cend(),
-                            [](const auto& layer) {
-                                return layer->vote == LayerVoteType::ExplicitExactOrMultiple;
-                            });
-
-        if (globalSignals.touch && hasExplicitExactOrMultiple) {
-            continue;
-        }
-
-        for (auto& [_, score, _1] : scores) {
-            score = 0;
-        }
-
-        for (const auto& layer : layersWithSameUid) {
-            if (layer->vote == LayerVoteType::NoVote || layer->vote == LayerVoteType::Min) {
-                continue;
-            }
-
-            LOG_ALWAYS_FATAL_IF(layer->vote != LayerVoteType::ExplicitDefault &&
-                                layer->vote != LayerVoteType::ExplicitExactOrMultiple &&
-                                layer->vote != LayerVoteType::ExplicitExact);
-            for (auto& [modeIt, score, _] : scores) {
-                constexpr bool isSeamlessSwitch = true;
-                const auto layerScore = calculateLayerScoreLocked(*layer, modeIt->second->getFps(),
-                                                                  isSeamlessSwitch);
-                score += layer->weight * layerScore;
-            }
-        }
-
-        // We just care about the refresh rates which are a divisor of the
-        // display refresh rate
-        const auto it = std::remove_if(scores.begin(), scores.end(), [&](RefreshRateScore score) {
-            const auto& [id, mode] = *score.modeIt;
-            return getFrameRateDivisor(displayRefreshRate, mode->getFps()) == 0;
-        });
-        scores.erase(it, scores.end());
-
-        // If we never scored any layers, we don't have a preferred frame rate
-        if (std::all_of(scores.begin(), scores.end(),
-                        [](RefreshRateScore score) { return score.overallScore == 0; })) {
-            continue;
-        }
-
-        // Now that we scored all the refresh rates we need to pick the lowest refresh rate
-        // that got the highest score.
-        const DisplayModePtr& bestRefreshRate =
-                std::min_element(scores.begin(), scores.end(),
-                                 RefreshRateScoreComparator{.refreshRateOrder =
-                                                                    RefreshRateOrder::Ascending})
-                        ->modeIt->second;
-        frameRateOverrides.emplace(uid, bestRefreshRate->getFps());
-    }
-
-    return frameRateOverrides;
-}
-
-std::optional<Fps> RefreshRateConfigs::onKernelTimerChanged(
-        std::optional<DisplayModeId> desiredActiveModeId, bool timerExpired) const {
-    std::lock_guard lock(mLock);
-
-    const DisplayModePtr& current = desiredActiveModeId
-            ? mDisplayModes.get(*desiredActiveModeId)->get()
-            : getActiveModeItLocked()->second;
-
-    const DisplayModePtr& min = mMinRefreshRateModeIt->second;
-    if (current == min) {
-        return {};
-    }
-
-    const auto& mode = timerExpired ? min : current;
-    return mode->getFps();
-}
-
-const DisplayModePtr& RefreshRateConfigs::getMinRefreshRateByPolicyLocked() const {
-    const auto& activeMode = *getActiveModeItLocked()->second;
-
-    for (const DisplayModeIterator modeIt : mPrimaryRefreshRates) {
-        const auto& mode = modeIt->second;
-        if (activeMode.getGroup() == mode->getGroup()) {
-            return mode;
-        }
-    }
-
-    ALOGE("Can't find min refresh rate by policy with the same mode group as the current mode %s",
-          to_string(activeMode).c_str());
-
-    // Default to the lowest refresh rate.
-    return mPrimaryRefreshRates.front()->second;
-}
-
-const DisplayModePtr& RefreshRateConfigs::getMaxRefreshRateByPolicyLocked(int anchorGroup) const {
-    for (auto it = mPrimaryRefreshRates.rbegin(); it != mPrimaryRefreshRates.rend(); ++it) {
-        const auto& mode = (*it)->second;
-        if (anchorGroup == mode->getGroup()) {
-            return mode;
-        }
-    }
-
-    ALOGE("Can't find max refresh rate by policy with the same group %d", anchorGroup);
-
-    // Default to the highest refresh rate.
-    return mPrimaryRefreshRates.back()->second;
-}
-
-std::vector<RefreshRateRanking> RefreshRateConfigs::getRefreshRatesByPolicyLocked(
-        std::optional<int> anchorGroupOpt, RefreshRateOrder refreshRateOrder,
-        std::optional<DisplayModeId> preferredDisplayModeOpt) const {
-    std::deque<RefreshRateRanking> rankings;
-    const auto makeRanking = [&](const DisplayModeIterator it) REQUIRES(mLock) {
-        const auto& mode = it->second;
-        if (anchorGroupOpt && mode->getGroup() != anchorGroupOpt) {
-            return;
-        }
-
-        float score = calculateRefreshRateScoreForFps(mode->getFps());
-        const bool inverseScore = (refreshRateOrder == RefreshRateOrder::Ascending);
-        if (inverseScore) {
-            score = 1.0f / score;
-        }
-        if (preferredDisplayModeOpt) {
-            if (*preferredDisplayModeOpt == mode->getId()) {
-                rankings.push_front(RefreshRateRanking{mode, /*score*/ 1.0f});
-                return;
-            }
-            constexpr float kNonPreferredModePenalty = 0.95f;
-            score *= kNonPreferredModePenalty;
-        }
-        rankings.push_back(RefreshRateRanking{mode, score});
-    };
-
-    if (refreshRateOrder == RefreshRateOrder::Ascending) {
-        std::for_each(mPrimaryRefreshRates.begin(), mPrimaryRefreshRates.end(), makeRanking);
-    } else {
-        std::for_each(mPrimaryRefreshRates.rbegin(), mPrimaryRefreshRates.rend(), makeRanking);
-    }
-
-    if (!rankings.empty() || !anchorGroupOpt) {
-        return {rankings.begin(), rankings.end()};
-    }
-
-    ALOGW("Can't find %s refresh rate by policy with the same mode group"
-          " as the mode group %d",
-          refreshRateOrder == RefreshRateOrder::Ascending ? "min" : "max", anchorGroupOpt.value());
-
-    return getRefreshRatesByPolicyLocked(/*anchorGroupOpt*/ std::nullopt, refreshRateOrder,
-                                         preferredDisplayModeOpt);
-}
-
-DisplayModePtr RefreshRateConfigs::getActiveModePtr() const {
-    std::lock_guard lock(mLock);
-    return getActiveModeItLocked()->second;
-}
-
-const DisplayMode& RefreshRateConfigs::getActiveMode() const {
-    // Reads from kMainThreadContext do not require mLock.
-    ftl::FakeGuard guard(mLock);
-    return *mActiveModeIt->second;
-}
-
-DisplayModeIterator RefreshRateConfigs::getActiveModeItLocked() const {
-    // Reads under mLock do not require kMainThreadContext.
-    return FTL_FAKE_GUARD(kMainThreadContext, mActiveModeIt);
-}
-
-void RefreshRateConfigs::setActiveModeId(DisplayModeId modeId) {
-    std::lock_guard lock(mLock);
-
-    // Invalidate the cached invocation to getRankedRefreshRates. This forces
-    // the refresh rate to be recomputed on the next call to getRankedRefreshRates.
-    mGetRankedRefreshRatesCache.reset();
-
-    mActiveModeIt = mDisplayModes.find(modeId);
-    LOG_ALWAYS_FATAL_IF(mActiveModeIt == mDisplayModes.end());
-}
-
-RefreshRateConfigs::RefreshRateConfigs(DisplayModes modes, DisplayModeId activeModeId,
-                                       Config config)
-      : mKnownFrameRates(constructKnownFrameRates(modes)), mConfig(config) {
-    initializeIdleTimer();
-    FTL_FAKE_GUARD(kMainThreadContext, updateDisplayModes(std::move(modes), activeModeId));
-}
-
-void RefreshRateConfigs::initializeIdleTimer() {
-    if (mConfig.idleTimerTimeout > 0ms) {
-        mIdleTimer.emplace(
-                "IdleTimer", mConfig.idleTimerTimeout,
-                [this] {
-                    std::scoped_lock lock(mIdleTimerCallbacksMutex);
-                    if (const auto callbacks = getIdleTimerCallbacks()) {
-                        callbacks->onReset();
-                    }
-                },
-                [this] {
-                    std::scoped_lock lock(mIdleTimerCallbacksMutex);
-                    if (const auto callbacks = getIdleTimerCallbacks()) {
-                        callbacks->onExpired();
-                    }
-                });
-    }
-}
-
-void RefreshRateConfigs::updateDisplayModes(DisplayModes modes, DisplayModeId activeModeId) {
-    std::lock_guard lock(mLock);
-
-    // Invalidate the cached invocation to getRankedRefreshRates. This forces
-    // the refresh rate to be recomputed on the next call to getRankedRefreshRates.
-    mGetRankedRefreshRatesCache.reset();
-
-    mDisplayModes = std::move(modes);
-    mActiveModeIt = mDisplayModes.find(activeModeId);
-    LOG_ALWAYS_FATAL_IF(mActiveModeIt == mDisplayModes.end());
-
-    const auto sortedModes =
-            sortByRefreshRate(mDisplayModes, [](const DisplayMode&) { return true; });
-    mMinRefreshRateModeIt = sortedModes.front();
-    mMaxRefreshRateModeIt = sortedModes.back();
-
-    // Reset the policy because the old one may no longer be valid.
-    mDisplayManagerPolicy = {};
-    mDisplayManagerPolicy.defaultMode = activeModeId;
-
-    mSupportsFrameRateOverrideByContent =
-            mConfig.enableFrameRateOverride && canModesSupportFrameRateOverride(sortedModes);
-
-    constructAvailableRefreshRates();
-}
-
-bool RefreshRateConfigs::isPolicyValidLocked(const Policy& policy) const {
-    // defaultMode must be a valid mode, and within the given refresh rate range.
-    if (const auto mode = mDisplayModes.get(policy.defaultMode)) {
-        if (!policy.primaryRange.includes(mode->get()->getFps())) {
-            ALOGE("Default mode is not in the primary range.");
-            return false;
-        }
-    } else {
-        ALOGE("Default mode is not found.");
-        return false;
-    }
-
-    using namespace fps_approx_ops;
-    return policy.appRequestRange.min <= policy.primaryRange.min &&
-            policy.appRequestRange.max >= policy.primaryRange.max;
-}
-
-auto RefreshRateConfigs::setPolicy(const PolicyVariant& policy) -> SetPolicyResult {
-    Policy oldPolicy;
-    {
-        std::lock_guard lock(mLock);
-        oldPolicy = *getCurrentPolicyLocked();
-
-        const bool valid = ftl::match(
-                policy,
-                [this](const auto& policy) {
-                    ftl::FakeGuard guard(mLock);
-                    if (!isPolicyValidLocked(policy)) {
-                        ALOGE("Invalid policy: %s", policy.toString().c_str());
-                        return false;
-                    }
-
-                    using T = std::decay_t<decltype(policy)>;
-
-                    if constexpr (std::is_same_v<T, DisplayManagerPolicy>) {
-                        mDisplayManagerPolicy = policy;
-                    } else {
-                        static_assert(std::is_same_v<T, OverridePolicy>);
-                        mOverridePolicy = policy;
-                    }
-                    return true;
-                },
-                [this](NoOverridePolicy) {
-                    ftl::FakeGuard guard(mLock);
-                    mOverridePolicy.reset();
-                    return true;
-                });
-
-        if (!valid) {
-            return SetPolicyResult::Invalid;
-        }
-
-        mGetRankedRefreshRatesCache.reset();
-
-        if (*getCurrentPolicyLocked() == oldPolicy) {
-            return SetPolicyResult::Unchanged;
-        }
-        constructAvailableRefreshRates();
-    }
-
-    const auto displayId = getActiveMode().getPhysicalDisplayId();
-    const unsigned numModeChanges = std::exchange(mNumModeSwitchesInPolicy, 0u);
-
-    ALOGI("Display %s policy changed\n"
-          "Previous: %s\n"
-          "Current:  %s\n"
-          "%u mode changes were performed under the previous policy",
-          to_string(displayId).c_str(), oldPolicy.toString().c_str(), toString(policy).c_str(),
-          numModeChanges);
-
-    return SetPolicyResult::Changed;
-}
-
-const RefreshRateConfigs::Policy* RefreshRateConfigs::getCurrentPolicyLocked() const {
-    return mOverridePolicy ? &mOverridePolicy.value() : &mDisplayManagerPolicy;
-}
-
-RefreshRateConfigs::Policy RefreshRateConfigs::getCurrentPolicy() const {
-    std::lock_guard lock(mLock);
-    return *getCurrentPolicyLocked();
-}
-
-RefreshRateConfigs::Policy RefreshRateConfigs::getDisplayManagerPolicy() const {
-    std::lock_guard lock(mLock);
-    return mDisplayManagerPolicy;
-}
-
-bool RefreshRateConfigs::isModeAllowed(DisplayModeId modeId) const {
-    std::lock_guard lock(mLock);
-    return std::any_of(mAppRequestRefreshRates.begin(), mAppRequestRefreshRates.end(),
-                       [modeId](DisplayModeIterator modeIt) {
-                           return modeIt->second->getId() == modeId;
-                       });
-}
-
-void RefreshRateConfigs::constructAvailableRefreshRates() {
-    // Filter modes based on current policy and sort on refresh rate.
-    const Policy* policy = getCurrentPolicyLocked();
-    ALOGV("%s: %s ", __func__, policy->toString().c_str());
-
-    const auto& defaultMode = mDisplayModes.get(policy->defaultMode)->get();
-
-    const auto filterRefreshRates = [&](FpsRange range, const char* rangeName) REQUIRES(mLock) {
-        const auto filter = [&](const DisplayMode& mode) {
-            return mode.getResolution() == defaultMode->getResolution() &&
-                    mode.getDpi() == defaultMode->getDpi() &&
-                    (policy->allowGroupSwitching || mode.getGroup() == defaultMode->getGroup()) &&
-                    range.includes(mode.getFps());
-        };
-
-        const auto modes = sortByRefreshRate(mDisplayModes, filter);
-        LOG_ALWAYS_FATAL_IF(modes.empty(), "No matching modes for %s range %s", rangeName,
-                            to_string(range).c_str());
-
-        const auto stringifyModes = [&] {
-            std::string str;
-            for (const auto modeIt : modes) {
-                str += to_string(modeIt->second->getFps());
-                str.push_back(' ');
-            }
-            return str;
-        };
-        ALOGV("%s refresh rates: %s", rangeName, stringifyModes().c_str());
-
-        return modes;
-    };
-
-    mPrimaryRefreshRates = filterRefreshRates(policy->primaryRange, "primary");
-    mAppRequestRefreshRates = filterRefreshRates(policy->appRequestRange, "app request");
-}
-
-Fps RefreshRateConfigs::findClosestKnownFrameRate(Fps frameRate) const {
-    using namespace fps_approx_ops;
-
-    if (frameRate <= mKnownFrameRates.front()) {
-        return mKnownFrameRates.front();
-    }
-
-    if (frameRate >= mKnownFrameRates.back()) {
-        return mKnownFrameRates.back();
-    }
-
-    auto lowerBound = std::lower_bound(mKnownFrameRates.begin(), mKnownFrameRates.end(), frameRate,
-                                       isStrictlyLess);
-
-    const auto distance1 = std::abs(frameRate.getValue() - lowerBound->getValue());
-    const auto distance2 = std::abs(frameRate.getValue() - std::prev(lowerBound)->getValue());
-    return distance1 < distance2 ? *lowerBound : *std::prev(lowerBound);
-}
-
-RefreshRateConfigs::KernelIdleTimerAction RefreshRateConfigs::getIdleTimerAction() const {
-    std::lock_guard lock(mLock);
-
-    const Fps deviceMinFps = mMinRefreshRateModeIt->second->getFps();
-    const DisplayModePtr& minByPolicy = getMinRefreshRateByPolicyLocked();
-
-    // Kernel idle timer will set the refresh rate to the device min. If DisplayManager says that
-    // the min allowed refresh rate is higher than the device min, we do not want to enable the
-    // timer.
-    if (isStrictlyLess(deviceMinFps, minByPolicy->getFps())) {
-        return KernelIdleTimerAction::TurnOff;
-    }
-
-    const DisplayModePtr& maxByPolicy =
-            getMaxRefreshRateByPolicyLocked(getActiveModeItLocked()->second->getGroup());
-    if (minByPolicy == maxByPolicy) {
-        // Turn on the timer when the min of the primary range is below the device min.
-        if (const Policy* currentPolicy = getCurrentPolicyLocked();
-            isApproxLess(currentPolicy->primaryRange.min, deviceMinFps)) {
-            return KernelIdleTimerAction::TurnOn;
-        }
-        return KernelIdleTimerAction::TurnOff;
-    }
-
-    // Turn on the timer in all other cases.
-    return KernelIdleTimerAction::TurnOn;
-}
-
-int RefreshRateConfigs::getFrameRateDivisor(Fps displayRefreshRate, Fps layerFrameRate) {
-    // This calculation needs to be in sync with the java code
-    // in DisplayManagerService.getDisplayInfoForFrameRateOverride
-
-    // The threshold must be smaller than 0.001 in order to differentiate
-    // between the fractional pairs (e.g. 59.94 and 60).
-    constexpr float kThreshold = 0.0009f;
-    const auto numPeriods = displayRefreshRate.getValue() / layerFrameRate.getValue();
-    const auto numPeriodsRounded = std::round(numPeriods);
-    if (std::abs(numPeriods - numPeriodsRounded) > kThreshold) {
-        return 0;
-    }
-
-    return static_cast<int>(numPeriodsRounded);
-}
-
-bool RefreshRateConfigs::isFractionalPairOrMultiple(Fps smaller, Fps bigger) {
-    if (isStrictlyLess(bigger, smaller)) {
-        return isFractionalPairOrMultiple(bigger, smaller);
-    }
-
-    const auto multiplier = std::round(bigger.getValue() / smaller.getValue());
-    constexpr float kCoef = 1000.f / 1001.f;
-    return isApproxEqual(bigger, Fps::fromValue(smaller.getValue() * multiplier / kCoef)) ||
-            isApproxEqual(bigger, Fps::fromValue(smaller.getValue() * multiplier * kCoef));
-}
-
-void RefreshRateConfigs::dump(utils::Dumper& dumper) const {
-    using namespace std::string_view_literals;
-
-    std::lock_guard lock(mLock);
-
-    const auto activeModeId = getActiveModeItLocked()->first;
-    dumper.dump("activeModeId"sv, std::to_string(activeModeId.value()));
-
-    dumper.dump("displayModes"sv);
-    {
-        utils::Dumper::Indent indent(dumper);
-        for (const auto& [id, mode] : mDisplayModes) {
-            dumper.dump({}, to_string(*mode));
-        }
-    }
-
-    dumper.dump("displayManagerPolicy"sv, mDisplayManagerPolicy.toString());
-
-    if (const Policy& currentPolicy = *getCurrentPolicyLocked();
-        mOverridePolicy && currentPolicy != mDisplayManagerPolicy) {
-        dumper.dump("overridePolicy"sv, currentPolicy.toString());
-    }
-
-    dumper.dump("supportsFrameRateOverrideByContent"sv, mSupportsFrameRateOverrideByContent);
-
-    std::string idleTimer;
-    if (mIdleTimer) {
-        idleTimer = mIdleTimer->dump();
-    } else {
-        idleTimer = "off"sv;
-    }
-
-    if (const auto controller = mConfig.kernelIdleTimerController) {
-        base::StringAppendF(&idleTimer, " (kernel via %s)", ftl::enum_string(*controller).c_str());
-    } else {
-        idleTimer += " (platform)"sv;
-    }
-
-    dumper.dump("idleTimer"sv, idleTimer);
-}
-
-std::chrono::milliseconds RefreshRateConfigs::getIdleTimerTimeout() {
-    return mConfig.idleTimerTimeout;
-}
-
-} // namespace android::scheduler
-
-// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic pop // ignored "-Wextra"
diff --git a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp
new file mode 100644
index 0000000..a05d3df
--- /dev/null
+++ b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp
@@ -0,0 +1,1304 @@
+/*
+ * Copyright 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.
+ */
+
+// #define LOG_NDEBUG 0
+#define ATRACE_TAG ATRACE_TAG_GRAPHICS
+
+// TODO(b/129481165): remove the #pragma below and fix conversion issues
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wextra"
+
+#include <chrono>
+#include <cmath>
+#include <deque>
+#include <map>
+
+#include <android-base/properties.h>
+#include <android-base/stringprintf.h>
+#include <ftl/enum.h>
+#include <ftl/fake_guard.h>
+#include <ftl/match.h>
+#include <ftl/unit.h>
+#include <scheduler/FrameRateMode.h>
+#include <utils/Trace.h>
+
+#include "../SurfaceFlingerProperties.h"
+#include "RefreshRateSelector.h"
+
+#undef LOG_TAG
+#define LOG_TAG "RefreshRateSelector"
+
+namespace android::scheduler {
+namespace {
+
+struct RefreshRateScore {
+    FrameRateMode frameRateMode;
+    float overallScore;
+    struct {
+        float modeBelowThreshold;
+        float modeAboveThreshold;
+    } fixedRateBelowThresholdLayersScore;
+};
+
+constexpr RefreshRateSelector::GlobalSignals kNoSignals;
+
+std::string formatLayerInfo(const RefreshRateSelector::LayerRequirement& layer, float weight) {
+    return base::StringPrintf("%s (type=%s, weight=%.2f, seamlessness=%s) %s", layer.name.c_str(),
+                              ftl::enum_string(layer.vote).c_str(), weight,
+                              ftl::enum_string(layer.seamlessness).c_str(),
+                              to_string(layer.desiredRefreshRate).c_str());
+}
+
+std::vector<Fps> constructKnownFrameRates(const DisplayModes& modes) {
+    std::vector<Fps> knownFrameRates = {24_Hz, 30_Hz, 45_Hz, 60_Hz, 72_Hz};
+    knownFrameRates.reserve(knownFrameRates.size() + modes.size());
+
+    // Add all supported refresh rates.
+    for (const auto& [id, mode] : modes) {
+        knownFrameRates.push_back(mode->getFps());
+    }
+
+    // Sort and remove duplicates.
+    std::sort(knownFrameRates.begin(), knownFrameRates.end(), isStrictlyLess);
+    knownFrameRates.erase(std::unique(knownFrameRates.begin(), knownFrameRates.end(),
+                                      isApproxEqual),
+                          knownFrameRates.end());
+    return knownFrameRates;
+}
+
+std::vector<DisplayModeIterator> sortByRefreshRate(const DisplayModes& modes) {
+    std::vector<DisplayModeIterator> sortedModes;
+    sortedModes.reserve(modes.size());
+    for (auto it = modes.begin(); it != modes.end(); ++it) {
+        sortedModes.push_back(it);
+    }
+
+    std::sort(sortedModes.begin(), sortedModes.end(), [](auto it1, auto it2) {
+        const auto& mode1 = it1->second;
+        const auto& mode2 = it2->second;
+
+        if (mode1->getVsyncPeriod() == mode2->getVsyncPeriod()) {
+            return mode1->getGroup() > mode2->getGroup();
+        }
+
+        return mode1->getVsyncPeriod() > mode2->getVsyncPeriod();
+    });
+
+    return sortedModes;
+}
+
+std::pair<unsigned, unsigned> divisorRange(Fps fps, FpsRange range,
+                                           RefreshRateSelector::Config::FrameRateOverride config) {
+    if (config != RefreshRateSelector::Config::FrameRateOverride::Enabled) {
+        return {1, 1};
+    }
+
+    using fps_approx_ops::operator/;
+    // use signed type as `fps / range.max` might be 0
+    const auto start = std::max(1, static_cast<int>(fps / range.max) - 1);
+    const auto end = fps /
+            std::max(range.min, RefreshRateSelector::kMinSupportedFrameRate,
+                     fps_approx_ops::operator<);
+
+    return {start, end};
+}
+
+bool shouldEnableFrameRateOverride(const std::vector<DisplayModeIterator>& sortedModes) {
+    for (const auto it1 : sortedModes) {
+        const auto& mode1 = it1->second;
+        for (const auto it2 : sortedModes) {
+            const auto& mode2 = it2->second;
+
+            if (RefreshRateSelector::getFrameRateDivisor(mode1->getFps(), mode2->getFps()) >= 2) {
+                return true;
+            }
+        }
+    }
+    return false;
+}
+
+std::string toString(const RefreshRateSelector::PolicyVariant& policy) {
+    using namespace std::string_literals;
+
+    return ftl::match(
+            policy,
+            [](const RefreshRateSelector::DisplayManagerPolicy& policy) {
+                return "DisplayManagerPolicy"s + policy.toString();
+            },
+            [](const RefreshRateSelector::OverridePolicy& policy) {
+                return "OverridePolicy"s + policy.toString();
+            },
+            [](RefreshRateSelector::NoOverridePolicy) { return "NoOverridePolicy"s; });
+}
+
+} // namespace
+
+auto RefreshRateSelector::createFrameRateModes(
+        std::function<bool(const DisplayMode&)>&& filterModes, const FpsRange& renderRange) const
+        -> std::vector<FrameRateMode> {
+    struct Key {
+        Fps fps;
+        int32_t group;
+    };
+
+    struct KeyLess {
+        bool operator()(const Key& a, const Key& b) const {
+            using namespace fps_approx_ops;
+            if (a.fps != b.fps) {
+                return a.fps < b.fps;
+            }
+
+            // For the same fps the order doesn't really matter, but we still
+            // want the behaviour of a strictly less operator.
+            // We use the group id as the secondary ordering for that.
+            return a.group < b.group;
+        }
+    };
+
+    std::map<Key, DisplayModeIterator, KeyLess> ratesMap;
+    for (auto it = mDisplayModes.begin(); it != mDisplayModes.end(); ++it) {
+        const auto& [id, mode] = *it;
+
+        if (!filterModes(*mode)) {
+            continue;
+        }
+        const auto [start, end] =
+                divisorRange(mode->getFps(), renderRange, mConfig.enableFrameRateOverride);
+        for (auto divisor = start; divisor <= end; divisor++) {
+            const auto fps = mode->getFps() / divisor;
+            using fps_approx_ops::operator<;
+            if (fps < kMinSupportedFrameRate) {
+                break;
+            }
+
+            if (mConfig.enableFrameRateOverride == Config::FrameRateOverride::Enabled &&
+                !renderRange.includes(fps)) {
+                continue;
+            }
+
+            if (mConfig.enableFrameRateOverride ==
+                        Config::FrameRateOverride::AppOverrideNativeRefreshRates &&
+                !isNativeRefreshRate(fps)) {
+                continue;
+            }
+
+            const auto [existingIter, emplaceHappened] =
+                    ratesMap.try_emplace(Key{fps, mode->getGroup()}, it);
+            if (emplaceHappened) {
+                ALOGV("%s: including %s (%s)", __func__, to_string(fps).c_str(),
+                      to_string(mode->getFps()).c_str());
+            } else {
+                // We might need to update the map as we found a lower refresh rate
+                if (isStrictlyLess(mode->getFps(), existingIter->second->second->getFps())) {
+                    existingIter->second = it;
+                    ALOGV("%s: changing %s (%s)", __func__, to_string(fps).c_str(),
+                          to_string(mode->getFps()).c_str());
+                }
+            }
+        }
+    }
+
+    std::vector<FrameRateMode> frameRateModes;
+    frameRateModes.reserve(ratesMap.size());
+    for (const auto& [key, mode] : ratesMap) {
+        frameRateModes.emplace_back(FrameRateMode{key.fps, ftl::as_non_null(mode->second)});
+    }
+
+    // We always want that the lowest frame rate will be corresponding to the
+    // lowest mode for power saving.
+    const auto lowestRefreshRateIt =
+            std::min_element(frameRateModes.begin(), frameRateModes.end(),
+                             [](const FrameRateMode& lhs, const FrameRateMode& rhs) {
+                                 return isStrictlyLess(lhs.modePtr->getFps(),
+                                                       rhs.modePtr->getFps());
+                             });
+    frameRateModes.erase(frameRateModes.begin(), lowestRefreshRateIt);
+
+    return frameRateModes;
+}
+
+struct RefreshRateSelector::RefreshRateScoreComparator {
+    bool operator()(const RefreshRateScore& lhs, const RefreshRateScore& rhs) const {
+        const auto& [frameRateMode, overallScore, _] = lhs;
+
+        std::string name = to_string(frameRateMode);
+
+        ALOGV("%s sorting scores %.2f", name.c_str(), overallScore);
+        ATRACE_INT(name.c_str(), static_cast<int>(std::round(overallScore * 100)));
+
+        if (!ScoredFrameRate::scoresEqual(overallScore, rhs.overallScore)) {
+            return overallScore > rhs.overallScore;
+        }
+
+        if (refreshRateOrder == RefreshRateOrder::Descending) {
+            using fps_approx_ops::operator>;
+            return frameRateMode.fps > rhs.frameRateMode.fps;
+        } else {
+            using fps_approx_ops::operator<;
+            return frameRateMode.fps < rhs.frameRateMode.fps;
+        }
+    }
+
+    const RefreshRateOrder refreshRateOrder;
+};
+
+std::string RefreshRateSelector::Policy::toString() const {
+    return base::StringPrintf("{defaultModeId=%d, allowGroupSwitching=%s"
+                              ", primaryRanges=%s, appRequestRanges=%s}",
+                              defaultMode.value(), allowGroupSwitching ? "true" : "false",
+                              to_string(primaryRanges).c_str(),
+                              to_string(appRequestRanges).c_str());
+}
+
+std::pair<nsecs_t, nsecs_t> RefreshRateSelector::getDisplayFrames(nsecs_t layerPeriod,
+                                                                  nsecs_t displayPeriod) const {
+    auto [quotient, remainder] = std::div(layerPeriod, displayPeriod);
+    if (remainder <= MARGIN_FOR_PERIOD_CALCULATION ||
+        std::abs(remainder - displayPeriod) <= MARGIN_FOR_PERIOD_CALCULATION) {
+        quotient++;
+        remainder = 0;
+    }
+
+    return {quotient, remainder};
+}
+
+float RefreshRateSelector::calculateNonExactMatchingLayerScoreLocked(const LayerRequirement& layer,
+                                                                     Fps refreshRate) const {
+    constexpr float kScoreForFractionalPairs = .8f;
+
+    const auto displayPeriod = refreshRate.getPeriodNsecs();
+    const auto layerPeriod = layer.desiredRefreshRate.getPeriodNsecs();
+    if (layer.vote == LayerVoteType::ExplicitDefault) {
+        // Find the actual rate the layer will render, assuming
+        // that layerPeriod is the minimal period to render a frame.
+        // For example if layerPeriod is 20ms and displayPeriod is 16ms,
+        // then the actualLayerPeriod will be 32ms, because it is the
+        // smallest multiple of the display period which is >= layerPeriod.
+        auto actualLayerPeriod = displayPeriod;
+        int multiplier = 1;
+        while (layerPeriod > actualLayerPeriod + MARGIN_FOR_PERIOD_CALCULATION) {
+            multiplier++;
+            actualLayerPeriod = displayPeriod * multiplier;
+        }
+
+        // Because of the threshold we used above it's possible that score is slightly
+        // above 1.
+        return std::min(1.0f,
+                        static_cast<float>(layerPeriod) / static_cast<float>(actualLayerPeriod));
+    }
+
+    if (layer.vote == LayerVoteType::ExplicitExactOrMultiple ||
+        layer.vote == LayerVoteType::Heuristic) {
+        const float multiplier = refreshRate.getValue() / layer.desiredRefreshRate.getValue();
+
+        // We only want to score this layer as a fractional pair if the content is not
+        // significantly faster than the display rate, at it would cause a significant frame drop.
+        // It is more appropriate to choose a higher display rate even if
+        // a pull-down will be required.
+        constexpr float kMinMultiplier = 0.25f;
+        if (multiplier >= kMinMultiplier &&
+            isFractionalPairOrMultiple(refreshRate, layer.desiredRefreshRate)) {
+            return kScoreForFractionalPairs;
+        }
+
+        // Calculate how many display vsyncs we need to present a single frame for this
+        // layer
+        const auto [displayFramesQuotient, displayFramesRemainder] =
+                getDisplayFrames(layerPeriod, displayPeriod);
+        static constexpr size_t MAX_FRAMES_TO_FIT = 10; // Stop calculating when score < 0.1
+        if (displayFramesRemainder == 0) {
+            // Layer desired refresh rate matches the display rate.
+            return 1.0f;
+        }
+
+        if (displayFramesQuotient == 0) {
+            // Layer desired refresh rate is higher than the display rate.
+            return (static_cast<float>(layerPeriod) / static_cast<float>(displayPeriod)) *
+                    (1.0f / (MAX_FRAMES_TO_FIT + 1));
+        }
+
+        // Layer desired refresh rate is lower than the display rate. Check how well it fits
+        // the cadence.
+        auto diff = std::abs(displayFramesRemainder - (displayPeriod - displayFramesRemainder));
+        int iter = 2;
+        while (diff > MARGIN_FOR_PERIOD_CALCULATION && iter < MAX_FRAMES_TO_FIT) {
+            diff = diff - (displayPeriod - diff);
+            iter++;
+        }
+
+        return (1.0f / iter);
+    }
+
+    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
+    return ratio * ratio;
+}
+
+float RefreshRateSelector::calculateLayerScoreLocked(const LayerRequirement& layer, Fps refreshRate,
+                                                     bool isSeamlessSwitch) const {
+    // Slightly prefer seamless switches.
+    constexpr float kSeamedSwitchPenalty = 0.95f;
+    const float seamlessness = isSeamlessSwitch ? 1.0f : kSeamedSwitchPenalty;
+
+    // If the layer wants Max, give higher score to the higher refresh rate
+    if (layer.vote == LayerVoteType::Max) {
+        return calculateDistanceScoreFromMax(refreshRate);
+    }
+
+    if (layer.vote == LayerVoteType::ExplicitExact) {
+        const int divisor = getFrameRateDivisor(refreshRate, layer.desiredRefreshRate);
+        if (supportsAppFrameRateOverrideByContent()) {
+            // Since we support frame rate override, allow refresh rates which are
+            // multiples of the layer's request, as those apps would be throttled
+            // down to run at the desired refresh rate.
+            return divisor > 0;
+        }
+
+        return divisor == 1;
+    }
+
+    // If the layer frame rate is a divisor of the refresh rate it should score
+    // the highest score.
+    if (getFrameRateDivisor(refreshRate, layer.desiredRefreshRate) > 0) {
+        return 1.0f * seamlessness;
+    }
+
+    // The layer frame rate is not a divisor of the refresh rate,
+    // there is a small penalty attached to the score to favor the frame rates
+    // the exactly matches the display refresh rate or a multiple.
+    constexpr float kNonExactMatchingPenalty = 0.95f;
+    return calculateNonExactMatchingLayerScoreLocked(layer, refreshRate) * seamlessness *
+            kNonExactMatchingPenalty;
+}
+
+auto RefreshRateSelector::getRankedFrameRates(const std::vector<LayerRequirement>& layers,
+                                              GlobalSignals signals) const -> RankedFrameRates {
+    std::lock_guard lock(mLock);
+
+    if (mGetRankedFrameRatesCache &&
+        mGetRankedFrameRatesCache->arguments == std::make_pair(layers, signals)) {
+        return mGetRankedFrameRatesCache->result;
+    }
+
+    const auto result = getRankedFrameRatesLocked(layers, signals);
+    mGetRankedFrameRatesCache = GetRankedFrameRatesCache{{layers, signals}, result};
+    return result;
+}
+
+auto RefreshRateSelector::getRankedFrameRatesLocked(const std::vector<LayerRequirement>& layers,
+                                                    GlobalSignals signals) const
+        -> RankedFrameRates {
+    using namespace fps_approx_ops;
+    ATRACE_CALL();
+    ALOGV("%s: %zu layers", __func__, layers.size());
+
+    const auto& activeMode = *getActiveModeLocked().modePtr;
+
+    // Keep the display at max frame rate for the duration of powering on the display.
+    if (signals.powerOnImminent) {
+        ALOGV("Power On Imminent");
+        return {rankFrameRates(activeMode.getGroup(), RefreshRateOrder::Descending),
+                GlobalSignals{.powerOnImminent = true}};
+    }
+
+    int noVoteLayers = 0;
+    int minVoteLayers = 0;
+    int maxVoteLayers = 0;
+    int explicitDefaultVoteLayers = 0;
+    int explicitExactOrMultipleVoteLayers = 0;
+    int explicitExact = 0;
+    int seamedFocusedLayers = 0;
+
+    for (const auto& layer : layers) {
+        switch (layer.vote) {
+            case LayerVoteType::NoVote:
+                noVoteLayers++;
+                break;
+            case LayerVoteType::Min:
+                minVoteLayers++;
+                break;
+            case LayerVoteType::Max:
+                maxVoteLayers++;
+                break;
+            case LayerVoteType::ExplicitDefault:
+                explicitDefaultVoteLayers++;
+                break;
+            case LayerVoteType::ExplicitExactOrMultiple:
+                explicitExactOrMultipleVoteLayers++;
+                break;
+            case LayerVoteType::ExplicitExact:
+                explicitExact++;
+                break;
+            case LayerVoteType::Heuristic:
+                break;
+        }
+
+        if (layer.seamlessness == Seamlessness::SeamedAndSeamless && layer.focused) {
+            seamedFocusedLayers++;
+        }
+    }
+
+    const bool hasExplicitVoteLayers = explicitDefaultVoteLayers > 0 ||
+            explicitExactOrMultipleVoteLayers > 0 || explicitExact > 0;
+
+    const Policy* policy = getCurrentPolicyLocked();
+    const auto& defaultMode = mDisplayModes.get(policy->defaultMode)->get();
+
+    // If the default mode group is different from the group of current mode,
+    // this means a layer requesting a seamed mode switch just disappeared and
+    // we should switch back to the default group.
+    // However if a seamed layer is still present we anchor around the group
+    // of the current mode, in order to prevent unnecessary seamed mode switches
+    // (e.g. when pausing a video playback).
+    const auto anchorGroup =
+            seamedFocusedLayers > 0 ? activeMode.getGroup() : defaultMode->getGroup();
+
+    // Consider the touch event if there are no Explicit* layers. Otherwise wait until after we've
+    // selected a refresh rate to see if we should apply touch boost.
+    if (signals.touch && !hasExplicitVoteLayers) {
+        ALOGV("Touch Boost");
+        return {rankFrameRates(anchorGroup, RefreshRateOrder::Descending),
+                GlobalSignals{.touch = true}};
+    }
+
+    // If the primary range consists of a single refresh rate then we can only
+    // move out the of range if layers explicitly request a different refresh
+    // rate.
+    const bool primaryRangeIsSingleRate =
+            isApproxEqual(policy->primaryRanges.physical.min, policy->primaryRanges.physical.max);
+
+    if (!signals.touch && signals.idle && !(primaryRangeIsSingleRate && hasExplicitVoteLayers)) {
+        ALOGV("Idle");
+        return {rankFrameRates(activeMode.getGroup(), RefreshRateOrder::Ascending),
+                GlobalSignals{.idle = true}};
+    }
+
+    if (layers.empty() || noVoteLayers == layers.size()) {
+        ALOGV("No layers with votes");
+        return {rankFrameRates(anchorGroup, RefreshRateOrder::Descending), kNoSignals};
+    }
+
+    // Only if all layers want Min we should return Min
+    if (noVoteLayers + minVoteLayers == layers.size()) {
+        ALOGV("All layers Min");
+        return {rankFrameRates(activeMode.getGroup(), RefreshRateOrder::Ascending), kNoSignals};
+    }
+
+    // Find the best refresh rate based on score
+    std::vector<RefreshRateScore> scores;
+    scores.reserve(mAppRequestFrameRates.size());
+
+    for (const FrameRateMode& it : mAppRequestFrameRates) {
+        scores.emplace_back(RefreshRateScore{it, 0.0f});
+    }
+
+    for (const auto& layer : layers) {
+        ALOGV("Calculating score for %s (%s, weight %.2f, desired %.2f) ", layer.name.c_str(),
+              ftl::enum_string(layer.vote).c_str(), layer.weight,
+              layer.desiredRefreshRate.getValue());
+        if (layer.vote == LayerVoteType::NoVote || layer.vote == LayerVoteType::Min) {
+            continue;
+        }
+
+        const auto weight = layer.weight;
+
+        for (auto& [mode, overallScore, fixedRateBelowThresholdLayersScore] : scores) {
+            const auto& [fps, modePtr] = mode;
+            const bool isSeamlessSwitch = modePtr->getGroup() == activeMode.getGroup();
+
+            if (layer.seamlessness == Seamlessness::OnlySeamless && !isSeamlessSwitch) {
+                ALOGV("%s ignores %s to avoid non-seamless switch. Current mode = %s",
+                      formatLayerInfo(layer, weight).c_str(), to_string(*modePtr).c_str(),
+                      to_string(activeMode).c_str());
+                continue;
+            }
+
+            if (layer.seamlessness == Seamlessness::SeamedAndSeamless && !isSeamlessSwitch &&
+                !layer.focused) {
+                ALOGV("%s ignores %s because it's not focused and the switch is going to be seamed."
+                      " Current mode = %s",
+                      formatLayerInfo(layer, weight).c_str(), to_string(*modePtr).c_str(),
+                      to_string(activeMode).c_str());
+                continue;
+            }
+
+            // Layers with default seamlessness vote for the current mode group if
+            // there are layers with seamlessness=SeamedAndSeamless and for the default
+            // mode group otherwise. In second case, if the current mode group is different
+            // from the default, this means a layer with seamlessness=SeamedAndSeamless has just
+            // disappeared.
+            const bool isInPolicyForDefault = modePtr->getGroup() == anchorGroup;
+            if (layer.seamlessness == Seamlessness::Default && !isInPolicyForDefault) {
+                ALOGV("%s ignores %s. Current mode = %s", formatLayerInfo(layer, weight).c_str(),
+                      to_string(*modePtr).c_str(), to_string(activeMode).c_str());
+                continue;
+            }
+
+            const bool inPrimaryRange = policy->primaryRanges.physical.includes(modePtr->getFps());
+            if ((primaryRangeIsSingleRate || !inPrimaryRange) &&
+                !(layer.focused &&
+                  (layer.vote == LayerVoteType::ExplicitDefault ||
+                   layer.vote == LayerVoteType::ExplicitExact))) {
+                // Only focused layers with ExplicitDefault frame rate settings are allowed to score
+                // refresh rates outside the primary range.
+                continue;
+            }
+
+            const float layerScore = calculateLayerScoreLocked(layer, fps, isSeamlessSwitch);
+            const float weightedLayerScore = weight * layerScore;
+
+            // Layer with fixed source has a special consideration which depends on the
+            // mConfig.frameRateMultipleThreshold. We don't want these layers to score
+            // refresh rates above the threshold, but we also don't want to favor the lower
+            // ones by having a greater number of layers scoring them. Instead, we calculate
+            // the score independently for these layers and later decide which
+            // refresh rates to add it. For example, desired 24 fps with 120 Hz threshold should not
+            // score 120 Hz, but desired 60 fps should contribute to the score.
+            const bool fixedSourceLayer = [](LayerVoteType vote) {
+                switch (vote) {
+                    case LayerVoteType::ExplicitExactOrMultiple:
+                    case LayerVoteType::Heuristic:
+                        return true;
+                    case LayerVoteType::NoVote:
+                    case LayerVoteType::Min:
+                    case LayerVoteType::Max:
+                    case LayerVoteType::ExplicitDefault:
+                    case LayerVoteType::ExplicitExact:
+                        return false;
+                }
+            }(layer.vote);
+            const bool layerBelowThreshold = mConfig.frameRateMultipleThreshold != 0 &&
+                    layer.desiredRefreshRate <
+                            Fps::fromValue(mConfig.frameRateMultipleThreshold / 2);
+            if (fixedSourceLayer && layerBelowThreshold) {
+                const bool modeAboveThreshold =
+                        modePtr->getFps() >= Fps::fromValue(mConfig.frameRateMultipleThreshold);
+                if (modeAboveThreshold) {
+                    ALOGV("%s gives %s (%s) fixed source (above threshold) score of %.4f",
+                          formatLayerInfo(layer, weight).c_str(), to_string(fps).c_str(),
+                          to_string(modePtr->getFps()).c_str(), layerScore);
+                    fixedRateBelowThresholdLayersScore.modeAboveThreshold += weightedLayerScore;
+                } else {
+                    ALOGV("%s gives %s (%s) fixed source (below threshold) score of %.4f",
+                          formatLayerInfo(layer, weight).c_str(), to_string(fps).c_str(),
+                          to_string(modePtr->getFps()).c_str(), layerScore);
+                    fixedRateBelowThresholdLayersScore.modeBelowThreshold += weightedLayerScore;
+                }
+            } else {
+                ALOGV("%s gives %s (%s) score of %.4f", formatLayerInfo(layer, weight).c_str(),
+                      to_string(fps).c_str(), to_string(modePtr->getFps()).c_str(), layerScore);
+                overallScore += weightedLayerScore;
+            }
+        }
+    }
+
+    // We want to find the best refresh rate without the fixed source layers,
+    // so we could know whether we should add the modeAboveThreshold scores or not.
+    // If the best refresh rate is already above the threshold, it means that
+    // some non-fixed source layers already scored it, so we can just add the score
+    // for all fixed source layers, even the ones that are above the threshold.
+    const bool maxScoreAboveThreshold = [&] {
+        if (mConfig.frameRateMultipleThreshold == 0 || scores.empty()) {
+            return false;
+        }
+
+        const auto maxScoreIt =
+                std::max_element(scores.begin(), scores.end(),
+                                 [](RefreshRateScore max, RefreshRateScore current) {
+                                     return current.overallScore > max.overallScore;
+                                 });
+        ALOGV("%s (%s) is the best refresh rate without fixed source layers. It is %s the "
+              "threshold for "
+              "refresh rate multiples",
+              to_string(maxScoreIt->frameRateMode.fps).c_str(),
+              to_string(maxScoreIt->frameRateMode.modePtr->getFps()).c_str(),
+              maxScoreAboveThreshold ? "above" : "below");
+        return maxScoreIt->frameRateMode.modePtr->getFps() >=
+                Fps::fromValue(mConfig.frameRateMultipleThreshold);
+    }();
+
+    // Now we can add the fixed rate layers score
+    for (auto& [frameRateMode, overallScore, fixedRateBelowThresholdLayersScore] : scores) {
+        overallScore += fixedRateBelowThresholdLayersScore.modeBelowThreshold;
+        if (maxScoreAboveThreshold) {
+            overallScore += fixedRateBelowThresholdLayersScore.modeAboveThreshold;
+        }
+        ALOGV("%s (%s) adjusted overallScore is %.4f", to_string(frameRateMode.fps).c_str(),
+              to_string(frameRateMode.modePtr->getFps()).c_str(), overallScore);
+    }
+
+    // Now that we scored all the refresh rates we need to pick the one that got the highest
+    // overallScore. Sort the scores based on their overallScore in descending order of priority.
+    const RefreshRateOrder refreshRateOrder =
+            maxVoteLayers > 0 ? RefreshRateOrder::Descending : RefreshRateOrder::Ascending;
+    std::sort(scores.begin(), scores.end(),
+              RefreshRateScoreComparator{.refreshRateOrder = refreshRateOrder});
+
+    FrameRateRanking ranking;
+    ranking.reserve(scores.size());
+
+    std::transform(scores.begin(), scores.end(), back_inserter(ranking),
+                   [](const RefreshRateScore& score) {
+                       return ScoredFrameRate{score.frameRateMode, score.overallScore};
+                   });
+
+    const bool noLayerScore = std::all_of(scores.begin(), scores.end(), [](RefreshRateScore score) {
+        return score.overallScore == 0;
+    });
+
+    if (primaryRangeIsSingleRate) {
+        // If we never scored any layers, then choose the rate from the primary
+        // range instead of picking a random score from the app range.
+        if (noLayerScore) {
+            ALOGV("Layers not scored");
+            return {rankFrameRates(anchorGroup, RefreshRateOrder::Descending), kNoSignals};
+        } else {
+            return {ranking, kNoSignals};
+        }
+    }
+
+    // Consider the touch event if there are no ExplicitDefault layers. ExplicitDefault are mostly
+    // interactive (as opposed to ExplicitExactOrMultiple) and therefore if those posted an explicit
+    // vote we should not change it if we get a touch event. Only apply touch boost if it will
+    // actually increase the refresh rate over the normal selection.
+    const bool touchBoostForExplicitExact = [&] {
+        if (supportsAppFrameRateOverrideByContent()) {
+            // Enable touch boost if there are other layers besides exact
+            return explicitExact + noVoteLayers != layers.size();
+        } else {
+            // Enable touch boost if there are no exact layers
+            return explicitExact == 0;
+        }
+    }();
+
+    const auto touchRefreshRates = rankFrameRates(anchorGroup, RefreshRateOrder::Descending);
+    using fps_approx_ops::operator<;
+
+    if (signals.touch && explicitDefaultVoteLayers == 0 && touchBoostForExplicitExact &&
+        scores.front().frameRateMode.fps < touchRefreshRates.front().frameRateMode.fps) {
+        ALOGV("Touch Boost");
+        return {touchRefreshRates, GlobalSignals{.touch = true}};
+    }
+
+    // If we never scored any layers, and we don't favor high refresh rates, prefer to stay with the
+    // current config
+    if (noLayerScore && refreshRateOrder == RefreshRateOrder::Ascending) {
+        const auto preferredDisplayMode = activeMode.getId();
+        return {rankFrameRates(anchorGroup, RefreshRateOrder::Ascending, preferredDisplayMode),
+                kNoSignals};
+    }
+
+    return {ranking, kNoSignals};
+}
+
+using LayerRequirementPtrs = std::vector<const RefreshRateSelector::LayerRequirement*>;
+using PerUidLayerRequirements = std::unordered_map<uid_t, LayerRequirementPtrs>;
+
+PerUidLayerRequirements groupLayersByUid(
+        const std::vector<RefreshRateSelector::LayerRequirement>& layers) {
+    PerUidLayerRequirements layersByUid;
+    for (const auto& layer : layers) {
+        const auto it = layersByUid.emplace(layer.ownerUid, LayerRequirementPtrs()).first;
+        auto& layersWithSameUid = it->second;
+        layersWithSameUid.push_back(&layer);
+    }
+
+    // Remove uids that can't have a frame rate override
+    for (auto it = layersByUid.begin(); it != layersByUid.end();) {
+        const auto& layersWithSameUid = it->second;
+        bool skipUid = false;
+        for (const auto& layer : layersWithSameUid) {
+            using LayerVoteType = RefreshRateSelector::LayerVoteType;
+
+            if (layer->vote == LayerVoteType::Max || layer->vote == LayerVoteType::Heuristic) {
+                skipUid = true;
+                break;
+            }
+        }
+        if (skipUid) {
+            it = layersByUid.erase(it);
+        } else {
+            ++it;
+        }
+    }
+
+    return layersByUid;
+}
+
+auto RefreshRateSelector::getFrameRateOverrides(const std::vector<LayerRequirement>& layers,
+                                                Fps displayRefreshRate,
+                                                GlobalSignals globalSignals) const
+        -> UidToFrameRateOverride {
+    ATRACE_CALL();
+    if (mConfig.enableFrameRateOverride == Config::FrameRateOverride::Disabled) {
+        return {};
+    }
+
+    ALOGV("%s: %zu layers", __func__, layers.size());
+    std::lock_guard lock(mLock);
+
+    const auto* policyPtr = getCurrentPolicyLocked();
+    // We don't want to run lower than 30fps
+    const Fps minFrameRate = std::max(policyPtr->appRequestRanges.render.min, 30_Hz, isApproxLess);
+
+    using fps_approx_ops::operator/;
+    const unsigned numMultiples = displayRefreshRate / minFrameRate;
+
+    std::vector<std::pair<Fps, float>> scoredFrameRates;
+    scoredFrameRates.reserve(numMultiples);
+
+    for (unsigned n = numMultiples; n > 0; n--) {
+        const Fps divisor = displayRefreshRate / n;
+        if (mConfig.enableFrameRateOverride ==
+                    Config::FrameRateOverride::AppOverrideNativeRefreshRates &&
+            !isNativeRefreshRate(divisor)) {
+            continue;
+        }
+
+        if (policyPtr->appRequestRanges.render.includes(divisor)) {
+            ALOGV("%s: adding %s as a potential frame rate", __func__, to_string(divisor).c_str());
+            scoredFrameRates.emplace_back(divisor, 0);
+        }
+    }
+
+    const auto layersByUid = groupLayersByUid(layers);
+    UidToFrameRateOverride frameRateOverrides;
+    for (const auto& [uid, layersWithSameUid] : layersByUid) {
+        // Layers with ExplicitExactOrMultiple expect touch boost
+        const bool hasExplicitExactOrMultiple =
+                std::any_of(layersWithSameUid.cbegin(), layersWithSameUid.cend(),
+                            [](const auto& layer) {
+                                return layer->vote == LayerVoteType::ExplicitExactOrMultiple;
+                            });
+
+        if (globalSignals.touch && hasExplicitExactOrMultiple) {
+            continue;
+        }
+
+        for (auto& [_, score] : scoredFrameRates) {
+            score = 0;
+        }
+
+        for (const auto& layer : layersWithSameUid) {
+            if (layer->vote == LayerVoteType::NoVote || layer->vote == LayerVoteType::Min) {
+                continue;
+            }
+
+            LOG_ALWAYS_FATAL_IF(layer->vote != LayerVoteType::ExplicitDefault &&
+                                layer->vote != LayerVoteType::ExplicitExactOrMultiple &&
+                                layer->vote != LayerVoteType::ExplicitExact);
+            for (auto& [fps, score] : scoredFrameRates) {
+                constexpr bool isSeamlessSwitch = true;
+                const auto layerScore = calculateLayerScoreLocked(*layer, fps, isSeamlessSwitch);
+                score += layer->weight * layerScore;
+            }
+        }
+
+        // If we never scored any layers, we don't have a preferred frame rate
+        if (std::all_of(scoredFrameRates.begin(), scoredFrameRates.end(),
+                        [](const auto& scoredFrameRate) {
+                            const auto [_, score] = scoredFrameRate;
+                            return score == 0;
+                        })) {
+            continue;
+        }
+
+        // Now that we scored all the refresh rates we need to pick the lowest refresh rate
+        // that got the highest score.
+        const auto [overrideFps, _] =
+                *std::max_element(scoredFrameRates.begin(), scoredFrameRates.end(),
+                                  [](const auto& lhsPair, const auto& rhsPair) {
+                                      const float lhs = lhsPair.second;
+                                      const float rhs = rhsPair.second;
+                                      return lhs < rhs && !ScoredFrameRate::scoresEqual(lhs, rhs);
+                                  });
+        ALOGV("%s: overriding to %s for uid=%d", __func__, to_string(overrideFps).c_str(), uid);
+        frameRateOverrides.emplace(uid, overrideFps);
+    }
+
+    return frameRateOverrides;
+}
+
+ftl::Optional<FrameRateMode> RefreshRateSelector::onKernelTimerChanged(
+        std::optional<DisplayModeId> desiredActiveModeId, 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->getFps(), ftl::as_non_null(modePtr)};
+        }
+
+        return getActiveModeLocked();
+    }();
+
+    const DisplayModePtr& min = mMinRefreshRateModeIt->second;
+    if (current.modePtr->getId() == min->getId()) {
+        return {};
+    }
+
+    return timerExpired ? FrameRateMode{min->getFps(), ftl::as_non_null(min)} : current;
+}
+
+const DisplayModePtr& RefreshRateSelector::getMinRefreshRateByPolicyLocked() const {
+    const auto& activeMode = *getActiveModeLocked().modePtr;
+
+    for (const FrameRateMode& mode : mPrimaryFrameRates) {
+        if (activeMode.getGroup() == mode.modePtr->getGroup()) {
+            return mode.modePtr.get();
+        }
+    }
+
+    ALOGE("Can't find min refresh rate by policy with the same mode group as the current mode %s",
+          to_string(activeMode).c_str());
+
+    // Default to the lowest refresh rate.
+    return mPrimaryFrameRates.front().modePtr.get();
+}
+
+const DisplayModePtr& RefreshRateSelector::getMaxRefreshRateByPolicyLocked(int anchorGroup) const {
+    const ftl::NonNull<DisplayModePtr>* maxByAnchor = &mPrimaryFrameRates.back().modePtr;
+    const ftl::NonNull<DisplayModePtr>* max = &mPrimaryFrameRates.back().modePtr;
+
+    bool maxByAnchorFound = false;
+    for (auto it = mPrimaryFrameRates.rbegin(); it != mPrimaryFrameRates.rend(); ++it) {
+        using namespace fps_approx_ops;
+        if (it->modePtr->getFps() > (*max)->getFps()) {
+            max = &it->modePtr;
+        }
+
+        if (anchorGroup == it->modePtr->getGroup() &&
+            it->modePtr->getFps() >= (*maxByAnchor)->getFps()) {
+            maxByAnchorFound = true;
+            maxByAnchor = &it->modePtr;
+        }
+    }
+
+    if (maxByAnchorFound) {
+        return maxByAnchor->get();
+    }
+
+    ALOGE("Can't find max refresh rate by policy with the same group %d", anchorGroup);
+
+    // Default to the highest refresh rate.
+    return max->get();
+}
+
+auto RefreshRateSelector::rankFrameRates(std::optional<int> anchorGroupOpt,
+                                         RefreshRateOrder refreshRateOrder,
+                                         std::optional<DisplayModeId> preferredDisplayModeOpt) const
+        -> FrameRateRanking {
+    const char* const whence = __func__;
+    std::deque<ScoredFrameRate> ranking;
+    const auto rankFrameRate = [&](const FrameRateMode& frameRateMode) REQUIRES(mLock) {
+        const auto& modePtr = frameRateMode.modePtr;
+        if (anchorGroupOpt && modePtr->getGroup() != anchorGroupOpt) {
+            return;
+        }
+
+        float score = calculateDistanceScoreFromMax(frameRateMode.fps);
+        const bool inverseScore = (refreshRateOrder == RefreshRateOrder::Ascending);
+        if (inverseScore) {
+            score = 1.0f / score;
+        }
+        if (preferredDisplayModeOpt) {
+            if (*preferredDisplayModeOpt == modePtr->getId()) {
+                constexpr float kScore = std::numeric_limits<float>::max();
+                ranking.emplace_front(ScoredFrameRate{frameRateMode, kScore});
+                return;
+            }
+            constexpr float kNonPreferredModePenalty = 0.95f;
+            score *= kNonPreferredModePenalty;
+        }
+        ALOGV("%s(%s) %s (%s) scored %.2f", whence, ftl::enum_string(refreshRateOrder).c_str(),
+              to_string(frameRateMode.fps).c_str(), to_string(modePtr->getFps()).c_str(), score);
+        ranking.emplace_back(ScoredFrameRate{frameRateMode, score});
+    };
+
+    if (refreshRateOrder == RefreshRateOrder::Ascending) {
+        std::for_each(mPrimaryFrameRates.begin(), mPrimaryFrameRates.end(), rankFrameRate);
+    } else {
+        std::for_each(mPrimaryFrameRates.rbegin(), mPrimaryFrameRates.rend(), rankFrameRate);
+    }
+
+    if (!ranking.empty() || !anchorGroupOpt) {
+        return {ranking.begin(), ranking.end()};
+    }
+
+    ALOGW("Can't find %s refresh rate by policy with the same mode group"
+          " as the mode group %d",
+          refreshRateOrder == RefreshRateOrder::Ascending ? "min" : "max", anchorGroupOpt.value());
+
+    constexpr std::optional<int> kNoAnchorGroup = std::nullopt;
+    return rankFrameRates(kNoAnchorGroup, refreshRateOrder, preferredDisplayModeOpt);
+}
+
+FrameRateMode RefreshRateSelector::getActiveMode() const {
+    std::lock_guard lock(mLock);
+    return getActiveModeLocked();
+}
+
+const FrameRateMode& RefreshRateSelector::getActiveModeLocked() const {
+    return *mActiveModeOpt;
+}
+
+void RefreshRateSelector::setActiveMode(DisplayModeId modeId, Fps renderFrameRate) {
+    std::lock_guard lock(mLock);
+
+    // Invalidate the cached invocation to getRankedFrameRates. This forces
+    // the refresh rate to be recomputed on the next call to getRankedFrameRates.
+    mGetRankedFrameRatesCache.reset();
+
+    const auto activeModeOpt = mDisplayModes.get(modeId);
+    LOG_ALWAYS_FATAL_IF(!activeModeOpt);
+
+    mActiveModeOpt.emplace(FrameRateMode{renderFrameRate, ftl::as_non_null(activeModeOpt->get())});
+}
+
+RefreshRateSelector::RefreshRateSelector(DisplayModes modes, DisplayModeId activeModeId,
+                                         Config config)
+      : mKnownFrameRates(constructKnownFrameRates(modes)), mConfig(config) {
+    initializeIdleTimer();
+    FTL_FAKE_GUARD(kMainThreadContext, updateDisplayModes(std::move(modes), activeModeId));
+}
+
+void RefreshRateSelector::initializeIdleTimer() {
+    if (mConfig.idleTimerTimeout > 0ms) {
+        mIdleTimer.emplace(
+                "IdleTimer", mConfig.idleTimerTimeout,
+                [this] {
+                    std::scoped_lock lock(mIdleTimerCallbacksMutex);
+                    if (const auto callbacks = getIdleTimerCallbacks()) {
+                        callbacks->onReset();
+                    }
+                },
+                [this] {
+                    std::scoped_lock lock(mIdleTimerCallbacksMutex);
+                    if (const auto callbacks = getIdleTimerCallbacks()) {
+                        callbacks->onExpired();
+                    }
+                });
+    }
+}
+
+void RefreshRateSelector::updateDisplayModes(DisplayModes modes, DisplayModeId activeModeId) {
+    std::lock_guard lock(mLock);
+
+    // Invalidate the cached invocation to getRankedFrameRates. This forces
+    // the refresh rate to be recomputed on the next call to getRankedFrameRates.
+    mGetRankedFrameRatesCache.reset();
+
+    mDisplayModes = std::move(modes);
+    const auto activeModeOpt = mDisplayModes.get(activeModeId);
+    LOG_ALWAYS_FATAL_IF(!activeModeOpt);
+    mActiveModeOpt =
+            FrameRateMode{activeModeOpt->get()->getFps(), ftl::as_non_null(activeModeOpt->get())};
+
+    const auto sortedModes = sortByRefreshRate(mDisplayModes);
+    mMinRefreshRateModeIt = sortedModes.front();
+    mMaxRefreshRateModeIt = sortedModes.back();
+
+    // Reset the policy because the old one may no longer be valid.
+    mDisplayManagerPolicy = {};
+    mDisplayManagerPolicy.defaultMode = activeModeId;
+
+    mFrameRateOverrideConfig = [&] {
+        switch (mConfig.enableFrameRateOverride) {
+            case Config::FrameRateOverride::Disabled:
+            case Config::FrameRateOverride::AppOverride:
+            case Config::FrameRateOverride::Enabled:
+                return mConfig.enableFrameRateOverride;
+            case Config::FrameRateOverride::AppOverrideNativeRefreshRates:
+                return shouldEnableFrameRateOverride(sortedModes)
+                        ? Config::FrameRateOverride::AppOverrideNativeRefreshRates
+                        : Config::FrameRateOverride::Disabled;
+        }
+    }();
+
+    if (mConfig.enableFrameRateOverride ==
+        Config::FrameRateOverride::AppOverrideNativeRefreshRates) {
+        for (const auto& [_, mode] : mDisplayModes) {
+            mAppOverrideNativeRefreshRates.try_emplace(mode->getFps(), ftl::unit);
+        }
+    }
+
+    constructAvailableRefreshRates();
+}
+
+bool RefreshRateSelector::isPolicyValidLocked(const Policy& policy) const {
+    // defaultMode must be a valid mode, and within the given refresh rate range.
+    if (const auto mode = mDisplayModes.get(policy.defaultMode)) {
+        if (!policy.primaryRanges.physical.includes(mode->get()->getFps())) {
+            ALOGE("Default mode is not in the primary range.");
+            return false;
+        }
+    } else {
+        ALOGE("Default mode is not found.");
+        return false;
+    }
+
+    const auto& primaryRanges = policy.primaryRanges;
+    const auto& appRequestRanges = policy.appRequestRanges;
+    ALOGE_IF(!appRequestRanges.physical.includes(primaryRanges.physical),
+             "Physical range is invalid: primary: %s appRequest: %s",
+             to_string(primaryRanges.physical).c_str(),
+             to_string(appRequestRanges.physical).c_str());
+    ALOGE_IF(!appRequestRanges.render.includes(primaryRanges.render),
+             "Render range is invalid: primary: %s appRequest: %s",
+             to_string(primaryRanges.render).c_str(), to_string(appRequestRanges.render).c_str());
+
+    return primaryRanges.valid() && appRequestRanges.valid();
+}
+
+auto RefreshRateSelector::setPolicy(const PolicyVariant& policy) -> SetPolicyResult {
+    Policy oldPolicy;
+    PhysicalDisplayId displayId;
+    {
+        std::lock_guard lock(mLock);
+        oldPolicy = *getCurrentPolicyLocked();
+
+        const bool valid = ftl::match(
+                policy,
+                [this](const auto& policy) {
+                    ftl::FakeGuard guard(mLock);
+                    if (!isPolicyValidLocked(policy)) {
+                        ALOGE("Invalid policy: %s", policy.toString().c_str());
+                        return false;
+                    }
+
+                    using T = std::decay_t<decltype(policy)>;
+
+                    if constexpr (std::is_same_v<T, DisplayManagerPolicy>) {
+                        mDisplayManagerPolicy = policy;
+                    } else {
+                        static_assert(std::is_same_v<T, OverridePolicy>);
+                        mOverridePolicy = policy;
+                    }
+                    return true;
+                },
+                [this](NoOverridePolicy) {
+                    ftl::FakeGuard guard(mLock);
+                    mOverridePolicy.reset();
+                    return true;
+                });
+
+        if (!valid) {
+            return SetPolicyResult::Invalid;
+        }
+
+        mGetRankedFrameRatesCache.reset();
+
+        if (*getCurrentPolicyLocked() == oldPolicy) {
+            return SetPolicyResult::Unchanged;
+        }
+        constructAvailableRefreshRates();
+
+        displayId = getActiveModeLocked().modePtr->getPhysicalDisplayId();
+    }
+
+    const unsigned numModeChanges = std::exchange(mNumModeSwitchesInPolicy, 0u);
+
+    ALOGI("Display %s policy changed\n"
+          "Previous: %s\n"
+          "Current:  %s\n"
+          "%u mode changes were performed under the previous policy",
+          to_string(displayId).c_str(), oldPolicy.toString().c_str(), toString(policy).c_str(),
+          numModeChanges);
+
+    return SetPolicyResult::Changed;
+}
+
+auto RefreshRateSelector::getCurrentPolicyLocked() const -> const Policy* {
+    return mOverridePolicy ? &mOverridePolicy.value() : &mDisplayManagerPolicy;
+}
+
+auto RefreshRateSelector::getCurrentPolicy() const -> Policy {
+    std::lock_guard lock(mLock);
+    return *getCurrentPolicyLocked();
+}
+
+auto RefreshRateSelector::getDisplayManagerPolicy() const -> Policy {
+    std::lock_guard lock(mLock);
+    return mDisplayManagerPolicy;
+}
+
+bool RefreshRateSelector::isModeAllowed(const FrameRateMode& mode) const {
+    std::lock_guard lock(mLock);
+    return std::find(mAppRequestFrameRates.begin(), mAppRequestFrameRates.end(), mode) !=
+            mAppRequestFrameRates.end();
+}
+
+void RefreshRateSelector::constructAvailableRefreshRates() {
+    // Filter modes based on current policy and sort on refresh rate.
+    const Policy* policy = getCurrentPolicyLocked();
+    ALOGV("%s: %s ", __func__, policy->toString().c_str());
+
+    const auto& defaultMode = mDisplayModes.get(policy->defaultMode)->get();
+
+    const auto filterRefreshRates = [&](const FpsRanges& ranges,
+                                        const char* rangeName) REQUIRES(mLock) {
+        const auto filterModes = [&](const DisplayMode& mode) {
+            return mode.getResolution() == defaultMode->getResolution() &&
+                    mode.getDpi() == defaultMode->getDpi() &&
+                    (policy->allowGroupSwitching || mode.getGroup() == defaultMode->getGroup()) &&
+                    ranges.physical.includes(mode.getFps()) &&
+                    (supportsFrameRateOverride() || ranges.render.includes(mode.getFps()));
+        };
+
+        const auto frameRateModes = createFrameRateModes(filterModes, ranges.render);
+        LOG_ALWAYS_FATAL_IF(frameRateModes.empty(),
+                            "No matching frame rate modes for %s range. policy: %s", rangeName,
+                            policy->toString().c_str());
+
+        const auto stringifyModes = [&] {
+            std::string str;
+            for (const auto& frameRateMode : frameRateModes) {
+                str += to_string(frameRateMode) + " ";
+            }
+            return str;
+        };
+        ALOGV("%s render rates: %s", rangeName, stringifyModes().c_str());
+
+        return frameRateModes;
+    };
+
+    mPrimaryFrameRates = filterRefreshRates(policy->primaryRanges, "primary");
+    mAppRequestFrameRates = filterRefreshRates(policy->appRequestRanges, "app request");
+}
+
+Fps RefreshRateSelector::findClosestKnownFrameRate(Fps frameRate) const {
+    using namespace fps_approx_ops;
+
+    if (frameRate <= mKnownFrameRates.front()) {
+        return mKnownFrameRates.front();
+    }
+
+    if (frameRate >= mKnownFrameRates.back()) {
+        return mKnownFrameRates.back();
+    }
+
+    auto lowerBound = std::lower_bound(mKnownFrameRates.begin(), mKnownFrameRates.end(), frameRate,
+                                       isStrictlyLess);
+
+    const auto distance1 = std::abs(frameRate.getValue() - lowerBound->getValue());
+    const auto distance2 = std::abs(frameRate.getValue() - std::prev(lowerBound)->getValue());
+    return distance1 < distance2 ? *lowerBound : *std::prev(lowerBound);
+}
+
+auto RefreshRateSelector::getIdleTimerAction() const -> KernelIdleTimerAction {
+    std::lock_guard lock(mLock);
+
+    const Fps deviceMinFps = mMinRefreshRateModeIt->second->getFps();
+    const DisplayModePtr& minByPolicy = getMinRefreshRateByPolicyLocked();
+
+    // Kernel idle timer will set the refresh rate to the device min. If DisplayManager says that
+    // the min allowed refresh rate is higher than the device min, we do not want to enable the
+    // timer.
+    if (isStrictlyLess(deviceMinFps, minByPolicy->getFps())) {
+        return KernelIdleTimerAction::TurnOff;
+    }
+
+    const DisplayModePtr& maxByPolicy =
+            getMaxRefreshRateByPolicyLocked(getActiveModeLocked().modePtr->getGroup());
+    if (minByPolicy == maxByPolicy) {
+        // Turn on the timer when the min of the primary range is below the device min.
+        if (const Policy* currentPolicy = getCurrentPolicyLocked();
+            isApproxLess(currentPolicy->primaryRanges.physical.min, deviceMinFps)) {
+            return KernelIdleTimerAction::TurnOn;
+        }
+        return KernelIdleTimerAction::TurnOff;
+    }
+
+    // Turn on the timer in all other cases.
+    return KernelIdleTimerAction::TurnOn;
+}
+
+int RefreshRateSelector::getFrameRateDivisor(Fps displayRefreshRate, Fps layerFrameRate) {
+    // This calculation needs to be in sync with the java code
+    // in DisplayManagerService.getDisplayInfoForFrameRateOverride
+
+    // The threshold must be smaller than 0.001 in order to differentiate
+    // between the fractional pairs (e.g. 59.94 and 60).
+    constexpr float kThreshold = 0.0009f;
+    const auto numPeriods = displayRefreshRate.getValue() / layerFrameRate.getValue();
+    const auto numPeriodsRounded = std::round(numPeriods);
+    if (std::abs(numPeriods - numPeriodsRounded) > kThreshold) {
+        return 0;
+    }
+
+    return static_cast<int>(numPeriodsRounded);
+}
+
+bool RefreshRateSelector::isFractionalPairOrMultiple(Fps smaller, Fps bigger) {
+    if (isStrictlyLess(bigger, smaller)) {
+        return isFractionalPairOrMultiple(bigger, smaller);
+    }
+
+    const auto multiplier = std::round(bigger.getValue() / smaller.getValue());
+    constexpr float kCoef = 1000.f / 1001.f;
+    return isApproxEqual(bigger, Fps::fromValue(smaller.getValue() * multiplier / kCoef)) ||
+            isApproxEqual(bigger, Fps::fromValue(smaller.getValue() * multiplier * kCoef));
+}
+
+void RefreshRateSelector::dump(utils::Dumper& dumper) const {
+    using namespace std::string_view_literals;
+
+    std::lock_guard lock(mLock);
+
+    const auto activeMode = getActiveModeLocked();
+    dumper.dump("activeMode"sv, to_string(activeMode));
+
+    dumper.dump("displayModes"sv);
+    {
+        utils::Dumper::Indent indent(dumper);
+        for (const auto& [id, mode] : mDisplayModes) {
+            dumper.dump({}, to_string(*mode));
+        }
+    }
+
+    dumper.dump("displayManagerPolicy"sv, mDisplayManagerPolicy.toString());
+
+    if (const Policy& currentPolicy = *getCurrentPolicyLocked();
+        mOverridePolicy && currentPolicy != mDisplayManagerPolicy) {
+        dumper.dump("overridePolicy"sv, currentPolicy.toString());
+    }
+
+    dumper.dump("frameRateOverrideConfig"sv, *ftl::enum_name(mFrameRateOverrideConfig));
+
+    dumper.dump("idleTimer"sv);
+    {
+        utils::Dumper::Indent indent(dumper);
+        dumper.dump("interval"sv, mIdleTimer.transform(&OneShotTimer::interval));
+        dumper.dump("controller"sv,
+                    mConfig.kernelIdleTimerController
+                            .and_then(&ftl::enum_name<KernelIdleTimerController>)
+                            .value_or("Platform"sv));
+    }
+}
+
+std::chrono::milliseconds RefreshRateSelector::getIdleTimerTimeout() {
+    return mConfig.idleTimerTimeout;
+}
+
+} // namespace android::scheduler
+
+// TODO(b/129481165): remove the #pragma below and fix conversion issues
+#pragma clang diagnostic pop // ignored "-Wextra"
diff --git a/services/surfaceflinger/Scheduler/RefreshRateConfigs.h b/services/surfaceflinger/Scheduler/RefreshRateSelector.h
similarity index 65%
rename from services/surfaceflinger/Scheduler/RefreshRateConfigs.h
rename to services/surfaceflinger/Scheduler/RefreshRateSelector.h
index 7219584..4f5842a 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateConfigs.h
+++ b/services/surfaceflinger/Scheduler/RefreshRateSelector.h
@@ -18,14 +18,17 @@
 
 #include <algorithm>
 #include <numeric>
-#include <optional>
 #include <type_traits>
 #include <utility>
 #include <variant>
 
+#include <ftl/concat.h>
+#include <ftl/optional.h>
+#include <ftl/unit.h>
 #include <gui/DisplayEventReceiver.h>
 
 #include <scheduler/Fps.h>
+#include <scheduler/FrameRateMode.h>
 #include <scheduler/Seamlessness.h>
 
 #include "DisplayHardware/DisplayMode.h"
@@ -46,28 +49,20 @@
     return static_cast<DisplayModeEvent>(static_cast<T>(lhs) | static_cast<T>(rhs));
 }
 
-struct RefreshRateRanking {
-    DisplayModePtr displayModePtr;
-    float score = 0.0f;
-
-    bool operator==(const RefreshRateRanking& ranking) const {
-        return displayModePtr == ranking.displayModePtr && score == ranking.score;
-    }
-};
-
 using FrameRateOverride = DisplayEventReceiver::Event::FrameRateOverride;
 
-/**
- * This class is used to encapsulate configuration for refresh rates. It holds information
- * about available refresh rates on the device, and the mapping between the numbers and human
- * readable names.
- */
-class RefreshRateConfigs {
+// Selects the refresh rate of a display by ranking its `DisplayModes` in accordance with
+// the DisplayManager (or override) `Policy`, the `LayerRequirement` of each active layer,
+// and `GlobalSignals`.
+class RefreshRateSelector {
 public:
     // Margin used when matching refresh rates to the content desired ones.
     static constexpr nsecs_t MARGIN_FOR_PERIOD_CALCULATION =
             std::chrono::nanoseconds(800us).count();
 
+    // The lowest Render Frame Rate that will ever be selected
+    static constexpr Fps kMinSupportedFrameRate = 20_Hz;
+
     class Policy {
         static constexpr int kAllowGroupSwitchingDefault = false;
 
@@ -77,40 +72,31 @@
         DisplayModeId defaultMode;
         // Whether or not we switch mode groups to get the best frame rate.
         bool allowGroupSwitching = kAllowGroupSwitchingDefault;
-        // The primary refresh rate range represents display manager's general guidance on the
-        // display modes we'll consider when switching refresh rates. Unless we get an explicit
-        // signal from an app, we should stay within this range.
-        FpsRange primaryRange;
-        // The app request refresh rate range allows us to consider more display modes when
-        // switching refresh rates. Although we should generally stay within the primary range,
-        // specific considerations, such as layer frame rate settings specified via the
-        // setFrameRate() api, may cause us to go outside the primary range. We never go outside the
-        // app request range. The app request range will be greater than or equal to the primary
-        // refresh rate range, never smaller.
-        FpsRange appRequestRange;
+        // The primary refresh rate ranges. @see DisplayModeSpecs.aidl for details.
+        // TODO(b/257072060): use the render range when selecting SF render rate
+        //  or the app override frame rate
+        FpsRanges primaryRanges;
+        // The app request refresh rate ranges. @see DisplayModeSpecs.aidl for details.
+        FpsRanges appRequestRanges;
 
         Policy() = default;
 
-        Policy(DisplayModeId defaultMode, FpsRange range)
-              : Policy(defaultMode, kAllowGroupSwitchingDefault, range, range) {}
+        Policy(DisplayModeId defaultMode, FpsRange range,
+               bool allowGroupSwitching = kAllowGroupSwitchingDefault)
+              : Policy(defaultMode, FpsRanges{range, range}, FpsRanges{range, range},
+                       allowGroupSwitching) {}
 
-        Policy(DisplayModeId defaultMode, bool allowGroupSwitching, FpsRange range)
-              : Policy(defaultMode, allowGroupSwitching, range, range) {}
-
-        Policy(DisplayModeId defaultMode, FpsRange primaryRange, FpsRange appRequestRange)
-              : Policy(defaultMode, kAllowGroupSwitchingDefault, primaryRange, appRequestRange) {}
-
-        Policy(DisplayModeId defaultMode, bool allowGroupSwitching, FpsRange primaryRange,
-               FpsRange appRequestRange)
+        Policy(DisplayModeId defaultMode, FpsRanges primaryRanges, FpsRanges appRequestRanges,
+               bool allowGroupSwitching = kAllowGroupSwitchingDefault)
               : defaultMode(defaultMode),
                 allowGroupSwitching(allowGroupSwitching),
-                primaryRange(primaryRange),
-                appRequestRange(appRequestRange) {}
+                primaryRanges(primaryRanges),
+                appRequestRanges(appRequestRanges) {}
 
         bool operator==(const Policy& other) const {
             using namespace fps_approx_ops;
-            return defaultMode == other.defaultMode && primaryRange == other.primaryRange &&
-                    appRequestRange == other.appRequestRange &&
+            return defaultMode == other.defaultMode && primaryRanges == other.primaryRanges &&
+                    appRequestRanges == other.appRequestRanges &&
                     allowGroupSwitching == other.allowGroupSwitching;
         }
 
@@ -147,7 +133,7 @@
     Policy getDisplayManagerPolicy() const EXCLUDES(mLock);
 
     // Returns true if mode is allowed by the current policy.
-    bool isModeAllowed(DisplayModeId) const EXCLUDES(mLock);
+    bool isModeAllowed(const FrameRateMode&) const EXCLUDES(mLock);
 
     // Describes the different options the layer voted for refresh rate
     enum class LayerVoteType {
@@ -208,26 +194,60 @@
             return touch == other.touch && idle == other.idle &&
                     powerOnImminent == other.powerOnImminent;
         }
+
+        auto toString() const {
+            return ftl::Concat("{touch=", touch, ", idle=", idle,
+                               ", powerOnImminent=", powerOnImminent, '}');
+        }
     };
 
-    // Returns the list in the descending order of refresh rates desired
-    // based on their overall score, and the GlobalSignals that were considered.
-    std::pair<std::vector<RefreshRateRanking>, GlobalSignals> getRankedRefreshRates(
-            const std::vector<LayerRequirement>&, GlobalSignals) const EXCLUDES(mLock);
+    struct ScoredFrameRate {
+        FrameRateMode frameRateMode;
+        float score = 0.0f;
+
+        bool operator==(const ScoredFrameRate& other) const {
+            return frameRateMode == other.frameRateMode && score == other.score;
+        }
+
+        static bool scoresEqual(float lhs, float rhs) {
+            constexpr float kEpsilon = 0.0001f;
+            return std::abs(lhs - rhs) <= kEpsilon;
+        }
+
+        struct DescendingScore {
+            bool operator()(const ScoredFrameRate& lhs, const ScoredFrameRate& rhs) const {
+                return lhs.score > rhs.score && !scoresEqual(lhs.score, rhs.score);
+            }
+        };
+    };
+
+    using FrameRateRanking = std::vector<ScoredFrameRate>;
+
+    struct RankedFrameRates {
+        FrameRateRanking ranking; // Ordered by descending score.
+        GlobalSignals consideredSignals;
+
+        bool operator==(const RankedFrameRates& other) const {
+            return ranking == other.ranking && consideredSignals == other.consideredSignals;
+        }
+    };
+
+    RankedFrameRates getRankedFrameRates(const std::vector<LayerRequirement>&, GlobalSignals) const
+            EXCLUDES(mLock);
 
     FpsRange getSupportedRefreshRateRange() const EXCLUDES(mLock) {
         std::lock_guard lock(mLock);
         return {mMinRefreshRateModeIt->second->getFps(), mMaxRefreshRateModeIt->second->getFps()};
     }
 
-    std::optional<Fps> onKernelTimerChanged(std::optional<DisplayModeId> desiredActiveModeId,
-                                            bool timerExpired) const EXCLUDES(mLock);
+    ftl::Optional<FrameRateMode> onKernelTimerChanged(
+            std::optional<DisplayModeId> desiredActiveModeId, bool timerExpired) const
+            EXCLUDES(mLock);
 
-    void setActiveModeId(DisplayModeId) EXCLUDES(mLock) REQUIRES(kMainThreadContext);
+    void setActiveMode(DisplayModeId, Fps renderFrameRate) EXCLUDES(mLock);
 
-    // See mActiveModeIt for thread safety.
-    DisplayModePtr getActiveModePtr() const EXCLUDES(mLock);
-    const DisplayMode& getActiveMode() const REQUIRES(kMainThreadContext);
+    // See mActiveModeOpt for thread safety.
+    FrameRateMode getActiveMode() const EXCLUDES(mLock);
 
     // Returns a known frame rate that is the closest to frameRate
     Fps findClosestKnownFrameRate(Fps frameRate) const;
@@ -236,7 +256,23 @@
 
     // Configuration flags.
     struct Config {
-        bool enableFrameRateOverride = false;
+        enum class FrameRateOverride {
+            // Do not override the frame rate for an app
+            Disabled,
+
+            // Override the frame rate for an app to a value which is also
+            // a display refresh rate
+            AppOverrideNativeRefreshRates,
+
+            // Override the frame rate for an app to any value
+            AppOverride,
+
+            // Override the frame rate for all apps and all values.
+            Enabled,
+
+            ftl_last = Enabled
+        };
+        FrameRateOverride enableFrameRateOverride = FrameRateOverride::Disabled;
 
         // Specifies the upper refresh rate threshold (inclusive) for layer vote types of multiple
         // or heuristic, such that refresh rates higher than this value will not be voted for. 0 if
@@ -248,37 +284,47 @@
 
         // The controller representing how the kernel idle timer will be configured
         // either on the HWC api or sysprop.
-        std::optional<KernelIdleTimerController> kernelIdleTimerController;
+        ftl::Optional<KernelIdleTimerController> kernelIdleTimerController;
     };
 
-    RefreshRateConfigs(DisplayModes, DisplayModeId activeModeId,
-                       Config config = {.enableFrameRateOverride = false,
-                                        .frameRateMultipleThreshold = 0,
-                                        .idleTimerTimeout = 0ms,
-                                        .kernelIdleTimerController = {}});
+    RefreshRateSelector(
+            DisplayModes, DisplayModeId activeModeId,
+            Config config = {.enableFrameRateOverride = Config::FrameRateOverride::Disabled,
+                             .frameRateMultipleThreshold = 0,
+                             .idleTimerTimeout = 0ms,
+                             .kernelIdleTimerController = {}});
 
-    RefreshRateConfigs(const RefreshRateConfigs&) = delete;
-    RefreshRateConfigs& operator=(const RefreshRateConfigs&) = delete;
+    RefreshRateSelector(const RefreshRateSelector&) = delete;
+    RefreshRateSelector& operator=(const RefreshRateSelector&) = delete;
 
     // Returns whether switching modes (refresh rate or resolution) is possible.
     // TODO(b/158780872): Consider HAL support, and skip frame rate detection if the modes only
-    // differ in resolution.
+    //  differ in resolution. Once Config::FrameRateOverride::Enabled becomes the default,
+    //  we can probably remove canSwitch altogether since all devices will be able
+    //  to switch to a frame rate divisor.
     bool canSwitch() const EXCLUDES(mLock) {
         std::lock_guard lock(mLock);
-        return mDisplayModes.size() > 1;
+        return mDisplayModes.size() > 1 ||
+                mFrameRateOverrideConfig == Config::FrameRateOverride::Enabled;
     }
 
     // Class to enumerate options around toggling the kernel timer on and off.
     enum class KernelIdleTimerAction {
-        TurnOff,  // Turn off the idle timer.
-        TurnOn    // Turn on the idle timer.
+        TurnOff, // Turn off the idle timer.
+        TurnOn   // Turn on the idle timer.
     };
 
     // Checks whether kernel idle timer should be active depending the policy decisions around
     // refresh rates.
     KernelIdleTimerAction getIdleTimerAction() const;
 
-    bool supportsFrameRateOverrideByContent() const { return mSupportsFrameRateOverrideByContent; }
+    bool supportsAppFrameRateOverrideByContent() const {
+        return mFrameRateOverrideConfig != Config::FrameRateOverride::Disabled;
+    }
+
+    bool supportsFrameRateOverride() const {
+        return mFrameRateOverrideConfig == Config::FrameRateOverride::Enabled;
+    }
 
     // Return the display refresh rate divisor to match the layer
     // frame rate, or 0 if the display refresh rate is not a multiple of the
@@ -332,14 +378,16 @@
         }
     }
 
-    void resetIdleTimer(bool kernelOnly) {
-        if (!mIdleTimer) {
-            return;
+    void resetKernelIdleTimer() {
+        if (mIdleTimer && mConfig.kernelIdleTimerController) {
+            mIdleTimer->reset();
         }
-        if (kernelOnly && !mConfig.kernelIdleTimerController.has_value()) {
-            return;
+    }
+
+    void resetIdleTimer() {
+        if (mIdleTimer) {
+            mIdleTimer->reset();
         }
-        mIdleTimer->reset();
     }
 
     void dump(utils::Dumper&) const EXCLUDES(mLock);
@@ -347,15 +395,15 @@
     std::chrono::milliseconds getIdleTimerTimeout();
 
 private:
-    friend struct TestableRefreshRateConfigs;
+    friend struct TestableRefreshRateSelector;
 
     void constructAvailableRefreshRates() REQUIRES(mLock);
 
-    // See mActiveModeIt for thread safety.
-    DisplayModeIterator getActiveModeItLocked() const REQUIRES(mLock);
+    // See mActiveModeOpt for thread safety.
+    const FrameRateMode& getActiveModeLocked() const REQUIRES(mLock);
 
-    std::pair<std::vector<RefreshRateRanking>, GlobalSignals> getRankedRefreshRatesLocked(
-            const std::vector<LayerRequirement>&, GlobalSignals) const REQUIRES(mLock);
+    RankedFrameRates getRankedFrameRatesLocked(const std::vector<LayerRequirement>& layers,
+                                               GlobalSignals signals) const REQUIRES(mLock);
 
     // Returns number of display frames and remainder when dividing the layer refresh period by
     // display refresh period.
@@ -371,19 +419,24 @@
 
     struct RefreshRateScoreComparator;
 
-    enum class RefreshRateOrder { Ascending, Descending };
+    enum class RefreshRateOrder {
+        Ascending,
+        Descending,
 
-    // Returns the rankings in RefreshRateOrder. May change at runtime.
+        ftl_last = Descending
+    };
+
     // Only uses the primary range, not the app request range.
-    std::vector<RefreshRateRanking> getRefreshRatesByPolicyLocked(
-            std::optional<int> anchorGroupOpt, RefreshRateOrder,
-            std::optional<DisplayModeId> preferredDisplayModeOpt) const REQUIRES(mLock);
+    FrameRateRanking rankFrameRates(
+            std::optional<int> anchorGroupOpt, RefreshRateOrder refreshRateOrder,
+            std::optional<DisplayModeId> preferredDisplayModeOpt = std::nullopt) const
+            REQUIRES(mLock);
 
     const Policy* getCurrentPolicyLocked() const REQUIRES(mLock);
     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 calculateRefreshRateScoreForFps(Fps refreshRate) const REQUIRES(mLock);
+    float calculateDistanceScoreFromMax(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,
@@ -404,21 +457,35 @@
                                                              : mIdleTimerCallbacks->platform;
     }
 
+    bool isNativeRefreshRate(Fps fps) const REQUIRES(mLock) {
+        LOG_ALWAYS_FATAL_IF(mConfig.enableFrameRateOverride !=
+                                    Config::FrameRateOverride::AppOverrideNativeRefreshRates,
+                            "should only be called when "
+                            "Config::FrameRateOverride::AppOverrideNativeRefreshRates is used");
+        return mAppOverrideNativeRefreshRates.contains(fps);
+    }
+
+    std::vector<FrameRateMode> createFrameRateModes(
+            std::function<bool(const DisplayMode&)>&& filterModes, const FpsRange&) const
+            REQUIRES(mLock);
+
     // The display modes of the active display. The DisplayModeIterators below are pointers into
     // this container, so must be invalidated whenever the DisplayModes change. The Policy below
     // is also dependent, so must be reset as well.
     DisplayModes mDisplayModes GUARDED_BY(mLock);
 
-    // Written under mLock exclusively from kMainThreadContext, so reads from kMainThreadContext
-    // need not be under mLock.
-    DisplayModeIterator mActiveModeIt GUARDED_BY(mLock) GUARDED_BY(kMainThreadContext);
+    // Set of supported display refresh rates for easy lookup
+    // when FrameRateOverride::AppOverrideNativeRefreshRates is in use.
+    ftl::SmallMap<Fps, ftl::Unit, 8, FpsApproxEqual> mAppOverrideNativeRefreshRates;
+
+    ftl::Optional<FrameRateMode> mActiveModeOpt GUARDED_BY(mLock);
 
     DisplayModeIterator mMinRefreshRateModeIt GUARDED_BY(mLock);
     DisplayModeIterator mMaxRefreshRateModeIt GUARDED_BY(mLock);
 
     // Display modes that satisfy the Policy's ranges, filtered and sorted by refresh rate.
-    std::vector<DisplayModeIterator> mPrimaryRefreshRates GUARDED_BY(mLock);
-    std::vector<DisplayModeIterator> mAppRequestRefreshRates GUARDED_BY(mLock);
+    std::vector<FrameRateMode> mPrimaryFrameRates GUARDED_BY(mLock);
+    std::vector<FrameRateMode> mAppRequestFrameRates GUARDED_BY(mLock);
 
     Policy mDisplayManagerPolicy GUARDED_BY(mLock);
     std::optional<Policy> mOverridePolicy GUARDED_BY(mLock);
@@ -432,19 +499,19 @@
     const std::vector<Fps> mKnownFrameRates;
 
     const Config mConfig;
-    bool mSupportsFrameRateOverrideByContent;
+    Config::FrameRateOverride mFrameRateOverrideConfig;
 
-    struct GetRankedRefreshRatesCache {
+    struct GetRankedFrameRatesCache {
         std::pair<std::vector<LayerRequirement>, GlobalSignals> arguments;
-        std::pair<std::vector<RefreshRateRanking>, GlobalSignals> result;
+        RankedFrameRates result;
     };
-    mutable std::optional<GetRankedRefreshRatesCache> mGetRankedRefreshRatesCache GUARDED_BY(mLock);
+    mutable std::optional<GetRankedFrameRatesCache> mGetRankedFrameRatesCache GUARDED_BY(mLock);
 
     // Declare mIdleTimer last to ensure its thread joins before the mutex/callbacks are destroyed.
     std::mutex mIdleTimerCallbacksMutex;
     std::optional<IdleTimerCallbacks> mIdleTimerCallbacks GUARDED_BY(mIdleTimerCallbacksMutex);
     // Used to detect (lack of) frame activity.
-    std::optional<scheduler::OneShotTimer> mIdleTimer;
+    ftl::Optional<scheduler::OneShotTimer> mIdleTimer;
 };
 
 } // namespace android::scheduler
diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp
index 037ed8f..34f8df2 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.cpp
+++ b/services/surfaceflinger/Scheduler/Scheduler.cpp
@@ -25,7 +25,9 @@
 #include <android/hardware/configstore/1.0/ISurfaceFlingerConfigs.h>
 #include <android/hardware/configstore/1.1/ISurfaceFlingerConfigs.h>
 #include <configstore/Utils.h>
+#include <ftl/enum.h>
 #include <ftl/fake_guard.h>
+#include <ftl/small_map.h>
 #include <gui/WindowInfo.h>
 #include <system/window.h>
 #include <utils/Timers.h>
@@ -41,6 +43,7 @@
 
 #include "../Layer.h"
 #include "DispSyncSource.h"
+#include "Display/DisplayMap.h"
 #include "EventThread.h"
 #include "FrameRateOverrideMappings.h"
 #include "OneShotTimer.h"
@@ -56,39 +59,6 @@
         }                                                            \
     } while (false)
 
-namespace {
-
-using android::Fps;
-using android::FpsApproxEqual;
-using android::FpsHash;
-using android::scheduler::AggregatedFpsScore;
-using android::scheduler::RefreshRateRankingsAndSignals;
-
-// Returns the aggregated score per Fps for the RefreshRateRankingsAndSignals sourced.
-auto getAggregatedScoresPerFps(
-        const std::vector<RefreshRateRankingsAndSignals>& refreshRateRankingsAndSignalsPerDisplay)
-        -> std::unordered_map<Fps, AggregatedFpsScore, FpsHash, FpsApproxEqual> {
-    std::unordered_map<Fps, AggregatedFpsScore, FpsHash, FpsApproxEqual> aggregatedScoresPerFps;
-
-    for (const auto& refreshRateRankingsAndSignal : refreshRateRankingsAndSignalsPerDisplay) {
-        const auto& refreshRateRankings = refreshRateRankingsAndSignal.refreshRateRankings;
-
-        std::for_each(refreshRateRankings.begin(), refreshRateRankings.end(), [&](const auto& it) {
-            const auto [score, result] =
-                    aggregatedScoresPerFps.try_emplace(it.displayModePtr->getFps(),
-                                                       AggregatedFpsScore{it.score,
-                                                                          /* numDisplays */ 1});
-            if (!result) { // update
-                score->second.totalScore += it.score;
-                score->second.numDisplays++;
-            }
-        });
-    }
-    return aggregatedScoresPerFps;
-}
-
-} // namespace
-
 namespace android::scheduler {
 
 Scheduler::Scheduler(ICompositor& compositor, ISchedulerCallback& callback, FeatureFlags features)
@@ -99,8 +69,8 @@
     mDisplayPowerTimer.reset();
     mTouchTimer.reset();
 
-    // Stop idle timer and clear callbacks, as the RefreshRateConfigs may outlive the Scheduler.
-    setRefreshRateConfigs(nullptr);
+    // Stop idle timer and clear callbacks, as the RefreshRateSelector may outlive the Scheduler.
+    demoteLeaderDisplay();
 }
 
 void Scheduler::startTimers() {
@@ -125,36 +95,29 @@
     }
 }
 
-void Scheduler::setRefreshRateConfigs(std::shared_ptr<RefreshRateConfigs> configs) {
-    // The current RefreshRateConfigs instance may outlive this call, so unbind its idle timer.
-    {
-        // mRefreshRateConfigsLock is not locked here to avoid the deadlock
-        // as the callback can attempt to acquire the lock before stopIdleTimer can finish
-        // the execution. It's safe to FakeGuard as main thread is the only thread that
-        // writes to the mRefreshRateConfigs.
-        ftl::FakeGuard guard(mRefreshRateConfigsLock);
-        if (mRefreshRateConfigs) {
-            mRefreshRateConfigs->stopIdleTimer();
-            mRefreshRateConfigs->clearIdleTimerCallbacks();
-        }
-    }
-    {
-        // Clear state that depends on the current instance.
-        std::scoped_lock lock(mPolicyLock);
-        mPolicy = {};
-    }
+void Scheduler::setLeaderDisplay(std::optional<PhysicalDisplayId> leaderIdOpt) {
+    demoteLeaderDisplay();
 
-    std::scoped_lock lock(mRefreshRateConfigsLock);
-    mRefreshRateConfigs = std::move(configs);
-    if (!mRefreshRateConfigs) return;
+    std::scoped_lock lock(mDisplayLock);
+    promoteLeaderDisplay(leaderIdOpt);
+}
 
-    mRefreshRateConfigs->setIdleTimerCallbacks(
-            {.platform = {.onReset = [this] { idleTimerCallback(TimerState::Reset); },
-                          .onExpired = [this] { idleTimerCallback(TimerState::Expired); }},
-             .kernel = {.onReset = [this] { kernelIdleTimerCallback(TimerState::Reset); },
-                        .onExpired = [this] { kernelIdleTimerCallback(TimerState::Expired); }}});
+void Scheduler::registerDisplay(PhysicalDisplayId displayId, RefreshRateSelectorPtr selectorPtr) {
+    demoteLeaderDisplay();
 
-    mRefreshRateConfigs->startIdleTimer();
+    std::scoped_lock lock(mDisplayLock);
+    mRefreshRateSelectors.emplace_or_replace(displayId, std::move(selectorPtr));
+
+    promoteLeaderDisplay();
+}
+
+void Scheduler::unregisterDisplay(PhysicalDisplayId displayId) {
+    demoteLeaderDisplay();
+
+    std::scoped_lock lock(mDisplayLock);
+    mRefreshRateSelectors.erase(displayId);
+
+    promoteLeaderDisplay();
 }
 
 void Scheduler::run() {
@@ -188,9 +151,8 @@
 }
 
 std::optional<Fps> Scheduler::getFrameRateOverride(uid_t uid) const {
-    const auto refreshRateConfigs = holdRefreshRateConfigs();
     const bool supportsFrameRateOverrideByContent =
-            refreshRateConfigs->supportsFrameRateOverrideByContent();
+            leaderSelectorPtr()->supportsAppFrameRateOverrideByContent();
     return mFrameRateOverrideMappings
             .getFrameRateOverrideForUid(uid, supportsFrameRateOverrideByContent);
 }
@@ -205,8 +167,6 @@
 }
 
 impl::EventThread::ThrottleVsyncCallback Scheduler::makeThrottleVsyncCallback() const {
-    std::scoped_lock lock(mRefreshRateConfigsLock);
-
     return [this](nsecs_t expectedVsyncTimestamp, uid_t uid) {
         return !isVsyncValid(TimePoint::fromNs(expectedVsyncTimestamp), uid);
     };
@@ -214,7 +174,7 @@
 
 impl::EventThread::GetVsyncPeriodFunction Scheduler::makeGetVsyncPeriodFunction() const {
     return [this](uid_t uid) {
-        const Fps refreshRate = holdRefreshRateConfigs()->getActiveModePtr()->getFps();
+        const Fps refreshRate = leaderSelectorPtr()->getActiveMode().fps;
         const nsecs_t currentPeriod = mVsyncSchedule->period().ns() ?: refreshRate.getPeriodNsecs();
 
         const auto frameRate = getFrameRateOverride(uid);
@@ -222,7 +182,7 @@
             return currentPeriod;
         }
 
-        const auto divisor = RefreshRateConfigs::getFrameRateDivisor(refreshRate, *frameRate);
+        const auto divisor = RefreshRateSelector::getFrameRateDivisor(refreshRate, *frameRate);
         if (divisor <= 1) {
             return currentPeriod;
         }
@@ -307,9 +267,8 @@
 }
 
 void Scheduler::onFrameRateOverridesChanged(ConnectionHandle handle, PhysicalDisplayId displayId) {
-    const auto refreshRateConfigs = holdRefreshRateConfigs();
     const bool supportsFrameRateOverrideByContent =
-            refreshRateConfigs->supportsFrameRateOverrideByContent();
+            leaderSelectorPtr()->supportsAppFrameRateOverrideByContent();
 
     std::vector<FrameRateOverride> overrides =
             mFrameRateOverrideMappings.getAllFrameRateOverrides(supportsFrameRateOverrideByContent);
@@ -323,7 +282,7 @@
     thread->onFrameRateOverridesChanged(displayId, std::move(overrides));
 }
 
-void Scheduler::onPrimaryDisplayModeChanged(ConnectionHandle handle, DisplayModePtr mode) {
+void Scheduler::onPrimaryDisplayModeChanged(ConnectionHandle handle, const FrameRateMode& mode) {
     {
         std::lock_guard<std::mutex> lock(mPolicyLock);
         // Cache the last reported modes for primary display.
@@ -338,7 +297,7 @@
 
 void Scheduler::dispatchCachedReportedMode() {
     // Check optional fields first.
-    if (!mPolicy.mode) {
+    if (!mPolicy.modeOpt) {
         ALOGW("No mode ID found, not dispatching cached mode.");
         return;
     }
@@ -350,22 +309,21 @@
     // If the mode is not the current mode, this means that a
     // mode change is in progress. In that case we shouldn't dispatch an event
     // as it will be dispatched when the current mode changes.
-    if (std::scoped_lock lock(mRefreshRateConfigsLock);
-        mRefreshRateConfigs->getActiveModePtr() != mPolicy.mode) {
+    if (leaderSelectorPtr()->getActiveMode() != mPolicy.modeOpt) {
         return;
     }
 
     // If there is no change from cached mode, there is no need to dispatch an event
-    if (mPolicy.mode == mPolicy.cachedModeChangedParams->mode) {
+    if (*mPolicy.modeOpt == mPolicy.cachedModeChangedParams->mode) {
         return;
     }
 
-    mPolicy.cachedModeChangedParams->mode = mPolicy.mode;
+    mPolicy.cachedModeChangedParams->mode = *mPolicy.modeOpt;
     onNonPrimaryDisplayModeChanged(mPolicy.cachedModeChangedParams->handle,
                                    mPolicy.cachedModeChangedParams->mode);
 }
 
-void Scheduler::onNonPrimaryDisplayModeChanged(ConnectionHandle handle, DisplayModePtr mode) {
+void Scheduler::onNonPrimaryDisplayModeChanged(ConnectionHandle handle, const FrameRateMode& mode) {
     android::EventThread* thread;
     {
         std::lock_guard<std::mutex> lock(mConnectionsLock);
@@ -437,6 +395,24 @@
     setVsyncPeriod(refreshRate.getPeriodNsecs());
 }
 
+void Scheduler::setRenderRate(Fps renderFrameRate) {
+    const auto mode = leaderSelectorPtr()->getActiveMode();
+
+    using fps_approx_ops::operator!=;
+    LOG_ALWAYS_FATAL_IF(renderFrameRate != mode.fps,
+                        "Mismatch in render frame rates. Selector: %s, Scheduler: %s",
+                        to_string(mode.fps).c_str(), to_string(renderFrameRate).c_str());
+
+    ALOGV("%s %s (%s)", __func__, to_string(mode.fps).c_str(),
+          to_string(mode.modePtr->getFps()).c_str());
+
+    const auto divisor = RefreshRateSelector::getFrameRateDivisor(mode.modePtr->getFps(), mode.fps);
+    LOG_ALWAYS_FATAL_IF(divisor == 0, "%s <> %s -- not divisors", to_string(mode.fps).c_str(),
+                        to_string(mode.fps).c_str());
+
+    mVsyncSchedule->getTracker().setDivisor(static_cast<unsigned>(divisor));
+}
+
 void Scheduler::resync() {
     static constexpr nsecs_t kIgnoreDelay = ms2ns(750);
 
@@ -444,10 +420,7 @@
     const nsecs_t last = mLastResyncTime.exchange(now);
 
     if (now - last > kIgnoreDelay) {
-        const auto refreshRate = [&] {
-            std::scoped_lock lock(mRefreshRateConfigsLock);
-            return mRefreshRateConfigs->getActiveModePtr()->getFps();
-        }();
+        const auto refreshRate = leaderSelectorPtr()->getActiveMode().modePtr->getFps();
         resyncToHardwareVsync(false, refreshRate);
     }
 }
@@ -506,12 +479,9 @@
 
 void Scheduler::recordLayerHistory(Layer* layer, nsecs_t presentTime,
                                    LayerHistory::LayerUpdateType updateType) {
-    {
-        std::scoped_lock lock(mRefreshRateConfigsLock);
-        if (!mRefreshRateConfigs->canSwitch()) return;
+    if (leaderSelectorPtr()->canSwitch()) {
+        mLayerHistory.record(layer, presentTime, systemTime(), updateType);
     }
-
-    mLayerHistory.record(layer, presentTime, systemTime(), updateType);
 }
 
 void Scheduler::setModeChangePending(bool pending) {
@@ -524,26 +494,23 @@
 }
 
 void Scheduler::chooseRefreshRateForContent() {
-    const auto configs = holdRefreshRateConfigs();
-    if (!configs->canSwitch()) return;
+    const auto selectorPtr = leaderSelectorPtr();
+    if (!selectorPtr->canSwitch()) return;
 
     ATRACE_CALL();
 
-    LayerHistory::Summary summary = mLayerHistory.summarize(*configs, systemTime());
+    LayerHistory::Summary summary = mLayerHistory.summarize(*selectorPtr, systemTime());
     applyPolicy(&Policy::contentRequirements, std::move(summary));
 }
 
 void Scheduler::resetIdleTimer() {
-    std::scoped_lock lock(mRefreshRateConfigsLock);
-    mRefreshRateConfigs->resetIdleTimer(/*kernelOnly*/ false);
+    leaderSelectorPtr()->resetIdleTimer();
 }
 
 void Scheduler::onTouchHint() {
     if (mTouchTimer) {
         mTouchTimer->reset();
-
-        std::scoped_lock lock(mRefreshRateConfigsLock);
-        mRefreshRateConfigs->resetIdleTimer(/*kernelOnly*/ true);
+        leaderSelectorPtr()->resetKernelIdleTimer();
     }
 }
 
@@ -568,10 +535,7 @@
 
     // TODO(145561154): cleanup the kernel idle timer implementation and the refresh rate
     // magic number
-    const Fps refreshRate = [&] {
-        std::scoped_lock lock(mRefreshRateConfigsLock);
-        return mRefreshRateConfigs->getActiveModePtr()->getFps();
-    }();
+    const Fps refreshRate = leaderSelectorPtr()->getActiveMode().modePtr->getFps();
 
     constexpr Fps FPS_THRESHOLD_FOR_KERNEL_TIMER = 65_Hz;
     using namespace fps_approx_ops;
@@ -613,22 +577,40 @@
     ATRACE_INT("ExpiredDisplayPowerTimer", static_cast<int>(state));
 }
 
-void Scheduler::dump(std::string& result) const {
-    using base::StringAppendF;
-
-    StringAppendF(&result, "+  Touch timer: %s\n",
-                  mTouchTimer ? mTouchTimer->dump().c_str() : "off");
-    StringAppendF(&result, "+  Content detection: %s %s\n\n",
-                  mFeatures.test(Feature::kContentDetection) ? "on" : "off",
-                  mLayerHistory.dump().c_str());
-
-    mFrameRateOverrideMappings.dump(result);
+void Scheduler::dump(utils::Dumper& dumper) const {
+    using namespace std::string_view_literals;
 
     {
+        utils::Dumper::Section section(dumper, "Features"sv);
+
+        for (Feature feature : ftl::enum_range<Feature>()) {
+            if (const auto flagOpt = ftl::flag_name(feature)) {
+                dumper.dump(flagOpt->substr(1), mFeatures.test(feature));
+            }
+        }
+    }
+    {
+        utils::Dumper::Section section(dumper, "Policy"sv);
+        {
+            std::scoped_lock lock(mDisplayLock);
+            ftl::FakeGuard guard(kMainThreadContext);
+            dumper.dump("leaderDisplayId"sv, mLeaderDisplayId);
+        }
+        dumper.dump("layerHistory"sv, mLayerHistory.dump());
+        dumper.dump("touchTimer"sv, mTouchTimer.transform(&OneShotTimer::interval));
+        dumper.dump("displayPowerTimer"sv, mDisplayPowerTimer.transform(&OneShotTimer::interval));
+    }
+
+    mFrameRateOverrideMappings.dump(dumper);
+    dumper.eol();
+
+    {
+        utils::Dumper::Section section(dumper, "Hardware VSYNC"sv);
+
         std::lock_guard lock(mHWVsyncLock);
-        StringAppendF(&result,
-                      "mScreenAcquired=%d mPrimaryHWVsyncEnabled=%d mHWVsyncAvailable=%d\n",
-                      mScreenAcquired.load(), mPrimaryHWVsyncEnabled, mHWVsyncAvailable);
+        dumper.dump("screenAcquired"sv, mScreenAcquired.load());
+        dumper.dump("hwVsyncAvailable"sv, mHWVsyncAvailable);
+        dumper.dump("hwVsyncEnabled"sv, mPrimaryHWVsyncEnabled);
     }
 }
 
@@ -637,73 +619,101 @@
 }
 
 bool Scheduler::updateFrameRateOverrides(GlobalSignals consideredSignals, Fps displayRefreshRate) {
-    const auto refreshRateConfigs = holdRefreshRateConfigs();
+    if (consideredSignals.idle) return false;
 
-    // we always update mFrameRateOverridesByContent here
-    // supportsFrameRateOverridesByContent will be checked
-    // when getting FrameRateOverrides from mFrameRateOverrideMappings
-    if (!consideredSignals.idle) {
-        const auto frameRateOverrides =
-                refreshRateConfigs->getFrameRateOverrides(mPolicy.contentRequirements,
-                                                          displayRefreshRate, consideredSignals);
-        return mFrameRateOverrideMappings.updateFrameRateOverridesByContent(frameRateOverrides);
+    const auto frameRateOverrides =
+            leaderSelectorPtr()->getFrameRateOverrides(mPolicy.contentRequirements,
+                                                       displayRefreshRate, consideredSignals);
+
+    // Note that RefreshRateSelector::supportsFrameRateOverrideByContent is checked when querying
+    // the FrameRateOverrideMappings rather than here.
+    return mFrameRateOverrideMappings.updateFrameRateOverridesByContent(frameRateOverrides);
+}
+
+void Scheduler::promoteLeaderDisplay(std::optional<PhysicalDisplayId> leaderIdOpt) {
+    // TODO(b/241286431): Choose the leader display.
+    mLeaderDisplayId = leaderIdOpt.value_or(mRefreshRateSelectors.begin()->first);
+    ALOGI("Display %s is the leader", to_string(*mLeaderDisplayId).c_str());
+
+    if (const auto leaderPtr = leaderSelectorPtrLocked()) {
+        leaderPtr->setIdleTimerCallbacks(
+                {.platform = {.onReset = [this] { idleTimerCallback(TimerState::Reset); },
+                              .onExpired = [this] { idleTimerCallback(TimerState::Expired); }},
+                 .kernel = {.onReset = [this] { kernelIdleTimerCallback(TimerState::Reset); },
+                            .onExpired =
+                                    [this] { kernelIdleTimerCallback(TimerState::Expired); }}});
+
+        leaderPtr->startIdleTimer();
     }
-    return false;
+}
+
+void Scheduler::demoteLeaderDisplay() {
+    // No need to lock for reads on kMainThreadContext.
+    if (const auto leaderPtr = FTL_FAKE_GUARD(mDisplayLock, leaderSelectorPtrLocked())) {
+        leaderPtr->stopIdleTimer();
+        leaderPtr->clearIdleTimerCallbacks();
+    }
+
+    // Clear state that depends on the leader's RefreshRateSelector.
+    std::scoped_lock lock(mPolicyLock);
+    mPolicy = {};
 }
 
 template <typename S, typename T>
 auto Scheduler::applyPolicy(S Policy::*statePtr, T&& newState) -> GlobalSignals {
-    DisplayModePtr newMode;
+    std::vector<display::DisplayModeRequest> modeRequests;
     GlobalSignals consideredSignals;
-    std::vector<DisplayModeConfig> displayModeConfigs;
 
     bool refreshRateChanged = false;
     bool frameRateOverridesChanged;
 
-    const auto refreshRateConfigs = holdRefreshRateConfigs();
     {
-        std::lock_guard<std::mutex> lock(mPolicyLock);
+        std::scoped_lock lock(mPolicyLock);
 
         auto& currentState = mPolicy.*statePtr;
         if (currentState == newState) return {};
         currentState = std::forward<T>(newState);
 
-        displayModeConfigs = getBestDisplayModeConfigs();
+        DisplayModeChoiceMap modeChoices;
+        ftl::Optional<FrameRateMode> modeOpt;
+        {
+            std::scoped_lock lock(mDisplayLock);
+            ftl::FakeGuard guard(kMainThreadContext);
 
-        // mPolicy holds the current mode, using the current mode we find out
-        // what display is currently being tracked through the policy and
-        // then find the DisplayModeConfig for that display. So that
-        // later we check if the policy mode has changed for the same display in policy.
-        // If mPolicy mode isn't available then we take the first display from the best display
-        // modes as the candidate for policy changes and frame rate overrides.
-        // TODO(b/240743786) Update the single display based assumptions and make mode changes
-        // and mPolicy per display.
-        const DisplayModeConfig& displayModeConfigForCurrentPolicy = mPolicy.mode
-                ? *std::find_if(displayModeConfigs.begin(), displayModeConfigs.end(),
-                                [&](const auto& displayModeConfig) REQUIRES(mPolicyLock) {
-                                    return displayModeConfig.displayModePtr
-                                                   ->getPhysicalDisplayId() ==
-                                            mPolicy.mode->getPhysicalDisplayId();
-                                })
-                : displayModeConfigs.front();
+            modeChoices = chooseDisplayModes();
 
-        newMode = displayModeConfigForCurrentPolicy.displayModePtr;
-        consideredSignals = displayModeConfigForCurrentPolicy.signals;
-        frameRateOverridesChanged = updateFrameRateOverrides(consideredSignals, newMode->getFps());
+            // TODO(b/240743786): The leader display's mode must change for any DisplayModeRequest
+            // to go through. Fix this by tracking per-display Scheduler::Policy and timers.
+            std::tie(modeOpt, consideredSignals) =
+                    modeChoices.get(*mLeaderDisplayId)
+                            .transform([](const DisplayModeChoice& choice) {
+                                return std::make_pair(choice.mode, choice.consideredSignals);
+                            })
+                            .value();
+        }
 
-        if (mPolicy.mode == newMode) {
+        modeRequests.reserve(modeChoices.size());
+        for (auto& [id, choice] : modeChoices) {
+            modeRequests.emplace_back(
+                    display::DisplayModeRequest{.mode = std::move(choice.mode),
+                                                .emitEvent = !choice.consideredSignals.idle});
+        }
+
+        frameRateOverridesChanged = updateFrameRateOverrides(consideredSignals, modeOpt->fps);
+
+        if (mPolicy.modeOpt != modeOpt) {
+            mPolicy.modeOpt = modeOpt;
+            refreshRateChanged = true;
+        } else {
             // We don't need to change the display mode, but we might need to send an event
             // about a mode change, since it was suppressed if previously considered idle.
             if (!consideredSignals.idle) {
                 dispatchCachedReportedMode();
             }
-        } else {
-            mPolicy.mode = newMode;
-            refreshRateChanged = true;
         }
     }
     if (refreshRateChanged) {
-        mSchedulerCallback.requestDisplayModes(std::move(displayModeConfigs));
+        mSchedulerCallback.requestDisplayModes(std::move(modeRequests));
     }
     if (frameRateOverridesChanged) {
         mSchedulerCallback.triggerOnFrameRateOverridesChanged();
@@ -711,97 +721,112 @@
     return consideredSignals;
 }
 
-void Scheduler::registerDisplay(const sp<const DisplayDevice>& display) {
-    const bool ok = mDisplays.try_emplace(display->getPhysicalId(), display).second;
-    ALOGE_IF(!ok, "Duplicate display registered");
-}
-
-void Scheduler::unregisterDisplay(PhysicalDisplayId displayId) {
-    mDisplays.erase(displayId);
-}
-
-std::vector<DisplayModeConfig> Scheduler::getBestDisplayModeConfigs() const {
+auto Scheduler::chooseDisplayModes() const -> DisplayModeChoiceMap {
     ATRACE_CALL();
 
-    std::vector<RefreshRateRankingsAndSignals> refreshRateRankingsAndSignalsPerDisplay;
-    refreshRateRankingsAndSignalsPerDisplay.reserve(mDisplays.size());
+    using RankedRefreshRates = RefreshRateSelector::RankedFrameRates;
+    display::PhysicalDisplayVector<RankedRefreshRates> perDisplayRanking;
 
-    const auto displayModeSelectionParams = getDisplayModeSelectionParams();
+    // Tallies the score of a refresh rate across `displayCount` displays.
+    struct RefreshRateTally {
+        explicit RefreshRateTally(float score) : score(score) {}
 
-    std::for_each(mDisplays.begin(), mDisplays.end(), [&](const auto& display) {
-        const auto& [refreshRateRankings, globalSignals] =
-                display.second->holdRefreshRateConfigs()
-                        ->getRankedRefreshRates(displayModeSelectionParams.layerRequirements,
-                                                displayModeSelectionParams.globalSignals);
-        refreshRateRankingsAndSignalsPerDisplay.emplace_back(
-                RefreshRateRankingsAndSignals{refreshRateRankings, globalSignals});
-    });
+        float score;
+        size_t displayCount = 1;
+    };
 
-    // FPS and their Aggregated score.
-    std::unordered_map<Fps, AggregatedFpsScore, FpsHash, FpsApproxEqual> aggregatedScoresPerFps =
-            getAggregatedScoresPerFps(refreshRateRankingsAndSignalsPerDisplay);
+    // Chosen to exceed a typical number of refresh rates across displays.
+    constexpr size_t kStaticCapacity = 8;
+    ftl::SmallMap<Fps, RefreshRateTally, kStaticCapacity, FpsApproxEqual> refreshRateTallies;
 
-    Fps chosenFps = std::max_element(aggregatedScoresPerFps.begin(), aggregatedScoresPerFps.end(),
-                                     [](const auto& max, const auto& current) {
-                                         return max.second.totalScore <= current.second.totalScore;
-                                     })
-                            ->first;
+    const auto globalSignals = makeGlobalSignals();
 
-    return getDisplayModeConfigsForTheChosenFps(chosenFps, refreshRateRankingsAndSignalsPerDisplay);
-}
+    for (const auto& [id, selectorPtr] : mRefreshRateSelectors) {
+        auto rankedFrameRates =
+                selectorPtr->getRankedFrameRates(mPolicy.contentRequirements, globalSignals);
 
-std::vector<DisplayModeConfig> Scheduler::getDisplayModeConfigsForTheChosenFps(
-        Fps chosenFps,
-        const std::vector<RefreshRateRankingsAndSignals>& refreshRateRankingsAndSignalsPerDisplay)
-        const {
-    std::vector<DisplayModeConfig> displayModeConfigs;
-    displayModeConfigs.reserve(mDisplays.size());
+        for (const auto& [frameRateMode, score] : rankedFrameRates.ranking) {
+            const auto [it, inserted] = refreshRateTallies.try_emplace(frameRateMode.fps, score);
+
+            if (!inserted) {
+                auto& tally = it->second;
+                tally.score += score;
+                tally.displayCount++;
+            }
+        }
+
+        perDisplayRanking.push_back(std::move(rankedFrameRates));
+    }
+
+    auto maxScoreIt = refreshRateTallies.cbegin();
+
+    // Find the first refresh rate common to all displays.
+    while (maxScoreIt != refreshRateTallies.cend() &&
+           maxScoreIt->second.displayCount != mRefreshRateSelectors.size()) {
+        ++maxScoreIt;
+    }
+
+    if (maxScoreIt != refreshRateTallies.cend()) {
+        // Choose the highest refresh rate common to all displays, if any.
+        for (auto it = maxScoreIt + 1; it != refreshRateTallies.cend(); ++it) {
+            const auto [fps, tally] = *it;
+
+            if (tally.displayCount == mRefreshRateSelectors.size() &&
+                tally.score > maxScoreIt->second.score) {
+                maxScoreIt = it;
+            }
+        }
+    }
+
+    const std::optional<Fps> chosenFps = maxScoreIt != refreshRateTallies.cend()
+            ? std::make_optional(maxScoreIt->first)
+            : std::nullopt;
+
+    DisplayModeChoiceMap modeChoices;
+
     using fps_approx_ops::operator==;
-    std::for_each(refreshRateRankingsAndSignalsPerDisplay.begin(),
-                  refreshRateRankingsAndSignalsPerDisplay.end(),
-                  [&](const auto& refreshRateRankingsAndSignal) {
-                      for (const auto& ranking : refreshRateRankingsAndSignal.refreshRateRankings) {
-                          if (ranking.displayModePtr->getFps() == chosenFps) {
-                              displayModeConfigs.emplace_back(
-                                      DisplayModeConfig{refreshRateRankingsAndSignal.globalSignals,
-                                                        ranking.displayModePtr});
-                              break;
-                          }
-                      }
-                  });
-    return displayModeConfigs;
+
+    for (auto& [ranking, signals] : perDisplayRanking) {
+        if (!chosenFps) {
+            const auto& [frameRateMode, _] = ranking.front();
+            modeChoices.try_emplace(frameRateMode.modePtr->getPhysicalDisplayId(),
+                                    DisplayModeChoice{frameRateMode, signals});
+            continue;
+        }
+
+        for (auto& [frameRateMode, _] : ranking) {
+            if (frameRateMode.fps == *chosenFps) {
+                modeChoices.try_emplace(frameRateMode.modePtr->getPhysicalDisplayId(),
+                                        DisplayModeChoice{frameRateMode, signals});
+                break;
+            }
+        }
+    }
+    return modeChoices;
 }
 
-DisplayModeSelectionParams Scheduler::getDisplayModeSelectionParams() const {
+GlobalSignals Scheduler::makeGlobalSignals() const {
     const bool powerOnImminent = mDisplayPowerTimer &&
             (mPolicy.displayPowerMode != hal::PowerMode::ON ||
              mPolicy.displayPowerTimer == TimerState::Reset);
 
-    const GlobalSignals signals{.touch = mTouchTimer && mPolicy.touch == TouchState::Active,
-                                .idle = mPolicy.idleTimer == TimerState::Expired,
-                                .powerOnImminent = powerOnImminent};
-
-    return {mPolicy.contentRequirements, signals};
+    return {.touch = mTouchTimer && mPolicy.touch == TouchState::Active,
+            .idle = mPolicy.idleTimer == TimerState::Expired,
+            .powerOnImminent = powerOnImminent};
 }
 
-auto Scheduler::getRankedDisplayModes()
-        -> std::pair<std::vector<RefreshRateRanking>, GlobalSignals> {
-    ATRACE_CALL();
-
-    const auto configs = holdRefreshRateConfigs();
-
-    const auto displayModeSelectionParams = getDisplayModeSelectionParams();
-    return configs->getRankedRefreshRates(displayModeSelectionParams.layerRequirements,
-                                          displayModeSelectionParams.globalSignals);
-}
-
-DisplayModePtr Scheduler::getPreferredDisplayMode() {
+ftl::Optional<FrameRateMode> Scheduler::getPreferredDisplayMode() {
     std::lock_guard<std::mutex> lock(mPolicyLock);
     // Make sure the stored mode is up to date.
-    if (mPolicy.mode) {
-        mPolicy.mode = getRankedDisplayModes().first.front().displayModePtr;
+    if (mPolicy.modeOpt) {
+        const auto ranking =
+                leaderSelectorPtr()
+                        ->getRankedFrameRates(mPolicy.contentRequirements, makeGlobalSignals())
+                        .ranking;
+
+        mPolicy.modeOpt = ranking.front().frameRateMode;
     }
-    return mPolicy.mode;
+    return mPolicy.modeOpt;
 }
 
 void Scheduler::onNewVsyncPeriodChangeTimeline(const hal::VsyncPeriodChangeTimeline& timeline) {
diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h
index 609cebc..cf2ffb8 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.h
+++ b/services/surfaceflinger/Scheduler/Scheduler.h
@@ -22,7 +22,6 @@
 #include <future>
 #include <memory>
 #include <mutex>
-#include <optional>
 #include <unordered_map>
 #include <utility>
 
@@ -33,17 +32,21 @@
 #include <ui/GraphicTypes.h>
 #pragma clang diagnostic pop // ignored "-Wconversion -Wextra"
 
-#include <DisplayDevice.h>
+#include <ftl/fake_guard.h>
+#include <ftl/optional.h>
 #include <scheduler/Features.h>
 #include <scheduler/Time.h>
+#include <ui/DisplayId.h>
 
 #include "Display/DisplayMap.h"
+#include "Display/DisplayModeRequest.h"
 #include "EventThread.h"
 #include "FrameRateOverrideMappings.h"
 #include "LayerHistory.h"
 #include "MessageQueue.h"
 #include "OneShotTimer.h"
-#include "RefreshRateConfigs.h"
+#include "RefreshRateSelector.h"
+#include "Utils/Dumper.h"
 #include "VsyncSchedule.h"
 
 namespace android::scheduler {
@@ -85,22 +88,11 @@
 
 namespace scheduler {
 
-using GlobalSignals = RefreshRateConfigs::GlobalSignals;
-
-// Config representing the DisplayMode and considered signals for the Display.
-struct DisplayModeConfig {
-    const GlobalSignals signals;
-    const DisplayModePtr displayModePtr;
-
-    DisplayModeConfig(GlobalSignals signals, DisplayModePtr displayModePtr)
-          : signals(signals), displayModePtr(std::move(displayModePtr)) {}
-};
+using GlobalSignals = RefreshRateSelector::GlobalSignals;
 
 struct ISchedulerCallback {
-    using DisplayModeEvent = scheduler::DisplayModeEvent;
-
     virtual void setVsyncEnabled(bool) = 0;
-    virtual void requestDisplayModes(std::vector<DisplayModeConfig>) = 0;
+    virtual void requestDisplayModes(std::vector<display::DisplayModeRequest>) = 0;
     virtual void kernelTimerChanged(bool expired) = 0;
     virtual void triggerOnFrameRateOverridesChanged() = 0;
 
@@ -108,25 +100,6 @@
     ~ISchedulerCallback() = default;
 };
 
-// Holds the total score of the FPS and
-// number of displays the FPS is found in.
-struct AggregatedFpsScore {
-    float totalScore;
-    size_t numDisplays;
-};
-
-// Represents LayerRequirements and GlobalSignals to be considered for the display mode selection.
-struct DisplayModeSelectionParams {
-    std::vector<RefreshRateConfigs::LayerRequirement> layerRequirements;
-    GlobalSignals globalSignals;
-};
-
-// Represents the RefreshRateRankings and GlobalSignals for the selected RefreshRateRankings.
-struct RefreshRateRankingsAndSignals {
-    std::vector<RefreshRateRanking> refreshRateRankings;
-    GlobalSignals globalSignals;
-};
-
 class Scheduler : android::impl::MessageQueue {
     using Impl = android::impl::MessageQueue;
 
@@ -135,8 +108,16 @@
     virtual ~Scheduler();
 
     void startTimers();
-    void setRefreshRateConfigs(std::shared_ptr<RefreshRateConfigs>)
-            EXCLUDES(mRefreshRateConfigsLock);
+
+    // TODO(b/241285191): Remove this API by promoting leader in onScreen{Acquired,Released}.
+    void setLeaderDisplay(std::optional<PhysicalDisplayId>) REQUIRES(kMainThreadContext)
+            EXCLUDES(mDisplayLock);
+
+    using RefreshRateSelectorPtr = std::shared_ptr<RefreshRateSelector>;
+
+    void registerDisplay(PhysicalDisplayId, RefreshRateSelectorPtr) REQUIRES(kMainThreadContext)
+            EXCLUDES(mDisplayLock);
+    void unregisterDisplay(PhysicalDisplayId) REQUIRES(kMainThreadContext) EXCLUDES(mDisplayLock);
 
     void run();
 
@@ -168,8 +149,8 @@
     sp<EventThreadConnection> getEventConnection(ConnectionHandle);
 
     void onHotplugReceived(ConnectionHandle, PhysicalDisplayId, bool connected);
-    void onPrimaryDisplayModeChanged(ConnectionHandle, DisplayModePtr) EXCLUDES(mPolicyLock);
-    void onNonPrimaryDisplayModeChanged(ConnectionHandle, DisplayModePtr);
+    void onPrimaryDisplayModeChanged(ConnectionHandle, const FrameRateMode&) EXCLUDES(mPolicyLock);
+    void onNonPrimaryDisplayModeChanged(ConnectionHandle, const FrameRateMode&);
     void onScreenAcquired(ConnectionHandle);
     void onScreenReleased(ConnectionHandle);
 
@@ -180,6 +161,9 @@
     void setDuration(ConnectionHandle, std::chrono::nanoseconds workDuration,
                      std::chrono::nanoseconds readyDuration);
 
+    // Sets the render rate for the scheduler to run at.
+    void setRenderRate(Fps);
+
     void enableHardwareVsync();
     void disableHardwareVsync(bool makeUnavailable);
 
@@ -188,7 +172,7 @@
     // Otherwise, if hardware vsync is not already enabled then this method will
     // no-op.
     void resyncToHardwareVsync(bool makeAvailable, Fps refreshRate);
-    void resync() EXCLUDES(mRefreshRateConfigsLock);
+    void resync() EXCLUDES(mDisplayLock);
     void forceNextResync() { mLastResyncTime = 0; }
 
     // Passes a vsync sample to VsyncController. periodFlushed will be true if
@@ -199,14 +183,14 @@
 
     // Layers are registered on creation, and unregistered when the weak reference expires.
     void registerLayer(Layer*);
-    void recordLayerHistory(Layer*, nsecs_t presentTime, LayerHistory::LayerUpdateType updateType)
-            EXCLUDES(mRefreshRateConfigsLock);
+    void recordLayerHistory(Layer*, nsecs_t presentTime, LayerHistory::LayerUpdateType)
+            EXCLUDES(mDisplayLock);
     void setModeChangePending(bool pending);
     void setDefaultFrameRateCompatibility(Layer*);
     void deregisterLayer(Layer*);
 
     // Detects content using layer history, and selects a matching refresh rate.
-    void chooseRefreshRateForContent() EXCLUDES(mRefreshRateConfigsLock);
+    void chooseRefreshRateForContent() EXCLUDES(mDisplayLock);
 
     void resetIdleTimer();
 
@@ -221,12 +205,12 @@
     // for a given uid
     bool isVsyncValid(TimePoint expectedVsyncTimestamp, uid_t uid) const;
 
-    void dump(std::string&) const;
+    void dump(utils::Dumper&) const;
     void dump(ConnectionHandle, std::string&) const;
     void dumpVsync(std::string&) const;
 
     // Get the appropriate refresh for current conditions.
-    DisplayModePtr getPreferredDisplayMode();
+    ftl::Optional<FrameRateMode> getPreferredDisplayMode();
 
     // Notifies the scheduler about a refresh rate timeline change.
     void onNewVsyncPeriodChangeTimeline(const hal::VsyncPeriodChangeTimeline& timeline);
@@ -251,11 +235,10 @@
     void setGameModeRefreshRateForUid(FrameRateOverride);
 
     // Retrieves the overridden refresh rate for a given uid.
-    std::optional<Fps> getFrameRateOverride(uid_t uid) const EXCLUDES(mRefreshRateConfigsLock);
+    std::optional<Fps> getFrameRateOverride(uid_t) const EXCLUDES(mDisplayLock);
 
-    nsecs_t getVsyncPeriodFromRefreshRateConfigs() const EXCLUDES(mRefreshRateConfigsLock) {
-        std::scoped_lock lock(mRefreshRateConfigsLock);
-        return mRefreshRateConfigs->getActiveModePtr()->getFps().getPeriodNsecs();
+    nsecs_t getLeaderVsyncPeriod() const EXCLUDES(mDisplayLock) {
+        return leaderSelectorPtr()->getActiveMode().fps.getPeriodNsecs();
     }
 
     // Returns the framerate of the layer with the given sequence ID
@@ -263,9 +246,6 @@
         return mLayerHistory.getLayerFramerate(now, id);
     }
 
-    void registerDisplay(const sp<const DisplayDevice>&);
-    void unregisterDisplay(PhysicalDisplayId);
-
 private:
     friend class TestableScheduler;
 
@@ -282,13 +262,22 @@
             EventThread*, EventRegistrationFlags eventRegistration = {});
 
     // Update feature state machine to given state when corresponding timer resets or expires.
-    void kernelIdleTimerCallback(TimerState) EXCLUDES(mRefreshRateConfigsLock);
+    void kernelIdleTimerCallback(TimerState) EXCLUDES(mDisplayLock);
     void idleTimerCallback(TimerState);
     void touchTimerCallback(TimerState);
     void displayPowerTimerCallback(TimerState);
 
     void setVsyncPeriod(nsecs_t period);
 
+    // Chooses a leader among the registered displays, unless `leaderIdOpt` is specified. The new
+    // `mLeaderDisplayId` is never `std::nullopt`.
+    void promoteLeaderDisplay(std::optional<PhysicalDisplayId> leaderIdOpt = std::nullopt)
+            REQUIRES(kMainThreadContext, mDisplayLock);
+
+    // Blocks until the leader's idle timer thread exits. `mDisplayLock` must not be locked by the
+    // caller on the main thread to avoid deadlock, since the timer thread locks it before exit.
+    void demoteLeaderDisplay() REQUIRES(kMainThreadContext) EXCLUDES(mDisplayLock, mPolicyLock);
+
     struct Policy;
 
     // Sets the S state of the policy to the T value under mPolicyLock, and chooses a display mode
@@ -296,36 +285,39 @@
     template <typename S, typename T>
     GlobalSignals applyPolicy(S Policy::*, T&&) EXCLUDES(mPolicyLock);
 
-    // Returns the list of display modes in descending order of their priority that fulfills the
-    // policy, and the signals that were considered.
-    std::pair<std::vector<RefreshRateRanking>, GlobalSignals> getRankedDisplayModes()
-            REQUIRES(mPolicyLock);
+    struct DisplayModeChoice {
+        DisplayModeChoice(FrameRateMode mode, GlobalSignals consideredSignals)
+              : mode(std::move(mode)), consideredSignals(consideredSignals) {}
 
-    // Returns the best display mode per display.
-    std::vector<DisplayModeConfig> getBestDisplayModeConfigs() const REQUIRES(mPolicyLock);
+        FrameRateMode mode;
+        GlobalSignals consideredSignals;
 
-    // Returns the list of DisplayModeConfigs per display for the chosenFps.
-    std::vector<DisplayModeConfig> getDisplayModeConfigsForTheChosenFps(
-            Fps chosenFps, const std::vector<RefreshRateRankingsAndSignals>&) const;
+        bool operator==(const DisplayModeChoice& other) const {
+            return mode == other.mode && consideredSignals == other.consideredSignals;
+        }
 
-    // Returns the DisplayModeSelectionParams to be considered for the
-    // DisplayMode selection based on the current Policy and GlobalSignals.
-    DisplayModeSelectionParams getDisplayModeSelectionParams() const REQUIRES(mPolicyLock);
+        // For tests.
+        friend std::ostream& operator<<(std::ostream& stream, const DisplayModeChoice& choice) {
+            return stream << '{' << to_string(*choice.mode.modePtr) << " considering "
+                          << choice.consideredSignals.toString().c_str() << '}';
+        }
+    };
+
+    using DisplayModeChoiceMap = display::PhysicalDisplayMap<PhysicalDisplayId, DisplayModeChoice>;
+
+    // See mDisplayLock for thread safety.
+    DisplayModeChoiceMap chooseDisplayModes() const
+            REQUIRES(mPolicyLock, mDisplayLock, kMainThreadContext);
+
+    GlobalSignals makeGlobalSignals() const REQUIRES(mPolicyLock);
 
     bool updateFrameRateOverrides(GlobalSignals, Fps displayRefreshRate) REQUIRES(mPolicyLock);
 
-    void dispatchCachedReportedMode() REQUIRES(mPolicyLock) EXCLUDES(mRefreshRateConfigsLock);
+    void dispatchCachedReportedMode() REQUIRES(mPolicyLock) EXCLUDES(mDisplayLock);
 
-    android::impl::EventThread::ThrottleVsyncCallback makeThrottleVsyncCallback() const
-            EXCLUDES(mRefreshRateConfigsLock);
+    android::impl::EventThread::ThrottleVsyncCallback makeThrottleVsyncCallback() const;
     android::impl::EventThread::GetVsyncPeriodFunction makeGetVsyncPeriodFunction() const;
 
-    std::shared_ptr<RefreshRateConfigs> holdRefreshRateConfigs() const
-            EXCLUDES(mRefreshRateConfigsLock) {
-        std::scoped_lock lock(mRefreshRateConfigsLock);
-        return mRefreshRateConfigs;
-    }
-
     // Stores EventThread associated with a given VSyncSource, and an initial EventThreadConnection.
     struct Connection {
         sp<EventThreadConnection> connection;
@@ -349,17 +341,40 @@
     LayerHistory mLayerHistory;
 
     // Timer used to monitor touch events.
-    std::optional<OneShotTimer> mTouchTimer;
+    ftl::Optional<OneShotTimer> mTouchTimer;
     // Timer used to monitor display power mode.
-    std::optional<OneShotTimer> mDisplayPowerTimer;
+    ftl::Optional<OneShotTimer> mDisplayPowerTimer;
 
     ISchedulerCallback& mSchedulerCallback;
 
+    // mDisplayLock may be locked while under mPolicyLock.
     mutable std::mutex mPolicyLock;
 
-    // Holds the Physical displays registered through the SurfaceFlinger, used for making
-    // the refresh rate selections.
-    display::PhysicalDisplayMap<PhysicalDisplayId, const sp<const DisplayDevice>> mDisplays;
+    // Only required for reads outside kMainThreadContext. kMainThreadContext is the only writer, so
+    // must lock for writes but not reads. See also mPolicyLock for locking order.
+    mutable std::mutex mDisplayLock;
+
+    display::PhysicalDisplayMap<PhysicalDisplayId, RefreshRateSelectorPtr> mRefreshRateSelectors
+            GUARDED_BY(mDisplayLock) GUARDED_BY(kMainThreadContext);
+
+    ftl::Optional<PhysicalDisplayId> mLeaderDisplayId GUARDED_BY(mDisplayLock)
+            GUARDED_BY(kMainThreadContext);
+
+    RefreshRateSelectorPtr leaderSelectorPtr() const EXCLUDES(mDisplayLock) {
+        std::scoped_lock lock(mDisplayLock);
+        return leaderSelectorPtrLocked();
+    }
+
+    RefreshRateSelectorPtr leaderSelectorPtrLocked() const REQUIRES(mDisplayLock) {
+        ftl::FakeGuard guard(kMainThreadContext);
+        const RefreshRateSelectorPtr noLeader;
+        return mLeaderDisplayId
+                .and_then([this](PhysicalDisplayId leaderId)
+                                  REQUIRES(mDisplayLock, kMainThreadContext) {
+                                      return mRefreshRateSelectors.get(leaderId);
+                                  })
+                .value_or(std::cref(noLeader));
+    }
 
     struct Policy {
         // Policy for choosing the display mode.
@@ -370,20 +385,17 @@
         hal::PowerMode displayPowerMode = hal::PowerMode::ON;
 
         // Chosen display mode.
-        DisplayModePtr mode;
+        ftl::Optional<FrameRateMode> modeOpt;
 
         struct ModeChangedParams {
             ConnectionHandle handle;
-            DisplayModePtr mode;
+            FrameRateMode mode;
         };
 
         // Parameters for latest dispatch of mode change event.
         std::optional<ModeChangedParams> cachedModeChangedParams;
     } mPolicy GUARDED_BY(mPolicyLock);
 
-    mutable std::mutex mRefreshRateConfigsLock;
-    std::shared_ptr<RefreshRateConfigs> mRefreshRateConfigs GUARDED_BY(mRefreshRateConfigsLock);
-
     std::mutex mVsyncTimelineLock;
     std::optional<hal::VsyncPeriodChangeTimeline> mLastVsyncPeriodChangeTimeline
             GUARDED_BY(mVsyncTimelineLock);
diff --git a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp
index 898e865..ed4d25e 100644
--- a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp
+++ b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp
@@ -34,7 +34,7 @@
 #include <utils/Log.h>
 #include <utils/Trace.h>
 
-#include "RefreshRateConfigs.h"
+#include "RefreshRateSelector.h"
 #include "VSyncPredictor.h"
 
 namespace android::scheduler {
@@ -253,7 +253,13 @@
 
 nsecs_t VSyncPredictor::nextAnticipatedVSyncTimeFrom(nsecs_t timePoint) const {
     std::lock_guard lock(mMutex);
-    return nextAnticipatedVSyncTimeFromLocked(timePoint);
+
+    // TODO(b/246164114): This implementation is not efficient at all. Refactor.
+    nsecs_t nextVsync = nextAnticipatedVSyncTimeFromLocked(timePoint);
+    while (!isVSyncInPhaseLocked(nextVsync, mDivisor)) {
+        nextVsync = nextAnticipatedVSyncTimeFromLocked(nextVsync + 1);
+    }
+    return nextVsync;
 }
 
 /*
@@ -265,6 +271,13 @@
  * isVSyncInPhase(50.0, 30) = true
  */
 bool VSyncPredictor::isVSyncInPhase(nsecs_t timePoint, Fps frameRate) const {
+    std::lock_guard lock(mMutex);
+    const auto divisor =
+            RefreshRateSelector::getFrameRateDivisor(Fps::fromPeriodNsecs(mIdealPeriod), frameRate);
+    return isVSyncInPhaseLocked(timePoint, static_cast<unsigned>(divisor));
+}
+
+bool VSyncPredictor::isVSyncInPhaseLocked(nsecs_t timePoint, unsigned divisor) const {
     struct VsyncError {
         nsecs_t vsyncTimestamp;
         float error;
@@ -272,9 +285,6 @@
         bool operator<(const VsyncError& other) const { return error < other.error; }
     };
 
-    std::lock_guard lock(mMutex);
-    const auto divisor =
-            RefreshRateConfigs::getFrameRateDivisor(Fps::fromPeriodNsecs(mIdealPeriod), frameRate);
     if (divisor <= 1 || timePoint == 0) {
         return true;
     }
@@ -312,6 +322,12 @@
     return std::abs(minVsyncError->vsyncTimestamp - timePoint) < period / 2;
 }
 
+void VSyncPredictor::setDivisor(unsigned divisor) {
+    ALOGV("%s: %d", __func__, divisor);
+    std::lock_guard lock(mMutex);
+    mDivisor = divisor;
+}
+
 VSyncPredictor::Model VSyncPredictor::getVSyncPredictionModel() const {
     std::lock_guard lock(mMutex);
     const auto model = VSyncPredictor::getVSyncPredictionModelLocked();
diff --git a/services/surfaceflinger/Scheduler/VSyncPredictor.h b/services/surfaceflinger/Scheduler/VSyncPredictor.h
index 3181102..4a3ba67 100644
--- a/services/surfaceflinger/Scheduler/VSyncPredictor.h
+++ b/services/surfaceflinger/Scheduler/VSyncPredictor.h
@@ -67,6 +67,8 @@
 
     bool isVSyncInPhase(nsecs_t timePoint, Fps frameRate) const final EXCLUDES(mMutex);
 
+    void setDivisor(unsigned divisor) final EXCLUDES(mMutex);
+
     void dump(std::string& result) const final EXCLUDES(mMutex);
 
 private:
@@ -89,6 +91,8 @@
 
     nsecs_t nextAnticipatedVSyncTimeFromLocked(nsecs_t timePoint) const REQUIRES(mMutex);
 
+    bool isVSyncInPhaseLocked(nsecs_t timePoint, unsigned divisor) const REQUIRES(mMutex);
+
     nsecs_t mIdealPeriod GUARDED_BY(mMutex);
     std::optional<nsecs_t> mKnownTimestamp GUARDED_BY(mMutex);
 
@@ -100,6 +104,8 @@
 
     size_t mLastTimestampIndex GUARDED_BY(mMutex) = 0;
     std::vector<nsecs_t> mTimestamps GUARDED_BY(mMutex);
+
+    unsigned mDivisor GUARDED_BY(mMutex) = 1;
 };
 
 } // namespace android::scheduler
diff --git a/services/surfaceflinger/Scheduler/VSyncTracker.h b/services/surfaceflinger/Scheduler/VSyncTracker.h
index 76315d2..8d1629f 100644
--- a/services/surfaceflinger/Scheduler/VSyncTracker.h
+++ b/services/surfaceflinger/Scheduler/VSyncTracker.h
@@ -79,6 +79,17 @@
      */
     virtual bool isVSyncInPhase(nsecs_t timePoint, Fps frameRate) const = 0;
 
+    /*
+     * Sets a divisor on the rate (which is a multiplier of the period).
+     * The tracker will continue to track the vsync timeline and expect it
+     * to match the current period, however, nextAnticipatedVSyncTimeFrom will
+     * return vsyncs according to the divisor set. Setting a divisor is useful
+     * when a display is running at 120Hz but the render frame rate is 60Hz.
+     *
+     * \param [in] divisor   The rate divisor the tracker should operate at.
+     */
+    virtual void setDivisor(unsigned divisor) = 0;
+
     virtual void dump(std::string& result) const = 0;
 
 protected:
diff --git a/services/surfaceflinger/Scheduler/include/scheduler/Fps.h b/services/surfaceflinger/Scheduler/include/scheduler/Fps.h
index 2c77142..5522ff8 100644
--- a/services/surfaceflinger/Scheduler/include/scheduler/Fps.h
+++ b/services/surfaceflinger/Scheduler/include/scheduler/Fps.h
@@ -66,6 +66,18 @@
     Fps max = Fps::fromValue(std::numeric_limits<float>::max());
 
     bool includes(Fps) const;
+    bool includes(FpsRange) const;
+};
+
+struct FpsRanges {
+    // The range of refresh rates that refers to the display mode setting.
+    FpsRange physical;
+
+    // the range of frame rates that refers to the render rate, which is
+    // the rate that frames are swapped.
+    FpsRange render;
+
+    bool valid() const;
 };
 
 static_assert(std::is_trivially_copyable_v<Fps>);
@@ -127,21 +139,43 @@
     return !(lhs == rhs);
 }
 
+inline bool operator==(const FpsRanges& lhs, const FpsRanges& rhs) {
+    return lhs.physical == rhs.physical && lhs.render == rhs.render;
+}
+
+inline bool operator!=(const FpsRanges& lhs, const FpsRanges& rhs) {
+    return !(lhs == rhs);
+}
+
+inline unsigned operator/(Fps lhs, Fps rhs) {
+    return static_cast<unsigned>(std::ceil(lhs.getValue() / rhs.getValue()));
+}
+
 } // namespace fps_approx_ops
 
+constexpr Fps operator/(Fps fps, unsigned divisor) {
+    return Fps::fromPeriodNsecs(fps.getPeriodNsecs() * static_cast<nsecs_t>(divisor));
+}
+
 inline bool FpsRange::includes(Fps fps) const {
     using fps_approx_ops::operator<=;
     return min <= fps && fps <= max;
 }
 
+inline bool FpsRange::includes(FpsRange range) const {
+    using namespace fps_approx_ops;
+    return min <= range.min && max >= range.max;
+}
+
+inline bool FpsRanges::valid() const {
+    using fps_approx_ops::operator>=;
+    return physical.max >= render.max;
+}
+
 struct FpsApproxEqual {
     bool operator()(Fps lhs, Fps rhs) const { return isApproxEqual(lhs, rhs); }
 };
 
-struct FpsHash {
-    size_t operator()(Fps fps) const { return std::hash<nsecs_t>()(fps.getPeriodNsecs()); }
-};
-
 inline std::string to_string(Fps fps) {
     return base::StringPrintf("%.2f Hz", fps.getValue());
 }
@@ -155,4 +189,10 @@
     return base::StringPrintf("[%s, %s]", to_string(min).c_str(), to_string(max).c_str());
 }
 
+inline std::string to_string(FpsRanges ranges) {
+    const auto& [physical, render] = ranges;
+    return base::StringPrintf("{physical=%s, render=%s}", to_string(physical).c_str(),
+                              to_string(render).c_str());
+}
+
 } // namespace android
diff --git a/services/surfaceflinger/Scheduler/include/scheduler/FrameRateMode.h b/services/surfaceflinger/Scheduler/include/scheduler/FrameRateMode.h
new file mode 100644
index 0000000..db38ebe
--- /dev/null
+++ b/services/surfaceflinger/Scheduler/include/scheduler/FrameRateMode.h
@@ -0,0 +1,42 @@
+/*
+ * 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 <ftl/non_null.h>
+#include <scheduler/Fps.h>
+
+// TODO(b/241285191): Pull this to <ui/DisplayMode.h>
+#include "DisplayHardware/DisplayMode.h"
+
+namespace android::scheduler {
+
+struct FrameRateMode {
+    Fps fps; // The render frame rate, which is a divisor of modePtr->getFps().
+    ftl::NonNull<DisplayModePtr> modePtr;
+
+    bool operator==(const FrameRateMode& other) const {
+        return isApproxEqual(fps, other.fps) && modePtr == other.modePtr;
+    }
+
+    bool operator!=(const FrameRateMode& other) const { return !(*this == other); }
+};
+
+inline std::string to_string(const FrameRateMode& mode) {
+    return to_string(mode.fps) + " (" + to_string(mode.modePtr->getFps()) + ")";
+}
+
+} // namespace android::scheduler
diff --git a/services/surfaceflinger/Scheduler/include/scheduler/Time.h b/services/surfaceflinger/Scheduler/include/scheduler/Time.h
index 2ca55d4..bd4e3c2 100644
--- a/services/surfaceflinger/Scheduler/include/scheduler/Time.h
+++ b/services/surfaceflinger/Scheduler/include/scheduler/Time.h
@@ -17,7 +17,9 @@
 #pragma once
 
 #include <chrono>
+#include <string>
 
+#include <android-base/stringprintf.h>
 #include <utils/Timers.h>
 
 namespace android {
@@ -79,4 +81,8 @@
     return std::chrono::duration_cast<D>(d).count();
 }
 
+inline std::string to_string(Duration d) {
+    return base::StringPrintf("%.3f ms", ticks<std::milli, float>(d));
+}
+
 } // namespace android
diff --git a/services/surfaceflinger/ScreenCaptureOutput.cpp b/services/surfaceflinger/ScreenCaptureOutput.cpp
new file mode 100644
index 0000000..37b3218
--- /dev/null
+++ b/services/surfaceflinger/ScreenCaptureOutput.cpp
@@ -0,0 +1,116 @@
+/*
+ * 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 "ScreenCaptureOutput.h"
+#include "ScreenCaptureRenderSurface.h"
+
+#include <compositionengine/CompositionEngine.h>
+#include <compositionengine/DisplayColorProfileCreationArgs.h>
+#include <compositionengine/impl/DisplayColorProfile.h>
+#include <ui/Rotation.h>
+
+namespace android {
+
+std::shared_ptr<ScreenCaptureOutput> createScreenCaptureOutput(ScreenCaptureOutputArgs args) {
+    std::shared_ptr<ScreenCaptureOutput> output = compositionengine::impl::createOutputTemplated<
+            ScreenCaptureOutput, compositionengine::CompositionEngine, const RenderArea&,
+            std::unordered_set<compositionengine::LayerFE*>,
+            const compositionengine::Output::ColorProfile&, bool>(args.compositionEngine,
+                                                                  args.renderArea,
+                                                                  std::move(
+                                                                          args.filterForScreenshot),
+                                                                  args.colorProfile,
+                                                                  args.regionSampling);
+    output->editState().isSecure = args.renderArea.isSecure();
+    output->setCompositionEnabled(true);
+    output->setLayerFilter({args.layerStack});
+    output->setRenderSurface(std::make_unique<ScreenCaptureRenderSurface>(std::move(args.buffer)));
+    output->setDisplayBrightness(args.sdrWhitePointNits, args.displayBrightnessNits);
+
+    output->setDisplayColorProfile(std::make_unique<compositionengine::impl::DisplayColorProfile>(
+            compositionengine::DisplayColorProfileCreationArgsBuilder()
+                    .setHasWideColorGamut(true)
+                    .Build()));
+
+    ui::Rotation orientation = ui::Transform::toRotation(args.renderArea.getRotationFlags());
+    Rect orientedDisplaySpaceRect{args.renderArea.getReqWidth(), args.renderArea.getReqHeight()};
+    output->setProjection(orientation, args.renderArea.getLayerStackSpaceRect(),
+                          orientedDisplaySpaceRect);
+
+    Rect sourceCrop = args.renderArea.getSourceCrop();
+    output->setDisplaySize({sourceCrop.getWidth(), sourceCrop.getHeight()});
+
+    return output;
+}
+
+ScreenCaptureOutput::ScreenCaptureOutput(
+        const RenderArea& renderArea,
+        std::unordered_set<compositionengine::LayerFE*> filterForScreenshot,
+        const compositionengine::Output::ColorProfile& colorProfile, bool regionSampling)
+      : mRenderArea(renderArea),
+        mFilterForScreenshot(std::move(filterForScreenshot)),
+        mColorProfile(colorProfile),
+        mRegionSampling(regionSampling) {}
+
+void ScreenCaptureOutput::updateColorProfile(const compositionengine::CompositionRefreshArgs&) {
+    auto& outputState = editState();
+    outputState.dataspace = mColorProfile.dataspace;
+    outputState.renderIntent = mColorProfile.renderIntent;
+}
+
+renderengine::DisplaySettings ScreenCaptureOutput::generateClientCompositionDisplaySettings()
+        const {
+    auto clientCompositionDisplay =
+            compositionengine::impl::Output::generateClientCompositionDisplaySettings();
+    clientCompositionDisplay.clip = mRenderArea.getSourceCrop();
+    clientCompositionDisplay.targetLuminanceNits = -1;
+    return clientCompositionDisplay;
+}
+
+std::vector<compositionengine::LayerFE::LayerSettings>
+ScreenCaptureOutput::generateClientCompositionRequests(
+        bool supportsProtectedContent, ui::Dataspace outputDataspace,
+        std::vector<compositionengine::LayerFE*>& outLayerFEs) {
+    auto clientCompositionLayers = compositionengine::impl::Output::
+            generateClientCompositionRequests(supportsProtectedContent, outputDataspace,
+                                              outLayerFEs);
+
+    if (mRegionSampling) {
+        for (auto& layer : clientCompositionLayers) {
+            layer.backgroundBlurRadius = 0;
+            layer.blurRegions.clear();
+        }
+    }
+
+    Rect sourceCrop = mRenderArea.getSourceCrop();
+    compositionengine::LayerFE::LayerSettings fillLayer;
+    fillLayer.source.buffer.buffer = nullptr;
+    fillLayer.source.solidColor = half3(0.0f, 0.0f, 0.0f);
+    fillLayer.geometry.boundaries =
+            FloatRect(static_cast<float>(sourceCrop.left), static_cast<float>(sourceCrop.top),
+                      static_cast<float>(sourceCrop.right), static_cast<float>(sourceCrop.bottom));
+    fillLayer.alpha = half(RenderArea::getCaptureFillValue(mRenderArea.getCaptureFill()));
+    clientCompositionLayers.insert(clientCompositionLayers.begin(), fillLayer);
+
+    return clientCompositionLayers;
+}
+
+bool ScreenCaptureOutput::layerNeedsFiltering(const compositionengine::OutputLayer* layer) const {
+    return mRenderArea.needsFiltering() ||
+            mFilterForScreenshot.find(&layer->getLayerFE()) != mFilterForScreenshot.end();
+}
+
+} // namespace android
diff --git a/services/surfaceflinger/ScreenCaptureOutput.h b/services/surfaceflinger/ScreenCaptureOutput.h
new file mode 100644
index 0000000..5dffc1d
--- /dev/null
+++ b/services/surfaceflinger/ScreenCaptureOutput.h
@@ -0,0 +1,72 @@
+/*
+ * 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 <compositionengine/DisplayColorProfile.h>
+#include <compositionengine/RenderSurface.h>
+#include <compositionengine/impl/Output.h>
+#include <ui/Rect.h>
+
+#include "RenderArea.h"
+
+namespace android {
+
+struct ScreenCaptureOutputArgs {
+    const compositionengine::CompositionEngine& compositionEngine;
+    const compositionengine::Output::ColorProfile& colorProfile;
+    const RenderArea& renderArea;
+    ui::LayerStack layerStack;
+    std::shared_ptr<renderengine::ExternalTexture> buffer;
+    float sdrWhitePointNits;
+    float displayBrightnessNits;
+    std::unordered_set<compositionengine::LayerFE*> filterForScreenshot;
+    bool regionSampling;
+};
+
+// ScreenCaptureOutput is used to compose a set of layers into a preallocated buffer.
+//
+// SurfaceFlinger passes instances of ScreenCaptureOutput to CompositionEngine in calls to
+// SurfaceFlinger::captureLayers and SurfaceFlinger::captureDisplay.
+class ScreenCaptureOutput : public compositionengine::impl::Output {
+public:
+    ScreenCaptureOutput(const RenderArea& renderArea,
+                        std::unordered_set<compositionengine::LayerFE*> filterForScreenshot,
+                        const compositionengine::Output::ColorProfile& colorProfile,
+                        bool regionSampling);
+
+    void updateColorProfile(const compositionengine::CompositionRefreshArgs&) override;
+
+    std::vector<compositionengine::LayerFE::LayerSettings> generateClientCompositionRequests(
+            bool supportsProtectedContent, ui::Dataspace outputDataspace,
+            std::vector<compositionengine::LayerFE*>& outLayerFEs) override;
+
+    bool layerNeedsFiltering(const compositionengine::OutputLayer*) const override;
+
+protected:
+    bool getSkipColorTransform() const override { return false; }
+    renderengine::DisplaySettings generateClientCompositionDisplaySettings() const override;
+
+private:
+    const RenderArea& mRenderArea;
+    const std::unordered_set<compositionengine::LayerFE*> mFilterForScreenshot;
+    const compositionengine::Output::ColorProfile& mColorProfile;
+    const bool mRegionSampling;
+};
+
+std::shared_ptr<ScreenCaptureOutput> createScreenCaptureOutput(ScreenCaptureOutputArgs);
+
+} // namespace android
diff --git a/services/surfaceflinger/ScreenCaptureRenderSurface.h b/services/surfaceflinger/ScreenCaptureRenderSurface.h
new file mode 100644
index 0000000..2097300
--- /dev/null
+++ b/services/surfaceflinger/ScreenCaptureRenderSurface.h
@@ -0,0 +1,81 @@
+/*
+ * 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 <memory>
+
+#include <compositionengine/RenderSurface.h>
+#include <renderengine/impl/ExternalTexture.h>
+#include <ui/Fence.h>
+#include <ui/Size.h>
+
+namespace android {
+
+// ScreenCaptureRenderSurface is a RenderSurface that returns a preallocated buffer used by
+// ScreenCaptureOutput.
+class ScreenCaptureRenderSurface : public compositionengine::RenderSurface {
+public:
+    ScreenCaptureRenderSurface(std::shared_ptr<renderengine::ExternalTexture> buffer)
+          : mBuffer(std::move(buffer)){};
+
+    std::shared_ptr<renderengine::ExternalTexture> dequeueBuffer(
+            base::unique_fd* /* bufferFence */) override {
+        return mBuffer;
+    }
+
+    void queueBuffer(base::unique_fd readyFence) override {
+        mRenderFence = sp<Fence>::make(readyFence.release());
+    }
+
+    const sp<Fence>& getClientTargetAcquireFence() const override { return mRenderFence; }
+
+    bool supportsCompositionStrategyPrediction() const override { return false; }
+
+    bool isValid() const override { return true; }
+
+    void initialize() override {}
+
+    const ui::Size& getSize() const override { return mSize; }
+
+    bool isProtected() const override { return mBuffer->getUsage() & GRALLOC_USAGE_PROTECTED; }
+
+    void setDisplaySize(const ui::Size&) override {}
+
+    void setBufferDataspace(ui::Dataspace) override {}
+
+    void setBufferPixelFormat(ui::PixelFormat) override {}
+
+    void setProtected(bool /* useProtected */) override {}
+
+    status_t beginFrame(bool /* mustRecompose */) override { return OK; }
+
+    void prepareFrame(bool /* usesClientComposition */, bool /* usesDeviceComposition */) override {
+    }
+
+    void onPresentDisplayCompleted() override {}
+
+    void dump(std::string& /* result */) const override {}
+
+private:
+    std::shared_ptr<renderengine::ExternalTexture> mBuffer;
+
+    sp<Fence> mRenderFence = Fence::NO_FENCE;
+
+    ui::Size mSize;
+};
+
+} // namespace android
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index 6167378..36ff3ec 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -44,16 +44,19 @@
 #include <compositionengine/CompositionRefreshArgs.h>
 #include <compositionengine/Display.h>
 #include <compositionengine/DisplayColorProfile.h>
+#include <compositionengine/DisplayColorProfileCreationArgs.h>
 #include <compositionengine/DisplayCreationArgs.h>
 #include <compositionengine/LayerFECompositionState.h>
 #include <compositionengine/OutputLayer.h>
 #include <compositionengine/RenderSurface.h>
+#include <compositionengine/impl/DisplayColorProfile.h>
 #include <compositionengine/impl/OutputCompositionState.h>
 #include <compositionengine/impl/OutputLayerCompositionState.h>
 #include <configstore/Utils.h>
 #include <cutils/compiler.h>
 #include <cutils/properties.h>
 #include <ftl/algorithm.h>
+#include <ftl/concat.h>
 #include <ftl/fake_guard.h>
 #include <ftl/future.h>
 #include <ftl/unit.h>
@@ -84,6 +87,7 @@
 #include <ui/DisplayState.h>
 #include <ui/DynamicDisplayInfo.h>
 #include <ui/GraphicBufferAllocator.h>
+#include <ui/LayerStack.h>
 #include <ui/PixelFormat.h>
 #include <ui/StaticDisplayInfo.h>
 #include <utils/StopWatch.h>
@@ -103,6 +107,7 @@
 #include <optional>
 #include <type_traits>
 #include <unordered_map>
+#include <vector>
 
 #include <ui/DisplayIdentification.h>
 #include "BackgroundExecutor.h"
@@ -122,6 +127,8 @@
 #include "FpsReporter.h"
 #include "FrameTimeline/FrameTimeline.h"
 #include "FrameTracer/FrameTracer.h"
+#include "FrontEnd/LayerCreationArgs.h"
+#include "FrontEnd/LayerHandle.h"
 #include "HdrLayerInfoReporter.h"
 #include "Layer.h"
 #include "LayerProtoHelper.h"
@@ -134,6 +141,7 @@
 #include "Scheduler/LayerHistory.h"
 #include "Scheduler/Scheduler.h"
 #include "Scheduler/VsyncConfiguration.h"
+#include "ScreenCaptureOutput.h"
 #include "StartPropertySetThread.h"
 #include "SurfaceFlingerProperties.h"
 #include "TimeStats/TimeStats.h"
@@ -157,6 +165,7 @@
 
 using namespace std::chrono_literals;
 using namespace std::string_literals;
+using namespace std::string_view_literals;
 
 using namespace hardware::configstore;
 using namespace hardware::configstore::V1_0;
@@ -171,6 +180,7 @@
 using base::StringAppendF;
 using display::PhysicalDisplay;
 using display::PhysicalDisplays;
+using frontend::TransactionHandler;
 using gui::DisplayInfo;
 using gui::GameMode;
 using gui::IDisplayEventConnection;
@@ -182,12 +192,15 @@
 using ui::DisplayPrimaries;
 using ui::RenderIntent;
 
-using KernelIdleTimerController = scheduler::RefreshRateConfigs::KernelIdleTimerController;
+using KernelIdleTimerController = scheduler::RefreshRateSelector::KernelIdleTimerController;
 
 namespace hal = android::hardware::graphics::composer::hal;
 
 namespace {
 
+static constexpr int FOUR_K_WIDTH = 3840;
+static constexpr int FOUR_K_HEIGHT = 2160;
+
 // TODO(b/141333600): Consolidate with DisplayMode::Builder::getDefaultDensity.
 constexpr float FALLBACK_DENSITY = ACONFIGURATION_DENSITY_TV;
 
@@ -235,6 +248,44 @@
     return displaySupportKernelIdleTimer || sysprop::support_kernel_idle_timer(false);
 }
 
+bool isAbove4k30(const ui::DisplayMode& outMode) {
+    using fps_approx_ops::operator>;
+    Fps refreshRate = Fps::fromValue(outMode.refreshRate);
+    return outMode.resolution.getWidth() >= FOUR_K_WIDTH &&
+            outMode.resolution.getHeight() >= FOUR_K_HEIGHT && refreshRate > 30_Hz;
+}
+
+void excludeDolbyVisionIf4k30Present(const std::vector<ui::Hdr>& displayHdrTypes,
+                                     ui::DisplayMode& outMode) {
+    if (isAbove4k30(outMode) &&
+        std::any_of(displayHdrTypes.begin(), displayHdrTypes.end(),
+                    [](ui::Hdr type) { return type == ui::Hdr::DOLBY_VISION_4K30; })) {
+        for (ui::Hdr type : displayHdrTypes) {
+            if (type != ui::Hdr::DOLBY_VISION_4K30 && type != ui::Hdr::DOLBY_VISION) {
+                outMode.supportedHdrTypes.push_back(type);
+            }
+        }
+    } else {
+        for (ui::Hdr type : displayHdrTypes) {
+            if (type != ui::Hdr::DOLBY_VISION_4K30) {
+                outMode.supportedHdrTypes.push_back(type);
+            }
+        }
+    }
+}
+
+HdrCapabilities filterOut4k30(const HdrCapabilities& displayHdrCapabilities) {
+    std::vector<ui::Hdr> hdrTypes;
+    for (ui::Hdr type : displayHdrCapabilities.getSupportedHdrTypes()) {
+        if (type != ui::Hdr::DOLBY_VISION_4K30) {
+            hdrTypes.push_back(type);
+        }
+    }
+    return {hdrTypes, displayHdrCapabilities.getDesiredMaxLuminance(),
+            displayHdrCapabilities.getDesiredMaxAverageLuminance(),
+            displayHdrCapabilities.getDesiredMinLuminance()};
+}
+
 }  // namespace anonymous
 
 // ---------------------------------------------------------------------------
@@ -305,8 +356,9 @@
         mCompositionEngine(mFactory.createCompositionEngine()),
         mHwcServiceName(base::GetProperty("debug.sf.hwc_service_name"s, "default"s)),
         mTunnelModeEnabledReporter(sp<TunnelModeEnabledReporter>::make()),
-        mInternalDisplayDensity(getDensityFromProperty("ro.sf.lcd_density", true)),
         mEmulatedDisplayDensity(getDensityFromProperty("qemu.sf.lcd_density", false)),
+        mInternalDisplayDensity(
+                getDensityFromProperty("ro.sf.lcd_density", !mEmulatedDisplayDensity)),
         mPowerAdvisor(std::make_unique<Hwc2::impl::PowerAdvisor>(*this)),
         mWindowInfosListenerInvoker(sp<WindowInfosListenerInvoker>::make()) {
     ALOGI("Using HWComposer service: %s", mHwcServiceName.c_str());
@@ -354,9 +406,6 @@
     // debugging stuff...
     char value[PROPERTY_VALUE_MAX];
 
-    property_get("ro.bq.gpu_to_cpu_unsupported", value, "0");
-    mGpuToCpuSupported = !atoi(value);
-
     property_get("ro.build.type", value, "user");
     mIsUserBuild = strcmp(value, "user") == 0;
 
@@ -367,7 +416,7 @@
     int debugDdms = atoi(value);
     ALOGI_IF(debugDdms, "DDMS debugging not supported");
 
-    property_get("debug.sf.enable_gl_backpressure", value, "0");
+    property_get("debug.sf.enable_gl_backpressure", value, "1");
     mPropagateBackpressureClientComposition = atoi(value);
     ALOGI_IF(mPropagateBackpressureClientComposition,
              "Enabling backpressure propagation for Client Composition");
@@ -414,7 +463,9 @@
         android::hardware::details::setTrebleTestingOverride(true);
     }
 
-    mRefreshRateOverlaySpinner = property_get_bool("sf.debug.show_refresh_rate_overlay_spinner", 0);
+    mRefreshRateOverlaySpinner = property_get_bool("debug.sf.show_refresh_rate_overlay_spinner", 0);
+    mRefreshRateOverlayRenderRate =
+            property_get_bool("debug.sf.show_refresh_rate_overlay_render_rate", 0);
 
     if (!mIsUserBuild && base::GetBoolProperty("debug.sf.enable_transaction_tracing"s, true)) {
         mTransactionTracing.emplace();
@@ -599,7 +650,7 @@
 }
 
 renderengine::RenderEngine& SurfaceFlinger::getRenderEngine() const {
-    return mCompositionEngine->getRenderEngine();
+    return *mRenderEngine;
 }
 
 compositionengine::CompositionEngine& SurfaceFlinger::getCompositionEngine() const {
@@ -729,6 +780,10 @@
         return renderengine::RenderEngine::RenderEngineType::SKIA_GL;
     } else if (strcmp(prop, "skiaglthreaded") == 0) {
         return renderengine::RenderEngine::RenderEngineType::SKIA_GL_THREADED;
+    } else if (strcmp(prop, "skiavk") == 0) {
+        return renderengine::RenderEngine::RenderEngineType::SKIA_VK;
+    } else if (strcmp(prop, "skiavkthreaded") == 0) {
+        return renderengine::RenderEngine::RenderEngineType::SKIA_VK_THREADED;
     } else {
         ALOGE("Unrecognized RenderEngineType %s; ignoring!", prop);
         return {};
@@ -760,7 +815,8 @@
     if (auto type = chooseRenderEngineTypeViaSysProp()) {
         builder.setRenderEngineType(type.value());
     }
-    mCompositionEngine->setRenderEngine(renderengine::RenderEngine::create(builder.build()));
+    mRenderEngine = renderengine::RenderEngine::create(builder.build());
+    mCompositionEngine->setRenderEngine(mRenderEngine.get());
     mMaxRenderTargetSize =
             std::min(getRenderEngine().getMaxTextureSize(), getRenderEngine().getMaxViewportDims());
 
@@ -833,8 +889,6 @@
         }
     }
 
-    onActiveDisplaySizeChanged(display);
-
     // Inform native graphics APIs whether the present timestamp is supported:
 
     const bool presentFenceReliable =
@@ -1020,7 +1074,8 @@
         // We add an additional 1ms to allow for processing time and
         // differences between the ideal and actual refresh rate.
         outMode.presentationDeadline = period - outMode.sfVsyncOffset + 1000000;
-
+        excludeDolbyVisionIf4k30Present(display->getHdrCapabilities().getSupportedHdrTypes(),
+                                        outMode);
         info->supportedDisplayModes.push_back(outMode);
     }
 
@@ -1028,9 +1083,11 @@
 
     const PhysicalDisplayId displayId = snapshot.displayId();
 
-    info->activeDisplayModeId = display->refreshRateConfigs().getActiveModePtr()->getId().value();
+    const auto mode = display->refreshRateSelector().getActiveMode();
+    info->activeDisplayModeId = mode.modePtr->getId().value();
+    info->renderFrameRate = mode.fps.getValue();
     info->activeColorMode = display->getCompositionDisplay()->getState().colorMode;
-    info->hdrCapabilities = display->getHdrCapabilities();
+    info->hdrCapabilities = filterOut4k30(display->getHdrCapabilities());
 
     info->autoLowLatencyModeSupported =
             getHwComposer().hasDisplayCapability(displayId,
@@ -1062,31 +1119,43 @@
     return NO_ERROR;
 }
 
-void SurfaceFlinger::setDesiredActiveMode(const ActiveModeInfo& info) {
+void SurfaceFlinger::setDesiredActiveMode(display::DisplayModeRequest&& request) {
     ATRACE_CALL();
 
-    if (!info.mode) {
-        ALOGW("requested display mode is null");
-        return;
-    }
-    auto display = getDisplayDeviceLocked(info.mode->getPhysicalDisplayId());
+    auto display = getDisplayDeviceLocked(request.mode.modePtr->getPhysicalDisplayId());
     if (!display) {
         ALOGW("%s: display is no longer valid", __func__);
         return;
     }
 
-    if (display->setDesiredActiveMode(info)) {
-        scheduleComposite(FrameHint::kNone);
+    const auto mode = request.mode;
+    const bool emitEvent = request.emitEvent;
 
-        // Start receiving vsync samples now, so that we can detect a period
-        // switch.
-        mScheduler->resyncToHardwareVsync(true, info.mode->getFps());
-        // As we called to set period, we will call to onRefreshRateChangeCompleted once
-        // VsyncController model is locked.
-        modulateVsync(&VsyncModulator::onRefreshRateChangeInitiated);
+    switch (display->setDesiredActiveMode(DisplayDevice::ActiveModeInfo(std::move(request)))) {
+        case DisplayDevice::DesiredActiveModeAction::InitiateDisplayModeSwitch:
+            scheduleComposite(FrameHint::kNone);
 
-        updatePhaseConfiguration(info.mode->getFps());
-        mScheduler->setModeChangePending(true);
+            // Start receiving vsync samples now, so that we can detect a period
+            // switch.
+            mScheduler->resyncToHardwareVsync(true, mode.modePtr->getFps());
+            // As we called to set period, we will call to onRefreshRateChangeCompleted once
+            // VsyncController model is locked.
+            modulateVsync(&VsyncModulator::onRefreshRateChangeInitiated);
+
+            updatePhaseConfiguration(mode.fps);
+            mScheduler->setModeChangePending(true);
+            break;
+        case DisplayDevice::DesiredActiveModeAction::InitiateRenderRateSwitch:
+            mScheduler->setRenderRate(mode.fps);
+            updatePhaseConfiguration(mode.fps);
+            mRefreshRateStats->setRefreshRate(mode.fps);
+            if (display->getPhysicalId() == mActiveDisplayId && emitEvent) {
+                mScheduler->onPrimaryDisplayModeChanged(mAppConnectionHandle, mode);
+            }
+
+            break;
+        case DisplayDevice::DesiredActiveModeAction::None:
+            break;
     }
 }
 
@@ -1127,11 +1196,11 @@
 
         // Keep the old switching type.
         const bool allowGroupSwitching =
-                display->refreshRateConfigs().getCurrentPolicy().allowGroupSwitching;
+                display->refreshRateSelector().getCurrentPolicy().allowGroupSwitching;
 
-        const scheduler::RefreshRateConfigs::DisplayManagerPolicy policy{modeId,
-                                                                         allowGroupSwitching,
-                                                                         {fps, fps}};
+        const scheduler::RefreshRateSelector::DisplayManagerPolicy policy{modeId,
+                                                                          {fps, fps},
+                                                                          allowGroupSwitching};
 
         return setDesiredDisplayModeSpecsInternal(display, policy);
     });
@@ -1148,18 +1217,19 @@
     }
 
     const auto upcomingModeInfo = display->getUpcomingActiveMode();
-    if (!upcomingModeInfo.mode) {
+    if (!upcomingModeInfo.modeOpt) {
         // There is no pending mode change. This can happen if the active
         // display changed and the mode change happened on a different display.
         return;
     }
 
-    if (display->getActiveMode().getResolution() != upcomingModeInfo.mode->getResolution()) {
+    if (display->getActiveMode().modePtr->getResolution() !=
+        upcomingModeInfo.modeOpt->modePtr->getResolution()) {
         auto& state = mCurrentState.displays.editValueFor(display->getDisplayToken());
         // We need to generate new sequenceId in order to recreate the display (and this
         // way the framebuffer).
         state.sequenceId = DisplayDeviceState{}.sequenceId;
-        state.physical->activeMode = upcomingModeInfo.mode;
+        state.physical->activeMode = upcomingModeInfo.modeOpt->modePtr.get();
         processDisplayChangesLocked();
 
         // processDisplayChangesLocked will update all necessary components so we're done here.
@@ -1170,15 +1240,17 @@
             .transform(&PhysicalDisplay::snapshotRef)
             .transform(ftl::unit_fn([&](const display::DisplaySnapshot& snapshot) {
                 FTL_FAKE_GUARD(kMainThreadContext,
-                               display->setActiveMode(upcomingModeInfo.mode->getId(), snapshot));
+                               display->setActiveMode(upcomingModeInfo.modeOpt->modePtr->getId(),
+                                                      upcomingModeInfo.modeOpt->modePtr->getFps(),
+                                                      upcomingModeInfo.modeOpt->fps));
             }));
 
-    const Fps refreshRate = upcomingModeInfo.mode->getFps();
+    const Fps refreshRate = upcomingModeInfo.modeOpt->fps;
     mRefreshRateStats->setRefreshRate(refreshRate);
     updatePhaseConfiguration(refreshRate);
 
-    if (upcomingModeInfo.event != DisplayModeEvent::None) {
-        mScheduler->onPrimaryDisplayModeChanged(mAppConnectionHandle, upcomingModeInfo.mode);
+    if (upcomingModeInfo.event != scheduler::DisplayModeEvent::None) {
+        mScheduler->onPrimaryDisplayModeChanged(mAppConnectionHandle, *upcomingModeInfo.modeOpt);
     }
 }
 
@@ -1190,10 +1262,12 @@
 }
 
 void SurfaceFlinger::desiredActiveModeChangeDone(const sp<DisplayDevice>& display) {
-    const auto refreshRate = display->getDesiredActiveMode()->mode->getFps();
+    const auto displayFps = display->getDesiredActiveMode()->modeOpt->modePtr->getFps();
+    const auto renderFps = display->getDesiredActiveMode()->modeOpt->fps;
     clearDesiredActiveModeState(display);
-    mScheduler->resyncToHardwareVsync(true, refreshRate);
-    updatePhaseConfiguration(refreshRate);
+    mScheduler->resyncToHardwareVsync(true, displayFps);
+    mScheduler->setRenderRate(renderFps);
+    updatePhaseConfiguration(renderFps);
 }
 
 void SurfaceFlinger::setActiveModeInHwcIfNeeded() {
@@ -1224,13 +1298,10 @@
             continue;
         }
 
-        const auto desiredModeId = desiredActiveMode->mode->getId();
-        const auto refreshRateOpt =
-                snapshot.displayModes()
-                        .get(desiredModeId)
-                        .transform([](const DisplayModePtr& mode) { return mode->getFps(); });
+        const auto desiredModeId = desiredActiveMode->modeOpt->modePtr->getId();
+        const auto displayModePtrOpt = snapshot.displayModes().get(desiredModeId);
 
-        if (!refreshRateOpt) {
+        if (!displayModePtrOpt) {
             ALOGW("Desired display mode is no longer supported. Mode ID = %d",
                   desiredModeId.value());
             clearDesiredActiveModeState(display);
@@ -1238,9 +1309,10 @@
         }
 
         ALOGV("%s changing active mode to %d(%s) for display %s", __func__, desiredModeId.value(),
-              to_string(*refreshRateOpt).c_str(), to_string(display->getId()).c_str());
+              to_string(displayModePtrOpt->get()->getFps()).c_str(),
+              to_string(display->getId()).c_str());
 
-        if (display->getActiveMode().getId() == desiredModeId) {
+        if (display->getActiveMode() == desiredActiveMode->modeOpt) {
             // we are already in the requested mode, there is nothing left to do
             desiredActiveModeChangeDone(display);
             continue;
@@ -1249,7 +1321,8 @@
         // 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->refreshRateConfigs().isModeAllowed(desiredModeId);
+        const auto displayModeAllowed =
+                display->refreshRateSelector().isModeAllowed(*desiredActiveMode->modeOpt);
         if (!displayModeAllowed) {
             clearDesiredActiveModeState(display);
             continue;
@@ -1271,7 +1344,7 @@
             continue;
         }
 
-        display->refreshRateConfigs().onModeChangeInitiated();
+        display->refreshRateSelector().onModeChangeInitiated();
         mScheduler->onNewVsyncPeriodChangeTimeline(outTimeline);
 
         if (outTimeline.refreshRequired) {
@@ -1290,8 +1363,7 @@
 
         const auto display = getDisplayDeviceLocked(*displayToUpdateImmediately);
         const auto desiredActiveMode = display->getDesiredActiveMode();
-        if (desiredActiveMode &&
-            display->getActiveMode().getId() == desiredActiveMode->mode->getId()) {
+        if (desiredActiveMode && display->getActiveMode() == desiredActiveMode->modeOpt) {
             desiredActiveModeChangeDone(display);
         }
     }
@@ -1383,6 +1455,29 @@
     return NO_ERROR;
 }
 
+status_t SurfaceFlinger::getOverlaySupport(gui::OverlayProperties* outProperties) const {
+    const auto& aidlProperties = getHwComposer().getOverlaySupport();
+    // convert aidl OverlayProperties to gui::OverlayProperties
+    outProperties->combinations.reserve(aidlProperties.combinations.size());
+    for (const auto& combination : aidlProperties.combinations) {
+        std::vector<int32_t> pixelFormats;
+        pixelFormats.reserve(combination.pixelFormats.size());
+        std::transform(combination.pixelFormats.cbegin(), combination.pixelFormats.cend(),
+                       std::back_inserter(pixelFormats),
+                       [](const auto& val) { return static_cast<int32_t>(val); });
+        std::vector<int32_t> dataspaces;
+        dataspaces.reserve(combination.dataspaces.size());
+        std::transform(combination.dataspaces.cbegin(), combination.dataspaces.cend(),
+                       std::back_inserter(dataspaces),
+                       [](const auto& val) { return static_cast<int32_t>(val); });
+        gui::OverlayProperties::SupportedBufferCombinations outCombination;
+        outCombination.pixelFormats = std::move(pixelFormats);
+        outCombination.dataspaces = std::move(dataspaces);
+        outProperties->combinations.emplace_back(outCombination);
+    }
+    return NO_ERROR;
+}
+
 status_t SurfaceFlinger::setBootDisplayMode(const sp<display::DisplayToken>& displayToken,
                                             DisplayModeId modeId) {
     const char* const whence = __func__;
@@ -1573,8 +1668,12 @@
         return BAD_VALUE;
     }
 
-    const wp<Layer> stopLayer = fromHandle(stopLayerHandle);
-    mRegionSamplingThread->addListener(samplingArea, stopLayer, listener);
+    // LayerHandle::getLayer promotes the layer object in a binder thread but we will not destroy
+    // the layer here since the caller has a strong ref to the layer's handle.
+    const sp<Layer> stopLayer = LayerHandle::getLayer(stopLayerHandle);
+    mRegionSamplingThread->addListener(samplingArea,
+                                       stopLayer ? stopLayer->getSequence() : UNASSIGNED_LAYER_ID,
+                                       listener);
     return NO_ERROR;
 }
 
@@ -1787,7 +1886,7 @@
     if (hint == FrameHint::kActive) {
         mScheduler->resetIdleTimer();
     }
-    mPowerAdvisor->notifyDisplayUpdateImminent();
+    mPowerAdvisor->notifyDisplayUpdateImminentAndCpuReset();
     mScheduler->scheduleFrame();
 }
 
@@ -2033,7 +2132,7 @@
             activeDisplay->getPowerMode() == hal::PowerMode::ON;
     if (mPowerHintSessionEnabled) {
         const auto& display = FTL_FAKE_GUARD(mStateLock, getDefaultDisplayDeviceLocked()).get();
-        const Period vsyncPeriod = Period::fromNs(display->getActiveMode().getVsyncPeriod());
+        const Period vsyncPeriod = Period::fromNs(display->getActiveMode().fps.getPeriodNsecs());
         mPowerAdvisor->setCommitStart(frameTime);
         mPowerAdvisor->setExpectedPresentTime(mExpectedPresentTime);
 
@@ -2172,10 +2271,13 @@
 
     refreshArgs.updatingOutputGeometryThisFrame = mVisibleRegionsDirty;
     refreshArgs.updatingGeometryThisFrame = mGeometryDirty.exchange(false) || mVisibleRegionsDirty;
-    mDrawingState.traverseInZOrder([&refreshArgs](Layer* layer) {
-        layer->updateSnapshot(refreshArgs.updatingGeometryThisFrame);
+    std::vector<Layer*> layers;
+
+    mDrawingState.traverseInZOrder([&refreshArgs, &layers](Layer* layer) {
         if (auto layerFE = layer->getCompositionEngineLayerFE()) {
+            layer->updateSnapshot(refreshArgs.updatingGeometryThisFrame);
             refreshArgs.layers.push_back(layerFE);
+            layers.push_back(layer);
         }
     });
     refreshArgs.blursAreExpensive = mBlursAreExpensive;
@@ -2205,7 +2307,25 @@
     // the scheduler.
     const auto presentTime = systemTime();
 
-    mCompositionEngine->present(refreshArgs);
+    {
+        std::vector<LayerSnapshotGuard> layerSnapshotGuards;
+        for (Layer* layer : layers) {
+            layerSnapshotGuards.emplace_back(layer);
+        }
+        mCompositionEngine->present(refreshArgs);
+    }
+
+    for (auto& layer : layers) {
+        CompositionResult compositionResult{
+                layer->getCompositionEngineLayerFE()->stealCompositionResult()};
+        layer->onPreComposition(compositionResult.refreshStartTime);
+        for (auto releaseFence : compositionResult.releaseFences) {
+            layer->onLayerDisplayed(releaseFence);
+        }
+        if (compositionResult.lastClientCompositionFence) {
+            layer->setWasClientComposed(compositionResult.lastClientCompositionFence);
+        }
+    }
 
     mTimeStats->recordFrameDuration(frameTime.ns(), systemTime());
 
@@ -2746,21 +2866,38 @@
         const auto [kernelIdleTimerController, idleTimerTimeoutMs] =
                 getKernelIdleTimerProperties(compositionDisplay->getId());
 
-        scheduler::RefreshRateConfigs::Config config =
-                {.enableFrameRateOverride = android::sysprop::enable_frame_rate_override(false),
+        const auto enableFrameRateOverride = [&] {
+            using Config = scheduler::RefreshRateSelector::Config;
+            if (!sysprop::enable_frame_rate_override(false)) {
+                return Config::FrameRateOverride::Disabled;
+            }
+
+            if (sysprop::frame_rate_override_for_native_rates(true)) {
+                return Config::FrameRateOverride::AppOverrideNativeRefreshRates;
+            }
+
+            if (!sysprop::frame_rate_override_global(false)) {
+                return Config::FrameRateOverride::AppOverride;
+            }
+
+            return Config::FrameRateOverride::Enabled;
+        }();
+
+        scheduler::RefreshRateSelector::Config config =
+                {.enableFrameRateOverride = enableFrameRateOverride,
                  .frameRateMultipleThreshold =
                          base::GetIntProperty("debug.sf.frame_rate_multiple_threshold", 0),
                  .idleTimerTimeout = idleTimerTimeoutMs,
                  .kernelIdleTimerController = kernelIdleTimerController};
 
-        creationArgs.refreshRateConfigs =
+        creationArgs.refreshRateSelector =
                 mPhysicalDisplays.get(physical->id)
                         .transform(&PhysicalDisplay::snapshotRef)
                         .transform([&](const display::DisplaySnapshot& snapshot) {
                             return std::make_shared<
-                                    scheduler::RefreshRateConfigs>(snapshot.displayModes(),
-                                                                   creationArgs.activeModeId,
-                                                                   config);
+                                    scheduler::RefreshRateSelector>(snapshot.displayModes(),
+                                                                    creationArgs.activeModeId,
+                                                                    config);
                         })
                         .value_or(nullptr);
 
@@ -2824,7 +2961,9 @@
                 .transform(&PhysicalDisplay::snapshotRef)
                 .transform(ftl::unit_fn([&](const display::DisplaySnapshot& snapshot) {
                     FTL_FAKE_GUARD(kMainThreadContext,
-                                   display->setActiveMode(physical->activeMode->getId(), snapshot));
+                                   display->setActiveMode(physical->activeMode->getId(),
+                                                          physical->activeMode->getFps(),
+                                                          physical->activeMode->getFps()));
                 }));
     }
 
@@ -2906,12 +3045,16 @@
                                                  displaySurface, producer);
 
     if (mScheduler && !display->isVirtual()) {
-        // Display modes are reloaded on hotplug reconnect.
-        if (display->isPrimary()) {
-            mScheduler->setRefreshRateConfigs(display->holdRefreshRateConfigs());
+        const auto displayId = display->getPhysicalId();
+        {
+            // TODO(b/241285876): Annotate `processDisplayAdded` instead.
+            ftl::FakeGuard guard(kMainThreadContext);
+
+            // For hotplug reconnect, renew the registration since display modes have been reloaded.
+            mScheduler->registerDisplay(displayId, display->holdRefreshRateSelector());
         }
-        mScheduler->registerDisplay(display);
-        dispatchDisplayHotplugEvent(display->getPhysicalId(), true);
+
+        dispatchDisplayHotplugEvent(displayId, true);
     }
 
     mDisplays.try_emplace(displayToken, std::move(display));
@@ -2963,8 +3106,6 @@
             display->disconnect();
             if (display->isVirtual()) {
                 releaseVirtualDisplay(display->getVirtualId());
-            } else {
-                mScheduler->unregisterDisplay(display->getPhysicalId());
             }
         }
 
@@ -3018,7 +3159,7 @@
 
 void SurfaceFlinger::updateInternalDisplayVsyncLocked(const sp<DisplayDevice>& activeDisplay) {
     mVsyncConfiguration->reset();
-    const Fps refreshRate = activeDisplay->getActiveMode().getFps();
+    const Fps refreshRate = activeDisplay->getActiveMode().fps;
     updatePhaseConfiguration(refreshRate);
     mRefreshRateStats->setRefreshRate(refreshRate);
 }
@@ -3069,6 +3210,10 @@
     const bool displayTransactionNeeded = transactionFlags & eDisplayTransactionNeeded;
     if (displayTransactionNeeded) {
         processDisplayChangesLocked();
+        mFrontEndDisplayInfos.clear();
+        for (const auto& [_, display] : mDisplays) {
+            mFrontEndDisplayInfos.try_emplace(display->getLayerStack(), display->getFrontEndInfo());
+        }
     }
     mForceTransactionDisplayChange = displayTransactionNeeded;
 
@@ -3117,20 +3262,21 @@
                 }
             }
 
-            if (!hintDisplay && mDisplays.size() > 0) {
+            if (!hintDisplay) {
                 // NOTE: TEMPORARY FIX ONLY. Real fix should cause layers to
                 // redraw after transform hint changes. See bug 8508397.
-
                 // could be null when this layer is using a layerStack
                 // that is not visible on any display. Also can occur at
                 // screen off/on times.
-                hintDisplay = getDefaultDisplayDeviceLocked();
-            }
-
-            if (hintDisplay) {
-                layer->updateTransformHint(hintDisplay->getTransformHint());
+                // U Update: Don't provide stale hints to the clients. For
+                // special cases where we want the app to draw its
+                // 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 {
-                ALOGW("Ignoring transform hint update for %s", layer->getDebugName());
+                layer->updateTransformHint(hintDisplay->getTransformHint());
             }
         });
     }
@@ -3228,7 +3374,7 @@
 
                 ALOGE_IF(error != NO_ERROR,
                          "Error setting display brightness for display %s: %d (%s)",
-                         display->getDebugName().c_str(), error, strerror(error));
+                         to_string(display->getId()).c_str(), error, strerror(error));
             }
             display->persistBrightness(needsComposite);
         }
@@ -3237,29 +3383,6 @@
 
 void SurfaceFlinger::buildWindowInfos(std::vector<WindowInfo>& outWindowInfos,
                                       std::vector<DisplayInfo>& outDisplayInfos) {
-    display::DisplayMap<ui::LayerStack, DisplayDevice::InputInfo> displayInputInfos;
-
-    for (const auto& [_, display] : FTL_FAKE_GUARD(mStateLock, mDisplays)) {
-        const auto layerStack = display->getLayerStack();
-        const auto info = display->getInputInfo();
-
-        const auto [it, emplaced] = displayInputInfos.try_emplace(layerStack, info);
-        if (emplaced) {
-            continue;
-        }
-
-        // If the layer stack is mirrored on multiple displays, the first display that is configured
-        // to receive input takes precedence.
-        auto& otherInfo = it->second;
-        if (otherInfo.receivesInput) {
-            ALOGW_IF(display->receivesInput(),
-                     "Multiple displays claim to accept input for the same layer stack: %u",
-                     layerStack.id);
-        } else {
-            otherInfo = info;
-        }
-    }
-
     static size_t sNumWindowInfos = 0;
     outWindowInfos.reserve(sNumWindowInfos);
     sNumWindowInfos = 0;
@@ -3267,8 +3390,8 @@
     mDrawingState.traverseInReverseZOrder([&](Layer* layer) {
         if (!layer->needsInputInfo()) return;
 
-        const auto opt = displayInputInfos.get(layer->getLayerStack())
-                                 .transform([](const DisplayDevice::InputInfo& info) {
+        const auto opt = mFrontEndDisplayInfos.get(layer->getLayerStack())
+                                 .transform([](const frontend::DisplayInfo& info) {
                                      return Layer::InputDisplayArgs{&info.transform, info.isSecure};
                                  });
 
@@ -3277,8 +3400,8 @@
 
     sNumWindowInfos = outWindowInfos.size();
 
-    outDisplayInfos.reserve(displayInputInfos.size());
-    for (const auto& [_, info] : displayInputInfos) {
+    outDisplayInfos.reserve(mFrontEndDisplayInfos.size());
+    for (const auto& [_, info] : mFrontEndDisplayInfos) {
         outDisplayInfos.push_back(info.info);
     }
 }
@@ -3291,37 +3414,45 @@
         }
     }
 
+    std::vector<LayerSnapshotGuard> layerSnapshotGuards;
+    mDrawingState.traverse([&layerSnapshotGuards](Layer* layer) {
+        if (layer->getLayerSnapshot()->compositionType ==
+            aidl::android::hardware::graphics::composer3::Composition::CURSOR) {
+            layer->updateSnapshot(false /* updateGeometry */);
+            layerSnapshotGuards.emplace_back(layer);
+        }
+    });
+
     mCompositionEngine->updateCursorAsync(refreshArgs);
 }
 
-void SurfaceFlinger::requestDisplayModes(
-        std::vector<scheduler::DisplayModeConfig> displayModeConfigs) {
+void SurfaceFlinger::requestDisplayModes(std::vector<display::DisplayModeRequest> modeRequests) {
     if (mBootStage != BootStage::FINISHED) {
         ALOGV("Currently in the boot stage, skipping display mode changes");
         return;
     }
 
     ATRACE_CALL();
+
     // If this is called from the main thread mStateLock must be locked before
     // Currently the only way to call this function from the main thread is from
     // Scheduler::chooseRefreshRateForContent
 
     ConditionalLock lock(mStateLock, std::this_thread::get_id() != mMainThreadId);
 
-    std::for_each(displayModeConfigs.begin(), displayModeConfigs.end(),
-                  [&](const auto& config) REQUIRES(mStateLock) {
-                      const auto& displayModePtr = config.displayModePtr;
-                      if (const auto display =
-                                  getDisplayDeviceLocked(displayModePtr->getPhysicalDisplayId());
-                          display->refreshRateConfigs().isModeAllowed(displayModePtr->getId())) {
-                          const auto event = config.signals.idle ? DisplayModeEvent::None
-                                                                 : DisplayModeEvent::Changed;
-                          setDesiredActiveMode({displayModePtr, event});
-                      } else {
-                          ALOGV("Skipping disallowed mode %d for display %" PRId64,
-                                displayModePtr->getId().value(), display->getPhysicalId().value);
-                      }
-                  });
+    for (auto& request : modeRequests) {
+        const auto& modePtr = request.mode.modePtr;
+        const auto display = getDisplayDeviceLocked(modePtr->getPhysicalDisplayId());
+
+        if (!display) continue;
+
+        if (display->refreshRateSelector().isModeAllowed(request.mode)) {
+            setDesiredActiveMode(std::move(request));
+        } else {
+            ALOGV("%s: Mode %d is disallowed for display %s", __func__, modePtr->getId().value(),
+                  to_string(display->getId()).c_str());
+        }
+    }
 }
 
 void SurfaceFlinger::triggerOnFrameRateOverridesChanged() {
@@ -3336,8 +3467,8 @@
 void SurfaceFlinger::initScheduler(const sp<const DisplayDevice>& display) {
     LOG_ALWAYS_FATAL_IF(mScheduler);
 
-    const auto activeModePtr = display->refreshRateConfigs().getActiveModePtr();
-    const Fps activeRefreshRate = activeModePtr->getFps();
+    const auto activeMode = display->refreshRateSelector().getActiveMode();
+    const Fps activeRefreshRate = activeMode.fps;
     mRefreshRateStats =
             std::make_unique<scheduler::RefreshRateStats>(*mTimeStats, activeRefreshRate,
                                                           hal::PowerMode::OFF);
@@ -3358,20 +3489,16 @@
         !getHwComposer().hasCapability(Capability::PRESENT_FENCE_IS_NOT_RELIABLE)) {
         features |= Feature::kPresentFences;
     }
+    if (display->refreshRateSelector().kernelIdleTimerController()) {
+        features |= Feature::kKernelIdleTimer;
+    }
 
     mScheduler = std::make_unique<scheduler::Scheduler>(static_cast<ICompositor&>(*this),
                                                         static_cast<ISchedulerCallback&>(*this),
                                                         features);
-    {
-        auto configs = display->holdRefreshRateConfigs();
-        if (configs->kernelIdleTimerController().has_value()) {
-            features |= Feature::kKernelIdleTimer;
-        }
+    mScheduler->createVsyncSchedule(features);
+    mScheduler->registerDisplay(display->getPhysicalId(), display->holdRefreshRateSelector());
 
-        mScheduler->createVsyncSchedule(features);
-        mScheduler->setRefreshRateConfigs(std::move(configs));
-        mScheduler->registerDisplay(display);
-    }
     setVsyncEnabled(false);
     mScheduler->startTimers();
 
@@ -3393,14 +3520,6 @@
             sp<RegionSamplingThread>::make(*this,
                                            RegionSamplingThread::EnvironmentTimingTunables());
     mFpsReporter = sp<FpsReporter>::make(*mFrameTimeline, *this);
-    // Dispatch a mode change request for the primary display on scheduler
-    // initialization, so that the EventThreads always contain a reference to a
-    // prior configuration.
-    //
-    // This is a bit hacky, but this avoids a back-pointer into the main SF
-    // classes from EventThread, and there should be no run-time binder cost
-    // anyway since there are no connected apps at this point.
-    mScheduler->onPrimaryDisplayModeChanged(mAppConnectionHandle, activeModePtr);
 }
 
 void SurfaceFlinger::updatePhaseConfiguration(const Fps& refreshRate) {
@@ -3566,9 +3685,9 @@
     return !mLayersWithQueuedFrames.empty() && newDataLatched;
 }
 
-status_t SurfaceFlinger::addClientLayer(const sp<Client>& client, const sp<IBinder>& handle,
+status_t SurfaceFlinger::addClientLayer(const LayerCreationArgs& args, const sp<IBinder>& handle,
                                         const sp<Layer>& layer, const wp<Layer>& parent,
-                                        bool addToRoot, uint32_t* outTransformHint) {
+                                        uint32_t* outTransformHint) {
     if (mNumLayers >= MAX_LAYERS) {
         ALOGE("AddClientLayer failed, mNumLayers (%zu) >= MAX_LAYERS (%zu)", mNumLayers.load(),
               MAX_LAYERS);
@@ -3599,12 +3718,7 @@
 
     {
         std::scoped_lock<std::mutex> lock(mCreatedLayersLock);
-        mCreatedLayers.emplace_back(layer, parent, addToRoot);
-    }
-
-    // attach this layer to the client
-    if (client != nullptr) {
-        client->attachLayer(handle, layer);
+        mCreatedLayers.emplace_back(layer, parent, args.addToRoot);
     }
 
     setTransactionFlags(eTransactionNeeded);
@@ -3664,7 +3778,7 @@
     using TransactionReadiness = TransactionHandler::TransactionReadiness;
     auto ready = TransactionReadiness::Ready;
     flushState.transaction->traverseStatesWithBuffersWhileTrue([&](const layer_state_t& s) -> bool {
-        sp<Layer> layer = Layer::fromHandle(s.surface).promote();
+        sp<Layer> layer = LayerHandle::getLayer(s.surface);
         const auto& transaction = *flushState.transaction;
         // check for barrier frames
         if (s.bufferData->hasBarrier &&
@@ -3709,7 +3823,10 @@
                 if (listener &&
                     (flushState.queueProcessTime - transaction.postTime) >
                             std::chrono::nanoseconds(4s).count()) {
-                    mTransactionHandler.onTransactionQueueStalled(transaction, listener);
+                    mTransactionHandler
+                            .onTransactionQueueStalled(transaction.id, listener,
+                                                       "Buffer processing hung up due to stuck "
+                                                       "fence. Indicates GPU hang");
                 }
                 return false;
             }
@@ -3837,7 +3954,7 @@
 }
 
 status_t SurfaceFlinger::setTransactionState(
-        const FrameTimelineInfo& frameTimelineInfo, const Vector<ComposerState>& states,
+        const FrameTimelineInfo& frameTimelineInfo, Vector<ComposerState>& states,
         const Vector<DisplayState>& displays, uint32_t flags, const sp<IBinder>& applyToken,
         const InputWindowCommands& inputWindowCommands, int64_t desiredPresentTime,
         bool isAutoTimestamp, const client_cache_t& uncacheBuffer, bool hasListenerCallbacks,
@@ -3869,7 +3986,29 @@
     IPCThreadState* ipc = IPCThreadState::self();
     const int originPid = ipc->getCallingPid();
     const int originUid = ipc->getCallingUid();
-    TransactionState state{frameTimelineInfo,  states,
+
+    std::vector<ResolvedComposerState> resolvedStates;
+    resolvedStates.reserve(states.size());
+    for (auto& state : states) {
+        resolvedStates.emplace_back(std::move(state));
+        auto& resolvedState = resolvedStates.back();
+        if (resolvedState.state.hasBufferChanges() && resolvedState.state.hasValidBuffer() &&
+            resolvedState.state.surface) {
+            sp<Layer> layer = LayerHandle::getLayer(resolvedState.state.surface);
+            std::string layerName = (layer) ?
+                    layer->getDebugName() : std::to_string(resolvedState.state.layerId);
+            resolvedState.externalTexture =
+                    getExternalTextureFromBufferData(*resolvedState.state.bufferData,
+                                                     layerName.c_str(), transactionId);
+            mBufferCountTracker.increment(resolvedState.state.surface->localBinder());
+            if (layer) {
+                resolvedState.hwcBufferSlot =
+                        layer->getHwcCacheSlot(resolvedState.state.bufferData->cachedBuffer);
+            }
+        }
+    }
+
+    TransactionState state{frameTimelineInfo,  resolvedStates,
                            displays,           flags,
                            applyToken,         inputWindowCommands,
                            desiredPresentTime, isAutoTimestamp,
@@ -3878,11 +4017,6 @@
                            listenerCallbacks,  originPid,
                            originUid,          transactionId};
 
-    // Check for incoming buffer updates and increment the pending buffer count.
-    state.traverseStatesWithBuffers([&](const layer_state_t& state) {
-        mBufferCountTracker.increment(state.surface->localBinder());
-    });
-
     if (mTransactionTracing) {
         mTransactionTracing->addQueuedTransaction(state);
     }
@@ -3901,7 +4035,7 @@
 }
 
 bool SurfaceFlinger::applyTransactionState(const FrameTimelineInfo& frameTimelineInfo,
-                                           Vector<ComposerState>& states,
+                                           std::vector<ResolvedComposerState>& states,
                                            const Vector<DisplayState>& displays, uint32_t flags,
                                            const InputWindowCommands& inputWindowCommands,
                                            const int64_t desiredPresentTime, bool isAutoTimestamp,
@@ -3923,12 +4057,12 @@
     }
 
     uint32_t clientStateFlags = 0;
-    for (int i = 0; i < states.size(); i++) {
-        ComposerState& state = states.editItemAt(i);
-        clientStateFlags |= setClientStateLocked(frameTimelineInfo, state, desiredPresentTime,
-                                                 isAutoTimestamp, postTime, permissions);
-        if ((flags & eAnimation) && state.state.surface) {
-            if (const auto layer = fromHandle(state.state.surface).promote()) {
+    for (auto& resolvedState : states) {
+        clientStateFlags |=
+                setClientStateLocked(frameTimelineInfo, resolvedState, desiredPresentTime,
+                                     isAutoTimestamp, postTime, permissions, transactionId);
+        if ((flags & eAnimation) && resolvedState.state.surface) {
+            if (const auto layer = LayerHandle::getLayer(resolvedState.state.surface)) {
                 using LayerUpdateType = scheduler::LayerHistory::LayerUpdateType;
                 mScheduler->recordLayerHistory(layer.get(),
                                                isAutoTimestamp ? 0 : desiredPresentTime,
@@ -4040,9 +4174,10 @@
 }
 
 uint32_t SurfaceFlinger::setClientStateLocked(const FrameTimelineInfo& frameTimelineInfo,
-                                              ComposerState& composerState,
+                                              ResolvedComposerState& composerState,
                                               int64_t desiredPresentTime, bool isAutoTimestamp,
-                                              int64_t postTime, uint32_t permissions) {
+                                              int64_t postTime, uint32_t permissions,
+                                              uint64_t transactionId) {
     layer_state_t& s = composerState.state;
     s.sanitize(permissions);
 
@@ -4068,7 +4203,7 @@
     uint32_t flags = 0;
     sp<Layer> layer = nullptr;
     if (s.surface) {
-        layer = fromHandle(s.surface).promote();
+        layer = LayerHandle::getLayer(s.surface);
     } else {
         // The client may provide us a null handle. Treat it as if the layer was removed.
         ALOGW("Attempt to set client state with a null layer handle");
@@ -4081,6 +4216,8 @@
         return 0;
     }
 
+    ui::LayerStack oldLayerStack = layer->getLayerStack(LayerVector::StateSet::Current);
+
     // Only set by BLAST adapter layers
     if (what & layer_state_t::eProducerDisconnect) {
         layer->onDisconnect();
@@ -4131,12 +4268,10 @@
         }
     }
     if (what & layer_state_t::eAlphaChanged) {
-        if (layer->setAlpha(s.alpha))
-            flags |= eTraversalNeeded;
+        if (layer->setAlpha(s.color.a)) flags |= eTraversalNeeded;
     }
     if (what & layer_state_t::eColorChanged) {
-        if (layer->setColor(s.color))
-            flags |= eTraversalNeeded;
+        if (layer->setColor(s.color.rgb)) flags |= eTraversalNeeded;
     }
     if (what & layer_state_t::eColorTransformChanged) {
         if (layer->setColorTransform(s.colorTransform)) {
@@ -4144,7 +4279,7 @@
         }
     }
     if (what & layer_state_t::eBackgroundColorChanged) {
-        if (layer->setBackgroundColor(s.color, s.bgColorAlpha, s.bgColorDataspace)) {
+        if (layer->setBackgroundColor(s.color.rgb, s.bgColorAlpha, s.bgColorDataspace)) {
             flags |= eTraversalNeeded;
         }
     }
@@ -4193,8 +4328,8 @@
             flags |= eTransactionNeeded | eTraversalNeeded | eTransformHintUpdateNeeded;
         }
     }
-    if (what & layer_state_t::eTransformChanged) {
-        if (layer->setTransform(s.transform)) flags |= eTraversalNeeded;
+    if (what & layer_state_t::eBufferTransformChanged) {
+        if (layer->setTransform(s.bufferTransform)) flags |= eTraversalNeeded;
     }
     if (what & layer_state_t::eTransformToDisplayInverseChanged) {
         if (layer->setTransformToDisplayInverse(s.transformToDisplayInverse))
@@ -4334,10 +4469,9 @@
     }
 
     if (what & layer_state_t::eBufferChanged) {
-        std::shared_ptr<renderengine::ExternalTexture> buffer =
-                getExternalTextureFromBufferData(*s.bufferData, layer->getDebugName());
-        if (layer->setBuffer(buffer, *s.bufferData, postTime, desiredPresentTime, isAutoTimestamp,
-                             dequeueBufferTimestamp, frameTimelineInfo)) {
+        if (layer->setBuffer(composerState.externalTexture, *s.bufferData, postTime,
+                             desiredPresentTime, isAutoTimestamp, dequeueBufferTimestamp,
+                             frameTimelineInfo, composerState.hwcBufferSlot)) {
             flags |= eTraversalNeeded;
         }
     } else if (frameTimelineInfo.vsyncId != FrameTimelineInfo::INVALID_VSYNC_ID) {
@@ -4347,6 +4481,13 @@
     if (layer->setTransactionCompletedListeners(callbackHandles)) flags |= eTraversalNeeded;
     // Do not put anything that updates layer state or modifies flags after
     // setTransactionCompletedListener
+
+    // if the layer has been parented on to a new display, update its transform hint.
+    if (((flags & eTransformHintUpdateNeeded) == 0) &&
+        oldLayerStack != layer->getLayerStack(LayerVector::StateSet::Current)) {
+        flags |= eTransformHintUpdateNeeded;
+    }
+
     return flags;
 }
 
@@ -4364,14 +4505,16 @@
 
     sp<Layer> mirrorLayer;
     sp<Layer> mirrorFrom;
+    LayerCreationArgs mirrorArgs(args);
     {
         Mutex::Autolock _l(mStateLock);
-        mirrorFrom = fromHandle(mirrorFromHandle).promote();
+        mirrorFrom = LayerHandle::getLayer(mirrorFromHandle);
         if (!mirrorFrom) {
             return NAME_NOT_FOUND;
         }
-        LayerCreationArgs mirrorArgs = args;
         mirrorArgs.flags |= ISurfaceComposerClient::eNoColorFill;
+        mirrorArgs.mirrorLayerHandle = mirrorFromHandle;
+        mirrorArgs.addToRoot = false;
         status_t result = createEffectLayer(mirrorArgs, &outResult.handle, &mirrorLayer);
         if (result != NO_ERROR) {
             return result;
@@ -4387,9 +4530,8 @@
                                                 mirrorLayer->sequence, args.name,
                                                 mirrorFrom->sequence);
     }
-    return addClientLayer(args.client, outResult.handle, mirrorLayer /* layer */,
-                          nullptr /* parent */, false /* addToRoot */,
-                          nullptr /* outTransformHint */);
+    return addClientLayer(mirrorArgs, outResult.handle, mirrorLayer /* layer */,
+                          nullptr /* parent */, nullptr /* outTransformHint */);
 }
 
 status_t SurfaceFlinger::mirrorDisplay(DisplayId displayId, const LayerCreationArgs& args,
@@ -4414,14 +4556,14 @@
         }
 
         layerStack = display->getLayerStack();
-        LayerCreationArgs mirrorArgs = args;
+        LayerCreationArgs mirrorArgs(args);
         mirrorArgs.flags |= ISurfaceComposerClient::eNoColorFill;
+        mirrorArgs.addToRoot = true;
         result = createEffectLayer(mirrorArgs, &outResult.handle, &rootMirrorLayer);
         outResult.layerId = rootMirrorLayer->sequence;
         outResult.layerName = String16(rootMirrorLayer->getDebugName());
-        result |= addClientLayer(args.client, outResult.handle, rootMirrorLayer /* layer */,
-                                 nullptr /* parent */, true /* addToRoot */,
-                                 nullptr /* outTransformHint */);
+        result |= addClientLayer(mirrorArgs, outResult.handle, rootMirrorLayer /* layer */,
+                                 nullptr /* parent */, nullptr /* outTransformHint */);
     }
 
     if (result != NO_ERROR) {
@@ -4442,8 +4584,7 @@
     return NO_ERROR;
 }
 
-status_t SurfaceFlinger::createLayer(LayerCreationArgs& args, const sp<IBinder>& parentHandle,
-                                     gui::CreateSurfaceResult& outResult) {
+status_t SurfaceFlinger::createLayer(LayerCreationArgs& args, gui::CreateSurfaceResult& outResult) {
     status_t result = NO_ERROR;
 
     sp<Layer> layer;
@@ -4472,28 +4613,23 @@
         return result;
     }
 
-    bool addToRoot = args.addToRoot && callingThreadHasUnscopedSurfaceFlingerAccess();
-    wp<Layer> parent = fromHandle(parentHandle);
-    if (parentHandle != nullptr && parent == nullptr) {
-        ALOGE("Invalid parent handle %p.", parentHandle.get());
-        addToRoot = false;
+    args.addToRoot = args.addToRoot && callingThreadHasUnscopedSurfaceFlingerAccess();
+    // We can safely promote the parent layer in binder thread because we have a strong reference
+    // to the layer's handle inside this scope.
+    sp<Layer> parent = LayerHandle::getLayer(args.parentHandle.promote());
+    if (args.parentHandle != nullptr && parent == nullptr) {
+        ALOGE("Invalid parent handle %p", args.parentHandle.promote().get());
+        args.addToRoot = false;
     }
 
-    int parentId = -1;
-    // We can safely promote the layer in binder thread because we have a strong reference
-    // to the layer's handle inside this scope or we were passed in a sp reference to the layer.
-    sp<Layer> parentSp = parent.promote();
-    if (parentSp != nullptr) {
-        parentId = parentSp->getSequence();
-    }
+    const int parentId = parent ? parent->getSequence() : -1;
     if (mTransactionTracing) {
         mTransactionTracing->onLayerAdded(outResult.handle->localBinder(), layer->sequence,
                                           args.name, args.flags, parentId);
     }
 
     uint32_t outTransformHint;
-    result = addClientLayer(args.client, outResult.handle, layer, parent, addToRoot,
-                            &outTransformHint);
+    result = addClientLayer(args, outResult.handle, layer, parent, &outTransformHint);
     if (result != NO_ERROR) {
         return result;
     }
@@ -4525,7 +4661,7 @@
     setTransactionFlags(eTransactionNeeded);
 }
 
-void SurfaceFlinger::onHandleDestroyed(BBinder* handle, sp<Layer>& layer) {
+void SurfaceFlinger::onHandleDestroyed(BBinder* handle, sp<Layer>& layer, uint32_t /* layerId */) {
     Mutex::Autolock lock(mStateLock);
     markLayerPendingRemovalLocked(layer);
     mBufferCountTracker.remove(handle);
@@ -4543,7 +4679,7 @@
     LOG_ALWAYS_FATAL_IF(token == nullptr);
 
     // reset screen orientation and use primary layer stack
-    Vector<ComposerState> state;
+    std::vector<ResolvedComposerState> state;
     Vector<DisplayState> displays;
     DisplayState d;
     d.what = DisplayState::eDisplayProjectionChanged |
@@ -4566,8 +4702,6 @@
                           {}, mPid, getuid(), transactionId);
 
     setPowerModeInternal(display, hal::PowerMode::ON);
-
-    mActiveDisplayTransformHint = display->getTransformHint();
 }
 
 void SurfaceFlinger::initializeDisplays() {
@@ -4586,8 +4720,8 @@
     const auto displayId = display->getPhysicalId();
     ALOGD("Setting power mode %d on display %s", mode, to_string(displayId).c_str());
 
-    std::optional<hal::PowerMode> currentMode = display->getPowerMode();
-    if (currentMode.has_value() && mode == *currentMode) {
+    const auto currentModeOpt = display->getPowerMode();
+    if (currentModeOpt == mode) {
         return;
     }
 
@@ -4597,18 +4731,18 @@
                                            .value_or(false);
 
     const auto activeDisplay = getDisplayDeviceLocked(mActiveDisplayId);
-    if (isInternalDisplay && activeDisplay != display && activeDisplay &&
-        activeDisplay->isPoweredOn()) {
-        ALOGW("Trying to change power mode on non active display while the active display is ON");
-    }
+    const bool isActiveDisplayPoweredOn = activeDisplay && activeDisplay->isPoweredOn();
+
+    ALOGW_IF(display != activeDisplay && isInternalDisplay && isActiveDisplayPoweredOn,
+             "Trying to change power mode on inactive display without powering off active display");
 
     display->setPowerMode(mode);
 
-    const auto refreshRate = display->refreshRateConfigs().getActiveMode().getFps();
-    if (*currentMode == hal::PowerMode::OFF) {
+    const auto refreshRate = display->refreshRateSelector().getActiveMode().modePtr->getFps();
+    if (!currentModeOpt || *currentModeOpt == hal::PowerMode::OFF) {
         // Turn on the display
-        if (isInternalDisplay && (!activeDisplay || !activeDisplay->isPoweredOn())) {
-            onActiveDisplayChangedLocked(display);
+        if (isInternalDisplay && !isActiveDisplayPoweredOn) {
+            onActiveDisplayChangedLocked(activeDisplay, display);
         }
         // Keep uclamp in a separate syscall and set it before changing to RT due to b/190237315.
         // We can merge the syscall later.
@@ -4635,7 +4769,7 @@
         if (SurfaceFlinger::setSchedAttr(false) != NO_ERROR) {
             ALOGW("Couldn't set uclamp.min on display off: %s\n", strerror(errno));
         }
-        if (isActiveDisplay && *currentMode != hal::PowerMode::DOZE_SUSPEND) {
+        if (isActiveDisplay && *currentModeOpt != hal::PowerMode::DOZE_SUSPEND) {
             mScheduler->disableHardwareVsync(true);
             mScheduler->onScreenReleased(mAppConnectionHandle);
         }
@@ -4649,7 +4783,7 @@
     } else if (mode == hal::PowerMode::DOZE || mode == hal::PowerMode::ON) {
         // Update display while dozing
         getHwComposer().setPowerMode(displayId, mode);
-        if (isActiveDisplay && *currentMode == hal::PowerMode::DOZE_SUSPEND) {
+        if (isActiveDisplay && *currentModeOpt == hal::PowerMode::DOZE_SUSPEND) {
             ALOGI("Force repainting for DOZE_SUSPEND -> DOZE or ON.");
             mVisibleRegionsDirty = true;
             scheduleRepaint();
@@ -4710,17 +4844,18 @@
                 {"--comp-displays"s, dumper(&SurfaceFlinger::dumpCompositionDisplays)},
                 {"--display-id"s, dumper(&SurfaceFlinger::dumpDisplayIdentificationData)},
                 {"--displays"s, dumper(&SurfaceFlinger::dumpDisplays)},
-                {"--dispsync"s, dumper([this](std::string& s) { mScheduler->dumpVsync(s); })},
                 {"--edid"s, argsDumper(&SurfaceFlinger::dumpRawDisplayIdentificationData)},
+                {"--events"s, dumper(&SurfaceFlinger::dumpEvents)},
+                {"--frametimeline"s, argsDumper(&SurfaceFlinger::dumpFrameTimeline)},
+                {"--hwclayers"s, dumper(&SurfaceFlinger::dumpHwcLayersMinidumpLocked)},
                 {"--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)},
+                {"--vsync"s, dumper(&SurfaceFlinger::dumpVsync)},
                 {"--wide-color"s, dumper(&SurfaceFlinger::dumpWideColorInfo)},
-                {"--frametimeline"s, argsDumper(&SurfaceFlinger::dumpFrameTimeline)},
-                {"--hwclayers"s, dumper(&SurfaceFlinger::dumpHwcLayersMinidumpLocked)},
         };
 
         const auto flag = args.empty() ? ""s : std::string(String8(args[0]));
@@ -4783,13 +4918,6 @@
 }
 
 status_t SurfaceFlinger::dumpCritical(int fd, const DumpArgs&, bool asProto) {
-    if (asProto) {
-        mLayerTracing.writeToFile();
-        if (mTransactionTracing) {
-            mTransactionTracing->writeToFile();
-        }
-    }
-
     return doDump(fd, DumpArgs(), asProto);
 }
 
@@ -4851,24 +4979,39 @@
     result.append("]");
 }
 
-void SurfaceFlinger::dumpVSync(std::string& result) const {
-    mScheduler->dump(result);
+void SurfaceFlinger::dumpScheduler(std::string& result) const {
+    utils::Dumper dumper{result};
+
+    mScheduler->dump(dumper);
+
+    // TODO(b/241286146): Move to Scheduler.
+    {
+        utils::Dumper::Indent indent(dumper);
+        dumper.dump("lastHwcVsyncState"sv, mLastHWCVsyncState);
+        dumper.dump("pendingHwcVsyncState"sv, mHWCVsyncPendingState);
+    }
+    dumper.eol();
+
+    // TODO(b/241285876): Move to DisplayModeController.
+    dumper.dump("debugDisplayModeSetByBackdoor"sv, mDebugDisplayModeSetByBackdoor);
+    dumper.eol();
 
     mRefreshRateStats->dump(result);
-    result.append("\n");
+    dumper.eol();
 
     mVsyncConfiguration->dump(result);
     StringAppendF(&result,
-                  "      present offset: %9" PRId64 " ns\t     VSYNC period: %9" PRId64 " ns\n\n",
+                  "         present offset: %9" PRId64 " ns\t        VSYNC period: %9" PRId64
+                  " ns\n\n",
                   dispSyncPresentTimeOffset, getVsyncPeriodFromHWC());
+}
 
-    StringAppendF(&result, "(mode override by backdoor: %s)\n\n",
-                  mDebugDisplayModeSetByBackdoor ? "yes" : "no");
-
+void SurfaceFlinger::dumpEvents(std::string& result) const {
     mScheduler->dump(mAppConnectionHandle, result);
+}
+
+void SurfaceFlinger::dumpVsync(std::string& result) const {
     mScheduler->dumpVsync(result);
-    StringAppendF(&result, "mHWCVsyncPendingState=%s mLastHWCVsyncState=%s\n",
-                  to_string(mHWCVsyncPendingState).c_str(), to_string(mLastHWCVsyncState).c_str());
 }
 
 void SurfaceFlinger::dumpPlannerInfo(const DumpArgs& args, std::string& result) const {
@@ -4889,19 +5032,21 @@
     utils::Dumper dumper{result};
 
     for (const auto& [id, display] : mPhysicalDisplays) {
+        utils::Dumper::Section section(dumper, ftl::Concat("Display ", id.value).str());
+
+        display.snapshot().dump(dumper);
+
         if (const auto device = getDisplayDeviceLocked(id)) {
             device->dump(dumper);
         }
-
-        utils::Dumper::Indent indent(dumper);
-        display.snapshot().dump(dumper);
-        dumper.eol();
     }
 
     for (const auto& [token, display] : mDisplays) {
         if (display->isVirtual()) {
+            const auto displayId = display->getId();
+            utils::Dumper::Section section(dumper,
+                                           ftl::Concat("Virtual Display ", displayId.value).str());
             display->dump(dumper);
-            dumper.eol();
         }
     }
 }
@@ -4980,8 +5125,22 @@
 }
 
 LayersProto SurfaceFlinger::dumpDrawingStateProto(uint32_t traceFlags) const {
+    std::unordered_set<uint64_t> stackIdsToSkip;
+
+    // Determine if virtual layers display should be skipped
+    if ((traceFlags & LayerTracing::TRACE_VIRTUAL_DISPLAYS) == 0) {
+        for (const auto& [_, display] : FTL_FAKE_GUARD(mStateLock, mDisplays)) {
+            if (display->isVirtual()) {
+                stackIdsToSkip.insert(display->getLayerStack().id);
+            }
+        }
+    }
+
     LayersProto layersProto;
     for (const sp<Layer>& layer : mDrawingState.layersSortedByZ) {
+        if (stackIdsToSkip.find(layer->getLayerStack().id) != stackIdsToSkip.end()) {
+            continue;
+        }
         layer->writeToProto(layersProto, traceFlags);
     }
 
@@ -5058,7 +5217,7 @@
         Layer::miniDumpHeader(result);
 
         const DisplayDevice& ref = *display;
-        mCurrentState.traverseInZOrder([&](Layer* layer) { layer->miniDump(result, ref); });
+        mDrawingState.traverseInZOrder([&](Layer* layer) { layer->miniDump(result, ref); });
         result.append("\n");
     }
 }
@@ -5098,7 +5257,9 @@
     colorizer.bold(result);
     result.append("Scheduler:\n");
     colorizer.reset(result);
-    dumpVSync(result);
+    dumpScheduler(result);
+    dumpEvents(result);
+    dumpVsync(result);
     result.append("\n");
 
     StringAppendF(&result, "Total missed frame count: %u\n", mFrameMissedCount.load());
@@ -5143,14 +5304,12 @@
         StringAppendF(&result, "  orientation=%s, isPoweredOn=%d\n",
                       toCString(display->getOrientation()), display->isPoweredOn());
     }
-    StringAppendF(&result,
-                  "  transaction-flags         : %08x\n"
-                  "  gpu_to_cpu_unsupported    : %d\n",
-                  mTransactionFlags.load(), !mGpuToCpuSupported);
+    StringAppendF(&result, "  transaction-flags         : %08x\n", mTransactionFlags.load());
 
     if (const auto display = getDefaultDisplayDeviceLocked()) {
         std::string fps, xDpi, yDpi;
-        if (const auto activeModePtr = display->refreshRateConfigs().getActiveModePtr()) {
+        if (const auto activeModePtr =
+                    display->refreshRateSelector().getActiveMode().modePtr.get()) {
             fps = to_string(activeModePtr->getFps());
 
             const auto dpi = activeModePtr->getDpi();
@@ -5696,8 +5855,8 @@
                                 // defaultMode. The defaultMode doesn't matter for the override
                                 // policy though, since we set allowGroupSwitching to true, so it's
                                 // not a problem.
-                                scheduler::RefreshRateConfigs::OverridePolicy overridePolicy;
-                                overridePolicy.defaultMode = display->refreshRateConfigs()
+                                scheduler::RefreshRateSelector::OverridePolicy overridePolicy;
+                                overridePolicy.defaultMode = display->refreshRateSelector()
                                                                      .getDisplayManagerPolicy()
                                                                      .defaultMode;
                                 overridePolicy.allowGroupSwitching = true;
@@ -5710,7 +5869,8 @@
                                 const auto display =
                                         FTL_FAKE_GUARD(mStateLock, getDefaultDisplayDeviceLocked());
                                 return setDesiredDisplayModeSpecsInternal(
-                                        display, scheduler::RefreshRateConfigs::NoOverridePolicy{});
+                                        display,
+                                        scheduler::RefreshRateSelector::NoOverridePolicy{});
                             })
                             .get();
                 }
@@ -5821,7 +5981,7 @@
     if (!updateOverlay) return;
 
     // Update the overlay on the main thread to avoid race conditions with
-    // mRefreshRateConfigs->getActiveMode()
+    // RefreshRateSelector::getActiveMode
     static_cast<void>(mScheduler->schedule([=] {
         const auto display = FTL_FAKE_GUARD(mStateLock, getDefaultDisplayDeviceLocked());
         if (!display) {
@@ -5832,7 +5992,8 @@
 
         const auto desiredActiveMode = display->getDesiredActiveMode();
         const std::optional<DisplayModeId> desiredModeId = desiredActiveMode
-                ? std::make_optional(desiredActiveMode->mode->getId())
+                ? std::make_optional(desiredActiveMode->modeOpt->modePtr->getId())
+
                 : std::nullopt;
 
         const bool timerExpired = mKernelIdleTimerEnabled && expired;
@@ -5885,7 +6046,7 @@
 }
 
 void SurfaceFlinger::toggleKernelIdleTimer() {
-    using KernelIdleTimerAction = scheduler::RefreshRateConfigs::KernelIdleTimerAction;
+    using KernelIdleTimerAction = scheduler::RefreshRateSelector::KernelIdleTimerAction;
 
     const auto display = getDefaultDisplayDeviceLocked();
     if (!display) {
@@ -5896,12 +6057,12 @@
     // If the support for kernel idle timer is disabled for the active display,
     // don't do anything.
     const std::optional<KernelIdleTimerController> kernelIdleTimerController =
-            display->refreshRateConfigs().kernelIdleTimerController();
+            display->refreshRateSelector().kernelIdleTimerController();
     if (!kernelIdleTimerController.has_value()) {
         return;
     }
 
-    const KernelIdleTimerAction action = display->refreshRateConfigs().getIdleTimerAction();
+    const KernelIdleTimerAction action = display->refreshRateSelector().getIdleTimerAction();
 
     switch (action) {
         case KernelIdleTimerAction::TurnOff:
@@ -5917,7 +6078,7 @@
             if (!mKernelIdleTimerEnabled) {
                 ATRACE_INT("KernelIdleTimer", 1);
                 const std::chrono::milliseconds timeout =
-                        display->refreshRateConfigs().getIdleTimerTimeout();
+                        display->refreshRateSelector().getIdleTimerTimeout();
                 updateKernelIdleTimer(timeout, kernelIdleTimerController.value(),
                                       display->getPhysicalId());
                 mKernelIdleTimerEnabled = true;
@@ -6137,7 +6298,7 @@
     {
         Mutex::Autolock lock(mStateLock);
 
-        parent = fromHandle(args.layerHandle).promote();
+        parent = LayerHandle::getLayer(args.layerHandle);
         if (parent == nullptr) {
             ALOGE("captureLayers called with an invalid or removed parent");
             return NAME_NOT_FOUND;
@@ -6168,7 +6329,7 @@
         reqSize = ui::Size(crop.width() * args.frameScaleX, crop.height() * args.frameScaleY);
 
         for (const auto& handle : args.excludeHandles) {
-            sp<Layer> excludeLayer = fromHandle(handle).promote();
+            sp<Layer> excludeLayer = LayerHandle::getLayer(handle);
             if (excludeLayer != nullptr) {
                 excludeLayers.emplace(excludeLayer);
             } else {
@@ -6190,7 +6351,8 @@
         return BAD_VALUE;
     }
 
-    Rect layerStackSpaceRect(0, 0, reqSize.width, reqSize.height);
+    Rect layerStackSpaceRect(crop.left, crop.top, crop.left + reqSize.width,
+                             crop.top + reqSize.height);
     bool childrenOnly = args.childrenOnly;
     RenderAreaFuture renderAreaFuture = ftl::defer([=]() -> std::unique_ptr<RenderArea> {
         return std::make_unique<LayerRenderArea>(*this, parent, crop, reqSize, dataspace,
@@ -6296,37 +6458,40 @@
 
     bool canCaptureBlackoutContent = hasCaptureBlackoutContentPermission();
 
-    auto future = mScheduler->schedule([=, renderAreaFuture = std::move(renderAreaFuture)]() mutable
-                                       -> ftl::SharedFuture<FenceResult> {
-        ScreenCaptureResults captureResults;
-        std::unique_ptr<RenderArea> renderArea = renderAreaFuture.get();
-        if (!renderArea) {
-            ALOGW("Skipping screen capture because of invalid render area.");
-            captureResults.fenceResult = base::unexpected(NO_MEMORY);
-            captureListener->onScreenCaptureCompleted(captureResults);
-            return ftl::yield<FenceResult>(base::unexpected(NO_ERROR)).share();
-        }
-
-        ftl::SharedFuture<FenceResult> renderFuture;
-        renderArea->render([&] {
-            renderFuture =
-                    renderScreenImpl(*renderArea, traverseLayers, buffer, canCaptureBlackoutContent,
-                                     regionSampling, grayscale, captureResults);
-        });
-
-        if (captureListener) {
-            // Defer blocking on renderFuture back to the Binder thread.
-            return ftl::Future(std::move(renderFuture))
-                    .then([captureListener, captureResults = std::move(captureResults)](
-                                  FenceResult fenceResult) mutable -> FenceResult {
-                        captureResults.fenceResult = std::move(fenceResult);
+    auto future = mScheduler->schedule(
+            [=, renderAreaFuture = std::move(renderAreaFuture)]() FTL_FAKE_GUARD(
+                    kMainThreadContext) mutable -> ftl::SharedFuture<FenceResult> {
+                ScreenCaptureResults captureResults;
+                std::unique_ptr<RenderArea> renderArea = renderAreaFuture.get();
+                if (!renderArea) {
+                    ALOGW("Skipping screen capture because of invalid render area.");
+                    if (captureListener) {
+                        captureResults.fenceResult = base::unexpected(NO_MEMORY);
                         captureListener->onScreenCaptureCompleted(captureResults);
-                        return base::unexpected(NO_ERROR);
-                    })
-                    .share();
-        }
-        return renderFuture;
-    });
+                    }
+                    return ftl::yield<FenceResult>(base::unexpected(NO_ERROR)).share();
+                }
+
+                ftl::SharedFuture<FenceResult> renderFuture;
+                renderArea->render([&]() FTL_FAKE_GUARD(kMainThreadContext) {
+                    renderFuture = renderScreenImpl(std::move(renderArea), traverseLayers, buffer,
+                                                    canCaptureBlackoutContent, regionSampling,
+                                                    grayscale, captureResults);
+                });
+
+                if (captureListener) {
+                    // Defer blocking on renderFuture back to the Binder thread.
+                    return ftl::Future(std::move(renderFuture))
+                            .then([captureListener, captureResults = std::move(captureResults)](
+                                          FenceResult fenceResult) mutable -> FenceResult {
+                                captureResults.fenceResult = std::move(fenceResult);
+                                captureListener->onScreenCaptureCompleted(captureResults);
+                                return base::unexpected(NO_ERROR);
+                            })
+                            .share();
+                }
+                return renderFuture;
+            });
 
     // Flatten nested futures.
     auto chain = ftl::Future(std::move(future)).then([](ftl::SharedFuture<FenceResult> future) {
@@ -6337,19 +6502,19 @@
 }
 
 ftl::SharedFuture<FenceResult> SurfaceFlinger::renderScreenImpl(
-        const RenderArea& renderArea, TraverseLayersFunction traverseLayers,
+        std::unique_ptr<RenderArea> renderArea, TraverseLayersFunction traverseLayers,
         const std::shared_ptr<renderengine::ExternalTexture>& buffer,
         bool canCaptureBlackoutContent, bool regionSampling, bool grayscale,
         ScreenCaptureResults& captureResults) {
     ATRACE_CALL();
 
+    size_t layerCount = 0;
     traverseLayers([&](Layer* layer) {
+        layerCount++;
         captureResults.capturedSecureLayers =
                 captureResults.capturedSecureLayers || (layer->isVisible() && layer->isSecure());
     });
 
-    const bool useProtected = buffer->getUsage() & GRALLOC_USAGE_PROTECTED;
-
     // 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.
@@ -6359,8 +6524,8 @@
     }
 
     captureResults.buffer = buffer->getBuffer();
-    auto dataspace = renderArea.getReqDataSpace();
-    auto parent = renderArea.getParentLayer();
+    auto dataspace = renderArea->getReqDataSpace();
+    auto parent = renderArea->getParentLayer();
     auto renderIntent = RenderIntent::TONE_MAP_COLORIMETRIC;
     auto sdrWhitePointNits = DisplayDevice::sDefaultMaxLumiance;
     auto displayBrightnessNits = DisplayDevice::sDefaultMaxLumiance;
@@ -6382,117 +6547,106 @@
     }
     captureResults.capturedDataspace = dataspace;
 
-    const auto reqWidth = renderArea.getReqWidth();
-    const auto reqHeight = renderArea.getReqHeight();
-    const auto sourceCrop = renderArea.getSourceCrop();
-    const auto transform = renderArea.getTransform();
-    const auto rotation = renderArea.getRotationFlags();
-    const auto& layerStackSpaceRect = renderArea.getLayerStackSpaceRect();
+    const auto transform = renderArea->getTransform();
+    const auto display = renderArea->getDisplayDevice();
 
-    renderengine::DisplaySettings clientCompositionDisplay;
-    std::vector<compositionengine::LayerFE::LayerSettings> clientCompositionLayers;
-
-    // assume that bounds are never offset, and that they are the same as the
-    // buffer bounds.
-    clientCompositionDisplay.physicalDisplay = Rect(reqWidth, reqHeight);
-    clientCompositionDisplay.clip = sourceCrop;
-    clientCompositionDisplay.orientation = rotation;
-
-    clientCompositionDisplay.outputDataspace = dataspace;
-    clientCompositionDisplay.currentLuminanceNits = displayBrightnessNits;
-    clientCompositionDisplay.maxLuminance = DisplayDevice::sDefaultMaxLumiance;
-    clientCompositionDisplay.renderIntent =
-            static_cast<aidl::android::hardware::graphics::composer3::RenderIntent>(renderIntent);
-
-    const float colorSaturation = grayscale ? 0 : 1;
-    clientCompositionDisplay.colorTransform = calculateColorMatrix(colorSaturation);
-
-    const float alpha = RenderArea::getCaptureFillValue(renderArea.getCaptureFill());
-
-    compositionengine::LayerFE::LayerSettings fillLayer;
-    fillLayer.source.buffer.buffer = nullptr;
-    fillLayer.source.solidColor = half3(0.0, 0.0, 0.0);
-    fillLayer.geometry.boundaries =
-            FloatRect(sourceCrop.left, sourceCrop.top, sourceCrop.right, sourceCrop.bottom);
-    fillLayer.alpha = half(alpha);
-    clientCompositionLayers.push_back(fillLayer);
-
-    const auto display = renderArea.getDisplayDevice();
-    std::vector<Layer*> renderedLayers;
-    bool disableBlurs = false;
+    std::vector<std::pair<Layer*, sp<LayerFE>>> layers;
+    layers.reserve(layerCount);
+    std::unordered_set<compositionengine::LayerFE*> filterForScreenshot;
     traverseLayers([&](Layer* layer) {
+        captureResults.capturedHdrLayers |= isHdrLayer(layer);
         // Layer::prepareClientComposition uses the layer's snapshot to populate the resulting
         // LayerSettings. Calling Layer::updateSnapshot ensures that LayerSettings are
         // generated with the layer's current buffer and geometry.
         layer->updateSnapshot(true /* updateGeometry */);
 
-        disableBlurs |= layer->getDrawingState().sidebandStream != nullptr;
+        layers.emplace_back(layer, layer->copyCompositionEngineLayerFE());
 
-        Region clip(renderArea.getBounds());
-        compositionengine::LayerFE::ClientCompositionTargetSettings targetSettings{
-                clip,
-                layer->needsFilteringForScreenshots(display.get(), transform) ||
-                        renderArea.needsFiltering(),
-                renderArea.isSecure(),
-                useProtected,
-                layerStackSpaceRect,
-                clientCompositionDisplay.outputDataspace,
-                true,  /* realContentIsVisible */
-                false, /* clearContent */
-                disableBlurs ? compositionengine::LayerFE::ClientCompositionTargetSettings::
-                                       BlurSetting::Disabled
-                             : compositionengine::LayerFE::ClientCompositionTargetSettings::
-                                       BlurSetting::Enabled,
-                isHdrLayer(layer) ? displayBrightnessNits : sdrWhitePointNits,
+        sp<LayerFE>& layerFE = layers.back().second;
 
-        };
-        std::optional<compositionengine::LayerFE::LayerSettings> settings =
-                layer->prepareClientComposition(targetSettings);
-        if (!settings) {
-            return;
+        layerFE->mSnapshot->geomLayerTransform =
+                renderArea->getTransform() * layerFE->mSnapshot->geomLayerTransform;
+
+        if (layer->needsFilteringForScreenshots(display.get(), transform)) {
+            filterForScreenshot.insert(layerFE.get());
         }
-
-        settings->geometry.positionTransform =
-                transform.asMatrix4() * settings->geometry.positionTransform;
-        // There's no need to process blurs when we're executing region sampling,
-        // we're just trying to understand what we're drawing, and doing so without
-        // blurs is already a pretty good approximation.
-        if (regionSampling) {
-            settings->backgroundBlurRadius = 0;
-            settings->blurRegions.clear();
-        }
-        captureResults.capturedHdrLayers |= isHdrLayer(layer);
-
-        clientCompositionLayers.push_back(std::move(*settings));
-        renderedLayers.push_back(layer);
     });
 
-    std::vector<renderengine::LayerSettings> clientRenderEngineLayers;
-    clientRenderEngineLayers.reserve(clientCompositionLayers.size());
-    std::transform(clientCompositionLayers.begin(), clientCompositionLayers.end(),
-                   std::back_inserter(clientRenderEngineLayers),
-                   [](compositionengine::LayerFE::LayerSettings& settings)
-                           -> renderengine::LayerSettings { return settings; });
-
-    // Use an empty fence for the buffer fence, since we just created the buffer so
-    // there is no need for synchronization with the GPU.
-    base::unique_fd bufferFence;
-    getRenderEngine().useProtectedContext(useProtected);
-
-    constexpr bool kUseFramebufferCache = false;
-    const auto future = getRenderEngine()
-                                .drawLayers(clientCompositionDisplay, clientRenderEngineLayers,
-                                            buffer, kUseFramebufferCache, std::move(bufferFence))
-                                .share();
-
-    for (auto* layer : renderedLayers) {
-        layer->onLayerDisplayed(future);
+    ui::LayerStack layerStack{ui::DEFAULT_LAYER_STACK};
+    if (!layers.empty()) {
+        const sp<LayerFE>& layerFE = layers.back().second;
+        layerStack = layerFE->getCompositionState()->outputFilter.layerStack;
     }
 
-    // Always switch back to unprotected context.
-    getRenderEngine().useProtectedContext(false);
+    auto copyLayerFEs = [&layers]() {
+        std::vector<sp<compositionengine::LayerFE>> layerFEs;
+        layerFEs.reserve(layers.size());
+        for (const auto& [_, layerFE] : layers) {
+            layerFEs.push_back(layerFE);
+        }
+        return layerFEs;
+    };
 
-    return future;
+    auto present = [this, buffer = std::move(buffer), dataspace, sdrWhitePointNits,
+                    displayBrightnessNits, filterForScreenshot = std::move(filterForScreenshot),
+                    grayscale, layerFEs = copyLayerFEs(), layerStack, regionSampling,
+                    renderArea = std::move(renderArea), renderIntent]() -> FenceResult {
+        std::unique_ptr<compositionengine::CompositionEngine> compositionEngine =
+                mFactory.createCompositionEngine();
+        compositionEngine->setRenderEngine(mRenderEngine.get());
+
+        compositionengine::Output::ColorProfile colorProfile{.dataspace = dataspace,
+                                                             .renderIntent = renderIntent};
+
+        std::shared_ptr<ScreenCaptureOutput> output = createScreenCaptureOutput(
+                ScreenCaptureOutputArgs{.compositionEngine = *compositionEngine,
+                                        .colorProfile = colorProfile,
+                                        .renderArea = *renderArea,
+                                        .layerStack = layerStack,
+                                        .buffer = std::move(buffer),
+                                        .sdrWhitePointNits = sdrWhitePointNits,
+                                        .displayBrightnessNits = displayBrightnessNits,
+                                        .filterForScreenshot = std::move(filterForScreenshot),
+                                        .regionSampling = regionSampling});
+
+        const float colorSaturation = grayscale ? 0 : 1;
+        compositionengine::CompositionRefreshArgs refreshArgs{
+                .outputs = {output},
+                .layers = std::move(layerFEs),
+                .updatingOutputGeometryThisFrame = true,
+                .updatingGeometryThisFrame = true,
+                .colorTransformMatrix = calculateColorMatrix(colorSaturation),
+        };
+        compositionEngine->present(refreshArgs);
+
+        return output->getRenderSurface()->getClientTargetAcquireFence();
+    };
+
+    // If RenderEngine is threaded, we can safely call CompositionEngine::present off the main
+    // thread as the RenderEngine::drawLayers call will run on RenderEngine's thread. Otherwise,
+    // we need RenderEngine to run on the main thread so we call CompositionEngine::present
+    // immediately.
+    //
+    // TODO(b/196334700) Once we use RenderEngineThreaded everywhere we can always defer the call
+    // to CompositionEngine::present.
+    const bool renderEngineIsThreaded = [&]() {
+        using Type = renderengine::RenderEngine::RenderEngineType;
+        const auto type = mRenderEngine->getRenderEngineType();
+        return type == Type::THREADED || type == Type::SKIA_GL_THREADED;
+    }();
+    auto presentFuture = renderEngineIsThreaded ? ftl::defer(std::move(present)).share()
+                                                : ftl::yield(present()).share();
+
+    for (auto& [layer, layerFE] : layers) {
+        layer->onLayerDisplayed(
+                ftl::Future(presentFuture)
+                        .then([layerFE = std::move(layerFE)](FenceResult) {
+                            return layerFE->stealCompositionResult().releaseFences.back().get();
+                        })
+                        .share());
+    }
+
+    return presentFuture;
 }
 
 // ---------------------------------------------------------------------------
@@ -6533,10 +6687,10 @@
     }
 }
 
-std::optional<DisplayModePtr> SurfaceFlinger::getPreferredDisplayMode(
+ftl::Optional<scheduler::FrameRateMode> SurfaceFlinger::getPreferredDisplayMode(
         PhysicalDisplayId displayId, DisplayModeId defaultModeId) const {
     if (const auto schedulerMode = mScheduler->getPreferredDisplayMode();
-        schedulerMode && schedulerMode->getPhysicalDisplayId() == displayId) {
+        schedulerMode && schedulerMode->modePtr->getPhysicalDisplayId() == displayId) {
         return schedulerMode;
     }
 
@@ -6544,12 +6698,15 @@
             .transform(&PhysicalDisplay::snapshotRef)
             .and_then([&](const display::DisplaySnapshot& snapshot) {
                 return snapshot.displayModes().get(defaultModeId);
+            })
+            .transform([](const DisplayModePtr& modePtr) {
+                return scheduler::FrameRateMode{modePtr->getFps(), ftl::as_non_null(modePtr)};
             });
 }
 
 status_t SurfaceFlinger::setDesiredDisplayModeSpecsInternal(
         const sp<DisplayDevice>& display,
-        const scheduler::RefreshRateConfigs::PolicyVariant& policy) {
+        const scheduler::RefreshRateSelector::PolicyVariant& policy) {
     const auto displayId = display->getPhysicalId();
 
     Mutex::Autolock lock(mStateLock);
@@ -6559,28 +6716,31 @@
         return NO_ERROR;
     }
 
-    auto& configs = display->refreshRateConfigs();
-    using SetPolicyResult = scheduler::RefreshRateConfigs::SetPolicyResult;
+    auto& selector = display->refreshRateSelector();
+    using SetPolicyResult = scheduler::RefreshRateSelector::SetPolicyResult;
 
-    switch (configs.setPolicy(policy)) {
+    switch (selector.setPolicy(policy)) {
         case SetPolicyResult::Invalid:
             return BAD_VALUE;
         case SetPolicyResult::Unchanged:
             return NO_ERROR;
         case SetPolicyResult::Changed:
-            break;
+            return applyRefreshRateSelectorPolicy(displayId, selector);
     }
+}
 
-    const scheduler::RefreshRateConfigs::Policy currentPolicy = configs.getCurrentPolicy();
+status_t SurfaceFlinger::applyRefreshRateSelectorPolicy(
+        PhysicalDisplayId displayId, const scheduler::RefreshRateSelector& selector) {
+    const scheduler::RefreshRateSelector::Policy currentPolicy = selector.getCurrentPolicy();
     ALOGV("Setting desired display mode specs: %s", currentPolicy.toString().c_str());
 
     // TODO(b/140204874): Leave the event in until we do proper testing with all apps that might
     // be depending in this callback.
-    if (const auto activeModePtr = configs.getActiveModePtr(); displayId == mActiveDisplayId) {
-        mScheduler->onPrimaryDisplayModeChanged(mAppConnectionHandle, activeModePtr);
+    if (const auto activeMode = selector.getActiveMode(); displayId == mActiveDisplayId) {
+        mScheduler->onPrimaryDisplayModeChanged(mAppConnectionHandle, activeMode);
         toggleKernelIdleTimer();
     } else {
-        mScheduler->onNonPrimaryDisplayModeChanged(mAppConnectionHandle, activeModePtr);
+        mScheduler->onNonPrimaryDisplayModeChanged(mAppConnectionHandle, activeMode);
     }
 
     auto preferredModeOpt = getPreferredDisplayMode(displayId, currentPolicy.defaultMode);
@@ -6590,24 +6750,47 @@
     }
 
     auto preferredMode = std::move(*preferredModeOpt);
-    const auto preferredModeId = preferredMode->getId();
+    const auto preferredModeId = preferredMode.modePtr->getId();
 
     ALOGV("Switching to Scheduler preferred mode %d (%s)", preferredModeId.value(),
-          to_string(preferredMode->getFps()).c_str());
+          to_string(preferredMode.fps).c_str());
 
-    if (!configs.isModeAllowed(preferredModeId)) {
+    if (!selector.isModeAllowed(preferredMode)) {
         ALOGE("%s: Preferred mode %d is disallowed", __func__, preferredModeId.value());
         return INVALID_OPERATION;
     }
 
-    setDesiredActiveMode({std::move(preferredMode), DisplayModeEvent::Changed});
+    setDesiredActiveMode({std::move(preferredMode), .emitEvent = true});
     return NO_ERROR;
 }
 
-status_t SurfaceFlinger::setDesiredDisplayModeSpecs(
-        const sp<IBinder>& displayToken, ui::DisplayModeId defaultMode, bool allowGroupSwitching,
-        float primaryRefreshRateMin, float primaryRefreshRateMax, float appRequestRefreshRateMin,
-        float appRequestRefreshRateMax) {
+namespace {
+FpsRange translate(const gui::DisplayModeSpecs::RefreshRateRanges::RefreshRateRange& aidlRange) {
+    return FpsRange{Fps::fromValue(aidlRange.min), Fps::fromValue(aidlRange.max)};
+}
+
+FpsRanges translate(const gui::DisplayModeSpecs::RefreshRateRanges& aidlRanges) {
+    return FpsRanges{translate(aidlRanges.physical), translate(aidlRanges.render)};
+}
+
+gui::DisplayModeSpecs::RefreshRateRanges::RefreshRateRange translate(const FpsRange& range) {
+    gui::DisplayModeSpecs::RefreshRateRanges::RefreshRateRange aidlRange;
+    aidlRange.min = range.min.getValue();
+    aidlRange.max = range.max.getValue();
+    return aidlRange;
+}
+
+gui::DisplayModeSpecs::RefreshRateRanges translate(const FpsRanges& ranges) {
+    gui::DisplayModeSpecs::RefreshRateRanges aidlRanges;
+    aidlRanges.physical = translate(ranges.physical);
+    aidlRanges.render = translate(ranges.render);
+    return aidlRanges;
+}
+
+} // namespace
+
+status_t SurfaceFlinger::setDesiredDisplayModeSpecs(const sp<IBinder>& displayToken,
+                                                    const gui::DisplayModeSpecs& specs) {
     ATRACE_CALL();
 
     if (!displayToken) {
@@ -6624,13 +6807,9 @@
             ALOGW("Attempt to set desired display modes for virtual display");
             return INVALID_OPERATION;
         } else {
-            using Policy = scheduler::RefreshRateConfigs::DisplayManagerPolicy;
-            const Policy policy{DisplayModeId(defaultMode),
-                                allowGroupSwitching,
-                                {Fps::fromValue(primaryRefreshRateMin),
-                                 Fps::fromValue(primaryRefreshRateMax)},
-                                {Fps::fromValue(appRequestRefreshRateMin),
-                                 Fps::fromValue(appRequestRefreshRateMax)}};
+            using Policy = scheduler::RefreshRateSelector::DisplayManagerPolicy;
+            const Policy policy{DisplayModeId(specs.defaultMode), translate(specs.primaryRanges),
+                                translate(specs.appRequestRanges), specs.allowGroupSwitching};
 
             return setDesiredDisplayModeSpecsInternal(display, policy);
         }
@@ -6640,16 +6819,10 @@
 }
 
 status_t SurfaceFlinger::getDesiredDisplayModeSpecs(const sp<IBinder>& displayToken,
-                                                    ui::DisplayModeId* outDefaultMode,
-                                                    bool* outAllowGroupSwitching,
-                                                    float* outPrimaryRefreshRateMin,
-                                                    float* outPrimaryRefreshRateMax,
-                                                    float* outAppRequestRefreshRateMin,
-                                                    float* outAppRequestRefreshRateMax) {
+                                                    gui::DisplayModeSpecs* outSpecs) {
     ATRACE_CALL();
 
-    if (!displayToken || !outDefaultMode || !outPrimaryRefreshRateMin ||
-        !outPrimaryRefreshRateMax || !outAppRequestRefreshRateMin || !outAppRequestRefreshRateMax) {
+    if (!displayToken || !outSpecs) {
         return BAD_VALUE;
     }
 
@@ -6663,21 +6836,15 @@
         return INVALID_OPERATION;
     }
 
-    scheduler::RefreshRateConfigs::Policy policy =
-            display->refreshRateConfigs().getDisplayManagerPolicy();
-    *outDefaultMode = policy.defaultMode.value();
-    *outAllowGroupSwitching = policy.allowGroupSwitching;
-    *outPrimaryRefreshRateMin = policy.primaryRange.min.getValue();
-    *outPrimaryRefreshRateMax = policy.primaryRange.max.getValue();
-    *outAppRequestRefreshRateMin = policy.appRequestRange.min.getValue();
-    *outAppRequestRefreshRateMax = policy.appRequestRange.max.getValue();
+    scheduler::RefreshRateSelector::Policy policy =
+            display->refreshRateSelector().getDisplayManagerPolicy();
+    outSpecs->defaultMode = policy.defaultMode.value();
+    outSpecs->allowGroupSwitching = policy.allowGroupSwitching;
+    outSpecs->primaryRanges = translate(policy.primaryRanges);
+    outSpecs->appRequestRanges = translate(policy.appRequestRanges);
     return NO_ERROR;
 }
 
-wp<Layer> SurfaceFlinger::fromHandle(const sp<IBinder>& handle) const {
-    return Layer::fromHandle(handle);
-}
-
 void SurfaceFlinger::onLayerFirstRef(Layer* layer) {
     mNumLayers++;
     if (!layer->isRemovedFromCurrentState()) {
@@ -6761,7 +6928,8 @@
     for (const auto& [id, display] : mPhysicalDisplays) {
         if (display.snapshot().connectionType() == ui::DisplayConnectionType::Internal) {
             if (const auto device = getDisplayDeviceLocked(id)) {
-                device->enableRefreshRateOverlay(enable, mRefreshRateOverlaySpinner);
+                device->enableRefreshRateOverlay(enable, mRefreshRateOverlaySpinner,
+                                                 mRefreshRateOverlayRenderRate);
             }
         }
     }
@@ -6785,7 +6953,7 @@
 
     if (!getHwComposer().isHeadless()) {
         if (const auto display = getDefaultDisplayDevice()) {
-            maxRefreshRate = display->refreshRateConfigs().getSupportedRefreshRateRange().max;
+            maxRefreshRate = display->refreshRateSelector().getSupportedRefreshRateRange().max;
         }
     }
 
@@ -6800,7 +6968,7 @@
         refreshRate = *frameRateOverride;
     } else if (!getHwComposer().isHeadless()) {
         if (const auto display = FTL_FAKE_GUARD(mStateLock, getDefaultDisplayDeviceLocked())) {
-            refreshRate = display->refreshRateConfigs().getActiveModePtr()->getFps();
+            refreshRate = display->refreshRateSelector().getActiveMode().fps;
         }
     }
 
@@ -6842,7 +7010,20 @@
         parent->addChild(layer);
     }
 
-    layer->updateTransformHint(mActiveDisplayTransformHint);
+    ui::LayerStack layerStack = layer->getLayerStack(LayerVector::StateSet::Current);
+    sp<const DisplayDevice> hintDisplay;
+    // Find the display that includes the layer.
+    for (const auto& [token, display] : mDisplays) {
+        if (display->getLayerStack() == layerStack) {
+            hintDisplay = display;
+            break;
+        }
+    }
+
+    if (hintDisplay) {
+        layer->updateTransformHint(hintDisplay->getTransformHint());
+    }
+
     if (mTransactionTracing) {
         mTransactionTracing->onLayerAddedToDrawingState(layer->getSequence(), vsyncId.value);
     }
@@ -6861,28 +7042,29 @@
     getRenderEngine().onActiveDisplaySizeChanged(activeDisplay->getSize());
 }
 
-void SurfaceFlinger::onActiveDisplayChangedLocked(const sp<DisplayDevice>& activeDisplay) {
+void SurfaceFlinger::onActiveDisplayChangedLocked(const sp<DisplayDevice>& inactiveDisplay,
+                                                  const sp<DisplayDevice>& activeDisplay) {
     ATRACE_CALL();
 
-    if (const auto display = getDisplayDeviceLocked(mActiveDisplayId)) {
-        display->getCompositionDisplay()->setLayerCachingTexturePoolEnabled(false);
+    if (inactiveDisplay) {
+        inactiveDisplay->getCompositionDisplay()->setLayerCachingTexturePoolEnabled(false);
     }
 
-    if (!activeDisplay) {
-        ALOGE("%s: activeDisplay is null", __func__);
-        return;
-    }
     mActiveDisplayId = activeDisplay->getPhysicalId();
     activeDisplay->getCompositionDisplay()->setLayerCachingTexturePoolEnabled(true);
+
     updateInternalDisplayVsyncLocked(activeDisplay);
     mScheduler->setModeChangePending(false);
-    mScheduler->setRefreshRateConfigs(activeDisplay->holdRefreshRateConfigs());
+    mScheduler->setLeaderDisplay(mActiveDisplayId);
+
     onActiveDisplaySizeChanged(activeDisplay);
     mActiveDisplayTransformHint = activeDisplay->getTransformHint();
 
-    // Update the kernel timer for the current active display, since the policy
-    // for this display might have changed when it was not the active display.
-    toggleKernelIdleTimer();
+    // The policy of the new active/leader 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.
+    applyRefreshRateSelectorPolicy(mActiveDisplayId, activeDisplay->refreshRateSelector());
 }
 
 status_t SurfaceFlinger::addWindowInfosListener(
@@ -6898,34 +7080,54 @@
 }
 
 std::shared_ptr<renderengine::ExternalTexture> SurfaceFlinger::getExternalTextureFromBufferData(
-        const BufferData& bufferData, const char* layerName) const {
-    bool cacheIdChanged = bufferData.flags.test(BufferData::BufferDataChange::cachedBufferChanged);
-    bool bufferSizeExceedsLimit = false;
-    std::shared_ptr<renderengine::ExternalTexture> buffer = nullptr;
-    if (cacheIdChanged && bufferData.buffer != nullptr) {
-        bufferSizeExceedsLimit = exceedsMaxRenderTargetSize(bufferData.buffer->getWidth(),
-                                                            bufferData.buffer->getHeight());
-        if (!bufferSizeExceedsLimit) {
-            ClientCache::getInstance().add(bufferData.cachedBuffer, bufferData.buffer);
-            buffer = ClientCache::getInstance().get(bufferData.cachedBuffer);
+        BufferData& bufferData, const char* layerName, uint64_t transactionId) {
+    if (bufferData.buffer &&
+        exceedsMaxRenderTargetSize(bufferData.buffer->getWidth(), bufferData.buffer->getHeight())) {
+        std::string errorMessage =
+                base::StringPrintf("Attempted to create an ExternalTexture with size (%u, %u) for "
+                                   "layer %s that exceeds render target size limit of %u.",
+                                   bufferData.buffer->getWidth(), bufferData.buffer->getHeight(),
+                                   layerName, static_cast<uint32_t>(mMaxRenderTargetSize));
+        ALOGD("%s", errorMessage.c_str());
+        if (bufferData.releaseBufferListener) {
+            bufferData.releaseBufferListener->onTransactionQueueStalled(
+                    String8(errorMessage.c_str()));
         }
-    } else if (cacheIdChanged) {
-        buffer = ClientCache::getInstance().get(bufferData.cachedBuffer);
-    } else if (bufferData.buffer != nullptr) {
-        bufferSizeExceedsLimit = exceedsMaxRenderTargetSize(bufferData.buffer->getWidth(),
-                                                            bufferData.buffer->getHeight());
-        if (!bufferSizeExceedsLimit) {
-            buffer = std::make_shared<
-                    renderengine::impl::ExternalTexture>(bufferData.buffer, getRenderEngine(),
-                                                         renderengine::impl::ExternalTexture::
-                                                                 Usage::READABLE);
-        }
+        return nullptr;
     }
-    ALOGE_IF(bufferSizeExceedsLimit,
-             "Attempted to create an ExternalTexture for layer %s that exceeds render target size "
-             "limit.",
-             layerName);
-    return buffer;
+
+    bool cachedBufferChanged =
+            bufferData.flags.test(BufferData::BufferDataChange::cachedBufferChanged);
+    if (cachedBufferChanged && bufferData.buffer) {
+        auto result = ClientCache::getInstance().add(bufferData.cachedBuffer, bufferData.buffer);
+        if (result.ok()) {
+            return result.value();
+        }
+
+        if (result.error() == ClientCache::AddError::CacheFull) {
+            ALOGE("Attempted to create an ExternalTexture for layer %s but CacheFull", layerName);
+
+            if (bufferData.releaseBufferListener) {
+                bufferData.releaseBufferListener->onTransactionQueueStalled(
+                        String8("Buffer processing hung due to full buffer cache"));
+            }
+        }
+
+        return nullptr;
+    }
+
+    if (cachedBufferChanged) {
+        return ClientCache::getInstance().get(bufferData.cachedBuffer);
+    }
+
+    if (bufferData.buffer) {
+        return std::make_shared<
+                renderengine::impl::ExternalTexture>(bufferData.buffer, getRenderEngine(),
+                                                     renderengine::impl::ExternalTexture::Usage::
+                                                             READABLE);
+    }
+
+    return nullptr;
 }
 
 bool SurfaceFlinger::commitMirrorDisplays(VsyncId vsyncId) {
@@ -6943,7 +7145,7 @@
     for (const auto& mirrorDisplay : mirrorDisplays) {
         // Set mirror layer's default layer stack to -1 so it doesn't end up rendered on a display
         // accidentally.
-        sp<Layer> rootMirrorLayer = Layer::fromHandle(mirrorDisplay.rootHandle).promote();
+        sp<Layer> rootMirrorLayer = LayerHandle::getLayer(mirrorDisplay.rootHandle);
         rootMirrorLayer->setLayerStack(ui::LayerStack::fromValue(-1));
         for (const auto& layer : mDrawingState.layersSortedByZ) {
             if (layer->getLayerStack() != mirrorDisplay.layerStack ||
@@ -7194,10 +7396,15 @@
             outMode.sfVsyncOffset = mode.sfVsyncOffset;
             outMode.presentationDeadline = mode.presentationDeadline;
             outMode.group = mode.group;
+            std::transform(mode.supportedHdrTypes.begin(), mode.supportedHdrTypes.end(),
+                           std::back_inserter(outMode.supportedHdrTypes),
+                           [](const ui::Hdr& value) { return static_cast<int32_t>(value); });
+
             outInfo->supportedDisplayModes.push_back(outMode);
         }
 
         outInfo->activeDisplayModeId = info.activeDisplayModeId;
+        outInfo->renderFrameRate = info.renderFrameRate;
 
         outInfo->supportedColorModes.clear();
         outInfo->supportedColorModes.reserve(info.supportedColorModes.size());
@@ -7274,6 +7481,14 @@
     return binderStatusFromStatusT(status);
 }
 
+binder::Status SurfaceComposerAIDL::getOverlaySupport(gui::OverlayProperties* outProperties) {
+    status_t status = checkAccessPermission();
+    if (status == OK) {
+        status = mFlinger->getOverlaySupport(outProperties);
+    }
+    return binderStatusFromStatusT(status);
+}
+
 binder::Status SurfaceComposerAIDL::getBootDisplayModeSupport(bool* outMode) {
     status_t status = checkAccessPermission();
     if (status == OK) {
@@ -7534,18 +7749,11 @@
     return binderStatusFromStatusT(status);
 }
 
-binder::Status SurfaceComposerAIDL::setDesiredDisplayModeSpecs(
-        const sp<IBinder>& displayToken, int32_t defaultMode, bool allowGroupSwitching,
-        float primaryRefreshRateMin, float primaryRefreshRateMax, float appRequestRefreshRateMin,
-        float appRequestRefreshRateMax) {
+binder::Status SurfaceComposerAIDL::setDesiredDisplayModeSpecs(const sp<IBinder>& displayToken,
+                                                               const gui::DisplayModeSpecs& specs) {
     status_t status = checkAccessPermission();
     if (status == OK) {
-        status = mFlinger->setDesiredDisplayModeSpecs(displayToken,
-                                                      static_cast<ui::DisplayModeId>(defaultMode),
-                                                      allowGroupSwitching, primaryRefreshRateMin,
-                                                      primaryRefreshRateMax,
-                                                      appRequestRefreshRateMin,
-                                                      appRequestRefreshRateMax);
+        status = mFlinger->setDesiredDisplayModeSpecs(displayToken, specs);
     }
     return binderStatusFromStatusT(status);
 }
@@ -7561,25 +7769,7 @@
         return binderStatusFromStatusT(status);
     }
 
-    ui::DisplayModeId displayModeId;
-    bool allowGroupSwitching;
-    float primaryRefreshRateMin;
-    float primaryRefreshRateMax;
-    float appRequestRefreshRateMin;
-    float appRequestRefreshRateMax;
-    status = mFlinger->getDesiredDisplayModeSpecs(displayToken, &displayModeId,
-                                                  &allowGroupSwitching, &primaryRefreshRateMin,
-                                                  &primaryRefreshRateMax, &appRequestRefreshRateMin,
-                                                  &appRequestRefreshRateMax);
-    if (status == NO_ERROR) {
-        outSpecs->defaultMode = displayModeId;
-        outSpecs->allowGroupSwitching = allowGroupSwitching;
-        outSpecs->primaryRefreshRateMin = primaryRefreshRateMin;
-        outSpecs->primaryRefreshRateMax = primaryRefreshRateMax;
-        outSpecs->appRequestRefreshRateMin = appRequestRefreshRateMin;
-        outSpecs->appRequestRefreshRateMax = appRequestRefreshRateMax;
-    }
-
+    status = mFlinger->getDesiredDisplayModeSpecs(displayToken, outSpecs);
     return binderStatusFromStatusT(status);
 }
 
@@ -7721,7 +7911,7 @@
     IPCThreadState* ipc = IPCThreadState::self();
     const int pid = ipc->getCallingPid();
     const int uid = ipc->getCallingUid();
-    if ((uid != AID_GRAPHICS) &&
+    if ((uid != AID_GRAPHICS) && (uid != AID_SYSTEM) &&
         !PermissionCache::checkPermission(sControlDisplayBrightness, pid, uid)) {
         ALOGE("Permission Denial: can't control brightness pid=%d, uid=%d", pid, uid);
         return PERMISSION_DENIED;
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index c4c5b56..6ddcfbc 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -29,6 +29,7 @@
 #include <cutils/atomic.h>
 #include <cutils/compiler.h>
 #include <ftl/future.h>
+#include <ftl/non_null.h>
 #include <gui/BufferQueue.h>
 #include <gui/CompositorTiming.h>
 #include <gui/FrameTimestamps.h>
@@ -65,8 +66,11 @@
 #include "DisplayIdGenerator.h"
 #include "Effects/Daltonizer.h"
 #include "FlagManager.h"
+#include "FrontEnd/DisplayInfo.h"
+#include "FrontEnd/LayerCreationArgs.h"
+#include "FrontEnd/TransactionHandler.h"
 #include "LayerVector.h"
-#include "Scheduler/RefreshRateConfigs.h"
+#include "Scheduler/RefreshRateSelector.h"
 #include "Scheduler/RefreshRateStats.h"
 #include "Scheduler/Scheduler.h"
 #include "Scheduler/VsyncModulator.h"
@@ -75,7 +79,6 @@
 #include "Tracing/LayerTracing.h"
 #include "Tracing/TransactionTracing.h"
 #include "TransactionCallbackInvoker.h"
-#include "TransactionHandler.h"
 #include "TransactionState.h"
 
 #include <atomic>
@@ -118,6 +121,7 @@
 class ScreenCapturer;
 class WindowInfosListenerInvoker;
 
+using frontend::TransactionHandler;
 using gui::CaptureArgs;
 using gui::DisplayCaptureArgs;
 using gui::IRegionSamplingListener;
@@ -271,6 +275,11 @@
     void removeHierarchyFromOffscreenLayers(Layer* layer);
     void removeFromOffscreenLayers(Layer* layer);
 
+    // Called when all clients have released all their references to
+    // this layer. The layer may still be kept alive by its parents but
+    // the client can no longer modify this layer directly.
+    void onHandleDestroyed(BBinder* handle, sp<Layer>& layer, uint32_t layerId);
+
     // TODO: Remove atomic if move dtor to main thread CL lands
     std::atomic<uint32_t> mNumClones;
 
@@ -278,12 +287,6 @@
         return mTransactionCallbackInvoker;
     }
 
-    // Converts from a binder handle to a Layer
-    // Returns nullptr if the handle does not point to an existing layer.
-    // Otherwise, returns a weak reference so that callers off the main-thread
-    // won't accidentally hold onto the last strong reference.
-    wp<Layer> fromHandle(const sp<IBinder>& handle) const;
-
     // If set, disables reusing client composition buffers. This can be set by
     // debug.sf.disable_client_composition_cache
     bool mDisableClientCompositionCache = false;
@@ -312,7 +315,7 @@
             REQUIRES(mStateLock);
 
     virtual std::shared_ptr<renderengine::ExternalTexture> getExternalTextureFromBufferData(
-            const BufferData& bufferData, const char* layerName) const;
+            BufferData& bufferData, const char* layerName, uint64_t transactionId);
 
     // Returns true if any display matches a `bool(const DisplayDevice&)` predicate.
     template <typename Predicate>
@@ -429,10 +432,6 @@
                 mCounterByLayerHandle GUARDED_BY(mLock);
     };
 
-    using ActiveModeInfo = DisplayDevice::ActiveModeInfo;
-    using KernelIdleTimerController =
-            ::android::scheduler::RefreshRateConfigs::KernelIdleTimerController;
-
     enum class BootStage {
         BOOTLOADER,
         BOOTANIMATION,
@@ -467,8 +466,7 @@
               typename Handler = VsyncModulator::VsyncConfigOpt (VsyncModulator::*)(Args...)>
     void modulateVsync(Handler handler, Args... args) {
         if (const auto config = (*mVsyncModulator.*handler)(args...)) {
-            const auto vsyncPeriod = mScheduler->getVsyncPeriodFromRefreshRateConfigs();
-            setVsyncConfig(*config, vsyncPeriod);
+            setVsyncConfig(*config, mScheduler->getLeaderVsyncPeriod());
         }
     }
 
@@ -493,9 +491,8 @@
 
     sp<IBinder> getPhysicalDisplayToken(PhysicalDisplayId displayId) const;
     status_t setTransactionState(const FrameTimelineInfo& frameTimelineInfo,
-                                 const Vector<ComposerState>& state,
-                                 const Vector<DisplayState>& displays, uint32_t flags,
-                                 const sp<IBinder>& applyToken,
+                                 Vector<ComposerState>& state, const Vector<DisplayState>& displays,
+                                 uint32_t flags, const sp<IBinder>& applyToken,
                                  const InputWindowCommands& inputWindowCommands,
                                  int64_t desiredPresentTime, bool isAutoTimestamp,
                                  const client_cache_t& uncacheBuffer, bool hasListenerCallbacks,
@@ -523,6 +520,7 @@
     status_t setActiveColorMode(const sp<IBinder>& displayToken, ui::ColorMode colorMode);
     status_t getBootDisplayModeSupport(bool* outSupport) const;
     status_t setBootDisplayMode(const sp<display::DisplayToken>&, DisplayModeId);
+    status_t getOverlaySupport(gui::OverlayProperties* outProperties) const;
     status_t clearBootDisplayMode(const sp<IBinder>& displayToken);
     void setAutoLowLatencyMode(const sp<IBinder>& displayToken, bool on);
     void setGameContentType(const sp<IBinder>& displayToken, bool on);
@@ -553,17 +551,8 @@
     status_t addTunnelModeEnabledListener(const sp<gui::ITunnelModeEnabledListener>& listener);
     status_t removeTunnelModeEnabledListener(const sp<gui::ITunnelModeEnabledListener>& listener);
     status_t setDesiredDisplayModeSpecs(const sp<IBinder>& displayToken,
-                                        ui::DisplayModeId displayModeId, bool allowGroupSwitching,
-                                        float primaryRefreshRateMin, float primaryRefreshRateMax,
-                                        float appRequestRefreshRateMin,
-                                        float appRequestRefreshRateMax);
-    status_t getDesiredDisplayModeSpecs(const sp<IBinder>& displayToken,
-                                        ui::DisplayModeId* outDefaultMode,
-                                        bool* outAllowGroupSwitching,
-                                        float* outPrimaryRefreshRateMin,
-                                        float* outPrimaryRefreshRateMax,
-                                        float* outAppRequestRefreshRateMin,
-                                        float* outAppRequestRefreshRateMax);
+                                        const gui::DisplayModeSpecs&);
+    status_t getDesiredDisplayModeSpecs(const sp<IBinder>& displayToken, gui::DisplayModeSpecs*);
     status_t getDisplayBrightnessSupport(const sp<IBinder>& displayToken, bool* outSupport) const;
     status_t setDisplayBrightness(const sp<IBinder>& displayToken,
                                   const gui::DisplayBrightness& brightness);
@@ -626,16 +615,17 @@
     // ISchedulerCallback overrides:
 
     // Toggles hardware VSYNC by calling into HWC.
+    // TODO(b/241286146): Rename for self-explanatory API.
     void setVsyncEnabled(bool) override;
-    // Sets the desired display mode per display if allowed by policy .
-    void requestDisplayModes(std::vector<scheduler::DisplayModeConfig>) override;
-    // Called when kernel idle timer has expired. Used to update the refresh rate overlay.
+    void requestDisplayModes(std::vector<display::DisplayModeRequest>) override;
     void kernelTimerChanged(bool expired) override;
-    // Called when the frame rate override list changed to trigger an event.
     void triggerOnFrameRateOverridesChanged() override;
 
     // Toggles the kernel idle timer on or off depending the policy decisions around refresh rates.
     void toggleKernelIdleTimer() REQUIRES(mStateLock);
+
+    using KernelIdleTimerController = scheduler::RefreshRateSelector::KernelIdleTimerController;
+
     // Get the controller and timeout that will help decide how the kernel idle timer will be
     // configured and what value to use as the timeout.
     std::pair<std::optional<KernelIdleTimerController>, std::chrono::milliseconds>
@@ -649,9 +639,11 @@
     bool mKernelIdleTimerEnabled = false;
     // Show spinner with refresh rate overlay
     bool mRefreshRateOverlaySpinner = false;
+    // Show render rate with refresh rate overlay
+    bool mRefreshRateOverlayRenderRate = false;
 
-    // Sets the desired active mode bit. It obtains the lock, and sets mDesiredActiveMode.
-    void setDesiredActiveMode(const ActiveModeInfo& info) REQUIRES(mStateLock);
+    void setDesiredActiveMode(display::DisplayModeRequest&&) REQUIRES(mStateLock);
+
     status_t setActiveModeFromBackdoor(const sp<display::DisplayToken>&, DisplayModeId);
     // Sets the active mode and a new refresh rate in SF.
     void updateInternalStateWithChangedMode() REQUIRES(mStateLock, kMainThreadContext);
@@ -670,14 +662,18 @@
 
     // Returns the preferred mode for PhysicalDisplayId if the Scheduler has selected one for that
     // display. Falls back to the display's defaultModeId otherwise.
-    std::optional<DisplayModePtr> getPreferredDisplayMode(PhysicalDisplayId,
-                                                          DisplayModeId defaultModeId) const
-            REQUIRES(mStateLock);
+    ftl::Optional<scheduler::FrameRateMode> getPreferredDisplayMode(
+            PhysicalDisplayId, DisplayModeId defaultModeId) const REQUIRES(mStateLock);
 
-    status_t setDesiredDisplayModeSpecsInternal(const sp<DisplayDevice>&,
-                                                const scheduler::RefreshRateConfigs::PolicyVariant&)
+    status_t setDesiredDisplayModeSpecsInternal(
+            const sp<DisplayDevice>&, const scheduler::RefreshRateSelector::PolicyVariant&)
             EXCLUDES(mStateLock) REQUIRES(kMainThreadContext);
 
+    // TODO(b/241285191): Look up RefreshRateSelector on Scheduler to remove redundant parameter.
+    status_t applyRefreshRateSelectorPolicy(PhysicalDisplayId,
+                                            const scheduler::RefreshRateSelector&)
+            REQUIRES(mStateLock, kMainThreadContext);
+
     void commitTransactions() EXCLUDES(mStateLock) REQUIRES(kMainThreadContext);
     void commitTransactionsLocked(uint32_t transactionFlags)
             REQUIRES(mStateLock, kMainThreadContext);
@@ -696,7 +692,7 @@
     void commitInputWindowCommands() REQUIRES(mStateLock);
     void updateCursorAsync();
 
-    void initScheduler(const sp<const DisplayDevice>&) REQUIRES(mStateLock);
+    void initScheduler(const sp<const DisplayDevice>&) REQUIRES(kMainThreadContext, mStateLock);
     void updatePhaseConfiguration(const Fps&) REQUIRES(mStateLock);
     void setVsyncConfig(const VsyncModulator::VsyncConfig&, nsecs_t vsyncPeriod);
 
@@ -704,7 +700,8 @@
     /*
      * Transactions
      */
-    bool applyTransactionState(const FrameTimelineInfo& info, Vector<ComposerState>& state,
+    bool applyTransactionState(const FrameTimelineInfo& info,
+                               std::vector<ResolvedComposerState>& state,
                                const Vector<DisplayState>& displays, uint32_t flags,
                                const InputWindowCommands& inputWindowCommands,
                                const int64_t desiredPresentTime, bool isAutoTimestamp,
@@ -725,9 +722,10 @@
             const TransactionHandler::TransactionFlushState& flushState)
             REQUIRES(kMainThreadContext);
 
-    uint32_t setClientStateLocked(const FrameTimelineInfo&, ComposerState&,
+    uint32_t setClientStateLocked(const FrameTimelineInfo&, ResolvedComposerState&,
                                   int64_t desiredPresentTime, bool isAutoTimestamp,
-                                  int64_t postTime, uint32_t permissions) REQUIRES(mStateLock);
+                                  int64_t postTime, uint32_t permissions, uint64_t transactionId)
+            REQUIRES(mStateLock);
 
     uint32_t getTransactionFlags() const;
 
@@ -754,8 +752,7 @@
     /*
      * Layer management
      */
-    status_t createLayer(LayerCreationArgs& args, const sp<IBinder>& parentHandle,
-                         gui::CreateSurfaceResult& outResult);
+    status_t createLayer(LayerCreationArgs& args, gui::CreateSurfaceResult& outResult);
 
     status_t createBufferStateLayer(LayerCreationArgs& args, sp<IBinder>* outHandle,
                                     sp<Layer>* outLayer);
@@ -769,15 +766,11 @@
     status_t mirrorDisplay(DisplayId displayId, const LayerCreationArgs& args,
                            gui::CreateSurfaceResult& outResult);
 
-    // called when all clients have released all their references to
-    // this layer meaning it is entirely safe to destroy all
-    // resources associated to this layer.
-    void onHandleDestroyed(BBinder* handle, sp<Layer>& layer);
     void markLayerPendingRemovalLocked(const sp<Layer>& layer);
 
     // add a layer to SurfaceFlinger
-    status_t addClientLayer(const sp<Client>& client, const sp<IBinder>& handle,
-                            const sp<Layer>& lbc, const wp<Layer>& parentLayer, bool addToRoot,
+    status_t addClientLayer(const LayerCreationArgs& args, const sp<IBinder>& handle,
+                            const sp<Layer>& layer, const wp<Layer>& parentLayer,
                             uint32_t* outTransformHint);
 
     // Traverse through all the layers and compute and cache its bounds.
@@ -795,9 +788,10 @@
             const std::shared_ptr<renderengine::ExternalTexture>&, bool regionSampling,
             bool grayscale, const sp<IScreenCaptureListener>&);
     ftl::SharedFuture<FenceResult> renderScreenImpl(
-            const RenderArea&, TraverseLayersFunction,
+            std::unique_ptr<RenderArea>, TraverseLayersFunction,
             const std::shared_ptr<renderengine::ExternalTexture>&, bool canCaptureBlackoutContent,
-            bool regionSampling, bool grayscale, ScreenCaptureResults&) EXCLUDES(mStateLock);
+            bool regionSampling, bool grayscale, ScreenCaptureResults&) EXCLUDES(mStateLock)
+            REQUIRES(kMainThreadContext);
 
     // If the uid provided is not UNSET_UID, the traverse will skip any layers that don't have a
     // matching ownerUid
@@ -942,7 +936,8 @@
             const sp<compositionengine::DisplaySurface>& displaySurface,
             const sp<IGraphicBufferProducer>& producer) REQUIRES(mStateLock);
     void processDisplayChangesLocked() REQUIRES(mStateLock, kMainThreadContext);
-    void processDisplayRemoved(const wp<IBinder>& displayToken) REQUIRES(mStateLock);
+    void processDisplayRemoved(const wp<IBinder>& displayToken)
+            REQUIRES(mStateLock, kMainThreadContext);
     void processDisplayChanged(const wp<IBinder>& displayToken,
                                const DisplayDeviceState& currentState,
                                const DisplayDeviceState& drawingState)
@@ -1007,7 +1002,10 @@
     VirtualDisplayId acquireVirtualDisplay(ui::Size, ui::PixelFormat) REQUIRES(mStateLock);
     void releaseVirtualDisplay(VirtualDisplayId);
 
-    void onActiveDisplayChangedLocked(const sp<DisplayDevice>& activeDisplay)
+    // TODO(b/255635821): Replace pointers with references. `inactiveDisplay` is only ever `nullptr`
+    // in tests, and `activeDisplay` must not be `nullptr` as a precondition.
+    void onActiveDisplayChangedLocked(const sp<DisplayDevice>& inactiveDisplay,
+                                      const sp<DisplayDevice>& activeDisplay)
             REQUIRES(mStateLock, kMainThreadContext);
 
     void onActiveDisplaySizeChanged(const sp<const DisplayDevice>&);
@@ -1027,7 +1025,9 @@
     void dumpFrameTimeline(const DumpArgs& args, std::string& result) const;
     void logFrameStats(TimePoint now) REQUIRES(kMainThreadContext);
 
-    void dumpVSync(std::string& result) const REQUIRES(mStateLock);
+    void dumpScheduler(std::string& result) const REQUIRES(mStateLock);
+    void dumpEvents(std::string& result) const REQUIRES(mStateLock);
+    void dumpVsync(std::string& result) const REQUIRES(mStateLock);
 
     void dumpCompositionDisplays(std::string& result) const REQUIRES(mStateLock);
     void dumpDisplays(std::string& result) const REQUIRES(mStateLock);
@@ -1114,7 +1114,6 @@
 
     // constant members (no synchronization needed for access)
     const nsecs_t mBootTime = systemTime();
-    bool mGpuToCpuSupported = false;
     bool mIsUserBuild = true;
 
     // Can only accessed from the main thread, these members
@@ -1253,6 +1252,7 @@
     ui::Dataspace mColorSpaceAgnosticDataspace;
     float mDimmingRatio = -1.f;
 
+    std::unique_ptr<renderengine::RenderEngine> mRenderEngine;
     std::unique_ptr<compositionengine::CompositionEngine> mCompositionEngine;
     // mMaxRenderTargetSize is only set once in init() so it doesn't need to be protected by
     // any mutex.
@@ -1297,8 +1297,8 @@
     sp<TunnelModeEnabledReporter> mTunnelModeEnabledReporter;
     ui::DisplayPrimaries mInternalDisplayPrimaries;
 
-    const float mInternalDisplayDensity;
     const float mEmulatedDisplayDensity;
+    const float mInternalDisplayDensity;
 
     // Should only be accessed by the main thread.
     sp<os::IInputFlinger> mInputFlinger;
@@ -1378,6 +1378,7 @@
     } mPowerHintSessionMode;
 
     TransactionHandler mTransactionHandler;
+    display::DisplayMap<ui::LayerStack, frontend::DisplayInfo> mFrontEndDisplayInfos;
 };
 
 class SurfaceComposerAIDL : public gui::BnSurfaceComposer {
@@ -1410,6 +1411,7 @@
     binder::Status setBootDisplayMode(const sp<IBinder>& display, int displayModeId) override;
     binder::Status clearBootDisplayMode(const sp<IBinder>& display) override;
     binder::Status getBootDisplayModeSupport(bool* outMode) override;
+    binder::Status getOverlaySupport(gui::OverlayProperties* outProperties) override;
     binder::Status setAutoLowLatencyMode(const sp<IBinder>& display, bool on) override;
     binder::Status setGameContentType(const sp<IBinder>& display, bool on) override;
     binder::Status captureDisplay(const DisplayCaptureArgs&,
@@ -1454,11 +1456,8 @@
             const sp<gui::ITunnelModeEnabledListener>& listener) override;
     binder::Status removeTunnelModeEnabledListener(
             const sp<gui::ITunnelModeEnabledListener>& listener) override;
-    binder::Status setDesiredDisplayModeSpecs(const sp<IBinder>& displayToken, int32_t defaultMode,
-                                              bool allowGroupSwitching, float primaryRefreshRateMin,
-                                              float primaryRefreshRateMax,
-                                              float appRequestRefreshRateMin,
-                                              float appRequestRefreshRateMax) override;
+    binder::Status setDesiredDisplayModeSpecs(const sp<IBinder>& displayToken,
+                                              const gui::DisplayModeSpecs&) override;
     binder::Status getDesiredDisplayModeSpecs(const sp<IBinder>& displayToken,
                                               gui::DisplayModeSpecs* outSpecs) override;
     binder::Status getDisplayBrightnessSupport(const sp<IBinder>& displayToken,
diff --git a/services/surfaceflinger/SurfaceFlingerDefaultFactory.cpp b/services/surfaceflinger/SurfaceFlingerDefaultFactory.cpp
index 2f1f263..7e6894d 100644
--- a/services/surfaceflinger/SurfaceFlingerDefaultFactory.cpp
+++ b/services/surfaceflinger/SurfaceFlingerDefaultFactory.cpp
@@ -91,6 +91,10 @@
     return sp<Layer>::make(args);
 }
 
+sp<LayerFE> DefaultFactory::createLayerFE(const std::string& layerName) {
+    return sp<LayerFE>::make(layerName);
+}
+
 std::unique_ptr<FrameTracer> DefaultFactory::createFrameTracer() {
     return std::make_unique<FrameTracer>();
 }
diff --git a/services/surfaceflinger/SurfaceFlingerDefaultFactory.h b/services/surfaceflinger/SurfaceFlingerDefaultFactory.h
index 447a02f..2c6de0e 100644
--- a/services/surfaceflinger/SurfaceFlingerDefaultFactory.h
+++ b/services/surfaceflinger/SurfaceFlingerDefaultFactory.h
@@ -42,6 +42,7 @@
     std::unique_ptr<compositionengine::CompositionEngine> createCompositionEngine() override;
     sp<Layer> createBufferStateLayer(const LayerCreationArgs& args) override;
     sp<Layer> createEffectLayer(const LayerCreationArgs& args) override;
+    sp<LayerFE> createLayerFE(const std::string& layerName) override;
     std::unique_ptr<FrameTracer> createFrameTracer() override;
     std::unique_ptr<frametimeline::FrameTimeline> createFrameTimeline(
             std::shared_ptr<TimeStats> timeStats, pid_t surfaceFlingerPid) override;
diff --git a/services/surfaceflinger/SurfaceFlingerFactory.h b/services/surfaceflinger/SurfaceFlingerFactory.h
index 9c4d5c8..f310c4a 100644
--- a/services/surfaceflinger/SurfaceFlingerFactory.h
+++ b/services/surfaceflinger/SurfaceFlingerFactory.h
@@ -38,12 +38,12 @@
 class IGraphicBufferConsumer;
 class IGraphicBufferProducer;
 class Layer;
+class LayerFE;
 class StartPropertySetThread;
 class SurfaceFlinger;
 class TimeStats;
 
 struct DisplayDeviceCreationArgs;
-struct LayerCreationArgs;
 
 namespace compositionengine {
 class CompositionEngine;
@@ -52,7 +52,6 @@
 namespace scheduler {
 class VsyncConfiguration;
 class VsyncController;
-class RefreshRateConfigs;
 } // namespace scheduler
 
 namespace frametimeline {
@@ -61,6 +60,7 @@
 
 namespace surfaceflinger {
 
+struct LayerCreationArgs;
 class NativeWindowSurface;
 
 // The interface that SurfaceFlinger uses to create all of the implementations
@@ -88,6 +88,7 @@
 
     virtual sp<Layer> createBufferStateLayer(const LayerCreationArgs& args) = 0;
     virtual sp<Layer> createEffectLayer(const LayerCreationArgs& args) = 0;
+    virtual sp<LayerFE> createLayerFE(const std::string& layerName) = 0;
     virtual std::unique_ptr<FrameTracer> createFrameTracer() = 0;
     virtual std::unique_ptr<frametimeline::FrameTimeline> createFrameTimeline(
             std::shared_ptr<TimeStats> timeStats, pid_t surfaceFlingerPid) = 0;
diff --git a/services/surfaceflinger/SurfaceFlingerProperties.cpp b/services/surfaceflinger/SurfaceFlingerProperties.cpp
index 20fa091..5b73030 100644
--- a/services/surfaceflinger/SurfaceFlingerProperties.cpp
+++ b/services/surfaceflinger/SurfaceFlingerProperties.cpp
@@ -367,6 +367,14 @@
     return SurfaceFlingerProperties::enable_frame_rate_override().value_or(defaultValue);
 }
 
+bool frame_rate_override_for_native_rates(bool defaultValue) {
+    return SurfaceFlingerProperties::frame_rate_override_for_native_rates().value_or(defaultValue);
+}
+
+bool frame_rate_override_global(bool defaultValue) {
+    return SurfaceFlingerProperties::frame_rate_override_global().value_or(defaultValue);
+}
+
 bool enable_layer_caching(bool defaultValue) {
     return SurfaceFlingerProperties::enable_layer_caching().value_or(defaultValue);
 }
diff --git a/services/surfaceflinger/SurfaceFlingerProperties.h b/services/surfaceflinger/SurfaceFlingerProperties.h
index 080feee..09629cf 100644
--- a/services/surfaceflinger/SurfaceFlingerProperties.h
+++ b/services/surfaceflinger/SurfaceFlingerProperties.h
@@ -96,6 +96,10 @@
 
 bool enable_frame_rate_override(bool defaultValue);
 
+bool frame_rate_override_for_native_rates(bool defaultValue);
+
+bool frame_rate_override_global(bool defaultValue);
+
 bool enable_layer_caching(bool defaultValue);
 
 bool enable_sdr_dimming(bool defaultValue);
diff --git a/services/surfaceflinger/TimeStats/TimeStats.cpp b/services/surfaceflinger/TimeStats/TimeStats.cpp
index e5a9dd4..e860d88 100644
--- a/services/surfaceflinger/TimeStats/TimeStats.cpp
+++ b/services/surfaceflinger/TimeStats/TimeStats.cpp
@@ -68,6 +68,8 @@
             return SurfaceflingerStatsLayerInfo::GAME_MODE_PERFORMANCE;
         case GameMode::Battery:
             return SurfaceflingerStatsLayerInfo::GAME_MODE_BATTERY;
+        case GameMode::Custom:
+            return SurfaceflingerStatsLayerInfo::GAME_MODE_CUSTOM;
         default:
             return SurfaceflingerStatsLayerInfo::GAME_MODE_UNSPECIFIED;
     }
diff --git a/services/surfaceflinger/TimeStats/timestatsatomsproto/timestats_atoms.proto b/services/surfaceflinger/TimeStats/timestatsatomsproto/timestats_atoms.proto
index e45757d..d4d444e 100644
--- a/services/surfaceflinger/TimeStats/timestatsatomsproto/timestats_atoms.proto
+++ b/services/surfaceflinger/TimeStats/timestatsatomsproto/timestats_atoms.proto
@@ -173,6 +173,7 @@
          GAME_MODE_STANDARD = 2;
          GAME_MODE_PERFORMANCE = 3;
          GAME_MODE_BATTERY = 4;
+         GAME_MODE_CUSTOM = 5;
     }
 
     // Game mode that the layer was running at. Used to track user engagement
diff --git a/services/surfaceflinger/Tracing/LayerTracing.h b/services/surfaceflinger/Tracing/LayerTracing.h
index e73dac6..b32001c 100644
--- a/services/surfaceflinger/Tracing/LayerTracing.h
+++ b/services/surfaceflinger/Tracing/LayerTracing.h
@@ -55,6 +55,7 @@
         TRACE_EXTRA = 1 << 3,
         TRACE_HWC = 1 << 4,
         TRACE_BUFFERS = 1 << 5,
+        TRACE_VIRTUAL_DISPLAYS = 1 << 6,
         TRACE_ALL = TRACE_INPUT | TRACE_COMPOSITION | TRACE_EXTRA,
     };
     void setTraceFlags(uint32_t flags);
diff --git a/services/surfaceflinger/Tracing/TransactionProtoParser.cpp b/services/surfaceflinger/Tracing/TransactionProtoParser.cpp
index 267f3d0..2f46487 100644
--- a/services/surfaceflinger/Tracing/TransactionProtoParser.cpp
+++ b/services/surfaceflinger/Tracing/TransactionProtoParser.cpp
@@ -111,7 +111,7 @@
     }
 
     if (layer.what & layer_state_t::eAlphaChanged) {
-        proto.set_alpha(layer.alpha);
+        proto.set_alpha(layer.color.a);
     }
 
     if (layer.what & layer_state_t::eColorChanged) {
@@ -123,8 +123,8 @@
     if (layer.what & layer_state_t::eTransparentRegionChanged) {
         LayerProtoHelper::writeToProto(layer.transparentRegion, proto.mutable_transparent_region());
     }
-    if (layer.what & layer_state_t::eTransformChanged) {
-        proto.set_transform(layer.transform);
+    if (layer.what & layer_state_t::eBufferTransformChanged) {
+        proto.set_transform(layer.bufferTransform);
     }
     if (layer.what & layer_state_t::eTransformToDisplayInverseChanged) {
         proto.set_transform_to_display_inverse(layer.transformToDisplayInverse);
@@ -310,10 +310,10 @@
     int32_t layerCount = proto.layer_changes_size();
     t.states.reserve(static_cast<size_t>(layerCount));
     for (int i = 0; i < layerCount; i++) {
-        ComposerState s;
+        ResolvedComposerState s;
         s.state.what = 0;
         fromProto(proto.layer_changes(i), s.state);
-        t.states.add(s);
+        t.states.emplace_back(s);
     }
 
     int32_t displayCount = proto.display_changes_size();
@@ -395,7 +395,7 @@
     }
 
     if (proto.what() & layer_state_t::eAlphaChanged) {
-        layer.alpha = proto.alpha();
+        layer.color.a = proto.alpha();
     }
 
     if (proto.what() & layer_state_t::eColorChanged) {
@@ -407,8 +407,8 @@
     if (proto.what() & layer_state_t::eTransparentRegionChanged) {
         LayerProtoHelper::readFromProto(proto.transparent_region(), layer.transparentRegion);
     }
-    if (proto.what() & layer_state_t::eTransformChanged) {
-        layer.transform = proto.transform();
+    if (proto.what() & layer_state_t::eBufferTransformChanged) {
+        layer.bufferTransform = proto.transform();
     }
     if (proto.what() & layer_state_t::eTransformToDisplayInverseChanged) {
         layer.transformToDisplayInverse = proto.transform_to_display_inverse();
diff --git a/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp b/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp
index d0364f2..f1a6c0e 100644
--- a/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp
+++ b/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp
@@ -82,6 +82,8 @@
 
     sp<Layer> createEffectLayer(const LayerCreationArgs& args) { return sp<Layer>::make(args); }
 
+    sp<LayerFE> createLayerFE(const std::string& layerName) { return sp<LayerFE>::make(layerName); }
+
     std::unique_ptr<FrameTracer> createFrameTracer() override {
         return std::make_unique<testing::NiceMock<mock::FrameTracer>>();
     }
@@ -98,7 +100,8 @@
     MockSurfaceFlinger(Factory& factory)
           : SurfaceFlinger(factory, SurfaceFlinger::SkipInitialization) {}
     std::shared_ptr<renderengine::ExternalTexture> getExternalTextureFromBufferData(
-            const BufferData& bufferData, const char* /* layerName */) const override {
+            BufferData& bufferData, const char* /* layerName */,
+            uint64_t /* transactionId */) override {
         return std::make_shared<renderengine::mock::FakeExternalTexture>(bufferData.getWidth(),
                                                                          bufferData.getHeight(),
                                                                          bufferData.getId(),
@@ -211,8 +214,8 @@
 
             gui::CreateSurfaceResult outResult;
             LayerCreationArgs args(mFlinger.flinger(), nullptr /* client */, tracingArgs.name,
-                                   tracingArgs.flags, LayerMetadata());
-            args.sequence = std::make_optional<int32_t>(tracingArgs.layerId);
+                                   tracingArgs.flags, LayerMetadata(),
+                                   std::make_optional<int32_t>(tracingArgs.layerId));
 
             if (tracingArgs.mirrorFromId == -1) {
                 sp<IBinder> parentHandle = nullptr;
@@ -237,13 +240,7 @@
         for (int j = 0; j < entry.transactions_size(); j++) {
             // apply transactions
             TransactionState transaction = parser.fromProto(entry.transactions(j));
-            mFlinger.setTransactionState(transaction.frameTimelineInfo, transaction.states,
-                                         transaction.displays, transaction.flags,
-                                         transaction.applyToken, transaction.inputWindowCommands,
-                                         transaction.desiredPresentTime,
-                                         transaction.isAutoTimestamp, {},
-                                         transaction.hasListenerCallbacks,
-                                         transaction.listenerCallbacks, transaction.id);
+            mFlinger.setTransactionStateInternal(transaction);
         }
 
         const auto frameTime = TimePoint::fromNs(entry.elapsed_realtime_nanos());
diff --git a/services/surfaceflinger/Tracing/tools/main.cpp b/services/surfaceflinger/Tracing/tools/main.cpp
index f3cf42d..9f9ae48 100644
--- a/services/surfaceflinger/Tracing/tools/main.cpp
+++ b/services/surfaceflinger/Tracing/tools/main.cpp
@@ -52,6 +52,10 @@
     ;
     ALOGD("Generating %s...", outputLayersTracePath);
     std::cout << "Generating " << outputLayersTracePath << "\n";
+
+    // sink any log spam from the stubbed surfaceflinger
+    __android_log_set_logger([](const struct __android_log_message* /* log_message */) {});
+
     if (!LayerTraceGenerator().generate(transactionTraceFile, outputLayersTracePath)) {
         std::cout << "Error: Failed to generate layers trace " << outputLayersTracePath;
         return -1;
diff --git a/services/surfaceflinger/TransactionCallbackInvoker.h b/services/surfaceflinger/TransactionCallbackInvoker.h
index 23ea7a5..61ff9bc 100644
--- a/services/surfaceflinger/TransactionCallbackInvoker.h
+++ b/services/surfaceflinger/TransactionCallbackInvoker.h
@@ -19,6 +19,7 @@
 #include <condition_variable>
 #include <deque>
 #include <mutex>
+#include <optional>
 #include <queue>
 #include <thread>
 #include <unordered_map>
@@ -48,7 +49,7 @@
     std::vector<ftl::SharedFuture<FenceResult>> previousReleaseFences;
     std::variant<nsecs_t, sp<Fence>> acquireTimeOrFence = -1;
     nsecs_t latchTime = -1;
-    uint32_t transformHint = 0;
+    std::optional<uint32_t> transformHint = std::nullopt;
     uint32_t currentMaxAcquiredBufferCount = 0;
     std::shared_ptr<FenceTime> gpuCompositionDoneFence{FenceTime::NO_FENCE};
     CompositorTiming compositorTiming;
diff --git a/services/surfaceflinger/TransactionState.h b/services/surfaceflinger/TransactionState.h
index 3cbfe81..7bde2c1 100644
--- a/services/surfaceflinger/TransactionState.h
+++ b/services/surfaceflinger/TransactionState.h
@@ -20,17 +20,27 @@
 #include <memory>
 #include <mutex>
 #include <vector>
+#include "renderengine/ExternalTexture.h"
 
 #include <gui/LayerState.h>
 #include <system/window.h>
 
 namespace android {
 
+// Extends the client side composer state by resolving buffer cache ids.
+class ResolvedComposerState : public ComposerState {
+public:
+    ResolvedComposerState() = default;
+    ResolvedComposerState(ComposerState&& source) { state = std::move(source.state); }
+    std::shared_ptr<renderengine::ExternalTexture> externalTexture;
+    int hwcBufferSlot = 0;
+};
+
 struct TransactionState {
     TransactionState() = default;
 
     TransactionState(const FrameTimelineInfo& frameTimelineInfo,
-                     const Vector<ComposerState>& composerStates,
+                     std::vector<ResolvedComposerState>& composerStates,
                      const Vector<DisplayState>& displayStates, uint32_t transactionFlags,
                      const sp<IBinder>& applyToken, const InputWindowCommands& inputWindowCommands,
                      int64_t desiredPresentTime, bool isAutoTimestamp,
@@ -38,7 +48,7 @@
                      bool hasListenerCallbacks, std::vector<ListenerCallbacks> listenerCallbacks,
                      int originPid, int originUid, uint64_t transactionId)
           : frameTimelineInfo(frameTimelineInfo),
-            states(composerStates),
+            states(std::move(composerStates)),
             displays(displayStates),
             flags(transactionFlags),
             applyToken(applyToken),
@@ -57,18 +67,20 @@
     // Invokes `void(const layer_state_t&)` visitor for matching layers.
     template <typename Visitor>
     void traverseStatesWithBuffers(Visitor&& visitor) const {
-        for (const auto& [state] : states) {
-            if (state.hasBufferChanges() && state.hasValidBuffer() && state.surface) {
-                visitor(state);
+        for (const auto& state : states) {
+            if (state.state.hasBufferChanges() && state.state.hasValidBuffer() &&
+                state.state.surface) {
+                visitor(state.state);
             }
         }
     }
 
     template <typename Visitor>
     void traverseStatesWithBuffersWhileTrue(Visitor&& visitor) const {
-        for (const auto& [state] : states) {
-            if (state.hasBufferChanges() && state.hasValidBuffer() && state.surface) {
-                if (!visitor(state)) return;
+        for (const auto& state : states) {
+            if (state.state.hasBufferChanges() && state.state.hasValidBuffer() &&
+                state.state.surface) {
+                if (!visitor(state.state)) return;
             }
         }
     }
@@ -79,8 +91,8 @@
     bool isFrameActive() const {
         if (!displays.empty()) return true;
 
-        for (const auto& [state] : states) {
-            if (state.frameRateCompatibility != ANATIVEWINDOW_FRAME_RATE_NO_VOTE) {
+        for (const auto& state : states) {
+            if (state.state.frameRateCompatibility != ANATIVEWINDOW_FRAME_RATE_NO_VOTE) {
                 return true;
             }
         }
@@ -89,7 +101,7 @@
     }
 
     FrameTimelineInfo frameTimelineInfo;
-    Vector<ComposerState> states;
+    std::vector<ResolvedComposerState> states;
     Vector<DisplayState> displays;
     uint32_t flags;
     sp<IBinder> applyToken;
diff --git a/services/surfaceflinger/Utils/Dumper.h b/services/surfaceflinger/Utils/Dumper.h
index 3761f9e..ee94217 100644
--- a/services/surfaceflinger/Utils/Dumper.h
+++ b/services/surfaceflinger/Utils/Dumper.h
@@ -16,10 +16,11 @@
 
 #pragma once
 
-#include <optional>
 #include <string>
 #include <string_view>
 
+#include <ftl/optional.h>
+
 namespace android::utils {
 
 // Dumps variables by appending their name and value to the output string. A variable is formatted
@@ -44,16 +45,48 @@
         eol();
     }
 
+    void dump(std::string_view name, const std::string& value) {
+        dump(name, static_cast<const std::string_view&>(value));
+    }
+
     void dump(std::string_view name, bool value) {
         using namespace std::string_view_literals;
         dump(name, value ? "true"sv : "false"sv);
     }
 
     template <typename T>
-    void dump(std::string_view name, const std::optional<T>& value) {
-        using namespace std::string_view_literals;
+    void dump(std::string_view name, const std::optional<T>& opt) {
+        if (opt) {
+            dump(name, *opt);
+        } else {
+            using namespace std::string_view_literals;
+            dump(name, "nullopt"sv);
+        }
+    }
+
+    template <typename T>
+    void dump(std::string_view name, const ftl::Optional<T>& opt) {
+        dump(name, static_cast<const std::optional<T>&>(opt));
+    }
+
+    template <typename T, typename... Ts>
+    void dump(std::string_view name, const T& value, const Ts&... rest) {
+        std::string string;
+
+        constexpr bool kIsTuple = sizeof...(Ts) > 0;
+        if constexpr (kIsTuple) {
+            string += '{';
+        }
+
         using std::to_string;
-        dump(name, value ? to_string(*value) : "nullopt"sv);
+        string += to_string(value);
+
+        if constexpr (kIsTuple) {
+            string += ((", " + to_string(rest)) + ...);
+            string += '}';
+        }
+
+        dump(name, string);
     }
 
     struct Indent {
@@ -63,6 +96,21 @@
         Dumper& dumper;
     };
 
+    struct Section {
+        Section(Dumper& dumper, std::string_view heading) : dumper(dumper) {
+            dumper.dump({}, heading);
+            indent.emplace(dumper);
+        }
+
+        ~Section() {
+            indent.reset();
+            dumper.eol();
+        }
+
+        Dumper& dumper;
+        std::optional<Indent> indent;
+    };
+
 private:
     std::string& mOut;
     int mIndent = 0;
diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_displayhardware_fuzzer.cpp b/services/surfaceflinger/fuzzer/surfaceflinger_displayhardware_fuzzer.cpp
index f8fc6f5..8a6af10 100644
--- a/services/surfaceflinger/fuzzer/surfaceflinger_displayhardware_fuzzer.cpp
+++ b/services/surfaceflinger/fuzzer/surfaceflinger_displayhardware_fuzzer.cpp
@@ -326,8 +326,8 @@
     invokeComposerHal2_3(&composer, display, outLayer);
     invokeComposerHal2_4(&composer, display, outLayer);
 
-    composer.executeCommands();
-    composer.resetCommands();
+    composer.executeCommands(display);
+    composer.resetCommands(display);
 
     composer.destroyLayer(display, outLayer);
     composer.destroyVirtualDisplay(display);
diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzer.cpp b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzer.cpp
index 22d80ca..ce4d18f 100644
--- a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzer.cpp
+++ b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzer.cpp
@@ -150,7 +150,7 @@
 
     sp<IBinder> handle = defaultServiceManager()->checkService(
             String16(mFdp.ConsumeRandomLengthString().c_str()));
-    mFlinger->fromHandle(handle);
+    LayerHandle::getLayer(handle);
     mFlinger->disableExpensiveRendering();
 }
 
@@ -245,6 +245,7 @@
     setDisplayStateLocked();
 
     setTransactionState();
+    mTestableFlinger.flushTransactionQueues();
 
     onTransact(data, size);
 }
diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h
index ed2fe23..c0a6bdb 100644
--- a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h
+++ b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h
@@ -34,11 +34,12 @@
 #include "DisplayHardware/ComposerHal.h"
 #include "FrameTimeline/FrameTimeline.h"
 #include "FrameTracer/FrameTracer.h"
+#include "FrontEnd/LayerHandle.h"
 #include "Layer.h"
 #include "NativeWindowSurface.h"
 #include "Scheduler/EventThread.h"
 #include "Scheduler/MessageQueue.h"
-#include "Scheduler/RefreshRateConfigs.h"
+#include "Scheduler/RefreshRateSelector.h"
 #include "Scheduler/VSyncTracker.h"
 #include "Scheduler/VsyncConfiguration.h"
 #include "Scheduler/VsyncController.h"
@@ -80,7 +81,6 @@
 using types::V1_1::RenderIntent;
 using types::V1_2::ColorMode;
 using types::V1_2::Dataspace;
-using types::V1_2::Hdr;
 using types::V1_2::PixelFormat;
 
 using V2_1::Config;
@@ -217,18 +217,21 @@
 
 class TestableScheduler : public Scheduler, private ICompositor {
 public:
-    TestableScheduler(const std::shared_ptr<scheduler::RefreshRateConfigs> &refreshRateConfigs,
-                      ISchedulerCallback &callback)
+    TestableScheduler(const std::shared_ptr<scheduler::RefreshRateSelector>& selectorPtr,
+                      ISchedulerCallback& callback)
           : TestableScheduler(std::make_unique<android::mock::VsyncController>(),
-                              std::make_unique<android::mock::VSyncTracker>(), refreshRateConfigs,
+                              std::make_unique<android::mock::VSyncTracker>(), selectorPtr,
                               callback) {}
 
     TestableScheduler(std::unique_ptr<VsyncController> controller,
                       std::unique_ptr<VSyncTracker> tracker,
-                      std::shared_ptr<RefreshRateConfigs> configs, ISchedulerCallback &callback)
+                      std::shared_ptr<RefreshRateSelector> selectorPtr,
+                      ISchedulerCallback& callback)
           : Scheduler(*this, callback, Feature::kContentDetection) {
         mVsyncSchedule.emplace(VsyncSchedule(std::move(tracker), nullptr, std::move(controller)));
-        setRefreshRateConfigs(std::move(configs));
+
+        const auto displayId = selectorPtr->getActiveMode().modePtr->getPhysicalDisplayId();
+        registerDisplay(displayId, std::move(selectorPtr));
     }
 
     ConnectionHandle createConnection(std::unique_ptr<EventThread> eventThread) {
@@ -240,7 +243,7 @@
 
     auto &mutableLayerHistory() { return mLayerHistory; }
 
-    auto refreshRateConfigs() { return holdRefreshRateConfigs(); }
+    auto refreshRateSelector() { return leaderSelectorPtr(); }
 
     void replaceTouchTimer(int64_t millis) {
         if (mTouchTimer) {
@@ -268,7 +271,7 @@
         mPolicy.cachedModeChangedParams.reset();
     }
 
-    void onNonPrimaryDisplayModeChanged(ConnectionHandle handle, DisplayModePtr mode) {
+    void onNonPrimaryDisplayModeChanged(ConnectionHandle handle, const FrameRateMode &mode) {
         return Scheduler::onNonPrimaryDisplayModeChanged(handle, mode);
     }
 
@@ -309,8 +312,8 @@
     }
 
     std::unique_ptr<scheduler::Scheduler> createScheduler(
-            const std::shared_ptr<scheduler::RefreshRateConfigs> &,
-            scheduler::ISchedulerCallback &) {
+            const std::shared_ptr<scheduler::RefreshRateSelector>&,
+            scheduler::ISchedulerCallback&) {
         return nullptr;
     }
 
@@ -354,6 +357,10 @@
         return sp<Layer>::make(args);
     }
 
+    sp<LayerFE> createLayerFE(const std::string &layerName) override {
+        return sp<LayerFE>::make(layerName);
+    }
+
     std::unique_ptr<FrameTracer> createFrameTracer() override {
         return std::make_unique<android::mock::FrameTracer>();
     }
@@ -499,16 +506,8 @@
     }
 
     void getDesiredDisplayModeSpecs(sp<IBinder> &display) {
-        ui::DisplayModeId outDefaultMode;
-        bool outAllowGroupSwitching;
-        float outPrimaryRefreshRateMin;
-        float outPrimaryRefreshRateMax;
-        float outAppRequestRefreshRateMin;
-        float outAppRequestRefreshRateMax;
-        mFlinger->getDesiredDisplayModeSpecs(display, &outDefaultMode, &outAllowGroupSwitching,
-                                             &outPrimaryRefreshRateMin, &outPrimaryRefreshRateMax,
-                                             &outAppRequestRefreshRateMin,
-                                             &outAppRequestRefreshRateMax);
+        gui::DisplayModeSpecs _;
+        mFlinger->getDesiredDisplayModeSpecs(display, &_);
     }
 
     void setVsyncConfig(FuzzedDataProvider *fdp) {
@@ -628,7 +627,8 @@
     }
 
     void setupRenderEngine(std::unique_ptr<renderengine::RenderEngine> renderEngine) {
-        mFlinger->mCompositionEngine->setRenderEngine(std::move(renderEngine));
+        mFlinger->mRenderEngine = std::move(renderEngine);
+        mFlinger->mCompositionEngine->setRenderEngine(mFlinger->mRenderEngine.get());
     }
 
     void setupComposer(std::unique_ptr<Hwc2::Composer> composer) {
@@ -655,9 +655,8 @@
             modes.try_emplace(kModeId90, mock::createDisplayMode(kModeId90, 90_Hz));
         }
 
-        mRefreshRateConfigs = std::make_shared<scheduler::RefreshRateConfigs>(modes, kModeId60);
-        const auto fps =
-                FTL_FAKE_GUARD(kMainThreadContext, mRefreshRateConfigs->getActiveMode().getFps());
+        mRefreshRateSelector = std::make_shared<scheduler::RefreshRateSelector>(modes, kModeId60);
+        const auto fps = mRefreshRateSelector->getActiveMode().modePtr->getFps();
         mFlinger->mVsyncConfiguration = mFactory.createVsyncConfiguration(fps);
         mFlinger->mVsyncModulator = sp<scheduler::VsyncModulator>::make(
                 mFlinger->mVsyncConfiguration->getCurrentConfigs());
@@ -666,7 +665,7 @@
                                                               hal::PowerMode::OFF);
 
         mScheduler = new scheduler::TestableScheduler(std::move(vsyncController),
-                                                      std::move(vsyncTracker), mRefreshRateConfigs,
+                                                      std::move(vsyncTracker), mRefreshRateSelector,
                                                       *(callback ?: this));
 
         mFlinger->mAppConnectionHandle = mScheduler->createConnection(std::move(appEventThread));
@@ -731,18 +730,25 @@
         return mFlinger->mTransactionHandler.mPendingTransactionQueues;
     }
 
-    auto setTransactionState(
-            const FrameTimelineInfo &frameTimelineInfo, const Vector<ComposerState> &states,
-            const Vector<DisplayState> &displays, uint32_t flags, const sp<IBinder> &applyToken,
-            const InputWindowCommands &inputWindowCommands, int64_t desiredPresentTime,
-            bool isAutoTimestamp, const client_cache_t &uncacheBuffer, bool hasListenerCallbacks,
-            std::vector<ListenerCallbacks> &listenerCallbacks, uint64_t transactionId) {
+    auto setTransactionState(const FrameTimelineInfo &frameTimelineInfo,
+                             Vector<ComposerState> &states, const Vector<DisplayState> &displays,
+                             uint32_t flags, const sp<IBinder> &applyToken,
+                             const InputWindowCommands &inputWindowCommands,
+                             int64_t desiredPresentTime, bool isAutoTimestamp,
+                             const client_cache_t &uncacheBuffer, bool hasListenerCallbacks,
+                             std::vector<ListenerCallbacks> &listenerCallbacks,
+                             uint64_t transactionId) {
         return mFlinger->setTransactionState(frameTimelineInfo, states, displays, flags, applyToken,
                                              inputWindowCommands, desiredPresentTime,
                                              isAutoTimestamp, uncacheBuffer, hasListenerCallbacks,
                                              listenerCallbacks, transactionId);
     }
 
+    auto flushTransactionQueues() {
+        ftl::FakeGuard guard(kMainThreadContext);
+        return mFlinger->flushTransactionQueues(VsyncId{0});
+    }
+
     auto onTransact(uint32_t code, const Parcel &data, Parcel *reply, uint32_t flags) {
         return mFlinger->onTransact(code, data, reply, flags);
     }
@@ -762,7 +768,7 @@
     auto& mutableDisplays() { return mFlinger->mDisplays; }
     auto& mutableDrawingState() { return mFlinger->mDrawingState; }
 
-    auto fromHandle(const sp<IBinder> &handle) { return mFlinger->fromHandle(handle); }
+    auto fromHandle(const sp<IBinder> &handle) { return LayerHandle::getLayer(handle); }
 
     ~TestableSurfaceFlinger() {
         mutableDisplays().clear();
@@ -770,13 +776,13 @@
         mutableDrawingState().displays.clear();
         mFlinger->mScheduler.reset();
         mFlinger->mCompositionEngine->setHwComposer(std::unique_ptr<HWComposer>());
-        mFlinger->mCompositionEngine->setRenderEngine(
-                std::unique_ptr<renderengine::RenderEngine>());
+        mFlinger->mRenderEngine = std::unique_ptr<renderengine::RenderEngine>();
+        mFlinger->mCompositionEngine->setRenderEngine(mFlinger->mRenderEngine.get());
     }
 
 private:
     void setVsyncEnabled(bool) override {}
-    void requestDisplayModes(std::vector<scheduler::DisplayModeConfig>) override {}
+    void requestDisplayModes(std::vector<display::DisplayModeRequest>) override {}
     void kernelTimerChanged(bool) override {}
     void triggerOnFrameRateOverridesChanged() override {}
 
@@ -784,7 +790,7 @@
     sp<SurfaceFlinger> mFlinger =
             sp<SurfaceFlinger>::make(mFactory, SurfaceFlinger::SkipInitialization);
     scheduler::TestableScheduler *mScheduler = nullptr;
-    std::shared_ptr<scheduler::RefreshRateConfigs> mRefreshRateConfigs;
+    std::shared_ptr<scheduler::RefreshRateSelector> mRefreshRateSelector;
 };
 
 } // namespace android
diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_layer_fuzzer.cpp b/services/surfaceflinger/fuzzer/surfaceflinger_layer_fuzzer.cpp
index 0a142c3..c5b3fa6 100644
--- a/services/surfaceflinger/fuzzer/surfaceflinger_layer_fuzzer.cpp
+++ b/services/surfaceflinger/fuzzer/surfaceflinger_layer_fuzzer.cpp
@@ -146,8 +146,7 @@
     layer->computeSourceBounds(getFuzzedFloatRect(&mFdp));
 
     layer->fenceHasSignaled();
-    layer->onPreComposition(mFdp.ConsumeIntegral<int64_t>(),
-                            false /*updatingOutputGeometryThisFrame*/);
+    layer->onPreComposition(mFdp.ConsumeIntegral<int64_t>());
     const std::vector<sp<CallbackHandle>> callbacks;
     layer->setTransactionCompletedListeners(callbacks);
 
@@ -161,7 +160,7 @@
     layer->setBuffer(texture, {} /*bufferData*/, mFdp.ConsumeIntegral<nsecs_t>() /*postTime*/,
                      mFdp.ConsumeIntegral<nsecs_t>() /*desiredTime*/,
                      mFdp.ConsumeBool() /*isAutoTimestamp*/,
-                     {mFdp.ConsumeIntegral<nsecs_t>()} /*dequeue*/, {} /*info*/);
+                     {mFdp.ConsumeIntegral<nsecs_t>()} /*dequeue*/, {} /*info*/, 0 /* hwcslot */);
 
     LayerRenderArea layerArea(*(flinger.flinger()), layer, getFuzzedRect(),
                               {mFdp.ConsumeIntegral<int32_t>(),
diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp
index 2614288..7959e52 100644
--- a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp
+++ b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp
@@ -23,6 +23,7 @@
 
 #include "Scheduler/DispSyncSource.h"
 #include "Scheduler/OneShotTimer.h"
+#include "Scheduler/RefreshRateSelector.h"
 #include "Scheduler/VSyncDispatchTimerQueue.h"
 #include "Scheduler/VSyncPredictor.h"
 #include "Scheduler/VSyncReactor.h"
@@ -38,7 +39,7 @@
                                      (72_Hz).getPeriodNsecs(), (90_Hz).getPeriodNsecs(),
                                      (120_Hz).getPeriodNsecs()};
 
-constexpr auto kLayerVoteTypes = ftl::enum_range<scheduler::RefreshRateConfigs::LayerVoteType>();
+constexpr auto kLayerVoteTypes = ftl::enum_range<scheduler::RefreshRateSelector::LayerVoteType>();
 
 constexpr PowerMode kPowerModes[] = {PowerMode::ON, PowerMode::DOZE, PowerMode::OFF,
                                      PowerMode::DOZE_SUSPEND, PowerMode::ON_SUSPEND};
@@ -59,7 +60,7 @@
 
 private:
     void fuzzRefreshRateSelection();
-    void fuzzRefreshRateConfigs();
+    void fuzzRefreshRateSelector();
     void fuzzPresentLatencyTracker();
     void fuzzVSyncModulator();
     void fuzzVSyncPredictor();
@@ -238,8 +239,8 @@
         time1 += mFdp.PickValueInArray(kVsyncPeriods);
         time2 += mFdp.PickValueInArray(kVsyncPeriods);
     }
-    historyV1.summarize(*scheduler->refreshRateConfigs(), time1);
-    historyV1.summarize(*scheduler->refreshRateConfigs(), time2);
+    historyV1.summarize(*scheduler->refreshRateSelector(), time1);
+    historyV1.summarize(*scheduler->refreshRateSelector(), time2);
 
     scheduler->createConnection(std::make_unique<android::mock::EventThread>());
 
@@ -248,7 +249,9 @@
     scheduler->setDuration(handle, (std::chrono::nanoseconds)mFdp.ConsumeIntegral<uint64_t>(),
                            (std::chrono::nanoseconds)mFdp.ConsumeIntegral<uint64_t>());
 
-    dump<scheduler::TestableScheduler>(scheduler, &mFdp);
+    std::string result = mFdp.ConsumeRandomLengthString(kRandomStringLength);
+    utils::Dumper dumper(result);
+    scheduler->dump(dumper);
 }
 
 void SchedulerFuzzer::fuzzVSyncReactor() {
@@ -327,9 +330,9 @@
     layer->setFrameRateSelectionPriority(mFdp.ConsumeIntegral<int16_t>());
 }
 
-void SchedulerFuzzer::fuzzRefreshRateConfigs() {
-    using RefreshRateConfigs = scheduler::RefreshRateConfigs;
-    using LayerRequirement = RefreshRateConfigs::LayerRequirement;
+void SchedulerFuzzer::fuzzRefreshRateSelector() {
+    using RefreshRateSelector = scheduler::RefreshRateSelector;
+    using LayerRequirement = RefreshRateSelector::LayerRequirement;
     using RefreshRateStats = scheduler::RefreshRateStats;
 
     const uint16_t minRefreshRate = mFdp.ConsumeIntegralInRange<uint16_t>(1, UINT16_MAX >> 1);
@@ -345,48 +348,49 @@
                                                          Fps::fromValue(static_cast<float>(fps))));
     }
 
-    RefreshRateConfigs refreshRateConfigs(displayModes, modeId);
+    RefreshRateSelector refreshRateSelector(displayModes, modeId);
 
-    const RefreshRateConfigs::GlobalSignals globalSignals = {.touch = false, .idle = false};
+    const RefreshRateSelector::GlobalSignals globalSignals = {.touch = false, .idle = false};
     std::vector<LayerRequirement> layers = {{.weight = mFdp.ConsumeFloatingPoint<float>()}};
 
-    refreshRateConfigs.getRankedRefreshRates(layers, globalSignals);
+    refreshRateSelector.getRankedFrameRates(layers, globalSignals);
 
     layers[0].name = mFdp.ConsumeRandomLengthString(kRandomStringLength);
     layers[0].ownerUid = mFdp.ConsumeIntegral<uint16_t>();
     layers[0].desiredRefreshRate = Fps::fromValue(mFdp.ConsumeFloatingPoint<float>());
     layers[0].vote = mFdp.PickValueInArray(kLayerVoteTypes.values);
     auto frameRateOverrides =
-            refreshRateConfigs.getFrameRateOverrides(layers,
-                                                     Fps::fromValue(
-                                                             mFdp.ConsumeFloatingPoint<float>()),
-                                                     globalSignals);
+            refreshRateSelector.getFrameRateOverrides(layers,
+                                                      Fps::fromValue(
+                                                              mFdp.ConsumeFloatingPoint<float>()),
+                                                      globalSignals);
 
     {
         ftl::FakeGuard guard(kMainThreadContext);
 
-        refreshRateConfigs.setPolicy(
-                RefreshRateConfigs::
+        refreshRateSelector.setPolicy(
+                RefreshRateSelector::
                         DisplayManagerPolicy{modeId,
                                              {Fps::fromValue(mFdp.ConsumeFloatingPoint<float>()),
                                               Fps::fromValue(mFdp.ConsumeFloatingPoint<float>())}});
-        refreshRateConfigs.setPolicy(
-                RefreshRateConfigs::OverridePolicy{modeId,
-                                                   {Fps::fromValue(
-                                                            mFdp.ConsumeFloatingPoint<float>()),
-                                                    Fps::fromValue(
-                                                            mFdp.ConsumeFloatingPoint<float>())}});
-        refreshRateConfigs.setPolicy(RefreshRateConfigs::NoOverridePolicy{});
+        refreshRateSelector.setPolicy(
+                RefreshRateSelector::OverridePolicy{modeId,
+                                                    {Fps::fromValue(
+                                                             mFdp.ConsumeFloatingPoint<float>()),
+                                                     Fps::fromValue(
+                                                             mFdp.ConsumeFloatingPoint<float>())}});
+        refreshRateSelector.setPolicy(RefreshRateSelector::NoOverridePolicy{});
 
-        refreshRateConfigs.setActiveModeId(modeId);
+        refreshRateSelector.setActiveMode(modeId,
+                                          Fps::fromValue(mFdp.ConsumeFloatingPoint<float>()));
     }
 
-    RefreshRateConfigs::isFractionalPairOrMultiple(Fps::fromValue(
-                                                           mFdp.ConsumeFloatingPoint<float>()),
-                                                   Fps::fromValue(
-                                                           mFdp.ConsumeFloatingPoint<float>()));
-    RefreshRateConfigs::getFrameRateDivisor(Fps::fromValue(mFdp.ConsumeFloatingPoint<float>()),
-                                            Fps::fromValue(mFdp.ConsumeFloatingPoint<float>()));
+    RefreshRateSelector::isFractionalPairOrMultiple(Fps::fromValue(
+                                                            mFdp.ConsumeFloatingPoint<float>()),
+                                                    Fps::fromValue(
+                                                            mFdp.ConsumeFloatingPoint<float>()));
+    RefreshRateSelector::getFrameRateDivisor(Fps::fromValue(mFdp.ConsumeFloatingPoint<float>()),
+                                             Fps::fromValue(mFdp.ConsumeFloatingPoint<float>()));
 
     android::mock::TimeStats timeStats;
     RefreshRateStats refreshRateStats(timeStats, Fps::fromValue(mFdp.ConsumeFloatingPoint<float>()),
@@ -407,7 +411,7 @@
 
 void SchedulerFuzzer::process() {
     fuzzRefreshRateSelection();
-    fuzzRefreshRateConfigs();
+    fuzzRefreshRateSelector();
     fuzzPresentLatencyTracker();
     fuzzVSyncModulator();
     fuzzVSyncPredictor();
diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.h b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.h
index 1a49ead..2bc5b46 100644
--- a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.h
+++ b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.h
@@ -27,7 +27,6 @@
 #include "Clock.h"
 #include "Layer.h"
 #include "Scheduler/EventThread.h"
-#include "Scheduler/RefreshRateConfigs.h"
 #include "Scheduler/Scheduler.h"
 #include "Scheduler/VSyncTracker.h"
 #include "Scheduler/VsyncModulator.h"
@@ -117,6 +116,8 @@
         return true;
     }
 
+    void setDivisor(unsigned) override {}
+
     nsecs_t nextVSyncTime(nsecs_t timePoint) const {
         if (timePoint % mPeriod == 0) {
             return timePoint;
diff --git a/services/surfaceflinger/layerproto/transactions.proto b/services/surfaceflinger/layerproto/transactions.proto
index b687abc..4c6a9cf 100644
--- a/services/surfaceflinger/layerproto/transactions.proto
+++ b/services/surfaceflinger/layerproto/transactions.proto
@@ -98,8 +98,7 @@
         eReparent = 0x00008000;
 
         eColorChanged = 0x00010000;
-        eDestroySurface = 0x00020000;
-        eTransformChanged = 0x00040000;
+        eBufferTransformChanged = 0x00040000;
         eTransformToDisplayInverseChanged = 0x00080000;
 
         eCropChanged = 0x00100000;
diff --git a/services/surfaceflinger/sysprop/SurfaceFlingerProperties.sysprop b/services/surfaceflinger/sysprop/SurfaceFlingerProperties.sysprop
index bcbe21a..8540c3d 100644
--- a/services/surfaceflinger/sysprop/SurfaceFlingerProperties.sysprop
+++ b/services/surfaceflinger/sysprop/SurfaceFlingerProperties.sysprop
@@ -445,6 +445,28 @@
     prop_name: "ro.surface_flinger.enable_frame_rate_override"
 }
 
+# Limits the frame rate override feature (enable_frame_rate_override) to override the refresh rate
+# to native display refresh rates only. Before introducing this flag, native display refresh rates
+# was the default behaviour. With this flag we can control which behaviour we want explicitly.
+# This flag is introduced as a fail-safe mechanism and planned to be defaulted to false.
+prop {
+    api_name: "frame_rate_override_for_native_rates"
+    type: Boolean
+    scope: Public
+    access: Readonly
+    prop_name: "ro.surface_flinger.frame_rate_override_for_native_rates"
+}
+
+# Enables the frame rate override feature (enable_frame_rate_override) to
+# override the frame rate globally instead of only for individual apps.
+prop {
+    api_name: "frame_rate_override_global"
+    type: Boolean
+    scope: Public
+    access: Readonly
+    prop_name: "ro.surface_flinger.frame_rate_override_global"
+}
+
 # Enables Layer Caching
 prop {
     api_name: "enable_layer_caching"
diff --git a/services/surfaceflinger/sysprop/api/SurfaceFlingerProperties-current.txt b/services/surfaceflinger/sysprop/api/SurfaceFlingerProperties-current.txt
index 348a462..9338133 100644
--- a/services/surfaceflinger/sysprop/api/SurfaceFlingerProperties-current.txt
+++ b/services/surfaceflinger/sysprop/api/SurfaceFlingerProperties-current.txt
@@ -61,6 +61,14 @@
     prop_name: "ro.surface_flinger.force_hwc_copy_for_virtual_displays"
   }
   prop {
+    api_name: "frame_rate_override_for_native_rates"
+    prop_name: "ro.surface_flinger.frame_rate_override_for_native_rates"
+  }
+  prop {
+    api_name: "frame_rate_override_global"
+    prop_name: "ro.surface_flinger.frame_rate_override_global"
+  }
+  prop {
     api_name: "has_HDR_display"
     prop_name: "ro.surface_flinger.has_HDR_display"
   }
diff --git a/services/surfaceflinger/tests/Credentials_test.cpp b/services/surfaceflinger/tests/Credentials_test.cpp
index 4f04934..1676844 100644
--- a/services/surfaceflinger/tests/Credentials_test.cpp
+++ b/services/surfaceflinger/tests/Credentials_test.cpp
@@ -207,23 +207,12 @@
 
 TEST_F(CredentialsTest, SetDesiredDisplayConfigsTest) {
     const auto display = getFirstDisplayToken();
-    ui::DisplayModeId defaultMode;
-    bool allowGroupSwitching;
-    float primaryFpsMin;
-    float primaryFpsMax;
-    float appRequestFpsMin;
-    float appRequestFpsMax;
-    status_t res =
-            SurfaceComposerClient::getDesiredDisplayModeSpecs(display, &defaultMode,
-                                                              &allowGroupSwitching, &primaryFpsMin,
-                                                              &primaryFpsMax, &appRequestFpsMin,
-                                                              &appRequestFpsMax);
+    gui::DisplayModeSpecs specs;
+    status_t res = SurfaceComposerClient::getDesiredDisplayModeSpecs(display, &specs);
     ASSERT_EQ(res, NO_ERROR);
+    gui::DisplayModeSpecs setSpecs;
     std::function<status_t()> condition = [=]() {
-        return SurfaceComposerClient::setDesiredDisplayModeSpecs(display, defaultMode,
-                                                                 allowGroupSwitching, primaryFpsMin,
-                                                                 primaryFpsMax, appRequestFpsMin,
-                                                                 appRequestFpsMax);
+        return SurfaceComposerClient::setDesiredDisplayModeSpecs(display, specs);
     };
     ASSERT_NO_FATAL_FAILURE(checkWithPrivileges<status_t>(condition, NO_ERROR, PERMISSION_DENIED));
 }
diff --git a/services/surfaceflinger/tests/DisplayConfigs_test.cpp b/services/surfaceflinger/tests/DisplayConfigs_test.cpp
index 02c934e..10dae46 100644
--- a/services/surfaceflinger/tests/DisplayConfigs_test.cpp
+++ b/services/surfaceflinger/tests/DisplayConfigs_test.cpp
@@ -39,37 +39,19 @@
  */
 class RefreshRateRangeTest : public ::testing::Test {
 private:
-    ui::DisplayModeId initialDefaultMode;
-    bool initialAllowGroupSwitching;
-    float initialPrimaryMin;
-    float initialPrimaryMax;
-    float initialAppRequestMin;
-    float initialAppRequestMax;
+    gui::DisplayModeSpecs mSpecs;
 
 protected:
     void SetUp() override {
         const auto ids = SurfaceComposerClient::getPhysicalDisplayIds();
         ASSERT_FALSE(ids.empty());
         mDisplayToken = SurfaceComposerClient::getPhysicalDisplayToken(ids.front());
-        status_t res =
-                SurfaceComposerClient::getDesiredDisplayModeSpecs(mDisplayToken,
-                                                                  &initialDefaultMode,
-                                                                  &initialAllowGroupSwitching,
-                                                                  &initialPrimaryMin,
-                                                                  &initialPrimaryMax,
-                                                                  &initialAppRequestMin,
-                                                                  &initialAppRequestMax);
+        status_t res = SurfaceComposerClient::getDesiredDisplayModeSpecs(mDisplayToken, &mSpecs);
         ASSERT_EQ(res, NO_ERROR);
     }
 
     void TearDown() override {
-        status_t res =
-                SurfaceComposerClient::setDesiredDisplayModeSpecs(mDisplayToken, initialDefaultMode,
-                                                                  initialAllowGroupSwitching,
-                                                                  initialPrimaryMin,
-                                                                  initialPrimaryMax,
-                                                                  initialAppRequestMin,
-                                                                  initialAppRequestMax);
+        status_t res = SurfaceComposerClient::setDesiredDisplayModeSpecs(mDisplayToken, mSpecs);
         ASSERT_EQ(res, NO_ERROR);
     }
 
@@ -85,61 +67,39 @@
     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++) {
-        res = SurfaceComposerClient::setDesiredDisplayModeSpecs(mDisplayToken, modes[i].id, false,
-                                                                modes[i].refreshRate,
-                                                                modes[i].refreshRate,
-                                                                modes[i].refreshRate,
-                                                                modes[i].refreshRate);
+        setSpecs.defaultMode = modes[i].id;
+        setSpecs.primaryRanges.physical.min = modes[i].refreshRate;
+        setSpecs.primaryRanges.physical.max = modes[i].refreshRate;
+        setSpecs.primaryRanges.render = setSpecs.primaryRanges.physical;
+        setSpecs.appRequestRanges = setSpecs.primaryRanges;
+        res = SurfaceComposerClient::setDesiredDisplayModeSpecs(mDisplayToken, setSpecs);
         ASSERT_EQ(res, NO_ERROR);
 
-        ui::DisplayModeId defaultConfig;
-        bool allowGroupSwitching;
-        float primaryRefreshRateMin;
-        float primaryRefreshRateMax;
-        float appRequestRefreshRateMin;
-        float appRequestRefreshRateMax;
-        res = SurfaceComposerClient::getDesiredDisplayModeSpecs(mDisplayToken, &defaultConfig,
-                                                                &allowGroupSwitching,
-                                                                &primaryRefreshRateMin,
-                                                                &primaryRefreshRateMax,
-                                                                &appRequestRefreshRateMin,
-                                                                &appRequestRefreshRateMax);
+        gui::DisplayModeSpecs getSpecs;
+        res = SurfaceComposerClient::getDesiredDisplayModeSpecs(mDisplayToken, &getSpecs);
         ASSERT_EQ(res, NO_ERROR);
-        ASSERT_EQ(defaultConfig, i);
-        ASSERT_EQ(allowGroupSwitching, false);
-        ASSERT_EQ(primaryRefreshRateMin, modes[i].refreshRate);
-        ASSERT_EQ(primaryRefreshRateMax, modes[i].refreshRate);
-        ASSERT_EQ(appRequestRefreshRateMin, modes[i].refreshRate);
-        ASSERT_EQ(appRequestRefreshRateMax, modes[i].refreshRate);
+        ASSERT_EQ(setSpecs, getSpecs);
     }
 }
 
 void RefreshRateRangeTest::testSetAllowGroupSwitching(bool allowGroupSwitching) {
-    status_t res =
-            SurfaceComposerClient::setDesiredDisplayModeSpecs(mDisplayToken, 0, allowGroupSwitching,
-                                                              0.f, 90.f, 0.f, 90.f);
-    ASSERT_EQ(res, NO_ERROR);
-    ui::DisplayModeId defaultConfig;
-    bool newAllowGroupSwitching;
-    float primaryRefreshRateMin;
-    float primaryRefreshRateMax;
-    float appRequestRefreshRateMin;
-    float appRequestRefreshRateMax;
+    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;
 
-    res = SurfaceComposerClient::getDesiredDisplayModeSpecs(mDisplayToken, &defaultConfig,
-                                                            &newAllowGroupSwitching,
-                                                            &primaryRefreshRateMin,
-                                                            &primaryRefreshRateMax,
-                                                            &appRequestRefreshRateMin,
-                                                            &appRequestRefreshRateMax);
+    status_t res = SurfaceComposerClient::setDesiredDisplayModeSpecs(mDisplayToken, setSpecs);
     ASSERT_EQ(res, NO_ERROR);
-    ASSERT_EQ(defaultConfig, 0);
-    ASSERT_EQ(newAllowGroupSwitching, allowGroupSwitching);
-    ASSERT_EQ(primaryRefreshRateMin, 0.f);
-    ASSERT_EQ(primaryRefreshRateMax, 90.f);
-    ASSERT_EQ(appRequestRefreshRateMin, 0.f);
-    ASSERT_EQ(appRequestRefreshRateMax, 90.f);
+    gui::DisplayModeSpecs getSpecs;
+    res = SurfaceComposerClient::getDesiredDisplayModeSpecs(mDisplayToken, &getSpecs);
+    ASSERT_EQ(res, NO_ERROR);
+    ASSERT_EQ(setSpecs, getSpecs);
 }
 
 TEST_F(RefreshRateRangeTest, setAllowGroupSwitching) {
diff --git a/services/surfaceflinger/tests/unittests/Android.bp b/services/surfaceflinger/tests/unittests/Android.bp
index d2b5813..8b0cd78 100644
--- a/services/surfaceflinger/tests/unittests/Android.bp
+++ b/services/surfaceflinger/tests/unittests/Android.bp
@@ -85,6 +85,7 @@
         "FpsTest.cpp",
         "FramebufferSurfaceTest.cpp",
         "FrameRateOverrideMappingsTest.cpp",
+        "FrameRateSelectionPriorityTest.cpp",
         "FrameTimelineTest.cpp",
         "GameModeTest.cpp",
         "HWComposerTest.cpp",
@@ -92,6 +93,7 @@
         "LayerHistoryTest.cpp",
         "LayerInfoTest.cpp",
         "LayerMetadataTest.cpp",
+        "LayerLifecycleManagerTest.cpp",
         "LayerTest.cpp",
         "LayerTestUtils.cpp",
         "MessageQueueTest.cpp",
@@ -109,10 +111,10 @@
         "SurfaceFlinger_SetPowerModeInternalTest.cpp",
         "SurfaceFlinger_SetupNewDisplayDeviceInternalTest.cpp",
         "SurfaceFlinger_UpdateLayerMetadataSnapshotTest.cpp",
+        "SurfaceFlinger_ExcludeDolbyVisionTest.cpp",
         "SchedulerTest.cpp",
         "SetFrameRateTest.cpp",
-        "RefreshRateConfigsTest.cpp",
-        "RefreshRateSelectionTest.cpp",
+        "RefreshRateSelectorTest.cpp",
         "RefreshRateStatsTest.cpp",
         "RegionSamplingTest.cpp",
         "TimeStatsTest.cpp",
@@ -150,7 +152,7 @@
         "android.hardware.power@1.1",
         "android.hardware.power@1.2",
         "android.hardware.power@1.3",
-        "android.hardware.power-V2-cpp",
+        "android.hardware.power-V4-cpp",
         "libaidlcommonsupport",
         "libcompositionengine_mocks",
         "libcompositionengine",
diff --git a/services/surfaceflinger/tests/unittests/CompositionTest.cpp b/services/surfaceflinger/tests/unittests/CompositionTest.cpp
index 7148c11..06b9caa 100644
--- a/services/surfaceflinger/tests/unittests/CompositionTest.cpp
+++ b/services/surfaceflinger/tests/unittests/CompositionTest.cpp
@@ -244,8 +244,8 @@
                                                                       HAL_PIXEL_FORMAT_RGBA_8888, 1,
                                                                       usage);
 
-    auto future = mFlinger.renderScreenImpl(*renderArea, traverseLayers, mCaptureScreenBuffer,
-                                            forSystem, regionSampling);
+    auto future = mFlinger.renderScreenImpl(std::move(renderArea), traverseLayers,
+                                            mCaptureScreenBuffer, forSystem, regionSampling);
     ASSERT_TRUE(future.valid());
     const auto fenceResult = future.get();
 
diff --git a/services/surfaceflinger/tests/unittests/DisplayDevice_InitiateModeChange.cpp b/services/surfaceflinger/tests/unittests/DisplayDevice_InitiateModeChange.cpp
index 982b9ff..60ad7a3 100644
--- a/services/surfaceflinger/tests/unittests/DisplayDevice_InitiateModeChange.cpp
+++ b/services/surfaceflinger/tests/unittests/DisplayDevice_InitiateModeChange.cpp
@@ -18,6 +18,7 @@
 #define LOG_TAG "LibSurfaceFlingerUnittests"
 
 #include "DisplayTransactionTestHelpers.h"
+#include "mock/MockFrameRateMode.h"
 
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
@@ -29,6 +30,7 @@
 
 class InitiateModeChangeTest : public DisplayTransactionTest {
 public:
+    using Action = DisplayDevice::DesiredActiveModeAction;
     using Event = scheduler::DisplayModeEvent;
 
     void SetUp() override {
@@ -56,31 +58,42 @@
     static constexpr DisplayModeId kModeId90{1};
     static constexpr DisplayModeId kModeId120{2};
 
-    static inline const DisplayModePtr kMode60 = createDisplayMode(kModeId60, 60_Hz);
-    static inline const DisplayModePtr kMode90 = createDisplayMode(kModeId90, 90_Hz);
-    static inline const DisplayModePtr kMode120 = createDisplayMode(kModeId120, 120_Hz);
+    static inline const ftl::NonNull<DisplayModePtr> kMode60 =
+            ftl::as_non_null(createDisplayMode(kModeId60, 60_Hz));
+    static inline const ftl::NonNull<DisplayModePtr> kMode90 =
+            ftl::as_non_null(createDisplayMode(kModeId90, 90_Hz));
+    static inline const ftl::NonNull<DisplayModePtr> kMode120 =
+            ftl::as_non_null(createDisplayMode(kModeId120, 120_Hz));
 };
 
 TEST_F(InitiateModeChangeTest, setDesiredActiveMode_setCurrentMode) {
-    EXPECT_FALSE(mDisplay->setDesiredActiveMode({kMode60, Event::None}));
+    EXPECT_EQ(Action::None,
+              mDisplay->setDesiredActiveMode(
+                      {scheduler::FrameRateMode{60_Hz, kMode60}, Event::None}));
     EXPECT_EQ(std::nullopt, mDisplay->getDesiredActiveMode());
 }
 
 TEST_F(InitiateModeChangeTest, setDesiredActiveMode_setNewMode) {
-    EXPECT_TRUE(mDisplay->setDesiredActiveMode({kMode90, Event::None}));
+    EXPECT_EQ(Action::InitiateDisplayModeSwitch,
+              mDisplay->setDesiredActiveMode(
+                      {scheduler::FrameRateMode{90_Hz, kMode90}, Event::None}));
     ASSERT_NE(std::nullopt, mDisplay->getDesiredActiveMode());
-    EXPECT_EQ(kMode90, mDisplay->getDesiredActiveMode()->mode);
+    EXPECT_FRAME_RATE_MODE(kMode90, 90_Hz, mDisplay->getDesiredActiveMode()->modeOpt);
     EXPECT_EQ(Event::None, mDisplay->getDesiredActiveMode()->event);
 
-    // Setting another mode should be cached but return false
-    EXPECT_FALSE(mDisplay->setDesiredActiveMode({kMode120, Event::None}));
+    // Setting another mode should be cached but return None
+    EXPECT_EQ(Action::None,
+              mDisplay->setDesiredActiveMode(
+                      {scheduler::FrameRateMode{120_Hz, kMode120}, Event::None}));
     ASSERT_NE(std::nullopt, mDisplay->getDesiredActiveMode());
-    EXPECT_EQ(kMode120, mDisplay->getDesiredActiveMode()->mode);
+    EXPECT_FRAME_RATE_MODE(kMode120, 120_Hz, mDisplay->getDesiredActiveMode()->modeOpt);
     EXPECT_EQ(Event::None, mDisplay->getDesiredActiveMode()->event);
 }
 
 TEST_F(InitiateModeChangeTest, clearDesiredActiveModeState) {
-    EXPECT_TRUE(mDisplay->setDesiredActiveMode({kMode90, Event::None}));
+    EXPECT_EQ(Action::InitiateDisplayModeSwitch,
+              mDisplay->setDesiredActiveMode(
+                      {scheduler::FrameRateMode{90_Hz, kMode90}, Event::None}));
     ASSERT_NE(std::nullopt, mDisplay->getDesiredActiveMode());
 
     mDisplay->clearDesiredActiveModeState();
@@ -88,9 +101,11 @@
 }
 
 TEST_F(InitiateModeChangeTest, initiateModeChange) NO_THREAD_SAFETY_ANALYSIS {
-    EXPECT_TRUE(mDisplay->setDesiredActiveMode({kMode90, Event::None}));
+    EXPECT_EQ(Action::InitiateDisplayModeSwitch,
+              mDisplay->setDesiredActiveMode(
+                      {scheduler::FrameRateMode{90_Hz, kMode90}, Event::None}));
     ASSERT_NE(std::nullopt, mDisplay->getDesiredActiveMode());
-    EXPECT_EQ(kMode90, mDisplay->getDesiredActiveMode()->mode);
+    EXPECT_FRAME_RATE_MODE(kMode90, 90_Hz, mDisplay->getDesiredActiveMode()->modeOpt);
     EXPECT_EQ(Event::None, mDisplay->getDesiredActiveMode()->event);
 
     hal::VsyncPeriodChangeConstraints constraints{
@@ -101,18 +116,27 @@
     EXPECT_EQ(OK,
               mDisplay->initiateModeChange(*mDisplay->getDesiredActiveMode(), constraints,
                                            &timeline));
-    EXPECT_EQ(kMode90, mDisplay->getUpcomingActiveMode().mode);
+    EXPECT_FRAME_RATE_MODE(kMode90, 90_Hz, *mDisplay->getUpcomingActiveMode().modeOpt);
     EXPECT_EQ(Event::None, mDisplay->getUpcomingActiveMode().event);
 
     mDisplay->clearDesiredActiveModeState();
     ASSERT_EQ(std::nullopt, mDisplay->getDesiredActiveMode());
 }
 
+TEST_F(InitiateModeChangeTest, initiateRenderRateChange) {
+    EXPECT_EQ(Action::InitiateRenderRateSwitch,
+              mDisplay->setDesiredActiveMode(
+                      {scheduler::FrameRateMode{30_Hz, kMode60}, Event::None}));
+    EXPECT_EQ(std::nullopt, mDisplay->getDesiredActiveMode());
+}
+
 TEST_F(InitiateModeChangeTest, getUpcomingActiveMode_desiredActiveModeChanged)
 NO_THREAD_SAFETY_ANALYSIS {
-    EXPECT_TRUE(mDisplay->setDesiredActiveMode({kMode90, Event::None}));
+    EXPECT_EQ(Action::InitiateDisplayModeSwitch,
+              mDisplay->setDesiredActiveMode(
+                      {scheduler::FrameRateMode{90_Hz, kMode90}, Event::None}));
     ASSERT_NE(std::nullopt, mDisplay->getDesiredActiveMode());
-    EXPECT_EQ(kMode90, mDisplay->getDesiredActiveMode()->mode);
+    EXPECT_FRAME_RATE_MODE(kMode90, 90_Hz, mDisplay->getDesiredActiveMode()->modeOpt);
     EXPECT_EQ(Event::None, mDisplay->getDesiredActiveMode()->event);
 
     hal::VsyncPeriodChangeConstraints constraints{
@@ -123,21 +147,23 @@
     EXPECT_EQ(OK,
               mDisplay->initiateModeChange(*mDisplay->getDesiredActiveMode(), constraints,
                                            &timeline));
-    EXPECT_EQ(kMode90, mDisplay->getUpcomingActiveMode().mode);
+    EXPECT_FRAME_RATE_MODE(kMode90, 90_Hz, *mDisplay->getUpcomingActiveMode().modeOpt);
     EXPECT_EQ(Event::None, mDisplay->getUpcomingActiveMode().event);
 
-    EXPECT_FALSE(mDisplay->setDesiredActiveMode({kMode120, Event::None}));
+    EXPECT_EQ(Action::None,
+              mDisplay->setDesiredActiveMode(
+                      {scheduler::FrameRateMode{120_Hz, kMode120}, Event::None}));
     ASSERT_NE(std::nullopt, mDisplay->getDesiredActiveMode());
-    EXPECT_EQ(kMode120, mDisplay->getDesiredActiveMode()->mode);
+    EXPECT_FRAME_RATE_MODE(kMode120, 120_Hz, mDisplay->getDesiredActiveMode()->modeOpt);
     EXPECT_EQ(Event::None, mDisplay->getDesiredActiveMode()->event);
 
-    EXPECT_EQ(kMode90, mDisplay->getUpcomingActiveMode().mode);
+    EXPECT_FRAME_RATE_MODE(kMode90, 90_Hz, *mDisplay->getUpcomingActiveMode().modeOpt);
     EXPECT_EQ(Event::None, mDisplay->getUpcomingActiveMode().event);
 
     EXPECT_EQ(OK,
               mDisplay->initiateModeChange(*mDisplay->getDesiredActiveMode(), constraints,
                                            &timeline));
-    EXPECT_EQ(kMode120, mDisplay->getUpcomingActiveMode().mode);
+    EXPECT_FRAME_RATE_MODE(kMode120, 120_Hz, *mDisplay->getUpcomingActiveMode().modeOpt);
     EXPECT_EQ(Event::None, mDisplay->getUpcomingActiveMode().event);
 
     mDisplay->clearDesiredActiveModeState();
diff --git a/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp b/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp
index 8b91c67..e0b508a 100644
--- a/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp
+++ b/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp
@@ -120,51 +120,6 @@
     });
 }
 
-sp<DisplayDevice> DisplayTransactionTest::injectDefaultInternalDisplay(
-        std::function<void(FakeDisplayDeviceInjector&)> injectExtra) {
-    constexpr PhysicalDisplayId DEFAULT_DISPLAY_ID = PhysicalDisplayId::fromPort(255u);
-    constexpr int DEFAULT_DISPLAY_WIDTH = 1080;
-    constexpr int DEFAULT_DISPLAY_HEIGHT = 1920;
-    constexpr HWDisplayId DEFAULT_DISPLAY_HWC_DISPLAY_ID = 0;
-
-    // The DisplayDevice is required to have a framebuffer (behind the
-    // ANativeWindow interface) which uses the actual hardware display
-    // size.
-    EXPECT_CALL(*mNativeWindow, query(NATIVE_WINDOW_WIDTH, _))
-            .WillRepeatedly(DoAll(SetArgPointee<1>(DEFAULT_DISPLAY_WIDTH), Return(0)));
-    EXPECT_CALL(*mNativeWindow, query(NATIVE_WINDOW_HEIGHT, _))
-            .WillRepeatedly(DoAll(SetArgPointee<1>(DEFAULT_DISPLAY_HEIGHT), Return(0)));
-    EXPECT_CALL(*mNativeWindow, perform(NATIVE_WINDOW_SET_BUFFERS_FORMAT));
-    EXPECT_CALL(*mNativeWindow, perform(NATIVE_WINDOW_API_CONNECT));
-    EXPECT_CALL(*mNativeWindow, perform(NATIVE_WINDOW_SET_USAGE64));
-    EXPECT_CALL(*mNativeWindow, perform(NATIVE_WINDOW_API_DISCONNECT)).Times(AnyNumber());
-
-    auto compositionDisplay =
-            compositionengine::impl::createDisplay(mFlinger.getCompositionEngine(),
-                                                   compositionengine::DisplayCreationArgsBuilder()
-                                                           .setId(DEFAULT_DISPLAY_ID)
-                                                           .setPixels({DEFAULT_DISPLAY_WIDTH,
-                                                                       DEFAULT_DISPLAY_HEIGHT})
-                                                           .setPowerAdvisor(&mPowerAdvisor)
-                                                           .build());
-
-    constexpr bool kIsPrimary = true;
-    auto injector = FakeDisplayDeviceInjector(mFlinger, compositionDisplay,
-                                              ui::DisplayConnectionType::Internal,
-                                              DEFAULT_DISPLAY_HWC_DISPLAY_ID, kIsPrimary);
-
-    injector.setNativeWindow(mNativeWindow);
-    if (injectExtra) {
-        injectExtra(injector);
-    }
-
-    auto displayDevice = injector.inject();
-
-    Mock::VerifyAndClear(mNativeWindow.get());
-
-    return displayDevice;
-}
-
 bool DisplayTransactionTest::hasPhysicalHwcDisplay(HWDisplayId hwcDisplayId) const {
     const auto& map = mFlinger.hwcPhysicalDisplayIdMap();
 
diff --git a/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h b/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h
index 9cceb5e..d58e644 100644
--- a/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h
+++ b/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h
@@ -42,6 +42,7 @@
 #include <renderengine/mock/RenderEngine.h>
 #include <ui/DebugUtils.h>
 
+#include "FakeDisplayInjector.h"
 #include "TestableScheduler.h"
 #include "TestableSurfaceFlinger.h"
 #include "mock/DisplayHardware/MockComposer.h"
@@ -88,8 +89,11 @@
     void injectMockComposer(int virtualDisplayCount);
     void injectFakeBufferQueueFactory();
     void injectFakeNativeWindowSurfaceFactory();
+
     sp<DisplayDevice> injectDefaultInternalDisplay(
-            std::function<void(TestableSurfaceFlinger::FakeDisplayDeviceInjector&)>);
+            std::function<void(TestableSurfaceFlinger::FakeDisplayDeviceInjector&)> injectExtra) {
+        return mFakeDisplayInjector.injectInternalDisplay(injectExtra);
+    }
 
     // --------------------------------------------------------------------
     // Postcondition helpers
@@ -114,6 +118,8 @@
     sp<GraphicBuffer> mBuffer = sp<GraphicBuffer>::make();
     Hwc2::mock::PowerAdvisor mPowerAdvisor;
 
+    FakeDisplayInjector mFakeDisplayInjector{mFlinger, mPowerAdvisor, mNativeWindow};
+
     // These mocks are created by the test, but are destroyed by SurfaceFlinger
     // by virtue of being stored into a std::unique_ptr. However we still need
     // to keep a reference to them for use in setting up call expectations.
@@ -353,6 +359,7 @@
     }
 
     // Called by tests to inject a HWC display setup
+    template <bool kInitPowerMode = true>
     static void injectHwcDisplayWithNoDefaultCapabilities(DisplayTransactionTest* test) {
         const auto displayId = DisplayVariant::DISPLAY_ID::get();
         ASSERT_FALSE(GpuVirtualDisplayId::tryCast(displayId));
@@ -361,18 +368,21 @@
                 .setHwcDisplayId(HWC_DISPLAY_ID)
                 .setResolution(DisplayVariant::RESOLUTION)
                 .setActiveConfig(HWC_ACTIVE_CONFIG_ID)
-                .setPowerMode(INIT_POWER_MODE)
+                .setPowerMode(kInitPowerMode ? std::make_optional(INIT_POWER_MODE) : std::nullopt)
                 .inject(&test->mFlinger, test->mComposer);
     }
 
     // Called by tests to inject a HWC display setup
+    template <bool kInitPowerMode = true>
     static void injectHwcDisplay(DisplayTransactionTest* test) {
         EXPECT_CALL(*test->mComposer, getDisplayCapabilities(HWC_DISPLAY_ID, _))
                 .WillOnce(DoAll(SetArgPointee<1>(std::vector<DisplayCapability>({})),
                                 Return(Error::NONE)));
-        EXPECT_CALL(*test->mComposer, setPowerMode(HWC_DISPLAY_ID, INIT_POWER_MODE))
-                .WillOnce(Return(Error::NONE));
-        injectHwcDisplayWithNoDefaultCapabilities(test);
+        if constexpr (kInitPowerMode) {
+            EXPECT_CALL(*test->mComposer, setPowerMode(HWC_DISPLAY_ID, INIT_POWER_MODE))
+                    .WillOnce(Return(Error::NONE));
+        }
+        injectHwcDisplayWithNoDefaultCapabilities<kInitPowerMode>(test);
     }
 
     static std::shared_ptr<compositionengine::Display> injectCompositionDisplay(
diff --git a/services/surfaceflinger/tests/unittests/EventThreadTest.cpp b/services/surfaceflinger/tests/unittests/EventThreadTest.cpp
index a5beaba..dd87f9d 100644
--- a/services/surfaceflinger/tests/unittests/EventThreadTest.cpp
+++ b/services/surfaceflinger/tests/unittests/EventThreadTest.cpp
@@ -633,9 +633,10 @@
                               .setId(DisplayModeId(7))
                               .setVsyncPeriod(16666666)
                               .build();
+    const Fps fps = mode->getFps() / 2;
 
-    mThread->onModeChanged(mode);
-    expectConfigChangedEventReceivedByConnection(INTERNAL_DISPLAY_ID, 7, 16666666);
+    mThread->onModeChanged({fps, ftl::as_non_null(mode)});
+    expectConfigChangedEventReceivedByConnection(INTERNAL_DISPLAY_ID, 7, fps.getPeriodNsecs());
 }
 
 TEST_F(EventThreadTest, postConfigChangedExternal) {
@@ -644,9 +645,10 @@
                               .setId(DisplayModeId(5))
                               .setVsyncPeriod(16666666)
                               .build();
+    const Fps fps = mode->getFps() / 2;
 
-    mThread->onModeChanged(mode);
-    expectConfigChangedEventReceivedByConnection(EXTERNAL_DISPLAY_ID, 5, 16666666);
+    mThread->onModeChanged({fps, ftl::as_non_null(mode)});
+    expectConfigChangedEventReceivedByConnection(EXTERNAL_DISPLAY_ID, 5, fps.getPeriodNsecs());
 }
 
 TEST_F(EventThreadTest, postConfigChangedPrimary64bit) {
@@ -655,8 +657,9 @@
                               .setId(DisplayModeId(7))
                               .setVsyncPeriod(16666666)
                               .build();
-    mThread->onModeChanged(mode);
-    expectConfigChangedEventReceivedByConnection(DISPLAY_ID_64BIT, 7, 16666666);
+    const Fps fps = mode->getFps() / 2;
+    mThread->onModeChanged({fps, ftl::as_non_null(mode)});
+    expectConfigChangedEventReceivedByConnection(DISPLAY_ID_64BIT, 7, fps.getPeriodNsecs());
 }
 
 TEST_F(EventThreadTest, suppressConfigChanged) {
@@ -669,9 +672,10 @@
                               .setId(DisplayModeId(9))
                               .setVsyncPeriod(16666666)
                               .build();
+    const Fps fps = mode->getFps() / 2;
 
-    mThread->onModeChanged(mode);
-    expectConfigChangedEventReceivedByConnection(INTERNAL_DISPLAY_ID, 9, 16666666);
+    mThread->onModeChanged({fps, ftl::as_non_null(mode)});
+    expectConfigChangedEventReceivedByConnection(INTERNAL_DISPLAY_ID, 9, fps.getPeriodNsecs());
 
     auto args = suppressConnectionEventRecorder.waitForCall();
     ASSERT_FALSE(args.has_value());
diff --git a/services/surfaceflinger/tests/unittests/FakeDisplayInjector.h b/services/surfaceflinger/tests/unittests/FakeDisplayInjector.h
index 81b420c..6e4bf2b 100644
--- a/services/surfaceflinger/tests/unittests/FakeDisplayInjector.h
+++ b/services/surfaceflinger/tests/unittests/FakeDisplayInjector.h
@@ -27,49 +27,54 @@
 using FakeDisplayDeviceInjector = TestableSurfaceFlinger::FakeDisplayDeviceInjector;
 using android::hardware::graphics::composer::hal::HWDisplayId;
 using android::Hwc2::mock::PowerAdvisor;
-using testing::_;
-using testing::AnyNumber;
-using testing::DoAll;
-using testing::Mock;
-using testing::ResultOf;
-using testing::Return;
-using testing::SetArgPointee;
+
+struct FakeDisplayInjectorArgs {
+    PhysicalDisplayId displayId = PhysicalDisplayId::fromPort(255u);
+    HWDisplayId hwcDisplayId = 0;
+    bool isPrimary = true;
+};
 
 class FakeDisplayInjector {
 public:
-    sp<DisplayDevice> injectDefaultInternalDisplay(
-            const std::function<void(FakeDisplayDeviceInjector&)>& injectExtra,
-            TestableSurfaceFlinger& flinger, uint8_t port = 255u) const {
-        constexpr int DEFAULT_DISPLAY_WIDTH = 1080;
-        constexpr int DEFAULT_DISPLAY_HEIGHT = 1920;
-        constexpr HWDisplayId DEFAULT_DISPLAY_HWC_DISPLAY_ID = 0;
+    FakeDisplayInjector(TestableSurfaceFlinger& flinger, Hwc2::mock::PowerAdvisor& powerAdvisor,
+                        sp<mock::NativeWindow> nativeWindow)
+          : mFlinger(flinger), mPowerAdvisor(powerAdvisor), mNativeWindow(nativeWindow) {}
 
-        const PhysicalDisplayId physicalDisplayId = PhysicalDisplayId::fromPort(port);
+    sp<DisplayDevice> injectInternalDisplay(
+            const std::function<void(FakeDisplayDeviceInjector&)>& injectExtra,
+            FakeDisplayInjectorArgs args = {}) {
+        using testing::_;
+        using testing::AnyNumber;
+        using testing::DoAll;
+        using testing::Mock;
+        using testing::Return;
+        using testing::SetArgPointee;
+
+        constexpr ui::Size kResolution = {1080, 1920};
 
         // The DisplayDevice is required to have a framebuffer (behind the
         // ANativeWindow interface) which uses the actual hardware display
         // size.
         EXPECT_CALL(*mNativeWindow, query(NATIVE_WINDOW_WIDTH, _))
-                .WillRepeatedly(DoAll(SetArgPointee<1>(DEFAULT_DISPLAY_WIDTH), Return(0)));
+                .WillRepeatedly(DoAll(SetArgPointee<1>(kResolution.getWidth()), Return(0)));
         EXPECT_CALL(*mNativeWindow, query(NATIVE_WINDOW_HEIGHT, _))
-                .WillRepeatedly(DoAll(SetArgPointee<1>(DEFAULT_DISPLAY_HEIGHT), Return(0)));
+                .WillRepeatedly(DoAll(SetArgPointee<1>(kResolution.getHeight()), Return(0)));
         EXPECT_CALL(*mNativeWindow, perform(NATIVE_WINDOW_SET_BUFFERS_FORMAT));
         EXPECT_CALL(*mNativeWindow, perform(NATIVE_WINDOW_API_CONNECT));
         EXPECT_CALL(*mNativeWindow, perform(NATIVE_WINDOW_SET_USAGE64));
         EXPECT_CALL(*mNativeWindow, perform(NATIVE_WINDOW_API_DISCONNECT)).Times(AnyNumber());
 
         auto compositionDisplay = compositionengine::impl::
-                createDisplay(flinger.getCompositionEngine(),
+                createDisplay(mFlinger.getCompositionEngine(),
                               compositionengine::DisplayCreationArgsBuilder()
-                                      .setId(physicalDisplayId)
-                                      .setPixels({DEFAULT_DISPLAY_WIDTH, DEFAULT_DISPLAY_HEIGHT})
-                                      .setPowerAdvisor(mPowerAdvisor)
+                                      .setId(args.displayId)
+                                      .setPixels(kResolution)
+                                      .setPowerAdvisor(&mPowerAdvisor)
                                       .build());
 
-        constexpr bool kIsPrimary = true;
-        auto injector = FakeDisplayDeviceInjector(flinger, compositionDisplay,
+        auto injector = FakeDisplayDeviceInjector(mFlinger, compositionDisplay,
                                                   ui::DisplayConnectionType::Internal,
-                                                  DEFAULT_DISPLAY_HWC_DISPLAY_ID, kIsPrimary);
+                                                  args.hwcDisplayId, args.isPrimary);
 
         injector.setNativeWindow(mNativeWindow);
         if (injectExtra) {
@@ -83,8 +88,9 @@
         return displayDevice;
     }
 
-    sp<mock::NativeWindow> mNativeWindow = sp<mock::NativeWindow>::make();
-    PowerAdvisor* mPowerAdvisor = new PowerAdvisor();
+    TestableSurfaceFlinger& mFlinger;
+    Hwc2::mock::PowerAdvisor& mPowerAdvisor;
+    sp<mock::NativeWindow> mNativeWindow;
 };
 
-} // namespace android
\ No newline at end of file
+} // namespace android
diff --git a/services/surfaceflinger/tests/unittests/RefreshRateSelectionTest.cpp b/services/surfaceflinger/tests/unittests/FrameRateSelectionPriorityTest.cpp
similarity index 100%
rename from services/surfaceflinger/tests/unittests/RefreshRateSelectionTest.cpp
rename to services/surfaceflinger/tests/unittests/FrameRateSelectionPriorityTest.cpp
diff --git a/services/surfaceflinger/tests/unittests/HWComposerTest.cpp b/services/surfaceflinger/tests/unittests/HWComposerTest.cpp
index 9d8e0a2..8f89a8c 100644
--- a/services/surfaceflinger/tests/unittests/HWComposerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/HWComposerTest.cpp
@@ -73,6 +73,7 @@
 
         EXPECT_CALL(*mHal, setClientTargetSlotCount(_));
         EXPECT_CALL(*mHal, setVsyncEnabled(hwcDisplayId, Hwc2::IComposerClient::Vsync::DISABLE));
+        EXPECT_CALL(*mHal, onHotplugConnect(hwcDisplayId));
     }
 };
 
@@ -145,6 +146,8 @@
                                     {kMetadata2Name, kMetadata2Mandatory},
                             }),
                             Return(hardware::graphics::composer::V2_4::Error::NONE)));
+    EXPECT_CALL(*mHal, getOverlaySupport(_)).WillOnce(Return(HalError::NONE));
+
     EXPECT_CALL(*mHal, registerCallback(_));
 
     mHwc.setCallback(mCallback);
@@ -161,6 +164,7 @@
     EXPECT_CALL(*mHal, getCapabilities()).WillOnce(Return(std::vector<aidl::Capability>{}));
     EXPECT_CALL(*mHal, getLayerGenericMetadataKeys(_))
             .WillOnce(Return(hardware::graphics::composer::V2_4::Error::UNSUPPORTED));
+    EXPECT_CALL(*mHal, getOverlaySupport(_)).WillOnce(Return(HalError::UNSUPPORTED));
     EXPECT_CALL(*mHal, registerCallback(_));
 
     mHwc.setCallback(mCallback);
diff --git a/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp b/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp
index 972198c..979924a 100644
--- a/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp
@@ -69,11 +69,11 @@
         // LayerHistory::summarize makes no guarantee of the order of the elements in the summary
         // however, for testing only, a stable order is required, therefore we sort the list here.
         // Any tests requiring ordered results must create layers with names.
-        auto summary = history().summarize(*mScheduler->refreshRateConfigs(), now);
+        auto summary = history().summarize(*mScheduler->refreshRateSelector(), now);
         std::sort(summary.begin(), summary.end(),
-                  [](const RefreshRateConfigs::LayerRequirement& a,
-                     const RefreshRateConfigs::LayerRequirement& b) -> bool {
-                      return a.name < b.name;
+                  [](const RefreshRateSelector::LayerRequirement& lhs,
+                     const RefreshRateSelector::LayerRequirement& rhs) -> bool {
+                      return lhs.name < rhs.name;
                   });
         return summary;
     }
@@ -125,16 +125,16 @@
         ASSERT_EQ(desiredRefreshRate, summary[0].desiredRefreshRate);
     }
 
-    std::shared_ptr<RefreshRateConfigs> mConfigs =
-            std::make_shared<RefreshRateConfigs>(makeModes(createDisplayMode(DisplayModeId(0),
-                                                                             LO_FPS),
-                                                           createDisplayMode(DisplayModeId(1),
-                                                                             HI_FPS)),
-                                                 DisplayModeId(0));
+    std::shared_ptr<RefreshRateSelector> mSelector =
+            std::make_shared<RefreshRateSelector>(makeModes(createDisplayMode(DisplayModeId(0),
+                                                                              LO_FPS),
+                                                            createDisplayMode(DisplayModeId(1),
+                                                                              HI_FPS)),
+                                                  DisplayModeId(0));
 
     mock::SchedulerCallback mSchedulerCallback;
 
-    TestableScheduler* mScheduler = new TestableScheduler(mConfigs, mSchedulerCallback);
+    TestableScheduler* mScheduler = new TestableScheduler(mSelector, mSchedulerCallback);
 
     TestableSurfaceFlinger mFlinger;
 };
diff --git a/services/surfaceflinger/tests/unittests/LayerLifecycleManagerTest.cpp b/services/surfaceflinger/tests/unittests/LayerLifecycleManagerTest.cpp
new file mode 100644
index 0000000..89440a6
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/LayerLifecycleManagerTest.cpp
@@ -0,0 +1,453 @@
+/*
+ * 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 <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "FrontEnd/LayerHandle.h"
+#include "FrontEnd/LayerLifecycleManager.h"
+#include "Layer.h"
+#include "gui/SurfaceComposerClient.h"
+
+using namespace android::surfaceflinger;
+
+namespace android::surfaceflinger::frontend {
+
+namespace {
+LayerCreationArgs createArgs(uint32_t id, bool canBeRoot, wp<IBinder> parent, wp<IBinder> mirror) {
+    LayerCreationArgs args(nullptr, nullptr, "testlayer", 0, {}, std::make_optional(id));
+    args.addToRoot = canBeRoot;
+    args.parentHandle = parent;
+    args.mirrorLayerHandle = mirror;
+    return args;
+}
+} // namespace
+
+// To run test:
+/**
+ mp :libsurfaceflinger_unittest && adb sync; adb shell \
+    /data/nativetest/libsurfaceflinger_unittest/libsurfaceflinger_unittest \
+    --gtest_filter="LayerLifecycleManagerTest.*" --gtest_repeat=100 \
+    --gtest_shuffle \
+    --gtest_brief=1
+*/
+class ExpectLayerLifecycleListener : public LayerLifecycleManager::ILifecycleListener {
+public:
+    void onLayerAdded(const RequestedLayerState& layer) override {
+        mActualLayersAdded.emplace(layer.id);
+    };
+    void onLayerDestroyed(const RequestedLayerState& layer) override {
+        mActualLayersDestroyed.emplace(layer.id);
+    };
+
+    void expectLayersAdded(const std::unordered_set<uint32_t>& expectedLayersAdded) {
+        EXPECT_EQ(expectedLayersAdded, mActualLayersAdded);
+        mActualLayersAdded.clear();
+    }
+    void expectLayersDestroyed(const std::unordered_set<uint32_t>& expectedLayersDestroyed) {
+        EXPECT_EQ(expectedLayersDestroyed, mActualLayersDestroyed);
+        mActualLayersDestroyed.clear();
+    }
+
+    std::unordered_set<uint32_t> mActualLayersAdded;
+    std::unordered_set<uint32_t> mActualLayersDestroyed;
+};
+
+class LayerLifecycleManagerTest : public testing::Test {
+protected:
+    std::unique_ptr<RequestedLayerState> rootLayer(uint32_t id) {
+        return std::make_unique<RequestedLayerState>(
+                createArgs(/*id=*/id, /*canBeRoot=*/true, /*parent=*/nullptr, /*mirror=*/nullptr));
+    }
+
+    std::unique_ptr<RequestedLayerState> childLayer(uint32_t id, uint32_t parentId) {
+        mHandles[parentId] = sp<LayerHandle>::make(parentId);
+        return std::make_unique<RequestedLayerState>(createArgs(/*id=*/id, /*canBeRoot=*/false,
+                                                                /*parent=*/mHandles[parentId],
+                                                                /*mirror=*/nullptr));
+    }
+
+    TransactionState reparentLayer(uint32_t id, uint32_t newParentId) {
+        TransactionState transaction;
+        transaction.states.push_back({});
+
+        if (newParentId == UNASSIGNED_LAYER_ID) {
+            transaction.states.front().state.parentSurfaceControlForChild = nullptr;
+        } else {
+            transaction.states.front().state.parentSurfaceControlForChild =
+                    sp<SurfaceControl>::make(SurfaceComposerClient::getDefault(),
+                                             sp<LayerHandle>::make(newParentId),
+                                             static_cast<int32_t>(newParentId), "Test");
+        }
+        transaction.states.front().state.what = layer_state_t::eReparent;
+        transaction.states.front().state.surface = sp<LayerHandle>::make(id);
+        return transaction;
+    }
+
+    TransactionState setLayer(uint32_t id, int32_t z) {
+        TransactionState transaction;
+        transaction.states.push_back({});
+        transaction.states.front().state.z = z;
+        transaction.states.front().state.what = layer_state_t::eLayerChanged;
+        transaction.states.front().state.surface = sp<LayerHandle>::make(id);
+        return transaction;
+    }
+
+    TransactionState makeRelative(uint32_t id, uint32_t relativeParentId) {
+        TransactionState transaction;
+        transaction.states.push_back({});
+
+        if (relativeParentId == UNASSIGNED_LAYER_ID) {
+            transaction.states.front().state.relativeLayerSurfaceControl = nullptr;
+        } else {
+            transaction.states.front().state.relativeLayerSurfaceControl =
+                    sp<SurfaceControl>::make(SurfaceComposerClient::getDefault(),
+                                             sp<LayerHandle>::make(relativeParentId),
+                                             static_cast<int32_t>(relativeParentId), "Test");
+        }
+        transaction.states.front().state.what = layer_state_t::eRelativeLayerChanged;
+        transaction.states.front().state.surface = sp<LayerHandle>::make(id);
+        return transaction;
+    }
+
+    RequestedLayerState* getRequestedLayerState(LayerLifecycleManager& lifecycleManager,
+                                                uint32_t layerId) {
+        return lifecycleManager.getLayerFromId(layerId);
+    }
+
+    std::unordered_map<uint32_t, sp<LayerHandle>> mHandles;
+};
+
+TEST_F(LayerLifecycleManagerTest, addLayers) {
+    LayerLifecycleManager lifecycleManager;
+    auto listener = std::make_shared<ExpectLayerLifecycleListener>();
+    lifecycleManager.addLifecycleListener(listener);
+    std::vector<std::unique_ptr<RequestedLayerState>> layers;
+    layers.emplace_back(rootLayer(1));
+    layers.emplace_back(rootLayer(2));
+    layers.emplace_back(rootLayer(3));
+    lifecycleManager.addLayers(std::move(layers));
+    lifecycleManager.onHandlesDestroyed({1, 2, 3});
+    EXPECT_TRUE(lifecycleManager.getGlobalChanges().test(RequestedLayerState::Changes::Hierarchy));
+    lifecycleManager.commitChanges();
+    EXPECT_FALSE(lifecycleManager.getGlobalChanges().test(RequestedLayerState::Changes::Hierarchy));
+    listener->expectLayersAdded({1, 2, 3});
+    listener->expectLayersDestroyed({1, 2, 3});
+}
+
+TEST_F(LayerLifecycleManagerTest, updateLayerStates) {
+    LayerLifecycleManager lifecycleManager;
+    std::vector<std::unique_ptr<RequestedLayerState>> layers;
+    layers.emplace_back(rootLayer(1));
+    lifecycleManager.addLayers(std::move(layers));
+
+    std::vector<TransactionState> transactions;
+    transactions.emplace_back();
+    transactions.back().states.push_back({});
+    transactions.back().states.front().state.z = 2;
+    transactions.back().states.front().state.what = layer_state_t::eLayerChanged;
+    sp<LayerHandle> handle = sp<LayerHandle>::make(1u);
+    transactions.back().states.front().state.surface = handle;
+    lifecycleManager.applyTransactions(transactions);
+    transactions.clear();
+
+    auto& managedLayers = lifecycleManager.getLayers();
+    ASSERT_EQ(managedLayers.size(), 1u);
+
+    EXPECT_EQ(managedLayers.front()->z, 2);
+    EXPECT_TRUE(managedLayers.front()->changes.test(RequestedLayerState::Changes::Z));
+
+    EXPECT_TRUE(lifecycleManager.getGlobalChanges().test(RequestedLayerState::Changes::Hierarchy));
+    lifecycleManager.commitChanges();
+    EXPECT_FALSE(lifecycleManager.getGlobalChanges().test(RequestedLayerState::Changes::Hierarchy));
+    ASSERT_EQ(managedLayers.size(), 1u);
+    EXPECT_FALSE(managedLayers.front()->changes.test(RequestedLayerState::Changes::Z));
+
+    // apply transactions that do not affect the hierarchy
+    transactions.emplace_back();
+    transactions.back().states.push_back({});
+    transactions.back().states.front().state.backgroundBlurRadius = 22;
+    transactions.back().states.front().state.what = layer_state_t::eBackgroundBlurRadiusChanged;
+    transactions.back().states.front().state.surface = handle;
+    lifecycleManager.applyTransactions(transactions);
+    EXPECT_FALSE(lifecycleManager.getGlobalChanges().test(RequestedLayerState::Changes::Hierarchy));
+    lifecycleManager.commitChanges();
+    EXPECT_FALSE(lifecycleManager.getGlobalChanges().test(RequestedLayerState::Changes::Hierarchy));
+    EXPECT_EQ(managedLayers.front()->backgroundBlurRadius, 22u);
+}
+
+TEST_F(LayerLifecycleManagerTest, layerWithoutHandleIsDestroyed) {
+    LayerLifecycleManager lifecycleManager;
+    auto listener = std::make_shared<ExpectLayerLifecycleListener>();
+    lifecycleManager.addLifecycleListener(listener);
+    std::vector<std::unique_ptr<RequestedLayerState>> layers;
+    layers.emplace_back(rootLayer(1));
+    layers.emplace_back(rootLayer(2));
+    lifecycleManager.addLayers(std::move(layers));
+    lifecycleManager.onHandlesDestroyed({1});
+    lifecycleManager.commitChanges();
+
+    SCOPED_TRACE("layerWithoutHandleIsDestroyed");
+    listener->expectLayersAdded({1, 2});
+    listener->expectLayersDestroyed({1});
+}
+
+TEST_F(LayerLifecycleManagerTest, rootLayerWithoutHandleIsDestroyed) {
+    LayerLifecycleManager lifecycleManager;
+    auto listener = std::make_shared<ExpectLayerLifecycleListener>();
+    lifecycleManager.addLifecycleListener(listener);
+    std::vector<std::unique_ptr<RequestedLayerState>> layers;
+    layers.emplace_back(rootLayer(1));
+    layers.emplace_back(rootLayer(2));
+    lifecycleManager.addLayers(std::move(layers));
+    lifecycleManager.onHandlesDestroyed({1});
+    lifecycleManager.commitChanges();
+    listener->expectLayersAdded({1, 2});
+    listener->expectLayersDestroyed({1});
+}
+
+TEST_F(LayerLifecycleManagerTest, offscreenLayerIsDestroyed) {
+    LayerLifecycleManager lifecycleManager;
+    auto listener = std::make_shared<ExpectLayerLifecycleListener>();
+    lifecycleManager.addLifecycleListener(listener);
+    std::vector<std::unique_ptr<RequestedLayerState>> layers;
+    layers.emplace_back(rootLayer(1));
+    layers.emplace_back(rootLayer(2));
+    layers.emplace_back(childLayer(3, /*parent*/ 2));
+    lifecycleManager.addLayers(std::move(layers));
+    lifecycleManager.commitChanges();
+    listener->expectLayersAdded({1, 2, 3});
+    listener->expectLayersDestroyed({});
+
+    lifecycleManager.applyTransactions({reparentLayer(3, UNASSIGNED_LAYER_ID)});
+    lifecycleManager.commitChanges();
+    listener->expectLayersAdded({});
+    listener->expectLayersDestroyed({});
+
+    lifecycleManager.onHandlesDestroyed({3});
+    lifecycleManager.commitChanges();
+    listener->expectLayersAdded({});
+    listener->expectLayersDestroyed({3});
+}
+
+TEST_F(LayerLifecycleManagerTest, offscreenChildLayerWithHandleIsNotDestroyed) {
+    LayerLifecycleManager lifecycleManager;
+    auto listener = std::make_shared<ExpectLayerLifecycleListener>();
+    lifecycleManager.addLifecycleListener(listener);
+    std::vector<std::unique_ptr<RequestedLayerState>> layers;
+    layers.emplace_back(rootLayer(1));
+    layers.emplace_back(rootLayer(2));
+    layers.emplace_back(childLayer(3, /*parent*/ 2));
+    layers.emplace_back(childLayer(4, /*parent*/ 3));
+    lifecycleManager.addLayers(std::move(layers));
+    lifecycleManager.commitChanges();
+    listener->expectLayersAdded({1, 2, 3, 4});
+    listener->expectLayersDestroyed({});
+
+    lifecycleManager.applyTransactions({reparentLayer(3, UNASSIGNED_LAYER_ID)});
+    lifecycleManager.onHandlesDestroyed({3});
+    lifecycleManager.commitChanges();
+    listener->expectLayersAdded({});
+    listener->expectLayersDestroyed({3});
+}
+
+TEST_F(LayerLifecycleManagerTest, offscreenChildLayerWithoutHandleIsDestroyed) {
+    LayerLifecycleManager lifecycleManager;
+    auto listener = std::make_shared<ExpectLayerLifecycleListener>();
+    lifecycleManager.addLifecycleListener(listener);
+    std::vector<std::unique_ptr<RequestedLayerState>> layers;
+    layers.emplace_back(rootLayer(1));
+    layers.emplace_back(rootLayer(2));
+    layers.emplace_back(childLayer(3, /*parent*/ 2));
+    layers.emplace_back(childLayer(4, /*parent*/ 3));
+    lifecycleManager.addLayers(std::move(layers));
+    lifecycleManager.commitChanges();
+    listener->expectLayersAdded({1, 2, 3, 4});
+    listener->expectLayersDestroyed({});
+
+    lifecycleManager.applyTransactions({reparentLayer(3, UNASSIGNED_LAYER_ID)});
+    lifecycleManager.onHandlesDestroyed({3, 4});
+    lifecycleManager.commitChanges();
+    listener->expectLayersAdded({});
+    listener->expectLayersDestroyed({3, 4});
+}
+
+TEST_F(LayerLifecycleManagerTest, reparentingDoesNotAffectRelativeZ) {
+    LayerLifecycleManager lifecycleManager;
+    auto listener = std::make_shared<ExpectLayerLifecycleListener>();
+    lifecycleManager.addLifecycleListener(listener);
+    std::vector<std::unique_ptr<RequestedLayerState>> layers;
+    layers.emplace_back(rootLayer(1));
+    layers.emplace_back(rootLayer(2));
+    layers.emplace_back(childLayer(3, /*parent*/ 2));
+    layers.emplace_back(childLayer(4, /*parent*/ 3));
+
+    lifecycleManager.addLayers(std::move(layers));
+    lifecycleManager.commitChanges();
+    listener->expectLayersAdded({1, 2, 3, 4});
+    listener->expectLayersDestroyed({});
+
+    lifecycleManager.applyTransactions({makeRelative(4, 1)});
+    EXPECT_TRUE(getRequestedLayerState(lifecycleManager, 4)->isRelativeOf);
+    lifecycleManager.applyTransactions({reparentLayer(4, 2)});
+    EXPECT_TRUE(getRequestedLayerState(lifecycleManager, 4)->isRelativeOf);
+
+    lifecycleManager.commitChanges();
+    listener->expectLayersAdded({});
+    listener->expectLayersDestroyed({});
+}
+
+TEST_F(LayerLifecycleManagerTest, reparentingToNullRemovesRelativeZ) {
+    LayerLifecycleManager lifecycleManager;
+    auto listener = std::make_shared<ExpectLayerLifecycleListener>();
+    lifecycleManager.addLifecycleListener(listener);
+    std::vector<std::unique_ptr<RequestedLayerState>> layers;
+    layers.emplace_back(rootLayer(1));
+    layers.emplace_back(rootLayer(2));
+    layers.emplace_back(childLayer(3, /*parent*/ 2));
+    layers.emplace_back(childLayer(4, /*parent*/ 3));
+
+    lifecycleManager.addLayers(std::move(layers));
+    lifecycleManager.commitChanges();
+    listener->expectLayersAdded({1, 2, 3, 4});
+    listener->expectLayersDestroyed({});
+
+    lifecycleManager.applyTransactions({makeRelative(4, 1)});
+    EXPECT_TRUE(getRequestedLayerState(lifecycleManager, 4)->isRelativeOf);
+    lifecycleManager.applyTransactions({reparentLayer(4, UNASSIGNED_LAYER_ID)});
+    EXPECT_FALSE(getRequestedLayerState(lifecycleManager, 4)->isRelativeOf);
+
+    lifecycleManager.commitChanges();
+    listener->expectLayersAdded({});
+    listener->expectLayersDestroyed({});
+}
+
+TEST_F(LayerLifecycleManagerTest, setZRemovesRelativeZ) {
+    LayerLifecycleManager lifecycleManager;
+    auto listener = std::make_shared<ExpectLayerLifecycleListener>();
+    lifecycleManager.addLifecycleListener(listener);
+    std::vector<std::unique_ptr<RequestedLayerState>> layers;
+    layers.emplace_back(rootLayer(1));
+    layers.emplace_back(rootLayer(2));
+    layers.emplace_back(childLayer(3, /*parent*/ 2));
+    layers.emplace_back(childLayer(4, /*parent*/ 3));
+
+    lifecycleManager.addLayers(std::move(layers));
+    lifecycleManager.commitChanges();
+    listener->expectLayersAdded({1, 2, 3, 4});
+    listener->expectLayersDestroyed({});
+
+    lifecycleManager.applyTransactions({makeRelative(4, 1)});
+    EXPECT_TRUE(getRequestedLayerState(lifecycleManager, 4)->isRelativeOf);
+    lifecycleManager.applyTransactions({setLayer(4, 1)});
+    EXPECT_FALSE(getRequestedLayerState(lifecycleManager, 4)->isRelativeOf);
+
+    lifecycleManager.commitChanges();
+    listener->expectLayersAdded({});
+    listener->expectLayersDestroyed({});
+}
+
+TEST_F(LayerLifecycleManagerTest, canAddBackgroundLayer) {
+    LayerLifecycleManager lifecycleManager;
+    auto listener = std::make_shared<ExpectLayerLifecycleListener>();
+    lifecycleManager.addLifecycleListener(listener);
+
+    std::vector<std::unique_ptr<RequestedLayerState>> layers;
+    layers.emplace_back(rootLayer(1));
+    lifecycleManager.addLayers(std::move(layers));
+
+    std::vector<TransactionState> transactions;
+    transactions.emplace_back();
+    transactions.back().states.push_back({});
+    transactions.back().states.front().state.bgColorAlpha = 0.5;
+    transactions.back().states.front().state.what = layer_state_t::eBackgroundColorChanged;
+    sp<LayerHandle> handle = sp<LayerHandle>::make(1u);
+    transactions.back().states.front().state.surface = handle;
+    lifecycleManager.applyTransactions(transactions);
+
+    auto& managedLayers = lifecycleManager.getLayers();
+    ASSERT_EQ(managedLayers.size(), 2u);
+
+    EXPECT_TRUE(lifecycleManager.getGlobalChanges().test(RequestedLayerState::Changes::Hierarchy));
+    lifecycleManager.commitChanges();
+    listener->expectLayersAdded({1, 2});
+    listener->expectLayersDestroyed({});
+    EXPECT_EQ(getRequestedLayerState(lifecycleManager, 2)->color.a, 0.5_hf);
+}
+
+TEST_F(LayerLifecycleManagerTest, canDestroyBackgroundLayer) {
+    LayerLifecycleManager lifecycleManager;
+    auto listener = std::make_shared<ExpectLayerLifecycleListener>();
+    lifecycleManager.addLifecycleListener(listener);
+
+    std::vector<std::unique_ptr<RequestedLayerState>> layers;
+    layers.emplace_back(rootLayer(1));
+    lifecycleManager.addLayers(std::move(layers));
+
+    std::vector<TransactionState> transactions;
+    transactions.emplace_back();
+    transactions.back().states.push_back({});
+    transactions.back().states.front().state.bgColorAlpha = 0.5;
+    transactions.back().states.front().state.what = layer_state_t::eBackgroundColorChanged;
+    sp<LayerHandle> handle = sp<LayerHandle>::make(1u);
+    transactions.back().states.front().state.surface = handle;
+    transactions.emplace_back();
+    transactions.back().states.push_back({});
+    transactions.back().states.front().state.bgColorAlpha = 0;
+    transactions.back().states.front().state.what = layer_state_t::eBackgroundColorChanged;
+    transactions.back().states.front().state.surface = handle;
+
+    lifecycleManager.applyTransactions(transactions);
+
+    ASSERT_EQ(lifecycleManager.getLayers().size(), 1u);
+    ASSERT_EQ(lifecycleManager.getDestroyedLayers().size(), 1u);
+
+    EXPECT_TRUE(lifecycleManager.getGlobalChanges().test(RequestedLayerState::Changes::Hierarchy));
+    lifecycleManager.commitChanges();
+    listener->expectLayersAdded({1, 2});
+    listener->expectLayersDestroyed({2});
+}
+
+TEST_F(LayerLifecycleManagerTest, onParentDestroyDestroysBackgroundLayer) {
+    LayerLifecycleManager lifecycleManager;
+    auto listener = std::make_shared<ExpectLayerLifecycleListener>();
+    lifecycleManager.addLifecycleListener(listener);
+
+    std::vector<std::unique_ptr<RequestedLayerState>> layers;
+    layers.emplace_back(rootLayer(1));
+    lifecycleManager.addLayers(std::move(layers));
+
+    std::vector<TransactionState> transactions;
+    transactions.emplace_back();
+    transactions.back().states.push_back({});
+    transactions.back().states.front().state.bgColorAlpha = 0.5;
+    transactions.back().states.front().state.what = layer_state_t::eBackgroundColorChanged;
+    sp<LayerHandle> handle = sp<LayerHandle>::make(1u);
+    transactions.back().states.front().state.surface = handle;
+    transactions.emplace_back();
+    lifecycleManager.applyTransactions(transactions);
+    lifecycleManager.onHandlesDestroyed({1});
+
+    ASSERT_EQ(lifecycleManager.getLayers().size(), 0u);
+    ASSERT_EQ(lifecycleManager.getDestroyedLayers().size(), 2u);
+
+    EXPECT_TRUE(lifecycleManager.getGlobalChanges().test(RequestedLayerState::Changes::Hierarchy));
+    lifecycleManager.commitChanges();
+    listener->expectLayersAdded({1, 2});
+    listener->expectLayersDestroyed({1, 2});
+}
+
+} // namespace android::surfaceflinger::frontend
diff --git a/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp b/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp
deleted file mode 100644
index 620825f..0000000
--- a/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp
+++ /dev/null
@@ -1,2425 +0,0 @@
-/*
- * Copyright 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.
- */
-
-#undef LOG_TAG
-#define LOG_TAG "SchedulerUnittests"
-
-#include <ftl/enum.h>
-#include <ftl/fake_guard.h>
-#include <gmock/gmock.h>
-#include <log/log.h>
-#include <ui/Size.h>
-
-#include "DisplayHardware/HWC2.h"
-#include "FpsOps.h"
-#include "Scheduler/RefreshRateConfigs.h"
-#include "mock/DisplayHardware/MockDisplayMode.h"
-
-using namespace std::chrono_literals;
-
-namespace android::scheduler {
-
-namespace hal = android::hardware::graphics::composer::hal;
-
-using SetPolicyResult = RefreshRateConfigs::SetPolicyResult;
-using LayerVoteType = RefreshRateConfigs::LayerVoteType;
-using LayerRequirement = RefreshRateConfigs::LayerRequirement;
-
-using mock::createDisplayMode;
-
-struct TestableRefreshRateConfigs : RefreshRateConfigs {
-    using RefreshRateConfigs::RefreshRateConfigs;
-    using RefreshRateConfigs::RefreshRateOrder;
-
-    void setActiveModeId(DisplayModeId modeId) {
-        ftl::FakeGuard guard(kMainThreadContext);
-        return RefreshRateConfigs::setActiveModeId(modeId);
-    }
-
-    const DisplayMode& getActiveMode() const {
-        ftl::FakeGuard guard(kMainThreadContext);
-        return RefreshRateConfigs::getActiveMode();
-    }
-
-    DisplayModePtr getMinSupportedRefreshRate() const {
-        std::lock_guard lock(mLock);
-        return mMinRefreshRateModeIt->second;
-    }
-
-    DisplayModePtr getMaxSupportedRefreshRate() const {
-        std::lock_guard lock(mLock);
-        return mMaxRefreshRateModeIt->second;
-    }
-
-    DisplayModePtr getMinRefreshRateByPolicy() const {
-        std::lock_guard lock(mLock);
-        return getMinRefreshRateByPolicyLocked();
-    }
-
-    DisplayModePtr getMaxRefreshRateByPolicy() const {
-        std::lock_guard lock(mLock);
-        return getMaxRefreshRateByPolicyLocked(getActiveModeItLocked()->second->getGroup());
-    }
-
-    std::vector<RefreshRateRanking> getRefreshRatesByPolicy(
-            std::optional<int> anchorGroupOpt, RefreshRateOrder refreshRateOrder) const {
-        std::lock_guard lock(mLock);
-        return RefreshRateConfigs::
-                getRefreshRatesByPolicyLocked(anchorGroupOpt, refreshRateOrder,
-                                              /*preferredDisplayModeOpt*/ std::nullopt);
-    }
-
-    const std::vector<Fps>& knownFrameRates() const { return mKnownFrameRates; }
-
-    using RefreshRateConfigs::GetRankedRefreshRatesCache;
-    auto& mutableGetRankedRefreshRatesCache() { return mGetRankedRefreshRatesCache; }
-
-    auto getRankedRefreshRatesAndSignals(const std::vector<LayerRequirement>& layers,
-                                         GlobalSignals signals) const {
-        return RefreshRateConfigs::getRankedRefreshRates(layers, signals);
-    }
-
-    DisplayModePtr getBestRefreshRate(const std::vector<LayerRequirement>& layers = {},
-                                      GlobalSignals signals = {}) const {
-        return getRankedRefreshRatesAndSignals(layers, signals).first.front().displayModePtr;
-    }
-
-    SetPolicyResult setPolicy(const PolicyVariant& policy) {
-        ftl::FakeGuard guard(kMainThreadContext);
-        return RefreshRateConfigs::setPolicy(policy);
-    }
-
-    SetPolicyResult setDisplayManagerPolicy(const DisplayManagerPolicy& policy) {
-        return setPolicy(policy);
-    }
-};
-
-class RefreshRateConfigsTest : public testing::Test {
-protected:
-    RefreshRateConfigsTest();
-    ~RefreshRateConfigsTest();
-
-    static constexpr DisplayModeId kModeId60{0};
-    static constexpr DisplayModeId kModeId90{1};
-    static constexpr DisplayModeId kModeId72{2};
-    static constexpr DisplayModeId kModeId120{3};
-    static constexpr DisplayModeId kModeId30{4};
-    static constexpr DisplayModeId kModeId25{5};
-    static constexpr DisplayModeId kModeId50{6};
-    static constexpr DisplayModeId kModeId24{7};
-    static constexpr DisplayModeId kModeId24Frac{8};
-    static constexpr DisplayModeId kModeId30Frac{9};
-    static constexpr DisplayModeId kModeId60Frac{10};
-
-    static inline const DisplayModePtr kMode60 = createDisplayMode(kModeId60, 60_Hz);
-    static inline const DisplayModePtr kMode60Frac = createDisplayMode(kModeId60Frac, 59.94_Hz);
-    static inline const DisplayModePtr kMode90 = createDisplayMode(kModeId90, 90_Hz);
-    static inline const DisplayModePtr kMode90_G1 = createDisplayMode(kModeId90, 90_Hz, 1);
-    static inline const DisplayModePtr kMode90_4K =
-            createDisplayMode(kModeId90, 90_Hz, 0, {3840, 2160});
-    static inline const DisplayModePtr kMode72 = createDisplayMode(kModeId72, 72_Hz);
-    static inline const DisplayModePtr kMode72_G1 = createDisplayMode(kModeId72, 72_Hz, 1);
-    static inline const DisplayModePtr kMode120 = createDisplayMode(kModeId120, 120_Hz);
-    static inline const DisplayModePtr kMode120_G1 = createDisplayMode(kModeId120, 120_Hz, 1);
-    static inline const DisplayModePtr kMode30 = createDisplayMode(kModeId30, 30_Hz);
-    static inline const DisplayModePtr kMode30_G1 = createDisplayMode(kModeId30, 30_Hz, 1);
-    static inline const DisplayModePtr kMode30Frac = createDisplayMode(kModeId30Frac, 29.97_Hz);
-    static inline const DisplayModePtr kMode25 = createDisplayMode(kModeId25, 25_Hz);
-    static inline const DisplayModePtr kMode25_G1 = createDisplayMode(kModeId25, 25_Hz, 1);
-    static inline const DisplayModePtr kMode50 = createDisplayMode(kModeId50, 50_Hz);
-    static inline const DisplayModePtr kMode24 = createDisplayMode(kModeId24, 24_Hz);
-    static inline const DisplayModePtr kMode24Frac = createDisplayMode(kModeId24Frac, 23.976_Hz);
-
-    // Test configurations.
-    static inline const DisplayModes kModes_60 = makeModes(kMode60);
-    static inline const DisplayModes kModes_60_90 = makeModes(kMode60, kMode90);
-    static inline const DisplayModes kModes_60_90_G1 = makeModes(kMode60, kMode90_G1);
-    static inline const DisplayModes kModes_60_90_4K = makeModes(kMode60, kMode90_4K);
-    static inline const DisplayModes kModes_60_72_90 = makeModes(kMode60, kMode90, kMode72);
-    static inline const DisplayModes kModes_60_90_72_120 =
-            makeModes(kMode60, kMode90, kMode72, kMode120);
-    static inline const DisplayModes kModes_30_60_72_90_120 =
-            makeModes(kMode60, kMode90, kMode72, kMode120, kMode30);
-
-    static inline const DisplayModes kModes_30_60 =
-            makeModes(kMode60, kMode90_G1, kMode72_G1, kMode120_G1, kMode30);
-    static inline const DisplayModes kModes_30_60_72_90 =
-            makeModes(kMode60, kMode90, kMode72, kMode120_G1, kMode30);
-    static inline const DisplayModes kModes_30_60_90 =
-            makeModes(kMode60, kMode90, kMode72_G1, kMode120_G1, kMode30);
-    static inline const DisplayModes kModes_25_30_50_60 =
-            makeModes(kMode60, kMode90, kMode72_G1, kMode120_G1, kMode30_G1, kMode25_G1, kMode50);
-    static inline const DisplayModes kModes_60_120 = makeModes(kMode60, kMode120);
-
-    // This is a typical TV configuration.
-    static inline const DisplayModes kModes_24_25_30_50_60_Frac =
-            makeModes(kMode24, kMode24Frac, kMode25, kMode30, kMode30Frac, kMode50, kMode60,
-                      kMode60Frac);
-};
-
-RefreshRateConfigsTest::RefreshRateConfigsTest() {
-    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());
-}
-
-RefreshRateConfigsTest::~RefreshRateConfigsTest() {
-    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());
-}
-
-namespace {
-
-TEST_F(RefreshRateConfigsTest, oneMode_canSwitch) {
-    RefreshRateConfigs configs(kModes_60, kModeId60);
-    EXPECT_FALSE(configs.canSwitch());
-}
-
-TEST_F(RefreshRateConfigsTest, invalidPolicy) {
-    TestableRefreshRateConfigs configs(kModes_60, kModeId60);
-
-    EXPECT_EQ(SetPolicyResult::Invalid,
-              configs.setDisplayManagerPolicy({DisplayModeId(10), {60_Hz, 60_Hz}}));
-    EXPECT_EQ(SetPolicyResult::Invalid,
-              configs.setDisplayManagerPolicy({kModeId60, {20_Hz, 40_Hz}}));
-}
-
-TEST_F(RefreshRateConfigsTest, unchangedPolicy) {
-    TestableRefreshRateConfigs configs(kModes_60_90, kModeId60);
-
-    EXPECT_EQ(SetPolicyResult::Changed,
-              configs.setDisplayManagerPolicy({kModeId90, {60_Hz, 90_Hz}}));
-
-    EXPECT_EQ(SetPolicyResult::Unchanged,
-              configs.setDisplayManagerPolicy({kModeId90, {60_Hz, 90_Hz}}));
-
-    // Override to the same policy.
-    EXPECT_EQ(SetPolicyResult::Unchanged,
-              configs.setPolicy(RefreshRateConfigs::OverridePolicy{kModeId90, {60_Hz, 90_Hz}}));
-
-    // Clear override to restore DisplayManagerPolicy.
-    EXPECT_EQ(SetPolicyResult::Unchanged,
-              configs.setPolicy(RefreshRateConfigs::NoOverridePolicy{}));
-
-    EXPECT_EQ(SetPolicyResult::Changed,
-              configs.setDisplayManagerPolicy({kModeId90, {30_Hz, 90_Hz}}));
-}
-
-TEST_F(RefreshRateConfigsTest, twoModes_storesFullRefreshRateMap) {
-    TestableRefreshRateConfigs configs(kModes_60_90, kModeId60);
-
-    const auto minRate = configs.getMinSupportedRefreshRate();
-    const auto performanceRate = configs.getMaxSupportedRefreshRate();
-
-    EXPECT_EQ(kMode60, minRate);
-    EXPECT_EQ(kMode90, performanceRate);
-
-    const auto minRateByPolicy = configs.getMinRefreshRateByPolicy();
-    const auto performanceRateByPolicy = configs.getMaxRefreshRateByPolicy();
-
-    EXPECT_EQ(minRateByPolicy, minRate);
-    EXPECT_EQ(performanceRateByPolicy, performanceRate);
-}
-
-TEST_F(RefreshRateConfigsTest, twoModes_storesFullRefreshRateMap_differentGroups) {
-    TestableRefreshRateConfigs configs(kModes_60_90_G1, kModeId60);
-
-    const auto minRate = configs.getMinRefreshRateByPolicy();
-    const auto performanceRate = configs.getMaxSupportedRefreshRate();
-    const auto minRate60 = configs.getMinRefreshRateByPolicy();
-    const auto performanceRate60 = configs.getMaxRefreshRateByPolicy();
-
-    EXPECT_EQ(kMode60, minRate);
-    EXPECT_EQ(kMode60, minRate60);
-    EXPECT_EQ(kMode60, performanceRate60);
-
-    EXPECT_EQ(SetPolicyResult::Changed,
-              configs.setDisplayManagerPolicy({kModeId90, {60_Hz, 90_Hz}}));
-    configs.setActiveModeId(kModeId90);
-
-    const auto minRate90 = configs.getMinRefreshRateByPolicy();
-    const auto performanceRate90 = configs.getMaxRefreshRateByPolicy();
-
-    EXPECT_EQ(kMode90_G1, performanceRate);
-    EXPECT_EQ(kMode90_G1, minRate90);
-    EXPECT_EQ(kMode90_G1, performanceRate90);
-}
-
-TEST_F(RefreshRateConfigsTest, twoModes_storesFullRefreshRateMap_differentResolutions) {
-    TestableRefreshRateConfigs configs(kModes_60_90_4K, kModeId60);
-
-    const auto minRate = configs.getMinRefreshRateByPolicy();
-    const auto performanceRate = configs.getMaxSupportedRefreshRate();
-    const auto minRate60 = configs.getMinRefreshRateByPolicy();
-    const auto performanceRate60 = configs.getMaxRefreshRateByPolicy();
-
-    EXPECT_EQ(kMode60, minRate);
-    EXPECT_EQ(kMode60, minRate60);
-    EXPECT_EQ(kMode60, performanceRate60);
-
-    EXPECT_EQ(SetPolicyResult::Changed,
-              configs.setDisplayManagerPolicy({kModeId90, {60_Hz, 90_Hz}}));
-    configs.setActiveModeId(kModeId90);
-
-    const auto minRate90 = configs.getMinRefreshRateByPolicy();
-    const auto performanceRate90 = configs.getMaxRefreshRateByPolicy();
-
-    EXPECT_EQ(kMode90_4K, performanceRate);
-    EXPECT_EQ(kMode90_4K, minRate90);
-    EXPECT_EQ(kMode90_4K, performanceRate90);
-}
-
-TEST_F(RefreshRateConfigsTest, twoModes_policyChange) {
-    TestableRefreshRateConfigs configs(kModes_60_90, kModeId60);
-
-    const auto minRate = configs.getMinRefreshRateByPolicy();
-    const auto performanceRate = configs.getMaxRefreshRateByPolicy();
-
-    EXPECT_EQ(kMode60, minRate);
-    EXPECT_EQ(kMode90, performanceRate);
-
-    EXPECT_EQ(SetPolicyResult::Changed,
-              configs.setDisplayManagerPolicy({kModeId60, {60_Hz, 60_Hz}}));
-
-    const auto minRate60 = configs.getMinRefreshRateByPolicy();
-    const auto performanceRate60 = configs.getMaxRefreshRateByPolicy();
-
-    EXPECT_EQ(kMode60, minRate60);
-    EXPECT_EQ(kMode60, performanceRate60);
-}
-
-TEST_F(RefreshRateConfigsTest, twoModes_getActiveMode) {
-    TestableRefreshRateConfigs configs(kModes_60_90, kModeId60);
-    {
-        const auto& mode = configs.getActiveMode();
-        EXPECT_EQ(mode.getId(), kModeId60);
-    }
-
-    configs.setActiveModeId(kModeId90);
-    {
-        const auto& mode = configs.getActiveMode();
-        EXPECT_EQ(mode.getId(), kModeId90);
-    }
-
-    EXPECT_EQ(SetPolicyResult::Changed,
-              configs.setDisplayManagerPolicy({kModeId90, {90_Hz, 90_Hz}}));
-    {
-        const auto& mode = configs.getActiveMode();
-        EXPECT_EQ(mode.getId(), kModeId90);
-    }
-}
-
-TEST_F(RefreshRateConfigsTest, getBestRefreshRate_noLayers) {
-    {
-        TestableRefreshRateConfigs configs(kModes_60_72_90, kModeId72);
-
-        // If there are no layers we select the default frame rate, which is the max of the primary
-        // range.
-        EXPECT_EQ(kMode90, configs.getBestRefreshRate());
-
-        EXPECT_EQ(SetPolicyResult::Changed,
-                  configs.setDisplayManagerPolicy({kModeId60, {60_Hz, 60_Hz}}));
-        EXPECT_EQ(kMode60, configs.getBestRefreshRate());
-    }
-    {
-        // We select max even when this will cause a non-seamless switch.
-        TestableRefreshRateConfigs configs(kModes_60_90_G1, kModeId60);
-        constexpr bool kAllowGroupSwitching = true;
-        EXPECT_EQ(SetPolicyResult::Changed,
-                  configs.setDisplayManagerPolicy(
-                          {kModeId90, kAllowGroupSwitching, {0_Hz, 90_Hz}}));
-        EXPECT_EQ(kMode90_G1, configs.getBestRefreshRate());
-    }
-}
-
-TEST_F(RefreshRateConfigsTest, getBestRefreshRate_exactDontChangeRefreshRateWhenNotInPolicy) {
-    TestableRefreshRateConfigs configs(kModes_30_60_72_90_120, kModeId72);
-
-    std::vector<LayerRequirement> layers = {{.weight = 1.f}};
-    layers[0].vote = LayerVoteType::ExplicitExact;
-    layers[0].desiredRefreshRate = 120_Hz;
-
-    EXPECT_EQ(SetPolicyResult::Changed,
-              configs.setDisplayManagerPolicy({kModeId72, {0_Hz, 90_Hz}}));
-    EXPECT_EQ(kMode72, configs.getBestRefreshRate(layers));
-}
-
-TEST_F(RefreshRateConfigsTest, getBestRefreshRate_60_90) {
-    TestableRefreshRateConfigs configs(kModes_60_90, kModeId60);
-
-    std::vector<LayerRequirement> layers = {{.weight = 1.f}};
-    auto& lr = layers[0];
-
-    lr.vote = LayerVoteType::Min;
-    lr.name = "Min";
-    EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers));
-
-    lr.vote = LayerVoteType::Max;
-    lr.name = "Max";
-    EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
-
-    lr.desiredRefreshRate = 90_Hz;
-    lr.vote = LayerVoteType::Heuristic;
-    lr.name = "90Hz Heuristic";
-    EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
-
-    lr.desiredRefreshRate = 60_Hz;
-    lr.name = "60Hz Heuristic";
-    EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers));
-
-    lr.desiredRefreshRate = 45_Hz;
-    lr.name = "45Hz Heuristic";
-    EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
-
-    lr.desiredRefreshRate = 30_Hz;
-    lr.name = "30Hz Heuristic";
-    EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers));
-
-    lr.desiredRefreshRate = 24_Hz;
-    lr.name = "24Hz Heuristic";
-    EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers));
-
-    lr.name = "";
-    EXPECT_EQ(SetPolicyResult::Changed,
-              configs.setDisplayManagerPolicy({kModeId60, {60_Hz, 60_Hz}}));
-
-    lr.vote = LayerVoteType::Min;
-    EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers));
-
-    lr.vote = LayerVoteType::Max;
-    EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers));
-
-    lr.desiredRefreshRate = 90_Hz;
-    lr.vote = LayerVoteType::Heuristic;
-    EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers));
-
-    lr.desiredRefreshRate = 60_Hz;
-    EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers));
-
-    lr.desiredRefreshRate = 45_Hz;
-    EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers));
-
-    lr.desiredRefreshRate = 30_Hz;
-    EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers));
-
-    lr.desiredRefreshRate = 24_Hz;
-    EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers));
-
-    EXPECT_EQ(SetPolicyResult::Changed,
-              configs.setDisplayManagerPolicy({kModeId90, {90_Hz, 90_Hz}}));
-
-    lr.vote = LayerVoteType::Min;
-    EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
-
-    lr.vote = LayerVoteType::Max;
-    EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
-
-    lr.desiredRefreshRate = 90_Hz;
-    lr.vote = LayerVoteType::Heuristic;
-    EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
-
-    lr.desiredRefreshRate = 60_Hz;
-    EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
-
-    lr.desiredRefreshRate = 45_Hz;
-    EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
-
-    lr.desiredRefreshRate = 30_Hz;
-    EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
-
-    lr.desiredRefreshRate = 24_Hz;
-    EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
-
-    EXPECT_EQ(SetPolicyResult::Changed,
-              configs.setDisplayManagerPolicy({kModeId60, {0_Hz, 120_Hz}}));
-    lr.vote = LayerVoteType::Min;
-    EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers));
-
-    lr.vote = LayerVoteType::Max;
-    EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
-
-    lr.desiredRefreshRate = 90_Hz;
-    lr.vote = LayerVoteType::Heuristic;
-    EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
-
-    lr.desiredRefreshRate = 60_Hz;
-    EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers));
-
-    lr.desiredRefreshRate = 45_Hz;
-    EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
-
-    lr.desiredRefreshRate = 30_Hz;
-    EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers));
-
-    lr.desiredRefreshRate = 24_Hz;
-    EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers));
-}
-
-TEST_F(RefreshRateConfigsTest, getBestRefreshRate_multipleThreshold_60_90) {
-    TestableRefreshRateConfigs configs(kModes_60_90, kModeId60, {.frameRateMultipleThreshold = 90});
-
-    std::vector<LayerRequirement> layers = {{.weight = 1.f}};
-    auto& lr = layers[0];
-
-    lr.vote = LayerVoteType::Min;
-    lr.name = "Min";
-    EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers));
-
-    lr.vote = LayerVoteType::Max;
-    lr.name = "Max";
-    EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
-
-    lr.desiredRefreshRate = 90_Hz;
-    lr.vote = LayerVoteType::Heuristic;
-    lr.name = "90Hz Heuristic";
-    EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
-
-    lr.desiredRefreshRate = 60_Hz;
-    lr.name = "60Hz Heuristic";
-    EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers));
-
-    lr.desiredRefreshRate = 45_Hz;
-    lr.name = "45Hz Heuristic";
-    EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
-
-    lr.desiredRefreshRate = 30_Hz;
-    lr.name = "30Hz Heuristic";
-    EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers));
-
-    lr.desiredRefreshRate = 24_Hz;
-    lr.name = "24Hz Heuristic";
-    EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers));
-}
-
-TEST_F(RefreshRateConfigsTest, getBestRefreshRate_60_72_90) {
-    TestableRefreshRateConfigs configs(kModes_60_72_90, kModeId60);
-
-    std::vector<LayerRequirement> layers = {{.weight = 1.f}};
-    auto& lr = layers[0];
-
-    lr.vote = LayerVoteType::Min;
-    EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers));
-
-    lr.vote = LayerVoteType::Max;
-    EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
-
-    lr.desiredRefreshRate = 90_Hz;
-    lr.vote = LayerVoteType::Heuristic;
-    EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
-
-    lr.desiredRefreshRate = 60_Hz;
-    EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers));
-
-    lr.desiredRefreshRate = 45_Hz;
-    EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
-
-    lr.desiredRefreshRate = 30_Hz;
-    EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers));
-
-    lr.desiredRefreshRate = 24_Hz;
-    EXPECT_EQ(kMode72, configs.getBestRefreshRate(layers));
-}
-
-TEST_F(RefreshRateConfigsTest, getBestRefreshRate_30_60_72_90_120) {
-    TestableRefreshRateConfigs configs(kModes_30_60_72_90_120, kModeId60);
-
-    std::vector<LayerRequirement> layers = {{.weight = 1.f}, {.weight = 1.f}};
-    auto& lr1 = layers[0];
-    auto& lr2 = layers[1];
-
-    lr1.desiredRefreshRate = 24_Hz;
-    lr1.vote = LayerVoteType::Heuristic;
-    lr2.desiredRefreshRate = 60_Hz;
-    lr2.vote = LayerVoteType::Heuristic;
-    EXPECT_EQ(kMode120, configs.getBestRefreshRate(layers));
-
-    lr1.desiredRefreshRate = 24_Hz;
-    lr1.vote = LayerVoteType::Heuristic;
-    lr2.desiredRefreshRate = 48_Hz;
-    lr2.vote = LayerVoteType::Heuristic;
-    EXPECT_EQ(kMode72, configs.getBestRefreshRate(layers));
-
-    lr1.desiredRefreshRate = 24_Hz;
-    lr1.vote = LayerVoteType::Heuristic;
-    lr2.desiredRefreshRate = 48_Hz;
-    lr2.vote = LayerVoteType::Heuristic;
-    EXPECT_EQ(kMode72, configs.getBestRefreshRate(layers));
-}
-
-TEST_F(RefreshRateConfigsTest, getBestRefreshRate_30_60_90_120_DifferentTypes) {
-    TestableRefreshRateConfigs configs(kModes_30_60_72_90_120, kModeId60);
-
-    std::vector<LayerRequirement> layers = {{.weight = 1.f}, {.weight = 1.f}};
-    auto& lr1 = layers[0];
-    auto& lr2 = layers[1];
-
-    lr1.desiredRefreshRate = 24_Hz;
-    lr1.vote = LayerVoteType::ExplicitDefault;
-    lr1.name = "24Hz ExplicitDefault";
-    lr2.desiredRefreshRate = 60_Hz;
-    lr2.vote = LayerVoteType::Heuristic;
-    lr2.name = "60Hz Heuristic";
-    EXPECT_EQ(kMode120, configs.getBestRefreshRate(layers));
-
-    lr1.desiredRefreshRate = 24_Hz;
-    lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
-    lr1.name = "24Hz ExplicitExactOrMultiple";
-    lr2.desiredRefreshRate = 60_Hz;
-    lr2.vote = LayerVoteType::Heuristic;
-    lr2.name = "60Hz Heuristic";
-    EXPECT_EQ(kMode120, configs.getBestRefreshRate(layers));
-
-    lr1.desiredRefreshRate = 24_Hz;
-    lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
-    lr1.name = "24Hz ExplicitExactOrMultiple";
-    lr2.desiredRefreshRate = 60_Hz;
-    lr2.vote = LayerVoteType::ExplicitDefault;
-    lr2.name = "60Hz ExplicitDefault";
-    EXPECT_EQ(kMode120, configs.getBestRefreshRate(layers));
-
-    lr1.desiredRefreshRate = 24_Hz;
-    lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
-    lr1.name = "24Hz ExplicitExactOrMultiple";
-    lr2.desiredRefreshRate = 90_Hz;
-    lr2.vote = LayerVoteType::Heuristic;
-    lr2.name = "90Hz Heuristic";
-    EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
-
-    lr1.desiredRefreshRate = 24_Hz;
-    lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
-    lr1.name = "24Hz ExplicitExactOrMultiple";
-    lr2.desiredRefreshRate = 90_Hz;
-    lr2.vote = LayerVoteType::ExplicitDefault;
-    lr2.name = "90Hz Heuristic";
-    EXPECT_EQ(kMode72, configs.getBestRefreshRate(layers));
-
-    lr1.desiredRefreshRate = 24_Hz;
-    lr1.vote = LayerVoteType::ExplicitDefault;
-    lr1.name = "24Hz ExplicitDefault";
-    lr2.desiredRefreshRate = 90_Hz;
-    lr2.vote = LayerVoteType::Heuristic;
-    lr2.name = "90Hz Heuristic";
-    EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
-
-    lr1.desiredRefreshRate = 24_Hz;
-    lr1.vote = LayerVoteType::Heuristic;
-    lr1.name = "24Hz Heuristic";
-    lr2.desiredRefreshRate = 90_Hz;
-    lr2.vote = LayerVoteType::ExplicitDefault;
-    lr2.name = "90Hz ExplicitDefault";
-    EXPECT_EQ(kMode72, configs.getBestRefreshRate(layers));
-
-    lr1.desiredRefreshRate = 24_Hz;
-    lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
-    lr1.name = "24Hz ExplicitExactOrMultiple";
-    lr2.desiredRefreshRate = 90_Hz;
-    lr2.vote = LayerVoteType::ExplicitDefault;
-    lr2.name = "90Hz ExplicitDefault";
-    EXPECT_EQ(kMode72, configs.getBestRefreshRate(layers));
-
-    lr1.desiredRefreshRate = 24_Hz;
-    lr1.vote = LayerVoteType::ExplicitDefault;
-    lr1.name = "24Hz ExplicitDefault";
-    lr2.desiredRefreshRate = 90_Hz;
-    lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
-    lr2.name = "90Hz ExplicitExactOrMultiple";
-    EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
-}
-
-TEST_F(RefreshRateConfigsTest, getBestRefreshRate_30_60_90_120_DifferentTypes_multipleThreshold) {
-    TestableRefreshRateConfigs configs(kModes_30_60_72_90_120, kModeId60,
-                                       {.frameRateMultipleThreshold = 120});
-
-    std::vector<LayerRequirement> layers = {{.weight = 1.f}, {.weight = 1.f}, {.weight = 1.f}};
-    auto& lr1 = layers[0];
-    auto& lr2 = layers[1];
-    auto& lr3 = layers[2];
-
-    lr1.desiredRefreshRate = 24_Hz;
-    lr1.vote = LayerVoteType::ExplicitDefault;
-    lr1.name = "24Hz ExplicitDefault";
-    lr2.desiredRefreshRate = 60_Hz;
-    lr2.vote = LayerVoteType::Heuristic;
-    lr2.name = "60Hz Heuristic";
-    EXPECT_EQ(kMode120, configs.getBestRefreshRate(layers));
-
-    lr1.desiredRefreshRate = 24_Hz;
-    lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
-    lr1.name = "24Hz ExplicitExactOrMultiple";
-    lr2.desiredRefreshRate = 60_Hz;
-    lr2.vote = LayerVoteType::Heuristic;
-    lr2.name = "60Hz Heuristic";
-    EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers));
-
-    lr1.desiredRefreshRate = 24_Hz;
-    lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
-    lr1.name = "24Hz ExplicitExactOrMultiple";
-    lr2.desiredRefreshRate = 60_Hz;
-    lr2.vote = LayerVoteType::ExplicitDefault;
-    lr2.name = "60Hz ExplicitDefault";
-    EXPECT_EQ(kMode72, configs.getBestRefreshRate(layers));
-
-    lr1.desiredRefreshRate = 24_Hz;
-    lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
-    lr1.name = "24Hz ExplicitExactOrMultiple";
-    lr2.desiredRefreshRate = 90_Hz;
-    lr2.vote = LayerVoteType::Heuristic;
-    lr2.name = "90Hz Heuristic";
-    EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
-
-    lr1.desiredRefreshRate = 24_Hz;
-    lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
-    lr1.name = "24Hz ExplicitExactOrMultiple";
-    lr2.desiredRefreshRate = 90_Hz;
-    lr2.vote = LayerVoteType::ExplicitDefault;
-    lr2.name = "90Hz Heuristic";
-    EXPECT_EQ(kMode72, configs.getBestRefreshRate(layers));
-
-    lr1.desiredRefreshRate = 24_Hz;
-    lr1.vote = LayerVoteType::ExplicitDefault;
-    lr1.name = "24Hz ExplicitDefault";
-    lr2.desiredRefreshRate = 90_Hz;
-    lr2.vote = LayerVoteType::Heuristic;
-    lr2.name = "90Hz Heuristic";
-    EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
-
-    lr1.desiredRefreshRate = 24_Hz;
-    lr1.vote = LayerVoteType::Heuristic;
-    lr1.name = "24Hz Heuristic";
-    lr2.desiredRefreshRate = 90_Hz;
-    lr2.vote = LayerVoteType::ExplicitDefault;
-    lr2.name = "90Hz ExplicitDefault";
-    EXPECT_EQ(kMode72, configs.getBestRefreshRate(layers));
-
-    lr1.desiredRefreshRate = 24_Hz;
-    lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
-    lr1.name = "24Hz ExplicitExactOrMultiple";
-    lr2.desiredRefreshRate = 90_Hz;
-    lr2.vote = LayerVoteType::ExplicitDefault;
-    lr2.name = "90Hz ExplicitDefault";
-    EXPECT_EQ(kMode72, configs.getBestRefreshRate(layers));
-
-    lr1.desiredRefreshRate = 24_Hz;
-    lr1.vote = LayerVoteType::ExplicitDefault;
-    lr1.name = "24Hz ExplicitDefault";
-    lr2.desiredRefreshRate = 90_Hz;
-    lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
-    lr2.name = "90Hz ExplicitExactOrMultiple";
-    EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
-
-    lr1.desiredRefreshRate = 24_Hz;
-    lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
-    lr1.name = "24Hz ExplicitExactOrMultiple";
-    lr2.vote = LayerVoteType::Max;
-    lr2.name = "Max";
-    EXPECT_EQ(kMode120, configs.getBestRefreshRate(layers));
-
-    lr1.desiredRefreshRate = 24_Hz;
-    lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
-    lr1.name = "24Hz ExplicitExactOrMultiple";
-    lr2.desiredRefreshRate = 120_Hz;
-    lr2.vote = LayerVoteType::ExplicitDefault;
-    lr2.name = "120Hz ExplicitDefault";
-    EXPECT_EQ(kMode120, configs.getBestRefreshRate(layers));
-
-    lr1.desiredRefreshRate = 24_Hz;
-    lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
-    lr1.name = "24Hz ExplicitExactOrMultiple";
-    lr2.desiredRefreshRate = 120_Hz;
-    lr2.vote = LayerVoteType::ExplicitExact;
-    lr2.name = "120Hz ExplicitExact";
-    EXPECT_EQ(kMode120, configs.getBestRefreshRate(layers));
-
-    lr1.desiredRefreshRate = 10_Hz;
-    lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
-    lr1.name = "30Hz ExplicitExactOrMultiple";
-    lr2.desiredRefreshRate = 120_Hz;
-    lr2.vote = LayerVoteType::Heuristic;
-    lr2.name = "120Hz ExplicitExact";
-    EXPECT_EQ(kMode120, configs.getBestRefreshRate(layers));
-
-    lr1.desiredRefreshRate = 30_Hz;
-    lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
-    lr1.name = "30Hz ExplicitExactOrMultiple";
-    lr2.desiredRefreshRate = 30_Hz;
-    lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
-    lr2.name = "30Hz ExplicitExactOrMultiple";
-    lr3.vote = LayerVoteType::Heuristic;
-    lr3.desiredRefreshRate = 120_Hz;
-    lr3.name = "120Hz Heuristic";
-    EXPECT_EQ(kMode120, configs.getBestRefreshRate(layers));
-}
-
-TEST_F(RefreshRateConfigsTest, getBestRefreshRate_30_60) {
-    TestableRefreshRateConfigs configs(kModes_30_60, kModeId60);
-
-    std::vector<LayerRequirement> layers = {{.weight = 1.f}};
-    auto& lr = layers[0];
-
-    lr.vote = LayerVoteType::Min;
-    EXPECT_EQ(kMode30, configs.getBestRefreshRate(layers));
-
-    lr.vote = LayerVoteType::Max;
-    EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers));
-
-    lr.desiredRefreshRate = 90_Hz;
-    lr.vote = LayerVoteType::Heuristic;
-    EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers));
-
-    lr.desiredRefreshRate = 60_Hz;
-    EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers));
-
-    lr.desiredRefreshRate = 45_Hz;
-    EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers));
-
-    lr.desiredRefreshRate = 30_Hz;
-    EXPECT_EQ(kMode30, configs.getBestRefreshRate(layers));
-
-    lr.desiredRefreshRate = 24_Hz;
-    EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers));
-}
-
-TEST_F(RefreshRateConfigsTest, getBestRefreshRate_30_60_72_90) {
-    TestableRefreshRateConfigs configs(kModes_30_60_72_90, kModeId60);
-
-    std::vector<LayerRequirement> layers = {{.weight = 1.f}};
-    auto& lr = layers[0];
-
-    lr.vote = LayerVoteType::Min;
-    lr.name = "Min";
-    EXPECT_EQ(kMode30, configs.getBestRefreshRate(layers));
-
-    lr.vote = LayerVoteType::Max;
-    lr.name = "Max";
-    EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
-
-    lr.desiredRefreshRate = 90_Hz;
-    lr.vote = LayerVoteType::Heuristic;
-    lr.name = "90Hz Heuristic";
-    EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
-
-    lr.desiredRefreshRate = 60_Hz;
-    lr.name = "60Hz Heuristic";
-    EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers));
-    EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers, {.touch = true}));
-
-    lr.desiredRefreshRate = 45_Hz;
-    lr.name = "45Hz Heuristic";
-    EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
-    EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers, {.touch = true}));
-
-    lr.desiredRefreshRate = 30_Hz;
-    lr.name = "30Hz Heuristic";
-    EXPECT_EQ(kMode30, configs.getBestRefreshRate(layers));
-    EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers, {.touch = true}));
-
-    lr.desiredRefreshRate = 24_Hz;
-    lr.name = "24Hz Heuristic";
-    EXPECT_EQ(kMode72, configs.getBestRefreshRate(layers));
-    EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers, {.touch = true}));
-
-    lr.desiredRefreshRate = 24_Hz;
-    lr.vote = LayerVoteType::ExplicitExactOrMultiple;
-    lr.name = "24Hz ExplicitExactOrMultiple";
-    EXPECT_EQ(kMode72, configs.getBestRefreshRate(layers));
-    EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers, {.touch = true}));
-}
-
-TEST_F(RefreshRateConfigsTest, getBestRefreshRate_PriorityTest) {
-    TestableRefreshRateConfigs configs(kModes_30_60_90, kModeId60);
-
-    std::vector<LayerRequirement> layers = {{.weight = 1.f}, {.weight = 1.f}};
-    auto& lr1 = layers[0];
-    auto& lr2 = layers[1];
-
-    lr1.vote = LayerVoteType::Min;
-    lr2.vote = LayerVoteType::Max;
-    EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
-
-    lr1.vote = LayerVoteType::Min;
-    lr2.vote = LayerVoteType::Heuristic;
-    lr2.desiredRefreshRate = 24_Hz;
-    EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers));
-
-    lr1.vote = LayerVoteType::Min;
-    lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
-    lr2.desiredRefreshRate = 24_Hz;
-    EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers));
-
-    lr1.vote = LayerVoteType::Max;
-    lr2.vote = LayerVoteType::Heuristic;
-    lr2.desiredRefreshRate = 60_Hz;
-    EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
-
-    lr1.vote = LayerVoteType::Max;
-    lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
-    lr2.desiredRefreshRate = 60_Hz;
-    EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
-
-    lr1.vote = LayerVoteType::Heuristic;
-    lr1.desiredRefreshRate = 15_Hz;
-    lr2.vote = LayerVoteType::Heuristic;
-    lr2.desiredRefreshRate = 45_Hz;
-    EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
-
-    lr1.vote = LayerVoteType::Heuristic;
-    lr1.desiredRefreshRate = 30_Hz;
-    lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
-    lr2.desiredRefreshRate = 45_Hz;
-    EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
-}
-
-TEST_F(RefreshRateConfigsTest, getBestRefreshRate_24FpsVideo) {
-    TestableRefreshRateConfigs configs(kModes_60_90, kModeId60);
-
-    std::vector<LayerRequirement> layers = {{.weight = 1.f}};
-    auto& lr = layers[0];
-
-    lr.vote = LayerVoteType::ExplicitExactOrMultiple;
-    for (float fps = 23.0f; fps < 25.0f; fps += 0.1f) {
-        lr.desiredRefreshRate = Fps::fromValue(fps);
-        const auto mode = configs.getBestRefreshRate(layers);
-        EXPECT_EQ(kMode60, mode) << lr.desiredRefreshRate << " chooses "
-                                 << to_string(mode->getFps());
-    }
-}
-
-TEST_F(RefreshRateConfigsTest, getBestRefreshRate_24FpsVideo_multipleThreshold_60_120) {
-    TestableRefreshRateConfigs configs(kModes_60_120, kModeId60,
-                                       {.frameRateMultipleThreshold = 120});
-
-    std::vector<LayerRequirement> layers = {{.weight = 1.f}};
-    auto& lr = layers[0];
-
-    lr.vote = LayerVoteType::ExplicitExactOrMultiple;
-    for (float fps = 23.0f; fps < 25.0f; fps += 0.1f) {
-        lr.desiredRefreshRate = Fps::fromValue(fps);
-        const auto mode = configs.getBestRefreshRate(layers);
-        EXPECT_EQ(kMode60, mode) << lr.desiredRefreshRate << " chooses "
-                                 << to_string(mode->getFps());
-    }
-}
-
-TEST_F(RefreshRateConfigsTest, twoModes_getBestRefreshRate_Explicit) {
-    TestableRefreshRateConfigs configs(kModes_60_90, kModeId60);
-
-    std::vector<LayerRequirement> layers = {{.weight = 1.f}, {.weight = 1.f}};
-    auto& lr1 = layers[0];
-    auto& lr2 = layers[1];
-
-    lr1.vote = LayerVoteType::Heuristic;
-    lr1.desiredRefreshRate = 60_Hz;
-    lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
-    lr2.desiredRefreshRate = 90_Hz;
-    EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
-
-    lr1.vote = LayerVoteType::ExplicitDefault;
-    lr1.desiredRefreshRate = 90_Hz;
-    lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
-    lr2.desiredRefreshRate = 60_Hz;
-    EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers));
-
-    lr1.vote = LayerVoteType::Heuristic;
-    lr1.desiredRefreshRate = 90_Hz;
-    lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
-    lr2.desiredRefreshRate = 60_Hz;
-    EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
-}
-
-TEST_F(RefreshRateConfigsTest, getBestRefreshRate_75HzContent) {
-    TestableRefreshRateConfigs configs(kModes_60_90, kModeId60);
-
-    std::vector<LayerRequirement> layers = {{.weight = 1.f}};
-    auto& lr = layers[0];
-
-    lr.vote = LayerVoteType::ExplicitExactOrMultiple;
-    for (float fps = 75.0f; fps < 100.0f; fps += 0.1f) {
-        lr.desiredRefreshRate = Fps::fromValue(fps);
-        const auto mode = configs.getBestRefreshRate(layers, {});
-        EXPECT_EQ(kMode90, mode) << lr.desiredRefreshRate << " chooses "
-                                 << to_string(mode->getFps());
-    }
-}
-
-TEST_F(RefreshRateConfigsTest, getBestRefreshRate_Multiples) {
-    TestableRefreshRateConfigs configs(kModes_60_90, kModeId60);
-
-    std::vector<LayerRequirement> layers = {{.weight = 1.f}, {.weight = 1.f}};
-    auto& lr1 = layers[0];
-    auto& lr2 = layers[1];
-
-    lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
-    lr1.desiredRefreshRate = 60_Hz;
-    lr1.name = "60Hz ExplicitExactOrMultiple";
-    lr2.vote = LayerVoteType::Heuristic;
-    lr2.desiredRefreshRate = 90_Hz;
-    lr2.name = "90Hz Heuristic";
-    EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
-
-    lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
-    lr1.desiredRefreshRate = 60_Hz;
-    lr1.name = "60Hz ExplicitExactOrMultiple";
-    lr2.vote = LayerVoteType::ExplicitDefault;
-    lr2.desiredRefreshRate = 90_Hz;
-    lr2.name = "90Hz ExplicitDefault";
-    EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers));
-
-    lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
-    lr1.desiredRefreshRate = 60_Hz;
-    lr1.name = "60Hz ExplicitExactOrMultiple";
-    lr2.vote = LayerVoteType::Max;
-    lr2.name = "Max";
-    EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
-
-    lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
-    lr1.desiredRefreshRate = 30_Hz;
-    lr1.name = "30Hz ExplicitExactOrMultiple";
-    lr2.vote = LayerVoteType::Heuristic;
-    lr2.desiredRefreshRate = 90_Hz;
-    lr2.name = "90Hz Heuristic";
-    EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
-
-    lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
-    lr1.desiredRefreshRate = 30_Hz;
-    lr1.name = "30Hz ExplicitExactOrMultiple";
-    lr2.vote = LayerVoteType::Max;
-    lr2.name = "Max";
-    EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
-}
-
-TEST_F(RefreshRateConfigsTest, scrollWhileWatching60fps_60_90) {
-    TestableRefreshRateConfigs configs(kModes_60_90, kModeId60);
-
-    std::vector<LayerRequirement> layers = {{.weight = 1.f}, {.weight = 1.f}};
-    auto& lr1 = layers[0];
-    auto& lr2 = layers[1];
-
-    lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
-    lr1.desiredRefreshRate = 60_Hz;
-    lr1.name = "60Hz ExplicitExactOrMultiple";
-    lr2.vote = LayerVoteType::NoVote;
-    lr2.name = "NoVote";
-    EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers));
-
-    lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
-    lr1.desiredRefreshRate = 60_Hz;
-    lr1.name = "60Hz ExplicitExactOrMultiple";
-    lr2.vote = LayerVoteType::NoVote;
-    lr2.name = "NoVote";
-    EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers, {.touch = true}));
-
-    lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
-    lr1.desiredRefreshRate = 60_Hz;
-    lr1.name = "60Hz ExplicitExactOrMultiple";
-    lr2.vote = LayerVoteType::Max;
-    lr2.name = "Max";
-    EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers, {.touch = true}));
-
-    lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
-    lr1.desiredRefreshRate = 60_Hz;
-    lr1.name = "60Hz ExplicitExactOrMultiple";
-    lr2.vote = LayerVoteType::Max;
-    lr2.name = "Max";
-    EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
-
-    // The other layer starts to provide buffers
-    lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
-    lr1.desiredRefreshRate = 60_Hz;
-    lr1.name = "60Hz ExplicitExactOrMultiple";
-    lr2.vote = LayerVoteType::Heuristic;
-    lr2.desiredRefreshRate = 90_Hz;
-    lr2.name = "90Hz Heuristic";
-    EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
-}
-
-TEST_F(RefreshRateConfigsTest, getMaxRefreshRatesByPolicy) {
-    // The kModes_30_60_90 contains two kMode72_G1, kMode120_G1 which are from the
-    // different group.
-    TestableRefreshRateConfigs configs(kModes_30_60_90, kModeId60);
-    const std::vector<RefreshRateRanking>& expectedRefreshRates = {RefreshRateRanking{kMode90},
-                                                                   RefreshRateRanking{kMode60},
-                                                                   RefreshRateRanking{kMode30}};
-
-    const std::vector<RefreshRateRanking>& refreshRates =
-            configs.getRefreshRatesByPolicy(configs.getActiveMode().getGroup(),
-                                            TestableRefreshRateConfigs::RefreshRateOrder::
-                                                    Descending);
-
-    ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size());
-    for (size_t i = 0; i < expectedRefreshRates.size(); ++i) {
-        EXPECT_EQ(expectedRefreshRates[i].displayModePtr, refreshRates[i].displayModePtr)
-                << "Expected fps " << expectedRefreshRates[i].displayModePtr->getFps().getIntValue()
-                << " Actual fps " << refreshRates[i].displayModePtr->getFps().getIntValue();
-    }
-}
-
-TEST_F(RefreshRateConfigsTest, getMinRefreshRatesByPolicy) {
-    // The kModes_30_60_90 contains two kMode72_G1, kMode120_G1 which are from the
-    // different group.
-    TestableRefreshRateConfigs configs(kModes_30_60_90, kModeId60);
-    const std::vector<RefreshRateRanking>& expectedRefreshRates = {RefreshRateRanking{kMode30},
-                                                                   RefreshRateRanking{kMode60},
-                                                                   RefreshRateRanking{kMode90}};
-
-    const std::vector<RefreshRateRanking>& refreshRates =
-            configs.getRefreshRatesByPolicy(configs.getActiveMode().getGroup(),
-                                            TestableRefreshRateConfigs::RefreshRateOrder::
-                                                    Ascending);
-
-    ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size());
-    for (size_t i = 0; i < expectedRefreshRates.size(); ++i) {
-        EXPECT_EQ(expectedRefreshRates[i].displayModePtr, refreshRates[i].displayModePtr)
-                << "Expected fps " << expectedRefreshRates[i].displayModePtr->getFps().getIntValue()
-                << " Actual fps " << refreshRates[i].displayModePtr->getFps().getIntValue();
-    }
-}
-
-TEST_F(RefreshRateConfigsTest, getMinRefreshRatesByPolicyOutsideTheGroup) {
-    // The kModes_30_60_90 contains two kMode72_G1, kMode120_G1 which are from the
-    // different group.
-    TestableRefreshRateConfigs configs(kModes_30_60_90, kModeId72);
-    const std::vector<RefreshRateRanking>& expectedRefreshRates = {RefreshRateRanking{kMode30},
-                                                                   RefreshRateRanking{kMode60},
-                                                                   RefreshRateRanking{kMode90}};
-
-    EXPECT_EQ(SetPolicyResult::Changed,
-              configs.setDisplayManagerPolicy({kModeId60, {30_Hz, 90_Hz}, {30_Hz, 90_Hz}}));
-
-    const std::vector<RefreshRateRanking>& refreshRates =
-            configs.getRefreshRatesByPolicy(/*anchorGroupOpt*/ std::nullopt,
-                                            TestableRefreshRateConfigs::RefreshRateOrder::
-                                                    Ascending);
-
-    ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size());
-    for (size_t i = 0; i < expectedRefreshRates.size(); ++i) {
-        EXPECT_EQ(expectedRefreshRates[i].displayModePtr, refreshRates[i].displayModePtr)
-                << "Expected fps " << expectedRefreshRates[i].displayModePtr->getFps().getIntValue()
-                << " Actual fps " << refreshRates[i].displayModePtr->getFps().getIntValue();
-    }
-}
-
-TEST_F(RefreshRateConfigsTest, getMaxRefreshRatesByPolicyOutsideTheGroup) {
-    // The kModes_30_60_90 contains two kMode72_G1, kMode120_G1 which are from the
-    // different group.
-    TestableRefreshRateConfigs configs(kModes_30_60_90, kModeId72);
-    const std::vector<RefreshRateRanking>& expectedRefreshRates = {RefreshRateRanking{kMode90},
-                                                                   RefreshRateRanking{kMode60},
-                                                                   RefreshRateRanking{kMode30}};
-
-    EXPECT_EQ(SetPolicyResult::Changed,
-              configs.setDisplayManagerPolicy({kModeId60, {30_Hz, 90_Hz}, {30_Hz, 90_Hz}}));
-
-    const std::vector<RefreshRateRanking>& refreshRates =
-            configs.getRefreshRatesByPolicy(/*anchorGroupOpt*/ std::nullopt,
-                                            TestableRefreshRateConfigs::RefreshRateOrder::
-                                                    Descending);
-
-    ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size());
-    for (size_t i = 0; i < expectedRefreshRates.size(); ++i) {
-        EXPECT_EQ(expectedRefreshRates[i].displayModePtr, refreshRates[i].displayModePtr)
-                << "Expected fps " << expectedRefreshRates[i].displayModePtr->getFps().getIntValue()
-                << " Actual fps " << refreshRates[i].displayModePtr->getFps().getIntValue();
-    }
-}
-
-TEST_F(RefreshRateConfigsTest, powerOnImminentConsidered) {
-    RefreshRateConfigs configs(kModes_60_90, kModeId60);
-    std::vector<RefreshRateRanking> expectedRefreshRates = {RefreshRateRanking{kMode90},
-                                                            RefreshRateRanking{kMode60}};
-
-    auto [refreshRates, signals] = configs.getRankedRefreshRates({}, {});
-    EXPECT_FALSE(signals.powerOnImminent);
-    ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size());
-    for (size_t i = 0; i < expectedRefreshRates.size(); ++i) {
-        EXPECT_EQ(expectedRefreshRates[i].displayModePtr, refreshRates[i].displayModePtr)
-                << "Expected fps " << expectedRefreshRates[i].displayModePtr->getFps().getIntValue()
-                << " Actual fps " << refreshRates[i].displayModePtr->getFps().getIntValue();
-    }
-
-    std::tie(refreshRates, signals) = configs.getRankedRefreshRates({}, {.powerOnImminent = true});
-    EXPECT_TRUE(signals.powerOnImminent);
-    ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size());
-    for (size_t i = 0; i < expectedRefreshRates.size(); ++i) {
-        EXPECT_EQ(expectedRefreshRates[i].displayModePtr, refreshRates[i].displayModePtr)
-                << "Expected fps " << expectedRefreshRates[i].displayModePtr->getFps().getIntValue()
-                << " Actual fps " << refreshRates[i].displayModePtr->getFps().getIntValue();
-    }
-
-    std::vector<LayerRequirement> layers = {{.weight = 1.f}};
-    auto& lr1 = layers[0];
-    lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
-    lr1.desiredRefreshRate = 60_Hz;
-    lr1.name = "60Hz ExplicitExactOrMultiple";
-
-    std::tie(refreshRates, signals) =
-            configs.getRankedRefreshRates(layers, {.powerOnImminent = true});
-    EXPECT_TRUE(signals.powerOnImminent);
-    ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size());
-    for (size_t i = 0; i < expectedRefreshRates.size(); ++i) {
-        EXPECT_EQ(expectedRefreshRates[i].displayModePtr, refreshRates[i].displayModePtr)
-                << "Expected fps " << expectedRefreshRates[i].displayModePtr->getFps().getIntValue()
-                << " Actual fps " << refreshRates[i].displayModePtr->getFps().getIntValue();
-    }
-
-    expectedRefreshRates = {RefreshRateRanking{kMode60}, RefreshRateRanking{kMode90}};
-    std::tie(refreshRates, signals) =
-            configs.getRankedRefreshRates(layers, {.powerOnImminent = false});
-    EXPECT_FALSE(signals.powerOnImminent);
-    ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size());
-    for (size_t i = 0; i < expectedRefreshRates.size(); ++i) {
-        EXPECT_EQ(expectedRefreshRates[i].displayModePtr, refreshRates[i].displayModePtr)
-                << "Expected fps " << expectedRefreshRates[i].displayModePtr->getFps().getIntValue()
-                << " Actual fps " << refreshRates[i].displayModePtr->getFps().getIntValue();
-    }
-}
-
-TEST_F(RefreshRateConfigsTest, touchConsidered) {
-    RefreshRateConfigs configs(kModes_60_90, kModeId60);
-
-    auto [_, signals] = configs.getRankedRefreshRates({}, {});
-    EXPECT_FALSE(signals.touch);
-
-    std::tie(std::ignore, signals) = configs.getRankedRefreshRates({}, {.touch = true});
-    EXPECT_TRUE(signals.touch);
-
-    std::vector<LayerRequirement> layers = {{.weight = 1.f}, {.weight = 1.f}};
-    auto& lr1 = layers[0];
-    auto& lr2 = layers[1];
-
-    lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
-    lr1.desiredRefreshRate = 60_Hz;
-    lr1.name = "60Hz ExplicitExactOrMultiple";
-    lr2.vote = LayerVoteType::Heuristic;
-    lr2.desiredRefreshRate = 60_Hz;
-    lr2.name = "60Hz Heuristic";
-    std::tie(std::ignore, signals) = configs.getRankedRefreshRates(layers, {.touch = true});
-    EXPECT_TRUE(signals.touch);
-
-    lr1.vote = LayerVoteType::ExplicitDefault;
-    lr1.desiredRefreshRate = 60_Hz;
-    lr1.name = "60Hz ExplicitDefault";
-    lr2.vote = LayerVoteType::Heuristic;
-    lr2.desiredRefreshRate = 60_Hz;
-    lr2.name = "60Hz Heuristic";
-    std::tie(std::ignore, signals) = configs.getRankedRefreshRates(layers, {.touch = true});
-    EXPECT_FALSE(signals.touch);
-
-    lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
-    lr1.desiredRefreshRate = 60_Hz;
-    lr1.name = "60Hz ExplicitExactOrMultiple";
-    lr2.vote = LayerVoteType::Heuristic;
-    lr2.desiredRefreshRate = 60_Hz;
-    lr2.name = "60Hz Heuristic";
-    std::tie(std::ignore, signals) = configs.getRankedRefreshRates(layers, {.touch = true});
-    EXPECT_TRUE(signals.touch);
-
-    lr1.vote = LayerVoteType::ExplicitDefault;
-    lr1.desiredRefreshRate = 60_Hz;
-    lr1.name = "60Hz ExplicitDefault";
-    lr2.vote = LayerVoteType::Heuristic;
-    lr2.desiredRefreshRate = 60_Hz;
-    lr2.name = "60Hz Heuristic";
-    std::tie(std::ignore, signals) = configs.getRankedRefreshRates(layers, {.touch = true});
-    EXPECT_FALSE(signals.touch);
-}
-
-TEST_F(RefreshRateConfigsTest, getBestRefreshRate_ExplicitDefault) {
-    TestableRefreshRateConfigs configs(kModes_60_90_72_120, kModeId60);
-
-    std::vector<LayerRequirement> layers = {{.weight = 1.f}};
-    auto& lr = layers[0];
-
-    // Prepare a table with the vote and the expected refresh rate
-    const std::initializer_list<std::pair<Fps, Fps>> testCases = {
-            {130_Hz, 120_Hz}, {120_Hz, 120_Hz}, {119_Hz, 120_Hz}, {110_Hz, 120_Hz},
-
-            {100_Hz, 90_Hz},  {90_Hz, 90_Hz},   {89_Hz, 90_Hz},
-
-            {80_Hz, 72_Hz},   {73_Hz, 72_Hz},   {72_Hz, 72_Hz},   {71_Hz, 72_Hz},   {70_Hz, 72_Hz},
-
-            {65_Hz, 60_Hz},   {60_Hz, 60_Hz},   {59_Hz, 60_Hz},   {58_Hz, 60_Hz},
-
-            {55_Hz, 90_Hz},   {50_Hz, 90_Hz},   {45_Hz, 90_Hz},
-
-            {42_Hz, 120_Hz},  {40_Hz, 120_Hz},  {39_Hz, 120_Hz},
-
-            {37_Hz, 72_Hz},   {36_Hz, 72_Hz},   {35_Hz, 72_Hz},
-
-            {30_Hz, 60_Hz},
-    };
-
-    for (auto [desired, expected] : testCases) {
-        lr.vote = LayerVoteType::ExplicitDefault;
-        lr.desiredRefreshRate = desired;
-
-        std::stringstream ss;
-        ss << "ExplicitDefault " << desired;
-        lr.name = ss.str();
-
-        EXPECT_EQ(expected, configs.getBestRefreshRate(layers)->getFps());
-    }
-}
-
-TEST_F(RefreshRateConfigsTest,
-       getBestRefreshRate_ExplicitExactOrMultiple_WithFractionalRefreshRates) {
-    std::vector<LayerRequirement> layers = {{.weight = 1.f}};
-    auto& lr = layers[0];
-
-    // Test that 23.976 will choose 24 if 23.976 is not supported
-    {
-        TestableRefreshRateConfigs configs(makeModes(kMode24, kMode25, kMode30, kMode30Frac,
-                                                     kMode60, kMode60Frac),
-                                           kModeId60);
-
-        lr.vote = LayerVoteType::ExplicitExactOrMultiple;
-        lr.desiredRefreshRate = 23.976_Hz;
-        lr.name = "ExplicitExactOrMultiple 23.976 Hz";
-        EXPECT_EQ(kModeId24, configs.getBestRefreshRate(layers)->getId());
-    }
-
-    // Test that 24 will choose 23.976 if 24 is not supported
-    {
-        TestableRefreshRateConfigs configs(makeModes(kMode24Frac, kMode25, kMode30, kMode30Frac,
-                                                     kMode60, kMode60Frac),
-                                           kModeId60);
-
-        lr.desiredRefreshRate = 24_Hz;
-        lr.name = "ExplicitExactOrMultiple 24 Hz";
-        EXPECT_EQ(kModeId24Frac, configs.getBestRefreshRate(layers)->getId());
-    }
-
-    // Test that 29.97 will prefer 59.94 over 60 and 30
-    {
-        TestableRefreshRateConfigs configs(makeModes(kMode24, kMode24Frac, kMode25, kMode30,
-                                                     kMode60, kMode60Frac),
-                                           kModeId60);
-
-        lr.desiredRefreshRate = 29.97_Hz;
-        lr.name = "ExplicitExactOrMultiple 29.97 Hz";
-        EXPECT_EQ(kModeId60Frac, configs.getBestRefreshRate(layers)->getId());
-    }
-}
-
-TEST_F(RefreshRateConfigsTest, getBestRefreshRate_ExplicitExact_WithFractionalRefreshRates) {
-    std::vector<LayerRequirement> layers = {{.weight = 1.f}};
-    auto& lr = layers[0];
-
-    // Test that voting for supported refresh rate will select this refresh rate
-    {
-        TestableRefreshRateConfigs configs(kModes_24_25_30_50_60_Frac, kModeId60);
-
-        for (auto desired : {23.976_Hz, 24_Hz, 25_Hz, 29.97_Hz, 30_Hz, 50_Hz, 59.94_Hz, 60_Hz}) {
-            lr.vote = LayerVoteType::ExplicitExact;
-            lr.desiredRefreshRate = desired;
-            std::stringstream ss;
-            ss << "ExplicitExact " << desired;
-            lr.name = ss.str();
-
-            EXPECT_EQ(lr.desiredRefreshRate, configs.getBestRefreshRate(layers)->getFps());
-        }
-    }
-}
-
-TEST_F(RefreshRateConfigsTest,
-       getBestRefreshRate_withDisplayManagerRequestingSingleRate_ignoresTouchFlag) {
-    TestableRefreshRateConfigs configs(kModes_60_90, kModeId90);
-
-    EXPECT_EQ(SetPolicyResult::Changed,
-              configs.setDisplayManagerPolicy({kModeId90, {90_Hz, 90_Hz}, {60_Hz, 90_Hz}}));
-
-    std::vector<LayerRequirement> layers = {{.weight = 1.f}};
-    auto& lr = layers[0];
-
-    lr.vote = LayerVoteType::ExplicitDefault;
-    lr.desiredRefreshRate = 60_Hz;
-    lr.name = "60Hz ExplicitDefault";
-    lr.focused = true;
-
-    const auto [mode, signals] =
-            configs.getRankedRefreshRates(layers, {.touch = true, .idle = true});
-
-    EXPECT_EQ(mode.begin()->displayModePtr, kMode60);
-    EXPECT_FALSE(signals.touch);
-}
-
-TEST_F(RefreshRateConfigsTest,
-       getBestRefreshRate_withDisplayManagerRequestingSingleRate_ignoresIdleFlag) {
-    TestableRefreshRateConfigs configs(kModes_60_90, kModeId60);
-
-    EXPECT_EQ(SetPolicyResult::Changed,
-              configs.setDisplayManagerPolicy({kModeId60, {60_Hz, 60_Hz}, {60_Hz, 90_Hz}}));
-
-    std::vector<LayerRequirement> layers = {{.weight = 1.f}};
-    auto& lr = layers[0];
-
-    lr.vote = LayerVoteType::ExplicitDefault;
-    lr.desiredRefreshRate = 90_Hz;
-    lr.name = "90Hz ExplicitDefault";
-    lr.focused = true;
-    EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers, {.idle = true}));
-}
-
-TEST_F(RefreshRateConfigsTest, testDisplayModeOrdering) {
-    TestableRefreshRateConfigs configs(kModes_30_60_72_90_120, kModeId60);
-
-    std::vector<LayerRequirement> layers = {{.weight = 1.f},
-                                            {.weight = 1.f},
-                                            {.weight = 1.f},
-                                            {.weight = 1.f},
-                                            {.weight = 1.f}};
-    auto& lr1 = layers[0];
-    auto& lr2 = layers[1];
-    auto& lr3 = layers[2];
-    auto& lr4 = layers[3];
-    auto& lr5 = layers[4];
-
-    lr1.desiredRefreshRate = 90_Hz;
-    lr1.name = "90Hz";
-    lr1.focused = true;
-
-    lr2.desiredRefreshRate = 60_Hz;
-    lr2.name = "60Hz";
-    lr2.focused = true;
-
-    lr3.desiredRefreshRate = 72_Hz;
-    lr3.name = "72Hz";
-    lr3.focused = true;
-
-    lr4.desiredRefreshRate = 120_Hz;
-    lr4.name = "120Hz";
-    lr4.focused = true;
-
-    lr5.desiredRefreshRate = 30_Hz;
-    lr5.name = "30Hz";
-    lr5.focused = true;
-
-    std::vector<RefreshRateRanking> expectedRankings = {
-            RefreshRateRanking{kMode120}, RefreshRateRanking{kMode90}, RefreshRateRanking{kMode72},
-            RefreshRateRanking{kMode60},  RefreshRateRanking{kMode30},
-    };
-
-    std::vector<RefreshRateRanking> actualOrder =
-            configs.getRankedRefreshRatesAndSignals(layers, {}).first;
-    ASSERT_EQ(expectedRankings.size(), actualOrder.size());
-    for (size_t i = 0; i < expectedRankings.size(); ++i) {
-        EXPECT_EQ(expectedRankings[i].displayModePtr, actualOrder[i].displayModePtr)
-                << "Expected fps " << expectedRankings[i].displayModePtr->getFps().getIntValue()
-                << " Actual fps " << actualOrder[i].displayModePtr->getFps().getIntValue();
-    }
-
-    lr1.vote = LayerVoteType::Max;
-    lr1.name = "Max";
-
-    lr2.desiredRefreshRate = 60_Hz;
-    lr2.name = "60Hz";
-
-    lr3.desiredRefreshRate = 72_Hz;
-    lr3.name = "72Hz";
-
-    lr4.desiredRefreshRate = 90_Hz;
-    lr4.name = "90Hz";
-
-    lr5.desiredRefreshRate = 120_Hz;
-    lr5.name = "120Hz";
-
-    expectedRankings = {
-            RefreshRateRanking{kMode120}, RefreshRateRanking{kMode90}, RefreshRateRanking{kMode72},
-            RefreshRateRanking{kMode60},  RefreshRateRanking{kMode30},
-    };
-
-    actualOrder = configs.getRankedRefreshRatesAndSignals(layers, {}).first;
-
-    ASSERT_EQ(expectedRankings.size(), actualOrder.size());
-    for (size_t i = 0; i < expectedRankings.size(); ++i) {
-        EXPECT_EQ(expectedRankings[i].displayModePtr, actualOrder[i].displayModePtr)
-                << "Expected fps " << expectedRankings[i].displayModePtr->getFps().getIntValue()
-                << " Actual fps " << actualOrder[i].displayModePtr->getFps().getIntValue();
-    }
-
-    lr1.vote = LayerVoteType::Heuristic;
-    lr1.desiredRefreshRate = 30_Hz;
-    lr1.name = "30Hz";
-
-    lr2.desiredRefreshRate = 120_Hz;
-    lr2.name = "120Hz";
-
-    lr3.desiredRefreshRate = 60_Hz;
-    lr3.name = "60Hz";
-
-    lr5.desiredRefreshRate = 72_Hz;
-    lr5.name = "72Hz";
-
-    expectedRankings = {
-            RefreshRateRanking{kMode30},  RefreshRateRanking{kMode60}, RefreshRateRanking{kMode90},
-            RefreshRateRanking{kMode120}, RefreshRateRanking{kMode72},
-    };
-
-    actualOrder = configs.getRankedRefreshRatesAndSignals(layers, {}).first;
-    ASSERT_EQ(expectedRankings.size(), actualOrder.size());
-    for (size_t i = 0; i < expectedRankings.size(); ++i) {
-        EXPECT_EQ(expectedRankings[i].displayModePtr, actualOrder[i].displayModePtr)
-                << "Expected fps " << expectedRankings[i].displayModePtr->getFps().getIntValue()
-                << " Actual fps " << actualOrder[i].displayModePtr->getFps().getIntValue();
-    }
-
-    lr1.desiredRefreshRate = 120_Hz;
-    lr1.name = "120Hz";
-    lr1.weight = 0.0f;
-
-    lr2.desiredRefreshRate = 60_Hz;
-    lr2.name = "60Hz";
-    lr2.vote = LayerVoteType::NoVote;
-
-    lr3.name = "60Hz-2";
-    lr3.vote = LayerVoteType::Heuristic;
-
-    lr4.vote = LayerVoteType::ExplicitExact;
-
-    lr5.desiredRefreshRate = 120_Hz;
-    lr5.name = "120Hz-2";
-
-    expectedRankings = {
-            RefreshRateRanking{kMode90}, RefreshRateRanking{kMode60}, RefreshRateRanking{kMode120},
-            RefreshRateRanking{kMode72}, RefreshRateRanking{kMode30},
-    };
-
-    actualOrder = configs.getRankedRefreshRatesAndSignals(layers, {}).first;
-    ASSERT_EQ(expectedRankings.size(), actualOrder.size());
-    for (size_t i = 0; i < expectedRankings.size(); ++i) {
-        EXPECT_EQ(expectedRankings[i].displayModePtr, actualOrder[i].displayModePtr)
-                << "Expected fps " << expectedRankings[i].displayModePtr->getFps().getIntValue()
-                << " Actual fps " << actualOrder[i].displayModePtr->getFps().getIntValue();
-    }
-}
-
-TEST_F(RefreshRateConfigsTest,
-       getBestRefreshRate_withDisplayManagerRequestingSingleRate_onlySwitchesRatesForExplicitFocusedLayers) {
-    TestableRefreshRateConfigs configs(kModes_60_90, kModeId90);
-
-    EXPECT_EQ(SetPolicyResult::Changed,
-              configs.setDisplayManagerPolicy({kModeId90, {90_Hz, 90_Hz}, {60_Hz, 90_Hz}}));
-
-    const auto [mode, signals] = configs.getRankedRefreshRatesAndSignals({}, {});
-    EXPECT_EQ(mode.front().displayModePtr, kMode90);
-    EXPECT_FALSE(signals.touch);
-
-    std::vector<LayerRequirement> layers = {{.weight = 1.f}};
-    auto& lr = layers[0];
-
-    lr.vote = LayerVoteType::ExplicitExactOrMultiple;
-    lr.desiredRefreshRate = 60_Hz;
-    lr.name = "60Hz ExplicitExactOrMultiple";
-    lr.focused = false;
-    EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
-
-    lr.focused = true;
-    EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
-
-    lr.vote = LayerVoteType::ExplicitDefault;
-    lr.desiredRefreshRate = 60_Hz;
-    lr.name = "60Hz ExplicitDefault";
-    lr.focused = false;
-    EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
-
-    lr.focused = true;
-    EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers));
-
-    lr.vote = LayerVoteType::Heuristic;
-    lr.desiredRefreshRate = 60_Hz;
-    lr.name = "60Hz Heuristic";
-    lr.focused = false;
-    EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
-
-    lr.focused = true;
-    EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
-
-    lr.vote = LayerVoteType::Max;
-    lr.desiredRefreshRate = 60_Hz;
-    lr.name = "60Hz Max";
-    lr.focused = false;
-    EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
-
-    lr.focused = true;
-    EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
-
-    lr.vote = LayerVoteType::Min;
-    lr.desiredRefreshRate = 60_Hz;
-    lr.name = "60Hz Min";
-    lr.focused = false;
-    EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
-
-    lr.focused = true;
-    EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
-}
-
-TEST_F(RefreshRateConfigsTest, groupSwitchingNotAllowed) {
-    TestableRefreshRateConfigs configs(kModes_60_90_G1, kModeId60);
-
-    // The default policy doesn't allow group switching. Verify that no
-    // group switches are performed.
-    std::vector<LayerRequirement> layers = {{.weight = 1.f}};
-    auto& layer = layers[0];
-    layer.vote = LayerVoteType::ExplicitDefault;
-    layer.desiredRefreshRate = 90_Hz;
-    layer.seamlessness = Seamlessness::SeamedAndSeamless;
-    layer.name = "90Hz ExplicitDefault";
-    layer.focused = true;
-
-    EXPECT_EQ(kModeId60, configs.getBestRefreshRate(layers)->getId());
-}
-
-TEST_F(RefreshRateConfigsTest, groupSwitchingWithOneLayer) {
-    TestableRefreshRateConfigs configs(kModes_60_90_G1, kModeId60);
-
-    RefreshRateConfigs::DisplayManagerPolicy policy;
-    policy.defaultMode = configs.getCurrentPolicy().defaultMode;
-    policy.allowGroupSwitching = true;
-    EXPECT_EQ(SetPolicyResult::Changed, configs.setPolicy(policy));
-
-    std::vector<LayerRequirement> layers = {{.weight = 1.f}};
-    auto& layer = layers[0];
-    layer.vote = LayerVoteType::ExplicitDefault;
-    layer.desiredRefreshRate = 90_Hz;
-    layer.seamlessness = Seamlessness::SeamedAndSeamless;
-    layer.name = "90Hz ExplicitDefault";
-    layer.focused = true;
-    EXPECT_EQ(kModeId90, configs.getBestRefreshRate(layers)->getId());
-}
-
-TEST_F(RefreshRateConfigsTest, groupSwitchingWithOneLayerOnlySeamless) {
-    TestableRefreshRateConfigs configs(kModes_60_90_G1, kModeId60);
-
-    RefreshRateConfigs::DisplayManagerPolicy policy;
-    policy.defaultMode = configs.getCurrentPolicy().defaultMode;
-    policy.allowGroupSwitching = true;
-    EXPECT_EQ(SetPolicyResult::Changed, configs.setPolicy(policy));
-
-    // Verify that we won't change the group if seamless switch is required.
-    std::vector<LayerRequirement> layers = {{.weight = 1.f}};
-    auto& layer = layers[0];
-    layer.vote = LayerVoteType::ExplicitDefault;
-    layer.desiredRefreshRate = 90_Hz;
-    layer.seamlessness = Seamlessness::OnlySeamless;
-    layer.name = "90Hz ExplicitDefault";
-    layer.focused = true;
-    EXPECT_EQ(kModeId60, configs.getBestRefreshRate(layers)->getId());
-}
-
-TEST_F(RefreshRateConfigsTest, groupSwitchingWithOneLayerOnlySeamlessDefaultFps) {
-    TestableRefreshRateConfigs configs(kModes_60_90_G1, kModeId60);
-
-    RefreshRateConfigs::DisplayManagerPolicy policy;
-    policy.defaultMode = configs.getCurrentPolicy().defaultMode;
-    policy.allowGroupSwitching = true;
-    EXPECT_EQ(SetPolicyResult::Changed, configs.setPolicy(policy));
-
-    configs.setActiveModeId(kModeId90);
-
-    // Verify that we won't do a seamless switch if we request the same mode as the default
-    std::vector<LayerRequirement> layers = {{.weight = 1.f}};
-    auto& layer = layers[0];
-    layer.vote = LayerVoteType::ExplicitDefault;
-    layer.desiredRefreshRate = 60_Hz;
-    layer.seamlessness = Seamlessness::OnlySeamless;
-    layer.name = "60Hz ExplicitDefault";
-    layer.focused = true;
-    EXPECT_EQ(kModeId90, configs.getBestRefreshRate(layers)->getId());
-}
-
-TEST_F(RefreshRateConfigsTest, groupSwitchingWithOneLayerDefaultSeamlessness) {
-    TestableRefreshRateConfigs configs(kModes_60_90_G1, kModeId60);
-
-    RefreshRateConfigs::DisplayManagerPolicy policy;
-    policy.defaultMode = configs.getCurrentPolicy().defaultMode;
-    policy.allowGroupSwitching = true;
-    EXPECT_EQ(SetPolicyResult::Changed, configs.setPolicy(policy));
-
-    configs.setActiveModeId(kModeId90);
-
-    // Verify that if the current config is in another group and there are no layers with
-    // seamlessness=SeamedAndSeamless we'll go back to the default group.
-
-    std::vector<LayerRequirement> layers = {{.weight = 1.f}};
-    auto& layer = layers[0];
-    layer.vote = LayerVoteType::ExplicitDefault;
-    layer.desiredRefreshRate = 60_Hz;
-    layer.seamlessness = Seamlessness::Default;
-    layer.name = "60Hz ExplicitDefault";
-    layer.focused = true;
-
-    EXPECT_EQ(kModeId60, configs.getBestRefreshRate(layers)->getId());
-}
-
-TEST_F(RefreshRateConfigsTest, groupSwitchingWithTwoLayersOnlySeamlessAndSeamed) {
-    TestableRefreshRateConfigs configs(kModes_60_90_G1, kModeId60);
-
-    RefreshRateConfigs::DisplayManagerPolicy policy;
-    policy.defaultMode = configs.getCurrentPolicy().defaultMode;
-    policy.allowGroupSwitching = true;
-    EXPECT_EQ(SetPolicyResult::Changed, configs.setPolicy(policy));
-
-    configs.setActiveModeId(kModeId90);
-
-    // If there's a layer with seamlessness=SeamedAndSeamless, another layer with
-    // seamlessness=OnlySeamless can't change the mode group.
-    std::vector<LayerRequirement> layers = {{.weight = 1.f}};
-    layers[0].vote = LayerVoteType::ExplicitDefault;
-    layers[0].desiredRefreshRate = 60_Hz;
-    layers[0].seamlessness = Seamlessness::OnlySeamless;
-    layers[0].name = "60Hz ExplicitDefault";
-    layers[0].focused = true;
-
-    layers.push_back(LayerRequirement{.weight = 0.5f});
-    layers[1].vote = LayerVoteType::ExplicitDefault;
-    layers[1].seamlessness = Seamlessness::SeamedAndSeamless;
-    layers[1].desiredRefreshRate = 90_Hz;
-    layers[1].name = "90Hz ExplicitDefault";
-    layers[1].focused = false;
-
-    EXPECT_EQ(kModeId90, configs.getBestRefreshRate(layers)->getId());
-}
-
-TEST_F(RefreshRateConfigsTest, groupSwitchingWithTwoLayersDefaultFocusedAndSeamed) {
-    TestableRefreshRateConfigs configs(kModes_60_90_G1, kModeId60);
-
-    RefreshRateConfigs::DisplayManagerPolicy policy;
-    policy.defaultMode = configs.getCurrentPolicy().defaultMode;
-    policy.allowGroupSwitching = true;
-    EXPECT_EQ(SetPolicyResult::Changed, configs.setPolicy(policy));
-
-    configs.setActiveModeId(kModeId90);
-
-    // If there's a focused layer with seamlessness=SeamedAndSeamless, another layer with
-    // seamlessness=Default can't change the mode group back to the group of the default
-    // mode.
-    // For example, this may happen when a video playback requests and gets a seamed switch,
-    // but another layer (with default seamlessness) starts animating. The animating layer
-    // should not cause a seamed switch.
-    std::vector<LayerRequirement> layers = {{.weight = 1.f}};
-    layers[0].seamlessness = Seamlessness::Default;
-    layers[0].desiredRefreshRate = 60_Hz;
-    layers[0].focused = true;
-    layers[0].vote = LayerVoteType::ExplicitDefault;
-    layers[0].name = "60Hz ExplicitDefault";
-
-    layers.push_back(LayerRequirement{.weight = 0.1f});
-    layers[1].seamlessness = Seamlessness::SeamedAndSeamless;
-    layers[1].desiredRefreshRate = 90_Hz;
-    layers[1].focused = true;
-    layers[1].vote = LayerVoteType::ExplicitDefault;
-    layers[1].name = "90Hz ExplicitDefault";
-
-    EXPECT_EQ(kModeId90, configs.getBestRefreshRate(layers)->getId());
-}
-
-TEST_F(RefreshRateConfigsTest, groupSwitchingWithTwoLayersDefaultNotFocusedAndSeamed) {
-    TestableRefreshRateConfigs configs(kModes_60_90_G1, kModeId60);
-
-    RefreshRateConfigs::DisplayManagerPolicy policy;
-    policy.defaultMode = configs.getCurrentPolicy().defaultMode;
-    policy.allowGroupSwitching = true;
-    EXPECT_EQ(SetPolicyResult::Changed, configs.setPolicy(policy));
-
-    configs.setActiveModeId(kModeId90);
-
-    // Layer with seamlessness=Default can change the mode group if there's a not
-    // focused layer with seamlessness=SeamedAndSeamless. This happens for example,
-    // when in split screen mode the user switches between the two visible applications.
-    std::vector<LayerRequirement> layers = {{.weight = 1.f}};
-    layers[0].seamlessness = Seamlessness::Default;
-    layers[0].desiredRefreshRate = 60_Hz;
-    layers[0].focused = true;
-    layers[0].vote = LayerVoteType::ExplicitDefault;
-    layers[0].name = "60Hz ExplicitDefault";
-
-    layers.push_back(LayerRequirement{.weight = 0.7f});
-    layers[1].seamlessness = Seamlessness::SeamedAndSeamless;
-    layers[1].desiredRefreshRate = 90_Hz;
-    layers[1].focused = false;
-    layers[1].vote = LayerVoteType::ExplicitDefault;
-    layers[1].name = "90Hz ExplicitDefault";
-
-    EXPECT_EQ(kModeId60, configs.getBestRefreshRate(layers)->getId());
-}
-
-TEST_F(RefreshRateConfigsTest, nonSeamlessVotePrefersSeamlessSwitches) {
-    TestableRefreshRateConfigs configs(kModes_30_60, kModeId60);
-
-    // Allow group switching.
-    RefreshRateConfigs::DisplayManagerPolicy policy;
-    policy.defaultMode = configs.getCurrentPolicy().defaultMode;
-    policy.allowGroupSwitching = true;
-    EXPECT_EQ(SetPolicyResult::Changed, configs.setPolicy(policy));
-
-    std::vector<LayerRequirement> layers = {{.weight = 1.f}};
-    auto& layer = layers[0];
-    layer.vote = LayerVoteType::ExplicitExactOrMultiple;
-    layer.desiredRefreshRate = 60_Hz;
-    layer.seamlessness = Seamlessness::SeamedAndSeamless;
-    layer.name = "60Hz ExplicitExactOrMultiple";
-    layer.focused = true;
-
-    EXPECT_EQ(kModeId60, configs.getBestRefreshRate(layers)->getId());
-
-    configs.setActiveModeId(kModeId120);
-    EXPECT_EQ(kModeId120, configs.getBestRefreshRate(layers)->getId());
-}
-
-TEST_F(RefreshRateConfigsTest, nonSeamlessExactAndSeamlessMultipleLayers) {
-    TestableRefreshRateConfigs configs(kModes_25_30_50_60, kModeId60);
-
-    // Allow group switching.
-    RefreshRateConfigs::DisplayManagerPolicy policy;
-    policy.defaultMode = configs.getCurrentPolicy().defaultMode;
-    policy.allowGroupSwitching = true;
-    EXPECT_EQ(SetPolicyResult::Changed, configs.setPolicy(policy));
-
-    std::vector<LayerRequirement> layers = {{.name = "60Hz ExplicitDefault",
-                                             .vote = LayerVoteType::ExplicitDefault,
-                                             .desiredRefreshRate = 60_Hz,
-                                             .seamlessness = Seamlessness::SeamedAndSeamless,
-                                             .weight = 0.5f,
-                                             .focused = false},
-                                            {.name = "25Hz ExplicitExactOrMultiple",
-                                             .vote = LayerVoteType::ExplicitExactOrMultiple,
-                                             .desiredRefreshRate = 25_Hz,
-                                             .seamlessness = Seamlessness::OnlySeamless,
-                                             .weight = 1.f,
-                                             .focused = true}};
-
-    EXPECT_EQ(kModeId50, configs.getBestRefreshRate(layers)->getId());
-
-    auto& seamedLayer = layers[0];
-    seamedLayer.desiredRefreshRate = 30_Hz;
-    seamedLayer.name = "30Hz ExplicitDefault";
-    configs.setActiveModeId(kModeId30);
-
-    EXPECT_EQ(kModeId25, configs.getBestRefreshRate(layers)->getId());
-}
-
-TEST_F(RefreshRateConfigsTest, minLayersDontTrigerSeamedSwitch) {
-    TestableRefreshRateConfigs configs(kModes_60_90_G1, kModeId90);
-
-    // Allow group switching.
-    RefreshRateConfigs::DisplayManagerPolicy policy;
-    policy.defaultMode = configs.getCurrentPolicy().defaultMode;
-    policy.allowGroupSwitching = true;
-    EXPECT_EQ(SetPolicyResult::Changed, configs.setPolicy(policy));
-
-    std::vector<LayerRequirement> layers = {
-            {.name = "Min", .vote = LayerVoteType::Min, .weight = 1.f, .focused = true}};
-
-    EXPECT_EQ(kModeId90, configs.getBestRefreshRate(layers)->getId());
-}
-
-TEST_F(RefreshRateConfigsTest, primaryVsAppRequestPolicy) {
-    TestableRefreshRateConfigs configs(kModes_30_60_90, kModeId60);
-
-    std::vector<LayerRequirement> layers = {{.weight = 1.f}};
-    layers[0].name = "Test layer";
-
-    struct Args {
-        bool touch = false;
-        bool focused = true;
-    };
-
-    // Return the config ID from calling getBestRefreshRate() for a single layer with the
-    // given voteType and fps.
-    auto getFrameRate = [&](LayerVoteType voteType, Fps fps, Args args = {}) -> DisplayModeId {
-        layers[0].vote = voteType;
-        layers[0].desiredRefreshRate = fps;
-        layers[0].focused = args.focused;
-        return configs.getBestRefreshRate(layers, {.touch = args.touch})->getId();
-    };
-
-    EXPECT_EQ(SetPolicyResult::Changed,
-              configs.setDisplayManagerPolicy({kModeId60, {30_Hz, 60_Hz}, {30_Hz, 90_Hz}}));
-
-    EXPECT_EQ(kModeId60, configs.getBestRefreshRate()->getId());
-    EXPECT_EQ(kModeId60, getFrameRate(LayerVoteType::NoVote, 90_Hz));
-    EXPECT_EQ(kModeId30, getFrameRate(LayerVoteType::Min, 90_Hz));
-    EXPECT_EQ(kModeId60, getFrameRate(LayerVoteType::Max, 90_Hz));
-    EXPECT_EQ(kModeId60, getFrameRate(LayerVoteType::Heuristic, 90_Hz));
-    EXPECT_EQ(kModeId90, getFrameRate(LayerVoteType::ExplicitDefault, 90_Hz));
-    EXPECT_EQ(kModeId60, getFrameRate(LayerVoteType::ExplicitExactOrMultiple, 90_Hz));
-
-    // Unfocused layers are not allowed to override primary config.
-    EXPECT_EQ(kModeId60, getFrameRate(LayerVoteType::ExplicitDefault, 90_Hz, {.focused = false}));
-    EXPECT_EQ(kModeId60,
-              getFrameRate(LayerVoteType::ExplicitExactOrMultiple, 90_Hz, {.focused = false}));
-
-    // Touch boost should be restricted to the primary range.
-    EXPECT_EQ(kModeId60, getFrameRate(LayerVoteType::Max, 90_Hz, {.touch = true}));
-
-    // When we're higher than the primary range max due to a layer frame rate setting, touch boost
-    // shouldn't drag us back down to the primary range max.
-    EXPECT_EQ(kModeId90, getFrameRate(LayerVoteType::ExplicitDefault, 90_Hz, {.touch = true}));
-    EXPECT_EQ(kModeId60,
-              getFrameRate(LayerVoteType::ExplicitExactOrMultiple, 90_Hz, {.touch = true}));
-
-    EXPECT_EQ(SetPolicyResult::Changed,
-              configs.setDisplayManagerPolicy({kModeId60, {60_Hz, 60_Hz}, {60_Hz, 60_Hz}}));
-
-    EXPECT_EQ(kModeId60, getFrameRate(LayerVoteType::NoVote, 90_Hz));
-    EXPECT_EQ(kModeId60, getFrameRate(LayerVoteType::Min, 90_Hz));
-    EXPECT_EQ(kModeId60, getFrameRate(LayerVoteType::Max, 90_Hz));
-    EXPECT_EQ(kModeId60, getFrameRate(LayerVoteType::Heuristic, 90_Hz));
-    EXPECT_EQ(kModeId60, getFrameRate(LayerVoteType::ExplicitDefault, 90_Hz));
-    EXPECT_EQ(kModeId60, getFrameRate(LayerVoteType::ExplicitExactOrMultiple, 90_Hz));
-}
-
-TEST_F(RefreshRateConfigsTest, idle) {
-    TestableRefreshRateConfigs configs(kModes_60_90, kModeId60);
-
-    std::vector<LayerRequirement> layers = {{.weight = 1.f}};
-    layers[0].name = "Test layer";
-
-    const auto getIdleFrameRate = [&](LayerVoteType voteType, bool touchActive) -> DisplayModeId {
-        layers[0].vote = voteType;
-        layers[0].desiredRefreshRate = 90_Hz;
-
-        const auto [refreshRate, signals] =
-                configs.getRankedRefreshRatesAndSignals(layers,
-                                                        {.touch = touchActive, .idle = true});
-
-        // Refresh rate will be chosen by either touch state or idle state.
-        EXPECT_EQ(!touchActive, signals.idle);
-        return refreshRate.front().displayModePtr->getId();
-    };
-
-    EXPECT_EQ(SetPolicyResult::Changed,
-              configs.setDisplayManagerPolicy({kModeId60, {60_Hz, 90_Hz}, {60_Hz, 90_Hz}}));
-
-    // Idle should be lower priority than touch boost.
-    {
-        constexpr bool kTouchActive = true;
-        EXPECT_EQ(kModeId90, getIdleFrameRate(LayerVoteType::NoVote, kTouchActive));
-        EXPECT_EQ(kModeId90, getIdleFrameRate(LayerVoteType::Min, kTouchActive));
-        EXPECT_EQ(kModeId90, getIdleFrameRate(LayerVoteType::Max, kTouchActive));
-        EXPECT_EQ(kModeId90, getIdleFrameRate(LayerVoteType::Heuristic, kTouchActive));
-        EXPECT_EQ(kModeId90, getIdleFrameRate(LayerVoteType::ExplicitDefault, kTouchActive));
-        EXPECT_EQ(kModeId90,
-                  getIdleFrameRate(LayerVoteType::ExplicitExactOrMultiple, kTouchActive));
-    }
-
-    // With no layers, idle should still be lower priority than touch boost.
-    EXPECT_EQ(kModeId90, configs.getBestRefreshRate({}, {.touch = true, .idle = true})->getId());
-
-    // Idle should be higher precedence than other layer frame rate considerations.
-    configs.setActiveModeId(kModeId90);
-
-    {
-        constexpr bool kTouchActive = false;
-        EXPECT_EQ(kModeId60, getIdleFrameRate(LayerVoteType::NoVote, kTouchActive));
-        EXPECT_EQ(kModeId60, getIdleFrameRate(LayerVoteType::Min, kTouchActive));
-        EXPECT_EQ(kModeId60, getIdleFrameRate(LayerVoteType::Max, kTouchActive));
-        EXPECT_EQ(kModeId60, getIdleFrameRate(LayerVoteType::Heuristic, kTouchActive));
-        EXPECT_EQ(kModeId60, getIdleFrameRate(LayerVoteType::ExplicitDefault, kTouchActive));
-        EXPECT_EQ(kModeId60,
-                  getIdleFrameRate(LayerVoteType::ExplicitExactOrMultiple, kTouchActive));
-    }
-
-    // Idle should be applied rather than the current config when there are no layers.
-    EXPECT_EQ(kModeId60, configs.getBestRefreshRate({}, {.idle = true})->getId());
-}
-
-TEST_F(RefreshRateConfigsTest, findClosestKnownFrameRate) {
-    TestableRefreshRateConfigs configs(kModes_60_90, kModeId60);
-
-    for (float fps = 1.0f; fps <= 120.0f; fps += 0.1f) {
-        const auto knownFrameRate = configs.findClosestKnownFrameRate(Fps::fromValue(fps));
-        const Fps expectedFrameRate = [fps] {
-            if (fps < 26.91f) return 24_Hz;
-            if (fps < 37.51f) return 30_Hz;
-            if (fps < 52.51f) return 45_Hz;
-            if (fps < 66.01f) return 60_Hz;
-            if (fps < 81.01f) return 72_Hz;
-            return 90_Hz;
-        }();
-
-        EXPECT_EQ(expectedFrameRate, knownFrameRate);
-    }
-}
-
-TEST_F(RefreshRateConfigsTest, getBestRefreshRate_KnownFrameRate) {
-    TestableRefreshRateConfigs configs(kModes_60_90, kModeId60);
-
-    struct Expectation {
-        Fps fps;
-        DisplayModePtr mode;
-    };
-
-    const std::initializer_list<Expectation> knownFrameRatesExpectations = {
-            {24_Hz, kMode60}, {30_Hz, kMode60}, {45_Hz, kMode90},
-            {60_Hz, kMode60}, {72_Hz, kMode90}, {90_Hz, kMode90},
-    };
-
-    // Make sure the test tests all the known frame rate
-    const auto& knownFrameRates = configs.knownFrameRates();
-    const bool equal = std::equal(knownFrameRates.begin(), knownFrameRates.end(),
-                                  knownFrameRatesExpectations.begin(),
-                                  [](Fps fps, const Expectation& expected) {
-                                      return isApproxEqual(fps, expected.fps);
-                                  });
-    EXPECT_TRUE(equal);
-
-    std::vector<LayerRequirement> layers = {{.weight = 1.f}};
-    auto& layer = layers[0];
-    layer.vote = LayerVoteType::Heuristic;
-
-    for (const auto& [fps, mode] : knownFrameRatesExpectations) {
-        layer.desiredRefreshRate = fps;
-        EXPECT_EQ(mode, configs.getBestRefreshRate(layers));
-    }
-}
-
-TEST_F(RefreshRateConfigsTest, getBestRefreshRate_ExplicitExact) {
-    TestableRefreshRateConfigs configs(kModes_30_60_72_90_120, kModeId60);
-
-    std::vector<LayerRequirement> layers = {{.weight = 1.f}, {.weight = 0.5f}};
-    auto& explicitExactLayer = layers[0];
-    auto& explicitExactOrMultipleLayer = layers[1];
-
-    explicitExactOrMultipleLayer.vote = LayerVoteType::ExplicitExactOrMultiple;
-    explicitExactOrMultipleLayer.name = "ExplicitExactOrMultiple";
-    explicitExactOrMultipleLayer.desiredRefreshRate = 60_Hz;
-
-    explicitExactLayer.vote = LayerVoteType::ExplicitExact;
-    explicitExactLayer.name = "ExplicitExact";
-    explicitExactLayer.desiredRefreshRate = 30_Hz;
-
-    EXPECT_EQ(kMode30, configs.getBestRefreshRate(layers));
-    EXPECT_EQ(kMode30, configs.getBestRefreshRate(layers, {.touch = true}));
-
-    explicitExactOrMultipleLayer.desiredRefreshRate = 120_Hz;
-    explicitExactLayer.desiredRefreshRate = 60_Hz;
-    EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers));
-
-    explicitExactLayer.desiredRefreshRate = 72_Hz;
-    EXPECT_EQ(kMode72, configs.getBestRefreshRate(layers));
-
-    explicitExactLayer.desiredRefreshRate = 90_Hz;
-    EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
-
-    explicitExactLayer.desiredRefreshRate = 120_Hz;
-    EXPECT_EQ(kMode120, configs.getBestRefreshRate(layers));
-}
-
-TEST_F(RefreshRateConfigsTest, getBestRefreshRate_ExplicitExactEnableFrameRateOverride) {
-    TestableRefreshRateConfigs configs(kModes_30_60_72_90_120, kModeId60,
-                                       {.enableFrameRateOverride = true});
-
-    std::vector<LayerRequirement> layers = {{.weight = 1.f}, {.weight = 0.5f}};
-    auto& explicitExactLayer = layers[0];
-    auto& explicitExactOrMultipleLayer = layers[1];
-
-    explicitExactOrMultipleLayer.vote = LayerVoteType::ExplicitExactOrMultiple;
-    explicitExactOrMultipleLayer.name = "ExplicitExactOrMultiple";
-    explicitExactOrMultipleLayer.desiredRefreshRate = 60_Hz;
-
-    explicitExactLayer.vote = LayerVoteType::ExplicitExact;
-    explicitExactLayer.name = "ExplicitExact";
-    explicitExactLayer.desiredRefreshRate = 30_Hz;
-
-    EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers));
-    EXPECT_EQ(kMode120, configs.getBestRefreshRate(layers, {.touch = true}));
-
-    explicitExactOrMultipleLayer.desiredRefreshRate = 120_Hz;
-    explicitExactLayer.desiredRefreshRate = 60_Hz;
-    EXPECT_EQ(kMode120, configs.getBestRefreshRate(layers));
-
-    explicitExactLayer.desiredRefreshRate = 72_Hz;
-    EXPECT_EQ(kMode72, configs.getBestRefreshRate(layers));
-
-    explicitExactLayer.desiredRefreshRate = 90_Hz;
-    EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
-
-    explicitExactLayer.desiredRefreshRate = 120_Hz;
-    EXPECT_EQ(kMode120, configs.getBestRefreshRate(layers));
-}
-
-TEST_F(RefreshRateConfigsTest, getBestRefreshRate_ReadsCache) {
-    TestableRefreshRateConfigs configs(kModes_30_60_72_90_120, kModeId60);
-
-    using GlobalSignals = RefreshRateConfigs::GlobalSignals;
-    const auto args = std::make_pair(std::vector<LayerRequirement>{},
-                                     GlobalSignals{.touch = true, .idle = true});
-
-    const auto result = std::make_pair(std::vector<RefreshRateRanking>{RefreshRateRanking{kMode90}},
-                                       GlobalSignals{.touch = true});
-
-    configs.mutableGetRankedRefreshRatesCache() = {args, result};
-
-    EXPECT_EQ(result, configs.getRankedRefreshRatesAndSignals(args.first, args.second));
-}
-
-TEST_F(RefreshRateConfigsTest, getBestRefreshRate_WritesCache) {
-    TestableRefreshRateConfigs configs(kModes_30_60_72_90_120, kModeId60);
-
-    EXPECT_FALSE(configs.mutableGetRankedRefreshRatesCache());
-
-    std::vector<LayerRequirement> layers = {{.weight = 1.f}, {.weight = 0.5f}};
-    RefreshRateConfigs::GlobalSignals globalSignals{.touch = true, .idle = true};
-
-    const auto result = configs.getRankedRefreshRatesAndSignals(layers, globalSignals);
-
-    const auto& cache = configs.mutableGetRankedRefreshRatesCache();
-    ASSERT_TRUE(cache);
-
-    EXPECT_EQ(cache->arguments, std::make_pair(layers, globalSignals));
-    EXPECT_EQ(cache->result, result);
-}
-
-TEST_F(RefreshRateConfigsTest, getBestRefreshRate_ExplicitExactTouchBoost) {
-    TestableRefreshRateConfigs configs(kModes_60_120, kModeId60, {.enableFrameRateOverride = true});
-
-    std::vector<LayerRequirement> layers = {{.weight = 1.f}, {.weight = 0.5f}};
-    auto& explicitExactLayer = layers[0];
-    auto& explicitExactOrMultipleLayer = layers[1];
-
-    explicitExactOrMultipleLayer.vote = LayerVoteType::ExplicitExactOrMultiple;
-    explicitExactOrMultipleLayer.name = "ExplicitExactOrMultiple";
-    explicitExactOrMultipleLayer.desiredRefreshRate = 60_Hz;
-
-    explicitExactLayer.vote = LayerVoteType::ExplicitExact;
-    explicitExactLayer.name = "ExplicitExact";
-    explicitExactLayer.desiredRefreshRate = 30_Hz;
-
-    EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers));
-    EXPECT_EQ(kMode120, configs.getBestRefreshRate(layers, {.touch = true}));
-
-    explicitExactOrMultipleLayer.vote = LayerVoteType::NoVote;
-
-    EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers));
-    EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers, {.touch = true}));
-}
-
-TEST_F(RefreshRateConfigsTest, getBestRefreshRate_FractionalRefreshRates_ExactAndDefault) {
-    TestableRefreshRateConfigs configs(kModes_24_25_30_50_60_Frac, kModeId60,
-                                       {.enableFrameRateOverride = true});
-
-    std::vector<LayerRequirement> layers = {{.weight = 0.5f}, {.weight = 0.5f}};
-    auto& explicitDefaultLayer = layers[0];
-    auto& explicitExactOrMultipleLayer = layers[1];
-
-    explicitExactOrMultipleLayer.vote = LayerVoteType::ExplicitExactOrMultiple;
-    explicitExactOrMultipleLayer.name = "ExplicitExactOrMultiple";
-    explicitExactOrMultipleLayer.desiredRefreshRate = 60_Hz;
-
-    explicitDefaultLayer.vote = LayerVoteType::ExplicitDefault;
-    explicitDefaultLayer.name = "ExplicitDefault";
-    explicitDefaultLayer.desiredRefreshRate = 59.94_Hz;
-
-    EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers));
-}
-
-// b/190578904
-TEST_F(RefreshRateConfigsTest, getBestRefreshRate_withCloseRefreshRates) {
-    constexpr int kMinRefreshRate = 10;
-    constexpr int kMaxRefreshRate = 240;
-
-    DisplayModes displayModes;
-    for (int fps = kMinRefreshRate; fps < kMaxRefreshRate; fps++) {
-        const DisplayModeId modeId(fps);
-        displayModes.try_emplace(modeId,
-                                 createDisplayMode(modeId,
-                                                   Fps::fromValue(static_cast<float>(fps))));
-    }
-
-    const TestableRefreshRateConfigs configs(std::move(displayModes),
-                                             DisplayModeId(kMinRefreshRate));
-
-    std::vector<LayerRequirement> layers = {{.weight = 1.f}};
-    const auto testRefreshRate = [&](Fps fps, LayerVoteType vote) {
-        layers[0].desiredRefreshRate = fps;
-        layers[0].vote = vote;
-        EXPECT_EQ(fps.getIntValue(), configs.getBestRefreshRate(layers)->getFps().getIntValue())
-                << "Failed for " << ftl::enum_string(vote);
-    };
-
-    for (int fps = kMinRefreshRate; fps < kMaxRefreshRate; fps++) {
-        const auto refreshRate = Fps::fromValue(static_cast<float>(fps));
-        testRefreshRate(refreshRate, LayerVoteType::Heuristic);
-        testRefreshRate(refreshRate, LayerVoteType::ExplicitDefault);
-        testRefreshRate(refreshRate, LayerVoteType::ExplicitExactOrMultiple);
-        testRefreshRate(refreshRate, LayerVoteType::ExplicitExact);
-    }
-}
-
-// b/190578904
-TEST_F(RefreshRateConfigsTest, getBestRefreshRate_conflictingVotes) {
-    constexpr DisplayModeId kActiveModeId{0};
-    DisplayModes displayModes = makeModes(createDisplayMode(kActiveModeId, 43_Hz),
-                                          createDisplayMode(DisplayModeId(1), 53_Hz),
-                                          createDisplayMode(DisplayModeId(2), 55_Hz),
-                                          createDisplayMode(DisplayModeId(3), 60_Hz));
-
-    const RefreshRateConfigs::GlobalSignals globalSignals = {.touch = false, .idle = false};
-    const TestableRefreshRateConfigs configs(std::move(displayModes), kActiveModeId);
-
-    const std::vector<LayerRequirement> layers = {
-            {
-                    .vote = LayerVoteType::ExplicitDefault,
-                    .desiredRefreshRate = 43_Hz,
-                    .seamlessness = Seamlessness::SeamedAndSeamless,
-                    .weight = 0.41f,
-            },
-            {
-                    .vote = LayerVoteType::ExplicitExactOrMultiple,
-                    .desiredRefreshRate = 53_Hz,
-                    .seamlessness = Seamlessness::SeamedAndSeamless,
-                    .weight = 0.41f,
-            },
-    };
-
-    EXPECT_EQ(53_Hz, configs.getBestRefreshRate(layers, globalSignals)->getFps());
-}
-
-TEST_F(RefreshRateConfigsTest, modeComparison) {
-    EXPECT_LT(kMode60->getFps(), kMode90->getFps());
-    EXPECT_GE(kMode60->getFps(), kMode60->getFps());
-    EXPECT_GE(kMode90->getFps(), kMode90->getFps());
-}
-
-TEST_F(RefreshRateConfigsTest, testKernelIdleTimerAction) {
-    using KernelIdleTimerAction = RefreshRateConfigs::KernelIdleTimerAction;
-
-    TestableRefreshRateConfigs configs(kModes_60_90, kModeId90);
-
-    // setPolicy(60, 90), current 90Hz => TurnOn.
-    EXPECT_EQ(KernelIdleTimerAction::TurnOn, configs.getIdleTimerAction());
-
-    // setPolicy(60, 90), current 60Hz => TurnOn.
-    EXPECT_EQ(SetPolicyResult::Changed,
-              configs.setDisplayManagerPolicy({kModeId60, {60_Hz, 90_Hz}}));
-    EXPECT_EQ(KernelIdleTimerAction::TurnOn, configs.getIdleTimerAction());
-
-    // setPolicy(60, 60), current 60Hz => TurnOff
-    EXPECT_EQ(SetPolicyResult::Changed,
-              configs.setDisplayManagerPolicy({kModeId60, {60_Hz, 60_Hz}}));
-    EXPECT_EQ(KernelIdleTimerAction::TurnOff, configs.getIdleTimerAction());
-
-    // setPolicy(90, 90), current 90Hz => TurnOff.
-    EXPECT_EQ(SetPolicyResult::Changed,
-              configs.setDisplayManagerPolicy({kModeId90, {90_Hz, 90_Hz}}));
-    EXPECT_EQ(KernelIdleTimerAction::TurnOff, configs.getIdleTimerAction());
-}
-
-TEST_F(RefreshRateConfigsTest, testKernelIdleTimerActionFor120Hz) {
-    using KernelIdleTimerAction = RefreshRateConfigs::KernelIdleTimerAction;
-
-    TestableRefreshRateConfigs configs(kModes_60_120, kModeId120);
-
-    // setPolicy(0, 60), current 60Hz => TurnOn.
-    EXPECT_EQ(SetPolicyResult::Changed,
-              configs.setDisplayManagerPolicy({kModeId60, {0_Hz, 60_Hz}}));
-    EXPECT_EQ(KernelIdleTimerAction::TurnOn, configs.getIdleTimerAction());
-
-    // setPolicy(60, 60), current 60Hz => TurnOff.
-    EXPECT_EQ(SetPolicyResult::Changed,
-              configs.setDisplayManagerPolicy({kModeId60, {60_Hz, 60_Hz}}));
-    EXPECT_EQ(KernelIdleTimerAction::TurnOff, configs.getIdleTimerAction());
-
-    // setPolicy(60, 120), current 60Hz => TurnOn.
-    EXPECT_EQ(SetPolicyResult::Changed,
-              configs.setDisplayManagerPolicy({kModeId60, {60_Hz, 120_Hz}}));
-    EXPECT_EQ(KernelIdleTimerAction::TurnOn, configs.getIdleTimerAction());
-
-    // setPolicy(120, 120), current 120Hz => TurnOff.
-    EXPECT_EQ(SetPolicyResult::Changed,
-              configs.setDisplayManagerPolicy({kModeId120, {120_Hz, 120_Hz}}));
-    EXPECT_EQ(KernelIdleTimerAction::TurnOff, configs.getIdleTimerAction());
-}
-
-TEST_F(RefreshRateConfigsTest, getFrameRateDivisor) {
-    TestableRefreshRateConfigs configs(kModes_30_60_72_90_120, kModeId30);
-
-    const auto frameRate = 30_Hz;
-    Fps displayRefreshRate = configs.getActiveMode().getFps();
-    EXPECT_EQ(1, RefreshRateConfigs::getFrameRateDivisor(displayRefreshRate, frameRate));
-
-    configs.setActiveModeId(kModeId60);
-    displayRefreshRate = configs.getActiveMode().getFps();
-    EXPECT_EQ(2, RefreshRateConfigs::getFrameRateDivisor(displayRefreshRate, frameRate));
-
-    configs.setActiveModeId(kModeId72);
-    displayRefreshRate = configs.getActiveMode().getFps();
-    EXPECT_EQ(0, RefreshRateConfigs::getFrameRateDivisor(displayRefreshRate, frameRate));
-
-    configs.setActiveModeId(kModeId90);
-    displayRefreshRate = configs.getActiveMode().getFps();
-    EXPECT_EQ(3, RefreshRateConfigs::getFrameRateDivisor(displayRefreshRate, frameRate));
-
-    configs.setActiveModeId(kModeId120);
-    displayRefreshRate = configs.getActiveMode().getFps();
-    EXPECT_EQ(4, RefreshRateConfigs::getFrameRateDivisor(displayRefreshRate, frameRate));
-
-    configs.setActiveModeId(kModeId90);
-    displayRefreshRate = configs.getActiveMode().getFps();
-    EXPECT_EQ(4, RefreshRateConfigs::getFrameRateDivisor(displayRefreshRate, 22.5_Hz));
-
-    EXPECT_EQ(0, RefreshRateConfigs::getFrameRateDivisor(24_Hz, 25_Hz));
-    EXPECT_EQ(0, RefreshRateConfigs::getFrameRateDivisor(24_Hz, 23.976_Hz));
-    EXPECT_EQ(0, RefreshRateConfigs::getFrameRateDivisor(30_Hz, 29.97_Hz));
-    EXPECT_EQ(0, RefreshRateConfigs::getFrameRateDivisor(60_Hz, 59.94_Hz));
-}
-
-TEST_F(RefreshRateConfigsTest, isFractionalPairOrMultiple) {
-    EXPECT_TRUE(RefreshRateConfigs::isFractionalPairOrMultiple(23.976_Hz, 24_Hz));
-    EXPECT_TRUE(RefreshRateConfigs::isFractionalPairOrMultiple(24_Hz, 23.976_Hz));
-
-    EXPECT_TRUE(RefreshRateConfigs::isFractionalPairOrMultiple(29.97_Hz, 30_Hz));
-    EXPECT_TRUE(RefreshRateConfigs::isFractionalPairOrMultiple(30_Hz, 29.97_Hz));
-
-    EXPECT_TRUE(RefreshRateConfigs::isFractionalPairOrMultiple(59.94_Hz, 60_Hz));
-    EXPECT_TRUE(RefreshRateConfigs::isFractionalPairOrMultiple(60_Hz, 59.94_Hz));
-
-    EXPECT_TRUE(RefreshRateConfigs::isFractionalPairOrMultiple(29.97_Hz, 60_Hz));
-    EXPECT_TRUE(RefreshRateConfigs::isFractionalPairOrMultiple(60_Hz, 29.97_Hz));
-
-    EXPECT_TRUE(RefreshRateConfigs::isFractionalPairOrMultiple(59.94_Hz, 30_Hz));
-    EXPECT_TRUE(RefreshRateConfigs::isFractionalPairOrMultiple(30_Hz, 59.94_Hz));
-
-    const auto refreshRates = {23.976_Hz, 24_Hz, 25_Hz, 29.97_Hz, 30_Hz, 50_Hz, 59.94_Hz, 60_Hz};
-    for (auto refreshRate : refreshRates) {
-        EXPECT_FALSE(RefreshRateConfigs::isFractionalPairOrMultiple(refreshRate, refreshRate));
-    }
-
-    EXPECT_FALSE(RefreshRateConfigs::isFractionalPairOrMultiple(24_Hz, 25_Hz));
-    EXPECT_FALSE(RefreshRateConfigs::isFractionalPairOrMultiple(23.978_Hz, 25_Hz));
-    EXPECT_FALSE(RefreshRateConfigs::isFractionalPairOrMultiple(29.97_Hz, 59.94_Hz));
-}
-
-TEST_F(RefreshRateConfigsTest, getFrameRateOverrides_noLayers) {
-    RefreshRateConfigs configs(kModes_30_60_72_90_120, kModeId120);
-
-    EXPECT_TRUE(configs.getFrameRateOverrides({}, 120_Hz, {}).empty());
-}
-
-TEST_F(RefreshRateConfigsTest, getFrameRateOverrides_60on120) {
-    RefreshRateConfigs configs(kModes_30_60_72_90_120, kModeId120,
-                               {.enableFrameRateOverride = true});
-
-    std::vector<LayerRequirement> layers = {{.weight = 1.f}};
-    layers[0].name = "Test layer";
-    layers[0].ownerUid = 1234;
-    layers[0].desiredRefreshRate = 60_Hz;
-    layers[0].vote = LayerVoteType::ExplicitDefault;
-
-    auto frameRateOverrides = configs.getFrameRateOverrides(layers, 120_Hz, {});
-    EXPECT_EQ(1u, frameRateOverrides.size());
-    ASSERT_EQ(1u, frameRateOverrides.count(1234));
-    EXPECT_EQ(60_Hz, frameRateOverrides.at(1234));
-
-    layers[0].vote = LayerVoteType::ExplicitExactOrMultiple;
-    frameRateOverrides = configs.getFrameRateOverrides(layers, 120_Hz, {});
-    EXPECT_EQ(1u, frameRateOverrides.size());
-    ASSERT_EQ(1u, frameRateOverrides.count(1234));
-    EXPECT_EQ(60_Hz, frameRateOverrides.at(1234));
-
-    layers[0].vote = LayerVoteType::NoVote;
-    frameRateOverrides = configs.getFrameRateOverrides(layers, 120_Hz, {});
-    EXPECT_TRUE(frameRateOverrides.empty());
-
-    layers[0].vote = LayerVoteType::Min;
-    frameRateOverrides = configs.getFrameRateOverrides(layers, 120_Hz, {});
-    EXPECT_TRUE(frameRateOverrides.empty());
-
-    layers[0].vote = LayerVoteType::Max;
-    frameRateOverrides = configs.getFrameRateOverrides(layers, 120_Hz, {});
-    EXPECT_TRUE(frameRateOverrides.empty());
-
-    layers[0].vote = LayerVoteType::Heuristic;
-    frameRateOverrides = configs.getFrameRateOverrides(layers, 120_Hz, {});
-    EXPECT_TRUE(frameRateOverrides.empty());
-}
-
-TEST_F(RefreshRateConfigsTest, getFrameRateOverrides_twoUids) {
-    RefreshRateConfigs configs(kModes_30_60_72_90_120, kModeId120,
-                               {.enableFrameRateOverride = true});
-
-    std::vector<LayerRequirement> layers = {{.ownerUid = 1234, .weight = 1.f},
-                                            {.ownerUid = 5678, .weight = 1.f}};
-
-    layers[0].name = "Test layer 1234";
-    layers[0].desiredRefreshRate = 60_Hz;
-    layers[0].vote = LayerVoteType::ExplicitDefault;
-
-    layers[1].name = "Test layer 5678";
-    layers[1].desiredRefreshRate = 30_Hz;
-    layers[1].vote = LayerVoteType::ExplicitDefault;
-    auto frameRateOverrides = configs.getFrameRateOverrides(layers, 120_Hz, {});
-
-    EXPECT_EQ(2u, frameRateOverrides.size());
-    ASSERT_EQ(1u, frameRateOverrides.count(1234));
-    EXPECT_EQ(60_Hz, frameRateOverrides.at(1234));
-    ASSERT_EQ(1u, frameRateOverrides.count(5678));
-    EXPECT_EQ(30_Hz, frameRateOverrides.at(5678));
-
-    layers[1].vote = LayerVoteType::Heuristic;
-    frameRateOverrides = configs.getFrameRateOverrides(layers, 120_Hz, {});
-    EXPECT_EQ(1u, frameRateOverrides.size());
-    ASSERT_EQ(1u, frameRateOverrides.count(1234));
-    EXPECT_EQ(60_Hz, frameRateOverrides.at(1234));
-
-    layers[1].ownerUid = 1234;
-    frameRateOverrides = configs.getFrameRateOverrides(layers, 120_Hz, {});
-    EXPECT_TRUE(frameRateOverrides.empty());
-}
-
-TEST_F(RefreshRateConfigsTest, getFrameRateOverrides_touch) {
-    RefreshRateConfigs configs(kModes_30_60_72_90_120, kModeId120,
-                               {.enableFrameRateOverride = true});
-
-    std::vector<LayerRequirement> layers = {{.ownerUid = 1234, .weight = 1.f}};
-    layers[0].name = "Test layer";
-    layers[0].desiredRefreshRate = 60_Hz;
-    layers[0].vote = LayerVoteType::ExplicitDefault;
-
-    auto frameRateOverrides = configs.getFrameRateOverrides(layers, 120_Hz, {});
-    EXPECT_EQ(1u, frameRateOverrides.size());
-    ASSERT_EQ(1u, frameRateOverrides.count(1234));
-    EXPECT_EQ(60_Hz, frameRateOverrides.at(1234));
-
-    frameRateOverrides = configs.getFrameRateOverrides(layers, 120_Hz, {.touch = true});
-    EXPECT_EQ(1u, frameRateOverrides.size());
-    ASSERT_EQ(1u, frameRateOverrides.count(1234));
-    EXPECT_EQ(60_Hz, frameRateOverrides.at(1234));
-
-    layers[0].vote = LayerVoteType::ExplicitExact;
-    frameRateOverrides = configs.getFrameRateOverrides(layers, 120_Hz, {});
-    EXPECT_EQ(1u, frameRateOverrides.size());
-    ASSERT_EQ(1u, frameRateOverrides.count(1234));
-    EXPECT_EQ(60_Hz, frameRateOverrides.at(1234));
-
-    frameRateOverrides = configs.getFrameRateOverrides(layers, 120_Hz, {.touch = true});
-    EXPECT_EQ(1u, frameRateOverrides.size());
-    ASSERT_EQ(1u, frameRateOverrides.count(1234));
-    EXPECT_EQ(60_Hz, frameRateOverrides.at(1234));
-
-    layers[0].vote = LayerVoteType::ExplicitExactOrMultiple;
-    frameRateOverrides = configs.getFrameRateOverrides(layers, 120_Hz, {});
-    EXPECT_EQ(1u, frameRateOverrides.size());
-    ASSERT_EQ(1u, frameRateOverrides.count(1234));
-    EXPECT_EQ(60_Hz, frameRateOverrides.at(1234));
-
-    frameRateOverrides = configs.getFrameRateOverrides(layers, 120_Hz, {.touch = true});
-    EXPECT_TRUE(frameRateOverrides.empty());
-}
-
-} // namespace
-} // namespace android::scheduler
diff --git a/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp b/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp
new file mode 100644
index 0000000..a3b3c4c
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp
@@ -0,0 +1,2960 @@
+/*
+ * Copyright 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.
+ */
+
+#undef LOG_TAG
+#define LOG_TAG "SchedulerUnittests"
+
+#include <algorithm>
+#include <array>
+
+#include <ftl/enum.h>
+#include <ftl/fake_guard.h>
+#include <gmock/gmock.h>
+#include <log/log.h>
+#include <ui/Size.h>
+
+#include <scheduler/FrameRateMode.h>
+#include "DisplayHardware/HWC2.h"
+#include "FpsOps.h"
+#include "Scheduler/RefreshRateSelector.h"
+#include "mock/DisplayHardware/MockDisplayMode.h"
+#include "mock/MockFrameRateMode.h"
+
+#include "libsurfaceflinger_unittest_main.h"
+
+using namespace std::chrono_literals;
+
+namespace android::scheduler {
+
+namespace hal = android::hardware::graphics::composer::hal;
+
+using Config = RefreshRateSelector::Config;
+using LayerRequirement = RefreshRateSelector::LayerRequirement;
+using LayerVoteType = RefreshRateSelector::LayerVoteType;
+using SetPolicyResult = RefreshRateSelector::SetPolicyResult;
+
+using mock::createDisplayMode;
+
+struct TestableRefreshRateSelector : RefreshRateSelector {
+    using RefreshRateSelector::FrameRateRanking;
+    using RefreshRateSelector::RefreshRateOrder;
+
+    using RefreshRateSelector::RefreshRateSelector;
+
+    void setActiveMode(DisplayModeId modeId, Fps renderFrameRate) {
+        ftl::FakeGuard guard(kMainThreadContext);
+        return RefreshRateSelector::setActiveMode(modeId, renderFrameRate);
+    }
+
+    const DisplayMode& getActiveMode() const {
+        std::lock_guard lock(mLock);
+        return *RefreshRateSelector::getActiveModeLocked().modePtr;
+    }
+
+    ftl::NonNull<DisplayModePtr> getMinSupportedRefreshRate() const {
+        std::lock_guard lock(mLock);
+        return ftl::as_non_null(mMinRefreshRateModeIt->second);
+    }
+
+    ftl::NonNull<DisplayModePtr> getMaxSupportedRefreshRate() const {
+        std::lock_guard lock(mLock);
+        return ftl::as_non_null(mMaxRefreshRateModeIt->second);
+    }
+
+    ftl::NonNull<DisplayModePtr> getMinRefreshRateByPolicy() const {
+        std::lock_guard lock(mLock);
+        return ftl::as_non_null(getMinRefreshRateByPolicyLocked());
+    }
+
+    ftl::NonNull<DisplayModePtr> getMaxRefreshRateByPolicy() const {
+        std::lock_guard lock(mLock);
+        return ftl::as_non_null(
+                getMaxRefreshRateByPolicyLocked(getActiveModeLocked().modePtr->getGroup()));
+    }
+
+    FrameRateRanking rankRefreshRates(std::optional<int> anchorGroupOpt,
+                                      RefreshRateOrder refreshRateOrder) const {
+        std::lock_guard lock(mLock);
+        return RefreshRateSelector::rankFrameRates(anchorGroupOpt, refreshRateOrder);
+    }
+
+    const std::vector<Fps>& knownFrameRates() const { return mKnownFrameRates; }
+
+    using RefreshRateSelector::GetRankedFrameRatesCache;
+    auto& mutableGetRankedRefreshRatesCache() { return mGetRankedFrameRatesCache; }
+
+    auto getRankedFrameRates(const std::vector<LayerRequirement>& layers,
+                             GlobalSignals signals) const {
+        const auto result = RefreshRateSelector::getRankedFrameRates(layers, signals);
+
+        EXPECT_TRUE(std::is_sorted(result.ranking.begin(), result.ranking.end(),
+                                   ScoredFrameRate::DescendingScore{}));
+
+        return result;
+    }
+
+    auto getRankedRefreshRatesAsPair(const std::vector<LayerRequirement>& layers,
+                                     GlobalSignals signals) const {
+        const auto [ranking, consideredSignals] = getRankedFrameRates(layers, signals);
+        return std::make_pair(ranking, consideredSignals);
+    }
+
+    ftl::NonNull<DisplayModePtr> getBestFrameRateMode(
+            const std::vector<LayerRequirement>& layers = {}, GlobalSignals signals = {}) const {
+        return getRankedFrameRates(layers, signals).ranking.front().frameRateMode.modePtr;
+    }
+
+    ScoredFrameRate getBestScoredFrameRate(const std::vector<LayerRequirement>& layers = {},
+                                           GlobalSignals signals = {}) const {
+        return getRankedFrameRates(layers, signals).ranking.front();
+    }
+
+    SetPolicyResult setPolicy(const PolicyVariant& policy) {
+        ftl::FakeGuard guard(kMainThreadContext);
+        return RefreshRateSelector::setPolicy(policy);
+    }
+
+    SetPolicyResult setDisplayManagerPolicy(const DisplayManagerPolicy& policy) {
+        return setPolicy(policy);
+    }
+
+    const auto& getPrimaryFrameRates() const { return mPrimaryFrameRates; }
+};
+
+class RefreshRateSelectorTest : public testing::TestWithParam<Config::FrameRateOverride> {
+protected:
+    using RefreshRateOrder = TestableRefreshRateSelector::RefreshRateOrder;
+
+    RefreshRateSelectorTest();
+    ~RefreshRateSelectorTest();
+
+    static constexpr DisplayModeId kModeId60{0};
+    static constexpr DisplayModeId kModeId90{1};
+    static constexpr DisplayModeId kModeId72{2};
+    static constexpr DisplayModeId kModeId120{3};
+    static constexpr DisplayModeId kModeId30{4};
+    static constexpr DisplayModeId kModeId25{5};
+    static constexpr DisplayModeId kModeId50{6};
+    static constexpr DisplayModeId kModeId24{7};
+    static constexpr DisplayModeId kModeId24Frac{8};
+    static constexpr DisplayModeId kModeId30Frac{9};
+    static constexpr DisplayModeId kModeId60Frac{10};
+    static constexpr DisplayModeId kModeId35{11};
+
+    static inline const ftl::NonNull<DisplayModePtr> kMode60 =
+            ftl::as_non_null(createDisplayMode(kModeId60, 60_Hz));
+    static inline const ftl::NonNull<DisplayModePtr> kMode60Frac =
+            ftl::as_non_null(createDisplayMode(kModeId60Frac, 59.94_Hz));
+    static inline const ftl::NonNull<DisplayModePtr> kMode90 =
+            ftl::as_non_null(createDisplayMode(kModeId90, 90_Hz));
+    static inline const ftl::NonNull<DisplayModePtr> kMode90_G1 =
+            ftl::as_non_null(createDisplayMode(kModeId90, 90_Hz, 1));
+    static inline const ftl::NonNull<DisplayModePtr> kMode90_4K =
+            ftl::as_non_null(createDisplayMode(kModeId90, 90_Hz, 0, {3840, 2160}));
+    static inline const ftl::NonNull<DisplayModePtr> kMode72 =
+            ftl::as_non_null(createDisplayMode(kModeId72, 72_Hz));
+    static inline const ftl::NonNull<DisplayModePtr> kMode72_G1 =
+            ftl::as_non_null(createDisplayMode(kModeId72, 72_Hz, 1));
+    static inline const ftl::NonNull<DisplayModePtr> kMode120 =
+            ftl::as_non_null(createDisplayMode(kModeId120, 120_Hz));
+    static inline const ftl::NonNull<DisplayModePtr> kMode120_G1 =
+            ftl::as_non_null(createDisplayMode(kModeId120, 120_Hz, 1));
+    static inline const ftl::NonNull<DisplayModePtr> kMode30 =
+            ftl::as_non_null(createDisplayMode(kModeId30, 30_Hz));
+    static inline const ftl::NonNull<DisplayModePtr> kMode30_G1 =
+            ftl::as_non_null(createDisplayMode(kModeId30, 30_Hz, 1));
+    static inline const ftl::NonNull<DisplayModePtr> kMode30Frac =
+            ftl::as_non_null(createDisplayMode(kModeId30Frac, 29.97_Hz));
+    static inline const ftl::NonNull<DisplayModePtr> kMode25 =
+            ftl::as_non_null(createDisplayMode(kModeId25, 25_Hz));
+    static inline const ftl::NonNull<DisplayModePtr> kMode25_G1 =
+            ftl::as_non_null(createDisplayMode(kModeId25, 25_Hz, 1));
+    static inline const ftl::NonNull<DisplayModePtr> kMode35 =
+            ftl::as_non_null(createDisplayMode(kModeId35, 35_Hz));
+    static inline const ftl::NonNull<DisplayModePtr> kMode50 =
+            ftl::as_non_null(createDisplayMode(kModeId50, 50_Hz));
+    static inline const ftl::NonNull<DisplayModePtr> kMode24 =
+            ftl::as_non_null(createDisplayMode(kModeId24, 24_Hz));
+    static inline const ftl::NonNull<DisplayModePtr> kMode24Frac =
+            ftl::as_non_null(createDisplayMode(kModeId24Frac, 23.976_Hz));
+
+    // Test configurations.
+    static inline const DisplayModes kModes_60 = makeModes(kMode60);
+    static inline const DisplayModes kModes_35_60_90 = makeModes(kMode35, kMode60, kMode90);
+    static inline const DisplayModes kModes_60_90 = makeModes(kMode60, kMode90);
+    static inline const DisplayModes kModes_60_90_G1 = makeModes(kMode60, kMode90_G1);
+    static inline const DisplayModes kModes_60_90_4K = makeModes(kMode60, kMode90_4K);
+    static inline const DisplayModes kModes_60_72_90 = makeModes(kMode60, kMode90, kMode72);
+    static inline const DisplayModes kModes_60_90_72_120 =
+            makeModes(kMode60, kMode90, kMode72, kMode120);
+    static inline const DisplayModes kModes_30_60_72_90_120 =
+            makeModes(kMode60, kMode90, kMode72, kMode120, kMode30);
+
+    static inline const DisplayModes kModes_30_60 =
+            makeModes(kMode60, kMode90_G1, kMode72_G1, kMode120_G1, kMode30);
+    static inline const DisplayModes kModes_30_60_72_90 =
+            makeModes(kMode60, kMode90, kMode72, kMode120_G1, kMode30);
+    static inline const DisplayModes kModes_30_60_90 =
+            makeModes(kMode60, kMode90, kMode72_G1, kMode120_G1, kMode30);
+    static inline const DisplayModes kModes_25_30_50_60 =
+            makeModes(kMode60, kMode90, kMode72_G1, kMode120_G1, kMode30_G1, kMode25_G1, kMode50);
+    static inline const DisplayModes kModes_60_120 = makeModes(kMode60, kMode120);
+
+    // This is a typical TV configuration.
+    static inline const DisplayModes kModes_24_25_30_50_60_Frac =
+            makeModes(kMode24, kMode24Frac, kMode25, kMode30, kMode30Frac, kMode50, kMode60,
+                      kMode60Frac);
+
+    static TestableRefreshRateSelector createSelector(DisplayModes modes,
+                                                      DisplayModeId activeModeId,
+                                                      Config config = {}) {
+        config.enableFrameRateOverride = GetParam();
+        return TestableRefreshRateSelector(modes, activeModeId, config);
+    }
+};
+
+RefreshRateSelectorTest::RefreshRateSelectorTest() {
+    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());
+}
+
+RefreshRateSelectorTest::~RefreshRateSelectorTest() {
+    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());
+}
+
+namespace {
+
+INSTANTIATE_TEST_SUITE_P(PerOverrideConfig, RefreshRateSelectorTest,
+                         testing::Values(Config::FrameRateOverride::Disabled,
+                                         Config::FrameRateOverride::AppOverrideNativeRefreshRates,
+                                         Config::FrameRateOverride::AppOverride,
+                                         Config::FrameRateOverride::Enabled));
+
+TEST_P(RefreshRateSelectorTest, oneMode_canSwitch) {
+    auto selector = createSelector(kModes_60, kModeId60);
+    if (GetParam() == Config::FrameRateOverride::Enabled) {
+        EXPECT_TRUE(selector.canSwitch());
+    } else {
+        EXPECT_FALSE(selector.canSwitch());
+    }
+}
+
+TEST_P(RefreshRateSelectorTest, invalidPolicy) {
+    auto selector = createSelector(kModes_60, kModeId60);
+
+    EXPECT_EQ(SetPolicyResult::Invalid,
+              selector.setDisplayManagerPolicy({DisplayModeId(10), {60_Hz, 60_Hz}}));
+    EXPECT_EQ(SetPolicyResult::Invalid,
+              selector.setDisplayManagerPolicy({kModeId60, {20_Hz, 40_Hz}}));
+}
+
+TEST_P(RefreshRateSelectorTest, unchangedPolicy) {
+    auto selector = createSelector(kModes_60_90, kModeId60);
+
+    EXPECT_EQ(SetPolicyResult::Changed,
+              selector.setDisplayManagerPolicy({kModeId90, {60_Hz, 90_Hz}}));
+
+    EXPECT_EQ(SetPolicyResult::Unchanged,
+              selector.setDisplayManagerPolicy({kModeId90, {60_Hz, 90_Hz}}));
+
+    // Override to the same policy.
+    EXPECT_EQ(SetPolicyResult::Unchanged,
+              selector.setPolicy(RefreshRateSelector::OverridePolicy{kModeId90, {60_Hz, 90_Hz}}));
+
+    // Clear override to restore DisplayManagerPolicy.
+    EXPECT_EQ(SetPolicyResult::Unchanged,
+              selector.setPolicy(RefreshRateSelector::NoOverridePolicy{}));
+
+    EXPECT_EQ(SetPolicyResult::Changed,
+              selector.setDisplayManagerPolicy({kModeId90, {30_Hz, 90_Hz}}));
+}
+
+TEST_P(RefreshRateSelectorTest, twoModes_storesFullRefreshRateMap) {
+    auto selector = createSelector(kModes_60_90, kModeId60);
+
+    const auto minRate = selector.getMinSupportedRefreshRate();
+    const auto performanceRate = selector.getMaxSupportedRefreshRate();
+
+    EXPECT_EQ(kMode60, minRate);
+    EXPECT_EQ(kMode90, performanceRate);
+
+    const auto minRateByPolicy = selector.getMinRefreshRateByPolicy();
+    const auto performanceRateByPolicy = selector.getMaxRefreshRateByPolicy();
+
+    EXPECT_EQ(minRateByPolicy, minRate);
+    EXPECT_EQ(performanceRateByPolicy, performanceRate);
+}
+
+TEST_P(RefreshRateSelectorTest, twoModes_storesFullRefreshRateMap_differentGroups) {
+    auto selector = createSelector(kModes_60_90_G1, kModeId60);
+
+    const auto minRate = selector.getMinRefreshRateByPolicy();
+    const auto performanceRate = selector.getMaxSupportedRefreshRate();
+    const auto minRate60 = selector.getMinRefreshRateByPolicy();
+    const auto performanceRate60 = selector.getMaxRefreshRateByPolicy();
+
+    EXPECT_EQ(kMode60, minRate);
+    EXPECT_EQ(kMode60, minRate60);
+    EXPECT_EQ(kMode60, performanceRate60);
+
+    EXPECT_EQ(SetPolicyResult::Changed,
+              selector.setDisplayManagerPolicy({kModeId90, {60_Hz, 90_Hz}}));
+    selector.setActiveMode(kModeId90, 90_Hz);
+
+    const auto minRate90 = selector.getMinRefreshRateByPolicy();
+    const auto performanceRate90 = selector.getMaxRefreshRateByPolicy();
+
+    EXPECT_EQ(kMode90_G1, performanceRate);
+    EXPECT_EQ(kMode90_G1, minRate90);
+    EXPECT_EQ(kMode90_G1, performanceRate90);
+}
+
+TEST_P(RefreshRateSelectorTest, twoModes_storesFullRefreshRateMap_differentResolutions) {
+    auto selector = createSelector(kModes_60_90_4K, kModeId60);
+
+    const auto minRate = selector.getMinRefreshRateByPolicy();
+    const auto performanceRate = selector.getMaxSupportedRefreshRate();
+    const auto minRate60 = selector.getMinRefreshRateByPolicy();
+    const auto performanceRate60 = selector.getMaxRefreshRateByPolicy();
+
+    EXPECT_EQ(kMode60, minRate);
+    EXPECT_EQ(kMode60, minRate60);
+    EXPECT_EQ(kMode60, performanceRate60);
+
+    EXPECT_EQ(SetPolicyResult::Changed,
+              selector.setDisplayManagerPolicy({kModeId90, {60_Hz, 90_Hz}}));
+    selector.setActiveMode(kModeId90, 90_Hz);
+
+    const auto minRate90 = selector.getMinRefreshRateByPolicy();
+    const auto performanceRate90 = selector.getMaxRefreshRateByPolicy();
+
+    EXPECT_EQ(kMode90_4K, performanceRate);
+    EXPECT_EQ(kMode90_4K, minRate90);
+    EXPECT_EQ(kMode90_4K, performanceRate90);
+}
+
+TEST_P(RefreshRateSelectorTest, twoModes_policyChange) {
+    auto selector = createSelector(kModes_60_90, kModeId60);
+
+    const auto minRate = selector.getMinRefreshRateByPolicy();
+    const auto performanceRate = selector.getMaxRefreshRateByPolicy();
+
+    EXPECT_EQ(kMode60, minRate);
+    EXPECT_EQ(kMode90, performanceRate);
+
+    EXPECT_EQ(SetPolicyResult::Changed,
+              selector.setDisplayManagerPolicy({kModeId60, {60_Hz, 60_Hz}}));
+
+    const auto minRate60 = selector.getMinRefreshRateByPolicy();
+    const auto performanceRate60 = selector.getMaxRefreshRateByPolicy();
+
+    EXPECT_EQ(kMode60, minRate60);
+    EXPECT_EQ(kMode60, performanceRate60);
+}
+
+TEST_P(RefreshRateSelectorTest, twoModes_getActiveMode) {
+    auto selector = createSelector(kModes_60_90, kModeId60);
+    {
+        const auto& mode = selector.getActiveMode();
+        EXPECT_EQ(mode.getId(), kModeId60);
+    }
+
+    selector.setActiveMode(kModeId90, 90_Hz);
+    {
+        const auto& mode = selector.getActiveMode();
+        EXPECT_EQ(mode.getId(), kModeId90);
+    }
+
+    EXPECT_EQ(SetPolicyResult::Changed,
+              selector.setDisplayManagerPolicy({kModeId90, {90_Hz, 90_Hz}}));
+    {
+        const auto& mode = selector.getActiveMode();
+        EXPECT_EQ(mode.getId(), kModeId90);
+    }
+}
+
+TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_noLayers) {
+    {
+        auto selector = createSelector(kModes_60_72_90, kModeId72);
+
+        // If there are no layers we select the default frame rate, which is the max of the primary
+        // range.
+        EXPECT_EQ(kMode90, selector.getBestFrameRateMode());
+
+        EXPECT_EQ(SetPolicyResult::Changed,
+                  selector.setDisplayManagerPolicy({kModeId60, {60_Hz, 60_Hz}}));
+        EXPECT_EQ(kMode60, selector.getBestFrameRateMode());
+    }
+    {
+        // We select max even when this will cause a non-seamless switch.
+        auto selector = createSelector(kModes_60_90_G1, kModeId60);
+        constexpr bool kAllowGroupSwitching = true;
+        EXPECT_EQ(SetPolicyResult::Changed,
+                  selector.setDisplayManagerPolicy(
+                          {kModeId90, {0_Hz, 90_Hz}, kAllowGroupSwitching}));
+        EXPECT_EQ(kMode90_G1, selector.getBestFrameRateMode());
+    }
+}
+
+TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_exactDontChangeRefreshRateWhenNotInPolicy) {
+    auto selector = createSelector(kModes_30_60_72_90_120, kModeId72);
+
+    std::vector<LayerRequirement> layers = {{.weight = 1.f}};
+    layers[0].vote = LayerVoteType::ExplicitExact;
+    layers[0].desiredRefreshRate = 120_Hz;
+
+    EXPECT_EQ(SetPolicyResult::Changed,
+              selector.setDisplayManagerPolicy({kModeId72, {0_Hz, 90_Hz}}));
+    EXPECT_EQ(kMode72, selector.getBestFrameRateMode(layers));
+}
+
+TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_60_90) {
+    auto selector = createSelector(kModes_60_90, kModeId60);
+
+    std::vector<LayerRequirement> layers = {{.weight = 1.f}};
+    auto& lr = layers[0];
+
+    lr.vote = LayerVoteType::Min;
+    lr.name = "Min";
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
+
+    lr.vote = LayerVoteType::Max;
+    lr.name = "Max";
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+
+    lr.desiredRefreshRate = 90_Hz;
+    lr.vote = LayerVoteType::Heuristic;
+    lr.name = "90Hz Heuristic";
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+
+    lr.desiredRefreshRate = 60_Hz;
+    lr.name = "60Hz Heuristic";
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
+
+    lr.desiredRefreshRate = 45_Hz;
+    lr.name = "45Hz Heuristic";
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+
+    lr.desiredRefreshRate = 30_Hz;
+    lr.name = "30Hz Heuristic";
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
+
+    lr.desiredRefreshRate = 24_Hz;
+    lr.name = "24Hz Heuristic";
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
+
+    lr.name = "";
+    EXPECT_EQ(SetPolicyResult::Changed,
+              selector.setDisplayManagerPolicy({kModeId60, {60_Hz, 60_Hz}}));
+
+    lr.vote = LayerVoteType::Min;
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
+
+    lr.vote = LayerVoteType::Max;
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
+
+    lr.desiredRefreshRate = 90_Hz;
+    lr.vote = LayerVoteType::Heuristic;
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
+
+    lr.desiredRefreshRate = 60_Hz;
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
+
+    lr.desiredRefreshRate = 45_Hz;
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
+
+    lr.desiredRefreshRate = 30_Hz;
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
+
+    lr.desiredRefreshRate = 24_Hz;
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
+
+    EXPECT_EQ(SetPolicyResult::Changed,
+              selector.setDisplayManagerPolicy({kModeId90, {90_Hz, 90_Hz}}));
+
+    lr.vote = LayerVoteType::Min;
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+
+    lr.vote = LayerVoteType::Max;
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+
+    lr.desiredRefreshRate = 90_Hz;
+    lr.vote = LayerVoteType::Heuristic;
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+
+    lr.desiredRefreshRate = 60_Hz;
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+
+    lr.desiredRefreshRate = 45_Hz;
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+
+    lr.desiredRefreshRate = 30_Hz;
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+
+    lr.desiredRefreshRate = 24_Hz;
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+
+    EXPECT_EQ(SetPolicyResult::Changed,
+              selector.setDisplayManagerPolicy({kModeId60, {0_Hz, 120_Hz}}));
+    lr.vote = LayerVoteType::Min;
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
+
+    lr.vote = LayerVoteType::Max;
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+
+    lr.desiredRefreshRate = 90_Hz;
+    lr.vote = LayerVoteType::Heuristic;
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+
+    lr.desiredRefreshRate = 60_Hz;
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
+
+    lr.desiredRefreshRate = 45_Hz;
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+
+    lr.desiredRefreshRate = 30_Hz;
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
+
+    lr.desiredRefreshRate = 24_Hz;
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
+}
+
+TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_multipleThreshold_60_90) {
+    auto selector = createSelector(kModes_60_90, kModeId60, {.frameRateMultipleThreshold = 90});
+
+    std::vector<LayerRequirement> layers = {{.weight = 1.f}};
+    auto& lr = layers[0];
+
+    lr.vote = LayerVoteType::Min;
+    lr.name = "Min";
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
+
+    lr.vote = LayerVoteType::Max;
+    lr.name = "Max";
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+
+    lr.desiredRefreshRate = 90_Hz;
+    lr.vote = LayerVoteType::Heuristic;
+    lr.name = "90Hz Heuristic";
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+
+    lr.desiredRefreshRate = 60_Hz;
+    lr.name = "60Hz Heuristic";
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
+
+    lr.desiredRefreshRate = 45_Hz;
+    lr.name = "45Hz Heuristic";
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+
+    lr.desiredRefreshRate = 30_Hz;
+    lr.name = "30Hz Heuristic";
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
+
+    lr.desiredRefreshRate = 24_Hz;
+    lr.name = "24Hz Heuristic";
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
+}
+
+TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_60_72_90) {
+    auto selector = createSelector(kModes_60_72_90, kModeId60);
+
+    std::vector<LayerRequirement> layers = {{.weight = 1.f}};
+    auto& lr = layers[0];
+
+    lr.vote = LayerVoteType::Min;
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
+
+    lr.vote = LayerVoteType::Max;
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+
+    lr.desiredRefreshRate = 90_Hz;
+    lr.vote = LayerVoteType::Heuristic;
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+
+    lr.desiredRefreshRate = 60_Hz;
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
+
+    lr.desiredRefreshRate = 45_Hz;
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+
+    lr.desiredRefreshRate = 30_Hz;
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
+
+    lr.desiredRefreshRate = 24_Hz;
+    EXPECT_EQ(kMode72, selector.getBestFrameRateMode(layers));
+}
+
+TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_30_60_72_90_120) {
+    auto selector = createSelector(kModes_30_60_72_90_120, kModeId60);
+
+    std::vector<LayerRequirement> layers = {{.weight = 1.f}, {.weight = 1.f}};
+    auto& lr1 = layers[0];
+    auto& lr2 = layers[1];
+
+    lr1.desiredRefreshRate = 24_Hz;
+    lr1.vote = LayerVoteType::Heuristic;
+    lr2.desiredRefreshRate = 60_Hz;
+    lr2.vote = LayerVoteType::Heuristic;
+    EXPECT_EQ(kMode120, selector.getBestFrameRateMode(layers));
+
+    lr1.desiredRefreshRate = 24_Hz;
+    lr1.vote = LayerVoteType::Heuristic;
+    lr2.desiredRefreshRate = 48_Hz;
+    lr2.vote = LayerVoteType::Heuristic;
+    EXPECT_EQ(kMode72, selector.getBestFrameRateMode(layers));
+
+    lr1.desiredRefreshRate = 24_Hz;
+    lr1.vote = LayerVoteType::Heuristic;
+    lr2.desiredRefreshRate = 48_Hz;
+    lr2.vote = LayerVoteType::Heuristic;
+    EXPECT_EQ(kMode72, selector.getBestFrameRateMode(layers));
+}
+
+TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_30_60_90_120_DifferentTypes) {
+    auto selector = createSelector(kModes_30_60_72_90_120, kModeId60);
+
+    std::vector<LayerRequirement> layers = {{.weight = 1.f}, {.weight = 1.f}};
+    auto& lr1 = layers[0];
+    auto& lr2 = layers[1];
+
+    lr1.desiredRefreshRate = 24_Hz;
+    lr1.vote = LayerVoteType::ExplicitDefault;
+    lr1.name = "24Hz ExplicitDefault";
+    lr2.desiredRefreshRate = 60_Hz;
+    lr2.vote = LayerVoteType::Heuristic;
+    lr2.name = "60Hz Heuristic";
+    EXPECT_EQ(kMode120, selector.getBestFrameRateMode(layers));
+
+    lr1.desiredRefreshRate = 24_Hz;
+    lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
+    lr1.name = "24Hz ExplicitExactOrMultiple";
+    lr2.desiredRefreshRate = 60_Hz;
+    lr2.vote = LayerVoteType::Heuristic;
+    lr2.name = "60Hz Heuristic";
+    EXPECT_EQ(kMode120, selector.getBestFrameRateMode(layers));
+
+    lr1.desiredRefreshRate = 24_Hz;
+    lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
+    lr1.name = "24Hz ExplicitExactOrMultiple";
+    lr2.desiredRefreshRate = 60_Hz;
+    lr2.vote = LayerVoteType::ExplicitDefault;
+    lr2.name = "60Hz ExplicitDefault";
+    EXPECT_EQ(kMode120, selector.getBestFrameRateMode(layers));
+
+    lr1.desiredRefreshRate = 24_Hz;
+    lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
+    lr1.name = "24Hz ExplicitExactOrMultiple";
+    lr2.desiredRefreshRate = 90_Hz;
+    lr2.vote = LayerVoteType::Heuristic;
+    lr2.name = "90Hz Heuristic";
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+
+    lr1.desiredRefreshRate = 24_Hz;
+    lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
+    lr1.name = "24Hz ExplicitExactOrMultiple";
+    lr2.desiredRefreshRate = 90_Hz;
+    lr2.vote = LayerVoteType::ExplicitDefault;
+    lr2.name = "90Hz Heuristic";
+    EXPECT_EQ(kMode72, selector.getBestFrameRateMode(layers));
+
+    lr1.desiredRefreshRate = 24_Hz;
+    lr1.vote = LayerVoteType::ExplicitDefault;
+    lr1.name = "24Hz ExplicitDefault";
+    lr2.desiredRefreshRate = 90_Hz;
+    lr2.vote = LayerVoteType::Heuristic;
+    lr2.name = "90Hz Heuristic";
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+
+    lr1.desiredRefreshRate = 24_Hz;
+    lr1.vote = LayerVoteType::Heuristic;
+    lr1.name = "24Hz Heuristic";
+    lr2.desiredRefreshRate = 90_Hz;
+    lr2.vote = LayerVoteType::ExplicitDefault;
+    lr2.name = "90Hz ExplicitDefault";
+    EXPECT_EQ(kMode72, selector.getBestFrameRateMode(layers));
+
+    lr1.desiredRefreshRate = 24_Hz;
+    lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
+    lr1.name = "24Hz ExplicitExactOrMultiple";
+    lr2.desiredRefreshRate = 90_Hz;
+    lr2.vote = LayerVoteType::ExplicitDefault;
+    lr2.name = "90Hz ExplicitDefault";
+    EXPECT_EQ(kMode72, selector.getBestFrameRateMode(layers));
+
+    lr1.desiredRefreshRate = 24_Hz;
+    lr1.vote = LayerVoteType::ExplicitDefault;
+    lr1.name = "24Hz ExplicitDefault";
+    lr2.desiredRefreshRate = 90_Hz;
+    lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
+    lr2.name = "90Hz ExplicitExactOrMultiple";
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+}
+
+TEST_P(RefreshRateSelectorTest,
+       getBestFrameRateMode_30_60_90_120_DifferentTypes_multipleThreshold) {
+    auto selector =
+            createSelector(kModes_30_60_72_90_120, kModeId60, {.frameRateMultipleThreshold = 120});
+
+    std::vector<LayerRequirement> layers = {{.weight = 1.f}, {.weight = 1.f}, {.weight = 1.f}};
+    auto& lr1 = layers[0];
+    auto& lr2 = layers[1];
+    auto& lr3 = layers[2];
+
+    lr1.desiredRefreshRate = 24_Hz;
+    lr1.vote = LayerVoteType::ExplicitDefault;
+    lr1.name = "24Hz ExplicitDefault";
+    lr2.desiredRefreshRate = 60_Hz;
+    lr2.vote = LayerVoteType::Heuristic;
+    lr2.name = "60Hz Heuristic";
+    EXPECT_EQ(kMode120, selector.getBestFrameRateMode(layers));
+
+    lr1.desiredRefreshRate = 24_Hz;
+    lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
+    lr1.name = "24Hz ExplicitExactOrMultiple";
+    lr2.desiredRefreshRate = 60_Hz;
+    lr2.vote = LayerVoteType::Heuristic;
+    lr2.name = "60Hz Heuristic";
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
+
+    lr1.desiredRefreshRate = 24_Hz;
+    lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
+    lr1.name = "24Hz ExplicitExactOrMultiple";
+    lr2.desiredRefreshRate = 60_Hz;
+    lr2.vote = LayerVoteType::ExplicitDefault;
+    lr2.name = "60Hz ExplicitDefault";
+    EXPECT_EQ(kMode72, selector.getBestFrameRateMode(layers));
+
+    lr1.desiredRefreshRate = 24_Hz;
+    lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
+    lr1.name = "24Hz ExplicitExactOrMultiple";
+    lr2.desiredRefreshRate = 90_Hz;
+    lr2.vote = LayerVoteType::Heuristic;
+    lr2.name = "90Hz Heuristic";
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+
+    lr1.desiredRefreshRate = 24_Hz;
+    lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
+    lr1.name = "24Hz ExplicitExactOrMultiple";
+    lr2.desiredRefreshRate = 90_Hz;
+    lr2.vote = LayerVoteType::ExplicitDefault;
+    lr2.name = "90Hz Heuristic";
+    EXPECT_EQ(kMode72, selector.getBestFrameRateMode(layers));
+
+    lr1.desiredRefreshRate = 24_Hz;
+    lr1.vote = LayerVoteType::ExplicitDefault;
+    lr1.name = "24Hz ExplicitDefault";
+    lr2.desiredRefreshRate = 90_Hz;
+    lr2.vote = LayerVoteType::Heuristic;
+    lr2.name = "90Hz Heuristic";
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+
+    lr1.desiredRefreshRate = 24_Hz;
+    lr1.vote = LayerVoteType::Heuristic;
+    lr1.name = "24Hz Heuristic";
+    lr2.desiredRefreshRate = 90_Hz;
+    lr2.vote = LayerVoteType::ExplicitDefault;
+    lr2.name = "90Hz ExplicitDefault";
+    EXPECT_EQ(kMode72, selector.getBestFrameRateMode(layers));
+
+    lr1.desiredRefreshRate = 24_Hz;
+    lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
+    lr1.name = "24Hz ExplicitExactOrMultiple";
+    lr2.desiredRefreshRate = 90_Hz;
+    lr2.vote = LayerVoteType::ExplicitDefault;
+    lr2.name = "90Hz ExplicitDefault";
+    EXPECT_EQ(kMode72, selector.getBestFrameRateMode(layers));
+
+    lr1.desiredRefreshRate = 24_Hz;
+    lr1.vote = LayerVoteType::ExplicitDefault;
+    lr1.name = "24Hz ExplicitDefault";
+    lr2.desiredRefreshRate = 90_Hz;
+    lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
+    lr2.name = "90Hz ExplicitExactOrMultiple";
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+
+    lr1.desiredRefreshRate = 24_Hz;
+    lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
+    lr1.name = "24Hz ExplicitExactOrMultiple";
+    lr2.vote = LayerVoteType::Max;
+    lr2.name = "Max";
+    EXPECT_EQ(kMode120, selector.getBestFrameRateMode(layers));
+
+    lr1.desiredRefreshRate = 24_Hz;
+    lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
+    lr1.name = "24Hz ExplicitExactOrMultiple";
+    lr2.desiredRefreshRate = 120_Hz;
+    lr2.vote = LayerVoteType::ExplicitDefault;
+    lr2.name = "120Hz ExplicitDefault";
+    EXPECT_EQ(kMode120, selector.getBestFrameRateMode(layers));
+
+    lr1.desiredRefreshRate = 24_Hz;
+    lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
+    lr1.name = "24Hz ExplicitExactOrMultiple";
+    lr2.desiredRefreshRate = 120_Hz;
+    lr2.vote = LayerVoteType::ExplicitExact;
+    lr2.name = "120Hz ExplicitExact";
+    EXPECT_EQ(kMode120, selector.getBestFrameRateMode(layers));
+
+    lr1.desiredRefreshRate = 10_Hz;
+    lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
+    lr1.name = "30Hz ExplicitExactOrMultiple";
+    lr2.desiredRefreshRate = 120_Hz;
+    lr2.vote = LayerVoteType::Heuristic;
+    lr2.name = "120Hz ExplicitExact";
+    EXPECT_EQ(kMode120, selector.getBestFrameRateMode(layers));
+
+    lr1.desiredRefreshRate = 30_Hz;
+    lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
+    lr1.name = "30Hz ExplicitExactOrMultiple";
+    lr2.desiredRefreshRate = 30_Hz;
+    lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
+    lr2.name = "30Hz ExplicitExactOrMultiple";
+    lr3.vote = LayerVoteType::Heuristic;
+    lr3.desiredRefreshRate = 120_Hz;
+    lr3.name = "120Hz Heuristic";
+    EXPECT_EQ(kMode120, selector.getBestFrameRateMode(layers));
+}
+
+TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_30_60) {
+    auto selector = createSelector(kModes_30_60, kModeId60);
+
+    std::vector<LayerRequirement> layers = {{.weight = 1.f}};
+    auto& lr = layers[0];
+
+    lr.vote = LayerVoteType::Min;
+    EXPECT_EQ(kMode30, selector.getBestFrameRateMode(layers));
+
+    lr.vote = LayerVoteType::Max;
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
+
+    lr.desiredRefreshRate = 90_Hz;
+    lr.vote = LayerVoteType::Heuristic;
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
+
+    lr.desiredRefreshRate = 60_Hz;
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
+
+    lr.desiredRefreshRate = 45_Hz;
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
+
+    lr.desiredRefreshRate = 30_Hz;
+    EXPECT_EQ(kMode30, selector.getBestFrameRateMode(layers));
+
+    lr.desiredRefreshRate = 24_Hz;
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
+}
+
+TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_30_60_72_90) {
+    auto selector = createSelector(kModes_30_60_72_90, kModeId60);
+
+    std::vector<LayerRequirement> layers = {{.weight = 1.f}};
+    auto& lr = layers[0];
+
+    lr.vote = LayerVoteType::Min;
+    lr.name = "Min";
+    EXPECT_EQ(kMode30, selector.getBestFrameRateMode(layers));
+
+    lr.vote = LayerVoteType::Max;
+    lr.name = "Max";
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+
+    lr.desiredRefreshRate = 90_Hz;
+    lr.vote = LayerVoteType::Heuristic;
+    lr.name = "90Hz Heuristic";
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+
+    lr.desiredRefreshRate = 60_Hz;
+    lr.name = "60Hz Heuristic";
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers, {.touch = true}));
+
+    lr.desiredRefreshRate = 45_Hz;
+    lr.name = "45Hz Heuristic";
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers, {.touch = true}));
+
+    lr.desiredRefreshRate = 30_Hz;
+    lr.name = "30Hz Heuristic";
+    EXPECT_EQ(kMode30, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers, {.touch = true}));
+
+    lr.desiredRefreshRate = 24_Hz;
+    lr.name = "24Hz Heuristic";
+    EXPECT_EQ(kMode72, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers, {.touch = true}));
+
+    lr.desiredRefreshRate = 24_Hz;
+    lr.vote = LayerVoteType::ExplicitExactOrMultiple;
+    lr.name = "24Hz ExplicitExactOrMultiple";
+    EXPECT_EQ(kMode72, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers, {.touch = true}));
+}
+
+TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_PriorityTest) {
+    auto selector = createSelector(kModes_30_60_90, kModeId60);
+
+    std::vector<LayerRequirement> layers = {{.weight = 1.f}, {.weight = 1.f}};
+    auto& lr1 = layers[0];
+    auto& lr2 = layers[1];
+
+    lr1.vote = LayerVoteType::Min;
+    lr2.vote = LayerVoteType::Max;
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+
+    lr1.vote = LayerVoteType::Min;
+    lr2.vote = LayerVoteType::Heuristic;
+    lr2.desiredRefreshRate = 24_Hz;
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
+
+    lr1.vote = LayerVoteType::Min;
+    lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
+    lr2.desiredRefreshRate = 24_Hz;
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
+
+    lr1.vote = LayerVoteType::Max;
+    lr2.vote = LayerVoteType::Heuristic;
+    lr2.desiredRefreshRate = 60_Hz;
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+
+    lr1.vote = LayerVoteType::Max;
+    lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
+    lr2.desiredRefreshRate = 60_Hz;
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+
+    lr1.vote = LayerVoteType::Heuristic;
+    lr1.desiredRefreshRate = 15_Hz;
+    lr2.vote = LayerVoteType::Heuristic;
+    lr2.desiredRefreshRate = 45_Hz;
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+
+    lr1.vote = LayerVoteType::Heuristic;
+    lr1.desiredRefreshRate = 30_Hz;
+    lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
+    lr2.desiredRefreshRate = 45_Hz;
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+}
+
+TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_24FpsVideo) {
+    auto selector = createSelector(kModes_60_90, kModeId60);
+
+    std::vector<LayerRequirement> layers = {{.weight = 1.f}};
+    auto& lr = layers[0];
+
+    lr.vote = LayerVoteType::ExplicitExactOrMultiple;
+    for (float fps = 23.0f; fps < 25.0f; fps += 0.1f) {
+        lr.desiredRefreshRate = Fps::fromValue(fps);
+        const auto mode = selector.getBestFrameRateMode(layers);
+        EXPECT_EQ(kMode60, mode) << lr.desiredRefreshRate << " chooses "
+                                 << to_string(mode->getFps());
+    }
+}
+
+TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_24FpsVideo_multipleThreshold_60_120) {
+    auto selector = createSelector(kModes_60_120, kModeId60, {.frameRateMultipleThreshold = 120});
+
+    std::vector<LayerRequirement> layers = {{.weight = 1.f}};
+    auto& lr = layers[0];
+
+    lr.vote = LayerVoteType::ExplicitExactOrMultiple;
+    for (float fps = 23.0f; fps < 25.0f; fps += 0.1f) {
+        lr.desiredRefreshRate = Fps::fromValue(fps);
+        const auto mode = selector.getBestFrameRateMode(layers);
+        EXPECT_EQ(kMode60, mode) << lr.desiredRefreshRate << " chooses "
+                                 << to_string(mode->getFps());
+    }
+}
+
+TEST_P(RefreshRateSelectorTest, twoModes_getBestFrameRateMode_Explicit) {
+    auto selector = createSelector(kModes_60_90, kModeId60);
+
+    std::vector<LayerRequirement> layers = {{.weight = 1.f}, {.weight = 1.f}};
+    auto& lr1 = layers[0];
+    auto& lr2 = layers[1];
+
+    lr1.vote = LayerVoteType::Heuristic;
+    lr1.desiredRefreshRate = 60_Hz;
+    lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
+    lr2.desiredRefreshRate = 90_Hz;
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+
+    lr1.vote = LayerVoteType::ExplicitDefault;
+    lr1.desiredRefreshRate = 90_Hz;
+    lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
+    lr2.desiredRefreshRate = 60_Hz;
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
+
+    lr1.vote = LayerVoteType::Heuristic;
+    lr1.desiredRefreshRate = 90_Hz;
+    lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
+    lr2.desiredRefreshRate = 60_Hz;
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+}
+
+TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_75HzContent) {
+    auto selector = createSelector(kModes_60_90, kModeId60);
+
+    std::vector<LayerRequirement> layers = {{.weight = 1.f}};
+    auto& lr = layers[0];
+
+    lr.vote = LayerVoteType::ExplicitExactOrMultiple;
+    for (float fps = 75.0f; fps < 100.0f; fps += 0.1f) {
+        lr.desiredRefreshRate = Fps::fromValue(fps);
+        const auto mode = selector.getBestFrameRateMode(layers, {});
+        EXPECT_EQ(kMode90, mode) << lr.desiredRefreshRate << " chooses "
+                                 << to_string(mode->getFps());
+    }
+}
+
+TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_Multiples) {
+    auto selector = createSelector(kModes_60_90, kModeId60);
+
+    std::vector<LayerRequirement> layers = {{.weight = 1.f}, {.weight = 1.f}};
+    auto& lr1 = layers[0];
+    auto& lr2 = layers[1];
+
+    lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
+    lr1.desiredRefreshRate = 60_Hz;
+    lr1.name = "60Hz ExplicitExactOrMultiple";
+    lr2.vote = LayerVoteType::Heuristic;
+    lr2.desiredRefreshRate = 90_Hz;
+    lr2.name = "90Hz Heuristic";
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+
+    lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
+    lr1.desiredRefreshRate = 60_Hz;
+    lr1.name = "60Hz ExplicitExactOrMultiple";
+    lr2.vote = LayerVoteType::ExplicitDefault;
+    lr2.desiredRefreshRate = 90_Hz;
+    lr2.name = "90Hz ExplicitDefault";
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
+
+    lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
+    lr1.desiredRefreshRate = 60_Hz;
+    lr1.name = "60Hz ExplicitExactOrMultiple";
+    lr2.vote = LayerVoteType::Max;
+    lr2.name = "Max";
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+
+    lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
+    lr1.desiredRefreshRate = 30_Hz;
+    lr1.name = "30Hz ExplicitExactOrMultiple";
+    lr2.vote = LayerVoteType::Heuristic;
+    lr2.desiredRefreshRate = 90_Hz;
+    lr2.name = "90Hz Heuristic";
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+
+    lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
+    lr1.desiredRefreshRate = 30_Hz;
+    lr1.name = "30Hz ExplicitExactOrMultiple";
+    lr2.vote = LayerVoteType::Max;
+    lr2.name = "Max";
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+}
+
+TEST_P(RefreshRateSelectorTest, scrollWhileWatching60fps_60_90) {
+    auto selector = createSelector(kModes_60_90, kModeId60);
+
+    std::vector<LayerRequirement> layers = {{.weight = 1.f}, {.weight = 1.f}};
+    auto& lr1 = layers[0];
+    auto& lr2 = layers[1];
+
+    lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
+    lr1.desiredRefreshRate = 60_Hz;
+    lr1.name = "60Hz ExplicitExactOrMultiple";
+    lr2.vote = LayerVoteType::NoVote;
+    lr2.name = "NoVote";
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
+
+    lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
+    lr1.desiredRefreshRate = 60_Hz;
+    lr1.name = "60Hz ExplicitExactOrMultiple";
+    lr2.vote = LayerVoteType::NoVote;
+    lr2.name = "NoVote";
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers, {.touch = true}));
+
+    lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
+    lr1.desiredRefreshRate = 60_Hz;
+    lr1.name = "60Hz ExplicitExactOrMultiple";
+    lr2.vote = LayerVoteType::Max;
+    lr2.name = "Max";
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers, {.touch = true}));
+
+    lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
+    lr1.desiredRefreshRate = 60_Hz;
+    lr1.name = "60Hz ExplicitExactOrMultiple";
+    lr2.vote = LayerVoteType::Max;
+    lr2.name = "Max";
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+
+    // The other layer starts to provide buffers
+    lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
+    lr1.desiredRefreshRate = 60_Hz;
+    lr1.name = "60Hz ExplicitExactOrMultiple";
+    lr2.vote = LayerVoteType::Heuristic;
+    lr2.desiredRefreshRate = 90_Hz;
+    lr2.name = "90Hz Heuristic";
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+}
+
+TEST_P(RefreshRateSelectorTest, getMaxRefreshRatesByPolicy) {
+    // The kModes_30_60_90 contains two kMode72_G1, kMode120_G1 which are from the
+    // different group.
+    auto selector = createSelector(kModes_30_60_90, kModeId60);
+    const auto refreshRates = selector.rankRefreshRates(selector.getActiveMode().getGroup(),
+                                                        RefreshRateOrder::Descending);
+
+    const auto expectedRefreshRates = []() -> std::vector<FrameRateMode> {
+        switch (GetParam()) {
+            case Config::FrameRateOverride::Disabled:
+            case Config::FrameRateOverride::AppOverrideNativeRefreshRates:
+            case Config::FrameRateOverride::AppOverride:
+                return {{90_Hz, kMode90}, {60_Hz, kMode60}, {30_Hz, kMode30}};
+            case Config::FrameRateOverride::Enabled:
+                return {{90_Hz, kMode90}, {60_Hz, kMode60}, {45_Hz, kMode90}, {30_Hz, kMode30}};
+        }
+    }();
+    ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size());
+
+    for (size_t i = 0; i < expectedRefreshRates.size(); ++i) {
+        EXPECT_EQ(expectedRefreshRates[i], refreshRates[i].frameRateMode)
+                << "Expected " << expectedRefreshRates[i].fps.getIntValue() << " ("
+                << expectedRefreshRates[i].modePtr->getFps().getIntValue() << ")"
+                << " Actual " << refreshRates[i].frameRateMode.fps.getIntValue() << " ("
+                << refreshRates[i].frameRateMode.modePtr->getFps().getIntValue() << ")";
+    }
+}
+
+TEST_P(RefreshRateSelectorTest, getMinRefreshRatesByPolicy) {
+    // The kModes_30_60_90 contains two kMode72_G1, kMode120_G1 which are from the
+    // different group.
+    auto selector = createSelector(kModes_30_60_90, kModeId60);
+
+    const auto refreshRates = selector.rankRefreshRates(selector.getActiveMode().getGroup(),
+                                                        RefreshRateOrder::Ascending);
+
+    const auto expectedRefreshRates = []() -> std::vector<FrameRateMode> {
+        switch (GetParam()) {
+            case Config::FrameRateOverride::Disabled:
+            case Config::FrameRateOverride::AppOverrideNativeRefreshRates:
+            case Config::FrameRateOverride::AppOverride:
+                return {{30_Hz, kMode30}, {60_Hz, kMode60}, {90_Hz, kMode90}};
+            case Config::FrameRateOverride::Enabled:
+                return {{30_Hz, kMode30}, {45_Hz, kMode90}, {60_Hz, kMode60}, {90_Hz, kMode90}};
+        }
+    }();
+    ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size());
+
+    for (size_t i = 0; i < expectedRefreshRates.size(); ++i) {
+        EXPECT_EQ(expectedRefreshRates[i], refreshRates[i].frameRateMode)
+                << "Expected " << expectedRefreshRates[i].fps.getIntValue() << " ("
+                << expectedRefreshRates[i].modePtr->getFps().getIntValue() << ")"
+                << " Actual " << refreshRates[i].frameRateMode.fps.getIntValue() << " ("
+                << refreshRates[i].frameRateMode.modePtr->getFps().getIntValue() << ")";
+    }
+}
+
+TEST_P(RefreshRateSelectorTest, getMinRefreshRatesByPolicyOutsideTheGroup) {
+    // The kModes_30_60_90 contains two kMode72_G1, kMode120_G1 which are from the
+    // different group.
+    auto selector = createSelector(kModes_30_60_90, kModeId72);
+
+    EXPECT_EQ(SetPolicyResult::Changed,
+              selector.setDisplayManagerPolicy({kModeId60, {30_Hz, 90_Hz}}));
+
+    const auto refreshRates =
+            selector.rankRefreshRates(/*anchorGroupOpt*/ std::nullopt, RefreshRateOrder::Ascending);
+
+    const auto expectedRefreshRates = []() -> std::vector<FrameRateMode> {
+        switch (GetParam()) {
+            case Config::FrameRateOverride::Disabled:
+            case Config::FrameRateOverride::AppOverrideNativeRefreshRates:
+            case Config::FrameRateOverride::AppOverride:
+                return {{30_Hz, kMode30}, {60_Hz, kMode60}, {90_Hz, kMode90}};
+            case Config::FrameRateOverride::Enabled:
+                return {{30_Hz, kMode30}, {45_Hz, kMode90}, {60_Hz, kMode60}, {90_Hz, kMode90}};
+        }
+    }();
+    ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size());
+
+    for (size_t i = 0; i < expectedRefreshRates.size(); ++i) {
+        EXPECT_EQ(expectedRefreshRates[i], refreshRates[i].frameRateMode)
+                << "Expected " << expectedRefreshRates[i].fps.getIntValue() << " ("
+                << expectedRefreshRates[i].modePtr->getFps().getIntValue() << ")"
+                << " Actual " << refreshRates[i].frameRateMode.fps.getIntValue() << " ("
+                << refreshRates[i].frameRateMode.modePtr->getFps().getIntValue() << ")";
+    }
+}
+
+TEST_P(RefreshRateSelectorTest, getMaxRefreshRatesByPolicyOutsideTheGroup) {
+    // The kModes_30_60_90 contains two kMode72_G1, kMode120_G1 which are from the
+    // different group.
+    auto selector = createSelector(kModes_30_60_90, kModeId72);
+
+    EXPECT_EQ(SetPolicyResult::Changed,
+              selector.setDisplayManagerPolicy({kModeId60, {30_Hz, 90_Hz}}));
+
+    const auto refreshRates = selector.rankRefreshRates(/*anchorGroupOpt*/ std::nullopt,
+                                                        RefreshRateOrder::Descending);
+
+    const auto expectedRefreshRates = []() -> std::vector<FrameRateMode> {
+        switch (GetParam()) {
+            case Config::FrameRateOverride::Disabled:
+            case Config::FrameRateOverride::AppOverrideNativeRefreshRates:
+            case Config::FrameRateOverride::AppOverride:
+                return {{90_Hz, kMode90}, {60_Hz, kMode60}, {30_Hz, kMode30}};
+            case Config::FrameRateOverride::Enabled:
+                return {{90_Hz, kMode90}, {60_Hz, kMode60}, {45_Hz, kMode90}, {30_Hz, kMode30}};
+        }
+    }();
+    ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size());
+
+    for (size_t i = 0; i < expectedRefreshRates.size(); ++i) {
+        EXPECT_EQ(expectedRefreshRates[i], refreshRates[i].frameRateMode)
+                << "Expected " << expectedRefreshRates[i].fps.getIntValue() << " ("
+                << expectedRefreshRates[i].modePtr->getFps().getIntValue() << ")"
+                << " Actual " << refreshRates[i].frameRateMode.fps.getIntValue() << " ("
+                << refreshRates[i].frameRateMode.modePtr->getFps().getIntValue() << ")";
+    }
+}
+
+TEST_P(RefreshRateSelectorTest, powerOnImminentConsidered) {
+    auto selector = createSelector(kModes_60_90, kModeId60);
+
+    auto [refreshRates, signals] = selector.getRankedFrameRates({}, {});
+    EXPECT_FALSE(signals.powerOnImminent);
+
+    auto expectedRefreshRates = []() -> std::vector<FrameRateMode> {
+        switch (GetParam()) {
+            case Config::FrameRateOverride::Disabled:
+            case Config::FrameRateOverride::AppOverrideNativeRefreshRates:
+            case Config::FrameRateOverride::AppOverride:
+                return {{90_Hz, kMode90}, {60_Hz, kMode60}};
+            case Config::FrameRateOverride::Enabled:
+                return {{90_Hz, kMode90}, {60_Hz, kMode60},   {45_Hz, kMode90},
+                        {30_Hz, kMode60}, {22.5_Hz, kMode90}, {20_Hz, kMode60}};
+        }
+    }();
+    ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size());
+
+    for (size_t i = 0; i < expectedRefreshRates.size(); ++i) {
+        EXPECT_EQ(expectedRefreshRates[i], refreshRates[i].frameRateMode)
+                << "Expected " << expectedRefreshRates[i].fps.getIntValue() << " ("
+                << expectedRefreshRates[i].modePtr->getFps().getIntValue() << ")"
+                << " Actual " << refreshRates[i].frameRateMode.fps.getIntValue() << " ("
+                << refreshRates[i].frameRateMode.modePtr->getFps().getIntValue() << ")";
+    }
+
+    std::tie(refreshRates, signals) =
+            selector.getRankedRefreshRatesAsPair({}, {.powerOnImminent = true});
+    EXPECT_TRUE(signals.powerOnImminent);
+
+    ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size());
+
+    for (size_t i = 0; i < expectedRefreshRates.size(); ++i) {
+        EXPECT_EQ(expectedRefreshRates[i], refreshRates[i].frameRateMode)
+                << "Expected " << expectedRefreshRates[i].fps.getIntValue() << " ("
+                << expectedRefreshRates[i].modePtr->getFps().getIntValue() << ")"
+                << " Actual " << refreshRates[i].frameRateMode.fps.getIntValue() << " ("
+                << refreshRates[i].frameRateMode.modePtr->getFps().getIntValue() << ")";
+    }
+
+    std::vector<LayerRequirement> layers = {{.weight = 1.f}};
+    auto& lr1 = layers[0];
+    lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
+    lr1.desiredRefreshRate = 60_Hz;
+    lr1.name = "60Hz ExplicitExactOrMultiple";
+
+    std::tie(refreshRates, signals) =
+            selector.getRankedRefreshRatesAsPair(layers, {.powerOnImminent = true});
+    EXPECT_TRUE(signals.powerOnImminent);
+
+    ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size());
+
+    for (size_t i = 0; i < expectedRefreshRates.size(); ++i) {
+        EXPECT_EQ(expectedRefreshRates[i], refreshRates[i].frameRateMode)
+                << "Expected " << expectedRefreshRates[i].fps.getIntValue() << " ("
+                << expectedRefreshRates[i].modePtr->getFps().getIntValue() << ")"
+                << " Actual " << refreshRates[i].frameRateMode.fps.getIntValue() << " ("
+                << refreshRates[i].frameRateMode.modePtr->getFps().getIntValue() << ")";
+    }
+
+    std::tie(refreshRates, signals) =
+            selector.getRankedRefreshRatesAsPair(layers, {.powerOnImminent = false});
+    EXPECT_FALSE(signals.powerOnImminent);
+
+    expectedRefreshRates = []() -> std::vector<FrameRateMode> {
+        switch (GetParam()) {
+            case Config::FrameRateOverride::Disabled:
+            case Config::FrameRateOverride::AppOverrideNativeRefreshRates:
+            case Config::FrameRateOverride::AppOverride:
+                return {{60_Hz, kMode60}, {90_Hz, kMode90}};
+            case Config::FrameRateOverride::Enabled:
+                return {{60_Hz, kMode60}, {90_Hz, kMode90},   {45_Hz, kMode90},
+                        {30_Hz, kMode60}, {22.5_Hz, kMode90}, {20_Hz, kMode60}};
+        }
+    }();
+    ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size());
+
+    for (size_t i = 0; i < expectedRefreshRates.size(); ++i) {
+        EXPECT_EQ(expectedRefreshRates[i], refreshRates[i].frameRateMode)
+                << "Expected " << expectedRefreshRates[i].fps.getIntValue() << " ("
+                << expectedRefreshRates[i].modePtr->getFps().getIntValue() << ")"
+                << " Actual " << refreshRates[i].frameRateMode.fps.getIntValue() << " ("
+                << refreshRates[i].frameRateMode.modePtr->getFps().getIntValue() << ")";
+    }
+}
+
+TEST_P(RefreshRateSelectorTest, touchConsidered) {
+    auto selector = createSelector(kModes_60_90, kModeId60);
+
+    auto [_, signals] = selector.getRankedFrameRates({}, {});
+    EXPECT_FALSE(signals.touch);
+
+    std::tie(std::ignore, signals) = selector.getRankedRefreshRatesAsPair({}, {.touch = true});
+    EXPECT_TRUE(signals.touch);
+
+    std::vector<LayerRequirement> layers = {{.weight = 1.f}, {.weight = 1.f}};
+    auto& lr1 = layers[0];
+    auto& lr2 = layers[1];
+
+    lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
+    lr1.desiredRefreshRate = 60_Hz;
+    lr1.name = "60Hz ExplicitExactOrMultiple";
+    lr2.vote = LayerVoteType::Heuristic;
+    lr2.desiredRefreshRate = 60_Hz;
+    lr2.name = "60Hz Heuristic";
+    std::tie(std::ignore, signals) = selector.getRankedRefreshRatesAsPair(layers, {.touch = true});
+    EXPECT_TRUE(signals.touch);
+
+    lr1.vote = LayerVoteType::ExplicitDefault;
+    lr1.desiredRefreshRate = 60_Hz;
+    lr1.name = "60Hz ExplicitDefault";
+    lr2.vote = LayerVoteType::Heuristic;
+    lr2.desiredRefreshRate = 60_Hz;
+    lr2.name = "60Hz Heuristic";
+    std::tie(std::ignore, signals) = selector.getRankedRefreshRatesAsPair(layers, {.touch = true});
+    EXPECT_FALSE(signals.touch);
+
+    lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
+    lr1.desiredRefreshRate = 60_Hz;
+    lr1.name = "60Hz ExplicitExactOrMultiple";
+    lr2.vote = LayerVoteType::Heuristic;
+    lr2.desiredRefreshRate = 60_Hz;
+    lr2.name = "60Hz Heuristic";
+    std::tie(std::ignore, signals) = selector.getRankedRefreshRatesAsPair(layers, {.touch = true});
+    EXPECT_TRUE(signals.touch);
+
+    lr1.vote = LayerVoteType::ExplicitDefault;
+    lr1.desiredRefreshRate = 60_Hz;
+    lr1.name = "60Hz ExplicitDefault";
+    lr2.vote = LayerVoteType::Heuristic;
+    lr2.desiredRefreshRate = 60_Hz;
+    lr2.name = "60Hz Heuristic";
+    std::tie(std::ignore, signals) = selector.getRankedRefreshRatesAsPair(layers, {.touch = true});
+    EXPECT_FALSE(signals.touch);
+}
+
+TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_ExplicitDefault) {
+    auto selector = createSelector(kModes_60_90_72_120, kModeId60);
+
+    std::vector<LayerRequirement> layers = {{.weight = 1.f}};
+    auto& lr = layers[0];
+
+    // Prepare a table with the vote and the expected refresh rate
+    const std::initializer_list<std::pair<Fps, Fps>> testCases = {
+            {130_Hz, 120_Hz}, {120_Hz, 120_Hz}, {119_Hz, 120_Hz}, {110_Hz, 120_Hz},
+
+            {100_Hz, 90_Hz},  {90_Hz, 90_Hz},   {89_Hz, 90_Hz},
+
+            {80_Hz, 72_Hz},   {73_Hz, 72_Hz},   {72_Hz, 72_Hz},   {71_Hz, 72_Hz},   {70_Hz, 72_Hz},
+
+            {65_Hz, 60_Hz},   {60_Hz, 60_Hz},   {59_Hz, 60_Hz},   {58_Hz, 60_Hz},
+
+            {55_Hz, 90_Hz},   {50_Hz, 90_Hz},   {45_Hz, 90_Hz},
+
+            {42_Hz, 120_Hz},  {40_Hz, 120_Hz},  {39_Hz, 120_Hz},
+
+            {37_Hz, 72_Hz},   {36_Hz, 72_Hz},   {35_Hz, 72_Hz},
+
+            {30_Hz, 60_Hz},
+    };
+
+    for (auto [desired, expected] : testCases) {
+        lr.vote = LayerVoteType::ExplicitDefault;
+        lr.desiredRefreshRate = desired;
+
+        std::stringstream ss;
+        ss << "ExplicitDefault " << desired;
+        lr.name = ss.str();
+
+        EXPECT_EQ(expected, selector.getBestFrameRateMode(layers)->getFps());
+    }
+}
+
+TEST_P(RefreshRateSelectorTest,
+       getBestFrameRateMode_ExplicitExactOrMultiple_WithFractionalRefreshRates) {
+    std::vector<LayerRequirement> layers = {{.weight = 1.f}};
+    auto& lr = layers[0];
+
+    // Test that 23.976 will choose 24 if 23.976 is not supported
+    {
+        auto selector = createSelector(makeModes(kMode24, kMode25, kMode30, kMode30Frac, kMode60,
+                                                 kMode60Frac),
+                                       kModeId60);
+
+        lr.vote = LayerVoteType::ExplicitExactOrMultiple;
+        lr.desiredRefreshRate = 23.976_Hz;
+        lr.name = "ExplicitExactOrMultiple 23.976 Hz";
+        EXPECT_EQ(kModeId24, selector.getBestFrameRateMode(layers)->getId());
+    }
+
+    // Test that 24 will choose 23.976 if 24 is not supported
+    {
+        auto selector = createSelector(makeModes(kMode24Frac, kMode25, kMode30, kMode30Frac,
+                                                 kMode60, kMode60Frac),
+                                       kModeId60);
+
+        lr.desiredRefreshRate = 24_Hz;
+        lr.name = "ExplicitExactOrMultiple 24 Hz";
+        EXPECT_EQ(kModeId24Frac, selector.getBestFrameRateMode(layers)->getId());
+    }
+
+    // Test that 29.97 will prefer 59.94 over 60 and 30
+    {
+        auto selector = createSelector(makeModes(kMode24, kMode24Frac, kMode25, kMode30, kMode60,
+                                                 kMode60Frac),
+                                       kModeId60);
+
+        lr.desiredRefreshRate = 29.97_Hz;
+        lr.name = "ExplicitExactOrMultiple 29.97 Hz";
+        EXPECT_EQ(kModeId60Frac, selector.getBestFrameRateMode(layers)->getId());
+    }
+}
+
+TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_ExplicitExact_WithFractionalRefreshRates) {
+    std::vector<LayerRequirement> layers = {{.weight = 1.f}};
+    auto& lr = layers[0];
+
+    // Test that voting for supported refresh rate will select this refresh rate
+    {
+        auto selector = createSelector(kModes_24_25_30_50_60_Frac, kModeId60);
+
+        for (auto desired : {23.976_Hz, 24_Hz, 25_Hz, 29.97_Hz, 30_Hz, 50_Hz, 59.94_Hz, 60_Hz}) {
+            lr.vote = LayerVoteType::ExplicitExact;
+            lr.desiredRefreshRate = desired;
+            std::stringstream ss;
+            ss << "ExplicitExact " << desired;
+            lr.name = ss.str();
+
+            EXPECT_EQ(lr.desiredRefreshRate, selector.getBestFrameRateMode(layers)->getFps());
+        }
+    }
+}
+
+TEST_P(RefreshRateSelectorTest,
+       getBestFrameRateMode_withDisplayManagerRequestingSingleRate_ignoresTouchFlag) {
+    auto selector = createSelector(kModes_60_90, kModeId90);
+
+    constexpr FpsRange k90 = {90_Hz, 90_Hz};
+    constexpr FpsRange k60_90 = {60_Hz, 90_Hz};
+    EXPECT_EQ(SetPolicyResult::Changed,
+              selector.setDisplayManagerPolicy({kModeId90, {k90, k90}, {k60_90, k60_90}}));
+
+    std::vector<LayerRequirement> layers = {{.weight = 1.f}};
+    auto& lr = layers[0];
+
+    lr.vote = LayerVoteType::ExplicitDefault;
+    lr.desiredRefreshRate = 60_Hz;
+    lr.name = "60Hz ExplicitDefault";
+    lr.focused = true;
+
+    const auto [rankedFrameRate, signals] =
+            selector.getRankedFrameRates(layers, {.touch = true, .idle = true});
+
+    EXPECT_EQ(rankedFrameRate.begin()->frameRateMode.modePtr, kMode60);
+    EXPECT_FALSE(signals.touch);
+}
+
+TEST_P(RefreshRateSelectorTest,
+       getBestFrameRateMode_withDisplayManagerRequestingSingleRate_ignoresIdleFlag) {
+    auto selector = createSelector(kModes_60_90, kModeId60);
+
+    constexpr FpsRange k60 = {60_Hz, 60_Hz};
+    constexpr FpsRange k60_90 = {60_Hz, 90_Hz};
+
+    EXPECT_EQ(SetPolicyResult::Changed,
+              selector.setDisplayManagerPolicy({kModeId60, {k60, k60}, {k60_90, k60_90}}));
+
+    std::vector<LayerRequirement> layers = {{.weight = 1.f}};
+    auto& lr = layers[0];
+
+    lr.vote = LayerVoteType::ExplicitDefault;
+    lr.desiredRefreshRate = 90_Hz;
+    lr.name = "90Hz ExplicitDefault";
+    lr.focused = true;
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers, {.idle = true}));
+}
+
+TEST_P(RefreshRateSelectorTest, testDisplayModeOrdering) {
+    auto selector = createSelector(kModes_30_60_72_90_120, kModeId60);
+
+    std::vector<LayerRequirement> layers = {{.weight = 1.f},
+                                            {.weight = 1.f},
+                                            {.weight = 1.f},
+                                            {.weight = 1.f},
+                                            {.weight = 1.f}};
+    auto& lr1 = layers[0];
+    auto& lr2 = layers[1];
+    auto& lr3 = layers[2];
+    auto& lr4 = layers[3];
+    auto& lr5 = layers[4];
+
+    lr1.desiredRefreshRate = 90_Hz;
+    lr1.name = "90Hz";
+    lr1.focused = true;
+
+    lr2.desiredRefreshRate = 60_Hz;
+    lr2.name = "60Hz";
+    lr2.focused = true;
+
+    lr3.desiredRefreshRate = 72_Hz;
+    lr3.name = "72Hz";
+    lr3.focused = true;
+
+    lr4.desiredRefreshRate = 120_Hz;
+    lr4.name = "120Hz";
+    lr4.focused = true;
+
+    lr5.desiredRefreshRate = 30_Hz;
+    lr5.name = "30Hz";
+    lr5.focused = true;
+
+    auto expectedRanking = []() -> std::vector<FrameRateMode> {
+        switch (GetParam()) {
+            case Config::FrameRateOverride::Disabled:
+            case Config::FrameRateOverride::AppOverrideNativeRefreshRates:
+            case Config::FrameRateOverride::AppOverride:
+                return {{120_Hz, kMode120},
+                        {90_Hz, kMode90},
+                        {72_Hz, kMode72},
+                        {60_Hz, kMode60},
+                        {30_Hz, kMode30}};
+            case Config::FrameRateOverride::Enabled:
+                return {{120_Hz, kMode120}, {90_Hz, kMode90},  {72_Hz, kMode72}, {60_Hz, kMode60},
+                        {45_Hz, kMode90},   {40_Hz, kMode120}, {36_Hz, kMode72}, {30_Hz, kMode30}};
+        }
+    }();
+
+    auto actualRanking = selector.getRankedFrameRates(layers, {}).ranking;
+    ASSERT_EQ(expectedRanking.size(), actualRanking.size());
+
+    for (size_t i = 0; i < expectedRanking.size(); ++i) {
+        EXPECT_EQ(expectedRanking[i], actualRanking[i].frameRateMode)
+                << "Expected " << expectedRanking[i].fps.getIntValue() << " ("
+                << expectedRanking[i].modePtr->getFps().getIntValue() << ")"
+                << " Actual " << actualRanking[i].frameRateMode.fps.getIntValue() << " ("
+                << actualRanking[i].frameRateMode.modePtr->getFps().getIntValue() << ")";
+    }
+
+    lr1.vote = LayerVoteType::Max;
+    lr1.name = "Max";
+
+    lr2.desiredRefreshRate = 60_Hz;
+    lr2.name = "60Hz";
+
+    lr3.desiredRefreshRate = 72_Hz;
+    lr3.name = "72Hz";
+
+    lr4.desiredRefreshRate = 90_Hz;
+    lr4.name = "90Hz";
+
+    lr5.desiredRefreshRate = 120_Hz;
+    lr5.name = "120Hz";
+
+    expectedRanking = []() -> std::vector<FrameRateMode> {
+        switch (GetParam()) {
+            case Config::FrameRateOverride::Disabled:
+            case Config::FrameRateOverride::AppOverrideNativeRefreshRates:
+            case Config::FrameRateOverride::AppOverride:
+                return {{120_Hz, kMode120},
+                        {90_Hz, kMode90},
+                        {72_Hz, kMode72},
+                        {60_Hz, kMode60},
+                        {30_Hz, kMode30}};
+            case Config::FrameRateOverride::Enabled:
+                return {{120_Hz, kMode120}, {90_Hz, kMode90},  {72_Hz, kMode72}, {60_Hz, kMode60},
+                        {45_Hz, kMode90},   {40_Hz, kMode120}, {36_Hz, kMode72}, {30_Hz, kMode30}};
+        }
+    }();
+    actualRanking = selector.getRankedFrameRates(layers, {}).ranking;
+
+    ASSERT_EQ(expectedRanking.size(), actualRanking.size());
+
+    for (size_t i = 0; i < expectedRanking.size(); ++i) {
+        EXPECT_EQ(expectedRanking[i], actualRanking[i].frameRateMode)
+                << "Expected " << expectedRanking[i].fps.getIntValue() << " ("
+                << expectedRanking[i].modePtr->getFps().getIntValue() << ")"
+                << " Actual " << actualRanking[i].frameRateMode.fps.getIntValue() << " ("
+                << actualRanking[i].frameRateMode.modePtr->getFps().getIntValue() << ")";
+    }
+
+    lr1.vote = LayerVoteType::Heuristic;
+    lr1.desiredRefreshRate = 30_Hz;
+    lr1.name = "30Hz";
+
+    lr2.desiredRefreshRate = 120_Hz;
+    lr2.name = "120Hz";
+
+    lr3.desiredRefreshRate = 60_Hz;
+    lr3.name = "60Hz";
+
+    lr5.desiredRefreshRate = 72_Hz;
+    lr5.name = "72Hz";
+
+    expectedRanking = []() -> std::vector<FrameRateMode> {
+        switch (GetParam()) {
+            case Config::FrameRateOverride::Disabled:
+            case Config::FrameRateOverride::AppOverrideNativeRefreshRates:
+            case Config::FrameRateOverride::AppOverride:
+                return {{30_Hz, kMode30},
+                        {60_Hz, kMode60},
+                        {90_Hz, kMode90},
+                        {120_Hz, kMode120},
+                        {72_Hz, kMode72}};
+            case Config::FrameRateOverride::Enabled:
+                return {{30_Hz, kMode30}, {60_Hz, kMode60},  {90_Hz, kMode90}, {120_Hz, kMode120},
+                        {45_Hz, kMode90}, {40_Hz, kMode120}, {72_Hz, kMode72}, {36_Hz, kMode72}};
+        }
+    }();
+    actualRanking = selector.getRankedFrameRates(layers, {}).ranking;
+
+    ASSERT_EQ(expectedRanking.size(), actualRanking.size());
+
+    for (size_t i = 0; i < expectedRanking.size(); ++i) {
+        EXPECT_EQ(expectedRanking[i], actualRanking[i].frameRateMode)
+                << "Expected " << expectedRanking[i].fps.getIntValue() << " ("
+                << expectedRanking[i].modePtr->getFps().getIntValue() << ")"
+                << " Actual " << actualRanking[i].frameRateMode.fps.getIntValue() << " ("
+                << actualRanking[i].frameRateMode.modePtr->getFps().getIntValue() << ")";
+    }
+
+    lr1.desiredRefreshRate = 120_Hz;
+    lr1.name = "120Hz";
+    lr1.weight = 0.0f;
+
+    lr2.desiredRefreshRate = 60_Hz;
+    lr2.name = "60Hz";
+    lr2.vote = LayerVoteType::NoVote;
+
+    lr3.name = "60Hz-2";
+    lr3.vote = LayerVoteType::Heuristic;
+
+    lr4.vote = LayerVoteType::ExplicitExact;
+
+    lr5.desiredRefreshRate = 120_Hz;
+    lr5.name = "120Hz-2";
+
+    expectedRanking = []() -> std::vector<FrameRateMode> {
+        switch (GetParam()) {
+            case Config::FrameRateOverride::Disabled:
+            case Config::FrameRateOverride::AppOverrideNativeRefreshRates:
+            case Config::FrameRateOverride::AppOverride:
+                return {{90_Hz, kMode90},
+                        {60_Hz, kMode60},
+                        {120_Hz, kMode120},
+                        {72_Hz, kMode72},
+                        {30_Hz, kMode30}};
+            case Config::FrameRateOverride::Enabled:
+                return {{90_Hz, kMode90}, {60_Hz, kMode60},  {120_Hz, kMode120}, {72_Hz, kMode72},
+                        {45_Hz, kMode90}, {40_Hz, kMode120}, {36_Hz, kMode72},   {30_Hz, kMode30}};
+        }
+    }();
+    actualRanking = selector.getRankedFrameRates(layers, {}).ranking;
+
+    ASSERT_EQ(expectedRanking.size(), actualRanking.size());
+
+    for (size_t i = 0; i < expectedRanking.size(); ++i) {
+        EXPECT_EQ(expectedRanking[i], actualRanking[i].frameRateMode)
+                << "Expected " << expectedRanking[i].fps.getIntValue() << " ("
+                << expectedRanking[i].modePtr->getFps().getIntValue() << ")"
+                << " Actual " << actualRanking[i].frameRateMode.fps.getIntValue() << " ("
+                << actualRanking[i].frameRateMode.modePtr->getFps().getIntValue() << ")";
+    }
+}
+
+TEST_P(RefreshRateSelectorTest,
+       getBestFrameRateMode_withDisplayManagerRequestingSingleRate_onlySwitchesRatesForExplicitFocusedLayers) {
+    auto selector = createSelector(kModes_60_90, kModeId90);
+
+    constexpr FpsRange k90 = {90_Hz, 90_Hz};
+    constexpr FpsRange k60_90 = {60_Hz, 90_Hz};
+
+    EXPECT_EQ(SetPolicyResult::Changed,
+              selector.setDisplayManagerPolicy({kModeId90, {k90, k90}, {k60_90, k60_90}}));
+
+    const auto [ranking, signals] = selector.getRankedFrameRates({}, {});
+    EXPECT_EQ(ranking.front().frameRateMode.modePtr, kMode90);
+    EXPECT_FALSE(signals.touch);
+
+    std::vector<LayerRequirement> layers = {{.weight = 1.f}};
+    auto& lr = layers[0];
+
+    lr.vote = LayerVoteType::ExplicitExactOrMultiple;
+    lr.desiredRefreshRate = 60_Hz;
+    lr.name = "60Hz ExplicitExactOrMultiple";
+    lr.focused = false;
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+
+    lr.focused = true;
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+
+    lr.vote = LayerVoteType::ExplicitDefault;
+    lr.desiredRefreshRate = 60_Hz;
+    lr.name = "60Hz ExplicitDefault";
+    lr.focused = false;
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+
+    lr.focused = true;
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
+
+    lr.vote = LayerVoteType::Heuristic;
+    lr.desiredRefreshRate = 60_Hz;
+    lr.name = "60Hz Heuristic";
+    lr.focused = false;
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+
+    lr.focused = true;
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+
+    lr.vote = LayerVoteType::Max;
+    lr.desiredRefreshRate = 60_Hz;
+    lr.name = "60Hz Max";
+    lr.focused = false;
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+
+    lr.focused = true;
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+
+    lr.vote = LayerVoteType::Min;
+    lr.desiredRefreshRate = 60_Hz;
+    lr.name = "60Hz Min";
+    lr.focused = false;
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+
+    lr.focused = true;
+    EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers));
+}
+
+TEST_P(RefreshRateSelectorTest, groupSwitchingNotAllowed) {
+    auto selector = createSelector(kModes_60_90_G1, kModeId60);
+
+    // The default policy doesn't allow group switching. Verify that no
+    // group switches are performed.
+    std::vector<LayerRequirement> layers = {{.weight = 1.f}};
+    auto& layer = layers[0];
+    layer.vote = LayerVoteType::ExplicitDefault;
+    layer.desiredRefreshRate = 90_Hz;
+    layer.seamlessness = Seamlessness::SeamedAndSeamless;
+    layer.name = "90Hz ExplicitDefault";
+    layer.focused = true;
+
+    EXPECT_EQ(kModeId60, selector.getBestFrameRateMode(layers)->getId());
+}
+
+TEST_P(RefreshRateSelectorTest, groupSwitchingWithOneLayer) {
+    auto selector = createSelector(kModes_60_90_G1, kModeId60);
+
+    RefreshRateSelector::DisplayManagerPolicy policy;
+    policy.defaultMode = selector.getCurrentPolicy().defaultMode;
+    policy.allowGroupSwitching = true;
+    EXPECT_EQ(SetPolicyResult::Changed, selector.setPolicy(policy));
+
+    std::vector<LayerRequirement> layers = {{.weight = 1.f}};
+    auto& layer = layers[0];
+    layer.vote = LayerVoteType::ExplicitDefault;
+    layer.desiredRefreshRate = 90_Hz;
+    layer.seamlessness = Seamlessness::SeamedAndSeamless;
+    layer.name = "90Hz ExplicitDefault";
+    layer.focused = true;
+    EXPECT_EQ(kModeId90, selector.getBestFrameRateMode(layers)->getId());
+}
+
+TEST_P(RefreshRateSelectorTest, groupSwitchingWithOneLayerOnlySeamless) {
+    auto selector = createSelector(kModes_60_90_G1, kModeId60);
+
+    RefreshRateSelector::DisplayManagerPolicy policy;
+    policy.defaultMode = selector.getCurrentPolicy().defaultMode;
+    policy.allowGroupSwitching = true;
+    EXPECT_EQ(SetPolicyResult::Changed, selector.setPolicy(policy));
+
+    // Verify that we won't change the group if seamless switch is required.
+    std::vector<LayerRequirement> layers = {{.weight = 1.f}};
+    auto& layer = layers[0];
+    layer.vote = LayerVoteType::ExplicitDefault;
+    layer.desiredRefreshRate = 90_Hz;
+    layer.seamlessness = Seamlessness::OnlySeamless;
+    layer.name = "90Hz ExplicitDefault";
+    layer.focused = true;
+    EXPECT_EQ(kModeId60, selector.getBestFrameRateMode(layers)->getId());
+}
+
+TEST_P(RefreshRateSelectorTest, groupSwitchingWithOneLayerOnlySeamlessDefaultFps) {
+    auto selector = createSelector(kModes_60_90_G1, kModeId60);
+
+    RefreshRateSelector::DisplayManagerPolicy policy;
+    policy.defaultMode = selector.getCurrentPolicy().defaultMode;
+    policy.allowGroupSwitching = true;
+    EXPECT_EQ(SetPolicyResult::Changed, selector.setPolicy(policy));
+
+    selector.setActiveMode(kModeId90, 90_Hz);
+
+    // Verify that we won't do a seamless switch if we request the same mode as the default
+    std::vector<LayerRequirement> layers = {{.weight = 1.f}};
+    auto& layer = layers[0];
+    layer.vote = LayerVoteType::ExplicitDefault;
+    layer.desiredRefreshRate = 60_Hz;
+    layer.seamlessness = Seamlessness::OnlySeamless;
+    layer.name = "60Hz ExplicitDefault";
+    layer.focused = true;
+    EXPECT_EQ(kModeId90, selector.getBestFrameRateMode(layers)->getId());
+}
+
+TEST_P(RefreshRateSelectorTest, groupSwitchingWithOneLayerDefaultSeamlessness) {
+    auto selector = createSelector(kModes_60_90_G1, kModeId60);
+
+    RefreshRateSelector::DisplayManagerPolicy policy;
+    policy.defaultMode = selector.getCurrentPolicy().defaultMode;
+    policy.allowGroupSwitching = true;
+    EXPECT_EQ(SetPolicyResult::Changed, selector.setPolicy(policy));
+
+    selector.setActiveMode(kModeId90, 90_Hz);
+
+    // Verify that if the active mode is in another group and there are no layers with
+    // Seamlessness::SeamedAndSeamless, we should switch back to the default group.
+
+    std::vector<LayerRequirement> layers = {{.weight = 1.f}};
+    auto& layer = layers[0];
+    layer.vote = LayerVoteType::ExplicitDefault;
+    layer.desiredRefreshRate = 60_Hz;
+    layer.seamlessness = Seamlessness::Default;
+    layer.name = "60Hz ExplicitDefault";
+    layer.focused = true;
+
+    EXPECT_EQ(kModeId60, selector.getBestFrameRateMode(layers)->getId());
+}
+
+TEST_P(RefreshRateSelectorTest, groupSwitchingWithTwoLayersOnlySeamlessAndSeamed) {
+    auto selector = createSelector(kModes_60_90_G1, kModeId60);
+
+    RefreshRateSelector::DisplayManagerPolicy policy;
+    policy.defaultMode = selector.getCurrentPolicy().defaultMode;
+    policy.allowGroupSwitching = true;
+    EXPECT_EQ(SetPolicyResult::Changed, selector.setPolicy(policy));
+
+    selector.setActiveMode(kModeId90, 90_Hz);
+
+    // If there's a layer with Seamlessness::SeamedAndSeamless, another layer with
+    // Seamlessness::OnlySeamless can't change the mode group.
+    std::vector<LayerRequirement> layers = {{.weight = 1.f}};
+    layers[0].vote = LayerVoteType::ExplicitDefault;
+    layers[0].desiredRefreshRate = 60_Hz;
+    layers[0].seamlessness = Seamlessness::OnlySeamless;
+    layers[0].name = "60Hz ExplicitDefault";
+    layers[0].focused = true;
+
+    layers.push_back(LayerRequirement{.weight = 0.5f});
+    layers[1].vote = LayerVoteType::ExplicitDefault;
+    layers[1].seamlessness = Seamlessness::SeamedAndSeamless;
+    layers[1].desiredRefreshRate = 90_Hz;
+    layers[1].name = "90Hz ExplicitDefault";
+    layers[1].focused = false;
+
+    EXPECT_EQ(kModeId90, selector.getBestFrameRateMode(layers)->getId());
+}
+
+TEST_P(RefreshRateSelectorTest, groupSwitchingWithTwoLayersDefaultFocusedAndSeamed) {
+    auto selector = createSelector(kModes_60_90_G1, kModeId60);
+
+    RefreshRateSelector::DisplayManagerPolicy policy;
+    policy.defaultMode = selector.getCurrentPolicy().defaultMode;
+    policy.allowGroupSwitching = true;
+    EXPECT_EQ(SetPolicyResult::Changed, selector.setPolicy(policy));
+
+    selector.setActiveMode(kModeId90, 90_Hz);
+
+    // If there's a focused layer with Seamlessness::SeamedAndSeamless, another layer with
+    // Seamlessness::Default can't change the mode group back to the group of the default
+    // mode.
+    // For example, this may happen when a video playback requests and gets a seamed switch,
+    // but another layer (with default seamlessness) starts animating. The animating layer
+    // should not cause a seamed switch.
+    std::vector<LayerRequirement> layers = {{.weight = 1.f}};
+    layers[0].seamlessness = Seamlessness::Default;
+    layers[0].desiredRefreshRate = 60_Hz;
+    layers[0].focused = true;
+    layers[0].vote = LayerVoteType::ExplicitDefault;
+    layers[0].name = "60Hz ExplicitDefault";
+
+    layers.push_back(LayerRequirement{.weight = 0.1f});
+    layers[1].seamlessness = Seamlessness::SeamedAndSeamless;
+    layers[1].desiredRefreshRate = 90_Hz;
+    layers[1].focused = true;
+    layers[1].vote = LayerVoteType::ExplicitDefault;
+    layers[1].name = "90Hz ExplicitDefault";
+
+    EXPECT_EQ(kModeId90, selector.getBestFrameRateMode(layers)->getId());
+}
+
+TEST_P(RefreshRateSelectorTest, groupSwitchingWithTwoLayersDefaultNotFocusedAndSeamed) {
+    auto selector = createSelector(kModes_60_90_G1, kModeId60);
+
+    RefreshRateSelector::DisplayManagerPolicy policy;
+    policy.defaultMode = selector.getCurrentPolicy().defaultMode;
+    policy.allowGroupSwitching = true;
+    EXPECT_EQ(SetPolicyResult::Changed, selector.setPolicy(policy));
+
+    selector.setActiveMode(kModeId90, 90_Hz);
+
+    // Layer with Seamlessness::Default can change the mode group if there's an
+    // unfocused layer with Seamlessness::SeamedAndSeamless. For example, this happens
+    // when in split screen mode the user switches between the two visible applications.
+    std::vector<LayerRequirement> layers = {{.weight = 1.f}};
+    layers[0].seamlessness = Seamlessness::Default;
+    layers[0].desiredRefreshRate = 60_Hz;
+    layers[0].focused = true;
+    layers[0].vote = LayerVoteType::ExplicitDefault;
+    layers[0].name = "60Hz ExplicitDefault";
+
+    layers.push_back(LayerRequirement{.weight = 0.7f});
+    layers[1].seamlessness = Seamlessness::SeamedAndSeamless;
+    layers[1].desiredRefreshRate = 90_Hz;
+    layers[1].focused = false;
+    layers[1].vote = LayerVoteType::ExplicitDefault;
+    layers[1].name = "90Hz ExplicitDefault";
+
+    EXPECT_EQ(kModeId60, selector.getBestFrameRateMode(layers)->getId());
+}
+
+TEST_P(RefreshRateSelectorTest, nonSeamlessVotePrefersSeamlessSwitches) {
+    auto selector = createSelector(kModes_30_60, kModeId60);
+
+    // Allow group switching.
+    RefreshRateSelector::DisplayManagerPolicy policy;
+    policy.defaultMode = selector.getCurrentPolicy().defaultMode;
+    policy.allowGroupSwitching = true;
+    EXPECT_EQ(SetPolicyResult::Changed, selector.setPolicy(policy));
+
+    std::vector<LayerRequirement> layers = {{.weight = 1.f}};
+    auto& layer = layers[0];
+    layer.vote = LayerVoteType::ExplicitExactOrMultiple;
+    layer.desiredRefreshRate = 60_Hz;
+    layer.seamlessness = Seamlessness::SeamedAndSeamless;
+    layer.name = "60Hz ExplicitExactOrMultiple";
+    layer.focused = true;
+
+    EXPECT_EQ(kModeId60, selector.getBestFrameRateMode(layers)->getId());
+
+    selector.setActiveMode(kModeId120, 120_Hz);
+    EXPECT_EQ(kModeId120, selector.getBestFrameRateMode(layers)->getId());
+}
+
+TEST_P(RefreshRateSelectorTest, nonSeamlessExactAndSeamlessMultipleLayers) {
+    auto selector = createSelector(kModes_25_30_50_60, kModeId60);
+
+    // Allow group switching.
+    RefreshRateSelector::DisplayManagerPolicy policy;
+    policy.defaultMode = selector.getCurrentPolicy().defaultMode;
+    policy.allowGroupSwitching = true;
+    EXPECT_EQ(SetPolicyResult::Changed, selector.setPolicy(policy));
+
+    std::vector<LayerRequirement> layers = {{.name = "60Hz ExplicitDefault",
+                                             .vote = LayerVoteType::ExplicitDefault,
+                                             .desiredRefreshRate = 60_Hz,
+                                             .seamlessness = Seamlessness::SeamedAndSeamless,
+                                             .weight = 0.5f,
+                                             .focused = false},
+                                            {.name = "25Hz ExplicitExactOrMultiple",
+                                             .vote = LayerVoteType::ExplicitExactOrMultiple,
+                                             .desiredRefreshRate = 25_Hz,
+                                             .seamlessness = Seamlessness::OnlySeamless,
+                                             .weight = 1.f,
+                                             .focused = true}};
+
+    EXPECT_EQ(kModeId50, selector.getBestFrameRateMode(layers)->getId());
+
+    auto& seamedLayer = layers[0];
+    seamedLayer.desiredRefreshRate = 30_Hz;
+    seamedLayer.name = "30Hz ExplicitDefault";
+    selector.setActiveMode(kModeId30, 30_Hz);
+
+    EXPECT_EQ(kModeId25, selector.getBestFrameRateMode(layers)->getId());
+}
+
+TEST_P(RefreshRateSelectorTest, minLayersDontTrigerSeamedSwitch) {
+    auto selector = createSelector(kModes_60_90_G1, kModeId90);
+
+    // Allow group switching.
+    RefreshRateSelector::DisplayManagerPolicy policy;
+    policy.defaultMode = selector.getCurrentPolicy().defaultMode;
+    policy.allowGroupSwitching = true;
+    EXPECT_EQ(SetPolicyResult::Changed, selector.setPolicy(policy));
+
+    std::vector<LayerRequirement> layers = {
+            {.name = "Min", .vote = LayerVoteType::Min, .weight = 1.f, .focused = true}};
+
+    EXPECT_EQ(kModeId90, selector.getBestFrameRateMode(layers)->getId());
+}
+
+TEST_P(RefreshRateSelectorTest, primaryVsAppRequestPolicy) {
+    auto selector = createSelector(kModes_30_60_90, kModeId60);
+
+    std::vector<LayerRequirement> layers = {{.weight = 1.f}};
+    layers[0].name = "Test layer";
+
+    struct Args {
+        bool touch = false;
+        bool focused = true;
+    };
+
+    // Returns the mode selected by getBestFrameRateMode for a single layer with the given
+    // arguments.
+    const auto getFrameRate = [&](LayerVoteType voteType, Fps fps,
+                                  Args args = {}) -> DisplayModeId {
+        layers[0].vote = voteType;
+        layers[0].desiredRefreshRate = fps;
+        layers[0].focused = args.focused;
+        return selector.getBestFrameRateMode(layers, {.touch = args.touch})->getId();
+    };
+
+    constexpr FpsRange k30_60 = {30_Hz, 60_Hz};
+    constexpr FpsRange k30_90 = {30_Hz, 90_Hz};
+
+    EXPECT_EQ(SetPolicyResult::Changed,
+              selector.setDisplayManagerPolicy({kModeId60, {k30_60, k30_60}, {k30_90, k30_90}}));
+
+    EXPECT_EQ(kModeId60, selector.getBestFrameRateMode()->getId());
+    EXPECT_EQ(kModeId60, getFrameRate(LayerVoteType::NoVote, 90_Hz));
+    EXPECT_EQ(kModeId30, getFrameRate(LayerVoteType::Min, 90_Hz));
+    EXPECT_EQ(kModeId60, getFrameRate(LayerVoteType::Max, 90_Hz));
+    EXPECT_EQ(kModeId60, getFrameRate(LayerVoteType::Heuristic, 90_Hz));
+    EXPECT_EQ(kModeId90, getFrameRate(LayerVoteType::ExplicitDefault, 90_Hz));
+    EXPECT_EQ(kModeId60, getFrameRate(LayerVoteType::ExplicitExactOrMultiple, 90_Hz));
+
+    // Unfocused layers are not allowed to override primary range.
+    EXPECT_EQ(kModeId60, getFrameRate(LayerVoteType::ExplicitDefault, 90_Hz, {.focused = false}));
+    EXPECT_EQ(kModeId60,
+              getFrameRate(LayerVoteType::ExplicitExactOrMultiple, 90_Hz, {.focused = false}));
+
+    // Touch boost should be restricted to the primary range.
+    EXPECT_EQ(kModeId60, getFrameRate(LayerVoteType::Max, 90_Hz, {.touch = true}));
+
+    // When we're higher than the primary range max due to a layer frame rate setting, touch boost
+    // shouldn't drag us back down to the primary range max.
+    EXPECT_EQ(kModeId90, getFrameRate(LayerVoteType::ExplicitDefault, 90_Hz, {.touch = true}));
+    EXPECT_EQ(kModeId60,
+              getFrameRate(LayerVoteType::ExplicitExactOrMultiple, 90_Hz, {.touch = true}));
+
+    EXPECT_EQ(SetPolicyResult::Changed,
+              selector.setDisplayManagerPolicy({kModeId60, {60_Hz, 60_Hz}}));
+
+    EXPECT_EQ(kModeId60, getFrameRate(LayerVoteType::NoVote, 90_Hz));
+    EXPECT_EQ(kModeId60, getFrameRate(LayerVoteType::Min, 90_Hz));
+    EXPECT_EQ(kModeId60, getFrameRate(LayerVoteType::Max, 90_Hz));
+    EXPECT_EQ(kModeId60, getFrameRate(LayerVoteType::Heuristic, 90_Hz));
+    EXPECT_EQ(kModeId60, getFrameRate(LayerVoteType::ExplicitDefault, 90_Hz));
+    EXPECT_EQ(kModeId60, getFrameRate(LayerVoteType::ExplicitExactOrMultiple, 90_Hz));
+}
+
+TEST_P(RefreshRateSelectorTest, idle) {
+    auto selector = createSelector(kModes_60_90, kModeId60);
+
+    std::vector<LayerRequirement> layers = {{.weight = 1.f}};
+    layers[0].name = "Test layer";
+
+    const auto getIdleDisplayModeId = [&](LayerVoteType voteType,
+                                          bool touchActive) -> DisplayModeId {
+        layers[0].vote = voteType;
+        layers[0].desiredRefreshRate = 90_Hz;
+
+        const auto [ranking, signals] =
+                selector.getRankedFrameRates(layers, {.touch = touchActive, .idle = true});
+
+        // Refresh rate will be chosen by either touch state or idle state.
+        EXPECT_EQ(!touchActive, signals.idle);
+        return ranking.front().frameRateMode.modePtr->getId();
+    };
+
+    EXPECT_EQ(SetPolicyResult::Changed,
+              selector.setDisplayManagerPolicy({kModeId60, {60_Hz, 90_Hz}}));
+
+    // Idle should be lower priority than touch boost.
+    {
+        constexpr bool kTouchActive = true;
+        EXPECT_EQ(kModeId90, getIdleDisplayModeId(LayerVoteType::NoVote, kTouchActive));
+        EXPECT_EQ(kModeId90, getIdleDisplayModeId(LayerVoteType::Min, kTouchActive));
+        EXPECT_EQ(kModeId90, getIdleDisplayModeId(LayerVoteType::Max, kTouchActive));
+        EXPECT_EQ(kModeId90, getIdleDisplayModeId(LayerVoteType::Heuristic, kTouchActive));
+        EXPECT_EQ(kModeId90, getIdleDisplayModeId(LayerVoteType::ExplicitDefault, kTouchActive));
+        EXPECT_EQ(kModeId90,
+                  getIdleDisplayModeId(LayerVoteType::ExplicitExactOrMultiple, kTouchActive));
+    }
+
+    // With no layers, idle should still be lower priority than touch boost.
+    EXPECT_EQ(kModeId90, selector.getBestFrameRateMode({}, {.touch = true, .idle = true})->getId());
+
+    // Idle should be higher precedence than other layer frame rate considerations.
+    selector.setActiveMode(kModeId90, 90_Hz);
+
+    {
+        constexpr bool kTouchActive = false;
+        EXPECT_EQ(kModeId60, getIdleDisplayModeId(LayerVoteType::NoVote, kTouchActive));
+        EXPECT_EQ(kModeId60, getIdleDisplayModeId(LayerVoteType::Min, kTouchActive));
+        EXPECT_EQ(kModeId60, getIdleDisplayModeId(LayerVoteType::Max, kTouchActive));
+        EXPECT_EQ(kModeId60, getIdleDisplayModeId(LayerVoteType::Heuristic, kTouchActive));
+        EXPECT_EQ(kModeId60, getIdleDisplayModeId(LayerVoteType::ExplicitDefault, kTouchActive));
+        EXPECT_EQ(kModeId60,
+                  getIdleDisplayModeId(LayerVoteType::ExplicitExactOrMultiple, kTouchActive));
+    }
+
+    // Idle should be applied rather than the active mode when there are no layers.
+    EXPECT_EQ(kModeId60, selector.getBestFrameRateMode({}, {.idle = true})->getId());
+}
+
+TEST_P(RefreshRateSelectorTest, findClosestKnownFrameRate) {
+    auto selector = createSelector(kModes_60_90, kModeId60);
+
+    for (float fps = 1.0f; fps <= 120.0f; fps += 0.1f) {
+        const auto knownFrameRate = selector.findClosestKnownFrameRate(Fps::fromValue(fps));
+        const Fps expectedFrameRate = [fps] {
+            if (fps < 26.91f) return 24_Hz;
+            if (fps < 37.51f) return 30_Hz;
+            if (fps < 52.51f) return 45_Hz;
+            if (fps < 66.01f) return 60_Hz;
+            if (fps < 81.01f) return 72_Hz;
+            return 90_Hz;
+        }();
+
+        EXPECT_EQ(expectedFrameRate, knownFrameRate);
+    }
+}
+
+TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_KnownFrameRate) {
+    auto selector = createSelector(kModes_60_90, kModeId60);
+
+    struct Expectation {
+        Fps fps;
+        ftl::NonNull<DisplayModePtr> mode;
+    };
+
+    const std::initializer_list<Expectation> knownFrameRatesExpectations = {
+            {24_Hz, kMode60}, {30_Hz, kMode60}, {45_Hz, kMode90},
+            {60_Hz, kMode60}, {72_Hz, kMode90}, {90_Hz, kMode90},
+    };
+
+    // Make sure the test tests all the known frame rate
+    const auto& knownFrameRates = selector.knownFrameRates();
+    const bool equal = std::equal(knownFrameRates.begin(), knownFrameRates.end(),
+                                  knownFrameRatesExpectations.begin(),
+                                  [](Fps fps, const Expectation& expected) {
+                                      return isApproxEqual(fps, expected.fps);
+                                  });
+    EXPECT_TRUE(equal);
+
+    std::vector<LayerRequirement> layers = {{.weight = 1.f}};
+    auto& layer = layers[0];
+    layer.vote = LayerVoteType::Heuristic;
+
+    for (const auto& [fps, mode] : knownFrameRatesExpectations) {
+        layer.desiredRefreshRate = fps;
+        EXPECT_EQ(mode, selector.getBestFrameRateMode(layers));
+    }
+}
+
+TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_ExplicitExact) {
+    auto selector = createSelector(kModes_30_60_72_90_120, kModeId60);
+
+    std::vector<LayerRequirement> layers = {{.weight = 1.f}, {.weight = 0.5f}};
+    auto& explicitExactLayer = layers[0];
+    auto& explicitExactOrMultipleLayer = layers[1];
+
+    explicitExactLayer.vote = LayerVoteType::ExplicitExact;
+    explicitExactLayer.name = "ExplicitExact";
+    explicitExactLayer.desiredRefreshRate = 30_Hz;
+
+    explicitExactOrMultipleLayer.vote = LayerVoteType::ExplicitExactOrMultiple;
+    explicitExactOrMultipleLayer.name = "ExplicitExactOrMultiple";
+    explicitExactOrMultipleLayer.desiredRefreshRate = 60_Hz;
+
+    if (GetParam() == Config::FrameRateOverride::Disabled) {
+        EXPECT_FRAME_RATE_MODE(kMode30, 30_Hz,
+                               selector.getBestScoredFrameRate(layers).frameRateMode);
+        EXPECT_FRAME_RATE_MODE(kMode30, 30_Hz,
+                               selector.getBestScoredFrameRate(layers, {.touch = true})
+                                       .frameRateMode);
+
+    } else {
+        EXPECT_FRAME_RATE_MODE(kMode60, 60_Hz,
+                               selector.getBestScoredFrameRate(layers).frameRateMode);
+        EXPECT_FRAME_RATE_MODE(kMode120, 120_Hz,
+                               selector.getBestScoredFrameRate(layers, {.touch = true})
+                                       .frameRateMode);
+    }
+
+    explicitExactOrMultipleLayer.desiredRefreshRate = 120_Hz;
+    explicitExactLayer.desiredRefreshRate = 60_Hz;
+
+    if (GetParam() == Config::FrameRateOverride::Disabled) {
+        EXPECT_FRAME_RATE_MODE(kMode60, 60_Hz,
+                               selector.getBestScoredFrameRate(layers).frameRateMode);
+    } else {
+        EXPECT_FRAME_RATE_MODE(kMode120, 120_Hz,
+                               selector.getBestScoredFrameRate(layers).frameRateMode);
+    }
+
+    explicitExactLayer.desiredRefreshRate = 72_Hz;
+    EXPECT_FRAME_RATE_MODE(kMode72, 72_Hz, selector.getBestScoredFrameRate(layers).frameRateMode);
+
+    explicitExactLayer.desiredRefreshRate = 90_Hz;
+    EXPECT_FRAME_RATE_MODE(kMode90, 90_Hz, selector.getBestScoredFrameRate(layers).frameRateMode);
+
+    explicitExactLayer.desiredRefreshRate = 120_Hz;
+    EXPECT_FRAME_RATE_MODE(kMode120, 120_Hz, selector.getBestScoredFrameRate(layers).frameRateMode);
+}
+
+TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_ReadsCache) {
+    auto selector = createSelector(kModes_30_60_72_90_120, kModeId60);
+
+    using GlobalSignals = RefreshRateSelector::GlobalSignals;
+    const auto args = std::make_pair(std::vector<LayerRequirement>{},
+                                     GlobalSignals{.touch = true, .idle = true});
+
+    const RefreshRateSelector::RankedFrameRates result = {{RefreshRateSelector::ScoredFrameRate{
+                                                                  {90_Hz, kMode90}}},
+                                                          GlobalSignals{.touch = true}};
+
+    selector.mutableGetRankedRefreshRatesCache() = {args, result};
+
+    EXPECT_EQ(result, selector.getRankedFrameRates(args.first, args.second));
+}
+
+TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_WritesCache) {
+    auto selector = createSelector(kModes_30_60_72_90_120, kModeId60);
+
+    EXPECT_FALSE(selector.mutableGetRankedRefreshRatesCache());
+
+    std::vector<LayerRequirement> layers = {{.weight = 1.f}, {.weight = 0.5f}};
+    RefreshRateSelector::GlobalSignals globalSignals{.touch = true, .idle = true};
+
+    const auto result = selector.getRankedFrameRates(layers, globalSignals);
+
+    const auto& cache = selector.mutableGetRankedRefreshRatesCache();
+    ASSERT_TRUE(cache);
+
+    EXPECT_EQ(cache->arguments, std::make_pair(layers, globalSignals));
+    EXPECT_EQ(cache->result, result);
+}
+
+TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_ExplicitExactTouchBoost) {
+    auto selector = createSelector(kModes_60_120, kModeId60);
+
+    std::vector<LayerRequirement> layers = {{.weight = 1.f}, {.weight = 0.5f}};
+    auto& explicitExactLayer = layers[0];
+    auto& explicitExactOrMultipleLayer = layers[1];
+
+    explicitExactOrMultipleLayer.vote = LayerVoteType::ExplicitExactOrMultiple;
+    explicitExactOrMultipleLayer.name = "ExplicitExactOrMultiple";
+    explicitExactOrMultipleLayer.desiredRefreshRate = 60_Hz;
+
+    explicitExactLayer.vote = LayerVoteType::ExplicitExact;
+    explicitExactLayer.name = "ExplicitExact";
+    explicitExactLayer.desiredRefreshRate = 30_Hz;
+
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
+    if (GetParam() == Config::FrameRateOverride::Disabled) {
+        EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers, {.touch = true}));
+    } else {
+        EXPECT_EQ(kMode120, selector.getBestFrameRateMode(layers, {.touch = true}));
+    }
+
+    explicitExactOrMultipleLayer.vote = LayerVoteType::NoVote;
+
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers, {.touch = true}));
+}
+
+TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_FractionalRefreshRates_ExactAndDefault) {
+    auto selector = createSelector(kModes_24_25_30_50_60_Frac, kModeId60);
+
+    std::vector<LayerRequirement> layers = {{.weight = 0.5f}, {.weight = 0.5f}};
+    auto& explicitDefaultLayer = layers[0];
+    auto& explicitExactOrMultipleLayer = layers[1];
+
+    explicitExactOrMultipleLayer.vote = LayerVoteType::ExplicitExactOrMultiple;
+    explicitExactOrMultipleLayer.name = "ExplicitExactOrMultiple";
+    explicitExactOrMultipleLayer.desiredRefreshRate = 60_Hz;
+
+    explicitDefaultLayer.vote = LayerVoteType::ExplicitDefault;
+    explicitDefaultLayer.name = "ExplicitDefault";
+    explicitDefaultLayer.desiredRefreshRate = 59.94_Hz;
+
+    EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers));
+}
+
+// b/190578904
+TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_withCloseRefreshRates) {
+    if (g_noSlowTests) {
+        GTEST_SKIP();
+    }
+
+    const int kMinRefreshRate = RefreshRateSelector::kMinSupportedFrameRate.getIntValue();
+    constexpr int kMaxRefreshRate = 240;
+
+    DisplayModes displayModes;
+    for (int fps = kMinRefreshRate; fps < kMaxRefreshRate; fps++) {
+        const DisplayModeId modeId(fps);
+        displayModes.try_emplace(modeId,
+                                 createDisplayMode(modeId,
+                                                   Fps::fromValue(static_cast<float>(fps))));
+    }
+
+    const auto selector = createSelector(std::move(displayModes), DisplayModeId(kMinRefreshRate));
+
+    std::vector<LayerRequirement> layers = {{.weight = 1.f}};
+    const auto testRefreshRate = [&](Fps fps, LayerVoteType vote) {
+        layers[0].desiredRefreshRate = fps;
+        layers[0].vote = vote;
+        EXPECT_EQ(fps.getIntValue(), selector.getBestFrameRateMode(layers)->getFps().getIntValue())
+                << "Failed for " << ftl::enum_string(vote);
+    };
+
+    for (int fps = kMinRefreshRate; fps < kMaxRefreshRate; fps++) {
+        const auto refreshRate = Fps::fromValue(static_cast<float>(fps));
+        testRefreshRate(refreshRate, LayerVoteType::Heuristic);
+        testRefreshRate(refreshRate, LayerVoteType::ExplicitDefault);
+        testRefreshRate(refreshRate, LayerVoteType::ExplicitExactOrMultiple);
+        testRefreshRate(refreshRate, LayerVoteType::ExplicitExact);
+    }
+}
+
+// b/190578904
+TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_conflictingVotes) {
+    constexpr DisplayModeId kActiveModeId{0};
+    DisplayModes displayModes = makeModes(createDisplayMode(kActiveModeId, 43_Hz),
+                                          createDisplayMode(DisplayModeId(1), 53_Hz),
+                                          createDisplayMode(DisplayModeId(2), 55_Hz),
+                                          createDisplayMode(DisplayModeId(3), 60_Hz));
+
+    const RefreshRateSelector::GlobalSignals globalSignals = {.touch = false, .idle = false};
+    const auto selector = createSelector(std::move(displayModes), kActiveModeId);
+
+    const std::vector<LayerRequirement> layers = {
+            {
+                    .vote = LayerVoteType::ExplicitDefault,
+                    .desiredRefreshRate = 43_Hz,
+                    .seamlessness = Seamlessness::SeamedAndSeamless,
+                    .weight = 0.41f,
+            },
+            {
+                    .vote = LayerVoteType::ExplicitExactOrMultiple,
+                    .desiredRefreshRate = 53_Hz,
+                    .seamlessness = Seamlessness::SeamedAndSeamless,
+                    .weight = 0.41f,
+            },
+    };
+
+    EXPECT_EQ(53_Hz, selector.getBestFrameRateMode(layers, globalSignals)->getFps());
+}
+
+TEST_P(RefreshRateSelectorTest, modeComparison) {
+    EXPECT_LT(kMode60->getFps(), kMode90->getFps());
+    EXPECT_GE(kMode60->getFps(), kMode60->getFps());
+    EXPECT_GE(kMode90->getFps(), kMode90->getFps());
+}
+
+TEST_P(RefreshRateSelectorTest, testKernelIdleTimerAction) {
+    using KernelIdleTimerAction = RefreshRateSelector::KernelIdleTimerAction;
+
+    auto selector = createSelector(kModes_60_90, kModeId90);
+
+    EXPECT_EQ(KernelIdleTimerAction::TurnOn, selector.getIdleTimerAction());
+
+    EXPECT_EQ(SetPolicyResult::Changed,
+              selector.setDisplayManagerPolicy({kModeId60, {60_Hz, 90_Hz}}));
+    EXPECT_EQ(KernelIdleTimerAction::TurnOn, selector.getIdleTimerAction());
+
+    EXPECT_EQ(SetPolicyResult::Changed,
+              selector.setDisplayManagerPolicy({kModeId60, {60_Hz, 60_Hz}}));
+    EXPECT_EQ(KernelIdleTimerAction::TurnOff, selector.getIdleTimerAction());
+
+    EXPECT_EQ(SetPolicyResult::Changed,
+              selector.setDisplayManagerPolicy({kModeId90, {90_Hz, 90_Hz}}));
+    EXPECT_EQ(KernelIdleTimerAction::TurnOff, selector.getIdleTimerAction());
+}
+
+TEST_P(RefreshRateSelectorTest, testKernelIdleTimerActionFor120Hz) {
+    using KernelIdleTimerAction = RefreshRateSelector::KernelIdleTimerAction;
+
+    auto selector = createSelector(kModes_60_120, kModeId120);
+
+    EXPECT_EQ(SetPolicyResult::Changed,
+              selector.setDisplayManagerPolicy({kModeId60, {0_Hz, 60_Hz}}));
+    EXPECT_EQ(KernelIdleTimerAction::TurnOn, selector.getIdleTimerAction());
+
+    EXPECT_EQ(SetPolicyResult::Changed,
+              selector.setDisplayManagerPolicy({kModeId60, {60_Hz, 60_Hz}}));
+    EXPECT_EQ(KernelIdleTimerAction::TurnOff, selector.getIdleTimerAction());
+
+    EXPECT_EQ(SetPolicyResult::Changed,
+              selector.setDisplayManagerPolicy({kModeId60, {60_Hz, 120_Hz}}));
+    EXPECT_EQ(KernelIdleTimerAction::TurnOn, selector.getIdleTimerAction());
+
+    EXPECT_EQ(SetPolicyResult::Changed,
+              selector.setDisplayManagerPolicy({kModeId120, {120_Hz, 120_Hz}}));
+    EXPECT_EQ(KernelIdleTimerAction::TurnOff, selector.getIdleTimerAction());
+}
+
+TEST_P(RefreshRateSelectorTest, getFrameRateDivisor) {
+    auto selector = createSelector(kModes_30_60_72_90_120, kModeId30);
+
+    const auto frameRate = 30_Hz;
+    Fps displayRefreshRate = selector.getActiveMode().getFps();
+    EXPECT_EQ(1, RefreshRateSelector::getFrameRateDivisor(displayRefreshRate, frameRate));
+
+    selector.setActiveMode(kModeId60, 60_Hz);
+    displayRefreshRate = selector.getActiveMode().getFps();
+    EXPECT_EQ(2, RefreshRateSelector::getFrameRateDivisor(displayRefreshRate, frameRate));
+
+    selector.setActiveMode(kModeId72, 72_Hz);
+    displayRefreshRate = selector.getActiveMode().getFps();
+    EXPECT_EQ(0, RefreshRateSelector::getFrameRateDivisor(displayRefreshRate, frameRate));
+
+    selector.setActiveMode(kModeId90, 90_Hz);
+    displayRefreshRate = selector.getActiveMode().getFps();
+    EXPECT_EQ(3, RefreshRateSelector::getFrameRateDivisor(displayRefreshRate, frameRate));
+
+    selector.setActiveMode(kModeId120, 120_Hz);
+    displayRefreshRate = selector.getActiveMode().getFps();
+    EXPECT_EQ(4, RefreshRateSelector::getFrameRateDivisor(displayRefreshRate, frameRate));
+
+    selector.setActiveMode(kModeId90, 90_Hz);
+    displayRefreshRate = selector.getActiveMode().getFps();
+    EXPECT_EQ(4, RefreshRateSelector::getFrameRateDivisor(displayRefreshRate, 22.5_Hz));
+
+    EXPECT_EQ(0, RefreshRateSelector::getFrameRateDivisor(24_Hz, 25_Hz));
+    EXPECT_EQ(0, RefreshRateSelector::getFrameRateDivisor(24_Hz, 23.976_Hz));
+    EXPECT_EQ(0, RefreshRateSelector::getFrameRateDivisor(30_Hz, 29.97_Hz));
+    EXPECT_EQ(0, RefreshRateSelector::getFrameRateDivisor(60_Hz, 59.94_Hz));
+}
+
+TEST_P(RefreshRateSelectorTest, isFractionalPairOrMultiple) {
+    EXPECT_TRUE(RefreshRateSelector::isFractionalPairOrMultiple(23.976_Hz, 24_Hz));
+    EXPECT_TRUE(RefreshRateSelector::isFractionalPairOrMultiple(24_Hz, 23.976_Hz));
+
+    EXPECT_TRUE(RefreshRateSelector::isFractionalPairOrMultiple(29.97_Hz, 30_Hz));
+    EXPECT_TRUE(RefreshRateSelector::isFractionalPairOrMultiple(30_Hz, 29.97_Hz));
+
+    EXPECT_TRUE(RefreshRateSelector::isFractionalPairOrMultiple(59.94_Hz, 60_Hz));
+    EXPECT_TRUE(RefreshRateSelector::isFractionalPairOrMultiple(60_Hz, 59.94_Hz));
+
+    EXPECT_TRUE(RefreshRateSelector::isFractionalPairOrMultiple(29.97_Hz, 60_Hz));
+    EXPECT_TRUE(RefreshRateSelector::isFractionalPairOrMultiple(60_Hz, 29.97_Hz));
+
+    EXPECT_TRUE(RefreshRateSelector::isFractionalPairOrMultiple(59.94_Hz, 30_Hz));
+    EXPECT_TRUE(RefreshRateSelector::isFractionalPairOrMultiple(30_Hz, 59.94_Hz));
+
+    const auto refreshRates = {23.976_Hz, 24_Hz, 25_Hz, 29.97_Hz, 30_Hz, 50_Hz, 59.94_Hz, 60_Hz};
+    for (auto refreshRate : refreshRates) {
+        EXPECT_FALSE(RefreshRateSelector::isFractionalPairOrMultiple(refreshRate, refreshRate));
+    }
+
+    EXPECT_FALSE(RefreshRateSelector::isFractionalPairOrMultiple(24_Hz, 25_Hz));
+    EXPECT_FALSE(RefreshRateSelector::isFractionalPairOrMultiple(23.978_Hz, 25_Hz));
+    EXPECT_FALSE(RefreshRateSelector::isFractionalPairOrMultiple(29.97_Hz, 59.94_Hz));
+}
+
+TEST_P(RefreshRateSelectorTest, getFrameRateOverrides_noLayers) {
+    auto selector = createSelector(kModes_30_60_72_90_120, kModeId120);
+
+    EXPECT_TRUE(selector.getFrameRateOverrides({}, 120_Hz, {}).empty());
+}
+
+TEST_P(RefreshRateSelectorTest, getFrameRateOverrides_NonExplicit) {
+    auto selector = createSelector(kModes_30_60_72_90_120, kModeId120);
+
+    std::vector<LayerRequirement> layers = {{.weight = 1.f}};
+    layers[0].name = "Test layer";
+    layers[0].ownerUid = 1234;
+    layers[0].desiredRefreshRate = 60_Hz;
+
+    layers[0].vote = LayerVoteType::NoVote;
+    EXPECT_TRUE(selector.getFrameRateOverrides(layers, 120_Hz, {}).empty());
+
+    layers[0].vote = LayerVoteType::Min;
+    EXPECT_TRUE(selector.getFrameRateOverrides(layers, 120_Hz, {}).empty());
+
+    layers[0].vote = LayerVoteType::Max;
+    EXPECT_TRUE(selector.getFrameRateOverrides(layers, 120_Hz, {}).empty());
+
+    layers[0].vote = LayerVoteType::Heuristic;
+    EXPECT_TRUE(selector.getFrameRateOverrides(layers, 120_Hz, {}).empty());
+}
+
+TEST_P(RefreshRateSelectorTest, getFrameRateOverrides_Disabled) {
+    if (GetParam() != Config::FrameRateOverride::Disabled) {
+        return;
+    }
+
+    auto selector = createSelector(kModes_30_60_72_90_120, kModeId120);
+
+    std::vector<LayerRequirement> layers = {{.weight = 1.f}};
+    layers[0].name = "Test layer";
+    layers[0].ownerUid = 1234;
+    layers[0].desiredRefreshRate = 60_Hz;
+
+    layers[0].vote = LayerVoteType::ExplicitDefault;
+    EXPECT_TRUE(selector.getFrameRateOverrides(layers, 120_Hz, {}).empty());
+
+    layers[0].vote = LayerVoteType::ExplicitExactOrMultiple;
+    EXPECT_TRUE(selector.getFrameRateOverrides(layers, 120_Hz, {}).empty());
+
+    layers[0].vote = LayerVoteType::ExplicitExact;
+    EXPECT_TRUE(selector.getFrameRateOverrides(layers, 120_Hz, {}).empty());
+}
+
+TEST_P(RefreshRateSelectorTest, getFrameRateOverrides_60on120) {
+    if (GetParam() == Config::FrameRateOverride::Disabled) {
+        return;
+    }
+
+    ASSERT_TRUE(GetParam() == Config::FrameRateOverride::AppOverrideNativeRefreshRates ||
+                GetParam() == Config::FrameRateOverride::AppOverride ||
+                GetParam() == Config::FrameRateOverride::Enabled);
+
+    auto selector = createSelector(kModes_30_60_72_90_120, kModeId120);
+
+    std::vector<LayerRequirement> layers = {{.weight = 1.f}};
+    layers[0].name = "Test layer";
+    layers[0].ownerUid = 1234;
+    layers[0].desiredRefreshRate = 60_Hz;
+
+    layers[0].vote = LayerVoteType::ExplicitDefault;
+    auto frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {});
+    EXPECT_EQ(1u, frameRateOverrides.size());
+    ASSERT_EQ(1u, frameRateOverrides.count(1234));
+    EXPECT_EQ(60_Hz, frameRateOverrides.at(1234));
+
+    layers[0].vote = LayerVoteType::ExplicitExactOrMultiple;
+    frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {});
+    EXPECT_EQ(1u, frameRateOverrides.size());
+    ASSERT_EQ(1u, frameRateOverrides.count(1234));
+    EXPECT_EQ(60_Hz, frameRateOverrides.at(1234));
+
+    layers[0].vote = LayerVoteType::ExplicitExact;
+    frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {});
+    EXPECT_EQ(1u, frameRateOverrides.size());
+    ASSERT_EQ(1u, frameRateOverrides.count(1234));
+    EXPECT_EQ(60_Hz, frameRateOverrides.at(1234));
+}
+
+TEST_P(RefreshRateSelectorTest, getFrameRateOverrides_twoUids) {
+    if (GetParam() == Config::FrameRateOverride::Disabled) {
+        return;
+    }
+
+    ASSERT_TRUE(GetParam() == Config::FrameRateOverride::AppOverrideNativeRefreshRates ||
+                GetParam() == Config::FrameRateOverride::AppOverride ||
+                GetParam() == Config::FrameRateOverride::Enabled);
+
+    auto selector = createSelector(kModes_30_60_72_90_120, kModeId120);
+
+    std::vector<LayerRequirement> layers = {{.ownerUid = 1234, .weight = 1.f},
+                                            {.ownerUid = 5678, .weight = 1.f}};
+
+    layers[0].name = "Test layer 1234";
+    layers[0].desiredRefreshRate = 60_Hz;
+    layers[0].vote = LayerVoteType::ExplicitDefault;
+
+    layers[1].name = "Test layer 5678";
+    layers[1].desiredRefreshRate = 30_Hz;
+    layers[1].vote = LayerVoteType::ExplicitDefault;
+    auto frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {});
+
+    EXPECT_EQ(2u, frameRateOverrides.size());
+    ASSERT_EQ(1u, frameRateOverrides.count(1234));
+    EXPECT_EQ(60_Hz, frameRateOverrides.at(1234));
+    ASSERT_EQ(1u, frameRateOverrides.count(5678));
+    EXPECT_EQ(30_Hz, frameRateOverrides.at(5678));
+
+    layers[1].vote = LayerVoteType::Heuristic;
+    frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {});
+    EXPECT_EQ(1u, frameRateOverrides.size());
+    ASSERT_EQ(1u, frameRateOverrides.count(1234));
+    EXPECT_EQ(60_Hz, frameRateOverrides.at(1234));
+
+    layers[1].ownerUid = 1234;
+    frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {});
+    EXPECT_TRUE(frameRateOverrides.empty());
+}
+
+TEST_P(RefreshRateSelectorTest, getFrameRateOverrides_touch) {
+    if (GetParam() == Config::FrameRateOverride::Disabled) {
+        return;
+    }
+
+    ASSERT_TRUE(GetParam() == Config::FrameRateOverride::AppOverrideNativeRefreshRates ||
+                GetParam() == Config::FrameRateOverride::AppOverride ||
+                GetParam() == Config::FrameRateOverride::Enabled);
+
+    auto selector = createSelector(kModes_30_60_72_90_120, kModeId120);
+
+    std::vector<LayerRequirement> layers = {{.ownerUid = 1234, .weight = 1.f}};
+    layers[0].name = "Test layer";
+    layers[0].desiredRefreshRate = 60_Hz;
+    layers[0].vote = LayerVoteType::ExplicitDefault;
+
+    auto frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {});
+    EXPECT_EQ(1u, frameRateOverrides.size());
+    ASSERT_EQ(1u, frameRateOverrides.count(1234));
+    EXPECT_EQ(60_Hz, frameRateOverrides.at(1234));
+
+    frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {.touch = true});
+    EXPECT_EQ(1u, frameRateOverrides.size());
+    ASSERT_EQ(1u, frameRateOverrides.count(1234));
+    EXPECT_EQ(60_Hz, frameRateOverrides.at(1234));
+
+    layers[0].vote = LayerVoteType::ExplicitExact;
+    frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {});
+    EXPECT_EQ(1u, frameRateOverrides.size());
+    ASSERT_EQ(1u, frameRateOverrides.count(1234));
+    EXPECT_EQ(60_Hz, frameRateOverrides.at(1234));
+
+    frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {.touch = true});
+    EXPECT_EQ(1u, frameRateOverrides.size());
+    ASSERT_EQ(1u, frameRateOverrides.count(1234));
+    EXPECT_EQ(60_Hz, frameRateOverrides.at(1234));
+
+    layers[0].vote = LayerVoteType::ExplicitExactOrMultiple;
+    frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {});
+    EXPECT_EQ(1u, frameRateOverrides.size());
+    ASSERT_EQ(1u, frameRateOverrides.count(1234));
+    EXPECT_EQ(60_Hz, frameRateOverrides.at(1234));
+
+    frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {.touch = true});
+    EXPECT_TRUE(frameRateOverrides.empty());
+}
+
+TEST_P(RefreshRateSelectorTest, getFrameRateOverrides_DivisorIsNotDisplayRefreshRate) {
+    if (GetParam() == Config::FrameRateOverride::Disabled) {
+        return;
+    }
+
+    ASSERT_TRUE(GetParam() == Config::FrameRateOverride::AppOverrideNativeRefreshRates ||
+                GetParam() == Config::FrameRateOverride::AppOverride ||
+                GetParam() == Config::FrameRateOverride::Enabled);
+
+    auto selector = createSelector(kModes_60_120, kModeId120);
+
+    std::vector<LayerRequirement> layers = {{.weight = 1.f}};
+    layers[0].name = "Test layer";
+    layers[0].ownerUid = 1234;
+    layers[0].desiredRefreshRate = 30_Hz;
+
+    const auto expetedFps =
+            GetParam() == Config::FrameRateOverride::AppOverrideNativeRefreshRates ? 60_Hz : 30_Hz;
+    layers[0].vote = LayerVoteType::ExplicitDefault;
+    auto frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {});
+    EXPECT_EQ(1u, frameRateOverrides.size());
+    ASSERT_EQ(1u, frameRateOverrides.count(1234));
+    EXPECT_EQ(expetedFps, frameRateOverrides.at(1234));
+
+    layers[0].vote = LayerVoteType::ExplicitExactOrMultiple;
+    frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {});
+    EXPECT_EQ(1u, frameRateOverrides.size());
+    ASSERT_EQ(1u, frameRateOverrides.count(1234));
+    EXPECT_EQ(expetedFps, frameRateOverrides.at(1234));
+
+    layers[0].vote = LayerVoteType::ExplicitExact;
+    frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {});
+    EXPECT_EQ(1u, frameRateOverrides.size());
+    ASSERT_EQ(1u, frameRateOverrides.count(1234));
+    EXPECT_EQ(expetedFps, frameRateOverrides.at(1234));
+}
+
+TEST_P(RefreshRateSelectorTest, renderFrameRateInvalidPolicy) {
+    auto selector = createSelector(kModes_60_120, kModeId120);
+
+    // The render frame rate cannot be greater than the physical refresh rate
+    {
+        const FpsRange physical = {60_Hz, 60_Hz};
+        const FpsRange render = {60_Hz, 120_Hz};
+        EXPECT_EQ(SetPolicyResult::Invalid,
+                  selector.setDisplayManagerPolicy(
+                          {kModeId60, {physical, render}, {physical, render}}));
+    }
+}
+
+TEST_P(RefreshRateSelectorTest, renderFrameRateRestrictsPhysicalRefreshRate) {
+    auto selector = createSelector(kModes_60_120, kModeId120);
+
+    {
+        const FpsRange physical = {0_Hz, 120_Hz};
+        const FpsRange render = {0_Hz, 60_Hz};
+        EXPECT_EQ(SetPolicyResult::Changed,
+                  selector.setDisplayManagerPolicy(
+                          {kModeId60, {physical, render}, {physical, render}}));
+        const auto expectedMaxMode =
+                GetParam() == Config::FrameRateOverride::Enabled ? kMode120 : kMode60;
+        EXPECT_EQ(expectedMaxMode, selector.getMaxRefreshRateByPolicy());
+        EXPECT_EQ(kMode60, selector.getMinRefreshRateByPolicy());
+    }
+
+    {
+        const FpsRange physical = {0_Hz, 120_Hz};
+        const FpsRange render = {120_Hz, 120_Hz};
+        EXPECT_EQ(SetPolicyResult::Changed,
+                  selector.setDisplayManagerPolicy(
+                          {kModeId60, {physical, render}, {physical, render}}));
+        EXPECT_EQ(kMode120, selector.getMaxRefreshRateByPolicy());
+        EXPECT_EQ(kMode120, selector.getMinRefreshRateByPolicy());
+    }
+}
+
+TEST_P(RefreshRateSelectorTest, getFrameRateOverrides_InPolicy) {
+    if (GetParam() != Config::FrameRateOverride::Enabled) {
+        return;
+    }
+    auto selector = createSelector(kModes_30_60_72_90_120, kModeId120);
+
+    std::vector<LayerRequirement> layers = {{.weight = 1.f}};
+    {
+        const FpsRange physical = {120_Hz, 120_Hz};
+        const FpsRange render = {60_Hz, 90_Hz};
+        EXPECT_EQ(SetPolicyResult::Changed,
+                  selector.setDisplayManagerPolicy(
+                          {kModeId120, {physical, render}, {physical, render}}));
+    }
+
+    layers[0].name = "30Hz";
+    layers[0].ownerUid = 1234;
+    layers[0].desiredRefreshRate = 30_Hz;
+    layers[0].vote = LayerVoteType::ExplicitDefault;
+
+    auto frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {});
+    EXPECT_EQ(1u, frameRateOverrides.size());
+    EXPECT_EQ(1u, frameRateOverrides.count(1234));
+    EXPECT_EQ(60_Hz, frameRateOverrides.at(1234));
+
+    {
+        const FpsRange physical = {120_Hz, 120_Hz};
+        const FpsRange render = {30_Hz, 90_Hz};
+        EXPECT_EQ(SetPolicyResult::Changed,
+                  selector.setDisplayManagerPolicy(
+                          {kModeId120, {physical, render}, {physical, render}}));
+    }
+
+    frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {});
+    EXPECT_EQ(1u, frameRateOverrides.size());
+    EXPECT_EQ(1u, frameRateOverrides.count(1234));
+    EXPECT_EQ(30_Hz, frameRateOverrides.at(1234));
+
+    {
+        const FpsRange physical = {120_Hz, 120_Hz};
+        const FpsRange render = {30_Hz, 30_Hz};
+        EXPECT_EQ(SetPolicyResult::Changed,
+                  selector.setDisplayManagerPolicy(
+                          {kModeId120, {physical, render}, {physical, render}}));
+    }
+
+    layers[0].name = "60Hz";
+    layers[0].ownerUid = 1234;
+    layers[0].desiredRefreshRate = 60_Hz;
+    layers[0].vote = LayerVoteType::ExplicitDefault;
+
+    frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {});
+    EXPECT_EQ(1u, frameRateOverrides.size());
+    EXPECT_EQ(1u, frameRateOverrides.count(1234));
+    EXPECT_EQ(30_Hz, frameRateOverrides.at(1234));
+}
+
+TEST_P(RefreshRateSelectorTest, renderFrameRates) {
+    auto selector = createSelector(kModes_30_60_72_90_120, kModeId120);
+
+    // [renderRate, refreshRate]
+    const auto expected = []() -> std::vector<std::pair<Fps, Fps>> {
+        switch (GetParam()) {
+            case Config::FrameRateOverride::Disabled:
+            case Config::FrameRateOverride::AppOverrideNativeRefreshRates:
+            case Config::FrameRateOverride::AppOverride:
+                return {{30_Hz, 30_Hz},
+                        {60_Hz, 60_Hz},
+                        {72_Hz, 72_Hz},
+                        {90_Hz, 90_Hz},
+                        {120_Hz, 120_Hz}};
+            case Config::FrameRateOverride::Enabled:
+                return {{30_Hz, 30_Hz}, {36_Hz, 72_Hz}, {40_Hz, 120_Hz}, {45_Hz, 90_Hz},
+                        {60_Hz, 60_Hz}, {72_Hz, 72_Hz}, {90_Hz, 90_Hz},  {120_Hz, 120_Hz}};
+        }
+    }();
+
+    const auto& primaryRefreshRates = selector.getPrimaryFrameRates();
+    ASSERT_EQ(expected.size(), primaryRefreshRates.size());
+
+    for (size_t i = 0; i < expected.size(); i++) {
+        const auto [expectedRenderRate, expectedRefreshRate] = expected[i];
+        EXPECT_EQ(expectedRenderRate, primaryRefreshRates[i].fps);
+        EXPECT_EQ(expectedRefreshRate, primaryRefreshRates[i].modePtr->getFps());
+    }
+}
+
+TEST_P(RefreshRateSelectorTest, refreshRateIsCappedWithRenderFrameRate) {
+    if (GetParam() != Config::FrameRateOverride::Enabled) {
+        return;
+    }
+
+    auto selector = createSelector(kModes_60_120, kModeId60);
+
+    constexpr FpsRange k0_120Hz = {0_Hz, 120_Hz};
+    constexpr FpsRange k0_60Hz = {0_Hz, 60_Hz};
+
+    constexpr FpsRanges kAppRequest = {/*physical*/ k0_120Hz,
+                                       /*render*/ k0_120Hz};
+
+    EXPECT_FRAME_RATE_MODE(kMode120, 120_Hz, selector.getBestScoredFrameRate().frameRateMode);
+    {
+        constexpr FpsRanges kPrimary = {/*physical*/ k0_120Hz,
+                                        /*render*/ k0_120Hz};
+        EXPECT_EQ(SetPolicyResult::Changed,
+                  selector.setDisplayManagerPolicy({/*defaultMode*/ kModeId60,
+                                                    /*primaryRanges*/
+                                                    kPrimary,
+                                                    /*appRequestRanges*/
+                                                    kAppRequest}));
+    }
+    EXPECT_FRAME_RATE_MODE(kMode120, 120_Hz, selector.getBestScoredFrameRate().frameRateMode);
+
+    {
+        constexpr FpsRanges kPrimary = {/*physical*/ k0_60Hz,
+                                        /*render*/ k0_60Hz};
+        EXPECT_EQ(SetPolicyResult::Changed,
+                  selector.setDisplayManagerPolicy({/*defaultMode*/ kModeId60,
+                                                    /*primaryRanges*/
+                                                    kPrimary,
+                                                    /*appRequestRanges*/
+                                                    kAppRequest}));
+    }
+    EXPECT_FRAME_RATE_MODE(kMode60, 60_Hz, selector.getBestScoredFrameRate().frameRateMode);
+
+    {
+        constexpr FpsRanges kPrimary = {/*physical*/ k0_120Hz,
+                                        /*render*/ k0_60Hz};
+        EXPECT_EQ(SetPolicyResult::Changed,
+                  selector.setDisplayManagerPolicy({/*defaultMode*/ kModeId60,
+                                                    /*primaryRanges*/
+                                                    kPrimary,
+                                                    /*appRequestRanges*/
+                                                    kAppRequest}));
+    }
+    EXPECT_FRAME_RATE_MODE(kMode60, 60_Hz, selector.getBestScoredFrameRate().frameRateMode);
+}
+
+TEST_P(RefreshRateSelectorTest, renderFrameRates_60_120) {
+    auto selector = createSelector(kModes_60_120, kModeId120);
+
+    std::vector<LayerRequirement> layers = {{.weight = 1.f}};
+    auto& layer = layers[0];
+
+    const auto expectedRenderRate =
+            GetParam() == Config::FrameRateOverride::Enabled ? 30_Hz : 60_Hz;
+
+    layer.name = "30Hz ExplicitDefault";
+    layer.desiredRefreshRate = 30_Hz;
+    layer.vote = LayerVoteType::ExplicitDefault;
+    EXPECT_FRAME_RATE_MODE(kMode60, expectedRenderRate,
+                           selector.getBestScoredFrameRate(layers).frameRateMode);
+
+    layer.name = "30Hz Heuristic";
+    layer.desiredRefreshRate = 30_Hz;
+    layer.vote = LayerVoteType::Heuristic;
+    EXPECT_FRAME_RATE_MODE(kMode60, expectedRenderRate,
+                           selector.getBestScoredFrameRate(layers).frameRateMode);
+
+    layer.name = "30Hz ExplicitExactOrMultiple";
+    layer.desiredRefreshRate = 30_Hz;
+    layer.vote = LayerVoteType::ExplicitExactOrMultiple;
+    EXPECT_FRAME_RATE_MODE(kMode60, expectedRenderRate,
+                           selector.getBestScoredFrameRate(layers).frameRateMode);
+}
+
+TEST_P(RefreshRateSelectorTest, idleWhenLowestRefreshRateIsNotDivisor) {
+    auto selector = createSelector(kModes_35_60_90, kModeId60);
+
+    std::vector<LayerRequirement> layers = {{.weight = 1.f}};
+    layers[0].name = "Test layer";
+
+    const auto getIdleDisplayModeId = [&](LayerVoteType voteType,
+                                          bool touchActive) -> DisplayModeId {
+        layers[0].vote = voteType;
+        layers[0].desiredRefreshRate = 90_Hz;
+
+        const auto [ranking, signals] =
+                selector.getRankedFrameRates(layers, {.touch = touchActive, .idle = true});
+
+        // Refresh rate will be chosen by either touch state or idle state.
+        EXPECT_EQ(!touchActive, signals.idle);
+        return ranking.front().frameRateMode.modePtr->getId();
+    };
+
+    EXPECT_EQ(SetPolicyResult::Changed,
+              selector.setDisplayManagerPolicy({kModeId60, {0_Hz, 90_Hz}}));
+
+    // With no layers, idle should still be lower priority than touch boost.
+    EXPECT_EQ(kModeId90, selector.getBestFrameRateMode({}, {.touch = true, .idle = true})->getId());
+
+    // Idle should be higher precedence than other layer frame rate considerations.
+    selector.setActiveMode(kModeId90, 90_Hz);
+    {
+        constexpr bool kTouchActive = false;
+        EXPECT_EQ(kModeId35, getIdleDisplayModeId(LayerVoteType::NoVote, kTouchActive));
+        EXPECT_EQ(kModeId35, getIdleDisplayModeId(LayerVoteType::Min, kTouchActive));
+        EXPECT_EQ(kModeId35, getIdleDisplayModeId(LayerVoteType::Max, kTouchActive));
+        EXPECT_EQ(kModeId35, getIdleDisplayModeId(LayerVoteType::Heuristic, kTouchActive));
+        EXPECT_EQ(kModeId35, getIdleDisplayModeId(LayerVoteType::ExplicitDefault, kTouchActive));
+        EXPECT_EQ(kModeId35,
+                  getIdleDisplayModeId(LayerVoteType::ExplicitExactOrMultiple, kTouchActive));
+    }
+
+    // Idle should be applied rather than the active mode when there are no layers.
+    EXPECT_EQ(kModeId35, selector.getBestFrameRateMode({}, {.idle = true})->getId());
+}
+
+TEST_P(RefreshRateSelectorTest, policyCanBeInfinity) {
+    auto selector = createSelector(kModes_60_120, kModeId120);
+
+    constexpr Fps inf = Fps::fromValue(std::numeric_limits<float>::infinity());
+
+    using namespace fps_approx_ops;
+    selector.setDisplayManagerPolicy({kModeId60, {0_Hz, inf}});
+
+    // With no layers, idle should still be lower priority than touch boost.
+    EXPECT_EQ(kMode120, selector.getMaxRefreshRateByPolicy());
+    EXPECT_EQ(kMode60, selector.getMinRefreshRateByPolicy());
+}
+
+} // namespace
+} // namespace android::scheduler
diff --git a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
index 8d2130f..3ee53c9 100644
--- a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
@@ -20,9 +20,8 @@
 
 #include <mutex>
 
-#include "FakeDisplayInjector.h"
 #include "Scheduler/EventThread.h"
-#include "Scheduler/RefreshRateConfigs.h"
+#include "Scheduler/RefreshRateSelector.h"
 #include "TestableScheduler.h"
 #include "TestableSurfaceFlinger.h"
 #include "mock/DisplayHardware/MockDisplayMode.h"
@@ -41,9 +40,6 @@
 
 using MockEventThread = android::mock::EventThread;
 using MockLayer = android::mock::MockLayer;
-using FakeDisplayDeviceInjector = TestableSurfaceFlinger::FakeDisplayDeviceInjector;
-
-constexpr PhysicalDisplayId PHYSICAL_DISPLAY_ID = PhysicalDisplayId::fromPort(255u);
 
 class SchedulerTest : public testing::Test {
 protected:
@@ -61,21 +57,35 @@
 
     SchedulerTest();
 
-    static inline const DisplayModePtr kMode60_1 = createDisplayMode(DisplayModeId(0), 60_Hz);
-    static inline const DisplayModePtr kMode120_1 = createDisplayMode(DisplayModeId(1), 120_Hz);
-    static inline const DisplayModePtr kMode60_2 = createDisplayMode(DisplayModeId(2), 60_Hz);
-    static inline const DisplayModePtr kMode120_2 = createDisplayMode(DisplayModeId(3), 120_Hz);
+    static constexpr PhysicalDisplayId kDisplayId1 = PhysicalDisplayId::fromPort(255u);
+    static inline const ftl::NonNull<DisplayModePtr> kDisplay1Mode60 =
+            ftl::as_non_null(createDisplayMode(kDisplayId1, DisplayModeId(0), 60_Hz));
+    static inline const ftl::NonNull<DisplayModePtr> kDisplay1Mode120 =
+            ftl::as_non_null(createDisplayMode(kDisplayId1, DisplayModeId(1), 120_Hz));
+    static inline const DisplayModes kDisplay1Modes = makeModes(kDisplay1Mode60, kDisplay1Mode120);
 
-    std::shared_ptr<RefreshRateConfigs> mConfigs =
-            std::make_shared<RefreshRateConfigs>(makeModes(kMode60_1), kMode60_1->getId());
+    static constexpr PhysicalDisplayId kDisplayId2 = PhysicalDisplayId::fromPort(254u);
+    static inline const ftl::NonNull<DisplayModePtr> kDisplay2Mode60 =
+            ftl::as_non_null(createDisplayMode(kDisplayId2, DisplayModeId(0), 60_Hz));
+    static inline const ftl::NonNull<DisplayModePtr> kDisplay2Mode120 =
+            ftl::as_non_null(createDisplayMode(kDisplayId2, DisplayModeId(1), 120_Hz));
+    static inline const DisplayModes kDisplay2Modes = makeModes(kDisplay2Mode60, kDisplay2Mode120);
+
+    static constexpr PhysicalDisplayId kDisplayId3 = PhysicalDisplayId::fromPort(253u);
+    static inline const ftl::NonNull<DisplayModePtr> kDisplay3Mode60 =
+            ftl::as_non_null(createDisplayMode(kDisplayId3, DisplayModeId(0), 60_Hz));
+    static inline const DisplayModes kDisplay3Modes = makeModes(kDisplay3Mode60);
+
+    std::shared_ptr<RefreshRateSelector> mSelector =
+            std::make_shared<RefreshRateSelector>(makeModes(kDisplay1Mode60),
+                                                  kDisplay1Mode60->getId());
 
     mock::SchedulerCallback mSchedulerCallback;
-    TestableScheduler* mScheduler = new TestableScheduler{mConfigs, mSchedulerCallback};
+    TestableScheduler* mScheduler = new TestableScheduler{mSelector, mSchedulerCallback};
 
     ConnectionHandle mConnectionHandle;
     MockEventThread* mEventThread;
     sp<MockEventThreadConnection> mEventThreadConnection;
-    FakeDisplayInjector mFakeDisplayInjector;
 
     TestableSurfaceFlinger mFlinger;
 };
@@ -110,7 +120,7 @@
 
     // The EXPECT_CALLS make sure we don't call the functions on the subsequent event threads.
     EXPECT_CALL(*mEventThread, onHotplugReceived(_, _)).Times(0);
-    mScheduler->onHotplugReceived(handle, PHYSICAL_DISPLAY_ID, false);
+    mScheduler->onHotplugReceived(handle, kDisplayId1, false);
 
     EXPECT_CALL(*mEventThread, onScreenAcquired()).Times(0);
     mScheduler->onScreenAcquired(handle);
@@ -134,8 +144,8 @@
     ASSERT_EQ(mEventThreadConnection, connection);
     EXPECT_TRUE(mScheduler->getEventConnection(mConnectionHandle));
 
-    EXPECT_CALL(*mEventThread, onHotplugReceived(PHYSICAL_DISPLAY_ID, false)).Times(1);
-    mScheduler->onHotplugReceived(mConnectionHandle, PHYSICAL_DISPLAY_ID, false);
+    EXPECT_CALL(*mEventThread, onHotplugReceived(kDisplayId1, false)).Times(1);
+    mScheduler->onHotplugReceived(mConnectionHandle, kDisplayId1, false);
 
     EXPECT_CALL(*mEventThread, onScreenAcquired()).Times(1);
     mScheduler->onScreenAcquired(mConnectionHandle);
@@ -180,9 +190,10 @@
     sp<MockLayer> layer = sp<MockLayer>::make(mFlinger.flinger());
     ASSERT_EQ(1u, mScheduler->layerHistorySize());
 
-    mScheduler->setRefreshRateConfigs(
-            std::make_shared<RefreshRateConfigs>(makeModes(kMode60_1, kMode120_1),
-                                                 kMode60_1->getId()));
+    // Replace `mSelector` with a new `RefreshRateSelector` that has different display modes.
+    mScheduler->registerDisplay(kDisplayId1,
+                                std::make_shared<RefreshRateSelector>(kDisplay1Modes,
+                                                                      kDisplay1Mode60->getId()));
 
     ASSERT_EQ(0u, mScheduler->getNumActiveLayers());
     mScheduler->recordLayerHistory(layer.get(), 0, LayerHistory::LayerUpdateType::Buffer);
@@ -199,14 +210,16 @@
 TEST_F(SchedulerTest, onNonPrimaryDisplayModeChanged_invalidParameters) {
     const auto mode = DisplayMode::Builder(hal::HWConfigId(0))
                               .setId(DisplayModeId(111))
-                              .setPhysicalDisplayId(PHYSICAL_DISPLAY_ID)
+                              .setPhysicalDisplayId(kDisplayId1)
                               .setVsyncPeriod(111111)
                               .build();
 
     // If the handle is incorrect, the function should return before
     // onModeChange is called.
     ConnectionHandle invalidHandle = {.id = 123};
-    EXPECT_NO_FATAL_FAILURE(mScheduler->onNonPrimaryDisplayModeChanged(invalidHandle, mode));
+    EXPECT_NO_FATAL_FAILURE(
+            mScheduler->onNonPrimaryDisplayModeChanged(invalidHandle,
+                                                       {90_Hz, ftl::as_non_null(mode)}));
     EXPECT_CALL(*mEventThread, onModeChanged(_)).Times(0);
 }
 
@@ -221,18 +234,13 @@
 }
 
 MATCHER(Is120Hz, "") {
-    return isApproxEqual(arg.front().displayModePtr->getFps(), 120_Hz);
+    return isApproxEqual(arg.front().mode.fps, 120_Hz);
 }
 
 TEST_F(SchedulerTest, chooseRefreshRateForContentSelectsMaxRefreshRate) {
-    auto display = mFakeDisplayInjector.injectDefaultInternalDisplay(
-            [&](FakeDisplayDeviceInjector& injector) {
-                injector.setDisplayModes(makeModes(kMode60_1, kMode120_1), kMode60_1->getId());
-            },
-            mFlinger);
-
-    mScheduler->registerDisplay(display);
-    mScheduler->setRefreshRateConfigs(display->holdRefreshRateConfigs());
+    mScheduler->registerDisplay(kDisplayId1,
+                                std::make_shared<RefreshRateSelector>(kDisplay1Modes,
+                                                                      kDisplay1Mode60->getId()));
 
     const sp<MockLayer> layer = sp<MockLayer>::make(mFlinger.flinger());
     EXPECT_CALL(*layer, isVisible()).WillOnce(Return(true));
@@ -253,102 +261,137 @@
     mScheduler->chooseRefreshRateForContent();
 }
 
-TEST_F(SchedulerTest, getBestDisplayMode_singleDisplay) {
-    auto display = mFakeDisplayInjector.injectDefaultInternalDisplay(
-            [&](FakeDisplayDeviceInjector& injector) {
-                injector.setDisplayModes(makeModes(kMode60_1, kMode120_1), kMode60_1->getId());
-            },
-            mFlinger);
-    mScheduler->registerDisplay(display);
+TEST_F(SchedulerTest, chooseDisplayModesSingleDisplay) {
+    mScheduler->registerDisplay(kDisplayId1,
+                                std::make_shared<RefreshRateSelector>(kDisplay1Modes,
+                                                                      kDisplay1Mode60->getId()));
 
-    std::vector<RefreshRateConfigs::LayerRequirement> layers =
-            std::vector<RefreshRateConfigs::LayerRequirement>({{.weight = 1.f}, {.weight = 1.f}});
+    std::vector<RefreshRateSelector::LayerRequirement> layers =
+            std::vector<RefreshRateSelector::LayerRequirement>({{.weight = 1.f}, {.weight = 1.f}});
     mScheduler->setContentRequirements(layers);
     GlobalSignals globalSignals = {.idle = true};
     mScheduler->setTouchStateAndIdleTimerPolicy(globalSignals);
 
-    std::vector<DisplayModeConfig> displayModeConfigs = mScheduler->getBestDisplayModeConfigs();
-    ASSERT_EQ(1ul, displayModeConfigs.size());
-    EXPECT_EQ(displayModeConfigs.front().displayModePtr, kMode60_1);
-    EXPECT_EQ(displayModeConfigs.front().signals, globalSignals);
+    using DisplayModeChoice = TestableScheduler::DisplayModeChoice;
+
+    auto modeChoices = mScheduler->chooseDisplayModes();
+    ASSERT_EQ(1u, modeChoices.size());
+
+    auto choice = modeChoices.get(kDisplayId1);
+    ASSERT_TRUE(choice);
+    EXPECT_EQ(choice->get(), DisplayModeChoice({60_Hz, kDisplay1Mode60}, globalSignals));
 
     globalSignals = {.idle = false};
     mScheduler->setTouchStateAndIdleTimerPolicy(globalSignals);
-    displayModeConfigs = mScheduler->getBestDisplayModeConfigs();
-    ASSERT_EQ(1ul, displayModeConfigs.size());
-    EXPECT_EQ(displayModeConfigs.front().displayModePtr, kMode120_1);
-    EXPECT_EQ(displayModeConfigs.front().signals, globalSignals);
+
+    modeChoices = mScheduler->chooseDisplayModes();
+    ASSERT_EQ(1u, modeChoices.size());
+
+    choice = modeChoices.get(kDisplayId1);
+    ASSERT_TRUE(choice);
+    EXPECT_EQ(choice->get(), DisplayModeChoice({120_Hz, kDisplay1Mode120}, globalSignals));
 
     globalSignals = {.touch = true};
     mScheduler->replaceTouchTimer(10);
     mScheduler->setTouchStateAndIdleTimerPolicy(globalSignals);
-    displayModeConfigs = mScheduler->getBestDisplayModeConfigs();
-    ASSERT_EQ(1ul, displayModeConfigs.size());
-    EXPECT_EQ(displayModeConfigs.front().displayModePtr, kMode120_1);
-    EXPECT_EQ(displayModeConfigs.front().signals, globalSignals);
 
-    mScheduler->unregisterDisplay(display->getPhysicalId());
-    EXPECT_TRUE(mScheduler->mutableDisplays().empty());
+    modeChoices = mScheduler->chooseDisplayModes();
+    ASSERT_EQ(1u, modeChoices.size());
+
+    choice = modeChoices.get(kDisplayId1);
+    ASSERT_TRUE(choice);
+    EXPECT_EQ(choice->get(), DisplayModeChoice({120_Hz, kDisplay1Mode120}, globalSignals));
+
+    mScheduler->unregisterDisplay(kDisplayId1);
+    EXPECT_FALSE(mScheduler->hasRefreshRateSelectors());
 }
 
-TEST_F(SchedulerTest, getBestDisplayModes_multipleDisplays) {
-    auto display1 = mFakeDisplayInjector.injectDefaultInternalDisplay(
-            [&](FakeDisplayDeviceInjector& injector) {
-                injector.setDisplayModes(makeModes(kMode60_1, kMode120_1), kMode60_1->getId());
-            },
-            mFlinger);
-    auto display2 = mFakeDisplayInjector.injectDefaultInternalDisplay(
-            [&](FakeDisplayDeviceInjector& injector) {
-                injector.setDisplayModes(makeModes(kMode60_2, kMode120_2), kMode60_2->getId());
-            },
-            mFlinger, /* port */ 253u);
-    mScheduler->registerDisplay(display1);
-    mScheduler->registerDisplay(display2);
+TEST_F(SchedulerTest, chooseDisplayModesMultipleDisplays) {
+    mScheduler->registerDisplay(kDisplayId1,
+                                std::make_shared<RefreshRateSelector>(kDisplay1Modes,
+                                                                      kDisplay1Mode60->getId()));
+    mScheduler->registerDisplay(kDisplayId2,
+                                std::make_shared<RefreshRateSelector>(kDisplay2Modes,
+                                                                      kDisplay2Mode60->getId()));
 
-    const std::vector<sp<DisplayDevice>>& expectedDisplays = {display1, display2};
-    std::vector<RefreshRateConfigs::LayerRequirement> layers = {{.weight = 1.f}, {.weight = 1.f}};
-    GlobalSignals globalSignals = {.idle = true};
-    std::vector<DisplayModeConfig> expectedConfigs = {DisplayModeConfig{globalSignals, kMode60_1},
-                                                      DisplayModeConfig{globalSignals, kMode60_2}};
+    using DisplayModeChoice = TestableScheduler::DisplayModeChoice;
+    TestableScheduler::DisplayModeChoiceMap expectedChoices;
 
-    mScheduler->setContentRequirements(layers);
-    mScheduler->setTouchStateAndIdleTimerPolicy(globalSignals);
-    std::vector<DisplayModeConfig> displayModeConfigs = mScheduler->getBestDisplayModeConfigs();
-    ASSERT_EQ(displayModeConfigs.size(), expectedConfigs.size());
-    for (size_t i = 0; i < expectedConfigs.size(); ++i) {
-        EXPECT_EQ(expectedConfigs.at(i).displayModePtr, displayModeConfigs.at(i).displayModePtr)
-                << "Expected fps " << expectedConfigs.at(i).displayModePtr->getFps().getIntValue()
-                << " Actual fps "
-                << displayModeConfigs.at(i).displayModePtr->getFps().getIntValue();
-        EXPECT_EQ(globalSignals, displayModeConfigs.at(i).signals);
+    {
+        const GlobalSignals globalSignals = {.idle = true};
+        expectedChoices =
+                ftl::init::map<const PhysicalDisplayId&,
+                               DisplayModeChoice>(kDisplayId1,
+                                                  FrameRateMode{60_Hz, kDisplay1Mode60},
+                                                  globalSignals)(kDisplayId2,
+                                                                 FrameRateMode{60_Hz,
+                                                                               kDisplay2Mode60},
+                                                                 globalSignals);
+
+        std::vector<RefreshRateSelector::LayerRequirement> layers = {{.weight = 1.f},
+                                                                     {.weight = 1.f}};
+        mScheduler->setContentRequirements(layers);
+        mScheduler->setTouchStateAndIdleTimerPolicy(globalSignals);
+
+        const auto actualChoices = mScheduler->chooseDisplayModes();
+        EXPECT_EQ(expectedChoices, actualChoices);
     }
+    {
+        const GlobalSignals globalSignals = {.idle = false};
+        expectedChoices =
+                ftl::init::map<const PhysicalDisplayId&,
+                               DisplayModeChoice>(kDisplayId1,
+                                                  FrameRateMode{120_Hz, kDisplay1Mode120},
+                                                  globalSignals)(kDisplayId2,
+                                                                 FrameRateMode{120_Hz,
+                                                                               kDisplay2Mode120},
+                                                                 globalSignals);
 
-    expectedConfigs = std::vector<DisplayModeConfig>{DisplayModeConfig{globalSignals, kMode120_1},
-                                                     DisplayModeConfig{globalSignals, kMode120_2}};
+        mScheduler->setTouchStateAndIdleTimerPolicy(globalSignals);
 
-    globalSignals = {.idle = false};
-    mScheduler->setTouchStateAndIdleTimerPolicy(globalSignals);
-    displayModeConfigs = mScheduler->getBestDisplayModeConfigs();
-    ASSERT_EQ(expectedConfigs.size(), displayModeConfigs.size());
-    for (size_t i = 0; i < expectedConfigs.size(); ++i) {
-        EXPECT_EQ(expectedConfigs.at(i).displayModePtr, displayModeConfigs.at(i).displayModePtr)
-                << "Expected fps " << expectedConfigs.at(i).displayModePtr->getFps().getIntValue()
-                << " Actual fps "
-                << displayModeConfigs.at(i).displayModePtr->getFps().getIntValue();
-        EXPECT_EQ(globalSignals, displayModeConfigs.at(i).signals);
+        const auto actualChoices = mScheduler->chooseDisplayModes();
+        EXPECT_EQ(expectedChoices, actualChoices);
     }
+    {
+        const GlobalSignals globalSignals = {.touch = true};
+        mScheduler->replaceTouchTimer(10);
+        mScheduler->setTouchStateAndIdleTimerPolicy(globalSignals);
 
-    globalSignals = {.touch = true};
-    mScheduler->replaceTouchTimer(10);
-    mScheduler->setTouchStateAndIdleTimerPolicy(globalSignals);
-    displayModeConfigs = mScheduler->getBestDisplayModeConfigs();
-    ASSERT_EQ(expectedConfigs.size(), displayModeConfigs.size());
-    for (size_t i = 0; i < expectedConfigs.size(); ++i) {
-        EXPECT_EQ(expectedConfigs.at(i).displayModePtr, displayModeConfigs.at(i).displayModePtr)
-                << "Expected fps " << expectedConfigs.at(i).displayModePtr->getFps().getIntValue()
-                << " Actual fps "
-                << displayModeConfigs.at(i).displayModePtr->getFps().getIntValue();
-        EXPECT_EQ(globalSignals, displayModeConfigs.at(i).signals);
+        expectedChoices =
+                ftl::init::map<const PhysicalDisplayId&,
+                               DisplayModeChoice>(kDisplayId1,
+                                                  FrameRateMode{120_Hz, kDisplay1Mode120},
+                                                  globalSignals)(kDisplayId2,
+                                                                 FrameRateMode{120_Hz,
+                                                                               kDisplay2Mode120},
+                                                                 globalSignals);
+
+        const auto actualChoices = mScheduler->chooseDisplayModes();
+        EXPECT_EQ(expectedChoices, actualChoices);
+    }
+    {
+        // This display does not support 120 Hz, so we should choose 60 Hz despite the touch signal.
+        mScheduler
+                ->registerDisplay(kDisplayId3,
+                                  std::make_shared<RefreshRateSelector>(kDisplay3Modes,
+                                                                        kDisplay3Mode60->getId()));
+
+        const GlobalSignals globalSignals = {.touch = true};
+        mScheduler->replaceTouchTimer(10);
+        mScheduler->setTouchStateAndIdleTimerPolicy(globalSignals);
+
+        expectedChoices = ftl::init::map<
+                const PhysicalDisplayId&,
+                DisplayModeChoice>(kDisplayId1, FrameRateMode{60_Hz, kDisplay1Mode60},
+                                   globalSignals)(kDisplayId2,
+                                                  FrameRateMode{60_Hz, kDisplay2Mode60},
+                                                  globalSignals)(kDisplayId3,
+                                                                 FrameRateMode{60_Hz,
+                                                                               kDisplay3Mode60},
+                                                                 globalSignals);
+
+        const auto actualChoices = mScheduler->chooseDisplayModes();
+        EXPECT_EQ(expectedChoices, actualChoices);
     }
 }
 
diff --git a/services/surfaceflinger/tests/unittests/SetFrameRateTest.cpp b/services/surfaceflinger/tests/unittests/SetFrameRateTest.cpp
index dfcfd91..6adcd52 100644
--- a/services/surfaceflinger/tests/unittests/SetFrameRateTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SetFrameRateTest.cpp
@@ -383,8 +383,8 @@
     history.record(parent.get(), 0, 0, LayerHistory::LayerUpdateType::Buffer);
     history.record(child.get(), 0, 0, LayerHistory::LayerUpdateType::Buffer);
 
-    const auto configs = mFlinger.mutableScheduler().refreshRateConfigs();
-    const auto summary = history.summarize(*configs, 0);
+    const auto selectorPtr = mFlinger.mutableScheduler().refreshRateSelector();
+    const auto summary = history.summarize(*selectorPtr, 0);
 
     ASSERT_EQ(2u, summary.size());
     EXPECT_EQ(FRAME_RATE_VOTE1.rate, summary[0].desiredRefreshRate);
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp
index 6b7e353..074bf8c 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp
@@ -14,6 +14,7 @@
  * limitations under the License.
  */
 
+#include "mock/MockDisplayModeSpecs.h"
 #include "mock/MockEventThread.h"
 #undef LOG_TAG
 #define LOG_TAG "LibSurfaceFlingerUnittests"
@@ -42,15 +43,15 @@
         PrimaryDisplayVariant::setupHwcGetActiveConfigCallExpectations(this);
 
         DisplayModes modes = makeModes(kMode60, kMode90, kMode120, kMode90_4K);
-        auto configs = std::make_shared<scheduler::RefreshRateConfigs>(modes, kModeId60);
+        auto selectorPtr = std::make_shared<scheduler::RefreshRateSelector>(modes, kModeId60);
 
-        setupScheduler(configs);
+        setupScheduler(selectorPtr);
 
         mFlinger.onComposerHalHotplug(PrimaryDisplayVariant::HWC_DISPLAY_ID, Connection::CONNECTED);
         mFlinger.configureAndCommit();
 
         mDisplay = PrimaryDisplayVariant::makeFakeExistingDisplayInjector(this)
-                           .setDisplayModes(std::move(modes), kModeId60, std::move(configs))
+                           .setDisplayModes(std::move(modes), kModeId60, std::move(selectorPtr))
                            .inject();
 
         // isVsyncPeriodSwitchSupported should return true, otherwise the SF's HWC proxy
@@ -60,7 +61,7 @@
     }
 
 protected:
-    void setupScheduler(std::shared_ptr<scheduler::RefreshRateConfigs>);
+    void setupScheduler(std::shared_ptr<scheduler::RefreshRateSelector>);
 
     sp<DisplayDevice> mDisplay;
     mock::EventThread* mAppEventThread;
@@ -80,7 +81,7 @@
 };
 
 void DisplayModeSwitchingTest::setupScheduler(
-        std::shared_ptr<scheduler::RefreshRateConfigs> configs) {
+        std::shared_ptr<scheduler::RefreshRateSelector> selectorPtr) {
     auto eventThread = std::make_unique<mock::EventThread>();
     mAppEventThread = eventThread.get();
     auto sfEventThread = std::make_unique<mock::EventThread>();
@@ -108,23 +109,24 @@
     mFlinger.setupScheduler(std::move(vsyncController), std::move(vsyncTracker),
                             std::move(eventThread), std::move(sfEventThread),
                             TestableSurfaceFlinger::SchedulerCallbackImpl::kNoOp,
-                            std::move(configs));
+                            std::move(selectorPtr));
 }
 
 TEST_F(DisplayModeSwitchingTest, changeRefreshRate_OnActiveDisplay_WithRefreshRequired) {
     ftl::FakeGuard guard(kMainThreadContext);
 
     ASSERT_FALSE(mDisplay->getDesiredActiveMode().has_value());
-    ASSERT_EQ(mDisplay->getActiveMode().getId(), kModeId60);
+    ASSERT_EQ(mDisplay->getActiveMode().modePtr->getId(), kModeId60);
 
     mFlinger.onActiveDisplayChanged(mDisplay);
 
-    mFlinger.setDesiredDisplayModeSpecs(mDisplay->getDisplayToken().promote(), kModeId90.value(),
-                                        false, 0.f, 120.f, 0.f, 120.f);
+    mFlinger.setDesiredDisplayModeSpecs(mDisplay->getDisplayToken().promote(),
+                                        mock::createDisplayModeSpecs(kModeId90.value(), false, 0,
+                                                                     120));
 
     ASSERT_TRUE(mDisplay->getDesiredActiveMode().has_value());
-    ASSERT_EQ(mDisplay->getDesiredActiveMode()->mode->getId(), kModeId90);
-    ASSERT_EQ(mDisplay->getActiveMode().getId(), kModeId60);
+    ASSERT_EQ(mDisplay->getDesiredActiveMode()->modeOpt->modePtr->getId(), kModeId90);
+    ASSERT_EQ(mDisplay->getActiveMode().modePtr->getId(), kModeId60);
 
     // Verify that next commit will call setActiveConfigWithConstraints in HWC
     const VsyncPeriodChangeTimeline timeline{.refreshRequired = true};
@@ -137,17 +139,18 @@
 
     Mock::VerifyAndClearExpectations(mComposer);
     ASSERT_TRUE(mDisplay->getDesiredActiveMode().has_value());
-    ASSERT_EQ(mDisplay->getActiveMode().getId(), kModeId60);
+    ASSERT_EQ(mDisplay->getActiveMode().modePtr->getId(), kModeId60);
 
     // Verify that the next commit will complete the mode change and send
     // a onModeChanged event to the framework.
 
-    EXPECT_CALL(*mAppEventThread, onModeChanged(kMode90));
+    EXPECT_CALL(*mAppEventThread,
+                onModeChanged(scheduler::FrameRateMode{90_Hz, ftl::as_non_null(kMode90)}));
     mFlinger.commit();
     Mock::VerifyAndClearExpectations(mAppEventThread);
 
     ASSERT_FALSE(mDisplay->getDesiredActiveMode().has_value());
-    ASSERT_EQ(mDisplay->getActiveMode().getId(), kModeId90);
+    ASSERT_EQ(mDisplay->getActiveMode().modePtr->getId(), kModeId90);
 }
 
 TEST_F(DisplayModeSwitchingTest, changeRefreshRate_OnActiveDisplay_WithoutRefreshRequired) {
@@ -157,12 +160,13 @@
 
     mFlinger.onActiveDisplayChanged(mDisplay);
 
-    mFlinger.setDesiredDisplayModeSpecs(mDisplay->getDisplayToken().promote(), kModeId90.value(),
-                                        true, 0.f, 120.f, 0.f, 120.f);
+    mFlinger.setDesiredDisplayModeSpecs(mDisplay->getDisplayToken().promote(),
+                                        mock::createDisplayModeSpecs(kModeId90.value(), true, 0,
+                                                                     120));
 
     ASSERT_TRUE(mDisplay->getDesiredActiveMode().has_value());
-    ASSERT_EQ(mDisplay->getDesiredActiveMode()->mode->getId(), kModeId90);
-    ASSERT_EQ(mDisplay->getActiveMode().getId(), kModeId60);
+    ASSERT_EQ(mDisplay->getDesiredActiveMode()->modeOpt->modePtr->getId(), kModeId90);
+    ASSERT_EQ(mDisplay->getActiveMode().modePtr->getId(), kModeId60);
 
     // Verify that next commit will call setActiveConfigWithConstraints in HWC
     // and complete the mode change.
@@ -172,12 +176,13 @@
                                                hal::HWConfigId(kModeId90.value()), _, _))
             .WillOnce(DoAll(SetArgPointee<3>(timeline), Return(Error::NONE)));
 
-    EXPECT_CALL(*mAppEventThread, onModeChanged(kMode90));
+    EXPECT_CALL(*mAppEventThread,
+                onModeChanged(scheduler::FrameRateMode{90_Hz, ftl::as_non_null(kMode90)}));
 
     mFlinger.commit();
 
     ASSERT_FALSE(mDisplay->getDesiredActiveMode().has_value());
-    ASSERT_EQ(mDisplay->getActiveMode().getId(), kModeId90);
+    ASSERT_EQ(mDisplay->getActiveMode().modePtr->getId(), kModeId90);
 }
 
 TEST_F(DisplayModeSwitchingTest, twoConsecutiveSetDesiredDisplayModeSpecs) {
@@ -187,12 +192,13 @@
     // is still being processed the later call will be respected.
 
     ASSERT_FALSE(mDisplay->getDesiredActiveMode().has_value());
-    ASSERT_EQ(mDisplay->getActiveMode().getId(), kModeId60);
+    ASSERT_EQ(mDisplay->getActiveMode().modePtr->getId(), kModeId60);
 
     mFlinger.onActiveDisplayChanged(mDisplay);
 
-    mFlinger.setDesiredDisplayModeSpecs(mDisplay->getDisplayToken().promote(), kModeId90.value(),
-                                        false, 0.f, 120.f, 0.f, 120.f);
+    mFlinger.setDesiredDisplayModeSpecs(mDisplay->getDisplayToken().promote(),
+                                        mock::createDisplayModeSpecs(kModeId90.value(), false, 0,
+                                                                     120));
 
     const VsyncPeriodChangeTimeline timeline{.refreshRequired = true};
     EXPECT_CALL(*mComposer,
@@ -202,11 +208,12 @@
 
     mFlinger.commit();
 
-    mFlinger.setDesiredDisplayModeSpecs(mDisplay->getDisplayToken().promote(), kModeId120.value(),
-                                        false, 0.f, 180.f, 0.f, 180.f);
+    mFlinger.setDesiredDisplayModeSpecs(mDisplay->getDisplayToken().promote(),
+                                        mock::createDisplayModeSpecs(kModeId120.value(), false, 0,
+                                                                     180));
 
     ASSERT_TRUE(mDisplay->getDesiredActiveMode().has_value());
-    ASSERT_EQ(mDisplay->getDesiredActiveMode()->mode->getId(), kModeId120);
+    ASSERT_EQ(mDisplay->getDesiredActiveMode()->modeOpt->modePtr->getId(), kModeId120);
 
     EXPECT_CALL(*mComposer,
                 setActiveConfigWithConstraints(PrimaryDisplayVariant::HWC_DISPLAY_ID,
@@ -216,28 +223,29 @@
     mFlinger.commit();
 
     ASSERT_TRUE(mDisplay->getDesiredActiveMode().has_value());
-    ASSERT_EQ(mDisplay->getDesiredActiveMode()->mode->getId(), kModeId120);
+    ASSERT_EQ(mDisplay->getDesiredActiveMode()->modeOpt->modePtr->getId(), kModeId120);
 
     mFlinger.commit();
 
     ASSERT_FALSE(mDisplay->getDesiredActiveMode().has_value());
-    ASSERT_EQ(mDisplay->getActiveMode().getId(), kModeId120);
+    ASSERT_EQ(mDisplay->getActiveMode().modePtr->getId(), kModeId120);
 }
 
 TEST_F(DisplayModeSwitchingTest, changeResolution_OnActiveDisplay_WithoutRefreshRequired) {
     ftl::FakeGuard guard(kMainThreadContext);
 
     ASSERT_FALSE(mDisplay->getDesiredActiveMode().has_value());
-    ASSERT_EQ(mDisplay->getActiveMode().getId(), kModeId60);
+    ASSERT_EQ(mDisplay->getActiveMode().modePtr->getId(), kModeId60);
 
     mFlinger.onActiveDisplayChanged(mDisplay);
 
-    mFlinger.setDesiredDisplayModeSpecs(mDisplay->getDisplayToken().promote(), kModeId90_4K.value(),
-                                        false, 0.f, 120.f, 0.f, 120.f);
+    mFlinger.setDesiredDisplayModeSpecs(mDisplay->getDisplayToken().promote(),
+                                        mock::createDisplayModeSpecs(kModeId90_4K.value(), false, 0,
+                                                                     120));
 
     ASSERT_TRUE(mDisplay->getDesiredActiveMode().has_value());
-    ASSERT_EQ(mDisplay->getDesiredActiveMode()->mode->getId(), kModeId90_4K);
-    ASSERT_EQ(mDisplay->getActiveMode().getId(), kModeId60);
+    ASSERT_EQ(mDisplay->getDesiredActiveMode()->modeOpt->modePtr->getId(), kModeId90_4K);
+    ASSERT_EQ(mDisplay->getActiveMode().modePtr->getId(), kModeId60);
 
     // Verify that next commit will call setActiveConfigWithConstraints in HWC
     // and complete the mode change.
@@ -272,7 +280,7 @@
     mDisplay = mFlinger.getDisplay(displayToken);
 
     ASSERT_FALSE(mDisplay->getDesiredActiveMode().has_value());
-    ASSERT_EQ(mDisplay->getActiveMode().getId(), kModeId90_4K);
+    ASSERT_EQ(mDisplay->getActiveMode().modePtr->getId(), kModeId90_4K);
 }
 
 } // namespace
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_ExcludeDolbyVisionTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_ExcludeDolbyVisionTest.cpp
new file mode 100644
index 0000000..11e734a
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_ExcludeDolbyVisionTest.cpp
@@ -0,0 +1,109 @@
+/*
+ * Copyright 2020 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 {
+namespace {
+
+using FakeDisplayDeviceInjector = TestableSurfaceFlinger::FakeDisplayDeviceInjector;
+
+class ExcludeDolbyVisionTest : public DisplayTransactionTest {
+public:
+    void injectDisplayModes(std::vector<DisplayModePtr> displayModePtrs) {
+        DisplayModes modes;
+        for (DisplayModePtr displayMode : displayModePtrs) {
+            modes.try_emplace(displayMode->getId(), displayMode);
+        }
+
+        mDisplay = PrimaryDisplayVariant::makeFakeExistingDisplayInjector(this)
+                           .setDisplayModes(std::move(modes), displayModePtrs[0]->getId())
+                           .inject();
+        mDisplay->overrideHdrTypes(types);
+    }
+
+protected:
+    sp<DisplayDevice> mDisplay;
+
+    static constexpr DisplayModeId modeId1080p60{0};
+    static constexpr DisplayModeId modeId4k30{1};
+    static constexpr DisplayModeId modeId4k60{2};
+
+    static inline const DisplayModePtr mode1080p60 =
+            createDisplayMode(modeId1080p60, 60_Hz, 0, ui::Size(1920, 1080));
+    static inline const DisplayModePtr mode4k30 =
+            createDisplayMode(modeId4k30, 30_Hz, 1, ui::Size(3840, 2160));
+    static inline const DisplayModePtr mode4k30NonStandard =
+            createDisplayMode(modeId4k30, 30.1_Hz, 1, ui::Size(3840, 2160));
+    static inline const DisplayModePtr mode4k60 =
+            createDisplayMode(modeId4k60, 60_Hz, 2, ui::Size(3840, 2160));
+
+    const std::vector<ui::Hdr> types = {ui::Hdr::DOLBY_VISION, ui::Hdr::DOLBY_VISION_4K30,
+                                        ui::Hdr::HDR10_PLUS};
+};
+
+TEST_F(ExcludeDolbyVisionTest, excludesDolbyVisionOnModesHigherThan4k30) {
+    injectDisplayModes({mode4k60});
+    ui::DynamicDisplayInfo info;
+    mFlinger.getDynamicDisplayInfo(mDisplay->getDisplayToken().promote(), &info);
+
+    std::vector<ui::DisplayMode> displayModes = info.supportedDisplayModes;
+
+    ASSERT_EQ(1, displayModes.size());
+    ASSERT_TRUE(std::any_of(displayModes[0].supportedHdrTypes.begin(),
+                            displayModes[0].supportedHdrTypes.end(),
+                            [](ui::Hdr type) { return type == ui::Hdr::HDR10_PLUS; }));
+    ASSERT_TRUE(displayModes[0].supportedHdrTypes.size() == 1);
+}
+
+TEST_F(ExcludeDolbyVisionTest, includesDolbyVisionOnModesLowerThanOrEqualTo4k30) {
+    injectDisplayModes({mode1080p60, mode4k30, mode4k30NonStandard});
+    ui::DynamicDisplayInfo info;
+    mFlinger.getDynamicDisplayInfo(mDisplay->getDisplayToken().promote(), &info);
+
+    std::vector<ui::DisplayMode> displayModes = info.supportedDisplayModes;
+
+    ASSERT_EQ(2, displayModes.size());
+    for (size_t i = 0; i < displayModes.size(); i++) {
+        ASSERT_TRUE(std::any_of(displayModes[i].supportedHdrTypes.begin(),
+                                displayModes[i].supportedHdrTypes.end(),
+                                [](ui::Hdr type) { return type == ui::Hdr::HDR10_PLUS; }));
+        ASSERT_TRUE(std::any_of(displayModes[i].supportedHdrTypes.begin(),
+                                displayModes[i].supportedHdrTypes.end(),
+                                [](ui::Hdr type) { return type == ui::Hdr::DOLBY_VISION; }));
+        ASSERT_TRUE(displayModes[i].supportedHdrTypes.size() == 2);
+    }
+}
+
+TEST_F(ExcludeDolbyVisionTest, 4k30IsNotReportedAsAValidHdrType) {
+    injectDisplayModes({mode4k60});
+    ui::DynamicDisplayInfo info;
+    mFlinger.getDynamicDisplayInfo(mDisplay->getDisplayToken().promote(), &info);
+
+    std::vector<ui::Hdr> displayHdrTypes = info.hdrCapabilities.getSupportedHdrTypes();
+
+    ASSERT_EQ(2, displayHdrTypes.size());
+    ASSERT_TRUE(std::any_of(displayHdrTypes.begin(), displayHdrTypes.end(),
+                            [](ui::Hdr type) { return type == ui::Hdr::HDR10_PLUS; }));
+    ASSERT_TRUE(std::any_of(displayHdrTypes.begin(), displayHdrTypes.end(),
+                            [](ui::Hdr type) { return type == ui::Hdr::DOLBY_VISION; }));
+}
+
+} // namespace
+} // namespace android
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_PowerHintTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_PowerHintTest.cpp
index bc66961..622717f 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_PowerHintTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_PowerHintTest.cpp
@@ -97,7 +97,6 @@
                     .setNativeWindow(mNativeWindow)
                     .setPowerMode(hal::PowerMode::ON)
                     .inject();
-    mFlinger.mutableActiveDisplayId() = mDisplay->getPhysicalId();
 }
 
 void SurfaceFlingerPowerHintTest::setupScheduler() {
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetPowerModeInternalTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetPowerModeInternalTest.cpp
index 25857ec..0fb8e2b 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetPowerModeInternalTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetPowerModeInternalTest.cpp
@@ -259,12 +259,6 @@
         auto injector = Display::makeFakeExistingDisplayInjector(test);
         const auto display = injector.inject();
         display->setPowerMode(mode);
-        if (injector.physicalDisplay()
-                    .transform(&display::PhysicalDisplay::isInternal)
-                    .value_or(false)) {
-            test->mFlinger.mutableActiveDisplayId() = display->getPhysicalId();
-        }
-
         return display;
     }
 
@@ -490,5 +484,37 @@
     transitionDisplayCommon<ExternalDisplayPowerCase<TransitionOnToUnknownVariant>>();
 }
 
+TEST_F(SetPowerModeInternalTest, designatesLeaderDisplay) {
+    using Case = SimplePrimaryDisplayCase;
+
+    // --------------------------------------------------------------------
+    // Preconditions
+
+    // Inject a primary display with uninitialized power mode.
+    constexpr bool kInitPowerMode = false;
+    Case::Display::injectHwcDisplay<kInitPowerMode>(this);
+    auto injector = Case::Display::makeFakeExistingDisplayInjector(this);
+    injector.setPowerMode(std::nullopt);
+    const auto display = injector.inject();
+
+    // --------------------------------------------------------------------
+    // Invocation
+
+    // FakeDisplayDeviceInjector registers the display with Scheduler, so it has already been
+    // designated as the leader. Set an arbitrary leader to verify that `setPowerModeInternal`
+    // designates a leader regardless of any preceding `Scheduler::registerDisplay` call(s).
+    constexpr PhysicalDisplayId kPlaceholderId = PhysicalDisplayId::fromPort(42);
+    ASSERT_NE(display->getPhysicalId(), kPlaceholderId);
+    mFlinger.scheduler()->setLeaderDisplay(kPlaceholderId);
+
+    mFlinger.setPowerModeInternal(display, PowerMode::ON);
+
+    // --------------------------------------------------------------------
+    // Postconditions
+
+    // The primary display should be designated as the leader.
+    EXPECT_EQ(mFlinger.scheduler()->leaderDisplayId(), display->getPhysicalId());
+}
+
 } // namespace
 } // namespace android
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetupNewDisplayDeviceInternalTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetupNewDisplayDeviceInternalTest.cpp
index 0384568..c0796df 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetupNewDisplayDeviceInternalTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetupNewDisplayDeviceInternalTest.cpp
@@ -288,7 +288,7 @@
 
     if constexpr (Case::Display::CONNECTION_TYPE::value) {
         ftl::FakeGuard guard(kMainThreadContext);
-        EXPECT_EQ(Case::Display::HWC_ACTIVE_CONFIG_ID, device->getActiveMode().getHwcId());
+        EXPECT_EQ(Case::Display::HWC_ACTIVE_CONFIG_ID, device->getActiveMode().modePtr->getHwcId());
     }
 }
 
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_UpdateLayerMetadataSnapshotTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_UpdateLayerMetadataSnapshotTest.cpp
index 0cf3bdf..fed6a1a 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_UpdateLayerMetadataSnapshotTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_UpdateLayerMetadataSnapshotTest.cpp
@@ -44,9 +44,11 @@
                                 std::move(eventThread), std::move(sfEventThread));
     }
 
-    sp<Layer> createLayer(const char* name, LayerMetadata layerMetadata) {
-        return sp<Layer>::make(
-                LayerCreationArgs{mFlinger.flinger(), nullptr, name, 0, layerMetadata});
+    sp<Layer> createLayer(const char* name, LayerMetadata& inOutlayerMetadata) {
+        LayerCreationArgs args =
+                LayerCreationArgs{mFlinger.flinger(), nullptr, name, 0, inOutlayerMetadata};
+        inOutlayerMetadata = args.metadata;
+        return sp<Layer>::make(args);
     }
 
     TestableSurfaceFlinger mFlinger;
@@ -90,7 +92,7 @@
 
     mFlinger.updateLayerMetadataSnapshot();
 
-    ASSERT_EQ(layer->getLayerSnapshot()->layerMetadata, layerMetadata);
+    EXPECT_EQ(layer->getLayerSnapshot()->layerMetadata, layerMetadata);
 }
 
 // Test that snapshot layer metadata is set by merging the child's metadata on top of its
@@ -109,10 +111,10 @@
 
     mFlinger.updateLayerMetadataSnapshot();
 
-    ASSERT_EQ(layerA->getLayerSnapshot()->layerMetadata, layerAMetadata);
+    EXPECT_EQ(layerA->getLayerSnapshot()->layerMetadata, layerAMetadata);
     auto expectedChildMetadata =
             LayerMetadataBuilder(layerAMetadata).setInt32(METADATA_TASK_ID, 3).build();
-    ASSERT_EQ(layerB->getLayerSnapshot()->layerMetadata, expectedChildMetadata);
+    EXPECT_EQ(layerB->getLayerSnapshot()->layerMetadata, expectedChildMetadata);
 }
 
 // Test that snapshot relative layer metadata is set to the parent's layer metadata merged on top of
@@ -129,8 +131,8 @@
 
     mFlinger.updateLayerMetadataSnapshot();
 
-    ASSERT_EQ(layerA->getLayerSnapshot()->relativeLayerMetadata, LayerMetadata{});
-    ASSERT_EQ(layerB->getLayerSnapshot()->relativeLayerMetadata, layerAMetadata);
+    EXPECT_EQ(layerA->getLayerSnapshot()->relativeLayerMetadata, LayerMetadata{});
+    EXPECT_EQ(layerB->getLayerSnapshot()->relativeLayerMetadata, layerAMetadata);
 }
 
 // Test that snapshot relative layer metadata is set correctly when a layer is interleaved within
@@ -154,7 +156,8 @@
                                   .build();
     auto layerB = createLayer("layer-b", layerBMetadata);
     auto layerBHandle = layerB->getHandle();
-    auto layerC = createLayer("layer-c", {});
+    LayerMetadata layerCMetadata;
+    auto layerC = createLayer("layer-c", layerCMetadata);
     auto layerDMetadata = LayerMetadataBuilder().setInt32(METADATA_TASK_ID, 4).build();
     auto layerD = createLayer("layer-d", layerDMetadata);
     auto layerDHandle = layerD->getHandle();
@@ -168,14 +171,18 @@
 
     mFlinger.updateLayerMetadataSnapshot();
 
-    auto expectedLayerDRelativeMetadata = LayerMetadataBuilder()
-                                                  // From layer A, parent of relative parent
-                                                  .setInt32(METADATA_OWNER_UID, 1)
-                                                  // From layer B, relative parent
-                                                  .setInt32(METADATA_TASK_ID, 2)
-                                                  .setInt32(METADATA_OWNER_PID, 3)
-                                                  .build();
-    ASSERT_EQ(layerD->getLayerSnapshot()->relativeLayerMetadata, expectedLayerDRelativeMetadata);
+    auto expectedLayerDRelativeMetadata =
+            LayerMetadataBuilder()
+                    // From layer A, parent of relative parent
+                    .setInt32(METADATA_OWNER_UID, 1)
+                    // From layer B, relative parent
+                    .setInt32(METADATA_TASK_ID, 2)
+                    .setInt32(METADATA_OWNER_PID, 3)
+                    // added by layer creation args
+                    .setInt32(gui::METADATA_CALLING_UID,
+                              layerDMetadata.getInt32(gui::METADATA_CALLING_UID, 0))
+                    .build();
+    EXPECT_EQ(layerD->getLayerSnapshot()->relativeLayerMetadata, expectedLayerDRelativeMetadata);
     auto expectedLayerCRelativeMetadata =
             LayerMetadataBuilder()
                     // From layer A, parent of relative parent
@@ -184,8 +191,11 @@
                     .setInt32(METADATA_OWNER_PID, 3)
                     // From layer D, relative parent
                     .setInt32(METADATA_TASK_ID, 4)
+                    // added by layer creation args
+                    .setInt32(gui::METADATA_CALLING_UID,
+                              layerDMetadata.getInt32(gui::METADATA_CALLING_UID, 0))
                     .build();
-    ASSERT_EQ(layerC->getLayerSnapshot()->relativeLayerMetadata, expectedLayerCRelativeMetadata);
+    EXPECT_EQ(layerC->getLayerSnapshot()->relativeLayerMetadata, expectedLayerCRelativeMetadata);
 }
 
 TEST_F(SurfaceFlingerUpdateLayerMetadataSnapshotTest,
@@ -193,8 +203,10 @@
     auto layerAMetadata = LayerMetadataBuilder().setInt32(METADATA_OWNER_UID, 1).build();
     auto layerA = createLayer("layer-a", layerAMetadata);
     auto layerAHandle = layerA->getHandle();
-    auto layerB = createLayer("layer-b", {});
-    auto layerC = createLayer("layer-c", {});
+    LayerMetadata layerBMetadata;
+    auto layerB = createLayer("layer-b", layerBMetadata);
+    LayerMetadata layerCMetadata;
+    auto layerC = createLayer("layer-c", layerCMetadata);
     layerB->setRelativeLayer(layerAHandle, 1);
     layerC->setRelativeLayer(layerAHandle, 2);
     layerA->commitChildList();
@@ -204,9 +216,9 @@
 
     mFlinger.updateLayerMetadataSnapshot();
 
-    ASSERT_EQ(layerA->getLayerSnapshot()->relativeLayerMetadata, LayerMetadata{});
-    ASSERT_EQ(layerB->getLayerSnapshot()->relativeLayerMetadata, layerAMetadata);
-    ASSERT_EQ(layerC->getLayerSnapshot()->relativeLayerMetadata, layerAMetadata);
+    EXPECT_EQ(layerA->getLayerSnapshot()->relativeLayerMetadata, LayerMetadata{});
+    EXPECT_EQ(layerB->getLayerSnapshot()->relativeLayerMetadata, layerAMetadata);
+    EXPECT_EQ(layerC->getLayerSnapshot()->relativeLayerMetadata, layerAMetadata);
 }
 
 } // namespace android
diff --git a/services/surfaceflinger/tests/unittests/TestableScheduler.h b/services/surfaceflinger/tests/unittests/TestableScheduler.h
index 68df987..b8a6063 100644
--- a/services/surfaceflinger/tests/unittests/TestableScheduler.h
+++ b/services/surfaceflinger/tests/unittests/TestableScheduler.h
@@ -17,6 +17,7 @@
 #pragma once
 
 #include <Scheduler/Scheduler.h>
+#include <ftl/fake_guard.h>
 #include <gmock/gmock.h>
 #include <gui/ISurfaceComposer.h>
 
@@ -32,17 +33,19 @@
 
 class TestableScheduler : public Scheduler, private ICompositor {
 public:
-    TestableScheduler(std::shared_ptr<RefreshRateConfigs> configs, ISchedulerCallback& callback)
+    TestableScheduler(RefreshRateSelectorPtr selectorPtr, ISchedulerCallback& callback)
           : TestableScheduler(std::make_unique<mock::VsyncController>(),
-                              std::make_unique<mock::VSyncTracker>(), std::move(configs),
+                              std::make_unique<mock::VSyncTracker>(), std::move(selectorPtr),
                               callback) {}
 
     TestableScheduler(std::unique_ptr<VsyncController> controller,
-                      std::unique_ptr<VSyncTracker> tracker,
-                      std::shared_ptr<RefreshRateConfigs> configs, ISchedulerCallback& callback)
+                      std::unique_ptr<VSyncTracker> tracker, RefreshRateSelectorPtr selectorPtr,
+                      ISchedulerCallback& callback)
           : Scheduler(*this, callback, Feature::kContentDetection) {
         mVsyncSchedule.emplace(VsyncSchedule(std::move(tracker), nullptr, std::move(controller)));
-        setRefreshRateConfigs(std::move(configs));
+
+        const auto displayId = selectorPtr->getActiveMode().modePtr->getPhysicalDisplayId();
+        registerDisplay(displayId, std::move(selectorPtr));
 
         ON_CALL(*this, postMessage).WillByDefault([](sp<MessageHandler>&& handler) {
             // Execute task to prevent broken promise exception on destruction.
@@ -66,16 +69,39 @@
     auto& mutablePrimaryHWVsyncEnabled() { return mPrimaryHWVsyncEnabled; }
     auto& mutableHWVsyncAvailable() { return mHWVsyncAvailable; }
 
-    auto& mutableLayerHistory() { return mLayerHistory; }
+    auto refreshRateSelector() { return leaderSelectorPtr(); }
 
-    auto& mutableDisplays() { return mDisplays; }
+    const auto& refreshRateSelectors() const NO_THREAD_SAFETY_ANALYSIS {
+        return mRefreshRateSelectors;
+    }
+
+    bool hasRefreshRateSelectors() const { return !refreshRateSelectors().empty(); }
+
+    void registerDisplay(PhysicalDisplayId displayId, RefreshRateSelectorPtr selectorPtr) {
+        ftl::FakeGuard guard(kMainThreadContext);
+        Scheduler::registerDisplay(displayId, std::move(selectorPtr));
+    }
+
+    void unregisterDisplay(PhysicalDisplayId displayId) {
+        ftl::FakeGuard guard(kMainThreadContext);
+        Scheduler::unregisterDisplay(displayId);
+    }
+
+    std::optional<PhysicalDisplayId> leaderDisplayId() const NO_THREAD_SAFETY_ANALYSIS {
+        return mLeaderDisplayId;
+    }
+
+    void setLeaderDisplay(PhysicalDisplayId displayId) {
+        ftl::FakeGuard guard(kMainThreadContext);
+        Scheduler::setLeaderDisplay(displayId);
+    }
+
+    auto& mutableLayerHistory() { return mLayerHistory; }
 
     size_t layerHistorySize() NO_THREAD_SAFETY_ANALYSIS {
         return mLayerHistory.mActiveLayerInfos.size() + mLayerHistory.mInactiveLayerInfos.size();
     }
 
-    auto refreshRateConfigs() { return holdRefreshRateConfigs(); }
-
     size_t getNumActiveLayers() NO_THREAD_SAFETY_ANALYSIS {
         return mLayerHistory.mActiveLayerInfos.size();
     }
@@ -102,19 +128,21 @@
         mPolicy.idleTimer = globalSignals.idle ? TimerState::Expired : TimerState::Reset;
     }
 
-    void setContentRequirements(std::vector<RefreshRateConfigs::LayerRequirement> layers) {
+    void setContentRequirements(std::vector<RefreshRateSelector::LayerRequirement> layers) {
         std::lock_guard<std::mutex> lock(mPolicyLock);
         mPolicy.contentRequirements = std::move(layers);
     }
 
-    std::vector<DisplayModeConfig> getBestDisplayModeConfigs() {
-        std::lock_guard<std::mutex> lock(mPolicyLock);
-        return Scheduler::getBestDisplayModeConfigs();
+    using Scheduler::DisplayModeChoice;
+    using Scheduler::DisplayModeChoiceMap;
+
+    DisplayModeChoiceMap chooseDisplayModes() NO_THREAD_SAFETY_ANALYSIS {
+        return Scheduler::chooseDisplayModes();
     }
 
     void dispatchCachedReportedMode() {
         std::lock_guard<std::mutex> lock(mPolicyLock);
-        return Scheduler::dispatchCachedReportedMode();
+        Scheduler::dispatchCachedReportedMode();
     }
 
     void clearCachedReportedMode() {
@@ -122,8 +150,8 @@
         mPolicy.cachedModeChangedParams.reset();
     }
 
-    void onNonPrimaryDisplayModeChanged(ConnectionHandle handle, DisplayModePtr mode) {
-        return Scheduler::onNonPrimaryDisplayModeChanged(handle, mode);
+    void onNonPrimaryDisplayModeChanged(ConnectionHandle handle, const FrameRateMode& mode) {
+        Scheduler::onNonPrimaryDisplayModeChanged(handle, mode);
     }
 
 private:
diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
index 29ff5ee..7d0b340 100644
--- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
+++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
@@ -30,13 +30,17 @@
 #include <ftl/fake_guard.h>
 #include <gui/ScreenCaptureResults.h>
 
+#include <ui/DynamicDisplayInfo.h>
 #include "DisplayDevice.h"
 #include "FakeVsyncConfiguration.h"
 #include "FrameTracer/FrameTracer.h"
+#include "FrontEnd/LayerCreationArgs.h"
+#include "FrontEnd/LayerHandle.h"
 #include "Layer.h"
 #include "NativeWindowSurface.h"
+#include "RenderArea.h"
 #include "Scheduler/MessageQueue.h"
-#include "Scheduler/RefreshRateConfigs.h"
+#include "Scheduler/RefreshRateSelector.h"
 #include "StartPropertySetThread.h"
 #include "SurfaceFlinger.h"
 #include "SurfaceFlingerDefaultFactory.h"
@@ -118,6 +122,10 @@
 
     sp<Layer> createEffectLayer(const LayerCreationArgs&) override { return nullptr; }
 
+    sp<LayerFE> createLayerFE(const std::string& layerName) override {
+        return sp<LayerFE>::make(layerName);
+    }
+
     std::unique_ptr<FrameTracer> createFrameTracer() override {
         return std::make_unique<mock::FrameTracer>();
     }
@@ -162,7 +170,8 @@
     // functions.
 
     void setupRenderEngine(std::unique_ptr<renderengine::RenderEngine> renderEngine) {
-        mFlinger->mCompositionEngine->setRenderEngine(std::move(renderEngine));
+        mFlinger->mRenderEngine = std::move(renderEngine);
+        mFlinger->mCompositionEngine->setRenderEngine(mFlinger->mRenderEngine.get());
     }
 
     void setupComposer(std::unique_ptr<Hwc2::Composer> composer) {
@@ -186,10 +195,10 @@
     static constexpr struct TwoDisplayModes {
     } kTwoDisplayModes;
 
-    using RefreshRateConfigsPtr = std::shared_ptr<scheduler::RefreshRateConfigs>;
+    using RefreshRateSelectorPtr = std::shared_ptr<scheduler::RefreshRateSelector>;
 
     using DisplayModesVariant =
-            std::variant<OneDisplayMode, TwoDisplayModes, RefreshRateConfigsPtr>;
+            std::variant<OneDisplayMode, TwoDisplayModes, RefreshRateSelectorPtr>;
 
     void setupScheduler(std::unique_ptr<scheduler::VsyncController> vsyncController,
                         std::unique_ptr<scheduler::VSyncTracker> vsyncTracker,
@@ -198,9 +207,9 @@
                         SchedulerCallbackImpl callbackImpl = SchedulerCallbackImpl::kNoOp,
                         DisplayModesVariant modesVariant = kOneDisplayMode,
                         bool useNiceMock = false) {
-        RefreshRateConfigsPtr configs;
-        if (std::holds_alternative<RefreshRateConfigsPtr>(modesVariant)) {
-            configs = std::move(std::get<RefreshRateConfigsPtr>(modesVariant));
+        RefreshRateSelectorPtr selectorPtr;
+        if (std::holds_alternative<RefreshRateSelectorPtr>(modesVariant)) {
+            selectorPtr = std::move(std::get<RefreshRateSelectorPtr>(modesVariant));
         } else {
             constexpr DisplayModeId kModeId60{0};
             DisplayModes modes = makeModes(mock::createDisplayMode(kModeId60, 60_Hz));
@@ -210,10 +219,10 @@
                 modes.try_emplace(kModeId90, mock::createDisplayMode(kModeId90, 90_Hz));
             }
 
-            configs = std::make_shared<scheduler::RefreshRateConfigs>(modes, kModeId60);
+            selectorPtr = std::make_shared<scheduler::RefreshRateSelector>(modes, kModeId60);
         }
 
-        const auto fps = FTL_FAKE_GUARD(kMainThreadContext, configs->getActiveMode().getFps());
+        const auto fps = selectorPtr->getActiveMode().fps;
         mFlinger->mVsyncConfiguration = mFactory.createVsyncConfiguration(fps);
         mFlinger->mVsyncModulator = sp<scheduler::VsyncModulator>::make(
                 mFlinger->mVsyncConfiguration->getCurrentConfigs());
@@ -231,12 +240,12 @@
             mScheduler =
                     new testing::NiceMock<scheduler::TestableScheduler>(std::move(vsyncController),
                                                                         std::move(vsyncTracker),
-                                                                        std::move(configs),
+                                                                        std::move(selectorPtr),
                                                                         callback);
         } else {
             mScheduler = new scheduler::TestableScheduler(std::move(vsyncController),
                                                           std::move(vsyncTracker),
-                                                          std::move(configs), callback);
+                                                          std::move(selectorPtr), callback);
         }
 
         mFlinger->mAppConnectionHandle = mScheduler->createConnection(std::move(appEventThread));
@@ -392,14 +401,15 @@
         return mFlinger->setPowerModeInternal(display, mode);
     }
 
-    auto renderScreenImpl(const RenderArea& renderArea,
-                                SurfaceFlinger::TraverseLayersFunction traverseLayers,
-                                const std::shared_ptr<renderengine::ExternalTexture>& buffer,
-                                bool forSystem, bool regionSampling) {
+    auto renderScreenImpl(std::unique_ptr<RenderArea> renderArea,
+                          SurfaceFlinger::TraverseLayersFunction traverseLayers,
+                          const std::shared_ptr<renderengine::ExternalTexture>& buffer,
+                          bool forSystem, bool regionSampling) {
         ScreenCaptureResults captureResults;
-        return mFlinger->renderScreenImpl(renderArea, traverseLayers, buffer, forSystem,
-                                                regionSampling, false /* grayscale */,
-                                                captureResults);
+        return FTL_FAKE_GUARD(kMainThreadContext,
+                              mFlinger->renderScreenImpl(std::move(renderArea), traverseLayers,
+                                                         buffer, forSystem, regionSampling,
+                                                         false /* grayscale */, captureResults));
     }
 
     auto traverseLayersInLayerStack(ui::LayerStack layerStack, int32_t uid,
@@ -420,18 +430,24 @@
         return mFlinger->mTransactionHandler.mPendingTransactionCount.load();
     }
 
-    auto setTransactionState(
-            const FrameTimelineInfo& frameTimelineInfo, const Vector<ComposerState>& states,
-            const Vector<DisplayState>& displays, uint32_t flags, const sp<IBinder>& applyToken,
-            const InputWindowCommands& inputWindowCommands, int64_t desiredPresentTime,
-            bool isAutoTimestamp, const client_cache_t& uncacheBuffer, bool hasListenerCallbacks,
-            std::vector<ListenerCallbacks>& listenerCallbacks, uint64_t transactionId) {
+    auto setTransactionState(const FrameTimelineInfo& frameTimelineInfo,
+                             Vector<ComposerState>& states, const Vector<DisplayState>& displays,
+                             uint32_t flags, const sp<IBinder>& applyToken,
+                             const InputWindowCommands& inputWindowCommands,
+                             int64_t desiredPresentTime, bool isAutoTimestamp,
+                             const client_cache_t& uncacheBuffer, bool hasListenerCallbacks,
+                             std::vector<ListenerCallbacks>& listenerCallbacks,
+                             uint64_t transactionId) {
         return mFlinger->setTransactionState(frameTimelineInfo, states, displays, flags, applyToken,
                                              inputWindowCommands, desiredPresentTime,
                                              isAutoTimestamp, uncacheBuffer, hasListenerCallbacks,
                                              listenerCallbacks, transactionId);
     }
 
+    auto setTransactionStateInternal(TransactionState& transaction) {
+        return mFlinger->mTransactionHandler.queueTransaction(std::move(transaction));
+    }
+
     auto flushTransactionQueues() {
         return FTL_FAKE_GUARD(kMainThreadContext, mFlinger->flushTransactionQueues(kVsyncId));
     }
@@ -447,25 +463,21 @@
         return SurfaceFlinger::calculateMaxAcquiredBufferCount(refreshRate, presentLatency);
     }
 
-    auto setDesiredDisplayModeSpecs(const sp<IBinder>& displayToken, ui::DisplayModeId defaultMode,
-                                    bool allowGroupSwitching, float primaryRefreshRateMin,
-                                    float primaryRefreshRateMax, float appRequestRefreshRateMin,
-                                    float appRequestRefreshRateMax) {
-        return mFlinger->setDesiredDisplayModeSpecs(displayToken, defaultMode, allowGroupSwitching,
-                                                    primaryRefreshRateMin, primaryRefreshRateMax,
-                                                    appRequestRefreshRateMin,
-                                                    appRequestRefreshRateMax);
+    auto setDesiredDisplayModeSpecs(const sp<IBinder>& displayToken,
+                                    const gui::DisplayModeSpecs& specs) {
+        return mFlinger->setDesiredDisplayModeSpecs(displayToken, specs);
     }
 
     void onActiveDisplayChanged(const sp<DisplayDevice>& activeDisplay) {
         Mutex::Autolock lock(mFlinger->mStateLock);
         ftl::FakeGuard guard(kMainThreadContext);
-        mFlinger->onActiveDisplayChangedLocked(activeDisplay);
+        mFlinger->onActiveDisplayChangedLocked(nullptr, activeDisplay);
     }
 
     auto createLayer(LayerCreationArgs& args, const sp<IBinder>& parentHandle,
                      gui::CreateSurfaceResult& outResult) {
-        return mFlinger->createLayer(args, parentHandle, outResult);
+        args.parentHandle = parentHandle;
+        return mFlinger->createLayer(args, outResult);
     }
 
     auto mirrorLayer(const LayerCreationArgs& args, const sp<IBinder>& mirrorFromHandle,
@@ -475,6 +487,11 @@
 
     void updateLayerMetadataSnapshot() { mFlinger->updateLayerMetadataSnapshot(); }
 
+    void getDynamicDisplayInfo(const sp<IBinder>& displayToken,
+                               ui::DynamicDisplayInfo* dynamicDisplayInfo) {
+        mFlinger->getDynamicDisplayInfo(displayToken, dynamicDisplayInfo);
+    }
+
     /* ------------------------------------------------------------------------
      * Read-only access to private data to assert post-conditions.
      */
@@ -523,9 +540,7 @@
     auto& mutablePrimaryHwcDisplayId() { return getHwComposer().mPrimaryHwcDisplayId; }
     auto& mutableActiveDisplayId() { return mFlinger->mActiveDisplayId; }
 
-    auto fromHandle(const sp<IBinder>& handle) {
-        return mFlinger->fromHandle(handle);
-    }
+    auto fromHandle(const sp<IBinder>& handle) { return LayerHandle::getLayer(handle); }
 
     ~TestableSurfaceFlinger() {
         // All these pointer and container clears help ensure that GMock does
@@ -537,8 +552,8 @@
         mutableDrawingState().displays.clear();
         mFlinger->mScheduler.reset();
         mFlinger->mCompositionEngine->setHwComposer(std::unique_ptr<HWComposer>());
-        mFlinger->mCompositionEngine->setRenderEngine(
-                std::unique_ptr<renderengine::RenderEngine>());
+        mFlinger->mRenderEngine = std::unique_ptr<renderengine::RenderEngine>();
+        mFlinger->mCompositionEngine->setRenderEngine(mFlinger->mRenderEngine.get());
     }
 
     /* ------------------------------------------------------------------------
@@ -612,7 +627,7 @@
             return *this;
         }
 
-        auto& setPowerMode(hal::PowerMode mode) {
+        auto& setPowerMode(std::optional<hal::PowerMode> mode) {
             mPowerMode = mode;
             return *this;
         }
@@ -634,16 +649,18 @@
             // is much longer lived.
             auto display = std::make_unique<HWC2Display>(*composer, *mCapabilities, mHwcDisplayId,
                                                          mHwcDisplayType);
-
             display->mutableIsConnected() = true;
-            display->setPowerMode(mPowerMode);
+
+            if (mPowerMode) {
+                display->setPowerMode(*mPowerMode);
+            }
+
             flinger->mutableHwcDisplayData()[mDisplayId].hwcDisplay = std::move(display);
 
             EXPECT_CALL(*composer, getDisplayConfigs(mHwcDisplayId, _))
                     .WillRepeatedly(
                             DoAll(SetArgPointee<1>(std::vector<hal::HWConfigId>{mActiveConfig}),
                                   Return(hal::Error::NONE)));
-
             EXPECT_CALL(*composer,
                         getDisplayAttribute(mHwcDisplayId, mActiveConfig, hal::Attribute::WIDTH, _))
                     .WillRepeatedly(DoAll(SetArgPointee<3>(mResolution.getWidth()),
@@ -702,7 +719,7 @@
         int32_t mDpiY = DEFAULT_DPI;
         int32_t mConfigGroup = DEFAULT_CONFIG_GROUP;
         hal::HWConfigId mActiveConfig = DEFAULT_ACTIVE_CONFIG;
-        hal::PowerMode mPowerMode = DEFAULT_POWER_MODE;
+        std::optional<hal::PowerMode> mPowerMode = DEFAULT_POWER_MODE;
         const std::unordered_set<aidl::android::hardware::graphics::composer3::Capability>*
                 mCapabilities = nullptr;
     };
@@ -750,16 +767,17 @@
             return mFlinger.mutableDisplays().get(mDisplayToken)->get();
         }
 
-        // If `configs` is nullptr, the injector creates RefreshRateConfigs from the `modes`.
-        // Otherwise, it uses `configs`, which the caller must create using the same `modes`.
+        // If `selectorPtr` is nullptr, the injector creates RefreshRateSelector from the `modes`.
+        // Otherwise, it uses `selectorPtr`, which the caller must create using the same `modes`.
         //
-        // TODO(b/182939859): Once `modes` can be retrieved from RefreshRateConfigs, remove
-        // the `configs` parameter in favor of an alternative setRefreshRateConfigs API.
-        auto& setDisplayModes(DisplayModes modes, DisplayModeId activeModeId,
-                              std::shared_ptr<scheduler::RefreshRateConfigs> configs = nullptr) {
+        // TODO(b/182939859): Once `modes` can be retrieved from RefreshRateSelector, remove
+        // the `selectorPtr` parameter in favor of an alternative setRefreshRateSelector API.
+        auto& setDisplayModes(
+                DisplayModes modes, DisplayModeId activeModeId,
+                std::shared_ptr<scheduler::RefreshRateSelector> selectorPtr = nullptr) {
             mDisplayModes = std::move(modes);
             mCreationArgs.activeModeId = activeModeId;
-            mCreationArgs.refreshRateConfigs = std::move(configs);
+            mCreationArgs.refreshRateSelector = std::move(selectorPtr);
             return *this;
         }
 
@@ -778,7 +796,7 @@
             return *this;
         }
 
-        auto& setPowerMode(hal::PowerMode mode) {
+        auto& setPowerMode(std::optional<hal::PowerMode> mode) {
             mCreationArgs.initialPowerMode = mode;
             return *this;
         }
@@ -806,7 +824,7 @@
             auto& modes = mDisplayModes;
             auto& activeModeId = mCreationArgs.activeModeId;
 
-            if (displayId && !mCreationArgs.refreshRateConfigs) {
+            if (displayId && !mCreationArgs.refreshRateSelector) {
                 if (const auto physicalId = PhysicalDisplayId::tryCast(*displayId)) {
                     if (modes.empty()) {
                         constexpr DisplayModeId kModeId{0};
@@ -826,40 +844,49 @@
                         activeModeId = kModeId;
                     }
 
-                    mCreationArgs.refreshRateConfigs =
-                            std::make_shared<scheduler::RefreshRateConfigs>(modes, activeModeId);
+                    mCreationArgs.refreshRateSelector =
+                            std::make_shared<scheduler::RefreshRateSelector>(modes, activeModeId);
                 }
             }
 
             sp<DisplayDevice> display = sp<DisplayDevice>::make(mCreationArgs);
             mFlinger.mutableDisplays().emplace_or_replace(mDisplayToken, display);
-            if (mFlinger.scheduler()) {
-                mFlinger.scheduler()->registerDisplay(display);
-            }
 
             DisplayDeviceState state;
             state.isSecure = mCreationArgs.isSecure;
 
             if (mConnectionType) {
                 LOG_ALWAYS_FATAL_IF(!displayId);
-                const auto physicalId = PhysicalDisplayId::tryCast(*displayId);
-                LOG_ALWAYS_FATAL_IF(!physicalId);
+                const auto physicalIdOpt = PhysicalDisplayId::tryCast(*displayId);
+                LOG_ALWAYS_FATAL_IF(!physicalIdOpt);
+                const auto physicalId = *physicalIdOpt;
+
+                if (mCreationArgs.isPrimary) {
+                    mFlinger.mutableActiveDisplayId() = physicalId;
+                }
+
                 LOG_ALWAYS_FATAL_IF(!mHwcDisplayId);
 
                 const auto activeMode = modes.get(activeModeId);
                 LOG_ALWAYS_FATAL_IF(!activeMode);
+                const auto fps = activeMode->get()->getFps();
 
-                state.physical = {.id = *physicalId,
+                state.physical = {.id = physicalId,
                                   .hwcDisplayId = *mHwcDisplayId,
                                   .activeMode = activeMode->get()};
 
-                const auto it = mFlinger.mutablePhysicalDisplays()
-                                        .emplace_or_replace(*physicalId, mDisplayToken, *physicalId,
-                                                            *mConnectionType, std::move(modes),
-                                                            ui::ColorModes(), std::nullopt)
-                                        .first;
+                mFlinger.mutablePhysicalDisplays().emplace_or_replace(physicalId, mDisplayToken,
+                                                                      physicalId, *mConnectionType,
+                                                                      std::move(modes),
+                                                                      ui::ColorModes(),
+                                                                      std::nullopt);
 
-                display->setActiveMode(activeModeId, it->second.snapshot());
+                if (mFlinger.scheduler()) {
+                    mFlinger.scheduler()->registerDisplay(physicalId,
+                                                          display->holdRefreshRateSelector());
+                }
+
+                display->setActiveMode(activeModeId, fps, fps);
             }
 
             mFlinger.mutableCurrentState().displays.add(mDisplayToken, state);
diff --git a/services/surfaceflinger/tests/unittests/TimeStatsTest.cpp b/services/surfaceflinger/tests/unittests/TimeStatsTest.cpp
index 6ffc039..1dd4f25 100644
--- a/services/surfaceflinger/tests/unittests/TimeStatsTest.cpp
+++ b/services/surfaceflinger/tests/unittests/TimeStatsTest.cpp
@@ -1320,6 +1320,7 @@
     insertTimeRecord(NORMAL_SEQUENCE, LAYER_ID_0, 3, 3000000, {}, GameMode::Performance);
     insertTimeRecord(NORMAL_SEQUENCE, LAYER_ID_0, 4, 4000000, {}, GameMode::Battery);
     insertTimeRecord(NORMAL_SEQUENCE, LAYER_ID_0, 5, 4000000, {}, GameMode::Battery);
+    insertTimeRecord(NORMAL_SEQUENCE, LAYER_ID_0, 6, 5000000, {}, GameMode::Custom);
 
     std::string pulledData;
     EXPECT_TRUE(mTimeStats->onPullAtom(10063 /*SURFACEFLINGER_STATS_LAYER_INFO*/, &pulledData));
@@ -1327,9 +1328,9 @@
     SurfaceflingerStatsLayerInfoWrapper atomList;
     ASSERT_TRUE(atomList.ParseFromString(pulledData));
     // The first time record is never uploaded to stats.
-    ASSERT_EQ(atomList.atom_size(), 3);
+    ASSERT_EQ(atomList.atom_size(), 4);
     // Layers are ordered based on the hash in LayerStatsKey. For this test, the order happens to
-    // be: 0 - Battery 1 - Performance 2 - Standard
+    // be: 0 - Battery 1 - Custom 2 - Performance 3 - Standard
     const SurfaceflingerStatsLayerInfo& atom0 = atomList.atom(0);
 
     EXPECT_EQ(atom0.layer_name(), genLayerName(LAYER_ID_0));
@@ -1364,7 +1365,7 @@
     EXPECT_EQ(atom1.uid(), UID_0);
     EXPECT_EQ(atom1.display_refresh_rate_bucket(), REFRESH_RATE_BUCKET_0);
     EXPECT_EQ(atom1.render_rate_bucket(), RENDER_RATE_BUCKET_0);
-    EXPECT_EQ(atom1.game_mode(), SurfaceflingerStatsLayerInfo::GAME_MODE_PERFORMANCE);
+    EXPECT_EQ(atom1.game_mode(), SurfaceflingerStatsLayerInfo::GAME_MODE_CUSTOM);
 
     const SurfaceflingerStatsLayerInfo& atom2 = atomList.atom(2);
 
@@ -1377,12 +1378,30 @@
     EXPECT_THAT(atom2.latch_to_present(), HistogramEq(buildExpectedHistogram({2}, {1})));
     EXPECT_THAT(atom2.desired_to_present(), HistogramEq(buildExpectedHistogram({1}, {1})));
     EXPECT_THAT(atom2.post_to_acquire(), HistogramEq(buildExpectedHistogram({1}, {1})));
-    EXPECT_EQ(atom2.late_acquire_frames(), LATE_ACQUIRE_FRAMES);
-    EXPECT_EQ(atom2.bad_desired_present_frames(), BAD_DESIRED_PRESENT_FRAMES);
+    EXPECT_EQ(atom2.late_acquire_frames(), 0);
+    EXPECT_EQ(atom2.bad_desired_present_frames(), 0);
     EXPECT_EQ(atom2.uid(), UID_0);
     EXPECT_EQ(atom2.display_refresh_rate_bucket(), REFRESH_RATE_BUCKET_0);
     EXPECT_EQ(atom2.render_rate_bucket(), RENDER_RATE_BUCKET_0);
-    EXPECT_EQ(atom2.game_mode(), SurfaceflingerStatsLayerInfo::GAME_MODE_STANDARD);
+    EXPECT_EQ(atom2.game_mode(), SurfaceflingerStatsLayerInfo::GAME_MODE_PERFORMANCE);
+
+    const SurfaceflingerStatsLayerInfo& atom3 = atomList.atom(3);
+
+    EXPECT_EQ(atom3.layer_name(), genLayerName(LAYER_ID_0));
+    EXPECT_EQ(atom3.total_frames(), 1);
+    EXPECT_EQ(atom3.dropped_frames(), 0);
+    EXPECT_THAT(atom3.present_to_present(), HistogramEq(buildExpectedHistogram({1}, {1})));
+    EXPECT_THAT(atom3.post_to_present(), HistogramEq(buildExpectedHistogram({4}, {1})));
+    EXPECT_THAT(atom3.acquire_to_present(), HistogramEq(buildExpectedHistogram({3}, {1})));
+    EXPECT_THAT(atom3.latch_to_present(), HistogramEq(buildExpectedHistogram({2}, {1})));
+    EXPECT_THAT(atom3.desired_to_present(), HistogramEq(buildExpectedHistogram({1}, {1})));
+    EXPECT_THAT(atom3.post_to_acquire(), HistogramEq(buildExpectedHistogram({1}, {1})));
+    EXPECT_EQ(atom3.late_acquire_frames(), LATE_ACQUIRE_FRAMES);
+    EXPECT_EQ(atom3.bad_desired_present_frames(), BAD_DESIRED_PRESENT_FRAMES);
+    EXPECT_EQ(atom3.uid(), UID_0);
+    EXPECT_EQ(atom3.display_refresh_rate_bucket(), REFRESH_RATE_BUCKET_0);
+    EXPECT_EQ(atom3.render_rate_bucket(), RENDER_RATE_BUCKET_0);
+    EXPECT_EQ(atom3.game_mode(), SurfaceflingerStatsLayerInfo::GAME_MODE_STANDARD);
 }
 
 TEST_F(TimeStatsTest, layerStatsCallback_pullsMultipleLayers) {
diff --git a/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp b/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp
index db438b7..d84698f 100644
--- a/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp
+++ b/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp
@@ -30,8 +30,9 @@
 #include <vector>
 #include <binder/Binder.h>
 
+#include "FrontEnd/TransactionHandler.h"
 #include "TestableSurfaceFlinger.h"
-#include "TransactionHandler.h"
+#include "TransactionState.h"
 #include "mock/MockEventThread.h"
 #include "mock/MockVsyncController.h"
 
@@ -41,6 +42,8 @@
 using testing::Return;
 
 using FakeHwcDisplayInjector = TestableSurfaceFlinger::FakeHwcDisplayInjector;
+using frontend::TransactionHandler;
+
 constexpr nsecs_t TRANSACTION_TIMEOUT = s2ns(5);
 class TransactionApplicationTest : public testing::Test {
 public:
@@ -294,7 +297,7 @@
 TEST_F(TransactionApplicationTest, FromHandle) {
     sp<IBinder> badHandle;
     auto ret = mFlinger.fromHandle(badHandle);
-    EXPECT_EQ(nullptr, ret.promote().get());
+    EXPECT_EQ(nullptr, ret.get());
 }
 
 class LatchUnsignaledTest : public TransactionApplicationTest {
@@ -359,13 +362,23 @@
         EXPECT_TRUE(mFlinger.getTransactionQueue().isEmpty());
         EXPECT_EQ(0u, mFlinger.getPendingTransactionQueue().size());
 
-        for (const auto& transaction : transactions) {
-            mFlinger.setTransactionState(transaction.frameTimelineInfo, transaction.states,
-                                         transaction.displays, transaction.flags,
-                                         transaction.applyToken, transaction.inputWindowCommands,
-                                         transaction.desiredPresentTime,
-                                         transaction.isAutoTimestamp, transaction.uncacheBuffer,
-                                         mHasListenerCallbacks, mCallbacks, transaction.id);
+        for (auto transaction : transactions) {
+            std::vector<ResolvedComposerState> resolvedStates;
+            resolvedStates.reserve(transaction.states.size());
+            for (auto& state : transaction.states) {
+                resolvedStates.emplace_back(std::move(state));
+            }
+
+            TransactionState transactionState(transaction.frameTimelineInfo, resolvedStates,
+                                              transaction.displays, transaction.flags,
+                                              transaction.applyToken,
+                                              transaction.inputWindowCommands,
+                                              transaction.desiredPresentTime,
+                                              transaction.isAutoTimestamp,
+                                              transaction.uncacheBuffer, systemTime(), 0,
+                                              mHasListenerCallbacks, mCallbacks, getpid(),
+                                              static_cast<int>(getuid()), transaction.id);
+            mFlinger.setTransactionStateInternal(transactionState);
         }
         mFlinger.flushTransactionQueues();
         EXPECT_TRUE(mFlinger.getTransactionQueue().isEmpty());
diff --git a/services/surfaceflinger/tests/unittests/TransactionFrameTracerTest.cpp b/services/surfaceflinger/tests/unittests/TransactionFrameTracerTest.cpp
index 1173d1c..09d002f 100644
--- a/services/surfaceflinger/tests/unittests/TransactionFrameTracerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/TransactionFrameTracerTest.cpp
@@ -126,7 +126,7 @@
                                                          HAL_PIXEL_FORMAT_RGBA_8888,
                                                          0ULL /*usage*/);
         layer->setBuffer(externalTexture, bufferData, postTime, /*desiredPresentTime*/ 30, false,
-                         dequeueTime, FrameTimelineInfo{});
+                         dequeueTime, FrameTimelineInfo{}, 0);
 
         commitTransaction(layer.get());
         nsecs_t latchTime = 25;
diff --git a/services/surfaceflinger/tests/unittests/TransactionProtoParserTest.cpp b/services/surfaceflinger/tests/unittests/TransactionProtoParserTest.cpp
index 14e1aac..b6427c0 100644
--- a/services/surfaceflinger/tests/unittests/TransactionProtoParserTest.cpp
+++ b/services/surfaceflinger/tests/unittests/TransactionProtoParserTest.cpp
@@ -46,14 +46,14 @@
     size_t layerCount = 2;
     t1.states.reserve(layerCount);
     for (uint32_t i = 0; i < layerCount; i++) {
-        ComposerState s;
+        ResolvedComposerState s;
         if (i == 1) {
             layer.parentSurfaceControlForChild =
                     sp<SurfaceControl>::make(SurfaceComposerClient::getDefault(), layerHandle, 42,
                                              "#42");
         }
         s.state = layer;
-        t1.states.add(s);
+        t1.states.emplace_back(s);
     }
 
     size_t displayCount = 2;
diff --git a/services/surfaceflinger/tests/unittests/TransactionSurfaceFrameTest.cpp b/services/surfaceflinger/tests/unittests/TransactionSurfaceFrameTest.cpp
index ae03db4..7dfbcc0 100644
--- a/services/surfaceflinger/tests/unittests/TransactionSurfaceFrameTest.cpp
+++ b/services/surfaceflinger/tests/unittests/TransactionSurfaceFrameTest.cpp
@@ -131,7 +131,7 @@
         FrameTimelineInfo ftInfo;
         ftInfo.vsyncId = 1;
         ftInfo.inputEventId = 0;
-        layer->setBuffer(externalTexture, bufferData, 10, 20, false, std::nullopt, ftInfo);
+        layer->setBuffer(externalTexture, bufferData, 10, 20, false, std::nullopt, ftInfo, 0);
         acquireFence->signalForTest(12);
 
         commitTransaction(layer.get());
@@ -166,7 +166,7 @@
         FrameTimelineInfo ftInfo;
         ftInfo.vsyncId = 1;
         ftInfo.inputEventId = 0;
-        layer->setBuffer(externalTexture1, bufferData, 10, 20, false, std::nullopt, ftInfo);
+        layer->setBuffer(externalTexture1, bufferData, 10, 20, false, std::nullopt, ftInfo, 0);
         EXPECT_EQ(0u, layer->mDrawingState.bufferlessSurfaceFramesTX.size());
         ASSERT_NE(nullptr, layer->mDrawingState.bufferSurfaceFrameTX);
         const auto droppedSurfaceFrame = layer->mDrawingState.bufferSurfaceFrameTX;
@@ -183,7 +183,7 @@
                                                          2ULL /* bufferId */,
                                                          HAL_PIXEL_FORMAT_RGBA_8888,
                                                          0ULL /*usage*/);
-        layer->setBuffer(externalTexture2, bufferData, 10, 20, false, std::nullopt, ftInfo);
+        layer->setBuffer(externalTexture2, bufferData, 10, 20, false, std::nullopt, ftInfo, 0);
         nsecs_t end = systemTime();
         acquireFence2->signalForTest(12);
 
@@ -229,7 +229,7 @@
                                                          1ULL /* bufferId */,
                                                          HAL_PIXEL_FORMAT_RGBA_8888,
                                                          0ULL /*usage*/);
-        layer->setBuffer(externalTexture, bufferData, 10, 20, false, std::nullopt, ftInfo);
+        layer->setBuffer(externalTexture, bufferData, 10, 20, false, std::nullopt, ftInfo, 0);
         acquireFence->signalForTest(12);
 
         EXPECT_EQ(0u, layer->mDrawingState.bufferlessSurfaceFramesTX.size());
@@ -264,7 +264,7 @@
         FrameTimelineInfo ftInfo;
         ftInfo.vsyncId = 1;
         ftInfo.inputEventId = 0;
-        layer->setBuffer(externalTexture, bufferData, 10, 20, false, std::nullopt, ftInfo);
+        layer->setBuffer(externalTexture, bufferData, 10, 20, false, std::nullopt, ftInfo, 0);
         EXPECT_EQ(0u, layer->mDrawingState.bufferlessSurfaceFramesTX.size());
         ASSERT_NE(nullptr, layer->mDrawingState.bufferSurfaceFrameTX);
 
@@ -307,7 +307,7 @@
         FrameTimelineInfo ftInfo3;
         ftInfo3.vsyncId = 3;
         ftInfo3.inputEventId = 0;
-        layer->setBuffer(externalTexture, bufferData, 10, 20, false, std::nullopt, ftInfo3);
+        layer->setBuffer(externalTexture, bufferData, 10, 20, false, std::nullopt, ftInfo3, 0);
         EXPECT_EQ(2u, layer->mDrawingState.bufferlessSurfaceFramesTX.size());
         ASSERT_NE(nullptr, layer->mDrawingState.bufferSurfaceFrameTX);
         const auto bufferSurfaceFrameTX = layer->mDrawingState.bufferSurfaceFrameTX;
@@ -352,7 +352,7 @@
         FrameTimelineInfo ftInfo;
         ftInfo.vsyncId = 1;
         ftInfo.inputEventId = 0;
-        layer->setBuffer(externalTexture1, bufferData, 10, 20, false, std::nullopt, ftInfo);
+        layer->setBuffer(externalTexture1, bufferData, 10, 20, false, std::nullopt, ftInfo, 0);
         ASSERT_NE(nullptr, layer->mDrawingState.bufferSurfaceFrameTX);
         const auto droppedSurfaceFrame = layer->mDrawingState.bufferSurfaceFrameTX;
 
@@ -367,7 +367,7 @@
                                                          1ULL /* bufferId */,
                                                          HAL_PIXEL_FORMAT_RGBA_8888,
                                                          0ULL /*usage*/);
-        layer->setBuffer(externalTexture2, bufferData, 10, 20, false, std::nullopt, ftInfo);
+        layer->setBuffer(externalTexture2, bufferData, 10, 20, false, std::nullopt, ftInfo, 0);
         acquireFence2->signalForTest(12);
 
         ASSERT_NE(nullptr, layer->mDrawingState.bufferSurfaceFrameTX);
@@ -404,7 +404,7 @@
         FrameTimelineInfo ftInfo;
         ftInfo.vsyncId = 1;
         ftInfo.inputEventId = 0;
-        layer->setBuffer(externalTexture1, bufferData, 10, 20, false, std::nullopt, ftInfo);
+        layer->setBuffer(externalTexture1, bufferData, 10, 20, false, std::nullopt, ftInfo, 0);
         EXPECT_EQ(0u, layer->mDrawingState.bufferlessSurfaceFramesTX.size());
         ASSERT_NE(nullptr, layer->mDrawingState.bufferSurfaceFrameTX);
         const auto droppedSurfaceFrame1 = layer->mDrawingState.bufferSurfaceFrameTX;
@@ -424,7 +424,7 @@
         FrameTimelineInfo ftInfoInv;
         ftInfoInv.vsyncId = FrameTimelineInfo::INVALID_VSYNC_ID;
         ftInfoInv.inputEventId = 0;
-        layer->setBuffer(externalTexture2, bufferData, 10, 20, false, std::nullopt, ftInfoInv);
+        layer->setBuffer(externalTexture2, bufferData, 10, 20, false, std::nullopt, ftInfoInv, 0);
         auto dropEndTime1 = systemTime();
         EXPECT_EQ(0u, layer->mDrawingState.bufferlessSurfaceFramesTX.size());
         ASSERT_NE(nullptr, layer->mDrawingState.bufferSurfaceFrameTX);
@@ -445,7 +445,7 @@
         FrameTimelineInfo ftInfo2;
         ftInfo2.vsyncId = 2;
         ftInfo2.inputEventId = 0;
-        layer->setBuffer(externalTexture3, bufferData, 10, 20, false, std::nullopt, ftInfo2);
+        layer->setBuffer(externalTexture3, bufferData, 10, 20, false, std::nullopt, ftInfo2, 0);
         auto dropEndTime2 = systemTime();
         acquireFence3->signalForTest(12);
 
@@ -494,7 +494,7 @@
             FrameTimelineInfo ftInfo;
             ftInfo.vsyncId = 1;
             ftInfo.inputEventId = 0;
-            layer->setBuffer(externalTexture, bufferData, 10, 20, false, std::nullopt, ftInfo);
+            layer->setBuffer(externalTexture, bufferData, 10, 20, false, std::nullopt, ftInfo, 0);
             FrameTimelineInfo ftInfo2;
             ftInfo2.vsyncId = 2;
             ftInfo2.inputEventId = 0;
diff --git a/services/surfaceflinger/tests/unittests/TransactionTracingTest.cpp b/services/surfaceflinger/tests/unittests/TransactionTracingTest.cpp
index 2dbcfbd..482c3a8 100644
--- a/services/surfaceflinger/tests/unittests/TransactionTracingTest.cpp
+++ b/services/surfaceflinger/tests/unittests/TransactionTracingTest.cpp
@@ -112,16 +112,16 @@
         {
             TransactionState transaction;
             transaction.id = 50;
-            ComposerState layerState;
+            ResolvedComposerState layerState;
             layerState.state.surface = fakeLayerHandle;
             layerState.state.what = layer_state_t::eLayerChanged;
             layerState.state.z = 42;
-            transaction.states.add(layerState);
-            ComposerState childState;
+            transaction.states.emplace_back(layerState);
+            ResolvedComposerState childState;
             childState.state.surface = fakeChildLayerHandle;
             childState.state.what = layer_state_t::eLayerChanged;
             childState.state.z = 43;
-            transaction.states.add(childState);
+            transaction.states.emplace_back(childState);
             mTracing.addQueuedTransaction(transaction);
 
             std::vector<TransactionState> transactions;
@@ -138,12 +138,12 @@
         {
             TransactionState transaction;
             transaction.id = 51;
-            ComposerState layerState;
+            ResolvedComposerState layerState;
             layerState.state.surface = fakeLayerHandle;
             layerState.state.what = layer_state_t::eLayerChanged | layer_state_t::ePositionChanged;
             layerState.state.z = 41;
             layerState.state.x = 22;
-            transaction.states.add(layerState);
+            transaction.states.emplace_back(layerState);
             mTracing.addQueuedTransaction(transaction);
 
             std::vector<TransactionState> transactions;
@@ -247,16 +247,16 @@
         {
             TransactionState transaction;
             transaction.id = 50;
-            ComposerState layerState;
+            ResolvedComposerState layerState;
             layerState.state.surface = fakeLayerHandle;
             layerState.state.what = layer_state_t::eLayerChanged;
             layerState.state.z = 42;
-            transaction.states.add(layerState);
-            ComposerState mirrorState;
+            transaction.states.emplace_back(layerState);
+            ResolvedComposerState mirrorState;
             mirrorState.state.surface = fakeMirrorLayerHandle;
             mirrorState.state.what = layer_state_t::eLayerChanged;
             mirrorState.state.z = 43;
-            transaction.states.add(mirrorState);
+            transaction.states.emplace_back(mirrorState);
             mTracing.addQueuedTransaction(transaction);
 
             std::vector<TransactionState> transactions;
diff --git a/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp b/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp
index 2da266b..47c2dee 100644
--- a/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp
+++ b/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp
@@ -54,6 +54,7 @@
     void resetModel() final {}
     bool needsMoreSamples() const final { return false; }
     bool isVSyncInPhase(nsecs_t, Fps) const final { return false; }
+    void setDivisor(unsigned) final {}
     void dump(std::string&) const final {}
 
 private:
@@ -91,6 +92,7 @@
     void resetModel() final {}
     bool needsMoreSamples() const final { return false; }
     bool isVSyncInPhase(nsecs_t, Fps) const final { return false; }
+    void setDivisor(unsigned) final {}
     void dump(std::string&) const final {}
 
 private:
diff --git a/services/surfaceflinger/tests/unittests/VSyncDispatchTimerQueueTest.cpp b/services/surfaceflinger/tests/unittests/VSyncDispatchTimerQueueTest.cpp
index f660753..2b86e94 100644
--- a/services/surfaceflinger/tests/unittests/VSyncDispatchTimerQueueTest.cpp
+++ b/services/surfaceflinger/tests/unittests/VSyncDispatchTimerQueueTest.cpp
@@ -55,6 +55,7 @@
     MOCK_METHOD0(resetModel, void());
     MOCK_CONST_METHOD0(needsMoreSamples, bool());
     MOCK_CONST_METHOD2(isVSyncInPhase, bool(nsecs_t, Fps));
+    MOCK_METHOD(void, setDivisor, (unsigned), (override));
     MOCK_CONST_METHOD1(dump, void(std::string&));
 
     nsecs_t nextVSyncTime(nsecs_t timePoint) const {
diff --git a/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp b/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp
index 74d2b7d..3095e8a 100644
--- a/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp
+++ b/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp
@@ -532,6 +532,26 @@
     EXPECT_THAT(intercept, IsCloseTo(expectedIntercept, mMaxRoundingError));
 }
 
+TEST_F(VSyncPredictorTest, setDivisorIsRespected) {
+    auto last = mNow;
+    for (auto i = 0u; i < kMinimumSamplesForPrediction; i++) {
+        EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(last + mPeriod));
+        mNow += mPeriod;
+        last = mNow;
+        tracker.addVsyncTimestamp(mNow);
+    }
+
+    tracker.setDivisor(3);
+
+    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(mNow + mPeriod));
+    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 100), Eq(mNow + mPeriod));
+    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 1100), Eq(mNow + 4 * mPeriod));
+    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 2100), Eq(mNow + 4 * mPeriod));
+    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 3100), Eq(mNow + 4 * mPeriod));
+    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 4100), Eq(mNow + 7 * mPeriod));
+    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 5100), Eq(mNow + 7 * mPeriod));
+}
+
 } // 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 a35ff96..8bd689a 100644
--- a/services/surfaceflinger/tests/unittests/VSyncReactorTest.cpp
+++ b/services/surfaceflinger/tests/unittests/VSyncReactorTest.cpp
@@ -50,6 +50,7 @@
     MOCK_METHOD0(resetModel, void());
     MOCK_CONST_METHOD0(needsMoreSamples, bool());
     MOCK_CONST_METHOD2(isVSyncInPhase, bool(nsecs_t, Fps));
+    MOCK_METHOD(void, setDivisor, (unsigned), (override));
     MOCK_CONST_METHOD1(dump, void(std::string&));
 };
 
diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockAidlPowerHalWrapper.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockAidlPowerHalWrapper.h
index c2c3d77..5654691 100644
--- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockAidlPowerHalWrapper.h
+++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockAidlPowerHalWrapper.h
@@ -36,7 +36,7 @@
     MockAidlPowerHalWrapper();
     ~MockAidlPowerHalWrapper() override;
     MOCK_METHOD(bool, setExpensiveRendering, (bool enabled), (override));
-    MOCK_METHOD(bool, notifyDisplayUpdateImminent, (), (override));
+    MOCK_METHOD(bool, notifyDisplayUpdateImminentAndCpuReset, (), (override));
     MOCK_METHOD(bool, supportsPowerHintSession, (), (override));
     MOCK_METHOD(bool, isPowerHintSessionRunning, (), (override));
     MOCK_METHOD(void, restartPowerHintSession, (), (override));
diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h
index aa8b521..836e3a4 100644
--- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h
+++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h
@@ -32,7 +32,6 @@
 using android::hardware::graphics::common::V1_1::RenderIntent;
 using android::hardware::graphics::common::V1_2::ColorMode;
 using android::hardware::graphics::common::V1_2::Dataspace;
-using android::hardware::graphics::common::V1_2::Hdr;
 using android::hardware::graphics::common::V1_2::PixelFormat;
 
 using android::hardware::graphics::composer::V2_1::Config;
@@ -56,8 +55,8 @@
                  std::vector<aidl::android::hardware::graphics::composer3::Capability>());
     MOCK_METHOD0(dumpDebugInfo, std::string());
     MOCK_METHOD1(registerCallback, void(HWC2::ComposerCallback&));
-    MOCK_METHOD0(resetCommands, void());
-    MOCK_METHOD0(executeCommands, Error());
+    MOCK_METHOD1(resetCommands, void(Display));
+    MOCK_METHOD1(executeCommands, Error(Display));
     MOCK_METHOD0(getMaxVirtualDisplayCount, uint32_t());
     MOCK_METHOD4(createVirtualDisplay, Error(uint32_t, uint32_t, PixelFormat*, Display*));
     MOCK_METHOD1(destroyVirtualDisplay, Error(Display));
@@ -164,6 +163,10 @@
     MOCK_METHOD2(setIdleTimerEnabled, Error(Display, std::chrono::milliseconds));
     MOCK_METHOD2(hasDisplayIdleTimerCapability, Error(Display, bool*));
     MOCK_METHOD2(getPhysicalDisplayOrientation, Error(Display, AidlTransform*));
+    MOCK_METHOD1(getOverlaySupport,
+                 Error(aidl::android::hardware::graphics::composer3::OverlayProperties*));
+    MOCK_METHOD1(onHotplugConnect, void(Display));
+    MOCK_METHOD1(onHotplugDisconnect, void(Display));
 };
 
 } // namespace Hwc2::mock
diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockDisplayMode.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockDisplayMode.h
index a83ecbc..c78b6bd 100644
--- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockDisplayMode.h
+++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockDisplayMode.h
@@ -33,4 +33,9 @@
             .build();
 }
 
+inline DisplayModePtr createDisplayMode(PhysicalDisplayId displayId, DisplayModeId modeId,
+                                        Fps refreshRate) {
+    return createDisplayMode(modeId, refreshRate, {}, {}, displayId);
+}
+
 } // namespace android::mock
diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockHWC2.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockHWC2.h
index 07cd15d..40f59b8 100644
--- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockHWC2.h
+++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockHWC2.h
@@ -105,6 +105,9 @@
     MOCK_METHOD(bool, hasDisplayIdleTimerCapability, (), (const override));
     MOCK_METHOD(hal::Error, getPhysicalDisplayOrientation, (Hwc2::AidlTransform *),
                 (const override));
+    MOCK_METHOD(hal::Error, getOverlaySupport,
+                (aidl::android::hardware::graphics::composer3::OverlayProperties *),
+                (const override));
 };
 
 class Layer : public HWC2::Layer {
diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockIPowerHintSession.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockIPowerHintSession.h
index 439f6f4..f4ded21 100644
--- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockIPowerHintSession.h
+++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockIPowerHintSession.h
@@ -23,6 +23,7 @@
 
 using android::binder::Status;
 using android::hardware::power::IPowerHintSession;
+using android::hardware::power::SessionHint;
 
 using namespace android::hardware::power;
 
@@ -40,6 +41,8 @@
     MOCK_METHOD(std::string, getInterfaceHash, (), (override));
     MOCK_METHOD(Status, updateTargetWorkDuration, (int64_t), (override));
     MOCK_METHOD(Status, reportActualWorkDuration, (const ::std::vector<WorkDuration>&), (override));
+    MOCK_METHOD(Status, sendHint, (SessionHint), (override));
+    MOCK_METHOD(Status, setThreads, (const ::std::vector<int32_t>&), (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 fb1b394..7fc625c 100644
--- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerAdvisor.h
+++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerAdvisor.h
@@ -32,7 +32,7 @@
     MOCK_METHOD(void, setExpensiveRenderingExpected, (DisplayId displayId, bool expected),
                 (override));
     MOCK_METHOD(bool, isUsingExpensiveRendering, (), (override));
-    MOCK_METHOD(void, notifyDisplayUpdateImminent, (), (override));
+    MOCK_METHOD(void, notifyDisplayUpdateImminentAndCpuReset, (), (override));
     MOCK_METHOD(bool, usePowerHintSession, (), (override));
     MOCK_METHOD(bool, supportsPowerHintSession, (), (override));
     MOCK_METHOD(bool, isPowerHintSessionRunning, (), (override));
diff --git a/services/surfaceflinger/tests/unittests/mock/MockDisplayModeSpecs.h b/services/surfaceflinger/tests/unittests/mock/MockDisplayModeSpecs.h
new file mode 100644
index 0000000..a71e82c
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/mock/MockDisplayModeSpecs.h
@@ -0,0 +1,35 @@
+/*
+ * 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 <android/gui/DisplayModeSpecs.h>
+
+namespace android::mock {
+
+inline gui::DisplayModeSpecs createDisplayModeSpecs(int32_t defaultMode, bool allowGroupSwitching,
+                                                    float minFps, float maxFps) {
+    gui::DisplayModeSpecs specs;
+    specs.defaultMode = defaultMode;
+    specs.allowGroupSwitching = allowGroupSwitching;
+    specs.primaryRanges.physical.min = minFps;
+    specs.primaryRanges.physical.max = maxFps;
+    specs.primaryRanges.render = specs.primaryRanges.physical;
+    specs.appRequestRanges = specs.primaryRanges;
+    return specs;
+}
+
+} // namespace android::mock
diff --git a/services/surfaceflinger/tests/unittests/mock/MockEventThread.h b/services/surfaceflinger/tests/unittests/mock/MockEventThread.h
index ded6806..f8567bd 100644
--- a/services/surfaceflinger/tests/unittests/mock/MockEventThread.h
+++ b/services/surfaceflinger/tests/unittests/mock/MockEventThread.h
@@ -34,7 +34,7 @@
     MOCK_METHOD0(onScreenReleased, void());
     MOCK_METHOD0(onScreenAcquired, void());
     MOCK_METHOD2(onHotplugReceived, void(PhysicalDisplayId, bool));
-    MOCK_METHOD1(onModeChanged, void(DisplayModePtr));
+    MOCK_METHOD1(onModeChanged, void(const scheduler::FrameRateMode &));
     MOCK_METHOD2(onFrameRateOverridesChanged,
                  void(PhysicalDisplayId, std::vector<FrameRateOverride>));
     MOCK_CONST_METHOD1(dump, void(std::string&));
diff --git a/libs/binder/tests/parcel_fuzzer/GenericDataParcelable.aidl b/services/surfaceflinger/tests/unittests/mock/MockFrameRateMode.h
similarity index 65%
copy from libs/binder/tests/parcel_fuzzer/GenericDataParcelable.aidl
copy to services/surfaceflinger/tests/unittests/mock/MockFrameRateMode.h
index fc2542b..ef9cd9b 100644
--- a/libs/binder/tests/parcel_fuzzer/GenericDataParcelable.aidl
+++ b/services/surfaceflinger/tests/unittests/mock/MockFrameRateMode.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2022 The Android Open Source Project
+ * 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.
@@ -14,11 +14,10 @@
  * limitations under the License.
  */
 
-parcelable GenericDataParcelable {
-    int data;
-    float majorVersion;
-    float minorVersion;
-    IBinder binder;
-    ParcelFileDescriptor fileDescriptor;
-    int[] array;
-}
\ No newline at end of file
+#pragma once
+
+#include <scheduler/FrameRateMode.h>
+
+// Use a C style macro to keep the line numbers printed in gtest
+#define EXPECT_FRAME_RATE_MODE(modePtr, fps, mode) \
+    EXPECT_EQ((scheduler::FrameRateMode{(fps), (modePtr)}), (mode))
diff --git a/services/surfaceflinger/tests/unittests/mock/MockSchedulerCallback.h b/services/surfaceflinger/tests/unittests/mock/MockSchedulerCallback.h
index 8af2dfa..7d4b159 100644
--- a/services/surfaceflinger/tests/unittests/mock/MockSchedulerCallback.h
+++ b/services/surfaceflinger/tests/unittests/mock/MockSchedulerCallback.h
@@ -24,14 +24,14 @@
 
 struct SchedulerCallback final : ISchedulerCallback {
     MOCK_METHOD(void, setVsyncEnabled, (bool), (override));
-    MOCK_METHOD(void, requestDisplayModes, (std::vector<scheduler::DisplayModeConfig>), (override));
+    MOCK_METHOD(void, requestDisplayModes, (std::vector<display::DisplayModeRequest>), (override));
     MOCK_METHOD(void, kernelTimerChanged, (bool), (override));
     MOCK_METHOD(void, triggerOnFrameRateOverridesChanged, (), (override));
 };
 
 struct NoOpSchedulerCallback final : ISchedulerCallback {
     void setVsyncEnabled(bool) override {}
-    void requestDisplayModes(std::vector<scheduler::DisplayModeConfig>) override {}
+    void requestDisplayModes(std::vector<display::DisplayModeRequest>) override {}
     void kernelTimerChanged(bool) override {}
     void triggerOnFrameRateOverridesChanged() override {}
 };
diff --git a/services/surfaceflinger/tests/unittests/mock/MockVSyncTracker.h b/services/surfaceflinger/tests/unittests/mock/MockVSyncTracker.h
index 5b0c1f3..6893154 100644
--- a/services/surfaceflinger/tests/unittests/mock/MockVSyncTracker.h
+++ b/services/surfaceflinger/tests/unittests/mock/MockVSyncTracker.h
@@ -34,6 +34,7 @@
     MOCK_METHOD0(resetModel, void());
     MOCK_CONST_METHOD0(needsMoreSamples, bool());
     MOCK_CONST_METHOD2(isVSyncInPhase, bool(nsecs_t, Fps));
+    MOCK_METHOD(void, setDivisor, (unsigned), (override));
     MOCK_CONST_METHOD1(dump, void(std::string&));
 };
 
diff --git a/services/vr/hardware_composer/Android.bp b/services/vr/hardware_composer/Android.bp
deleted file mode 100644
index 80e9a3c..0000000
--- a/services/vr/hardware_composer/Android.bp
+++ /dev/null
@@ -1,134 +0,0 @@
-package {
-    // See: http://go/android-license-faq
-    // A large-scale-change added 'default_applicable_licenses' to import
-    // all of the 'license_kinds' from "frameworks_native_license"
-    // to get the below license kinds:
-    //   SPDX-license-identifier-Apache-2.0
-    default_applicable_licenses: ["frameworks_native_license"],
-}
-
-cc_library_shared {
-    name: "libvr_hwc-hal",
-
-    system_ext_specific: true,
-
-    srcs: [
-        "impl/vr_hwc.cpp",
-        "impl/vr_composer_client.cpp",
-    ],
-
-    static_libs: [
-        "libbroadcastring",
-        "libdisplay",
-    ],
-
-    shared_libs: [
-        "android.frameworks.vr.composer@2.0",
-        "android.hardware.graphics.composer@2.1",
-        "android.hardware.graphics.composer@2.2",
-        "android.hardware.graphics.composer@2.3",
-        "android.hardware.graphics.composer@2.1-resources",
-        "android.hardware.graphics.mapper@2.0",
-        "android.hardware.graphics.mapper@3.0",
-        "android.hardware.graphics.mapper@4.0",
-        "libbase",
-        "libbufferhubqueue",
-        "libbinder",
-        "libcutils",
-        "libfmq",
-        "libhardware",
-        "libhidlbase",
-        "liblog",
-        "libsync",
-        "libui",
-        "libutils",
-        "libpdx_default_transport",
-    ],
-
-    header_libs: [
-        "android.hardware.graphics.composer@2.1-command-buffer",
-        "android.hardware.graphics.composer@2.3-hal",
-    ],
-
-    export_header_lib_headers: [
-        "android.hardware.graphics.composer@2.3-hal",
-    ],
-
-    export_static_lib_headers: [
-        "libdisplay",
-    ],
-
-    export_shared_lib_headers: [
-        "android.frameworks.vr.composer@2.0",
-        "android.hardware.graphics.composer@2.1",
-        "android.hardware.graphics.composer@2.2",
-        "android.hardware.graphics.composer@2.3",
-    ],
-
-    export_include_dirs: ["."],
-
-    cflags: [
-        "-DLOG_TAG=\"vr_hwc\"",
-        "-DATRACE_TAG=ATRACE_TAG_GRAPHICS",
-        "-Wall",
-        "-Werror",
-        "-Wno-error=unused-private-field",
-        // Warnings in vr_hwc.cpp to be fixed after sync of goog/master.
-        "-Wno-sign-compare",
-        "-Wno-unused-parameter",
-    ],
-
-}
-
-cc_library_static {
-    name: "libvr_hwc-impl",
-    srcs: [
-        "vr_composer.cpp",
-    ],
-    static_libs: [
-        "libvr_hwc-binder",
-    ],
-    shared_libs: [
-        "libbase",
-        "libbinder",
-        "liblog",
-        "libui",
-        "libutils",
-        "libvr_hwc-hal",
-    ],
-    export_shared_lib_headers: [
-        "libvr_hwc-hal",
-    ],
-    cflags: [
-        "-DLOG_TAG=\"vr_hwc\"",
-        "-Wall",
-        "-Werror",
-    ],
-}
-
-cc_test {
-    name: "vr_hwc_test",
-    gtest: true,
-    srcs: ["tests/vr_composer_test.cpp"],
-    static_libs: [
-        "libgtest",
-        "libvr_hwc-impl",
-        // NOTE: This needs to be included after the *-impl lib otherwise the
-        // symbols in the *-binder library get optimized out.
-        "libvr_hwc-binder",
-    ],
-    cflags: [
-        "-Wall",
-        "-Werror",
-        // warnings in vr_composer_test.cpp to be fixed after merge of goog/master
-        "-Wno-sign-compare",
-        "-Wno-unused-parameter",
-    ],
-    shared_libs: [
-        "libbase",
-        "libbinder",
-        "liblog",
-        "libui",
-        "libutils",
-    ],
-}
diff --git a/services/vr/hardware_composer/aidl/Android.bp b/services/vr/hardware_composer/aidl/Android.bp
deleted file mode 100644
index fa71ed7..0000000
--- a/services/vr/hardware_composer/aidl/Android.bp
+++ /dev/null
@@ -1,36 +0,0 @@
-package {
-    // See: http://go/android-license-faq
-    // A large-scale-change added 'default_applicable_licenses' to import
-    // all of the 'license_kinds' from "frameworks_native_license"
-    // to get the below license kinds:
-    //   SPDX-license-identifier-Apache-2.0
-    default_applicable_licenses: ["frameworks_native_license"],
-}
-
-cc_library_static {
-    name: "libvr_hwc-binder",
-    srcs: [
-        "android/dvr/IVrComposer.aidl",
-        "android/dvr/IVrComposerCallback.aidl",
-        "android/dvr/parcelable_composer_frame.cpp",
-        "android/dvr/parcelable_composer_layer.cpp",
-        "android/dvr/parcelable_unique_fd.cpp",
-    ],
-    aidl: {
-        local_include_dirs: ["."],
-        export_aidl_headers: true,
-    },
-    export_include_dirs: ["."],
-
-    cflags: [
-        "-Wall",
-        "-Werror",
-    ],
-
-    shared_libs: [
-        "libbinder",
-        "libui",
-        "libutils",
-        "libvr_hwc-hal",
-    ],
-}
diff --git a/services/vr/hardware_composer/aidl/android/dvr/IVrComposer.aidl b/services/vr/hardware_composer/aidl/android/dvr/IVrComposer.aidl
deleted file mode 100644
index be1ec5b..0000000
--- a/services/vr/hardware_composer/aidl/android/dvr/IVrComposer.aidl
+++ /dev/null
@@ -1,25 +0,0 @@
-package android.dvr;
-
-import android.dvr.IVrComposerCallback;
-
-/**
- * Service interface exposed by VR HWC exposed to system apps which allows one
- * system app to connect to get SurfaceFlinger's outputs (all displays). This
- * is active when SurfaceFlinger is in VR mode, where all 2D output is
- * redirected to VR HWC.
- *
- * @hide */
-interface IVrComposer
-{
-  const String SERVICE_NAME = "vr_hwc";
-
-  /**
-   * Registers a callback used to receive frame notifications.
-   */
-  void registerObserver(in IVrComposerCallback callback);
-
-  /**
-   * Clears a previously registered frame notification callback.
-   */
-  void clearObserver();
-}
diff --git a/services/vr/hardware_composer/aidl/android/dvr/IVrComposerCallback.aidl b/services/vr/hardware_composer/aidl/android/dvr/IVrComposerCallback.aidl
deleted file mode 100644
index aa70de1..0000000
--- a/services/vr/hardware_composer/aidl/android/dvr/IVrComposerCallback.aidl
+++ /dev/null
@@ -1,22 +0,0 @@
-package android.dvr;
-
-import android.dvr.ParcelableComposerFrame;
-import android.dvr.ParcelableUniqueFd;
-
-/**
- * A system app will implement and register this callback with VRComposer
- * to receive the layers SurfaceFlinger presented when in VR mode.
- *
- * @hide */
-interface IVrComposerCallback {
-  /**
-   * Called by the VR HWC service when a new frame is ready to be presented.
-   *
-   * @param frame The new frame VR HWC wants to present.
-   * @return A fence FD used to signal when the previous frame is no longer
-   * used by the client. This may be an invalid fence (-1) if the client is not
-   * using the previous frame, in which case the previous frame may be re-used
-   * at any point in time.
-   */
-  ParcelableUniqueFd onNewFrame(in ParcelableComposerFrame frame);
-}
diff --git a/services/vr/hardware_composer/aidl/android/dvr/ParcelableComposerFrame.aidl b/services/vr/hardware_composer/aidl/android/dvr/ParcelableComposerFrame.aidl
deleted file mode 100644
index 84abc19..0000000
--- a/services/vr/hardware_composer/aidl/android/dvr/ParcelableComposerFrame.aidl
+++ /dev/null
@@ -1,3 +0,0 @@
-package android.dvr;
-
-parcelable ParcelableComposerFrame cpp_header "android/dvr/parcelable_composer_frame.h";
diff --git a/services/vr/hardware_composer/aidl/android/dvr/ParcelableComposerLayer.aidl b/services/vr/hardware_composer/aidl/android/dvr/ParcelableComposerLayer.aidl
deleted file mode 100644
index a200345..0000000
--- a/services/vr/hardware_composer/aidl/android/dvr/ParcelableComposerLayer.aidl
+++ /dev/null
@@ -1,3 +0,0 @@
-package android.dvr;
-
-parcelable ParcelableComposerLayer cpp_header "android/dvr/parcelable_composer_layer.h";
diff --git a/services/vr/hardware_composer/aidl/android/dvr/ParcelableUniqueFd.aidl b/services/vr/hardware_composer/aidl/android/dvr/ParcelableUniqueFd.aidl
deleted file mode 100644
index eee9d13..0000000
--- a/services/vr/hardware_composer/aidl/android/dvr/ParcelableUniqueFd.aidl
+++ /dev/null
@@ -1,3 +0,0 @@
-package android.dvr;
-
-parcelable ParcelableUniqueFd cpp_header "android/dvr/parcelable_unique_fd.h";
diff --git a/services/vr/hardware_composer/aidl/android/dvr/parcelable_composer_frame.cpp b/services/vr/hardware_composer/aidl/android/dvr/parcelable_composer_frame.cpp
deleted file mode 100644
index db7d5dc..0000000
--- a/services/vr/hardware_composer/aidl/android/dvr/parcelable_composer_frame.cpp
+++ /dev/null
@@ -1,110 +0,0 @@
-#include "aidl/android/dvr/parcelable_composer_frame.h"
-
-#include <binder/Parcel.h>
-
-#include "aidl/android/dvr/parcelable_composer_layer.h"
-
-namespace android {
-namespace dvr {
-
-ParcelableComposerFrame::ParcelableComposerFrame() {}
-
-ParcelableComposerFrame::ParcelableComposerFrame(
-    const ComposerView::Frame& frame)
-    : frame_(frame) {}
-
-ParcelableComposerFrame::~ParcelableComposerFrame() {}
-
-status_t ParcelableComposerFrame::writeToParcel(Parcel* parcel) const {
-  status_t ret = parcel->writeUint64(frame_.display_id);
-  if (ret != OK) return ret;
-
-  ret = parcel->writeInt32(frame_.display_width);
-  if (ret != OK) return ret;
-
-  ret = parcel->writeInt32(frame_.display_height);
-  if (ret != OK) return ret;
-
-  ret = parcel->writeBool(frame_.removed);
-  if (ret != OK) return ret;
-
-  ret = parcel->writeUint32(static_cast<uint32_t>(frame_.active_config));
-  if (ret != OK) return ret;
-
-  ret = parcel->writeUint32(static_cast<uint32_t>(frame_.color_mode));
-  if (ret != OK) return ret;
-
-  ret = parcel->writeUint32(static_cast<uint32_t>(frame_.power_mode));
-  if (ret != OK) return ret;
-
-  ret = parcel->writeUint32(static_cast<uint32_t>(frame_.vsync_enabled));
-  if (ret != OK) return ret;
-
-  ret = parcel->writeInt32(frame_.color_transform_hint);
-  if (ret != OK) return ret;
-
-  for(size_t i = 0; i < 16; i++) {
-    ret = parcel->writeFloat(frame_.color_transform[i]);
-    if (ret != OK) return ret;
-  }
-
-  std::vector<ParcelableComposerLayer> layers;
-  for (size_t i = 0; i < frame_.layers.size(); ++i)
-    layers.push_back(ParcelableComposerLayer(frame_.layers[i]));
-
-  ret = parcel->writeParcelableVector(layers);
-
-  return ret;
-}
-
-status_t ParcelableComposerFrame::readFromParcel(const Parcel* parcel) {
-  status_t ret = parcel->readUint64(&frame_.display_id);
-  if (ret != OK) return ret;
-
-  ret = parcel->readInt32(&frame_.display_width);
-  if (ret != OK) return ret;
-
-  ret = parcel->readInt32(&frame_.display_height);
-  if (ret != OK) return ret;
-
-  ret = parcel->readBool(&frame_.removed);
-  if (ret != OK) return ret;
-
-  uint32_t value;
-  ret = parcel->readUint32(&value);
-  if (ret != OK) return ret;
-  frame_.active_config = static_cast<Config>(value);
-
-  ret = parcel->readUint32(&value);
-  if (ret != OK) return ret;
-  frame_.color_mode = static_cast<ColorMode>(value);
-
-  ret = parcel->readUint32(&value);
-  if (ret != OK) return ret;
-  frame_.power_mode = static_cast<IComposerClient::PowerMode>(value);
-
-  ret = parcel->readUint32(&value);
-  if (ret != OK) return ret;
-  frame_.vsync_enabled = static_cast<IComposerClient::Vsync>(value);
-
-  ret = parcel->readInt32(&frame_.color_transform_hint);
-  if (ret != OK) return ret;
-
-  for(size_t i = 0; i < 16; i++) {
-    ret = parcel->readFloat(&frame_.color_transform[i]);
-    if (ret != OK) return ret;
-  }
-
-  std::vector<ParcelableComposerLayer> layers;
-  ret = parcel->readParcelableVector(&layers);
-  if (ret != OK) return ret;
-
-  frame_.layers.clear();
-  for (size_t i = 0; i < layers.size(); ++i)
-    frame_.layers.push_back(layers[i].layer());
-
-  return ret;
-}
-
-}  // namespace dvr
-}  // namespace android
diff --git a/services/vr/hardware_composer/aidl/android/dvr/parcelable_composer_frame.h b/services/vr/hardware_composer/aidl/android/dvr/parcelable_composer_frame.h
deleted file mode 100644
index a82df7f..0000000
--- a/services/vr/hardware_composer/aidl/android/dvr/parcelable_composer_frame.h
+++ /dev/null
@@ -1,28 +0,0 @@
-#ifndef ANDROID_DVR_HARDWARE_COMPOSER_AIDL_ANDROID_DVR_PARCELABLE_COMPOSER_FRAME_H
-#define ANDROID_DVR_HARDWARE_COMPOSER_AIDL_ANDROID_DVR_PARCELABLE_COMPOSER_FRAME_H
-
-#include <binder/Parcelable.h>
-#include <impl/vr_hwc.h>
-
-namespace android {
-namespace dvr {
-
-class ParcelableComposerFrame : public Parcelable {
- public:
-  ParcelableComposerFrame();
-  explicit ParcelableComposerFrame(const ComposerView::Frame& frame);
-  ~ParcelableComposerFrame() override;
-
-  ComposerView::Frame frame() const { return frame_; }
-
-  status_t writeToParcel(Parcel* parcel) const override;
-  status_t readFromParcel(const Parcel* parcel) override;
-
- private:
-  ComposerView::Frame frame_;
-};
-
-}  // namespace dvr
-}  // namespace android
-
-#endif  // ANDROID_DVR_HARDWARE_COMPOSER_AIDL_ANDROID_DVR_PARCELABLE_COMPOSER_FRAME_H
diff --git a/services/vr/hardware_composer/aidl/android/dvr/parcelable_composer_layer.cpp b/services/vr/hardware_composer/aidl/android/dvr/parcelable_composer_layer.cpp
deleted file mode 100644
index c3621eb..0000000
--- a/services/vr/hardware_composer/aidl/android/dvr/parcelable_composer_layer.cpp
+++ /dev/null
@@ -1,240 +0,0 @@
-#include "aidl/android/dvr/parcelable_composer_layer.h"
-
-#include <binder/Parcel.h>
-#include <ui/Fence.h>
-#include <ui/GraphicBuffer.h>
-#include <ui/GraphicBufferMapper.h>
-
-namespace android {
-namespace dvr {
-
-ParcelableComposerLayer::ParcelableComposerLayer() {}
-
-ParcelableComposerLayer::ParcelableComposerLayer(
-    const ComposerView::ComposerLayer& layer) : layer_(layer) {}
-
-ParcelableComposerLayer::~ParcelableComposerLayer() {}
-
-status_t ParcelableComposerLayer::writeToParcel(Parcel* parcel) const {
-  status_t ret = parcel->writeUint64(layer_.id);
-  if (ret != OK) return ret;
-
-  ret = parcel->write(*layer_.buffer);
-  if (ret != OK) return ret;
-
-  ret = parcel->writeBool(layer_.fence->isValid());
-  if (ret != OK) return ret;
-
-  if (layer_.fence->isValid()) {
-    ret = parcel->writeFileDescriptor(layer_.fence->dup(), true);
-    if (ret != OK) return ret;
-  }
-
-  ret = parcel->writeInt32(layer_.display_frame.left);
-  if (ret != OK) return ret;
-
-  ret = parcel->writeInt32(layer_.display_frame.top);
-  if (ret != OK) return ret;
-
-  ret = parcel->writeInt32(layer_.display_frame.right);
-  if (ret != OK) return ret;
-
-  ret = parcel->writeInt32(layer_.display_frame.bottom);
-  if (ret != OK) return ret;
-
-  ret = parcel->writeFloat(layer_.crop.left);
-  if (ret != OK) return ret;
-
-  ret = parcel->writeFloat(layer_.crop.top);
-  if (ret != OK) return ret;
-
-  ret = parcel->writeFloat(layer_.crop.right);
-  if (ret != OK) return ret;
-
-  ret = parcel->writeFloat(layer_.crop.bottom);
-  if (ret != OK) return ret;
-
-  ret = parcel->writeInt32(static_cast<int32_t>(layer_.blend_mode));
-  if (ret != OK) return ret;
-
-  ret = parcel->writeFloat(layer_.alpha);
-  if (ret != OK) return ret;
-
-  ret = parcel->writeUint32(layer_.type);
-  if (ret != OK) return ret;
-
-  ret = parcel->writeUint32(layer_.app_id);
-  if (ret != OK) return ret;
-
-  ret = parcel->writeUint32(layer_.z_order);
-  if (ret != OK) return ret;
-
-  ret = parcel->writeInt32(layer_.cursor_x);
-  if (ret != OK) return ret;
-
-  ret = parcel->writeInt32(layer_.cursor_y);
-  if (ret != OK) return ret;
-
-  uint32_t color = layer_.color.r |
-      (static_cast<uint32_t>(layer_.color.g) << 8) |
-      (static_cast<uint32_t>(layer_.color.b) << 16) |
-      (static_cast<uint32_t>(layer_.color.a) << 24);
-  ret = parcel->writeUint32(color);
-  if (ret != OK) return ret;
-
-  ret = parcel->writeInt32(layer_.dataspace);
-  if (ret != OK) return ret;
-
-  ret = parcel->writeInt32(layer_.transform);
-  if (ret != OK) return ret;
-
-  ret = parcel->writeUint32(static_cast<uint32_t>(layer_.visible_regions.size()));
-  if (ret != OK) return ret;
-
-  for (auto& rect: layer_.visible_regions) {
-    ret = parcel->writeInt32(rect.left);
-    ret = parcel->writeInt32(rect.top);
-    ret = parcel->writeInt32(rect.right);
-    ret = parcel->writeInt32(rect.bottom);
-    if (ret != OK) return ret;
-  }
-
-  ret = parcel->writeUint32(static_cast<uint32_t>(layer_.damaged_regions.size()));
-  if (ret != OK) return ret;
-
-  for (auto& rect: layer_.damaged_regions) {
-    ret = parcel->writeInt32(rect.left);
-    ret = parcel->writeInt32(rect.top);
-    ret = parcel->writeInt32(rect.right);
-    ret = parcel->writeInt32(rect.bottom);
-    if (ret != OK) return ret;
-  }
-
-  return OK;
-}
-
-status_t ParcelableComposerLayer::readFromParcel(const Parcel* parcel) {
-  status_t ret = parcel->readUint64(&layer_.id);
-  if (ret != OK) return ret;
-
-  layer_.buffer = new GraphicBuffer();
-  ret = parcel->read(*layer_.buffer);
-  if (ret != OK) {
-    layer_.buffer.clear();
-    return ret;
-  }
-
-  bool has_fence = 0;
-  ret = parcel->readBool(&has_fence);
-  if (ret != OK) return ret;
-
-  if (has_fence)
-    layer_.fence = new Fence(dup(parcel->readFileDescriptor()));
-  else
-    layer_.fence = new Fence();
-
-  ret = parcel->readInt32(&layer_.display_frame.left);
-  if (ret != OK) return ret;
-
-  ret = parcel->readInt32(&layer_.display_frame.top);
-  if (ret != OK) return ret;
-
-  ret = parcel->readInt32(&layer_.display_frame.right);
-  if (ret != OK) return ret;
-
-  ret = parcel->readInt32(&layer_.display_frame.bottom);
-  if (ret != OK) return ret;
-
-  ret = parcel->readFloat(&layer_.crop.left);
-  if (ret != OK) return ret;
-
-  ret = parcel->readFloat(&layer_.crop.top);
-  if (ret != OK) return ret;
-
-  ret = parcel->readFloat(&layer_.crop.right);
-  if (ret != OK) return ret;
-
-  ret = parcel->readFloat(&layer_.crop.bottom);
-  if (ret != OK) return ret;
-
-  ret = parcel->readInt32(reinterpret_cast<int32_t*>(&layer_.blend_mode));
-  if (ret != OK) return ret;
-
-  ret = parcel->readFloat(&layer_.alpha);
-  if (ret != OK) return ret;
-
-  ret = parcel->readUint32(&layer_.type);
-  if (ret != OK) return ret;
-
-  ret = parcel->readUint32(&layer_.app_id);
-  if (ret != OK) return ret;
-
-  ret = parcel->readUint32(&layer_.z_order);
-  if (ret != OK) return ret;
-
-  ret = parcel->readInt32(&layer_.cursor_x);
-  if (ret != OK) return ret;
-
-  ret = parcel->readInt32(&layer_.cursor_y);
-  if (ret != OK) return ret;
-
-  uint32_t color;
-  ret = parcel->readUint32(&color);
-  if (ret != OK) return ret;
-  layer_.color.r = color & 0xFF;
-  layer_.color.g = (color >> 8) & 0xFF;
-  layer_.color.b = (color >> 16) & 0xFF;
-  layer_.color.a = (color >> 24) & 0xFF;
-
-  ret = parcel->readInt32(&layer_.dataspace);
-  if (ret != OK) return ret;
-
-  ret = parcel->readInt32(&layer_.transform);
-  if (ret != OK) return ret;
-
-  uint32_t size;
-  ret = parcel->readUint32(&size);
-  if (ret != OK) return ret;
-
-  for(size_t i = 0; i < size; i++) {
-    hwc_rect_t rect;
-    ret = parcel->readInt32(&rect.left);
-    if (ret != OK) return ret;
-
-    ret = parcel->readInt32(&rect.top);
-    if (ret != OK) return ret;
-
-    ret = parcel->readInt32(&rect.right);
-    if (ret != OK) return ret;
-
-    ret = parcel->readInt32(&rect.bottom);
-    if (ret != OK) return ret;
-
-    layer_.visible_regions.push_back(rect);
-  }
-
-  ret = parcel->readUint32(&size);
-  if (ret != OK) return ret;
-
-  for(size_t i = 0; i < size; i++) {
-    hwc_rect_t rect;
-    ret = parcel->readInt32(&rect.left);
-    if (ret != OK) return ret;
-
-    ret = parcel->readInt32(&rect.top);
-    if (ret != OK) return ret;
-
-    ret = parcel->readInt32(&rect.right);
-    if (ret != OK) return ret;
-
-    ret = parcel->readInt32(&rect.bottom);
-    if (ret != OK) return ret;
-
-    layer_.damaged_regions.push_back(rect);
-  }
-
-  return OK;
-}
-
-}  // namespace dvr
-}  // namespace android
diff --git a/services/vr/hardware_composer/aidl/android/dvr/parcelable_composer_layer.h b/services/vr/hardware_composer/aidl/android/dvr/parcelable_composer_layer.h
deleted file mode 100644
index 6d2ac09..0000000
--- a/services/vr/hardware_composer/aidl/android/dvr/parcelable_composer_layer.h
+++ /dev/null
@@ -1,30 +0,0 @@
-#ifndef ANDROID_DVR_HARDWARE_COMPOSER_AIDL_ANDROID_DVR_PARCELABLE_COMPOSER_LAYER_H
-#define ANDROID_DVR_HARDWARE_COMPOSER_AIDL_ANDROID_DVR_PARCELABLE_COMPOSER_LAYER_H
-
-#include <binder/Parcelable.h>
-#include <impl/vr_hwc.h>
-
-#include <memory>
-
-namespace android {
-namespace dvr {
-
-class ParcelableComposerLayer : public Parcelable {
- public:
-  ParcelableComposerLayer();
-  explicit ParcelableComposerLayer(const ComposerView::ComposerLayer& layer);
-  ~ParcelableComposerLayer() override;
-
-  ComposerView::ComposerLayer layer() const { return layer_; }
-
-  status_t writeToParcel(Parcel* parcel) const override;
-  status_t readFromParcel(const Parcel* parcel) override;
-
- private:
-  ComposerView::ComposerLayer layer_;
-};
-
-}  // namespace dvr
-}  // namespace android
-
-#endif  // ANDROID_DVR_HARDWARE_COMPOSER_AIDL_ANDROID_DVR_PARCELABLE_COMPOSER_LAYER_H
diff --git a/services/vr/hardware_composer/aidl/android/dvr/parcelable_unique_fd.cpp b/services/vr/hardware_composer/aidl/android/dvr/parcelable_unique_fd.cpp
deleted file mode 100644
index 9486f3c..0000000
--- a/services/vr/hardware_composer/aidl/android/dvr/parcelable_unique_fd.cpp
+++ /dev/null
@@ -1,37 +0,0 @@
-#include "android/dvr/parcelable_unique_fd.h"
-
-#include <binder/Parcel.h>
-
-namespace android {
-namespace dvr {
-
-ParcelableUniqueFd::ParcelableUniqueFd() {}
-
-ParcelableUniqueFd::ParcelableUniqueFd(const base::unique_fd& fence)
-    : fence_(dup(fence.get())) {}
-
-ParcelableUniqueFd::~ParcelableUniqueFd() {}
-
-status_t ParcelableUniqueFd::writeToParcel(Parcel* parcel) const {
-  status_t ret = parcel->writeBool(fence_.get() >= 0);
-  if (ret != OK) return ret;
-
-  if (fence_.get() >= 0)
-    ret = parcel->writeUniqueFileDescriptor(fence_);
-
-  return ret;
-}
-
-status_t ParcelableUniqueFd::readFromParcel(const Parcel* parcel) {
-  bool has_fence = 0;
-  status_t ret = parcel->readBool(&has_fence);
-  if (ret != OK) return ret;
-
-  if (has_fence)
-    ret = parcel->readUniqueFileDescriptor(&fence_);
-
-  return ret;
-}
-
-}  // namespace dvr
-}  // namespace android
diff --git a/services/vr/hardware_composer/aidl/android/dvr/parcelable_unique_fd.h b/services/vr/hardware_composer/aidl/android/dvr/parcelable_unique_fd.h
deleted file mode 100644
index c4216f6..0000000
--- a/services/vr/hardware_composer/aidl/android/dvr/parcelable_unique_fd.h
+++ /dev/null
@@ -1,34 +0,0 @@
-#ifndef ANDROID_DVR_HARDWARE_COMPOSER_AIDL_ANDROID_DVR_PARCELABLE_UNIQUE_FD_H
-#define ANDROID_DVR_HARDWARE_COMPOSER_AIDL_ANDROID_DVR_PARCELABLE_UNIQUE_FD_H
-
-#include <android-base/unique_fd.h>
-#include <binder/Parcelable.h>
-
-namespace android {
-namespace dvr {
-
-// Provide a wrapper to serialized base::unique_fd. The wrapper also handles the
-// case where the FD is invalid (-1), unlike FileDescriptor which expects a
-// valid FD.
-class ParcelableUniqueFd : public Parcelable {
- public:
-  ParcelableUniqueFd();
-  explicit ParcelableUniqueFd(const base::unique_fd& fence);
-  ~ParcelableUniqueFd() override;
-
-  void set_fence(const base::unique_fd& fence) {
-    fence_.reset(dup(fence.get()));
-  }
-  base::unique_fd fence() const { return base::unique_fd(dup(fence_.get())); }
-
-  status_t writeToParcel(Parcel* parcel) const override;
-  status_t readFromParcel(const Parcel* parcel) override;
-
- private:
-  base::unique_fd fence_;
-};
-
-}  // namespace dvr
-}  // namespace android
-
-#endif  // ANDROID_DVR_HARDWARE_COMPOSER_AIDL_ANDROID_DVR_PARCELABLE_UNIQUE_FD_H
diff --git a/services/vr/hardware_composer/impl/vr_composer_client.cpp b/services/vr/hardware_composer/impl/vr_composer_client.cpp
deleted file mode 100644
index dd1603d..0000000
--- a/services/vr/hardware_composer/impl/vr_composer_client.cpp
+++ /dev/null
@@ -1,123 +0,0 @@
-/*
- * Copyright 2016 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/frameworks/vr/composer/2.0/IVrComposerClient.h>
-#include <hardware/gralloc.h>
-#include <hardware/gralloc1.h>
-#include <log/log.h>
-
-#include <memory>
-
-#include "impl/vr_hwc.h"
-#include "impl/vr_composer_client.h"
-
-namespace android {
-namespace dvr {
-
-using android::frameworks::vr::composer::V2_0::IVrComposerClient;
-
-VrComposerClient::VrComposerClient(dvr::VrHwc& hal)
-    : ComposerClient(&hal), mVrHal(hal) {
-  if (!init()) {
-      LOG_ALWAYS_FATAL("failed to initialize VrComposerClient");
-  }
-}
-
-VrComposerClient::~VrComposerClient() {}
-
-std::unique_ptr<ComposerCommandEngine>
-VrComposerClient::createCommandEngine() {
-  return std::make_unique<VrCommandEngine>(*this);
-}
-
-VrComposerClient::VrCommandEngine::VrCommandEngine(VrComposerClient& client)
-    : ComposerCommandEngine(client.mHal, client.mResources.get()),
-      mVrHal(client.mVrHal) {}
-
-VrComposerClient::VrCommandEngine::~VrCommandEngine() {}
-
-bool VrComposerClient::VrCommandEngine::executeCommand(
-    hardware::graphics::composer::V2_1::IComposerClient::Command command,
-    uint16_t length) {
-  IVrComposerClient::VrCommand vrCommand =
-      static_cast<IVrComposerClient::VrCommand>(command);
-  switch (vrCommand) {
-    case IVrComposerClient::VrCommand::SET_LAYER_INFO:
-      return executeSetLayerInfo(length);
-    case IVrComposerClient::VrCommand::SET_CLIENT_TARGET_METADATA:
-      return executeSetClientTargetMetadata(length);
-    case IVrComposerClient::VrCommand::SET_LAYER_BUFFER_METADATA:
-      return executeSetLayerBufferMetadata(length);
-    default:
-      return ComposerCommandEngine::executeCommand(command, length);
-  }
-}
-
-bool VrComposerClient::VrCommandEngine::executeSetLayerInfo(uint16_t length) {
-  if (length != 2) {
-    return false;
-  }
-
-  auto err = mVrHal.setLayerInfo(mCurrentDisplay, mCurrentLayer, read(), read());
-  if (err != Error::NONE) {
-    mWriter->setError(getCommandLoc(), err);
-  }
-
-  return true;
-}
-
-bool VrComposerClient::VrCommandEngine::executeSetClientTargetMetadata(
-    uint16_t length) {
-  if (length != 7)
-    return false;
-
-  auto err = mVrHal.setClientTargetMetadata(mCurrentDisplay, readBufferMetadata());
-  if (err != Error::NONE)
-    mWriter->setError(getCommandLoc(), err);
-
-  return true;
-}
-
-bool VrComposerClient::VrCommandEngine::executeSetLayerBufferMetadata(
-    uint16_t length) {
-  if (length != 7)
-    return false;
-
-  auto err = mVrHal.setLayerBufferMetadata(mCurrentDisplay, mCurrentLayer,
-                                           readBufferMetadata());
-  if (err != Error::NONE)
-    mWriter->setError(getCommandLoc(), err);
-
-  return true;
-}
-
-IVrComposerClient::BufferMetadata
-VrComposerClient::VrCommandEngine::readBufferMetadata() {
-  IVrComposerClient::BufferMetadata metadata = {
-      .width = read(),
-      .height = read(),
-      .stride = read(),
-      .layerCount = read(),
-      .format =
-          static_cast<android::hardware::graphics::common::V1_2::PixelFormat>(
-              readSigned()),
-      .usage = read64(),
-  };
-  return metadata;
-}
-
-}  // namespace dvr
-}  // namespace android
diff --git a/services/vr/hardware_composer/impl/vr_composer_client.h b/services/vr/hardware_composer/impl/vr_composer_client.h
deleted file mode 100644
index 1b2b5f4..0000000
--- a/services/vr/hardware_composer/impl/vr_composer_client.h
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright 2017 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 ANDROID_DVR_HARDWARE_COMPOSER_IMPL_VR_COMPOSER_CLIENT_H
-#define ANDROID_DVR_HARDWARE_COMPOSER_IMPL_VR_COMPOSER_CLIENT_H
-
-#include <android/frameworks/vr/composer/2.0/IVrComposerClient.h>
-#include <composer-command-buffer/2.3/ComposerCommandBuffer.h>
-#include <composer-hal/2.1/ComposerClient.h>
-#include <composer-hal/2.1/ComposerCommandEngine.h>
-#include <composer-hal/2.2/ComposerClient.h>
-#include <composer-hal/2.3/ComposerClient.h>
-
-namespace android {
-namespace dvr {
-
-class VrHwc;
-
-using hardware::graphics::composer::V2_1::hal::ComposerCommandEngine;
-using hardware::graphics::composer::V2_3::hal::ComposerHal;
-using hardware::graphics::composer::V2_3::hal::detail::ComposerClientImpl;
-
-using ComposerClient = ComposerClientImpl<IVrComposerClient, ComposerHal>;
-
-class VrComposerClient : public ComposerClient {
- public:
-  explicit VrComposerClient(android::dvr::VrHwc& hal);
-  virtual ~VrComposerClient();
-
- private:
-  class VrCommandEngine : public ComposerCommandEngine {
-   public:
-    explicit VrCommandEngine(VrComposerClient& client);
-    ~VrCommandEngine() override;
-
-    bool executeCommand(
-        hardware::graphics::composer::V2_1::IComposerClient::Command command,
-        uint16_t length) override;
-
-   private:
-    bool executeSetLayerInfo(uint16_t length);
-    bool executeSetClientTargetMetadata(uint16_t length);
-    bool executeSetLayerBufferMetadata(uint16_t length);
-
-    IVrComposerClient::BufferMetadata readBufferMetadata();
-
-    android::dvr::VrHwc& mVrHal;
-
-    VrCommandEngine(const VrCommandEngine&) = delete;
-    void operator=(const VrCommandEngine&) = delete;
-  };
-
-  VrComposerClient(const VrComposerClient&) = delete;
-  void operator=(const VrComposerClient&) = delete;
-
-  std::unique_ptr<ComposerCommandEngine> createCommandEngine() override;
-  dvr::VrHwc& mVrHal;
-};
-
-} // namespace dvr
-} // namespace android
-
-#endif  // ANDROID_DVR_HARDWARE_COMPOSER_IMPL_VR_COMPOSER_CLIENT_H
diff --git a/services/vr/hardware_composer/impl/vr_hwc.cpp b/services/vr/hardware_composer/impl/vr_hwc.cpp
deleted file mode 100644
index e530b16..0000000
--- a/services/vr/hardware_composer/impl/vr_hwc.cpp
+++ /dev/null
@@ -1,1178 +0,0 @@
-/*
- * Copyright 2016 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 "impl/vr_hwc.h"
-
-#include "android-base/stringprintf.h"
-#include <binder/IServiceManager.h>
-#include <cutils/properties.h>
-#include <private/dvr/display_client.h>
-#include <ui/Fence.h>
-#include <utils/Trace.h>
-
-#include <mutex>
-
-#include "vr_composer_client.h"
-
-using namespace android::hardware::graphics::common::V1_0;
-using namespace android::hardware::graphics::composer::V2_3;
-
-using android::base::StringPrintf;
-using android::hardware::hidl_handle;
-using android::hardware::hidl_string;
-using android::hardware::hidl_vec;
-using android::hardware::Return;
-using android::hardware::Void;
-
-namespace types = android::hardware::graphics::common;
-
-namespace android {
-namespace dvr {
-namespace {
-
-const Display kDefaultDisplayId = 1;
-const Config kDefaultConfigId = 1;
-
-sp<GraphicBuffer> CreateGraphicBuffer(
-    const native_handle_t* handle,
-    const IVrComposerClient::BufferMetadata& metadata) {
-   sp<GraphicBuffer> buffer = new GraphicBuffer(
-      handle, GraphicBuffer::CLONE_HANDLE, metadata.width, metadata.height,
-      static_cast<int32_t>(metadata.format), metadata.layerCount,
-      metadata.usage, metadata.stride);
-   if (buffer->initCheck() != OK) {
-     ALOGE("Failed to create graphic buffer");
-     return nullptr;
-   }
-
-   return buffer;
-}
-
-void GetPrimaryDisplaySize(int32_t* width, int32_t* height) {
-  *width = 1080;
-  *height = 1920;
-
-  int error = 0;
-  auto display_client = display::DisplayClient::Create(&error);
-  if (!display_client) {
-    ALOGE("Could not connect to display service : %s(%d)", strerror(error),
-          error);
-    return;
-  }
-
-  auto status = display_client->GetDisplayMetrics();
-  if (!status) {
-    ALOGE("Could not get display metrics from display service : %s(%d)",
-          status.GetErrorMessage().c_str(), status.error());
-    return;
-  }
-
-  *width = status.get().display_width;
-  *height = status.get().display_height;
-}
-
-}  // namespace
-
-HwcDisplay::HwcDisplay(int32_t width, int32_t height)
-    : width_(width), height_(height) {}
-
-HwcDisplay::~HwcDisplay() {}
-
-bool HwcDisplay::SetClientTarget(const native_handle_t* handle,
-                                 base::unique_fd fence) {
-  if (handle)
-    buffer_ = CreateGraphicBuffer(handle, buffer_metadata_);
-
-  fence_ = new Fence(fence.release());
-  return true;
-}
-
-void HwcDisplay::SetClientTargetMetadata(
-    const IVrComposerClient::BufferMetadata& metadata) {
-  buffer_metadata_ = metadata;
-}
-
-HwcLayer* HwcDisplay::CreateLayer() {
-  uint64_t layer_id = layer_ids_++;
-  layers_.push_back(HwcLayer(layer_id));
-  return &layers_.back();
-}
-
-HwcLayer* HwcDisplay::GetLayer(Layer id) {
-  for (size_t i = 0; i < layers_.size(); ++i)
-    if (layers_[i].info.id == id)
-      return &layers_[i];
-
-  return nullptr;
-}
-
-bool HwcDisplay::DestroyLayer(Layer id) {
-  for (auto it = layers_.begin(); it != layers_.end(); ++it) {
-    if (it->info.id == id) {
-      layers_.erase(it);
-      return true;
-    }
-  }
-
-  return false;
-}
-
-void HwcDisplay::GetChangedCompositionTypes(
-    std::vector<Layer>* layer_ids,
-    std::vector<IComposerClient::Composition>* types) {
-  std::sort(layers_.begin(), layers_.end(),
-            [](const auto& lhs, const auto& rhs) {
-              return lhs.info.z_order < rhs.info.z_order;
-            });
-
-  const size_t no_layer = std::numeric_limits<size_t>::max();
-  size_t first_client_layer = no_layer, last_client_layer = no_layer;
-  for (size_t i = 0; i < layers_.size(); ++i) {
-    switch (layers_[i].composition_type) {
-      case IComposerClient::Composition::SOLID_COLOR:
-      case IComposerClient::Composition::CURSOR:
-      case IComposerClient::Composition::SIDEBAND:
-        if (first_client_layer == no_layer)
-          first_client_layer = i;
-
-        last_client_layer = i;
-        break;
-      default:
-        break;
-    }
-  }
-
-  for (size_t i = 0; i < layers_.size(); ++i) {
-    if (i >= first_client_layer && i <= last_client_layer) {
-      if (layers_[i].composition_type != IComposerClient::Composition::CLIENT) {
-        layer_ids->push_back(layers_[i].info.id);
-        types->push_back(IComposerClient::Composition::CLIENT);
-        layers_[i].composition_type = IComposerClient::Composition::CLIENT;
-      }
-
-      continue;
-    }
-
-    if (layers_[i].composition_type != IComposerClient::Composition::DEVICE) {
-      layer_ids->push_back(layers_[i].info.id);
-      types->push_back(IComposerClient::Composition::DEVICE);
-      layers_[i].composition_type = IComposerClient::Composition::DEVICE;
-    }
-  }
-}
-
-Error HwcDisplay::GetFrame(
-    std::vector<ComposerView::ComposerLayer>* out_frames) {
-  bool queued_client_target = false;
-  std::vector<ComposerView::ComposerLayer> frame;
-  for (const auto& layer : layers_) {
-    if (layer.composition_type == IComposerClient::Composition::CLIENT) {
-      if (queued_client_target)
-        continue;
-
-      if (!buffer_.get()) {
-        ALOGE("Client composition requested but no client target buffer");
-        return Error::BAD_LAYER;
-      }
-
-      ComposerView::ComposerLayer client_target_layer = {
-          .buffer = buffer_,
-          .fence = fence_.get() ? fence_ : new Fence(-1),
-          .display_frame = {0, 0, static_cast<int32_t>(buffer_->getWidth()),
-            static_cast<int32_t>(buffer_->getHeight())},
-          .crop = {0.0f, 0.0f, static_cast<float>(buffer_->getWidth()),
-            static_cast<float>(buffer_->getHeight())},
-          .blend_mode = IComposerClient::BlendMode::NONE,
-      };
-
-      frame.push_back(client_target_layer);
-      queued_client_target = true;
-    } else {
-      if (!layer.info.buffer.get() || !layer.info.fence.get()) {
-        ALOGV("Layer requested without valid buffer");
-        continue;
-      }
-
-      frame.push_back(layer.info);
-    }
-  }
-
-  out_frames->swap(frame);
-  return Error::NONE;
-}
-
-std::vector<Layer> HwcDisplay::UpdateLastFrameAndGetLastFrameLayers() {
-  std::vector<Layer> last_frame_layers;
-  last_frame_layers.swap(last_frame_layers_ids_);
-
-  for (const auto& layer : layers_)
-    last_frame_layers_ids_.push_back(layer.info.id);
-
-  return last_frame_layers;
-}
-
-void HwcDisplay::SetColorTransform(const float* matrix, int32_t hint) {
-  color_transform_hint_ = hint;
-  if (matrix)
-    memcpy(color_transform_, matrix, sizeof(color_transform_));
-}
-
-void HwcDisplay::dumpDebugInfo(std::string* result) const {
-  if (!result) {
-    return;
-  }
-  *result += StringPrintf("HwcDisplay: width: %d, height: %d, layers size: %zu, colormode: %d\
-      , config: %d\n", width_, height_, layers_.size(), color_mode_, active_config_);
-  *result += StringPrintf("HwcDisplay buffer metadata: width: %d, height: %d, stride: %d,\
-      layerCount: %d, pixelFormat: %d\n", buffer_metadata_.width, buffer_metadata_.height,
-      buffer_metadata_.stride, buffer_metadata_.layerCount, buffer_metadata_.format);
-  for (const auto& layer : layers_) {
-    layer.dumpDebugInfo(result);
-  }
-}
-
-////////////////////////////////////////////////////////////////////////////////
-// VrHwcClient
-
-VrHwc::VrHwc() {
-  vsync_callback_ = new VsyncCallback;
-}
-
-VrHwc::~VrHwc() {
-  vsync_callback_->SetEventCallback(nullptr);
-}
-
-bool VrHwc::hasCapability(hwc2_capability_t /* capability */) { return false; }
-
-void VrHwc::registerEventCallback(EventCallback* callback) {
-  std::unique_lock<std::mutex> lock(mutex_);
-  event_callback_ = callback;
-  int32_t width, height;
-  GetPrimaryDisplaySize(&width, &height);
-  // Create the primary display late to avoid initialization issues between
-  // VR HWC and SurfaceFlinger.
-  displays_[kDefaultDisplayId].reset(new HwcDisplay(width, height));
-
-  // Surface flinger will make calls back into vr_hwc when it receives the
-  // onHotplug() call, so it's important to release mutex_ here.
-  lock.unlock();
-  event_callback_->onHotplug(kDefaultDisplayId,
-                             hardware::graphics::composer::V2_1::
-                                 IComposerCallback::Connection::CONNECTED);
-  lock.lock();
-  UpdateVsyncCallbackEnabledLocked();
-}
-
-void VrHwc::unregisterEventCallback() {
-  std::lock_guard<std::mutex> guard(mutex_);
-  event_callback_ = nullptr;
-  UpdateVsyncCallbackEnabledLocked();
-}
-
-uint32_t VrHwc::getMaxVirtualDisplayCount() { return 1; }
-
-Error VrHwc::destroyVirtualDisplay(Display display) {
-  std::lock_guard<std::mutex> guard(mutex_);
-  if (display == kDefaultDisplayId || displays_.erase(display) == 0)
-    return Error::BAD_DISPLAY;
-  ComposerView::Frame frame;
-  frame.display_id = display;
-  frame.removed = true;
-  if (observer_)
-    observer_->OnNewFrame(frame);
-  return Error::NONE;
-}
-
-Error VrHwc::createLayer(Display display, Layer* outLayer) {
-  std::lock_guard<std::mutex> guard(mutex_);
-  auto display_ptr = FindDisplay(display);
-  if (!display_ptr)
-    return Error::BAD_DISPLAY;
-
-  HwcLayer* layer = display_ptr->CreateLayer();
-  *outLayer = layer->info.id;
-  return Error::NONE;
-}
-
-Error VrHwc::destroyLayer(Display display, Layer layer) {
-  std::lock_guard<std::mutex> guard(mutex_);
-  auto display_ptr = FindDisplay(display);
-  if (!display_ptr) {
-    return Error::BAD_DISPLAY;
-  }
-
-  return display_ptr->DestroyLayer(layer) ? Error::NONE : Error::BAD_LAYER;
-}
-
-Error VrHwc::getActiveConfig(Display display, Config* outConfig) {
-  std::lock_guard<std::mutex> guard(mutex_);
-  if (!FindDisplay(display))
-    return Error::BAD_DISPLAY;
-  *outConfig = kDefaultConfigId;
-  return Error::NONE;
-}
-
-Error VrHwc::getDisplayAttribute(Display display, Config config,
-                                 IComposerClient::Attribute attribute,
-                                 int32_t* outValue) {
-  std::lock_guard<std::mutex> guard(mutex_);
-  auto display_ptr = FindDisplay(display);
-  if (!display_ptr) {
-    return Error::BAD_DISPLAY;
-  }
-  if (config != kDefaultConfigId) {
-    return Error::BAD_CONFIG;
-  }
-
-  switch (attribute) {
-    case IComposerClient::Attribute::WIDTH:
-      *outValue = display_ptr->width();
-      break;
-    case IComposerClient::Attribute::HEIGHT:
-      *outValue = display_ptr->height();
-      break;
-    case IComposerClient::Attribute::VSYNC_PERIOD:
-      {
-        int error = 0;
-        auto display_client = display::DisplayClient::Create(&error);
-        if (!display_client) {
-          ALOGE("Could not connect to display service : %s(%d)",
-                strerror(error), error);
-          // Return a default value of 30 fps
-          *outValue = 1000 * 1000 * 1000 / 30;
-        } else {
-          auto metrics = display_client->GetDisplayMetrics();
-          *outValue = metrics.get().vsync_period_ns;
-        }
-      }
-      break;
-    case IComposerClient::Attribute::DPI_X:
-    case IComposerClient::Attribute::DPI_Y:
-      {
-        constexpr int32_t kDefaultDPI = 300;
-        int32_t dpi = property_get_int32("ro.vr.hwc.dpi", kDefaultDPI);
-        if (dpi <= 0) {
-          dpi = kDefaultDPI;
-        }
-        *outValue = 1000 * dpi;
-      }
-      break;
-    default:
-      return Error::BAD_PARAMETER;
-  }
-
-  return Error::NONE;
-}
-
-Error VrHwc::getDisplayConfigs(Display display, hidl_vec<Config>* outConfigs) {
-  std::lock_guard<std::mutex> guard(mutex_);
-  if (!FindDisplay(display))
-    return Error::BAD_DISPLAY;
-  std::vector<Config> configs(1, kDefaultConfigId);
-  *outConfigs = hidl_vec<Config>(configs);
-  return Error::NONE;
-}
-
-Error VrHwc::getDisplayName(Display /* display */, hidl_string* outName) {
-  *outName = hidl_string();
-  return Error::NONE;
-}
-
-Error VrHwc::getDisplayType(Display display,
-                            IComposerClient::DisplayType* outType) {
-  std::lock_guard<std::mutex> guard(mutex_);
-  auto display_ptr = FindDisplay(display);
-  if (!display_ptr) {
-    *outType = IComposerClient::DisplayType::INVALID;
-    return Error::BAD_DISPLAY;
-  }
-
-  if (display == kDefaultDisplayId)
-    *outType = IComposerClient::DisplayType::PHYSICAL;
-  else
-    *outType = IComposerClient::DisplayType::VIRTUAL;
-
-  return Error::NONE;
-}
-
-Error VrHwc::getDozeSupport(Display display, bool* outSupport) {
-  *outSupport = false;
-  std::lock_guard<std::mutex> guard(mutex_);
-  if (!FindDisplay(display))
-    return Error::BAD_DISPLAY;
-  return Error::NONE;
-}
-
-Error VrHwc::setActiveConfig(Display display, Config config) {
-  std::lock_guard<std::mutex> guard(mutex_);
-  auto display_ptr = FindDisplay(display);
-  if (!display_ptr)
-    return Error::BAD_DISPLAY;
-  if (config != kDefaultConfigId)
-    return Error::BAD_CONFIG;
-
-  display_ptr->set_active_config(config);
-  return Error::NONE;
-}
-
-Error VrHwc::setVsyncEnabled(Display display, IComposerClient::Vsync enabled) {
-  std::lock_guard<std::mutex> guard(mutex_);
-  auto display_ptr = FindDisplay(display);
-  if (!display_ptr)
-    return Error::BAD_DISPLAY;
-
-  if (enabled != IComposerClient::Vsync::ENABLE &&
-      enabled != IComposerClient::Vsync::DISABLE) {
-    return Error::BAD_PARAMETER;
-  }
-
-  Error set_vsync_result = Error::NONE;
-  if (display == kDefaultDisplayId) {
-    sp<IVsyncService> vsync_service = interface_cast<IVsyncService>(
-        defaultServiceManager()->getService(
-            String16(IVsyncService::GetServiceName())));
-    if (vsync_service == nullptr) {
-      ALOGE("Failed to get vsync service");
-      return Error::NO_RESOURCES;
-    }
-
-    if (enabled == IComposerClient::Vsync::ENABLE) {
-      ALOGI("Enable vsync");
-      display_ptr->set_vsync_enabled(true);
-      status_t result = vsync_service->registerCallback(vsync_callback_);
-      if (result != OK) {
-        ALOGE("%s service registerCallback() failed: %s (%d)",
-            IVsyncService::GetServiceName(), strerror(-result), result);
-        set_vsync_result = Error::NO_RESOURCES;
-      }
-    } else if (enabled == IComposerClient::Vsync::DISABLE) {
-      ALOGI("Disable vsync");
-      display_ptr->set_vsync_enabled(false);
-      status_t result = vsync_service->unregisterCallback(vsync_callback_);
-      if (result != OK) {
-        ALOGE("%s service unregisterCallback() failed: %s (%d)",
-            IVsyncService::GetServiceName(), strerror(-result), result);
-        set_vsync_result = Error::NO_RESOURCES;
-      }
-    }
-
-    UpdateVsyncCallbackEnabledLocked();
-  }
-
-  return set_vsync_result;
-}
-
-Error VrHwc::setColorTransform(Display display, const float* matrix,
-                               int32_t hint) {
-  std::lock_guard<std::mutex> guard(mutex_);
-  auto display_ptr = FindDisplay(display);
-  if (!display_ptr)
-    return Error::BAD_DISPLAY;
-
-  display_ptr->SetColorTransform(matrix, hint);
-  return Error::NONE;
-}
-
-Error VrHwc::setClientTarget(Display display, buffer_handle_t target,
-                             int32_t acquireFence, int32_t /* dataspace */,
-                             const std::vector<hwc_rect_t>& /* damage */) {
-  base::unique_fd fence(acquireFence);
-  std::lock_guard<std::mutex> guard(mutex_);
-  auto display_ptr = FindDisplay(display);
-  if (!display_ptr)
-    return Error::BAD_DISPLAY;
-
-  if (target == nullptr)
-    return Error::NONE;
-
-  if (!display_ptr->SetClientTarget(target, std::move(fence)))
-    return Error::BAD_PARAMETER;
-
-  return Error::NONE;
-}
-
-Error VrHwc::setOutputBuffer(Display display, buffer_handle_t /* buffer */,
-                             int32_t releaseFence) {
-  base::unique_fd fence(releaseFence);
-  std::lock_guard<std::mutex> guard(mutex_);
-  auto display_ptr = FindDisplay(display);
-  if (!display_ptr)
-    return Error::BAD_DISPLAY;
-
-  // TODO(dnicoara): Is it necessary to do anything here?
-  return Error::NONE;
-}
-
-Error VrHwc::validateDisplay(
-    Display display, std::vector<Layer>* outChangedLayers,
-    std::vector<IComposerClient::Composition>* outCompositionTypes,
-    uint32_t* /* outDisplayRequestMask */,
-    std::vector<Layer>* /* outRequestedLayers */,
-    std::vector<uint32_t>* /* outRequestMasks */) {
-  std::lock_guard<std::mutex> guard(mutex_);
-  auto display_ptr = FindDisplay(display);
-  if (!display_ptr)
-    return Error::BAD_DISPLAY;
-
-  display_ptr->GetChangedCompositionTypes(outChangedLayers,
-                                          outCompositionTypes);
-  return Error::NONE;
-}
-
-Error VrHwc::acceptDisplayChanges(Display /* display */) { return Error::NONE; }
-
-Error VrHwc::presentDisplay(Display display, int32_t* outPresentFence,
-                            std::vector<Layer>* outLayers,
-                            std::vector<int32_t>* outReleaseFences) {
-  *outPresentFence = -1;
-  outLayers->clear();
-  outReleaseFences->clear();
-
-  std::lock_guard<std::mutex> guard(mutex_);
-  auto display_ptr = FindDisplay(display);
-
-  if (!display_ptr)
-    return Error::BAD_DISPLAY;
-
-  ComposerView::Frame frame;
-  std::vector<Layer> last_frame_layers;
-  Error status = display_ptr->GetFrame(&frame.layers);
-  frame.display_id = display;
-  frame.display_width = display_ptr->width();
-  frame.display_height = display_ptr->height();
-  frame.active_config = display_ptr->active_config();
-  frame.power_mode = display_ptr->power_mode();
-  frame.vsync_enabled = display_ptr->vsync_enabled() ?
-      IComposerClient::Vsync::ENABLE : IComposerClient::Vsync::DISABLE;
-  frame.color_transform_hint = display_ptr->color_transform_hint();
-  frame.color_mode = display_ptr->color_mode();
-  memcpy(frame.color_transform, display_ptr->color_transform(),
-         sizeof(frame.color_transform));
-  if (status != Error::NONE)
-    return status;
-
-  last_frame_layers = display_ptr->UpdateLastFrameAndGetLastFrameLayers();
-
-  base::unique_fd fence;
-  if (observer_)
-    fence = observer_->OnNewFrame(frame);
-
-  if (fence.get() < 0)
-    return Error::NONE;
-
-  *outPresentFence = dup(fence.get());
-  outLayers->swap(last_frame_layers);
-  for (size_t i = 0; i < outLayers->size(); ++i)
-    outReleaseFences->push_back(dup(fence.get()));
-
-  return Error::NONE;
-}
-
-Error VrHwc::setLayerCursorPosition(Display display, Layer layer, int32_t x,
-                                    int32_t y) {
-  std::lock_guard<std::mutex> guard(mutex_);
-  auto display_ptr = FindDisplay(display);
-  if (!display_ptr)
-    return Error::BAD_DISPLAY;
-
-  HwcLayer* hwc_layer = display_ptr->GetLayer(layer);
-  if (!hwc_layer)
-    return Error::BAD_LAYER;
-
-  hwc_layer->info.cursor_x = x;
-  hwc_layer->info.cursor_y = y;
-  return Error::NONE;
-}
-
-Error VrHwc::setLayerBuffer(Display display, Layer layer,
-                            buffer_handle_t buffer, int32_t acquireFence) {
-  base::unique_fd fence(acquireFence);
-  std::lock_guard<std::mutex> guard(mutex_);
-  auto display_ptr = FindDisplay(display);
-  if (!display_ptr)
-    return Error::BAD_DISPLAY;
-
-  HwcLayer* hwc_layer = display_ptr->GetLayer(layer);
-  if (!hwc_layer)
-    return Error::BAD_LAYER;
-
-  hwc_layer->info.buffer = CreateGraphicBuffer(
-      buffer, hwc_layer->buffer_metadata);
-  hwc_layer->info.fence = new Fence(fence.release());
-
-  return Error::NONE;
-}
-
-Error VrHwc::setLayerSurfaceDamage(Display display, Layer layer,
-                                   const std::vector<hwc_rect_t>& damage) {
-  std::lock_guard<std::mutex> guard(mutex_);
-  auto display_ptr = FindDisplay(display);
-  if (!display_ptr)
-    return Error::BAD_DISPLAY;
-
-  HwcLayer* hwc_layer = display_ptr->GetLayer(layer);
-  if (!hwc_layer)
-    return Error::BAD_LAYER;
-
-  hwc_layer->info.damaged_regions = damage;
-  return Error::NONE;
-}
-
-Error VrHwc::setLayerBlendMode(Display display, Layer layer, int32_t mode) {
-  std::lock_guard<std::mutex> guard(mutex_);
-  auto display_ptr = FindDisplay(display);
-  if (!display_ptr)
-    return Error::BAD_DISPLAY;
-
-  HwcLayer* hwc_layer = display_ptr->GetLayer(layer);
-  if (!hwc_layer)
-    return Error::BAD_LAYER;
-
-  hwc_layer->info.blend_mode =
-      static_cast<ComposerView::ComposerLayer::BlendMode>(mode);
-
-  return Error::NONE;
-}
-
-Error VrHwc::setLayerColor(Display display, Layer layer,
-                           IComposerClient::Color color) {
-  std::lock_guard<std::mutex> guard(mutex_);
-  auto display_ptr = FindDisplay(display);
-  if (!display_ptr)
-    return Error::BAD_DISPLAY;
-
-  HwcLayer* hwc_layer = display_ptr->GetLayer(layer);
-  if (!hwc_layer)
-    return Error::BAD_LAYER;
-
-  hwc_layer->info.color = color;
-  return Error::NONE;
-}
-
-Error VrHwc::setLayerCompositionType(Display display, Layer layer,
-                                     int32_t type) {
-  std::lock_guard<std::mutex> guard(mutex_);
-  auto display_ptr = FindDisplay(display);
-  if (!display_ptr)
-    return Error::BAD_DISPLAY;
-
-  HwcLayer* hwc_layer = display_ptr->GetLayer(layer);
-  if (!hwc_layer)
-    return Error::BAD_LAYER;
-
-  hwc_layer->composition_type = static_cast<HwcLayer::Composition>(type);
-
-  return Error::NONE;
-}
-
-Error VrHwc::setLayerDataspace(Display display, Layer layer,
-                               int32_t dataspace) {
-  std::lock_guard<std::mutex> guard(mutex_);
-  auto display_ptr = FindDisplay(display);
-  if (!display_ptr)
-    return Error::BAD_DISPLAY;
-
-  HwcLayer* hwc_layer = display_ptr->GetLayer(layer);
-  if (!hwc_layer)
-    return Error::BAD_LAYER;
-
-  hwc_layer->info.dataspace = dataspace;
-  return Error::NONE;
-}
-
-Error VrHwc::setLayerDisplayFrame(Display display, Layer layer,
-                                  const hwc_rect_t& frame) {
-  std::lock_guard<std::mutex> guard(mutex_);
-  auto display_ptr = FindDisplay(display);
-  if (!display_ptr)
-    return Error::BAD_DISPLAY;
-
-  HwcLayer* hwc_layer = display_ptr->GetLayer(layer);
-  if (!hwc_layer)
-    return Error::BAD_LAYER;
-
-  hwc_layer->info.display_frame =
-      {frame.left, frame.top, frame.right, frame.bottom};
-
-  return Error::NONE;
-}
-
-Error VrHwc::setLayerPlaneAlpha(Display display, Layer layer, float alpha) {
-  std::lock_guard<std::mutex> guard(mutex_);
-  auto display_ptr = FindDisplay(display);
-  if (!display_ptr)
-    return Error::BAD_DISPLAY;
-
-  HwcLayer* hwc_layer = display_ptr->GetLayer(layer);
-  if (!hwc_layer)
-    return Error::BAD_LAYER;
-
-  hwc_layer->info.alpha = alpha;
-
-  return Error::NONE;
-}
-
-Error VrHwc::setLayerSidebandStream(Display display, Layer /* layer */,
-                                    buffer_handle_t /* stream */) {
-  std::lock_guard<std::mutex> guard(mutex_);
-  if (!FindDisplay(display))
-    return Error::BAD_DISPLAY;
-  return Error::NONE;
-}
-
-Error VrHwc::setLayerSourceCrop(Display display, Layer layer,
-                                const hwc_frect_t& crop) {
-  std::lock_guard<std::mutex> guard(mutex_);
-  auto display_ptr = FindDisplay(display);
-  if (!display_ptr)
-    return Error::BAD_DISPLAY;
-
-  HwcLayer* hwc_layer = display_ptr->GetLayer(layer);
-  if (!hwc_layer)
-    return Error::BAD_LAYER;
-
-  hwc_layer->info.crop = {crop.left, crop.top, crop.right, crop.bottom};
-
-  return Error::NONE;
-}
-
-Error VrHwc::setLayerTransform(Display display, Layer layer,
-                               int32_t transform) {
-  std::lock_guard<std::mutex> guard(mutex_);
-  auto display_ptr = FindDisplay(display);
-  if (!display_ptr)
-    return Error::BAD_DISPLAY;
-
-  HwcLayer* hwc_layer = display_ptr->GetLayer(layer);
-  if (!hwc_layer)
-    return Error::BAD_LAYER;
-
-  hwc_layer->info.transform = transform;
-  return Error::NONE;
-}
-
-Error VrHwc::setLayerVisibleRegion(Display display, Layer layer,
-                                   const std::vector<hwc_rect_t>& visible) {
-  std::lock_guard<std::mutex> guard(mutex_);
-  auto display_ptr = FindDisplay(display);
-  if (!display_ptr)
-    return Error::BAD_DISPLAY;
-
-  HwcLayer* hwc_layer = display_ptr->GetLayer(layer);
-  if (!hwc_layer)
-    return Error::BAD_LAYER;
-
-  hwc_layer->info.visible_regions = visible;
-  return Error::NONE;
-}
-
-Error VrHwc::setLayerZOrder(Display display, Layer layer, uint32_t z) {
-  std::lock_guard<std::mutex> guard(mutex_);
-  auto display_ptr = FindDisplay(display);
-  if (!display_ptr)
-    return Error::BAD_DISPLAY;
-
-  HwcLayer* hwc_layer = display_ptr->GetLayer(layer);
-  if (!hwc_layer)
-    return Error::BAD_LAYER;
-
-  hwc_layer->info.z_order = z;
-
-  return Error::NONE;
-}
-
-Error VrHwc::setLayerInfo(Display display, Layer layer, uint32_t type,
-                          uint32_t appId) {
-  std::lock_guard<std::mutex> guard(mutex_);
-  auto display_ptr = FindDisplay(display);
-  if (!display_ptr)
-    return Error::BAD_DISPLAY;
-
-  HwcLayer* hwc_layer = display_ptr->GetLayer(layer);
-  if (!hwc_layer)
-    return Error::BAD_LAYER;
-
-  hwc_layer->info.type = type;
-  hwc_layer->info.app_id = appId;
-
-  return Error::NONE;
-}
-
-Error VrHwc::setClientTargetMetadata(
-    Display display, const IVrComposerClient::BufferMetadata& metadata) {
-  std::lock_guard<std::mutex> guard(mutex_);
-  auto display_ptr = FindDisplay(display);
-  if (!display_ptr)
-    return Error::BAD_DISPLAY;
-
-  display_ptr->SetClientTargetMetadata(metadata);
-
-  return Error::NONE;
-}
-
-Error VrHwc::setLayerBufferMetadata(
-    Display display, Layer layer,
-    const IVrComposerClient::BufferMetadata& metadata) {
-  std::lock_guard<std::mutex> guard(mutex_);
-  auto display_ptr = FindDisplay(display);
-  if (!display_ptr)
-    return Error::BAD_DISPLAY;
-
-  HwcLayer* hwc_layer = display_ptr->GetLayer(layer);
-  if (!hwc_layer)
-    return Error::BAD_LAYER;
-
-  hwc_layer->buffer_metadata = metadata;
-
-  return Error::NONE;
-}
-
-Return<void> VrHwc::getCapabilities(getCapabilities_cb hidl_cb) {
-  hidl_cb(hidl_vec<Capability>());
-  return Void();
-}
-
-Return<void> VrHwc::dumpDebugInfo(dumpDebugInfo_cb hidl_cb) {
-  std::string result;
-
-  {
-    std::lock_guard<std::mutex> guard(mutex_);
-    result = "\nVrHwc states:\n";
-    for (const auto& pair : displays_) {
-      result += StringPrintf("Display id: %lu\n", (unsigned long)pair.first);
-      pair.second->dumpDebugInfo(&result);
-    }
-    result += "\n";
-  }
-
-  hidl_cb(hidl_string(result));
-  return Void();
-}
-
-Return<void> VrHwc::createClient(createClient_cb hidl_cb) {
-  std::lock_guard<std::mutex> guard(mutex_);
-
-  Error status = Error::NONE;
-  sp<VrComposerClient> client;
-  if (!client_.promote().get()) {
-    client = new VrComposerClient(*this);
-  } else {
-    ALOGE("Already have a client");
-    status = Error::NO_RESOURCES;
-  }
-
-  client_ = client;
-  hidl_cb(status, client);
-  return Void();
-}
-
-Return<void> VrHwc::createClient_2_3(IComposer::createClient_2_3_cb hidl_cb) {
-  std::lock_guard<std::mutex> guard(mutex_);
-
-  Error status = Error::NONE;
-  sp<VrComposerClient> client;
-  if (!client_.promote().get()) {
-    client = new VrComposerClient(*this);
-  } else {
-    ALOGE("Already have a client");
-    status = Error::NO_RESOURCES;
-  }
-
-  client_ = client;
-  hidl_cb(status, client);
-  return Void();
-}
-
-void VrHwc::ForceDisplaysRefresh() {
-  std::lock_guard<std::mutex> guard(mutex_);
-  if (event_callback_ != nullptr) {
-    for (const auto& pair : displays_)
-      event_callback_->onRefresh(pair.first);
-  }
-}
-
-void VrHwc::RegisterObserver(Observer* observer) {
-  std::lock_guard<std::mutex> guard(mutex_);
-  if (observer_)
-    ALOGE("Overwriting observer");
-  else
-    observer_ = observer;
-}
-
-void VrHwc::UnregisterObserver(Observer* observer) {
-  std::lock_guard<std::mutex> guard(mutex_);
-  if (observer != observer_)
-    ALOGE("Trying to unregister unknown observer");
-  else
-    observer_ = nullptr;
-}
-
-HwcDisplay* VrHwc::FindDisplay(Display display) {
-  auto iter = displays_.find(display);
-  return iter == displays_.end() ? nullptr : iter->second.get();
-}
-
-void VrHwc::UpdateVsyncCallbackEnabledLocked() {
-  auto primary_display = FindDisplay(kDefaultDisplayId);
-  LOG_ALWAYS_FATAL_IF(event_callback_ != nullptr && primary_display == nullptr,
-      "Should have created the primary display by now");
-  bool send_vsync =
-      event_callback_ != nullptr && primary_display->vsync_enabled();
-  vsync_callback_->SetEventCallback(send_vsync ? event_callback_ : nullptr);
-}
-
-Return<void> VrHwc::debug(const hidl_handle& fd,
-                          const hidl_vec<hidl_string>& args) {
-  std::string result;
-
-  {
-    std::lock_guard<std::mutex> guard(mutex_);
-    for (const auto& pair : displays_) {
-      result += StringPrintf("Display id: %d\n", static_cast<int>(pair.first));
-      pair.second->dumpDebugInfo(&result);
-    }
-    result += "\n";
-  }
-
-  FILE* out = fdopen(dup(fd->data[0]), "w");
-  fprintf(out, "%s", result.c_str());
-  fclose(out);
-
-  return Void();
-}
-
-void HwcLayer::dumpDebugInfo(std::string* result) const {
-  if (!result) {
-    return;
-  }
-  *result += StringPrintf("Layer: composition_type: %d, type: %d, app_id: %d, z_order: %d,\
-      cursor_x: %d, cursor_y: %d, color(rgba): (%d,%d,%d,%d), dataspace: %d, transform: %d,\
-      display_frame(LTRB): (%d,%d,%d,%d), crop(LTRB): (%.1f,%.1f,%.1f,%.1f), blend_mode: %d\n",
-      composition_type, info.type, info.app_id, info.z_order, info.cursor_x, info.cursor_y,
-      info.color.r, info.color.g, info.color.b, info.color.a, info.dataspace, info.transform,
-      info.display_frame.left, info.display_frame.top, info.display_frame.right,
-      info.display_frame.bottom, info.crop.left, info.crop.top, info.crop.right,
-      info.crop.bottom, info.blend_mode);
-  *result += StringPrintf("Layer buffer metadata: width: %d, height: %d, stride: %d, layerCount: %d\
-      , pixelFormat: %d\n", buffer_metadata.width, buffer_metadata.height, buffer_metadata.stride,
-      buffer_metadata.layerCount, buffer_metadata.format);
-}
-
-status_t VrHwc::VsyncCallback::onVsync(int64_t vsync_timestamp) {
-  ATRACE_NAME("vr_hwc onVsync");
-  std::lock_guard<std::mutex> guard(mutex_);
-  if (callback_ != nullptr)
-    callback_->onVsync(kDefaultDisplayId, vsync_timestamp);
-  return OK;
-}
-
-void VrHwc::VsyncCallback::SetEventCallback(EventCallback* callback) {
-  std::lock_guard<std::mutex> guard(mutex_);
-  callback_ = callback;
-}
-
-// composer::V2_2::ComposerHal
-Error VrHwc::setReadbackBuffer(Display display,
-                               const native_handle_t* bufferHandle,
-                               android::base::unique_fd fenceFd) {
-  return Error::NONE;
-}
-
-Error VrHwc::getReadbackBufferFence(Display display,
-                                    android::base::unique_fd* outFenceFd) {
-  return Error::NONE;
-}
-
-Error VrHwc::createVirtualDisplay_2_2(uint32_t width, uint32_t height,
-                                      types::V1_1::PixelFormat* format,
-                                      Display* outDisplay) {
-  *format = types::V1_1::PixelFormat::RGBA_8888;
-  *outDisplay = display_count_;
-  displays_[display_count_].reset(new HwcDisplay(width, height));
-  display_count_++;
-  return Error::NONE;
-}
-
-Error VrHwc::setPowerMode_2_2(Display display,
-                              IComposerClient::PowerMode mode) {
-  bool dozeSupported = false;
-
-  Error dozeSupportError = getDozeSupport(display, &dozeSupported);
-
-  if (dozeSupportError != Error::NONE)
-    return dozeSupportError;
-
-  std::lock_guard<std::mutex> guard(mutex_);
-  auto display_ptr = FindDisplay(display);
-  if (!display_ptr)
-    return Error::BAD_DISPLAY;
-
-  if (mode < IComposerClient::PowerMode::OFF ||
-      mode > IComposerClient::PowerMode::DOZE_SUSPEND) {
-    return Error::BAD_PARAMETER;
-  }
-
-  if (!dozeSupported && (mode == IComposerClient::PowerMode::DOZE ||
-                         mode == IComposerClient::PowerMode::DOZE_SUSPEND)) {
-    return Error::UNSUPPORTED;
-  }
-
-  display_ptr->set_power_mode(mode);
-  return Error::NONE;
-}
-
-Error VrHwc::setLayerFloatColor(Display display, Layer layer,
-                                IComposerClient::FloatColor color) {
-  return Error::NONE;
-}
-
-Error VrHwc::getRenderIntents(Display display, types::V1_1::ColorMode mode,
-                              std::vector<RenderIntent>* outIntents) {
-  return Error::NONE;
-}
-
-std::array<float, 16> VrHwc::getDataspaceSaturationMatrix(
-    types::V1_1::Dataspace dataspace) {
-  return {};
-}
-
-// composer::V2_3::ComposerHal
-Error VrHwc::getHdrCapabilities_2_3(Display /*display*/,
-                                    hidl_vec<Hdr>* /*outTypes*/,
-                                    float* outMaxLuminance,
-                                    float* outMaxAverageLuminance,
-                                    float* outMinLuminance) {
-  *outMaxLuminance = 0;
-  *outMaxAverageLuminance = 0;
-  *outMinLuminance = 0;
-  return Error::NONE;
-}
-
-Error VrHwc::setLayerPerFrameMetadata_2_3(
-    Display display, Layer layer,
-    const std::vector<IComposerClient::PerFrameMetadata>& metadata) {
-  return Error::NONE;
-}
-
-Error VrHwc::getPerFrameMetadataKeys_2_3(
-    Display display,
-    std::vector<IComposerClient::PerFrameMetadataKey>* outKeys) {
-  return Error::NONE;
-}
-
-Error VrHwc::setColorMode_2_3(Display display, ColorMode mode,
-                              RenderIntent intent) {
-  std::lock_guard<std::mutex> guard(mutex_);
-  auto display_ptr = FindDisplay(display);
-  if (!display_ptr)
-    return Error::BAD_DISPLAY;
-
-  if (mode < ColorMode::NATIVE || mode > ColorMode::DISPLAY_P3)
-    return Error::BAD_PARAMETER;
-
-  display_ptr->set_color_mode(mode);
-  return Error::NONE;
-}
-
-Error VrHwc::getRenderIntents_2_3(Display display, ColorMode mode,
-                                  std::vector<RenderIntent>* outIntents) {
-  return Error::NONE;
-}
-
-Error VrHwc::getColorModes_2_3(Display display, hidl_vec<ColorMode>* outModes) {
-  return Error::NONE;
-}
-
-Error VrHwc::getClientTargetSupport_2_3(Display display, uint32_t width,
-                                        uint32_t height, PixelFormat format,
-                                        Dataspace dataspace) {
-  return Error::NONE;
-}
-
-Error VrHwc::getReadbackBufferAttributes_2_3(Display display,
-                                             PixelFormat* outFormat,
-                                             Dataspace* outDataspace) {
-  return Error::NONE;
-}
-
-Error VrHwc::getDisplayIdentificationData(Display display, uint8_t* outPort,
-                                          std::vector<uint8_t>* outData) {
-  int error = 0;
-  auto display_client = display::DisplayClient::Create(&error);
-  if (!display_client) {
-    ALOGE("Could not connect to display service : %s(%d)", strerror(error),
-          error);
-    return Error::BAD_CONFIG;
-  }
-  auto edid_data = display_client->GetConfigurationData(
-      display::ConfigFileType::kDeviceEdid);
-  auto display_identification_port =
-      display_client->GetDisplayIdentificationPort();
-  *outPort = display_identification_port.get();
-
-  std::copy(edid_data.get().begin(), edid_data.get().end(),
-            std::back_inserter(*outData));
-  return Error::NONE;
-}
-
-Error VrHwc::setLayerColorTransform(Display display, Layer layer,
-                                    const float* matrix) {
-  return Error::NONE;
-}
-
-Error VrHwc::getDisplayedContentSamplingAttributes(
-    Display display, PixelFormat& format, Dataspace& dataspace,
-    hidl_bitfield<IComposerClient::FormatColorComponent>& componentMask) {
-  return Error::NONE;
-}
-
-Error VrHwc::setDisplayedContentSamplingEnabled(
-    Display display, IComposerClient::DisplayedContentSampling enable,
-    hidl_bitfield<IComposerClient::FormatColorComponent> componentMask,
-    uint64_t maxFrames) {
-  return Error::NONE;
-}
-
-Error VrHwc::getDisplayedContentSample(Display display, uint64_t maxFrames,
-                                       uint64_t timestamp, uint64_t& frameCount,
-                                       hidl_vec<uint64_t>& sampleComponent0,
-                                       hidl_vec<uint64_t>& sampleComponent1,
-                                       hidl_vec<uint64_t>& sampleComponent2,
-                                       hidl_vec<uint64_t>& sampleComponent3) {
-  return Error::NONE;
-}
-
-Error VrHwc::getDisplayCapabilities(
-    Display display,
-    std::vector<IComposerClient::DisplayCapability>* outCapabilities) {
-  return Error::NONE;
-}
-
-Error VrHwc::setLayerPerFrameMetadataBlobs(
-    Display display, Layer layer,
-    std::vector<IComposerClient::PerFrameMetadataBlob>& blobs) {
-  return Error::NONE;
-}
-
-Error VrHwc::getDisplayBrightnessSupport(Display display, bool* outSupport) {
-  return Error::NONE;
-}
-
-Error VrHwc::setDisplayBrightness(Display display, float brightness) {
-  return Error::NONE;
-}
-
-}  // namespace dvr
-}  // namespace android
diff --git a/services/vr/hardware_composer/impl/vr_hwc.h b/services/vr/hardware_composer/impl/vr_hwc.h
deleted file mode 100644
index 3e3a630..0000000
--- a/services/vr/hardware_composer/impl/vr_hwc.h
+++ /dev/null
@@ -1,410 +0,0 @@
-/*
- * Copyright 2016 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 ANDROID_DVR_HARDWARE_COMPOSER_IMPL_VR_HWC_H
-#define ANDROID_DVR_HARDWARE_COMPOSER_IMPL_VR_HWC_H
-
-#include <android-base/unique_fd.h>
-#include <android/frameworks/vr/composer/2.0/IVrComposerClient.h>
-#include <android/hardware/graphics/composer/2.3/IComposer.h>
-#include <composer-hal/2.3/ComposerHal.h>
-#include <private/dvr/vsync_service.h>
-#include <ui/Fence.h>
-#include <ui/GraphicBuffer.h>
-#include <utils/StrongPointer.h>
-
-#include <mutex>
-#include <unordered_map>
-
-using namespace android::frameworks::vr::composer::V2_0;
-using namespace android::hardware::graphics::common::V1_0;
-using namespace android::hardware::graphics::composer::V2_3;
-
-using android::hardware::hidl_bitfield;
-using android::hardware::hidl_handle;
-using android::hardware::hidl_string;
-using android::hardware::hidl_vec;
-using android::hardware::Return;
-using android::hardware::Void;
-using android::hardware::graphics::composer::V2_1::Config;
-using android::hardware::graphics::composer::V2_1::Display;
-using android::hardware::graphics::composer::V2_1::Error;
-using android::hardware::graphics::composer::V2_1::Layer;
-using android::hardware::graphics::composer::V2_3::IComposerClient;
-
-namespace android {
-
-class Fence;
-
-namespace dvr {
-
-class VrComposerClient;
-
-using android::hardware::graphics::composer::V2_3::hal::ComposerHal;
-
-namespace types = android::hardware::graphics::common;
-
-using types::V1_1::RenderIntent;
-using types::V1_2::ColorMode;
-using types::V1_2::Dataspace;
-using types::V1_2::Hdr;
-using types::V1_2::PixelFormat;
-
-class ComposerView {
- public:
-  struct ComposerLayer {
-    using Recti = hardware::graphics::composer::V2_3::IComposerClient::Rect;
-    using Rectf = hardware::graphics::composer::V2_3::IComposerClient::FRect;
-    using BlendMode =
-        hardware::graphics::composer::V2_3::IComposerClient::BlendMode;
-
-    Layer id;
-    sp<GraphicBuffer> buffer;
-    sp<Fence> fence;
-    Recti display_frame;
-    Rectf crop;
-    BlendMode blend_mode;
-    float alpha;
-    uint32_t type;
-    uint32_t app_id;
-    uint32_t z_order;
-    int32_t cursor_x;
-    int32_t cursor_y;
-    IComposerClient::Color color;
-    int32_t dataspace;
-    int32_t transform;
-    std::vector<hwc_rect_t> visible_regions;
-    std::vector<hwc_rect_t> damaged_regions;
-  };
-
-  struct Frame {
-    Display display_id;
-    // This is set to true to notify the upper layer that the display is
-    // being removed, or left false in the case of a normal frame. The upper
-    // layer tracks display IDs and will handle new ones showing up.
-    bool removed = false;
-    int32_t display_width;
-    int32_t display_height;
-    Config active_config;
-    ColorMode color_mode;
-    IComposerClient::PowerMode power_mode;
-    IComposerClient::Vsync vsync_enabled;
-    float color_transform[16];
-    int32_t color_transform_hint;
-    std::vector<ComposerLayer> layers;
-  };
-
-  class Observer {
-   public:
-    virtual ~Observer() {}
-
-    // Returns a list of layers that need to be shown together. Layers are
-    // returned in z-order, with the lowest layer first.
-    virtual base::unique_fd OnNewFrame(const Frame& frame) = 0;
-  };
-
-  virtual ~ComposerView() {}
-
-  virtual void ForceDisplaysRefresh() = 0;
-  virtual void RegisterObserver(Observer* observer) = 0;
-  virtual void UnregisterObserver(Observer* observer) = 0;
-};
-
-struct HwcLayer {
-  using Composition =
-      hardware::graphics::composer::V2_3::IComposerClient::Composition;
-
-  explicit HwcLayer(Layer new_id) { info.id = new_id; }
-
-  void dumpDebugInfo(std::string* result) const;
-
-  Composition composition_type;
-  ComposerView::ComposerLayer info;
-  IVrComposerClient::BufferMetadata buffer_metadata;
-};
-
-class HwcDisplay {
- public:
-  HwcDisplay(int32_t width, int32_t height);
-  ~HwcDisplay();
-
-  int32_t width() const { return width_; }
-  int32_t height() const { return height_; }
-
-  HwcLayer* CreateLayer();
-  bool DestroyLayer(Layer id);
-  HwcLayer* GetLayer(Layer id);
-
-  bool SetClientTarget(const native_handle_t* handle, base::unique_fd fence);
-  void SetClientTargetMetadata(
-      const IVrComposerClient::BufferMetadata& metadata);
-
-  void GetChangedCompositionTypes(
-      std::vector<Layer>* layer_ids,
-      std::vector<IComposerClient::Composition>* composition);
-
-  Error GetFrame(std::vector<ComposerView::ComposerLayer>* out_frame);
-
-  std::vector<Layer> UpdateLastFrameAndGetLastFrameLayers();
-
-  Config active_config() const { return active_config_; }
-  void set_active_config(Config config) { active_config_ = config; }
-
-  ColorMode color_mode() const { return color_mode_; }
-  void set_color_mode(ColorMode mode) { color_mode_ = mode; }
-
-  IComposerClient::PowerMode power_mode() const { return power_mode_; }
-  void set_power_mode(IComposerClient::PowerMode mode) { power_mode_ = mode; }
-
-  bool vsync_enabled() const { return vsync_enabled_; }
-  void set_vsync_enabled(bool vsync) {vsync_enabled_ = vsync;}
-
-  const float* color_transform() const { return color_transform_; }
-  int32_t color_transform_hint() const { return color_transform_hint_; }
-  void SetColorTransform(const float* matrix, int32_t hint);
-
-  void dumpDebugInfo(std::string* result) const;
-
- private:
-  // The client target buffer and the associated fence.
-  sp<GraphicBuffer> buffer_;
-  IVrComposerClient::BufferMetadata buffer_metadata_;
-  sp<Fence> fence_;
-
-  // List of currently active layers.
-  std::vector<HwcLayer> layers_;
-
-  std::vector<Layer> last_frame_layers_ids_;
-
-  // Layer ID generator.
-  uint64_t layer_ids_ = 1;
-
-  int32_t width_;
-  int32_t height_;
-
-  Config active_config_;
-  ColorMode color_mode_;
-  IComposerClient::PowerMode power_mode_;
-  bool vsync_enabled_ = false;
-  float color_transform_[16];
-  int32_t color_transform_hint_;
-
-  HwcDisplay(const HwcDisplay&) = delete;
-  void operator=(const HwcDisplay&) = delete;
-};
-
-class VrHwc : public IComposer, public ComposerHal, public ComposerView {
- public:
-  VrHwc();
-  ~VrHwc() override;
-
-  Error setLayerInfo(Display display, Layer layer, uint32_t type,
-                     uint32_t appId);
-  Error setClientTargetMetadata(
-      Display display, const IVrComposerClient::BufferMetadata& metadata);
-  Error setLayerBufferMetadata(
-      Display display, Layer layer,
-      const IVrComposerClient::BufferMetadata& metadata);
-
-  // composer::V2_1::ComposerHal
-  bool hasCapability(hwc2_capability_t capability) override;
-
-  std::string dumpDebugInfo() override { return {}; }
-
-  void registerEventCallback(ComposerHal::EventCallback* callback) override;
-  void unregisterEventCallback() override;
-
-  uint32_t getMaxVirtualDisplayCount() override;
-  Error destroyVirtualDisplay(Display display) override;
-
-  Error createLayer(Display display, Layer* outLayer) override;
-  Error destroyLayer(Display display, Layer layer) override;
-
-  Error getActiveConfig(Display display, Config* outConfig) override;
-  Error getDisplayAttribute(Display display, Config config,
-                            IComposerClient::Attribute attribute,
-                            int32_t* outValue) override;
-  Error getDisplayConfigs(Display display, hidl_vec<Config>* outConfigs) override;
-  Error getDisplayName(Display display, hidl_string* outName) override;
-  Error getDisplayType(Display display,
-                       IComposerClient::DisplayType* outType) override;
-  Error getDozeSupport(Display display, bool* outSupport) override;
-
-  Error setActiveConfig(Display display, Config config) override;
-  Error setVsyncEnabled(Display display, IComposerClient::Vsync enabled) override;
-
-  Error setColorTransform(Display display, const float* matrix,
-                          int32_t hint) override;
-  Error setClientTarget(Display display, buffer_handle_t target,
-                        int32_t acquireFence, int32_t dataspace,
-                        const std::vector<hwc_rect_t>& damage) override;
-  Error setOutputBuffer(Display display, buffer_handle_t buffer,
-                        int32_t releaseFence) override;
-  Error validateDisplay(
-      Display display, std::vector<Layer>* outChangedLayers,
-      std::vector<IComposerClient::Composition>* outCompositionTypes,
-      uint32_t* outDisplayRequestMask, std::vector<Layer>* outRequestedLayers,
-      std::vector<uint32_t>* outRequestMasks) override;
-  Error acceptDisplayChanges(Display display) override;
-  Error presentDisplay(Display display, int32_t* outPresentFence,
-                       std::vector<Layer>* outLayers,
-                       std::vector<int32_t>* outReleaseFences) override;
-
-  Error setLayerCursorPosition(Display display, Layer layer, int32_t x,
-                               int32_t y) override;
-  Error setLayerBuffer(Display display, Layer layer, buffer_handle_t buffer,
-                       int32_t acquireFence) override;
-  Error setLayerSurfaceDamage(Display display, Layer layer,
-                              const std::vector<hwc_rect_t>& damage) override;
-  Error setLayerBlendMode(Display display, Layer layer, int32_t mode) override;
-  Error setLayerColor(Display display, Layer layer,
-                      IComposerClient::Color color) override;
-  Error setLayerCompositionType(Display display, Layer layer,
-                                int32_t type) override;
-  Error setLayerDataspace(Display display, Layer layer,
-                          int32_t dataspace) override;
-  Error setLayerDisplayFrame(Display display, Layer layer,
-                             const hwc_rect_t& frame) override;
-  Error setLayerPlaneAlpha(Display display, Layer layer, float alpha) override;
-  Error setLayerSidebandStream(Display display, Layer layer,
-                               buffer_handle_t stream) override;
-  Error setLayerSourceCrop(Display display, Layer layer,
-                           const hwc_frect_t& crop) override;
-  Error setLayerTransform(Display display, Layer layer,
-                          int32_t transform) override;
-  Error setLayerVisibleRegion(Display display, Layer layer,
-                              const std::vector<hwc_rect_t>& visible) override;
-  Error setLayerZOrder(Display display, Layer layer, uint32_t z) override;
-
-  // composer::V2_2::ComposerHal
-  Error setReadbackBuffer(Display display, const native_handle_t* bufferHandle,
-                          android::base::unique_fd fenceFd) override;
-  Error getReadbackBufferFence(Display display,
-                               android::base::unique_fd* outFenceFd) override;
-  Error createVirtualDisplay_2_2(uint32_t width, uint32_t height,
-                                 types::V1_1::PixelFormat* format,
-                                 Display* outDisplay) override;
-  Error setPowerMode_2_2(Display display,
-                         IComposerClient::PowerMode mode) override;
-  Error setLayerFloatColor(Display display, Layer layer,
-                           IComposerClient::FloatColor color) override;
-  Error getRenderIntents(Display display, types::V1_1::ColorMode mode,
-                         std::vector<RenderIntent>* outIntents) override;
-  std::array<float, 16> getDataspaceSaturationMatrix(
-      types::V1_1::Dataspace dataspace) override;
-
-  // composer::V2_3::ComposerHal
-  Error getHdrCapabilities_2_3(Display display, hidl_vec<Hdr>* outTypes,
-                               float* outMaxLuminance,
-                               float* outMaxAverageLuminance,
-                               float* outMinLuminance) override;
-  Error setLayerPerFrameMetadata_2_3(
-      Display display, Layer layer,
-      const std::vector<IComposerClient::PerFrameMetadata>& metadata) override;
-  Error getPerFrameMetadataKeys_2_3(
-      Display display,
-      std::vector<IComposerClient::PerFrameMetadataKey>* outKeys) override;
-  Error setColorMode_2_3(Display display, ColorMode mode,
-                         RenderIntent intent) override;
-  Error getRenderIntents_2_3(Display display, ColorMode mode,
-                             std::vector<RenderIntent>* outIntents) override;
-  Error getColorModes_2_3(Display display,
-                          hidl_vec<ColorMode>* outModes) override;
-  Error getClientTargetSupport_2_3(Display display, uint32_t width,
-                                   uint32_t height, PixelFormat format,
-                                   Dataspace dataspace) override;
-  Error getReadbackBufferAttributes_2_3(Display display, PixelFormat* outFormat,
-                                        Dataspace* outDataspace) override;
-  Error getDisplayIdentificationData(Display display, uint8_t* outPort,
-                                     std::vector<uint8_t>* outData) override;
-  Error setLayerColorTransform(Display display, Layer layer,
-                               const float* matrix) override;
-  Error getDisplayedContentSamplingAttributes(
-      Display display, PixelFormat& format, Dataspace& dataspace,
-      hidl_bitfield<IComposerClient::FormatColorComponent>& componentMask)
-      override;
-  Error setDisplayedContentSamplingEnabled(
-      Display display, IComposerClient::DisplayedContentSampling enable,
-      hidl_bitfield<IComposerClient::FormatColorComponent> componentMask,
-      uint64_t maxFrames) override;
-  Error getDisplayedContentSample(
-      Display display, uint64_t maxFrames, uint64_t timestamp,
-      uint64_t& frameCount, hidl_vec<uint64_t>& sampleComponent0,
-      hidl_vec<uint64_t>& sampleComponent1,
-      hidl_vec<uint64_t>& sampleComponent2,
-      hidl_vec<uint64_t>& sampleComponent3) override;
-  Error getDisplayCapabilities(Display display,
-                               std::vector<IComposerClient::DisplayCapability>*
-                                   outCapabilities) override;
-  Error setLayerPerFrameMetadataBlobs(
-      Display display, Layer layer,
-      std::vector<IComposerClient::PerFrameMetadataBlob>& blobs) override;
-  Error getDisplayBrightnessSupport(Display display, bool* outSupport) override;
-  Error setDisplayBrightness(Display display, float brightness) override;
-
-  // IComposer:
-  Return<void> getCapabilities(getCapabilities_cb hidl_cb) override;
-  Return<void> dumpDebugInfo(dumpDebugInfo_cb hidl_cb) override;
-  Return<void> createClient(createClient_cb hidl_cb) override;
-  Return<void> createClient_2_3(
-      IComposer::createClient_2_3_cb hidl_cb) override;
-
-  // ComposerView:
-  void ForceDisplaysRefresh() override;
-  void RegisterObserver(Observer* observer) override;
-  void UnregisterObserver(Observer* observer) override;
-
-  Return<void> debug(const hidl_handle& fd,
-                     const hidl_vec<hidl_string>& args) override;
-
- private:
-  class VsyncCallback : public BnVsyncCallback {
-   public:
-    status_t onVsync(int64_t vsync_timestamp) override;
-    void SetEventCallback(EventCallback* callback);
-   private:
-    std::mutex mutex_;
-    EventCallback* callback_;
-  };
-
-  HwcDisplay* FindDisplay(Display display);
-
-  // Re-evaluate whether or not we should start making onVsync() callbacks to
-  // the client. We need enableCallback(true) to have been called, and
-  // setVsyncEnabled() to have been called for the primary display. The caller
-  // must have mutex_ locked already.
-  void UpdateVsyncCallbackEnabledLocked();
-
-  wp<VrComposerClient> client_;
-
-  // Guard access to internal state from binder threads.
-  std::mutex mutex_;
-
-  std::unordered_map<Display, std::unique_ptr<HwcDisplay>> displays_;
-  Display display_count_ = 2;
-
-  EventCallback* event_callback_ = nullptr;
-  Observer* observer_ = nullptr;
-
-  sp<VsyncCallback> vsync_callback_;
-
-  VrHwc(const VrHwc&) = delete;
-  void operator=(const VrHwc&) = delete;
-};
-
-}  // namespace dvr
-}  // namespace android
-
-#endif  // ANDROID_DVR_HARDWARE_COMPOSER_IMPL_VR_HWC_H
diff --git a/services/vr/hardware_composer/tests/vr_composer_test.cpp b/services/vr/hardware_composer/tests/vr_composer_test.cpp
deleted file mode 100644
index 2e70928..0000000
--- a/services/vr/hardware_composer/tests/vr_composer_test.cpp
+++ /dev/null
@@ -1,172 +0,0 @@
-#include <android/dvr/BnVrComposerCallback.h>
-#include <binder/IServiceManager.h>
-#include <gtest/gtest.h>
-#include <sys/eventfd.h>
-#include <vr_composer.h>
-
-namespace android {
-namespace dvr {
-namespace {
-
-const char kVrDisplayName[] = "VrDisplay_Test";
-
-class TestComposerView : public ComposerView {
- public:
-  TestComposerView() {}
-  ~TestComposerView() override = default;
-
-  size_t display_refresh_count() const { return display_refresh_count_; }
-
-  void ForceDisplaysRefresh() override { display_refresh_count_++; }
-  void RegisterObserver(Observer* observer) override {}
-  void UnregisterObserver(Observer* observer) override {}
-
-  TestComposerView(const TestComposerView&) = delete;
-  void operator=(const TestComposerView&) = delete;
-
- private:
-  size_t display_refresh_count_ = 0;
-};
-
-class TestComposerCallback : public BnVrComposerCallback {
- public:
-  TestComposerCallback() {}
-  ~TestComposerCallback() override = default;
-
-  ComposerView::Frame last_frame() const { return last_frame_; }
-
-  binder::Status onNewFrame(
-      const ParcelableComposerFrame& frame,
-      ParcelableUniqueFd* /* fence */) override {
-    last_frame_ = frame.frame();
-    return binder::Status::ok();
-  }
-
- private:
-  ComposerView::Frame last_frame_;
-
-  TestComposerCallback(const TestComposerCallback&) = delete;
-  void operator=(const TestComposerCallback&) = delete;
-};
-
-class TestComposerCallbackWithFence : public TestComposerCallback {
- public:
-  ~TestComposerCallbackWithFence() override = default;
-
-  binder::Status onNewFrame(
-      const ParcelableComposerFrame& frame,
-      ParcelableUniqueFd* fence) override {
-    binder::Status status = TestComposerCallback::onNewFrame(frame, fence);
-
-    base::unique_fd fd(eventfd(0, 0));
-    EXPECT_LE(0, fd.get());
-    fence->set_fence(fd);
-
-    return status;
-  }
-};
-
-sp<GraphicBuffer> CreateBuffer() {
-  return new GraphicBuffer(600, 400, PIXEL_FORMAT_RGBA_8888,
-                           GraphicBuffer::USAGE_HW_TEXTURE);
-}
-
-}  // namespace
-
-class VrComposerTest : public testing::Test {
- public:
-  VrComposerTest() : composer_(new VrComposer(&composer_view_)) {}
-  ~VrComposerTest() override = default;
-
-  sp<IVrComposer> GetComposerProxy() const {
-    sp<IServiceManager> sm(defaultServiceManager());
-    return interface_cast<IVrComposer>(sm->getService(String16(kVrDisplayName)));
-  }
-
-  void SetUp() override {
-    sp<IServiceManager> sm(defaultServiceManager());
-    EXPECT_EQ(OK,
-              sm->addService(String16(kVrDisplayName), composer_, false));
-  }
-
- protected:
-  TestComposerView composer_view_;
-  sp<VrComposer> composer_;
-
-  VrComposerTest(const VrComposerTest&) = delete;
-  void operator=(const VrComposerTest&) = delete;
-};
-
-TEST_F(VrComposerTest, TestWithoutObserver) {
-  sp<IVrComposer> composer = GetComposerProxy();
-  ComposerView::Frame frame;
-
-  base::unique_fd fence = composer_->OnNewFrame(frame);
-  ASSERT_EQ(-1, fence.get());
-}
-
-TEST_F(VrComposerTest, TestWithObserver) {
-  sp<IVrComposer> composer = GetComposerProxy();
-  sp<TestComposerCallback> callback = new TestComposerCallback();
-  ASSERT_EQ(0, composer_view_.display_refresh_count());
-  ASSERT_TRUE(composer->registerObserver(callback).isOk());
-  ASSERT_EQ(1, composer_view_.display_refresh_count());
-
-  ComposerView::Frame frame;
-  base::unique_fd fence = composer_->OnNewFrame(frame);
-  ASSERT_EQ(-1, fence.get());
-}
-
-TEST_F(VrComposerTest, TestWithOneLayer) {
-  sp<IVrComposer> composer = GetComposerProxy();
-  sp<TestComposerCallback> callback = new TestComposerCallbackWithFence();
-  ASSERT_TRUE(composer->registerObserver(callback).isOk());
-
-  ComposerView::Frame frame;
-  frame.display_id = 1;
-  frame.removed = false;
-  frame.display_width = 600;
-  frame.display_height = 400;
-  frame.layers.push_back(ComposerView::ComposerLayer{
-    .id = 1,
-    .buffer = CreateBuffer(),
-    .fence = new Fence(eventfd(0, 0)),
-    .display_frame = {0, 0, 600, 400},
-    .crop = {0.0f, 0.0f, 600.0f, 400.0f},
-    .blend_mode = IComposerClient::BlendMode::NONE,
-    .alpha = 1.0f,
-    .type = 1,
-    .app_id = 1,
-  });
-  base::unique_fd fence = composer_->OnNewFrame(frame);
-  ASSERT_LE(0, fence.get());
-
-  ComposerView::Frame received_frame = callback->last_frame();
-  ASSERT_EQ(frame.display_id, received_frame.display_id);
-  ASSERT_EQ(frame.display_width, received_frame.display_width);
-  ASSERT_EQ(frame.display_height, received_frame.display_height);
-  ASSERT_EQ(frame.removed, received_frame.removed);
-  ASSERT_EQ(1u, received_frame.layers.size());
-  ASSERT_EQ(frame.layers[0].id, received_frame.layers[0].id);
-  ASSERT_NE(nullptr, received_frame.layers[0].buffer.get());
-  ASSERT_TRUE(received_frame.layers[0].fence->isValid());
-  ASSERT_EQ(frame.layers[0].display_frame.left,
-            received_frame.layers[0].display_frame.left);
-  ASSERT_EQ(frame.layers[0].display_frame.top,
-            received_frame.layers[0].display_frame.top);
-  ASSERT_EQ(frame.layers[0].display_frame.right,
-            received_frame.layers[0].display_frame.right);
-  ASSERT_EQ(frame.layers[0].display_frame.bottom,
-            received_frame.layers[0].display_frame.bottom);
-  ASSERT_EQ(frame.layers[0].crop.left, received_frame.layers[0].crop.left);
-  ASSERT_EQ(frame.layers[0].crop.top, received_frame.layers[0].crop.top);
-  ASSERT_EQ(frame.layers[0].crop.right, received_frame.layers[0].crop.right);
-  ASSERT_EQ(frame.layers[0].crop.bottom, received_frame.layers[0].crop.bottom);
-  ASSERT_EQ(frame.layers[0].blend_mode, received_frame.layers[0].blend_mode);
-  ASSERT_EQ(frame.layers[0].alpha, received_frame.layers[0].alpha);
-  ASSERT_EQ(frame.layers[0].type, received_frame.layers[0].type);
-  ASSERT_EQ(frame.layers[0].app_id, received_frame.layers[0].app_id);
-}
-
-}  // namespace dvr
-}  // namespace android
diff --git a/services/vr/hardware_composer/vr_composer.cpp b/services/vr/hardware_composer/vr_composer.cpp
deleted file mode 100644
index d93f370..0000000
--- a/services/vr/hardware_composer/vr_composer.cpp
+++ /dev/null
@@ -1,85 +0,0 @@
-#include "vr_composer.h"
-
-#include <binder/IPCThreadState.h>
-#include <binder/PermissionCache.h>
-
-namespace android {
-namespace dvr {
-namespace {
-
-bool CheckPermission() {
-  const android::IPCThreadState* ipc = android::IPCThreadState::self();
-  const pid_t pid = ipc->getCallingPid();
-  const uid_t uid = ipc->getCallingUid();
-  const bool permission = PermissionCache::checkPermission(
-      String16("android.permission.RESTRICTED_VR_ACCESS"), pid, uid);
-  if (!permission)
-    ALOGE("permission denied to pid=%d uid=%u", pid, uid);
-
-  return permission;
-}
-
-}  // namespace
-
-VrComposer::VrComposer(ComposerView* composer_view)
-  : composer_view_(composer_view) {
-  composer_view_->RegisterObserver(this);
-}
-
-VrComposer::~VrComposer() {
-  composer_view_->UnregisterObserver(this);
-}
-
-binder::Status VrComposer::registerObserver(
-    const sp<IVrComposerCallback>& callback) {
-  {
-    std::lock_guard<std::mutex> guard(mutex_);
-
-    if (!CheckPermission())
-      return binder::Status::fromStatusT(PERMISSION_DENIED);
-
-    if (callback_.get()) {
-      ALOGE("Failed to register callback, already registered");
-      return binder::Status::fromStatusT(ALREADY_EXISTS);
-    }
-
-    callback_ = callback;
-    IInterface::asBinder(callback_)->linkToDeath(this);
-  }
-
-  // Don't take the lock to force display refresh otherwise it could end in a
-  // deadlock since HWC calls this with new frames and it has a lock of its own
-  // to serialize access to the display information.
-  composer_view_->ForceDisplaysRefresh();
-  return binder::Status::ok();
-}
-
-binder::Status VrComposer::clearObserver() {
-  std::lock_guard<std::mutex> guard(mutex_);
-  callback_ = nullptr;
-  return binder::Status::ok();
-}
-
-base::unique_fd VrComposer::OnNewFrame(const ComposerView::Frame& frame) {
-  std::lock_guard<std::mutex> guard(mutex_);
-
-  if (!callback_.get())
-    return base::unique_fd();
-
-  ParcelableComposerFrame parcelable_frame(frame);
-  ParcelableUniqueFd fence;
-  binder::Status ret = callback_->onNewFrame(parcelable_frame, &fence);
-  if (!ret.isOk())
-    ALOGE("Failed to send new frame: %s", ret.toString8().string());
-
-  return fence.fence();
-}
-
-void VrComposer::binderDied(const wp<IBinder>& /* who */) {
-  std::lock_guard<std::mutex> guard(mutex_);
-
-  callback_ = nullptr;
-}
-
-}  // namespace dvr
-}  // namespace android
diff --git a/services/vr/hardware_composer/vr_composer.h b/services/vr/hardware_composer/vr_composer.h
deleted file mode 100644
index 1273352..0000000
--- a/services/vr/hardware_composer/vr_composer.h
+++ /dev/null
@@ -1,52 +0,0 @@
-#ifndef ANDROID_DVR_HARDWARE_COMPOSER_VR_COMPOSER_H
-#define ANDROID_DVR_HARDWARE_COMPOSER_VR_COMPOSER_H
-
-#include <android/dvr/BnVrComposer.h>
-#include <impl/vr_hwc.h>
-
-namespace android {
-namespace dvr {
-
-class VrComposerCallback;
-
-// Implementation of the IVrComposer service used to notify VR Window Manager
-// when SurfaceFlinger presents 2D UI changes.
-//
-// VR HWC updates the presented frame via the ComposerView::Observer interface.
-// On notification |callback_| is called to update VR Window Manager.
-// NOTE: If VR Window Manager isn't connected, the notification is a no-op.
-class VrComposer
-    : public BnVrComposer,
-      public ComposerView::Observer,
-      public IBinder::DeathRecipient {
- public:
-  explicit VrComposer(ComposerView* composer_view);
-  ~VrComposer() override;
-
-  // BnVrComposer:
-  binder::Status registerObserver(
-      const sp<IVrComposerCallback>& callback) override;
-
-  binder::Status clearObserver() override;
-
-  // ComposerView::Observer:
-  base::unique_fd OnNewFrame(const ComposerView::Frame& frame) override;
-
- private:
-  // IBinder::DeathRecipient:
-  void binderDied(const wp<IBinder>& who) override;
-
-  std::mutex mutex_;
-
-  sp<IVrComposerCallback> callback_;
-
-  ComposerView* composer_view_;  // Not owned.
-
-  VrComposer(const VrComposer&) = delete;
-  void operator=(const VrComposer&) = delete;
-};
-
-}  // namespace dvr
-}  // namespace android
-
-#endif  //  ANDROID_DVR_HARDWARE_COMPOSER_VR_COMPOSER_H
diff --git a/vulkan/include/vulkan/vk_android_native_buffer.h b/vulkan/include/vulkan/vk_android_native_buffer.h
index ba98696..40cf9fb 100644
--- a/vulkan/include/vulkan/vk_android_native_buffer.h
+++ b/vulkan/include/vulkan/vk_android_native_buffer.h
@@ -49,7 +49,13 @@
  * in VkBindImageMemorySwapchainInfoKHR will be additionally chained to the
  * pNext chain of VkBindImageMemoryInfo and passed down to the driver.
  */
-#define VK_ANDROID_NATIVE_BUFFER_SPEC_VERSION 8
+/*
+ * NOTE ON VK_ANDROID_NATIVE_BUFFER_SPEC_VERSION 9
+ *
+ * This version of the extension is largely designed to clean up the mix of
+ * GrallocUsage and GrallocUsage2
+ */
+#define VK_ANDROID_NATIVE_BUFFER_SPEC_VERSION 9
 #define VK_ANDROID_NATIVE_BUFFER_EXTENSION_NAME "VK_ANDROID_native_buffer"
 
 #define VK_ANDROID_NATIVE_BUFFER_ENUM(type, id) \
@@ -61,6 +67,8 @@
     VK_ANDROID_NATIVE_BUFFER_ENUM(VkStructureType, 1)
 #define VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRESENTATION_PROPERTIES_ANDROID \
     VK_ANDROID_NATIVE_BUFFER_ENUM(VkStructureType, 2)
+#define VK_STRUCTURE_TYPE_GRALLOC_USAGE_INFO_ANDROID \
+    VK_ANDROID_NATIVE_BUFFER_ENUM(VkStructureType, 3)
 
 /* clang-format off */
 typedef enum VkSwapchainImageUsageFlagBitsANDROID {
@@ -90,6 +98,7 @@
  * format: gralloc format requested when the buffer was allocated
  * 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
  */
 typedef struct {
     VkStructureType                   sType;
@@ -98,7 +107,8 @@
     int                               stride;
     int                               format;
     int                               usage; /* DEPRECATED in SPEC_VERSION 6 */
-    VkNativeBufferUsage2ANDROID       usage2; /* ADDED in SPEC_VERSION 6 */
+    VkNativeBufferUsage2ANDROID       usage2; /* DEPRECATED in SPEC_VERSION 9 */
+    uint64_t                          usage3; /* ADDED in SPEC_VERSION 9 */
 } VkNativeBufferANDROID;
 
 /*
@@ -127,6 +137,21 @@
     VkBool32                          sharedImage;
 } VkPhysicalDevicePresentationPropertiesANDROID;
 
+/*
+ * struct VkGrallocUsageInfoANDROID
+ *
+ * sType: VK_STRUCTURE_TYPE_GRALLOC_USAGE_INFO_ANDROID
+ * pNext: NULL or a pointer to a structure extending this structure
+ * format: value specifying the format the image will be created with
+ * imageUsage: bitmask of VkImageUsageFlagBits describing intended usage
+ */
+typedef struct {
+    VkStructureType                   sType;
+    const void*                       pNext;
+    VkFormat                          format;
+    VkImageUsageFlags                 imageUsage;
+} VkGrallocUsageInfoANDROID;
+
 /* DEPRECATED in SPEC_VERSION 6 */
 typedef VkResult (VKAPI_PTR *PFN_vkGetSwapchainGrallocUsageANDROID)(
     VkDevice                          device,
@@ -134,7 +159,7 @@
     VkImageUsageFlags                 imageUsage,
     int*                              grallocUsage);
 
-/* ADDED in SPEC_VERSION 6 */
+/* DEPRECATED in SPEC_VERSION 9 */
 typedef VkResult (VKAPI_PTR *PFN_vkGetSwapchainGrallocUsage2ANDROID)(
     VkDevice                          device,
     VkFormat                          format,
@@ -143,6 +168,12 @@
     uint64_t*                         grallocConsumerUsage,
     uint64_t*                         grallocProducerUsage);
 
+/* ADDED in SPEC_VERSION 9 */
+typedef VkResult (VKAPI_PTR *PFN_vkGetSwapchainGrallocUsage3ANDROID)(
+    VkDevice                          device,
+    const VkGrallocUsageInfoANDROID*  grallocUsageInfo,
+    uint64_t*                         grallocUsage);
+
 typedef VkResult (VKAPI_PTR *PFN_vkAcquireImageANDROID)(
     VkDevice                          device,
     VkImage                           image,
@@ -167,7 +198,7 @@
     int*                              grallocUsage
 );
 
-/* ADDED in SPEC_VERSION 6 */
+/* DEPRECATED in SPEC_VERSION 9 */
 VKAPI_ATTR VkResult VKAPI_CALL vkGetSwapchainGrallocUsage2ANDROID(
     VkDevice                          device,
     VkFormat                          format,
@@ -177,6 +208,13 @@
     uint64_t*                         grallocProducerUsage
 );
 
+/* ADDED in SPEC_VERSION 9 */
+VKAPI_ATTR VkResult VKAPI_CALL vkGetSwapchainGrallocUsage3ANDROID(
+    VkDevice                          device,
+    const VkGrallocUsageInfoANDROID*  grallocUsageInfo,
+    uint64_t*                         grallocUsage
+);
+
 VKAPI_ATTR VkResult VKAPI_CALL vkAcquireImageANDROID(
     VkDevice                          device,
     VkImage                           image,
diff --git a/vulkan/libvulkan/driver.cpp b/vulkan/libvulkan/driver.cpp
index 7664518..a99355f 100644
--- a/vulkan/libvulkan/driver.cpp
+++ b/vulkan/libvulkan/driver.cpp
@@ -1027,6 +1027,51 @@
     }
 }
 
+VkResult GetAndroidNativeBufferSpecVersion9Support(
+    VkPhysicalDevice physicalDevice,
+    bool& support) {
+    support = false;
+
+    const InstanceData& data = GetData(physicalDevice);
+
+    // Call to get propertyCount
+    uint32_t propertyCount = 0;
+    ATRACE_BEGIN("driver.EnumerateDeviceExtensionProperties");
+    VkResult result = data.driver.EnumerateDeviceExtensionProperties(
+        physicalDevice, nullptr, &propertyCount, nullptr);
+    ATRACE_END();
+
+    if (result != VK_SUCCESS && result != VK_INCOMPLETE) {
+        return result;
+    }
+
+    // Call to enumerate properties
+    std::vector<VkExtensionProperties> properties(propertyCount);
+    ATRACE_BEGIN("driver.EnumerateDeviceExtensionProperties");
+    result = data.driver.EnumerateDeviceExtensionProperties(
+        physicalDevice, nullptr, &propertyCount, properties.data());
+    ATRACE_END();
+
+    if (result != VK_SUCCESS && result != VK_INCOMPLETE) {
+        return result;
+    }
+
+    for (uint32_t i = 0; i < propertyCount; i++) {
+        auto& prop = properties[i];
+
+        if (strcmp(prop.extensionName,
+                   VK_ANDROID_NATIVE_BUFFER_EXTENSION_NAME) != 0)
+            continue;
+
+        if (prop.specVersion >= 9) {
+            support = true;
+            return result;
+        }
+    }
+
+    return result;
+}
+
 VkResult EnumerateDeviceExtensionProperties(
     VkPhysicalDevice physicalDevice,
     const char* pLayerName,
@@ -1061,6 +1106,49 @@
                 VK_GOOGLE_DISPLAY_TIMING_SPEC_VERSION});
     }
 
+    // Conditionally add VK_EXT_IMAGE_COMPRESSION_CONTROL* if feature and ANB
+    // support is provided by the driver
+    VkPhysicalDeviceImageCompressionControlSwapchainFeaturesEXT
+        swapchainCompFeats = {};
+    swapchainCompFeats.sType =
+        VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_COMPRESSION_CONTROL_SWAPCHAIN_FEATURES_EXT;
+    swapchainCompFeats.pNext = nullptr;
+    swapchainCompFeats.imageCompressionControlSwapchain = false;
+    VkPhysicalDeviceImageCompressionControlFeaturesEXT imageCompFeats = {};
+    imageCompFeats.sType =
+        VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_COMPRESSION_CONTROL_FEATURES_EXT;
+    imageCompFeats.pNext = &swapchainCompFeats;
+    imageCompFeats.imageCompressionControl = false;
+
+    VkPhysicalDeviceFeatures2 feats2 = {};
+    feats2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2;
+    feats2.pNext = &imageCompFeats;
+
+    const auto& driver = GetData(physicalDevice).driver;
+    if (driver.GetPhysicalDeviceFeatures2 ||
+        driver.GetPhysicalDeviceFeatures2KHR) {
+        GetPhysicalDeviceFeatures2(physicalDevice, &feats2);
+    }
+
+    bool anb9 = false;
+    VkResult result =
+        GetAndroidNativeBufferSpecVersion9Support(physicalDevice, anb9);
+
+    if (result != VK_SUCCESS && result != VK_INCOMPLETE) {
+        return result;
+    }
+
+    if (anb9 && imageCompFeats.imageCompressionControl) {
+        loader_extensions.push_back(
+            {VK_EXT_IMAGE_COMPRESSION_CONTROL_EXTENSION_NAME,
+             VK_EXT_IMAGE_COMPRESSION_CONTROL_SPEC_VERSION});
+    }
+    if (anb9 && swapchainCompFeats.imageCompressionControlSwapchain) {
+        loader_extensions.push_back(
+            {VK_EXT_IMAGE_COMPRESSION_CONTROL_SWAPCHAIN_EXTENSION_NAME,
+             VK_EXT_IMAGE_COMPRESSION_CONTROL_SWAPCHAIN_SPEC_VERSION});
+    }
+
     // enumerate our extensions first
     if (!pLayerName && pProperties) {
         uint32_t count = std::min(
@@ -1078,7 +1166,7 @@
     }
 
     ATRACE_BEGIN("driver.EnumerateDeviceExtensionProperties");
-    VkResult result = data.driver.EnumerateDeviceExtensionProperties(
+    result = data.driver.EnumerateDeviceExtensionProperties(
         physicalDevice, pLayerName, pPropertyCount, pProperties);
     ATRACE_END();
 
@@ -1254,15 +1342,18 @@
         return VK_ERROR_INCOMPATIBLE_DRIVER;
     }
 
-    // sanity check ANDROID_native_buffer implementation, whose set of
+    // Confirming ANDROID_native_buffer implementation, whose set of
     // entrypoints varies according to the spec version.
     if ((wrapper.GetHalExtensions()[ProcHook::ANDROID_native_buffer]) &&
         !data->driver.GetSwapchainGrallocUsageANDROID &&
-        !data->driver.GetSwapchainGrallocUsage2ANDROID) {
-        ALOGE("Driver's implementation of ANDROID_native_buffer is broken;"
-              " must expose at least one of "
-              "vkGetSwapchainGrallocUsageANDROID or "
-              "vkGetSwapchainGrallocUsage2ANDROID");
+        !data->driver.GetSwapchainGrallocUsage2ANDROID &&
+        !data->driver.GetSwapchainGrallocUsage3ANDROID) {
+        ALOGE(
+            "Driver's implementation of ANDROID_native_buffer is broken;"
+            " must expose at least one of "
+            "vkGetSwapchainGrallocUsageANDROID or "
+            "vkGetSwapchainGrallocUsage2ANDROID or "
+            "vkGetSwapchainGrallocUsage3ANDROID");
 
         data->driver.DestroyDevice(dev, pAllocator);
         FreeDeviceData(data, data_allocator);
@@ -1441,10 +1532,89 @@
 
     if (driver.GetPhysicalDeviceFeatures2) {
         driver.GetPhysicalDeviceFeatures2(physicalDevice, pFeatures);
+    } else {
+        driver.GetPhysicalDeviceFeatures2KHR(physicalDevice, pFeatures);
+    }
+
+    // Conditionally add imageCompressionControlSwapchain if
+    // imageCompressionControl is supported Check for imageCompressionControl in
+    // the pChain
+    bool imageCompressionControl = false;
+    bool imageCompressionControlInChain = false;
+    bool imageCompressionControlSwapchainInChain = false;
+    VkPhysicalDeviceFeatures2* pFeats = pFeatures;
+    while (pFeats) {
+        switch (pFeats->sType) {
+            case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_COMPRESSION_CONTROL_FEATURES_EXT: {
+                const VkPhysicalDeviceImageCompressionControlFeaturesEXT*
+                    compressionFeat = reinterpret_cast<
+                        const VkPhysicalDeviceImageCompressionControlFeaturesEXT*>(
+                        pFeats);
+                imageCompressionControl =
+                    compressionFeat->imageCompressionControl;
+                imageCompressionControlInChain = true;
+            } break;
+
+            case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_COMPRESSION_CONTROL_SWAPCHAIN_FEATURES_EXT: {
+                VkPhysicalDeviceImageCompressionControlSwapchainFeaturesEXT*
+                    compressionFeat = reinterpret_cast<
+                        VkPhysicalDeviceImageCompressionControlSwapchainFeaturesEXT*>(
+                        pFeats);
+                compressionFeat->imageCompressionControlSwapchain = false;
+                imageCompressionControlSwapchainInChain = true;
+            } break;
+
+            default:
+                break;
+        }
+        pFeats = reinterpret_cast<VkPhysicalDeviceFeatures2*>(pFeats->pNext);
+    }
+
+    if (!imageCompressionControlSwapchainInChain) {
         return;
     }
 
-    driver.GetPhysicalDeviceFeatures2KHR(physicalDevice, pFeatures);
+    // If not in pchain, explicitly query for imageCompressionControl
+    if (!imageCompressionControlInChain) {
+        VkPhysicalDeviceImageCompressionControlFeaturesEXT imageCompFeats = {};
+        imageCompFeats.sType =
+            VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_COMPRESSION_CONTROL_FEATURES_EXT;
+        imageCompFeats.pNext = nullptr;
+        imageCompFeats.imageCompressionControl = false;
+
+        VkPhysicalDeviceFeatures2 feats2 = {};
+        feats2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2;
+        feats2.pNext = &imageCompFeats;
+
+        if (driver.GetPhysicalDeviceFeatures2) {
+            driver.GetPhysicalDeviceFeatures2(physicalDevice, &feats2);
+        } else {
+            driver.GetPhysicalDeviceFeatures2KHR(physicalDevice, &feats2);
+        }
+
+        imageCompressionControl = imageCompFeats.imageCompressionControl;
+    }
+
+    // Only enumerate imageCompressionControlSwapchin if imageCompressionControl
+    if (imageCompressionControl) {
+        pFeats = pFeatures;
+        while (pFeats) {
+            switch (pFeats->sType) {
+                case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_COMPRESSION_CONTROL_SWAPCHAIN_FEATURES_EXT: {
+                    VkPhysicalDeviceImageCompressionControlSwapchainFeaturesEXT*
+                        compressionFeat = reinterpret_cast<
+                            VkPhysicalDeviceImageCompressionControlSwapchainFeaturesEXT*>(
+                            pFeats);
+                    compressionFeat->imageCompressionControlSwapchain = true;
+                } break;
+
+                default:
+                    break;
+            }
+            pFeats =
+                reinterpret_cast<VkPhysicalDeviceFeatures2*>(pFeats->pNext);
+        }
+    }
 }
 
 void GetPhysicalDeviceProperties2(VkPhysicalDevice physicalDevice,
diff --git a/vulkan/libvulkan/driver.h b/vulkan/libvulkan/driver.h
index 14c516b..4d2bbd6 100644
--- a/vulkan/libvulkan/driver.h
+++ b/vulkan/libvulkan/driver.h
@@ -107,6 +107,8 @@
     VkPhysicalDevice physicalDevice,
     VkPhysicalDevicePresentationPropertiesANDROID* presentation_properties);
 
+bool GetAndroidNativeBufferSpecVersion9Support(VkPhysicalDevice physicalDevice);
+
 VKAPI_ATTR PFN_vkVoidFunction GetInstanceProcAddr(VkInstance instance,
                                                   const char* pName);
 VKAPI_ATTR PFN_vkVoidFunction GetDeviceProcAddr(VkDevice device,
diff --git a/vulkan/libvulkan/driver_gen.cpp b/vulkan/libvulkan/driver_gen.cpp
index b436db1..de98aa7 100644
--- a/vulkan/libvulkan/driver_gen.cpp
+++ b/vulkan/libvulkan/driver_gen.cpp
@@ -496,6 +496,13 @@
         nullptr,
     },
     {
+        "vkGetSwapchainGrallocUsage3ANDROID",
+        ProcHook::DEVICE,
+        ProcHook::ANDROID_native_buffer,
+        nullptr,
+        nullptr,
+    },
+    {
         "vkGetSwapchainGrallocUsageANDROID",
         ProcHook::DEVICE,
         ProcHook::ANDROID_native_buffer,
@@ -664,6 +671,7 @@
     INIT_PROC(false, dev, GetDeviceQueue2);
     INIT_PROC_EXT(ANDROID_native_buffer, false, dev, GetSwapchainGrallocUsageANDROID);
     INIT_PROC_EXT(ANDROID_native_buffer, false, dev, GetSwapchainGrallocUsage2ANDROID);
+    INIT_PROC_EXT(ANDROID_native_buffer, false, dev, GetSwapchainGrallocUsage3ANDROID);
     INIT_PROC_EXT(ANDROID_native_buffer, true, dev, AcquireImageANDROID);
     INIT_PROC_EXT(ANDROID_native_buffer, true, dev, QueueSignalReleaseImageANDROID);
     // clang-format on
diff --git a/vulkan/libvulkan/driver_gen.h b/vulkan/libvulkan/driver_gen.h
index 079f9cc..2f60086 100644
--- a/vulkan/libvulkan/driver_gen.h
+++ b/vulkan/libvulkan/driver_gen.h
@@ -123,6 +123,7 @@
     PFN_vkGetDeviceQueue2 GetDeviceQueue2;
     PFN_vkGetSwapchainGrallocUsageANDROID GetSwapchainGrallocUsageANDROID;
     PFN_vkGetSwapchainGrallocUsage2ANDROID GetSwapchainGrallocUsage2ANDROID;
+    PFN_vkGetSwapchainGrallocUsage3ANDROID GetSwapchainGrallocUsage3ANDROID;
     PFN_vkAcquireImageANDROID AcquireImageANDROID;
     PFN_vkQueueSignalReleaseImageANDROID QueueSignalReleaseImageANDROID;
     // clang-format on
diff --git a/vulkan/libvulkan/libvulkan.map.txt b/vulkan/libvulkan/libvulkan.map.txt
index f49e8f3..b189c68 100644
--- a/vulkan/libvulkan/libvulkan.map.txt
+++ b/vulkan/libvulkan/libvulkan.map.txt
@@ -178,6 +178,7 @@
     vkGetImageSparseMemoryRequirements;
     vkGetImageSparseMemoryRequirements2; # introduced=28
     vkGetImageSubresourceLayout;
+    vkGetImageSubresourceLayout2EXT; # introduced=UpsideDownCake
     vkGetInstanceProcAddr;
     vkGetMemoryAndroidHardwareBufferANDROID; # introduced=28
     vkGetPhysicalDeviceExternalBufferProperties; # introduced=28
diff --git a/vulkan/libvulkan/swapchain.cpp b/vulkan/libvulkan/swapchain.cpp
index abcac3c..759149d 100644
--- a/vulkan/libvulkan/swapchain.cpp
+++ b/vulkan/libvulkan/swapchain.cpp
@@ -715,6 +715,17 @@
     capabilities->minImageExtent = VkExtent2D{1, 1};
     capabilities->maxImageExtent = VkExtent2D{4096, 4096};
 
+    if (capabilities->maxImageExtent.height <
+        capabilities->currentExtent.height) {
+        capabilities->maxImageExtent.height =
+            capabilities->currentExtent.height;
+    }
+
+    if (capabilities->maxImageExtent.width <
+        capabilities->currentExtent.width) {
+        capabilities->maxImageExtent.width = capabilities->currentExtent.width;
+    }
+
     capabilities->maxImageArrayLayers = 1;
 
     capabilities->supportedTransforms = kSupportedTransforms;
@@ -743,7 +754,6 @@
 
     const InstanceData& instance_data = GetData(pdev);
 
-    bool wide_color_support = false;
     uint64_t consumer_usage = 0;
     bool colorspace_ext =
         instance_data.hook_extensions.test(ProcHook::EXT_swapchain_colorspace);
@@ -754,27 +764,15 @@
         if (!surfaceless_enabled) {
             return VK_ERROR_SURFACE_LOST_KHR;
         }
-        // Support for VK_GOOGLE_surfaceless_query.  The EGL loader
-        // unconditionally supports wide color formats, even if they will cause
-        // a SurfaceFlinger fallback.  Based on that, wide_color_support will be
-        // set to true in this case.
-        wide_color_support = true;
+        // Support for VK_GOOGLE_surfaceless_query.
 
         // TODO(b/203826952): research proper value; temporarily use the
         // values seen on Pixel
         consumer_usage = AHARDWAREBUFFER_USAGE_COMPOSER_OVERLAY;
     } else {
         Surface& surface = *SurfaceFromHandle(surface_handle);
-        int err = native_window_get_wide_color_support(surface.window.get(),
-                                                       &wide_color_support);
-        if (err) {
-            return VK_ERROR_SURFACE_LOST_KHR;
-        }
-        ALOGV("wide_color_support is: %d", wide_color_support);
-
         consumer_usage = surface.consumer_usage;
     }
-    wide_color_support = wide_color_support && colorspace_ext;
 
     AHardwareBuffer_Desc desc = {};
     desc.width = 1;
@@ -787,17 +785,15 @@
     std::vector<VkSurfaceFormatKHR> all_formats = {
         {VK_FORMAT_R8G8B8A8_UNORM, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR},
         {VK_FORMAT_R8G8B8A8_SRGB, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR},
-        // Also allow to use PASS_THROUGH + HAL_DATASPACE_ARBITRARY
-        {VK_FORMAT_R8G8B8A8_UNORM, VK_COLOR_SPACE_PASS_THROUGH_EXT},
-        {VK_FORMAT_R8G8B8A8_SRGB, VK_COLOR_SPACE_PASS_THROUGH_EXT},
     };
 
     if (colorspace_ext) {
         all_formats.emplace_back(VkSurfaceFormatKHR{
+            VK_FORMAT_R8G8B8A8_UNORM, VK_COLOR_SPACE_PASS_THROUGH_EXT});
+        all_formats.emplace_back(VkSurfaceFormatKHR{
+            VK_FORMAT_R8G8B8A8_SRGB, VK_COLOR_SPACE_PASS_THROUGH_EXT});
+        all_formats.emplace_back(VkSurfaceFormatKHR{
             VK_FORMAT_R8G8B8A8_UNORM, VK_COLOR_SPACE_BT709_LINEAR_EXT});
-    }
-
-    if (wide_color_support) {
         all_formats.emplace_back(VkSurfaceFormatKHR{
             VK_FORMAT_R8G8B8A8_UNORM, VK_COLOR_SPACE_DISPLAY_P3_NONLINEAR_EXT});
         all_formats.emplace_back(VkSurfaceFormatKHR{
@@ -812,17 +808,21 @@
     if (AHardwareBuffer_isSupported(&desc)) {
         all_formats.emplace_back(VkSurfaceFormatKHR{
             VK_FORMAT_R5G6B5_UNORM_PACK16, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR});
-        all_formats.emplace_back(VkSurfaceFormatKHR{
-            VK_FORMAT_R5G6B5_UNORM_PACK16, VK_COLOR_SPACE_PASS_THROUGH_EXT});
+        if (colorspace_ext) {
+            all_formats.emplace_back(
+                VkSurfaceFormatKHR{VK_FORMAT_R5G6B5_UNORM_PACK16,
+                                   VK_COLOR_SPACE_PASS_THROUGH_EXT});
+        }
     }
 
     desc.format = AHARDWAREBUFFER_FORMAT_R16G16B16A16_FLOAT;
     if (AHardwareBuffer_isSupported(&desc)) {
         all_formats.emplace_back(VkSurfaceFormatKHR{
             VK_FORMAT_R16G16B16A16_SFLOAT, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR});
-        all_formats.emplace_back(VkSurfaceFormatKHR{
-            VK_FORMAT_R16G16B16A16_SFLOAT, VK_COLOR_SPACE_PASS_THROUGH_EXT});
-        if (wide_color_support) {
+        if (colorspace_ext) {
+            all_formats.emplace_back(
+                VkSurfaceFormatKHR{VK_FORMAT_R16G16B16A16_SFLOAT,
+                                   VK_COLOR_SPACE_PASS_THROUGH_EXT});
             all_formats.emplace_back(
                 VkSurfaceFormatKHR{VK_FORMAT_R16G16B16A16_SFLOAT,
                                    VK_COLOR_SPACE_EXTENDED_SRGB_LINEAR_EXT});
@@ -837,10 +837,10 @@
         all_formats.emplace_back(
             VkSurfaceFormatKHR{VK_FORMAT_A2B10G10R10_UNORM_PACK32,
                                VK_COLOR_SPACE_SRGB_NONLINEAR_KHR});
-        all_formats.emplace_back(
-            VkSurfaceFormatKHR{VK_FORMAT_A2B10G10R10_UNORM_PACK32,
-                               VK_COLOR_SPACE_PASS_THROUGH_EXT});
-        if (wide_color_support) {
+        if (colorspace_ext) {
+            all_formats.emplace_back(
+                VkSurfaceFormatKHR{VK_FORMAT_A2B10G10R10_UNORM_PACK32,
+                                   VK_COLOR_SPACE_PASS_THROUGH_EXT});
             all_formats.emplace_back(
                 VkSurfaceFormatKHR{VK_FORMAT_A2B10G10R10_UNORM_PACK32,
                                    VK_COLOR_SPACE_DISPLAY_P3_NONLINEAR_EXT});
@@ -849,9 +849,10 @@
 
     desc.format = AHARDWAREBUFFER_FORMAT_R8_UNORM;
     if (AHardwareBuffer_isSupported(&desc)) {
-        all_formats.emplace_back(
-            VkSurfaceFormatKHR{VK_FORMAT_R8_UNORM,
-                               VK_COLOR_SPACE_PASS_THROUGH_EXT});
+        if (colorspace_ext) {
+            all_formats.emplace_back(VkSurfaceFormatKHR{
+                VK_FORMAT_R8_UNORM, VK_COLOR_SPACE_PASS_THROUGH_EXT});
+        }
     }
 
     // NOTE: Any new formats that are added must be coordinated across different
@@ -929,25 +930,87 @@
         return GetPhysicalDeviceSurfaceFormatsKHR(physicalDevice,
                                                   pSurfaceInfo->surface,
                                                   pSurfaceFormatCount, nullptr);
-    } else {
-        // temp vector for forwarding; we'll marshal it into the pSurfaceFormats
-        // after the call.
-        std::vector<VkSurfaceFormatKHR> surface_formats(*pSurfaceFormatCount);
-        VkResult result = GetPhysicalDeviceSurfaceFormatsKHR(
-            physicalDevice, pSurfaceInfo->surface, pSurfaceFormatCount,
-            surface_formats.data());
+    }
 
-        if (result == VK_SUCCESS || result == VK_INCOMPLETE) {
-            // marshal results individually due to stride difference.
-            // completely ignore any chained extension structs.
-            uint32_t formats_to_marshal = *pSurfaceFormatCount;
-            for (uint32_t i = 0u; i < formats_to_marshal; i++) {
-                pSurfaceFormats[i].surfaceFormat = surface_formats[i];
-            }
-        }
+    // temp vector for forwarding; we'll marshal it into the pSurfaceFormats
+    // after the call.
+    std::vector<VkSurfaceFormatKHR> surface_formats(*pSurfaceFormatCount);
+    VkResult result = GetPhysicalDeviceSurfaceFormatsKHR(
+        physicalDevice, pSurfaceInfo->surface, pSurfaceFormatCount,
+        surface_formats.data());
 
+    if (result != VK_SUCCESS && result != VK_INCOMPLETE) {
         return result;
     }
+
+    const auto& driver = GetData(physicalDevice).driver;
+
+    // marshal results individually due to stride difference.
+    uint32_t formats_to_marshal = *pSurfaceFormatCount;
+    for (uint32_t i = 0u; i < formats_to_marshal; i++) {
+        pSurfaceFormats[i].surfaceFormat = surface_formats[i];
+
+        // Query the compression properties for the surface format
+        VkSurfaceFormat2KHR* pSurfaceFormat = &pSurfaceFormats[i];
+        while (pSurfaceFormat->pNext) {
+            pSurfaceFormat =
+                reinterpret_cast<VkSurfaceFormat2KHR*>(pSurfaceFormat->pNext);
+            switch (pSurfaceFormat->sType) {
+                case VK_STRUCTURE_TYPE_IMAGE_COMPRESSION_PROPERTIES_EXT: {
+                    VkImageCompressionPropertiesEXT* surfaceCompressionProps =
+                        reinterpret_cast<VkImageCompressionPropertiesEXT*>(
+                            pSurfaceFormat);
+
+                    if (surfaceCompressionProps &&
+                        driver.GetPhysicalDeviceImageFormatProperties2KHR) {
+                        VkPhysicalDeviceImageFormatInfo2 imageFormatInfo = {};
+                        imageFormatInfo.sType =
+                            VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_FORMAT_INFO_2;
+                        imageFormatInfo.format =
+                            pSurfaceFormats[i].surfaceFormat.format;
+                        imageFormatInfo.pNext = nullptr;
+
+                        VkImageCompressionControlEXT compressionControl = {};
+                        compressionControl.sType =
+                            VK_STRUCTURE_TYPE_IMAGE_COMPRESSION_CONTROL_EXT;
+                        compressionControl.pNext = imageFormatInfo.pNext;
+
+                        imageFormatInfo.pNext = &compressionControl;
+
+                        VkImageCompressionPropertiesEXT compressionProps = {};
+                        compressionProps.sType =
+                            VK_STRUCTURE_TYPE_IMAGE_COMPRESSION_PROPERTIES_EXT;
+                        compressionProps.pNext = nullptr;
+
+                        VkImageFormatProperties2KHR imageFormatProps = {};
+                        imageFormatProps.sType =
+                            VK_STRUCTURE_TYPE_IMAGE_FORMAT_PROPERTIES_2_KHR;
+                        imageFormatProps.pNext = &compressionProps;
+
+                        VkResult compressionRes =
+                            driver.GetPhysicalDeviceImageFormatProperties2KHR(
+                                physicalDevice, &imageFormatInfo,
+                                &imageFormatProps);
+                        if (compressionRes == VK_SUCCESS) {
+                            surfaceCompressionProps->imageCompressionFlags =
+                                compressionProps.imageCompressionFlags;
+                            surfaceCompressionProps
+                                ->imageCompressionFixedRateFlags =
+                                compressionProps.imageCompressionFixedRateFlags;
+                        } else {
+                            return compressionRes;
+                        }
+                    }
+                } break;
+
+                default:
+                    // Ignore all other extension structs
+                    break;
+            }
+        }
+    }
+
+    return result;
 }
 
 VKAPI_ATTR
@@ -1360,8 +1423,48 @@
         num_images = 1;
     }
 
+    void* usage_info_pNext = nullptr;
+    VkImageCompressionControlEXT image_compression = {};
     uint64_t native_usage = 0;
-    if (dispatch.GetSwapchainGrallocUsage2ANDROID) {
+    if (dispatch.GetSwapchainGrallocUsage3ANDROID) {
+        ATRACE_BEGIN("GetSwapchainGrallocUsage3ANDROID");
+        VkGrallocUsageInfoANDROID gralloc_usage_info = {};
+        gralloc_usage_info.sType = VK_STRUCTURE_TYPE_GRALLOC_USAGE_INFO_ANDROID;
+        gralloc_usage_info.format = create_info->imageFormat;
+        gralloc_usage_info.imageUsage = create_info->imageUsage;
+
+        // Look through the pNext chain for an image compression control struct
+        // if one is found AND the appropriate extensions are enabled,
+        // append it to be the gralloc usage pNext chain
+        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;
+                    usage_info_pNext = &image_compression;
+                } break;
+
+                default:
+                    // Ignore all other info structs
+                    break;
+            }
+        }
+        gralloc_usage_info.pNext = usage_info_pNext;
+
+        result = dispatch.GetSwapchainGrallocUsage3ANDROID(
+            device, &gralloc_usage_info, &native_usage);
+        ATRACE_END();
+        if (result != VK_SUCCESS) {
+            ALOGE("vkGetSwapchainGrallocUsage3ANDROID failed: %d", result);
+            return VK_ERROR_SURFACE_LOST_KHR;
+        }
+    } else if (dispatch.GetSwapchainGrallocUsage2ANDROID) {
         uint64_t consumer_usage, producer_usage;
         ATRACE_BEGIN("GetSwapchainGrallocUsage2ANDROID");
         result = dispatch.GetSwapchainGrallocUsage2ANDROID(
@@ -1373,7 +1476,7 @@
             return VK_ERROR_SURFACE_LOST_KHR;
         }
         native_usage =
-            convertGralloc1ToBufferUsage(consumer_usage, producer_usage);
+            convertGralloc1ToBufferUsage(producer_usage, consumer_usage);
     } else if (dispatch.GetSwapchainGrallocUsageANDROID) {
         ATRACE_BEGIN("GetSwapchainGrallocUsageANDROID");
         int32_t legacy_usage = 0;
@@ -1427,7 +1530,7 @@
 #pragma clang diagnostic ignored "-Wold-style-cast"
         .sType = VK_STRUCTURE_TYPE_SWAPCHAIN_IMAGE_CREATE_INFO_ANDROID,
 #pragma clang diagnostic pop
-        .pNext = nullptr,
+        .pNext = usage_info_pNext,
         .usage = swapchain_image_usage,
     };
     VkNativeBufferANDROID image_native_buffer = {
@@ -1485,6 +1588,7 @@
         android_convertGralloc0To1Usage(int(img.buffer->usage),
             &image_native_buffer.usage2.producer,
             &image_native_buffer.usage2.consumer);
+        image_native_buffer.usage3 = img.buffer->usage;
 
         ATRACE_BEGIN("CreateImage");
         result =
diff --git a/vulkan/nulldrv/null_driver.cpp b/vulkan/nulldrv/null_driver.cpp
index 3c91150..f998b1a 100644
--- a/vulkan/nulldrv/null_driver.cpp
+++ b/vulkan/nulldrv/null_driver.cpp
@@ -948,6 +948,17 @@
     return VK_SUCCESS;
 }
 
+VkResult GetSwapchainGrallocUsage3ANDROID(
+    VkDevice,
+    const VkGrallocUsageInfoANDROID* grallocUsageInfo,
+    uint64_t* grallocUsage) {
+    // The null driver never reads or writes the gralloc buffer
+    ALOGV("TODO: vk%s - grallocUsageInfo->format:%i", __FUNCTION__,
+          grallocUsageInfo->format);
+    *grallocUsage = 0;
+    return VK_SUCCESS;
+}
+
 VkResult AcquireImageANDROID(VkDevice,
                              VkImage,
                              int fence,
diff --git a/vulkan/nulldrv/null_driver_gen.cpp b/vulkan/nulldrv/null_driver_gen.cpp
index f6dcf09..0cb7bd3 100644
--- a/vulkan/nulldrv/null_driver_gen.cpp
+++ b/vulkan/nulldrv/null_driver_gen.cpp
@@ -261,6 +261,7 @@
     {"vkGetRenderAreaGranularity", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkGetRenderAreaGranularity>(GetRenderAreaGranularity))},
     {"vkGetSemaphoreCounterValue", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkGetSemaphoreCounterValue>(GetSemaphoreCounterValue))},
     {"vkGetSwapchainGrallocUsage2ANDROID", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkGetSwapchainGrallocUsage2ANDROID>(GetSwapchainGrallocUsage2ANDROID))},
+    {"vkGetSwapchainGrallocUsage3ANDROID", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkGetSwapchainGrallocUsage3ANDROID>(GetSwapchainGrallocUsage3ANDROID))},
     {"vkGetSwapchainGrallocUsageANDROID", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkGetSwapchainGrallocUsageANDROID>(GetSwapchainGrallocUsageANDROID))},
     {"vkInvalidateMappedMemoryRanges", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkInvalidateMappedMemoryRanges>(InvalidateMappedMemoryRanges))},
     {"vkMapMemory", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkMapMemory>(MapMemory))},
diff --git a/vulkan/nulldrv/null_driver_gen.h b/vulkan/nulldrv/null_driver_gen.h
index 3e003e3..5c7fea0 100644
--- a/vulkan/nulldrv/null_driver_gen.h
+++ b/vulkan/nulldrv/null_driver_gen.h
@@ -209,6 +209,7 @@
 VKAPI_ATTR void GetDescriptorSetLayoutSupport(VkDevice device, const VkDescriptorSetLayoutCreateInfo* pCreateInfo, VkDescriptorSetLayoutSupport* pSupport);
 VKAPI_ATTR VkResult GetSwapchainGrallocUsageANDROID(VkDevice device, VkFormat format, VkImageUsageFlags imageUsage, int* grallocUsage);
 VKAPI_ATTR VkResult GetSwapchainGrallocUsage2ANDROID(VkDevice device, VkFormat format, VkImageUsageFlags imageUsage, VkSwapchainImageUsageFlagsANDROID swapchainImageUsage, uint64_t* grallocConsumerUsage, uint64_t* grallocProducerUsage);
+VKAPI_ATTR VkResult GetSwapchainGrallocUsage3ANDROID(VkDevice device, const VkGrallocUsageInfoANDROID* grallocUsageInfo, uint64_t* grallocUsage);
 VKAPI_ATTR VkResult AcquireImageANDROID(VkDevice device, VkImage image, int nativeFenceFd, VkSemaphore semaphore, VkFence fence);
 VKAPI_ATTR VkResult QueueSignalReleaseImageANDROID(VkQueue queue, uint32_t waitSemaphoreCount, const VkSemaphore* pWaitSemaphores, VkImage image, int* pNativeFenceFd);
 VKAPI_ATTR VkResult CreateRenderPass2(VkDevice device, const VkRenderPassCreateInfo2* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkRenderPass* pRenderPass);
diff --git a/vulkan/scripts/generator_common.py b/vulkan/scripts/generator_common.py
index 4176509..c25c6cb 100644
--- a/vulkan/scripts/generator_common.py
+++ b/vulkan/scripts/generator_common.py
@@ -69,6 +69,7 @@
 _OPTIONAL_COMMANDS = [
     'vkGetSwapchainGrallocUsageANDROID',
     'vkGetSwapchainGrallocUsage2ANDROID',
+    'vkGetSwapchainGrallocUsage3ANDROID',
 ]
 
 # Dict for mapping dispatch table to a type.