Merge "binder: Don't use reinterpret_cast to down cast" am: 171a64a823 am: 2ed961592f am: 4726666296

Original change: https://android-review.googlesource.com/c/platform/frameworks/native/+/2043432

Change-Id: I0b5e0d2c68a2581814f4fbcc1ae4e580a4dbd042
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index 7ef26bf..6d837c2 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -28,6 +28,7 @@
                services/vibratorservice/
                services/vr/
                vulkan/
+bpfmt = -d
 
 [Hook Scripts]
 owners_hook = ${REPO_ROOT}/frameworks/base/tools/aosp/aosp_sha.sh ${PREUPLOAD_COMMIT} "OWNERS$"
diff --git a/cmds/atrace/atrace.cpp b/cmds/atrace/atrace.cpp
index 9bd733d..6fb9a4d 100644
--- a/cmds/atrace/atrace.cpp
+++ b/cmds/atrace/atrace.cpp
@@ -244,6 +244,7 @@
         { OPT,      "events/kmem/ion_heap_shrink/enable" },
         { OPT,      "events/ion/ion_stat/enable" },
         { OPT,      "events/gpu_mem/gpu_mem_total/enable" },
+        { OPT,      "events/fastrpc/fastrpc_dma_stat/enable" },
     } },
     { "thermal",  "Thermal event", ATRACE_TAG_THERMAL, {
         { REQ,      "events/thermal/thermal_temperature/enable" },
diff --git a/cmds/installd/InstalldNativeService.cpp b/cmds/installd/InstalldNativeService.cpp
index b2a6ccf..c104fea 100644
--- a/cmds/installd/InstalldNativeService.cpp
+++ b/cmds/installd/InstalldNativeService.cpp
@@ -56,7 +56,7 @@
 #include <cutils/properties.h>
 #include <cutils/sched_policy.h>
 #include <linux/quota.h>
-#include <log/log.h> // TODO: Move everything to base/logging.
+#include <log/log.h>               // TODO: Move everything to base/logging.
 #include <logwrap/logwrap.h>
 #include <private/android_filesystem_config.h>
 #include <private/android_projectid_config.h>
@@ -80,7 +80,7 @@
 #define LOG_TAG "installd"
 #endif
 
-// #define GRANULAR_LOCKS
+#define GRANULAR_LOCKS
 
 using android::base::ParseUint;
 using android::base::Split;
@@ -464,7 +464,6 @@
     free(after);
     return res;
 }
-
 static bool internal_storage_has_project_id() {
     // The following path is populated in setFirstBoot, so if this file is present
     // then project ids can be used. Using call once to cache the result of this check
@@ -1889,7 +1888,7 @@
 }
 
 binder::Status InstalldNativeService::freeCache(const std::optional<std::string>& uuid,
-        int64_t targetFreeBytes, int64_t cacheReservedBytes, int32_t flags) {
+        int64_t targetFreeBytes, int32_t flags) {
     ENFORCE_UID(AID_SYSTEM);
     CHECK_ARGUMENT_UUID(uuid);
 #ifndef GRANULAR_LOCKS
@@ -1900,6 +1899,7 @@
     const char* uuid_ = uuid ? uuid->c_str() : nullptr;
     auto data_path = create_data_path(uuid_);
     auto noop = (flags & FLAG_FREE_CACHE_NOOP);
+    auto defy_target = (flags & FLAG_FREE_CACHE_DEFY_TARGET_FREE_BYTES);
 
     int64_t free = data_disk_free(data_path);
     if (free < 0) {
@@ -1908,11 +1908,13 @@
 
     int64_t cleared = 0;
     int64_t needed = targetFreeBytes - free;
-    LOG(DEBUG) << "Device " << data_path << " has " << free << " free; requested "
-            << targetFreeBytes << "; needed " << needed;
+    if (!defy_target) {
+        LOG(DEBUG) << "Device " << data_path << " has " << free << " free; requested "
+                << targetFreeBytes << "; needed " << needed;
 
-    if (free >= targetFreeBytes) {
-        return ok();
+        if (free >= targetFreeBytes) {
+            return ok();
+        }
     }
 
     if (flags & FLAG_FREE_CACHE_V2) {
@@ -2005,12 +2007,6 @@
                 break;
             }
 
-            // Only keep clearing when we haven't pushed into reserved area
-            if (cacheReservedBytes > 0 && cleared >= (cacheTotal - cacheReservedBytes)) {
-                LOG(DEBUG) << "Refusing to clear cached data in reserved space";
-                break;
-            }
-
             // Find the best tracker to work with; this might involve swapping
             // if the active tracker is no longer the most over quota
             bool nextBetter = active && !queue.empty()
@@ -2043,15 +2039,17 @@
                 cleared += item->size;
             }
 
-            // Verify that we're actually done before bailing, since sneaky
-            // apps might be using hardlinks
-            if (needed <= 0) {
-                free = data_disk_free(data_path);
-                needed = targetFreeBytes - free;
+            if (!defy_target) {
+                // Verify that we're actually done before bailing, since sneaky
+                // apps might be using hardlinks
                 if (needed <= 0) {
-                    break;
-                } else {
-                    LOG(WARNING) << "Expected to be done but still need " << needed;
+                    free = data_disk_free(data_path);
+                    needed = targetFreeBytes - free;
+                    if (needed <= 0) {
+                        break;
+                    } else {
+                        LOG(WARNING) << "Expected to be done but still need " << needed;
+                    }
                 }
             }
         }
@@ -2061,12 +2059,16 @@
         return error("Legacy cache logic no longer supported");
     }
 
-    free = data_disk_free(data_path);
-    if (free >= targetFreeBytes) {
-        return ok();
+    if (!defy_target) {
+        free = data_disk_free(data_path);
+        if (free >= targetFreeBytes) {
+            return ok();
+        } else {
+            return error(StringPrintf("Failed to free up %" PRId64 " on %s; final free space %" PRId64,
+                    targetFreeBytes, data_path.c_str(), free));
+        }
     } else {
-        return error(StringPrintf("Failed to free up %" PRId64 " on %s; final free space %" PRId64,
-                targetFreeBytes, data_path.c_str(), free));
+        return ok();
     }
 }
 
@@ -2119,6 +2121,7 @@
     return res.str();
 }
 #endif
+
 // On devices without sdcardfs, if internal and external are on
 // the same volume, a uid such as u0_a123 is used for both
 // internal and external storage; therefore, subtract that
@@ -2332,6 +2335,7 @@
     if (internal_storage_has_project_id()) {
         return false;
     }
+
     //  Fetch external storage owner appid  and check if it is the same as the
     //  current appId whose size is calculated
     struct stat s;
@@ -3013,22 +3017,20 @@
         const char* default_value = nullptr) {
     return data ? data->c_str() : default_value;
 }
-binder::Status InstalldNativeService::dexopt(const std::string& apkPath, int32_t uid,
-        const std::optional<std::string>& packageName, const std::string& instructionSet,
-        int32_t dexoptNeeded, const std::optional<std::string>& outputPath, int32_t dexFlags,
+binder::Status InstalldNativeService::dexopt(
+        const std::string& apkPath, int32_t uid, const std::string& packageName,
+        const std::string& instructionSet, int32_t dexoptNeeded,
+        const std::optional<std::string>& outputPath, int32_t dexFlags,
         const std::string& compilerFilter, const std::optional<std::string>& uuid,
         const std::optional<std::string>& classLoaderContext,
         const std::optional<std::string>& seInfo, bool downgrade, int32_t targetSdkVersion,
         const std::optional<std::string>& profileName,
         const std::optional<std::string>& dexMetadataPath,
-        const std::optional<std::string>& compilationReason,
-        bool* aidl_return) {
+        const std::optional<std::string>& compilationReason, bool* aidl_return) {
     ENFORCE_UID(AID_SYSTEM);
     CHECK_ARGUMENT_UUID(uuid);
     CHECK_ARGUMENT_PATH(apkPath);
-    if (packageName && *packageName != "*") {
-        CHECK_ARGUMENT_PACKAGE_NAME(*packageName);
-    }
+    CHECK_ARGUMENT_PACKAGE_NAME(packageName);
     CHECK_ARGUMENT_PATH(outputPath);
     CHECK_ARGUMENT_PATH(dexMetadataPath);
     const auto userId = multiuser_get_user_id(uid);
@@ -3036,13 +3038,13 @@
 
     const char* oat_dir = getCStr(outputPath);
     const char* instruction_set = instructionSet.c_str();
-    if (oat_dir != nullptr && !createOatDir(oat_dir, instruction_set).isOk()) {
+    if (oat_dir != nullptr && !createOatDir(packageName, oat_dir, instruction_set).isOk()) {
         // Can't create oat dir - let dexopt use cache dir.
         oat_dir = nullptr;
     }
 
     const char* apk_path = apkPath.c_str();
-    const char* pkgname = getCStr(packageName, "*");
+    const char* pkgname = packageName.c_str();
     const char* compiler_filter = compilerFilter.c_str();
     const char* volume_uuid = getCStr(uuid);
     const char* class_loader_context = getCStr(classLoaderContext);
@@ -3252,9 +3254,11 @@
     return res;
 }
 
-binder::Status InstalldNativeService::createOatDir(const std::string& oatDir,
-        const std::string& instructionSet) {
+binder::Status InstalldNativeService::createOatDir(const std::string& packageName,
+                                                   const std::string& oatDir,
+                                                   const std::string& instructionSet) {
     ENFORCE_UID(AID_SYSTEM);
+    CHECK_ARGUMENT_PACKAGE_NAME(packageName);
     CHECK_ARGUMENT_PATH(oatDir);
     LOCK_PACKAGE();
 
@@ -3278,7 +3282,8 @@
     return ok();
 }
 
-binder::Status InstalldNativeService::rmPackageDir(const std::string& packageDir) {
+binder::Status InstalldNativeService::rmPackageDir(const std::string& packageName,
+                                                   const std::string& packageDir) {
     ENFORCE_UID(AID_SYSTEM);
     CHECK_ARGUMENT_PATH(packageDir);
     LOCK_PACKAGE();
@@ -3292,9 +3297,12 @@
     return ok();
 }
 
-binder::Status InstalldNativeService::linkFile(const std::string& relativePath,
-        const std::string& fromBase, const std::string& toBase) {
+binder::Status InstalldNativeService::linkFile(const std::string& packageName,
+                                               const std::string& relativePath,
+                                               const std::string& fromBase,
+                                               const std::string& toBase) {
     ENFORCE_UID(AID_SYSTEM);
+    CHECK_ARGUMENT_PACKAGE_NAME(packageName);
     CHECK_ARGUMENT_PATH(fromBase);
     CHECK_ARGUMENT_PATH(toBase);
     LOCK_PACKAGE();
@@ -3322,9 +3330,12 @@
     return ok();
 }
 
-binder::Status InstalldNativeService::moveAb(const std::string& apkPath,
-        const std::string& instructionSet, const std::string& outputPath) {
+binder::Status InstalldNativeService::moveAb(const std::string& packageName,
+                                             const std::string& apkPath,
+                                             const std::string& instructionSet,
+                                             const std::string& outputPath) {
     ENFORCE_UID(AID_SYSTEM);
+    CHECK_ARGUMENT_PACKAGE_NAME(packageName);
     CHECK_ARGUMENT_PATH(apkPath);
     CHECK_ARGUMENT_PATH(outputPath);
     LOCK_PACKAGE();
@@ -3337,10 +3348,13 @@
     return success ? ok() : error();
 }
 
-binder::Status InstalldNativeService::deleteOdex(const std::string& apkPath,
-        const std::string& instructionSet, const std::optional<std::string>& outputPath,
-        int64_t* _aidl_return) {
+binder::Status InstalldNativeService::deleteOdex(const std::string& packageName,
+                                                 const std::string& apkPath,
+                                                 const std::string& instructionSet,
+                                                 const std::optional<std::string>& outputPath,
+                                                 int64_t* _aidl_return) {
     ENFORCE_UID(AID_SYSTEM);
+    CHECK_ARGUMENT_PACKAGE_NAME(packageName);
     CHECK_ARGUMENT_PATH(apkPath);
     CHECK_ARGUMENT_PATH(outputPath);
     LOCK_PACKAGE();
@@ -3385,15 +3399,20 @@
         dexPath, packageName, uid, volumeUuid, storageFlag, _aidl_return);
     return result ? ok() : error();
 }
-
+/**
+ * Returns true if ioctl feature (F2FS_IOC_FS{GET,SET}XATTR) is supported as
+ * these were introduced in Linux 4.14, so kernel versions before that will fail
+ * while setting project id attributes. Only when these features are enabled,
+ * storage calculation using project_id is enabled
+ */
 bool check_if_ioctl_feature_is_supported() {
     bool result = false;
     auto temp_path = StringPrintf("%smisc/installd/ioctl_check", android_data_dir.c_str());
     if (access(temp_path.c_str(), F_OK) != 0) {
-        open(temp_path.c_str(), O_CREAT | O_TRUNC | O_RDWR | O_CLOEXEC, 0644);
+        int fd = open(temp_path.c_str(), O_CREAT | O_TRUNC | O_RDWR | O_CLOEXEC, 0644);
         result = set_quota_project_id(temp_path, 0, true) == 0;
+        close(fd);
         // delete the temp file
-        // remove the external file
         remove(temp_path.c_str());
     }
     return result;
@@ -3407,7 +3426,7 @@
         auto first_boot_path =
                 StringPrintf("%smisc/installd/using_project_ids", android_data_dir.c_str());
         if (access(first_boot_path.c_str(), F_OK) != 0) {
-            open(first_boot_path.c_str(), O_CREAT | O_TRUNC | O_RDWR | O_CLOEXEC, 0644);
+            close(open(first_boot_path.c_str(), O_CREAT | O_TRUNC | O_RDWR | O_CLOEXEC, 0644));
         }
     }
     return ok();
@@ -3632,5 +3651,23 @@
     return ok();
 }
 
+binder::Status InstalldNativeService::getOdexVisibility(
+        const std::string& packageName, const std::string& apkPath,
+        const std::string& instructionSet, const std::optional<std::string>& outputPath,
+        int32_t* _aidl_return) {
+    ENFORCE_UID(AID_SYSTEM);
+    CHECK_ARGUMENT_PACKAGE_NAME(packageName);
+    CHECK_ARGUMENT_PATH(apkPath);
+    CHECK_ARGUMENT_PATH(outputPath);
+    LOCK_PACKAGE();
+
+    const char* apk_path = apkPath.c_str();
+    const char* instruction_set = instructionSet.c_str();
+    const char* oat_dir = outputPath ? outputPath->c_str() : nullptr;
+
+    *_aidl_return = get_odex_visibility(apk_path, instruction_set, oat_dir);
+    return *_aidl_return == -1 ? error() : ok();
+}
+
 }  // namespace installd
 }  // namespace android
diff --git a/cmds/installd/InstalldNativeService.h b/cmds/installd/InstalldNativeService.h
index 95ac516..87a9206 100644
--- a/cmds/installd/InstalldNativeService.h
+++ b/cmds/installd/InstalldNativeService.h
@@ -114,16 +114,15 @@
             int32_t appId, const std::string& seInfo,
             int32_t targetSdkVersion, const std::string& fromCodePath);
 
-    binder::Status dexopt(const std::string& apkPath, int32_t uid,
-            const std::optional<std::string>& packageName, const std::string& instructionSet,
-            int32_t dexoptNeeded, const std::optional<std::string>& outputPath, int32_t dexFlags,
-            const std::string& compilerFilter, const std::optional<std::string>& uuid,
-            const std::optional<std::string>& classLoaderContext,
-            const std::optional<std::string>& seInfo, bool downgrade,
-            int32_t targetSdkVersion, const std::optional<std::string>& profileName,
-            const std::optional<std::string>& dexMetadataPath,
-            const std::optional<std::string>& compilationReason,
-            bool* aidl_return);
+    binder::Status dexopt(const std::string& apkPath, int32_t uid, const std::string& packageName,
+                          const std::string& instructionSet, int32_t dexoptNeeded,
+                          const std::optional<std::string>& outputPath, int32_t dexFlags,
+                          const std::string& compilerFilter, const std::optional<std::string>& uuid,
+                          const std::optional<std::string>& classLoaderContext,
+                          const std::optional<std::string>& seInfo, bool downgrade,
+                          int32_t targetSdkVersion, const std::optional<std::string>& profileName,
+                          const std::optional<std::string>& dexMetadataPath,
+                          const std::optional<std::string>& compilationReason, bool* aidl_return);
 
     binder::Status controlDexOptBlocking(bool block);
 
@@ -147,18 +146,20 @@
     binder::Status destroyProfileSnapshot(const std::string& packageName,
             const std::string& profileName);
 
-    binder::Status rmPackageDir(const std::string& packageDir);
+    binder::Status rmPackageDir(const std::string& packageName, const std::string& packageDir);
     binder::Status freeCache(const std::optional<std::string>& uuid, int64_t targetFreeBytes,
-            int64_t cacheReservedBytes, int32_t flags);
+            int32_t flags);
     binder::Status linkNativeLibraryDirectory(const std::optional<std::string>& uuid,
             const std::string& packageName, const std::string& nativeLibPath32, int32_t userId);
-    binder::Status createOatDir(const std::string& oatDir, const std::string& instructionSet);
-    binder::Status linkFile(const std::string& relativePath, const std::string& fromBase,
-            const std::string& toBase);
-    binder::Status moveAb(const std::string& apkPath, const std::string& instructionSet,
-            const std::string& outputPath);
-    binder::Status deleteOdex(const std::string& apkPath, const std::string& instructionSet,
-            const std::optional<std::string>& outputPath, int64_t* _aidl_return);
+    binder::Status createOatDir(const std::string& packageName, const std::string& oatDir,
+                                const std::string& instructionSet);
+    binder::Status linkFile(const std::string& packageName, const std::string& relativePath,
+                            const std::string& fromBase, const std::string& toBase);
+    binder::Status moveAb(const std::string& packageName, const std::string& apkPath,
+                          const std::string& instructionSet, const std::string& outputPath);
+    binder::Status deleteOdex(const std::string& packageName, const std::string& apkPath,
+                              const std::string& instructionSet,
+                              const std::optional<std::string>& outputPath, int64_t* _aidl_return);
     binder::Status reconcileSecondaryDexFile(const std::string& dexPath,
         const std::string& packageName, int32_t uid, const std::vector<std::string>& isa,
         const std::optional<std::string>& volumeUuid, int32_t storage_flag, bool* _aidl_return);
@@ -183,6 +184,11 @@
     binder::Status cleanupInvalidPackageDirs(const std::optional<std::string>& uuid, int32_t userId,
                                              int32_t flags);
 
+    binder::Status getOdexVisibility(const std::string& packageName, const std::string& apkPath,
+                                     const std::string& instructionSet,
+                                     const std::optional<std::string>& outputPath,
+                                     int32_t* _aidl_return);
+
 private:
     std::recursive_mutex mLock;
     std::unordered_map<userid_t, std::weak_ptr<std::shared_mutex>> mUserIdLock;
diff --git a/cmds/installd/binder/android/os/IInstalld.aidl b/cmds/installd/binder/android/os/IInstalld.aidl
index c17c6bf..79c02e8 100644
--- a/cmds/installd/binder/android/os/IInstalld.aidl
+++ b/cmds/installd/binder/android/os/IInstalld.aidl
@@ -58,7 +58,7 @@
             @utf8InCpp String seInfo, int targetSdkVersion, @utf8InCpp String fromCodePath);
 
     // Returns false if it is cancelled. Returns true if it is completed or have other errors.
-    boolean dexopt(@utf8InCpp String apkPath, int uid, @nullable @utf8InCpp String packageName,
+    boolean dexopt(@utf8InCpp String apkPath, int uid, @utf8InCpp String packageName,
             @utf8InCpp String instructionSet, int dexoptNeeded,
             @nullable @utf8InCpp String outputPath, int dexFlags,
             @utf8InCpp String compilerFilter, @nullable @utf8InCpp String uuid,
@@ -87,18 +87,18 @@
             @utf8InCpp String profileName, @utf8InCpp String classpath);
     void destroyProfileSnapshot(@utf8InCpp String packageName, @utf8InCpp String profileName);
 
-    void rmPackageDir(@utf8InCpp String packageDir);
-    void freeCache(@nullable @utf8InCpp String uuid, long targetFreeBytes,
-            long cacheReservedBytes, int flags);
+    void rmPackageDir(@utf8InCpp String packageName, @utf8InCpp String packageDir);
+    void freeCache(@nullable @utf8InCpp String uuid, long targetFreeBytes, int flags);
     void linkNativeLibraryDirectory(@nullable @utf8InCpp String uuid,
             @utf8InCpp String packageName, @utf8InCpp String nativeLibPath32, int userId);
-    void createOatDir(@utf8InCpp String oatDir, @utf8InCpp String instructionSet);
-    void linkFile(@utf8InCpp String relativePath, @utf8InCpp String fromBase,
-            @utf8InCpp String toBase);
-    void moveAb(@utf8InCpp String apkPath, @utf8InCpp String instructionSet,
-            @utf8InCpp String outputPath);
-    long deleteOdex(@utf8InCpp String apkPath, @utf8InCpp String instructionSet,
-            @nullable @utf8InCpp String outputPath);
+    void createOatDir(@utf8InCpp String packageName, @utf8InCpp String oatDir,
+            @utf8InCpp String instructionSet);
+    void linkFile(@utf8InCpp String packageName, @utf8InCpp String relativePath,
+            @utf8InCpp String fromBase, @utf8InCpp String toBase);
+    void moveAb(@utf8InCpp String packageName, @utf8InCpp String apkPath,
+            @utf8InCpp String instructionSet, @utf8InCpp String outputPath);
+    long deleteOdex(@utf8InCpp String packageName, @utf8InCpp String apkPath,
+            @utf8InCpp String instructionSet, @nullable @utf8InCpp String outputPath);
 
     boolean reconcileSecondaryDexFile(@utf8InCpp String dexPath, @utf8InCpp String pkgName,
         int uid, in @utf8InCpp String[] isas, @nullable @utf8InCpp String volume_uuid,
@@ -130,6 +130,9 @@
 
     void cleanupInvalidPackageDirs(@nullable @utf8InCpp String uuid, int userId, int flags);
 
+    int getOdexVisibility(@utf8InCpp String packageName, @utf8InCpp String apkPath,
+            @utf8InCpp String instructionSet, @nullable @utf8InCpp String outputPath);
+
     const int FLAG_STORAGE_DE = 0x1;
     const int FLAG_STORAGE_CE = 0x2;
     const int FLAG_STORAGE_EXTERNAL = 0x4;
@@ -141,6 +144,8 @@
     const int FLAG_FREE_CACHE_V2 = 0x100;
     const int FLAG_FREE_CACHE_V2_DEFY_QUOTA = 0x200;
     const int FLAG_FREE_CACHE_NOOP = 0x400;
+    // Set below flag to clear cache irrespective of target free bytes required
+    const int FLAG_FREE_CACHE_DEFY_TARGET_FREE_BYTES = 0x800;
 
     const int FLAG_USE_QUOTA = 0x1000;
     const int FLAG_FORCE = 0x2000;
diff --git a/cmds/installd/dexopt.cpp b/cmds/installd/dexopt.cpp
index 9647865..894c7d3 100644
--- a/cmds/installd/dexopt.cpp
+++ b/cmds/installd/dexopt.cpp
@@ -2773,13 +2773,23 @@
                          const std::string& profile_name,
                          const std::string& code_path,
                          const std::optional<std::string>& dex_metadata) {
-    // Prepare the current profile.
-    std::string cur_profile  = create_current_profile_path(user_id, package_name, profile_name,
-            /*is_secondary_dex*/ false);
-    uid_t uid = multiuser_get_uid(user_id, app_id);
-    if (fs_prepare_file_strict(cur_profile.c_str(), 0600, uid, uid) != 0) {
-        PLOG(ERROR) << "Failed to prepare " << cur_profile;
-        return false;
+    if (user_id != USER_NULL) {
+        if (user_id < 0) {
+            LOG(ERROR) << "Unexpected user ID " << user_id;
+            return false;
+        }
+
+        // Prepare the current profile.
+        std::string cur_profile = create_current_profile_path(user_id, package_name, profile_name,
+                                                              /*is_secondary_dex*/ false);
+        uid_t uid = multiuser_get_uid(user_id, app_id);
+        if (fs_prepare_file_strict(cur_profile.c_str(), 0600, uid, uid) != 0) {
+            PLOG(ERROR) << "Failed to prepare " << cur_profile;
+            return false;
+        }
+    } else {
+        // Prepare the reference profile as the system user.
+        user_id = USER_SYSTEM;
     }
 
     // Check if we need to install the profile from the dex metadata.
@@ -2788,8 +2798,9 @@
     }
 
     // We have a dex metdata. Merge the profile into the reference profile.
-    unique_fd ref_profile_fd = open_reference_profile(uid, package_name, profile_name,
-            /*read_write*/ true, /*is_secondary_dex*/ false);
+    unique_fd ref_profile_fd =
+            open_reference_profile(multiuser_get_uid(user_id, app_id), package_name, profile_name,
+                                   /*read_write*/ true, /*is_secondary_dex*/ false);
     unique_fd dex_metadata_fd(TEMP_FAILURE_RETRY(
             open(dex_metadata->c_str(), O_RDONLY | O_NOFOLLOW)));
     unique_fd apk_fd(TEMP_FAILURE_RETRY(open(code_path.c_str(), O_RDONLY | O_NOFOLLOW)));
@@ -2823,5 +2834,22 @@
     return true;
 }
 
+int get_odex_visibility(const char* apk_path, const char* instruction_set, const char* oat_dir) {
+    char oat_path[PKG_PATH_MAX];
+    if (!create_oat_out_path(apk_path, instruction_set, oat_dir, /*is_secondary_dex=*/false,
+                             oat_path)) {
+        return -1;
+    }
+    struct stat st;
+    if (stat(oat_path, &st) == -1) {
+        if (errno == ENOENT) {
+            return ODEX_NOT_FOUND;
+        }
+        PLOG(ERROR) << "Could not stat " << oat_path;
+        return -1;
+    }
+    return (st.st_mode & S_IROTH) ? ODEX_IS_PUBLIC : ODEX_IS_PRIVATE;
+}
+
 }  // namespace installd
 }  // namespace android
diff --git a/cmds/installd/dexopt.h b/cmds/installd/dexopt.h
index 12579b0..f7af929 100644
--- a/cmds/installd/dexopt.h
+++ b/cmds/installd/dexopt.h
@@ -98,10 +98,9 @@
                          const std::string& pkgname,
                          const std::string& profile_name);
 
-// Prepare the app profile for the given code path:
-//  - create the current profile using profile_name
-//  - merge the profile from the dex metadata file (if present) into
-//    the reference profile.
+// Prepares the app profile for the package at the given path:
+// - Creates the current profile for the given user ID, unless the user ID is `USER_NULL`.
+// - Merges the profile from the dex metadata file (if present) into the reference profile.
 bool prepare_app_profile(const std::string& package_name,
                          userid_t user_id,
                          appid_t app_id,
@@ -153,6 +152,12 @@
         bool is_release,
         bool is_debuggable_build);
 
+// Returns `ODEX_NOT_FOUND` if the optimized artifacts are not found, or `ODEX_IS_PUBLIC` if the
+// optimized artifacts are accessible by all apps, or `ODEX_IS_PRIVATE` if the optimized artifacts
+// are only accessible by this app, or -1 if failed to get the visibility of the optimized
+// artifacts.
+int get_odex_visibility(const char* apk_path, const char* instruction_set, const char* oat_dir);
+
 }  // namespace installd
 }  // namespace android
 
diff --git a/cmds/installd/installd_constants.h b/cmds/installd/installd_constants.h
index 00d8441..3623f9b 100644
--- a/cmds/installd/installd_constants.h
+++ b/cmds/installd/installd_constants.h
@@ -18,6 +18,8 @@
 #ifndef INSTALLD_CONSTANTS_H_
 #define INSTALLD_CONSTANTS_H_
 
+#include "cutils/multiuser.h"
+
 namespace android {
 namespace installd {
 
@@ -83,6 +85,15 @@
 constexpr int PROFILES_ANALYSIS_DONT_OPTIMIZE_SMALL_DELTA    = 2;
 constexpr int PROFILES_ANALYSIS_DONT_OPTIMIZE_EMPTY_PROFILES = 3;
 
+// NOTE: keep in sync with Installer.java
+constexpr int ODEX_NOT_FOUND = 0;
+constexpr int ODEX_IS_PUBLIC = 1;
+constexpr int ODEX_IS_PRIVATE = 2;
+
+// NOTE: keep in sync with UserHandle.java
+constexpr userid_t USER_NULL = -10000;
+constexpr userid_t USER_SYSTEM = 0;
+
 #define ARRAY_SIZE(a) (sizeof(a) / sizeof(*(a)))
 
 }  // namespace installd
diff --git a/cmds/installd/tests/installd_cache_test.cpp b/cmds/installd/tests/installd_cache_test.cpp
index 72f5f4b..4976646 100644
--- a/cmds/installd/tests/installd_cache_test.cpp
+++ b/cmds/installd/tests/installd_cache_test.cpp
@@ -42,6 +42,7 @@
 
 #define FLAG_FREE_CACHE_V2 InstalldNativeService::FLAG_FREE_CACHE_V2
 #define FLAG_FREE_CACHE_V2_DEFY_QUOTA InstalldNativeService::FLAG_FREE_CACHE_V2_DEFY_QUOTA
+#define FLAG_FREE_CACHE_DEFY_TARGET_FREE_BYTES InstalldNativeService::FLAG_FREE_CACHE_DEFY_TARGET_FREE_BYTES
 
 int get_property(const char *key, char *value, const char *default_value) {
     return property_get(key, value, default_value);
@@ -146,7 +147,7 @@
     EXPECT_EQ(0, exists("com.example/cache/foo/one"));
     EXPECT_EQ(0, exists("com.example/cache/foo/two"));
 
-    service->freeCache(testUuid, kTbInBytes, 0,
+    service->freeCache(testUuid, kTbInBytes,
             FLAG_FREE_CACHE_V2 | FLAG_FREE_CACHE_V2_DEFY_QUOTA);
 
     EXPECT_EQ(0, exists("com.example/normal"));
@@ -154,6 +155,62 @@
     EXPECT_EQ(-1, exists("com.example/cache/foo/two"));
 }
 
+TEST_F(CacheTest, FreeCache_NonAggressive) {
+    LOG(INFO) << "FreeCache_NonAggressive";
+
+    mkdir("com.example");
+    touch("com.example/normal", 1 * kMbInBytes, 60);
+    mkdir("com.example/cache");
+    mkdir("com.example/cache/foo");
+    touch("com.example/cache/foo/one", 65 * kMbInBytes, 60);
+    touch("com.example/cache/foo/two", 2 * kMbInBytes, 120);
+
+    EXPECT_EQ(0, exists("com.example/normal"));
+    EXPECT_EQ(0, exists("com.example/cache/foo/one"));
+    EXPECT_EQ(0, exists("com.example/cache/foo/two"));
+
+    service->freeCache(testUuid, kTbInBytes, FLAG_FREE_CACHE_V2);
+
+    EXPECT_EQ(0, exists("com.example/normal"));
+    EXPECT_EQ(-1, exists("com.example/cache/foo/one"));
+    EXPECT_EQ(0, exists("com.example/cache/foo/two"));
+
+    service->freeCache(testUuid, kTbInBytes, FLAG_FREE_CACHE_V2);
+
+    EXPECT_EQ(0, exists("com.example/normal"));
+    EXPECT_EQ(-1, exists("com.example/cache/foo/one"));
+    EXPECT_EQ(0, exists("com.example/cache/foo/two"));
+}
+
+TEST_F(CacheTest, FreeCache_DefyTargetFreeBytes) {
+    LOG(INFO) << "FreeCache_DefyTargetFreeBytes";
+
+    mkdir("com.example");
+    touch("com.example/normal", 1 * kMbInBytes, 60);
+    mkdir("com.example/cache");
+    mkdir("com.example/cache/foo");
+    touch("com.example/cache/foo/one", 65 * kMbInBytes, 60);
+    touch("com.example/cache/foo/two", 2 * kMbInBytes, 120);
+
+    EXPECT_EQ(0, exists("com.example/normal"));
+    EXPECT_EQ(0, exists("com.example/cache/foo/one"));
+    EXPECT_EQ(0, exists("com.example/cache/foo/two"));
+
+    service->freeCache(testUuid, kMbInBytes, FLAG_FREE_CACHE_V2
+            | FLAG_FREE_CACHE_DEFY_TARGET_FREE_BYTES);
+
+    EXPECT_EQ(0, exists("com.example/normal"));
+    EXPECT_EQ(-1, exists("com.example/cache/foo/one"));
+    EXPECT_EQ(0, exists("com.example/cache/foo/two"));
+
+    service->freeCache(testUuid, kMbInBytes, FLAG_FREE_CACHE_V2
+            | FLAG_FREE_CACHE_DEFY_TARGET_FREE_BYTES);
+
+    EXPECT_EQ(0, exists("com.example/normal"));
+    EXPECT_EQ(-1, exists("com.example/cache/foo/one"));
+    EXPECT_EQ(0, exists("com.example/cache/foo/two"));
+}
+
 TEST_F(CacheTest, FreeCache_Age) {
     LOG(INFO) << "FreeCache_Age";
 
@@ -163,13 +220,13 @@
     touch("com.example/cache/foo/one", kMbInBytes, 60);
     touch("com.example/cache/foo/two", kMbInBytes, 120);
 
-    service->freeCache(testUuid, free() + kKbInBytes, 0,
+    service->freeCache(testUuid, free() + kKbInBytes,
             FLAG_FREE_CACHE_V2 | FLAG_FREE_CACHE_V2_DEFY_QUOTA);
 
     EXPECT_EQ(-1, exists("com.example/cache/foo/one"));
     EXPECT_EQ(0, exists("com.example/cache/foo/two"));
 
-    service->freeCache(testUuid, free() + kKbInBytes, 0,
+    service->freeCache(testUuid, free() + kKbInBytes,
             FLAG_FREE_CACHE_V2 | FLAG_FREE_CACHE_V2_DEFY_QUOTA);
 
     EXPECT_EQ(-1, exists("com.example/cache/foo/one"));
@@ -197,7 +254,7 @@
     EXPECT_EQ(2 * kMbInBytes, size("com.example/cache/bar/bar1"));
     EXPECT_EQ(2 * kMbInBytes, size("com.example/cache/bar/bar2"));
 
-    service->freeCache(testUuid, kTbInBytes, 0,
+    service->freeCache(testUuid, kTbInBytes,
             FLAG_FREE_CACHE_V2 | FLAG_FREE_CACHE_V2_DEFY_QUOTA);
 
     EXPECT_EQ(-1, exists("com.example/cache/foo/foo1"));
@@ -219,7 +276,7 @@
 
     setxattr("com.example/cache/foo", "user.cache_group");
 
-    service->freeCache(testUuid, free() + kKbInBytes, 0,
+    service->freeCache(testUuid, free() + kKbInBytes,
             FLAG_FREE_CACHE_V2 | FLAG_FREE_CACHE_V2_DEFY_QUOTA);
 
     EXPECT_EQ(-1, exists("com.example/cache/foo/foo1"));
@@ -264,7 +321,7 @@
     setxattr("com.example/cache/tomb", "user.cache_tombstone");
     setxattr("com.example/cache/tomb/group", "user.cache_group");
 
-    service->freeCache(testUuid, free() + kKbInBytes, 0,
+    service->freeCache(testUuid, free() + kKbInBytes,
             FLAG_FREE_CACHE_V2 | FLAG_FREE_CACHE_V2_DEFY_QUOTA);
 
     EXPECT_EQ(kMbInBytes, size("com.example/cache/group/file1"));
@@ -285,7 +342,7 @@
     EXPECT_EQ(0, size("com.example/cache/tomb/group/dir/file1"));
     EXPECT_EQ(0, size("com.example/cache/tomb/group/dir/file2"));
 
-    service->freeCache(testUuid, free() + kKbInBytes, 0,
+    service->freeCache(testUuid, free() + kKbInBytes,
             FLAG_FREE_CACHE_V2 | FLAG_FREE_CACHE_V2_DEFY_QUOTA);
 
     EXPECT_EQ(-1, size("com.example/cache/group/file1"));
@@ -306,7 +363,7 @@
     EXPECT_EQ(0, size("com.example/cache/tomb/group/dir/file1"));
     EXPECT_EQ(0, size("com.example/cache/tomb/group/dir/file2"));
 
-    service->freeCache(testUuid, kTbInBytes, 0,
+    service->freeCache(testUuid, kTbInBytes,
             FLAG_FREE_CACHE_V2 | FLAG_FREE_CACHE_V2_DEFY_QUOTA);
 
     EXPECT_EQ(-1, size("com.example/cache/group/file1"));
diff --git a/cmds/installd/tests/installd_dexopt_test.cpp b/cmds/installd/tests/installd_dexopt_test.cpp
index aeba451..4eb30e2 100644
--- a/cmds/installd/tests/installd_dexopt_test.cpp
+++ b/cmds/installd/tests/installd_dexopt_test.cpp
@@ -637,6 +637,7 @@
 
         int64_t bytes_freed;
         binder::Status result = service_->deleteOdex(
+            package_name_,
             apk_path_,
             kRuntimeIsa,
             in_dalvik_cache ? std::nullopt : std::make_optional<std::string>(app_oat_dir_.c_str()),
@@ -649,6 +650,36 @@
 
         ASSERT_EQ(expected_bytes_freed, bytes_freed);
     }
+
+    void checkVisibility(bool in_dalvik_cache, int32_t expected_visibility) {
+        int32_t visibility;
+        ASSERT_BINDER_SUCCESS(service_->getOdexVisibility(package_name_, apk_path_, kRuntimeIsa,
+                                                          in_dalvik_cache
+                                                                  ? std::nullopt
+                                                                  : std::make_optional<std::string>(
+                                                                            app_oat_dir_.c_str()),
+                                                          &visibility));
+        EXPECT_EQ(visibility, expected_visibility);
+    }
+
+    void TestGetOdexVisibility(bool in_dalvik_cache) {
+        const char* oat_dir = in_dalvik_cache ? nullptr : app_oat_dir_.c_str();
+
+        checkVisibility(in_dalvik_cache, ODEX_NOT_FOUND);
+
+        CompilePrimaryDexOk("speed-profile",
+                            DEXOPT_BOOTCOMPLETE | DEXOPT_PROFILE_GUIDED | DEXOPT_PUBLIC |
+                                    DEXOPT_GENERATE_APP_IMAGE,
+                            oat_dir, kTestAppGid, DEX2OAT_FROM_SCRATCH,
+                            /*binder_result=*/nullptr, empty_dm_file_.c_str());
+        checkVisibility(in_dalvik_cache, ODEX_IS_PUBLIC);
+
+        CompilePrimaryDexOk("speed-profile",
+                            DEXOPT_BOOTCOMPLETE | DEXOPT_PROFILE_GUIDED | DEXOPT_GENERATE_APP_IMAGE,
+                            oat_dir, kTestAppGid, DEX2OAT_FROM_SCRATCH,
+                            /*binder_result=*/nullptr, empty_dm_file_.c_str());
+        checkVisibility(in_dalvik_cache, ODEX_IS_PRIVATE);
+    }
 };
 
 
@@ -731,7 +762,7 @@
 
 TEST_F(DexoptTest, DexoptPrimaryPublicCreateOatDir) {
     LOG(INFO) << "DexoptPrimaryPublic";
-    ASSERT_BINDER_SUCCESS(service_->createOatDir(app_oat_dir_, kRuntimeIsa));
+    ASSERT_BINDER_SUCCESS(service_->createOatDir(package_name_, app_oat_dir_, kRuntimeIsa));
     CompilePrimaryDexOk("verify",
                         DEXOPT_BOOTCOMPLETE | DEXOPT_PUBLIC,
                         app_oat_dir_.c_str(),
@@ -829,6 +860,16 @@
     TestDeleteOdex(/*in_dalvik_cache=*/ true);
 }
 
+TEST_F(DexoptTest, GetOdexVisibilityData) {
+    LOG(INFO) << "GetOdexVisibilityData";
+    TestGetOdexVisibility(/*in_dalvik_cache=*/false);
+}
+
+TEST_F(DexoptTest, GetOdexVisibilityDalvikCache) {
+    LOG(INFO) << "GetOdexVisibilityDalvikCache";
+    TestGetOdexVisibility(/*in_dalvik_cache=*/true);
+}
+
 TEST_F(DexoptTest, ResolveStartupConstStrings) {
     LOG(INFO) << "DexoptDex2oatResolveStartupStrings";
     const std::string property = "persist.device_config.runtime.dex2oat_resolve_startup_strings";
@@ -1104,13 +1145,16 @@
         ASSERT_TRUE(AreFilesEqual(ref_profile_content, ref_profile_));
     }
 
-    // TODO(calin): add dex metadata tests once the ART change is merged.
     void preparePackageProfile(const std::string& package_name, const std::string& profile_name,
-            bool expected_result) {
+                               bool has_dex_metadata, bool has_user_id, bool expected_result) {
         bool result;
-        ASSERT_BINDER_SUCCESS(service_->prepareAppProfile(
-                package_name, kTestUserId, kTestAppId, profile_name, apk_path_,
-                /*dex_metadata*/ {}, &result));
+        ASSERT_BINDER_SUCCESS(
+                service_->prepareAppProfile(package_name, has_user_id ? kTestUserId : USER_NULL,
+                                            kTestAppId, profile_name, apk_path_,
+                                            has_dex_metadata ? std::make_optional<std::string>(
+                                                                       empty_dm_file_)
+                                                             : std::nullopt,
+                                            &result));
         ASSERT_EQ(expected_result, result);
 
         if (!expected_result) {
@@ -1118,16 +1162,29 @@
             return;
         }
 
-        std::string code_path_cur_prof = create_current_profile_path(
-                kTestUserId, package_name, profile_name, /*is_secondary_dex*/ false);
-        std::string code_path_ref_profile = create_reference_profile_path(package_name,
-                profile_name, /*is_secondary_dex*/ false);
+        std::string code_path_cur_prof =
+                create_current_profile_path(kTestUserId, package_name, profile_name,
+                                            /*is_secondary_dex*/ false);
+        std::string code_path_ref_profile =
+                create_reference_profile_path(package_name, profile_name,
+                                              /*is_secondary_dex*/ false);
 
-        // Check that we created the current profile.
-        CheckFileAccess(code_path_cur_prof, kTestAppUid, kTestAppUid, 0600 | S_IFREG);
+        if (has_user_id) {
+            // Check that we created the current profile.
+            CheckFileAccess(code_path_cur_prof, kTestAppUid, kTestAppUid, 0600 | S_IFREG);
+        } else {
+            // Without a user ID, we don't generate a current profile.
+            ASSERT_EQ(-1, access(code_path_cur_prof.c_str(), R_OK));
+        }
 
-        // Without dex metadata we don't generate a reference profile.
-        ASSERT_EQ(-1, access(code_path_ref_profile.c_str(), R_OK));
+        if (has_dex_metadata) {
+            int32_t uid = has_user_id ? kTestAppUid : multiuser_get_uid(USER_SYSTEM, kTestAppId);
+            // Check that we created the reference profile.
+            CheckFileAccess(code_path_ref_profile, uid, uid, 0640 | S_IFREG);
+        } else {
+            // Without dex metadata, we don't generate a reference profile.
+            ASSERT_EQ(-1, access(code_path_ref_profile.c_str(), R_OK));
+        }
     }
 
   protected:
@@ -1272,12 +1329,32 @@
 
 TEST_F(ProfileTest, ProfilePrepareOk) {
     LOG(INFO) << "ProfilePrepareOk";
-    preparePackageProfile(package_name_, "split.prof", /*expected_result*/ true);
+    preparePackageProfile(package_name_, "split.prof", /*has_dex_metadata*/ true,
+                          /*has_user_id*/ true, /*expected_result*/ true);
+}
+
+TEST_F(ProfileTest, ProfilePrepareOkNoUser) {
+    LOG(INFO) << "ProfilePrepareOk";
+    preparePackageProfile(package_name_, "split.prof", /*has_dex_metadata*/ true,
+                          /*has_user_id*/ false, /*expected_result*/ true);
+}
+
+TEST_F(ProfileTest, ProfilePrepareOkNoDm) {
+    LOG(INFO) << "ProfilePrepareOk";
+    preparePackageProfile(package_name_, "split.prof", /*has_dex_metadata*/ false,
+                          /*has_user_id*/ true, /*expected_result*/ true);
+}
+
+TEST_F(ProfileTest, ProfilePrepareOkNoUserNoDm) {
+    LOG(INFO) << "ProfilePrepareOk";
+    preparePackageProfile(package_name_, "split.prof", /*has_dex_metadata*/ false,
+                          /*has_user_id*/ false, /*expected_result*/ true);
 }
 
 TEST_F(ProfileTest, ProfilePrepareFailInvalidPackage) {
     LOG(INFO) << "ProfilePrepareFailInvalidPackage";
-    preparePackageProfile("not.there.package", "split.prof", /*expected_result*/ false);
+    preparePackageProfile("not.there.package", "split.prof", /*has_dex_metadata*/ true,
+                          /*has_user_id*/ true, /*expected_result*/ false);
 }
 
 TEST_F(ProfileTest, ProfilePrepareFailProfileChangedUid) {
@@ -1285,7 +1362,8 @@
     SetupProfiles(/*setup_ref*/ false);
     // Change the uid on the profile to trigger a failure.
     ::chown(cur_profile_.c_str(), kTestAppUid + 1, kTestAppGid + 1);
-    preparePackageProfile(package_name_, "primary.prof", /*expected_result*/ false);
+    preparePackageProfile(package_name_, "primary.prof", /*has_dex_metadata*/ true,
+                          /*has_user_id*/ true, /*expected_result*/ false);
 }
 
 
diff --git a/cmds/installd/tests/installd_service_test.cpp b/cmds/installd/tests/installd_service_test.cpp
index cf88856..38cb370 100644
--- a/cmds/installd/tests/installd_service_test.cpp
+++ b/cmds/installd/tests/installd_service_test.cpp
@@ -465,7 +465,7 @@
     EXPECT_TRUE(create_cache_path(buf, "/path/to/file.apk", "isa"));
     EXPECT_EQ("/data/dalvik-cache/isa/path@to@file.apk@classes.dex", std::string(buf));
 }
-TEST_F(ServiceTest, GetAppSizeManualForMedia) {
+TEST_F(ServiceTest, GetAppSize) {
     struct stat s;
 
     std::string externalPicDir =
@@ -509,81 +509,6 @@
         system(removeCommand.c_str());
     }
 }
-TEST_F(ServiceTest, GetAppSizeProjectID_UID) {
-    struct stat s;
-    std::string externalPicDir =
-            StringPrintf("%s/Pictures", create_data_media_path(nullptr, 0).c_str());
-    if (stat(externalPicDir.c_str(), &s) == 0) {
-        // fetch the appId from the uid of the external storage owning app
-        int32_t externalStorageAppId = multiuser_get_app_id(s.st_uid);
-        // Fetch Package Name for the external storage owning app uid
-        std::string pkg = get_package_name(s.st_uid);
-
-        std::vector<int64_t> externalStorageSize, externalStorageSizeAfterAddingCacheFile;
-        std::vector<int64_t> ceDataInodes;
-
-        std::vector<std::string> codePaths;
-        std::vector<std::string> packageNames;
-        // set up parameters
-        packageNames.push_back(pkg);
-        ceDataInodes.push_back(0);
-        // initialise the mounts
-        service->invalidateMounts();
-        auto using_project_ids =
-                StringPrintf("%smisc/installd/using_project_ids", android_data_dir.c_str());
-        bool usingProjectIds = access(using_project_ids.c_str(), F_OK) == 0;
-        if (!usingProjectIds) {
-            service->setFirstBoot();
-        }
-        // call the getAppSize to get the current size of the external storage owning app
-        service->getAppSize(std::nullopt, packageNames, 0, InstalldNativeService::FLAG_USE_QUOTA,
-                            externalStorageAppId, ceDataInodes, codePaths, &externalStorageSize);
-        // add a file with 20MB size to the external storage
-        std::string externalStorageCacheDir =
-                StringPrintf("%s/%s/cache", create_data_user_ce_path(nullptr, 0).c_str(),
-                             pkg.c_str());
-        std::string cacheFileLocation =
-                StringPrintf("%s/%s", externalStorageCacheDir.c_str(), "External.jpg");
-        std::string externalFileContentCommand =
-                StringPrintf("dd if=/dev/zero of=%s bs=1M count=20", cacheFileLocation.c_str());
-        system(externalFileContentCommand.c_str());
-        // call the getAppSize again to get the new size of the external storage owning app
-        service->getAppSize(std::nullopt, packageNames, 0, InstalldNativeService::FLAG_USE_QUOTA,
-                            externalStorageAppId, ceDataInodes, codePaths,
-                            &externalStorageSizeAfterAddingCacheFile);
-        // check that the size of cache and data increases when cache file is added
-        int64_t sizeDiffData = externalStorageSizeAfterAddingCacheFile[1] - externalStorageSize[1];
-        int64_t sizeDiffCache = externalStorageSizeAfterAddingCacheFile[2] - externalStorageSize[2];
-        ASSERT_TRUE(sizeDiffData == sizeDiffCache);
-        // remove the external file
-        std::string removeCommand = StringPrintf("rm -f %s", cacheFileLocation.c_str());
-        system(removeCommand.c_str());
-        // remove the setFirstBoot setting
-        std::string removeCommand2 = "rm -f /data/misc/installd/using_project_ids";
-        system(removeCommand2.c_str());
-        // Do now without project id
-        std::vector<int64_t> sizeWithUID, sizeWithUIDAfterAddingCacheFile;
-        // call the getAppSize to get the current size of the external storage owning app
-        service->getAppSize(std::nullopt, packageNames, 0, InstalldNativeService::FLAG_USE_QUOTA,
-                            externalStorageAppId, ceDataInodes, codePaths, &sizeWithUID);
-        // add a file with 20MB size to the external storage
-        system(externalFileContentCommand.c_str());
-        // call the getAppSize again to get the new size of the external storage owning app
-        service->getAppSize(std::nullopt, packageNames, 0, InstalldNativeService::FLAG_USE_QUOTA,
-                            externalStorageAppId, ceDataInodes, codePaths,
-                            &sizeWithUIDAfterAddingCacheFile);
-        // check that the size of cache and data increases when cache file is added
-        sizeDiffData = sizeWithUIDAfterAddingCacheFile[1] - sizeWithUID[1];
-        sizeDiffCache = sizeWithUIDAfterAddingCacheFile[2] - sizeWithUID[2];
-        ASSERT_TRUE(sizeDiffData == sizeDiffCache);
-        // remove the external file
-        system(removeCommand.c_str());
-        // reset the using_project_id if it was initially set
-        if (usingProjectIds) {
-            service->setFirstBoot();
-        }
-    }
-}
 TEST_F(ServiceTest, GetAppSizeWrongSizes) {
     int32_t externalStorageAppId = -1;
     std::vector<int64_t> externalStorageSize;
diff --git a/cmds/installd/utils.cpp b/cmds/installd/utils.cpp
index 2ed971d..9801a9b 100644
--- a/cmds/installd/utils.cpp
+++ b/cmds/installd/utils.cpp
@@ -424,6 +424,7 @@
 
     return users;
 }
+
 long get_project_id(uid_t uid, long start_project_id_range) {
     return uid - AID_APP_START + start_project_id_range;
 }
@@ -462,6 +463,7 @@
     }
     return 0;
 }
+
 int calculate_tree_size(const std::string& path, int64_t* size,
         int32_t include_gid, int32_t exclude_gid, bool exclude_apps) {
     FTS *fts;
diff --git a/cmds/surfacereplayer/replayer/Replayer.cpp b/cmds/surfacereplayer/replayer/Replayer.cpp
index cfd42fe..3f7c7d6 100644
--- a/cmds/surfacereplayer/replayer/Replayer.cpp
+++ b/cmds/surfacereplayer/replayer/Replayer.cpp
@@ -528,7 +528,7 @@
 void Replayer::setLayerStack(SurfaceComposerClient::Transaction& t,
         layer_id id, const LayerStackChange& lsc) {
     ALOGV("Layer %d: Setting LayerStack -- layer_stack=%d", id, lsc.layer_stack());
-    t.setLayerStack(mLayers[id], lsc.layer_stack());
+    t.setLayerStack(mLayers[id], ui::LayerStack::fromValue(lsc.layer_stack()));
 }
 
 void Replayer::setHiddenFlag(SurfaceComposerClient::Transaction& t,
@@ -566,7 +566,7 @@
 
 void Replayer::setDisplayLayerStack(SurfaceComposerClient::Transaction& t,
         display_id id, const LayerStackChange& lsc) {
-    t.setDisplayLayerStack(mDisplays[id], lsc.layer_stack());
+    t.setDisplayLayerStack(mDisplays[id], ui::LayerStack::fromValue(lsc.layer_stack()));
 }
 
 void Replayer::setDisplaySize(SurfaceComposerClient::Transaction& t,
diff --git a/data/etc/Android.bp b/data/etc/Android.bp
index c901c59..5003151 100644
--- a/data/etc/Android.bp
+++ b/data/etc/Android.bp
@@ -119,6 +119,12 @@
 }
 
 prebuilt_etc {
+    name: "android.hardware.sensor.dynamic.head_tracker.prebuilt.xml",
+    src: "android.hardware.sensor.dynamic.head_tracker.xml",
+    defaults: ["frameworks_native_data_etc_defaults"],
+}
+
+prebuilt_etc {
     name: "android.hardware.sensor.gyroscope.prebuilt.xml",
     src: "android.hardware.sensor.gyroscope.xml",
     defaults: ["frameworks_native_data_etc_defaults"],
@@ -255,11 +261,3 @@
     src: "handheld_core_hardware.xml",
     defaults: ["frameworks_native_data_etc_defaults"],
 }
-
-prebuilt_etc {
-    name: "android.software.app_compat_overrides.xml",
-    product_specific: true,
-    sub_dir: "permissions",
-    src: "android.software.app_compat_overrides.xml",
-    filename_from_src: true,
-}
diff --git a/data/etc/android.hardware.sensor.accelerometer_limited_axes.xml b/data/etc/android.hardware.sensor.accelerometer_limited_axes.xml
new file mode 100644
index 0000000..6a43d5d
--- /dev/null
+++ b/data/etc/android.hardware.sensor.accelerometer_limited_axes.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<!-- Feature for devices with a limited axes accelerometer sensor. -->
+<permissions>
+    <feature name="android.hardware.sensor.accelerometer_limited_axes" />
+</permissions>
diff --git a/data/etc/android.hardware.sensor.accelerometer_limited_axes_uncalibrated.xml b/data/etc/android.hardware.sensor.accelerometer_limited_axes_uncalibrated.xml
new file mode 100644
index 0000000..4c6755c
--- /dev/null
+++ b/data/etc/android.hardware.sensor.accelerometer_limited_axes_uncalibrated.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<!-- Feature for devices with an uncalibrated limited axes accelerometer sensor. -->
+<permissions>
+    <feature name="android.hardware.sensor.accelerometer_limited_axes_uncalibrated" />
+</permissions>
diff --git a/data/etc/android.hardware.sensor.dynamic.head_tracker.xml b/data/etc/android.hardware.sensor.dynamic.head_tracker.xml
new file mode 100644
index 0000000..ece58c9
--- /dev/null
+++ b/data/etc/android.hardware.sensor.dynamic.head_tracker.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<!-- Feature for devices with a head tracker sensor. -->
+<permissions>
+    <feature name="android.hardware.sensor.dynamic.head_tracker" />
+</permissions>
\ No newline at end of file
diff --git a/data/etc/android.hardware.sensor.gyroscope_limited_axes.xml b/data/etc/android.hardware.sensor.gyroscope_limited_axes.xml
new file mode 100644
index 0000000..45c6547
--- /dev/null
+++ b/data/etc/android.hardware.sensor.gyroscope_limited_axes.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<!-- Feature for devices with a limited axes gyroscope. -->
+<permissions>
+    <feature name="android.hardware.sensor.gyroscope_limited_axes" />
+</permissions>
diff --git a/data/etc/android.hardware.sensor.gyroscope_limited_axes_uncalibrated.xml b/data/etc/android.hardware.sensor.gyroscope_limited_axes_uncalibrated.xml
new file mode 100644
index 0000000..2ca8364
--- /dev/null
+++ b/data/etc/android.hardware.sensor.gyroscope_limited_axes_uncalibrated.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<!-- Feature for devices with an uncalibrated limited axes gyroscope. -->
+<permissions>
+    <feature name="android.hardware.sensor.gyroscope_limited_axes_uncalibrated" />
+</permissions>
diff --git a/data/etc/android.hardware.sensor.heading.xml b/data/etc/android.hardware.sensor.heading.xml
new file mode 100644
index 0000000..8b81823
--- /dev/null
+++ b/data/etc/android.hardware.sensor.heading.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<!-- Feature for devices with a heading sensor. -->
+<permissions>
+    <feature name="android.hardware.sensor.heading" />
+</permissions>
diff --git a/data/etc/android.hardware.telephony.calling.xml b/data/etc/android.hardware.telephony.calling.xml
new file mode 100644
index 0000000..ab245fc
--- /dev/null
+++ b/data/etc/android.hardware.telephony.calling.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<!-- This is the standard set of features for devices to support Telephony Calling API. -->
+<permissions>
+    <feature name="android.hardware.telephony" />
+    <feature name="android.software.telecom" />
+    <feature name="android.hardware.telephony.radio.access" />
+    <feature name="android.hardware.telephony.subscription" />
+    <feature name="android.hardware.telephony.calling" />
+</permissions>
diff --git a/data/etc/android.hardware.telephony.cdma.xml b/data/etc/android.hardware.telephony.cdma.xml
index 082378d..0a8cfc4 100644
--- a/data/etc/android.hardware.telephony.cdma.xml
+++ b/data/etc/android.hardware.telephony.cdma.xml
@@ -4,9 +4,9 @@
      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.
@@ -17,5 +17,11 @@
 <!-- This is the standard set of telephony features for a CDMA phone. -->
 <permissions>
     <feature name="android.hardware.telephony" />
+    <feature name="android.software.telecom" />
+    <feature name="android.hardware.telephony.radio.access" />
+    <feature name="android.hardware.telephony.subscription" />
     <feature name="android.hardware.telephony.cdma" />
+    <feature name="android.hardware.telephony.calling" />
+    <feature name="android.hardware.telephony.data" />
+    <feature name="android.hardware.telephony.messaging" />
 </permissions>
diff --git a/data/etc/android.hardware.telephony.data.xml b/data/etc/android.hardware.telephony.data.xml
new file mode 100644
index 0000000..a9bb36e
--- /dev/null
+++ b/data/etc/android.hardware.telephony.data.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<!-- This is the standard set of features for devices to support Telephony Data API. -->
+<permissions>
+    <feature name="android.hardware.telephony" />
+    <feature name="android.hardware.telephony.radio.access" />
+    <feature name="android.hardware.telephony.subscription" />
+    <feature name="android.hardware.telephony.data" />
+</permissions>
diff --git a/data/etc/android.hardware.telephony.euicc.mep.xml b/data/etc/android.hardware.telephony.euicc.mep.xml
new file mode 100644
index 0000000..f958e4b
--- /dev/null
+++ b/data/etc/android.hardware.telephony.euicc.mep.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<!-- Feature for devices with an eUICC supporting MEP(Multiple enabled profiles) -->
+<permissions>
+    <feature name="android.hardware.telephony.euicc" />
+    <feature name="android.hardware.telephony.euicc.mep" />
+</permissions>
\ No newline at end of file
diff --git a/data/etc/android.hardware.telephony.gsm.xml b/data/etc/android.hardware.telephony.gsm.xml
index 7927fa8..e3952bf 100644
--- a/data/etc/android.hardware.telephony.gsm.xml
+++ b/data/etc/android.hardware.telephony.gsm.xml
@@ -4,9 +4,9 @@
      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.
@@ -17,5 +17,11 @@
 <!-- This is the standard set of telephony features for a GSM phone. -->
 <permissions>
     <feature name="android.hardware.telephony" />
+    <feature name="android.software.telecom" />
+    <feature name="android.hardware.telephony.radio.access" />
+    <feature name="android.hardware.telephony.subscription" />
     <feature name="android.hardware.telephony.gsm" />
+    <feature name="android.hardware.telephony.calling" />
+    <feature name="android.hardware.telephony.data" />
+    <feature name="android.hardware.telephony.messaging" />
 </permissions>
diff --git a/data/etc/android.hardware.telephony.radio.access.xml b/data/etc/android.hardware.telephony.radio.access.xml
new file mode 100644
index 0000000..c98267c
--- /dev/null
+++ b/data/etc/android.hardware.telephony.radio.access.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<!-- This is the standard set of features for devices to support Telephony Radio Access API. -->
+<permissions>
+  <feature name="android.hardware.telephony" />
+  <feature name="android.hardware.telephony.radio.access" />
+</permissions>
diff --git a/data/etc/android.hardware.telephony.subscription.xml b/data/etc/android.hardware.telephony.subscription.xml
new file mode 100644
index 0000000..449a2a6
--- /dev/null
+++ b/data/etc/android.hardware.telephony.subscription.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<!-- This is the standard set of features for devices to support Telephony Subscription API. -->
+<permissions>
+  <feature name="android.hardware.telephony" />
+  <feature name="android.hardware.telephony.subscription" />
+</permissions>
diff --git a/data/etc/android.hardware.type.automotive.xml b/data/etc/android.hardware.type.automotive.xml
index a9b4b05..113945b 100644
--- a/data/etc/android.hardware.type.automotive.xml
+++ b/data/etc/android.hardware.type.automotive.xml
@@ -17,4 +17,6 @@
 <!-- These features determine that the device running android is a car. -->
 <permissions>
     <feature name="android.hardware.type.automotive" />
+    <!-- TODO: Revert this after enabling work profiles refer b/170332519 -->
+    <unavailable-feature name="android.software.managed_users"/>
 </permissions>
diff --git a/data/etc/android.hardware.vulkan.version-1_3.xml b/data/etc/android.hardware.vulkan.version-1_3.xml
new file mode 100644
index 0000000..4ecea7b
--- /dev/null
+++ b/data/etc/android.hardware.vulkan.version-1_3.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<!-- This is the standard feature indicating that the device has a Vulkan
+     driver that supports API version 1.3 (0x00403000) -->
+<permissions>
+    <feature name="android.hardware.vulkan.version" version="4206592" />
+</permissions>
diff --git a/data/etc/android.software.telecom.xml b/data/etc/android.software.telecom.xml
new file mode 100644
index 0000000..db28ba6
--- /dev/null
+++ b/data/etc/android.software.telecom.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<!-- This is the standard set of features for devices that support the Telecom API. -->
+<permissions>
+    <feature name="android.software.telecom" />
+</permissions>
diff --git a/data/etc/android.software.window_magnification.xml b/data/etc/android.software.window_magnification.xml
new file mode 100644
index 0000000..10a2de1
--- /dev/null
+++ b/data/etc/android.software.window_magnification.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<!-- This is the feature indicating device supports window magnifier.
+     And the hand-held and PC platform should support this.
+-->
+<permissions>
+    <feature name="android.software.window_magnification" />
+</permissions>
diff --git a/data/etc/car_core_hardware.xml b/data/etc/car_core_hardware.xml
index cc0ee82..95b8110 100644
--- a/data/etc/car_core_hardware.xml
+++ b/data/etc/car_core_hardware.xml
@@ -38,7 +38,7 @@
     <feature name="android.hardware.security.model.compatible" />
 
     <!-- basic system services -->
-    <feature name="android.software.connectionservice" />
+    <feature name="android.software.telecom" />
     <feature name="android.software.voice_recognizers" notLowRam="true" />
     <feature name="android.software.home_screen" />
     <feature name="android.software.companion_device_setup" />
diff --git a/data/etc/go_handheld_core_hardware.xml b/data/etc/go_handheld_core_hardware.xml
index e6db4ad..8df7fdb 100644
--- a/data/etc/go_handheld_core_hardware.xml
+++ b/data/etc/go_handheld_core_hardware.xml
@@ -37,7 +37,7 @@
     <feature name="android.hardware.security.model.compatible" />
 
     <!-- basic system services -->
-    <feature name="android.software.connectionservice" />
+    <feature name="android.software.telecom" />
     <feature name="android.software.backup" />
     <feature name="android.software.home_screen" />
     <feature name="android.software.input_methods" />
@@ -46,6 +46,7 @@
     <feature name="android.software.autofill" />
     <feature name="android.software.cant_save_state" />
     <feature name="android.software.secure_lock_screen" />
+    <feature name="android.software.window_magnification" />
 
     <!-- Feature to specify if the device supports adding device admins. -->
     <feature name="android.software.device_admin" />
diff --git a/data/etc/handheld_core_hardware.xml b/data/etc/handheld_core_hardware.xml
index 41f1514..8fdd8d0 100644
--- a/data/etc/handheld_core_hardware.xml
+++ b/data/etc/handheld_core_hardware.xml
@@ -42,7 +42,7 @@
 
     <!-- basic system services -->
     <feature name="android.software.app_widgets" />
-    <feature name="android.software.connectionservice" />
+    <feature name="android.software.telecom" />
     <feature name="android.software.voice_recognizers" notLowRam="true" />
     <feature name="android.software.backup" />
     <feature name="android.software.home_screen" />
@@ -54,6 +54,7 @@
     <feature name="android.software.autofill" />
     <feature name="android.software.cant_save_state" />
     <feature name="android.software.secure_lock_screen" />
+    <feature name="android.software.window_magnification" />
 
     <!-- Feature to specify if the device supports adding device admins. -->
     <feature name="android.software.device_admin" />
diff --git a/data/etc/pc_core_hardware.xml b/data/etc/pc_core_hardware.xml
index b490ba0..3155710 100644
--- a/data/etc/pc_core_hardware.xml
+++ b/data/etc/pc_core_hardware.xml
@@ -49,6 +49,7 @@
     <feature name="android.software.autofill" />
     <feature name="android.software.cant_save_state" />
     <feature name="android.software.secure_lock_screen" />
+    <feature name="android.software.window_magnification" />
 
     <!-- Feature to specify if the device supports adding device admins. -->
     <feature name="android.software.device_admin" />
@@ -62,4 +63,4 @@
     <!-- Feature to specify if the device supports freeform. -->
     <feature name="android.software.freeform_window_management" />
     <feature name="android.hardware.type.pc" />
-</permissions>
\ No newline at end of file
+</permissions>
diff --git a/data/etc/tablet_core_hardware.xml b/data/etc/tablet_core_hardware.xml
index 8c369de..59d5b10 100644
--- a/data/etc/tablet_core_hardware.xml
+++ b/data/etc/tablet_core_hardware.xml
@@ -54,6 +54,7 @@
     <feature name="android.software.autofill" />
     <feature name="android.software.cant_save_state" />
     <feature name="android.software.secure_lock_screen" />
+    <feature name="android.software.window_magnification" />
 
     <!-- Feature to specify if the device supports adding device admins. -->
     <feature name="android.software.device_admin" />
diff --git a/headers/Android.bp b/headers/Android.bp
index 7481a23..cb18837 100644
--- a/headers/Android.bp
+++ b/headers/Android.bp
@@ -28,6 +28,11 @@
         "libstagefright_foundation_headers",
     ],
     min_sdk_version: "29",
+    apex_available: [
+        "//apex_available:platform",
+        "com.android.media",
+        "com.android.media.swcodec",
+    ],
 
     host_supported: true,
     target: {
diff --git a/include/android/bitmap.h b/include/android/bitmap.h
index 6704a1d..35f87f9 100644
--- a/include/android/bitmap.h
+++ b/include/android/bitmap.h
@@ -68,6 +68,8 @@
     ANDROID_BITMAP_FORMAT_A_8       = 8,
     /** Each component is stored as a half float. **/
     ANDROID_BITMAP_FORMAT_RGBA_F16  = 9,
+    /** Red: 10 bits, Green: 10 bits, Blue: 10 bits, Alpha: 2 bits. **/
+    ANDROID_BITMAP_FORMAT_RGBA_1010102 = 10,
 };
 
 /** Bitmap alpha format */
diff --git a/include/android/choreographer.h b/include/android/choreographer.h
index b743f49..63aa7ff 100644
--- a/include/android/choreographer.h
+++ b/include/android/choreographer.h
@@ -39,6 +39,18 @@
  */
 typedef struct AChoreographer AChoreographer;
 
+
+/**
+ * The identifier of a frame timeline.
+ */
+typedef int64_t AVsyncId;
+
+struct AChoreographerFrameCallbackData;
+/**
+ * Opaque type that provides access to an AChoreographerFrameCallbackData object.
+ */
+typedef struct AChoreographerFrameCallbackData AChoreographerFrameCallbackData;
+
 /**
  * Prototype of the function that is called when a new frame is being rendered.
  * It's passed the time that the frame is being rendered as nanoseconds in the
@@ -60,6 +72,14 @@
 typedef void (*AChoreographer_frameCallback64)(int64_t frameTimeNanos, void* data);
 
 /**
+ * Prototype of the function that is called when a new frame is being rendered.
+ * It's passed the frame data that should not outlive the callback, as well as the data pointer
+ * provided by the application that registered a callback.
+ */
+typedef void (*AChoreographer_vsyncCallback)(
+        const AChoreographerFrameCallbackData* callbackData, void* data);
+
+/**
  * Prototype of the function that is called when the display refresh rate
  * changes. It's passed the new vsync period in nanoseconds, as well as the data
  * pointer provided by the application that registered a callback.
@@ -111,6 +131,14 @@
                                                uint32_t delayMillis) __INTRODUCED_IN(29);
 
 /**
+ * Posts a callback to run on the next frame. The data pointer provided will
+ * be passed to the callback function when it's called.
+ */
+void AChoreographer_postVsyncCallback(AChoreographer* choreographer,
+                                        AChoreographer_vsyncCallback callback, void* data)
+        __INTRODUCED_IN(33);
+
+/**
  * Registers a callback to be run when the display refresh rate changes. The
  * data pointer provided will be passed to the callback function when it's
  * called. The same callback may be registered multiple times, provided that a
@@ -160,6 +188,42 @@
                                                   AChoreographer_refreshRateCallback, void* data)
         __INTRODUCED_IN(30);
 
+/**
+ * The time in nanoseconds when the frame started being rendered.
+ */
+int64_t AChoreographerFrameCallbackData_getFrameTimeNanos(
+        const AChoreographerFrameCallbackData* data) __INTRODUCED_IN(33);
+
+/**
+ * The number of possible frame timelines.
+ */
+size_t AChoreographerFrameCallbackData_getFrameTimelinesLength(
+        const AChoreographerFrameCallbackData* data) __INTRODUCED_IN(33);
+
+/**
+ * Get index of the platform-preferred FrameTimeline.
+ */
+size_t AChoreographerFrameCallbackData_getPreferredFrameTimelineIndex(
+        const AChoreographerFrameCallbackData* data) __INTRODUCED_IN(33);
+
+/**
+ * The vsync ID token used to map Choreographer data.
+ */
+AVsyncId AChoreographerFrameCallbackData_getFrameTimelineVsyncId(
+        const AChoreographerFrameCallbackData* data, size_t index) __INTRODUCED_IN(33);
+
+/**
+ * The time in nanoseconds which the frame at given index is expected to be presented.
+ */
+int64_t AChoreographerFrameCallbackData_getFrameTimelineExpectedPresentationTimeNanos(
+        const AChoreographerFrameCallbackData* data, size_t index) __INTRODUCED_IN(33);
+
+/**
+ * The time in nanoseconds which the frame at given index needs to be ready by.
+ */
+int64_t AChoreographerFrameCallbackData_getFrameTimelineDeadlineNanos(
+        const AChoreographerFrameCallbackData* data, size_t index) __INTRODUCED_IN(33);
+
 __END_DECLS
 
 #endif // ANDROID_CHOREOGRAPHER_H
diff --git a/include/android/input.h b/include/android/input.h
index 7642215..fb5e204 100644
--- a/include/android/input.h
+++ b/include/android/input.h
@@ -169,6 +169,9 @@
 
     /** Drag event */
     AINPUT_EVENT_TYPE_DRAG = 5,
+
+    /** TouchMode event */
+    AINPUT_EVENT_TYPE_TOUCH_MODE = 6,
 };
 
 /**
@@ -805,6 +808,33 @@
 };
 
 /**
+ * Constants that identify different gesture classification types.
+ */
+enum {
+    /**
+     * Classification constant: None.
+     *
+     * No additional information is available about the current motion event stream.
+     */
+    AMOTION_EVENT_CLASSIFICATION_NONE = 0,
+    /**
+     * Classification constant: Ambiguous gesture.
+     *
+     * The user's intent with respect to the current event stream is not yet determined.
+     * Gestural actions, such as scrolling, should be inhibited until the classification resolves
+     * to another value or the event stream ends.
+     */
+    AMOTION_EVENT_CLASSIFICATION_AMBIGUOUS_GESTURE = 1,
+    /**
+     * Classification constant: Deep press.
+     *
+     * The current event stream represents the user intentionally pressing harder on the screen.
+     * This classification type should be used to accelerate the long press behaviour.
+     */
+    AMOTION_EVENT_CLASSIFICATION_DEEP_PRESS = 2,
+};
+
+/**
  * Input source masks.
  *
  * Refer to the documentation on android.view.InputDevice for more details about input sources
@@ -874,6 +904,7 @@
  * Keyboard types.
  *
  * Refer to the documentation on android.view.InputDevice for more details.
+ * Note: When adding a new keyboard type here InputDeviceInfo::setKeyboardType needs to be updated.
  */
 enum {
     /** none */
@@ -1323,6 +1354,23 @@
         int32_t axis, size_t pointer_index, size_t history_index);
 
 /**
+ * Get the action button for the motion event. Returns a valid action button when the
+ * event is associated with a button press or button release action. For other actions
+ * the return value is undefined.
+ */
+int32_t AMotionEvent_getActionButton(const AInputEvent* motion_event);
+
+/**
+ * Returns the classification for the current gesture.
+ * The classification may change as more events become available for the same gesture.
+ *
+ * @see #AMOTION_EVENT_CLASSIFICATION_NONE
+ * @see #AMOTION_EVENT_CLASSIFICATION_AMBIGUOUS_GESTURE
+ * @see #AMOTION_EVENT_CLASSIFICATION_DEEP_PRESS
+*/
+int32_t AMotionEvent_getClassification(const AInputEvent* motion_event);
+
+/**
  * Creates a native AInputEvent* object that is a copy of the specified Java
  * android.view.MotionEvent. The result may be used with generic and MotionEvent-specific
  * AInputEvent_* functions. The object returned by this function must be disposed using
@@ -1382,6 +1430,17 @@
  */
 void AInputQueue_finishEvent(AInputQueue* queue, AInputEvent* event, int handled);
 
+/**
+ * Returns the AInputQueue* object associated with the supplied Java InputQueue
+ * object. The returned native object holds a weak reference to the Java object,
+ * and is only valid as long as the Java object has not yet been disposed. You
+ * should ensure that there is a strong reference to the Java object and that it
+ * has not been disposed before using the returned object.
+ *
+ * Available since API level 33.
+ */
+AInputQueue* AInputQueue_fromJava(JNIEnv* env, jobject inputQueue) __INTRODUCED_IN(33);
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/include/android/performance_hint.h b/include/android/performance_hint.h
new file mode 100644
index 0000000..5fa47f6
--- /dev/null
+++ b/include/android/performance_hint.h
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_NATIVE_PERFORMANCE_HINT_H
+#define ANDROID_NATIVE_PERFORMANCE_HINT_H
+
+#include <sys/cdefs.h>
+
+/******************************************************************
+ *
+ * IMPORTANT NOTICE:
+ *
+ *   This file is part of Android's set of stable system headers
+ *   exposed by the Android NDK (Native Development Kit).
+ *
+ *   Third-party source AND binary code relies on the definitions
+ *   here to be FROZEN ON ALL UPCOMING PLATFORM RELEASES.
+ *
+ *   - DO NOT MODIFY ENUMS (EXCEPT IF YOU ADD NEW 32-BIT VALUES)
+ *   - DO NOT MODIFY CONSTANTS OR FUNCTIONAL MACROS
+ *   - DO NOT CHANGE THE SIGNATURE OF FUNCTIONS IN ANY WAY
+ *   - DO NOT CHANGE THE LAYOUT OR SIZE OF STRUCTURES
+ */
+
+#include <android/api-level.h>
+#include <stdint.h>
+
+__BEGIN_DECLS
+
+struct APerformanceHintManager;
+struct APerformanceHintSession;
+
+/**
+ * An opaque type representing a handle to a performance hint manager.
+ * It must be released after use.
+ *
+ * <p>To use:<ul>
+ *    <li>Obtain the performance hint manager instance by calling
+ *        {@link APerformanceHint_getManager} function.</li>
+ *    <li>Create an {@link APerformanceHintSession} with
+ *        {@link APerformanceHint_createSession}.</li>
+ *    <li>Get the preferred update rate in nanoseconds with
+ *        {@link APerformanceHint_getPreferredUpdateRateNanos}.</li>
+ */
+typedef struct APerformanceHintManager APerformanceHintManager;
+
+/**
+ * An opaque type representing a handle to a performance hint session.
+ * A session can only be acquired from a {@link APerformanceHintManager}
+ * with {@link APerformanceHint_getPreferredUpdateRateNanos}. It must be
+ * freed with {@link APerformanceHint_closeSession} after use.
+ *
+ * A Session represents a group of threads with an inter-related workload such that hints for
+ * their performance should be considered as a unit. The threads in a given session should be
+ * long-life and not created or destroyed dynamically.
+ *
+ * <p>Each session is expected to have a periodic workload with a target duration for each
+ * cycle. The cycle duration is likely greater than the target work duration to allow other
+ * parts of the pipeline to run within the available budget. For example, a renderer thread may
+ * work at 60hz in order to produce frames at the display's frame but have a target work
+ * duration of only 6ms.</p>
+ *
+ * <p>After each cycle of work, the client is expected to use
+ * {@link APerformanceHint_reportActualWorkDuration} to report the actual time taken to
+ * complete.</p>
+ *
+ * <p>To use:<ul>
+ *    <li>Update a sessions target duration for each cycle of work
+ *        with  {@link APerformanceHint_updateTargetWorkDuration}.</li>
+ *    <li>Report the actual duration for the last cycle of work with
+ *        {@link APerformanceHint_reportActualWorkDuration}.</li>
+ *    <li>Release the session instance with
+ *        {@link APerformanceHint_closeSession}.</li></ul></p>
+ */
+typedef struct APerformanceHintSession APerformanceHintSession;
+
+/**
+  * Acquire an instance of the performance hint manager.
+  *
+  * @return manager instance on success, nullptr on failure.
+  */
+APerformanceHintManager* APerformanceHint_getManager() __INTRODUCED_IN(__ANDROID_API_T__);
+
+/**
+ * Creates a session for the given set of threads and sets their initial target work
+ * duration.
+ * @param manager The performance hint manager instance.
+ * @param threadIds The list of threads to be associated with this session. They must be part of
+ *     this app's thread group.
+ * @param size the size of threadIds.
+ * @param initialTargetWorkDurationNanos The desired duration in nanoseconds for the new session.
+ *     This must be positive.
+ * @return manager instance on success, nullptr on failure.
+ */
+APerformanceHintSession* APerformanceHint_createSession(
+        APerformanceHintManager* manager,
+        const int32_t* threadIds, size_t size,
+        int64_t initialTargetWorkDurationNanos) __INTRODUCED_IN(__ANDROID_API_T__);
+
+/**
+ * Get preferred update rate information for this device.
+ *
+ * @param manager The performance hint manager instance.
+ * @return the preferred update rate supported by device software.
+ */
+int64_t APerformanceHint_getPreferredUpdateRateNanos(
+        APerformanceHintManager* manager) __INTRODUCED_IN(__ANDROID_API_T__);
+
+/**
+ * Updates this session's target duration for each cycle of work.
+ *
+ * @param session The performance hint session instance to update.
+ * @param targetDurationNanos the new desired duration in nanoseconds. This must be positive.
+ * @return 0 on success
+ *         EINVAL if targetDurationNanos is not positive.
+ *         EPIPE if communication with the system service has failed.
+ */
+int APerformanceHint_updateTargetWorkDuration(
+        APerformanceHintSession* session,
+        int64_t targetDurationNanos) __INTRODUCED_IN(__ANDROID_API_T__);
+
+/**
+ * Reports the actual duration for the last cycle of work.
+ *
+ * <p>The system will attempt to adjust the core placement of the threads within the thread
+ * group and/or the frequency of the core on which they are run to bring the actual duration
+ * close to the target duration.</p>
+ *
+ * @param session The performance hint session instance to update.
+ * @param actualDurationNanos how long the thread group took to complete its last task in
+ *     nanoseconds. This must be positive.
+ * @return 0 on success
+ *         EINVAL if actualDurationNanos is not positive.
+ *         EPIPE if communication with the system service has failed.
+ */
+int APerformanceHint_reportActualWorkDuration(
+        APerformanceHintSession* session,
+        int64_t actualDurationNanos) __INTRODUCED_IN(__ANDROID_API_T__);
+
+/**
+ * Release the performance hint manager pointer acquired via
+ * {@link APerformanceHint_createSession}.
+ *
+ * @param session The performance hint session instance to release.
+ */
+void APerformanceHint_closeSession(
+        APerformanceHintSession* session) __INTRODUCED_IN(__ANDROID_API_T__);
+
+__END_DECLS
+
+#endif // ANDROID_NATIVE_PERFORMANCE_HINT_H
diff --git a/include/android/sensor.h b/include/android/sensor.h
index 9dc6983..c714b05 100644
--- a/include/android/sensor.h
+++ b/include/android/sensor.h
@@ -256,6 +256,59 @@
      * The hinge angle sensor value is returned in degrees.
      */
     ASENSOR_TYPE_HINGE_ANGLE = 36,
+    /**
+     * {@link ASENSOR_TYPE_HEAD_TRACKER}
+     * reporting-mode: continuous
+     *
+     * Measures the orientation and rotational velocity of a user's head.
+     */
+    ASENSOR_TYPE_HEAD_TRACKER = 37,
+    /**
+     * {@link ASENSOR_TYPE_ACCELEROMETER_LIMITED_AXES}
+     * reporting-mode: continuous
+     *
+     * The first three values are in SI units (m/s^2) and measure the acceleration of the device
+     * minus the force of gravity. The last three values indicate which acceleration axes are
+     * supported. A value of 1.0 means supported and a value of 0 means not supported.
+     */
+    ASENSOR_TYPE_ACCELEROMETER_LIMITED_AXES = 38,
+    /**
+     * {@link ASENSOR_TYPE_GYROSCOPE_LIMITED_AXES}
+     * reporting-mode: continuous
+     *
+     * The first three values are in radians/second and measure the rate of rotation around the X,
+     * Y and Z axis. The last three values indicate which rotation axes are supported. A value of
+     * 1.0 means supported and a value of 0 means not supported.
+     */
+    ASENSOR_TYPE_GYROSCOPE_LIMITED_AXES = 39,
+    /**
+     * {@link ASENSOR_TYPE_ACCELEROMETER_LIMITED_AXES_UNCALIBRATED}
+     * reporting-mode: continuous
+     *
+     * The first three values are in SI units (m/s^2) and measure the acceleration of the device
+     * minus the force of gravity. The middle three values represent the estimated bias for each
+     * axis. The last three values indicate which acceleration axes are supported. A value of 1.0
+     * means supported and a value of 0 means not supported.
+     */
+    ASENSOR_TYPE_ACCELEROMETER_LIMITED_AXES_UNCALIBRATED = 40,
+    /**
+     * {@link ASENSOR_TYPE_GYROSCOPE_LIMITED_AXES_UNCALIBRATED}
+     * reporting-mode: continuous
+     *
+     * The first three values are in radians/second and measure the rate of rotation around the X,
+     * Y and Z axis. The middle three values represent the estimated drift around each axis in
+     * rad/s. The last three values indicate which rotation axes are supported. A value of 1.0 means
+     * supported and a value of 0 means not supported.
+     */
+    ASENSOR_TYPE_GYROSCOPE_LIMITED_AXES_UNCALIBRATED = 41,
+    /**
+     * {@link ASENSOR_TYPE_HEADING}
+     * reporting-mode: continuous
+     *
+     * A heading sensor measures the direction in which the device is pointing
+     * relative to true north in degrees.
+     */
+    ASENSOR_TYPE_HEADING = 42,
 };
 
 /**
@@ -440,6 +493,101 @@
     };
 } AAdditionalInfoEvent;
 
+typedef struct AHeadTrackerEvent {
+    /**
+     * The fields rx, ry, rz are an Euler vector (rotation vector, i.e. a vector
+     * whose direction indicates the axis of rotation and magnitude indicates
+     * the angle to rotate around that axis) representing the transform from
+     * the (arbitrary, possibly slowly drifting) reference frame to the
+     * head frame. Expressed in radians. Magnitude of the vector must be
+     * in the range [0, pi], while the value of individual axes are
+     * in the range [-pi, pi].
+     */
+    float rx;
+    float ry;
+    float rz;
+
+    /**
+     * The fields vx, vy, vz are an Euler vector (rotation vector) representing
+     * the angular velocity of the head (relative to itself), in radians per
+     * second. The direction of this vector indicates the axis of rotation, and
+     * the magnitude indicates the rate of rotation.
+     */
+    float vx;
+    float vy;
+    float vz;
+
+    /**
+     * This value changes each time the reference frame is suddenly and
+     * significantly changed, for example if an orientation filter algorithm
+     * used for determining the orientation has had its state reset.
+     */
+    int32_t discontinuity_count;
+} AHeadTrackerEvent;
+
+typedef struct ALimitedAxesImuEvent {
+    union {
+        float calib[3];
+        struct {
+            float x;
+            float y;
+            float z;
+        };
+    };
+    union {
+        float supported[3];
+        struct {
+            float x_supported;
+            float y_supported;
+            float z_supported;
+        };
+    };
+} ALimitedAxesImuEvent;
+
+typedef struct ALimitedAxesImuUncalibratedEvent {
+    union {
+        float uncalib[3];
+        struct {
+            float x_uncalib;
+            float y_uncalib;
+            float z_uncalib;
+        };
+    };
+    union {
+        float bias[3];
+        struct {
+            float x_bias;
+            float y_bias;
+            float z_bias;
+        };
+    };
+    union {
+        float supported[3];
+        struct {
+            float x_supported;
+            float y_supported;
+            float z_supported;
+        };
+    };
+} ALimitedAxesImuUncalibratedEvent;
+
+typedef struct AHeadingEvent {
+    /**
+     * The direction in which the device is pointing relative to true north in
+     * degrees. The value must be between 0.0 (inclusive) and 360.0 (exclusive),
+     * with 0 indicating north, 90 east, 180 south, and 270 west.
+     */
+    float heading;
+    /**
+     * Accuracy is defined at 68% confidence. In the case where the underlying
+     * distribution is assumed Gaussian normal, this would be considered one
+     * standard deviation. For example, if the heading returns 60 degrees, and
+     * accuracy returns 10 degrees, then there is a 68 percent probability of
+     * the true heading being between 50 degrees and 70 degrees.
+     */
+    float accuracy;
+} AHeadingEvent;
+
 /**
  * Information that describes a sensor event, refer to
  * <a href="/reference/android/hardware/SensorEvent">SensorEvent</a> for additional
@@ -476,6 +624,10 @@
             AHeartRateEvent heart_rate;
             ADynamicSensorEvent dynamic_sensor_meta;
             AAdditionalInfoEvent additional_info;
+            AHeadTrackerEvent head_tracker;
+            ALimitedAxesImuEvent limited_axes_imu;
+            ALimitedAxesImuUncalibratedEvent limited_axes_imu_uncalibrated;
+            AHeadingEvent heading;
         };
         union {
             uint64_t        data[8];
@@ -591,11 +743,36 @@
 ASensorManager* ASensorManager_getInstanceForPackage(const char* packageName) __INTRODUCED_IN(26);
 
 /**
- * Returns the list of available sensors.
+ * Returns the list of available sensors. The returned list is owned by the
+ * sensor manager and will not change between calls to this function.
+ *
+ * \param manager the {@link ASensorManager} instance obtained from
+ *                {@link ASensorManager_getInstanceForPackage}.
+ * \param list    the returned list of sensors.
+ * \return positive number of returned sensors or negative error code.
+ *         BAD_VALUE: manager is NULL.
  */
 int ASensorManager_getSensorList(ASensorManager* manager, ASensorList* list);
 
 /**
+ * Returns the list of available dynamic sensors. If there are no dynamic
+ * sensors available, returns nullptr in list.
+ *
+ * Each time this is called, the previously returned list is deallocated and
+ * must no longer be used.
+ *
+ * Available since API level 33.
+ *
+ * \param manager the {@link ASensorManager} instance obtained from
+ *                {@link ASensorManager_getInstanceForPackage}.
+ * \param list    the returned list of dynamic sensors.
+ * \return positive number of returned sensors or negative error code.
+ *         BAD_VALUE: manager is NULL.
+ */
+ssize_t ASensorManager_getDynamicSensorList(
+        ASensorManager* manager, ASensorList* list) __INTRODUCED_IN(33);
+
+/**
  * Returns the default sensor for the given type, or NULL if no sensor
  * of that type exists.
  */
diff --git a/include/android/surface_control.h b/include/android/surface_control.h
index 059bc41..9a36ecb 100644
--- a/include/android/surface_control.h
+++ b/include/android/surface_control.h
@@ -28,6 +28,7 @@
 
 #include <sys/cdefs.h>
 
+#include <android/choreographer.h>
 #include <android/data_space.h>
 #include <android/hardware_buffer.h>
 #include <android/hdr_metadata.h>
@@ -595,6 +596,25 @@
                                                bool enableBackPressure)
                                                __INTRODUCED_IN(31);
 
+/**
+ * Sets the frame timeline to use in Surface Flinger.
+ *
+ * A frame timeline should be chosen based on what frame deadline the application
+ * can meet when rendering the frame and the application's desired present time.
+ * By setting a frame timeline, Surface Flinger tries to present the frame at the corresponding
+ * expected present time.
+ *
+ * To receive frame timelines, a callback must be posted to Choreographer using
+ * AChoreographer_postExtendedFrameCallback(). The \a vsnycId can then be extracted from the
+ * callback payload using AChoreographerFrameCallbackData_getFrameTimelineVsyncId().
+ *
+ * \param vsyncId The vsync ID received from AChoreographer, setting the frame's present target to
+ * the corresponding expected present time and deadline from the frame to be rendered. A stale or
+ * invalid value will be ignored.
+ */
+void ASurfaceTransaction_setFrameTimeline(ASurfaceTransaction* transaction,
+                                          AVsyncId vsyncId) __INTRODUCED_IN(33);
+
 __END_DECLS
 
 #endif // ANDROID_SURFACE_CONTROL_H
diff --git a/include/ftl/Flags.h b/include/ftl/Flags.h
index 27c8476..708eaf5 100644
--- a/include/ftl/Flags.h
+++ b/include/ftl/Flags.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 The Android Open Source Project
+ * 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.
@@ -14,79 +14,22 @@
  * limitations under the License.
  */
 
-#include <android-base/stringprintf.h>
+#pragma once
 
-#include <array>
+#include <ftl/enum.h>
+#include <ftl/string.h>
+
 #include <cstdint>
-#include <optional>
+#include <iterator>
 #include <string>
 #include <type_traits>
 
-#include <ftl/NamedEnum.h>
 #include "utils/BitSet.h"
 
-#pragma once
+// TODO(b/185536303): Align with FTL style and namespace.
 
 namespace android {
 
-namespace details {
-
-template <typename F>
-inline constexpr auto flag_count = sizeof(F) * __CHAR_BIT__;
-
-template <typename F, typename T, T... I>
-constexpr auto generate_flag_values(std::integer_sequence<T, I...> seq) {
-    constexpr size_t count = seq.size();
-
-    std::array<F, count> values{};
-    for (size_t i = 0, v = 0; v < count; ++i) {
-        values[v++] = static_cast<F>(T{1} << i);
-    }
-
-    return values;
-}
-
-template <typename F>
-inline constexpr auto flag_values = generate_flag_values<F>(
-        std::make_integer_sequence<std::underlying_type_t<F>, flag_count<F>>{});
-
-template <typename F, std::size_t... I>
-constexpr auto generate_flag_names(std::index_sequence<I...>) noexcept {
-    return std::array<std::optional<std::string_view>, sizeof...(I)>{
-            {enum_value_name<F, flag_values<F>[I]>()...}};
-}
-
-template <typename F>
-inline constexpr auto flag_names =
-        generate_flag_names<F>(std::make_index_sequence<flag_count<F>>{});
-
-// A trait for determining whether a type is specifically an enum class or not.
-template <typename T, bool = std::is_enum_v<T>>
-struct is_enum_class : std::false_type {};
-
-// By definition, an enum class is an enum that is not implicitly convertible to its underlying
-// type.
-template <typename T>
-struct is_enum_class<T, true>
-      : std::bool_constant<!std::is_convertible_v<T, std::underlying_type_t<T>>> {};
-
-template <typename T>
-inline constexpr bool is_enum_class_v = is_enum_class<T>::value;
-} // namespace details
-
-template <auto V>
-constexpr auto flag_name() {
-    using F = decltype(V);
-    return details::enum_value_name<F, V>();
-}
-
-template <typename F>
-constexpr std::optional<std::string_view> flag_name(F flag) {
-    using U = std::underlying_type_t<F>;
-    auto idx = static_cast<size_t>(__builtin_ctzl(static_cast<U>(flag)));
-    return details::flag_names<F>[idx];
-}
-
 /* A class for handling flags defined by an enum or enum class in a type-safe way. */
 template <typename F>
 class Flags {
@@ -94,7 +37,7 @@
     // further to avoid this restriction but in general we want to encourage the use of enums
     // anyways.
     static_assert(std::is_enum_v<F>, "Flags type must be an enum");
-    using U = typename std::underlying_type_t<F>;
+    using U = std::underlying_type_t<F>;
 
 public:
     constexpr Flags(F f) : mFlags(static_cast<U>(f)) {}
@@ -106,11 +49,10 @@
     // should force them to be explicitly constructed from their underlying types to make full use
     // of the type checker.
     template <typename T = U>
-    constexpr Flags(T t, typename std::enable_if_t<!details::is_enum_class_v<F>, T>* = nullptr)
-          : mFlags(t) {}
+    constexpr Flags(T t, std::enable_if_t<!ftl::is_scoped_enum_v<F>, T>* = nullptr) : mFlags(t) {}
+
     template <typename T = U>
-    explicit constexpr Flags(T t,
-                             typename std::enable_if_t<details::is_enum_class_v<F>, T>* = nullptr)
+    explicit constexpr Flags(T t, std::enable_if_t<ftl::is_scoped_enum_v<F>, T>* = nullptr)
           : mFlags(t) {}
 
     class Iterator {
@@ -178,10 +120,10 @@
     }
 
     /* Tests whether any of the given flags are set */
-    bool any(Flags<F> f) { return (mFlags & f.mFlags) != 0; }
+    bool any(Flags<F> f) const { return (mFlags & f.mFlags) != 0; }
 
     /* Tests whether all of the given flags are set */
-    bool all(Flags<F> f) { return (mFlags & f.mFlags) == f.mFlags; }
+    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); }
     Flags<F>& operator|=(Flags<F> rhs) {
@@ -211,6 +153,10 @@
         return *this;
     }
 
+    inline Flags<F>& clear(Flags<F> f = static_cast<F>(~static_cast<U>(0))) {
+        return *this &= ~f;
+    }
+
     Iterator begin() const { return Iterator(*this); }
 
     Iterator end() const { return Iterator(); }
@@ -229,16 +175,16 @@
         bool first = true;
         U unstringified = 0;
         for (const F f : *this) {
-            std::optional<std::string_view> flagString = flag_name(f);
-            if (flagString) {
-                appendFlag(result, flagString.value(), first);
+            if (const auto flagName = ftl::flag_name(f)) {
+                appendFlag(result, flagName.value(), first);
             } else {
                 unstringified |= static_cast<U>(f);
             }
         }
 
         if (unstringified != 0) {
-            appendFlag(result, base::StringPrintf("0x%08x", unstringified), first);
+            constexpr auto radix = sizeof(U) == 1 ? ftl::Radix::kBin : ftl::Radix::kHex;
+            appendFlag(result, ftl::to_string(unstringified, radix), first);
         }
 
         if (first) {
@@ -265,15 +211,14 @@
 // as flags. In order to use these, add them via a `using namespace` declaration.
 namespace flag_operators {
 
-template <typename F, typename = std::enable_if_t<details::is_enum_class_v<F>>>
+template <typename F, typename = std::enable_if_t<ftl::is_scoped_enum_v<F>>>
 inline Flags<F> operator~(F f) {
-    using U = typename std::underlying_type_t<F>;
-    return static_cast<F>(~static_cast<U>(f));
+    return static_cast<F>(~ftl::to_underlying(f));
 }
-template <typename F, typename = std::enable_if_t<details::is_enum_class_v<F>>>
+
+template <typename F, typename = std::enable_if_t<ftl::is_scoped_enum_v<F>>>
 Flags<F> operator|(F lhs, F rhs) {
-    using U = typename std::underlying_type_t<F>;
-    return static_cast<F>(static_cast<U>(lhs) | static_cast<U>(rhs));
+    return static_cast<F>(ftl::to_underlying(lhs) | ftl::to_underlying(rhs));
 }
 
 } // namespace flag_operators
diff --git a/include/ftl/NamedEnum.h b/include/ftl/NamedEnum.h
deleted file mode 100644
index 6e98fee..0000000
--- a/include/ftl/NamedEnum.h
+++ /dev/null
@@ -1,129 +0,0 @@
-/*
- * Copyright (C) 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.
- */
-
-#include <android-base/stringprintf.h>
-
-#include <array>
-#include <cstdint>
-#include <optional>
-#include <string>
-
-#pragma once
-
-namespace android {
-
-namespace details {
-template <typename E, E V>
-constexpr std::optional<std::string_view> enum_value_name() {
-    // Should look something like (but all on one line):
-    //   std::optional<std::string_view>
-    //   android::details::enum_value_name()
-    //   [E = android::test::TestEnums, V = android::test::TestEnums::ONE]
-    std::string_view view = __PRETTY_FUNCTION__;
-    size_t templateStart = view.rfind("[");
-    size_t templateEnd = view.rfind("]");
-    if (templateStart == std::string::npos || templateEnd == std::string::npos) {
-        return std::nullopt;
-    }
-
-    // Extract the template parameters without the enclosing braces.
-    // Example (cont'd): E = android::test::TestEnums, V = android::test::TestEnums::ONE
-    view = view.substr(templateStart + 1, templateEnd - templateStart - 1);
-    size_t valStart = view.rfind("V = ");
-    if (valStart == std::string::npos) {
-        return std::nullopt;
-    }
-
-    // Example (cont'd): V = android::test::TestEnums::ONE
-    view = view.substr(valStart);
-    // Check invalid enum values with cast, like V = (android::test::TestEnums)8.
-    if (view.find('(') != std::string::npos) {
-        return std::nullopt;
-    }
-    size_t nameStart = view.rfind("::");
-    if (nameStart == std::string::npos) {
-        return std::nullopt;
-    }
-
-    // Chop off the initial "::"
-    nameStart += 2;
-    return view.substr(nameStart);
-}
-
-template <typename E, typename T, T... I>
-constexpr auto generate_enum_values(std::integer_sequence<T, I...> seq) {
-    constexpr size_t count = seq.size();
-
-    std::array<E, count> values{};
-    for (size_t i = 0, v = 0; v < count; ++i) {
-        values[v++] = static_cast<E>(T{0} + i);
-    }
-
-    return values;
-}
-
-template <typename E, std::size_t N>
-inline constexpr auto enum_values =
-        generate_enum_values<E>(std::make_integer_sequence<std::underlying_type_t<E>, N>{});
-
-template <typename E, std::size_t N, std::size_t... I>
-constexpr auto generate_enum_names(std::index_sequence<I...>) noexcept {
-    return std::array<std::optional<std::string_view>, sizeof...(I)>{
-            {enum_value_name<E, enum_values<E, N>[I]>()...}};
-}
-
-template <typename E, std::size_t N>
-inline constexpr auto enum_names = generate_enum_names<E, N>(std::make_index_sequence<N>{});
-
-} // namespace details
-
-class NamedEnum {
-public:
-    // By default allowed enum value range is 0 ~ 7.
-    template <typename E>
-    static constexpr size_t max = 8;
-
-    template <auto V>
-    static constexpr auto enum_name() {
-        using E = decltype(V);
-        return details::enum_value_name<E, V>();
-    }
-
-    template <typename E>
-    static constexpr std::optional<std::string_view> enum_name(E val) {
-        auto idx = static_cast<size_t>(val);
-        return idx < max<E> ? details::enum_names<E, max<E>>[idx] : std::nullopt;
-    }
-
-    // Helper function for parsing enum value to string.
-    // Example : enum class TestEnums { ZERO = 0x0 };
-    // NamedEnum::string(TestEnums::ZERO) returns string of "ZERO".
-    // Note the default maximum enum is 8, if the enum ID to be parsed if greater than 8 like 16,
-    // it should be declared to specialized the maximum enum by below:
-    // template <> constexpr size_t NamedEnum::max<TestEnums> = 16;
-    // If the enum class definition is sparse and contains enum values starting from a large value,
-    // Do not specialize it to a large number to avoid performance issues.
-    // The recommended maximum enum number to specialize is 64.
-    template <typename E>
-    static const std::string string(E val, const char* fallbackFormat = "%02d") {
-        std::string result;
-        std::optional<std::string_view> enumString = enum_name(val);
-        result += enumString ? enumString.value() : base::StringPrintf(fallbackFormat, val);
-        return result;
-    }
-};
-
-} // namespace android
diff --git a/include/ftl/cast.h b/include/ftl/cast.h
new file mode 100644
index 0000000..ff1b58a
--- /dev/null
+++ b/include/ftl/cast.h
@@ -0,0 +1,84 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <limits>
+#include <type_traits>
+
+#include <ftl/details/cast.h>
+
+namespace android::ftl {
+
+enum class CastSafety { kSafe, kUnderflow, kOverflow };
+
+// Returns whether static_cast<R>(v) is safe, or would result in underflow or overflow.
+//
+//   static_assert(ftl::cast_safety<uint8_t>(-1) == ftl::CastSafety::kUnderflow);
+//   static_assert(ftl::cast_safety<int8_t>(128u) == ftl::CastSafety::kOverflow);
+//
+//   static_assert(ftl::cast_safety<uint32_t>(-.1f) == ftl::CastSafety::kUnderflow);
+//   static_assert(ftl::cast_safety<int32_t>(static_cast<float>(INT32_MAX)) ==
+//                                           ftl::CastSafety::kOverflow);
+//
+//   static_assert(ftl::cast_safety<float>(-DBL_MAX) == ftl::CastSafety::kUnderflow);
+//
+template <typename R, typename T>
+constexpr CastSafety cast_safety(T v) {
+  static_assert(std::is_arithmetic_v<T>);
+  static_assert(std::is_arithmetic_v<R>);
+
+  constexpr bool kFromSigned = std::is_signed_v<T>;
+  constexpr bool kToSigned = std::is_signed_v<R>;
+
+  using details::max_exponent;
+
+  // If the R range contains the T range, then casting is always safe.
+  if constexpr ((kFromSigned == kToSigned && max_exponent<R> >= max_exponent<T>) ||
+                (!kFromSigned && kToSigned && max_exponent<R> > max_exponent<T>)) {
+    return CastSafety::kSafe;
+  }
+
+  using C = std::common_type_t<R, T>;
+
+  if constexpr (kFromSigned) {
+    using L = details::safe_limits<R, T>;
+
+    if constexpr (kToSigned) {
+      // Signed to signed.
+      if (v < L::lowest()) return CastSafety::kUnderflow;
+      return v <= L::max() ? CastSafety::kSafe : CastSafety::kOverflow;
+    } else {
+      // Signed to unsigned.
+      if (v < 0) return CastSafety::kUnderflow;
+      return static_cast<C>(v) <= static_cast<C>(L::max()) ? CastSafety::kSafe
+                                                           : CastSafety::kOverflow;
+    }
+  } else {
+    using L = std::numeric_limits<R>;
+
+    if constexpr (kToSigned) {
+      // Unsigned to signed.
+      return static_cast<C>(v) <= static_cast<C>(L::max()) ? CastSafety::kSafe
+                                                           : CastSafety::kOverflow;
+    } else {
+      // Unsigned to unsigned.
+      return v <= L::max() ? CastSafety::kSafe : CastSafety::kOverflow;
+    }
+  }
+}
+
+}  // namespace android::ftl
diff --git a/include/ftl/concat.h b/include/ftl/concat.h
new file mode 100644
index 0000000..ded48f7
--- /dev/null
+++ b/include/ftl/concat.h
@@ -0,0 +1,84 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <ftl/details/concat.h>
+
+namespace android::ftl {
+
+// Lightweight (not allocating nor sprintf-based) concatenation.
+//
+//   std::string_view name = "Volume";
+//   ftl::Concat string(ftl::truncated<3>(name), ": ", -3, " dB");
+//
+//   assert(string.str() == "Vol: -3 dB");
+//   assert(string.c_str()[string.size()] == '\0');
+//
+template <std::size_t, typename... Ts>
+struct Concat;
+
+template <std::size_t N, typename T, typename... Ts>
+struct Concat<N, T, Ts...> : Concat<N + details::StaticString<T>::N, Ts...> {
+  explicit constexpr Concat(T v, Ts... args) { append(v, args...); }
+
+ protected:
+  constexpr Concat() = default;
+
+  constexpr void append(T v, Ts... args) {
+    using Str = details::StaticString<T>;
+    const Str str(v);
+
+    // TODO: Replace with constexpr std::copy in C++20.
+    for (auto it = str.view.begin(); it != str.view.end();) {
+      *this->end_++ = *it++;
+    }
+
+    using Base = Concat<N + Str::N, Ts...>;
+    this->Base::append(args...);
+  }
+};
+
+template <std::size_t N>
+struct Concat<N> {
+  static constexpr std::size_t max_size() { return N; }
+  constexpr std::size_t size() const { return end_ - buffer_; }
+
+  constexpr const char* c_str() const { return buffer_; }
+
+  constexpr std::string_view str() const {
+    // TODO: Replace with {buffer_, end_} in C++20.
+    return {buffer_, size()};
+  }
+
+ protected:
+  constexpr Concat() : end_(buffer_) {}
+  constexpr void append() { *end_ = '\0'; }
+
+  char buffer_[N + 1];
+  char* end_;
+};
+
+// Deduction guide.
+template <typename... Ts>
+Concat(Ts&&...) -> Concat<0, Ts...>;
+
+template <std::size_t N>
+constexpr auto truncated(std::string_view v) {
+  return details::Truncated<N>{v};
+}
+
+}  // namespace android::ftl
diff --git a/include/ftl/array_traits.h b/include/ftl/details/array_traits.h
similarity index 63%
rename from include/ftl/array_traits.h
rename to include/ftl/details/array_traits.h
index 1265fa1..5234c38 100644
--- a/include/ftl/array_traits.h
+++ b/include/ftl/details/array_traits.h
@@ -21,9 +21,9 @@
 #include <new>
 #include <type_traits>
 
-#define FTL_ARRAY_TRAIT(T, U) using U = typename ArrayTraits<T>::U
+#define FTL_ARRAY_TRAIT(T, U) using U = typename details::ArrayTraits<T>::U
 
-namespace android::ftl {
+namespace android::ftl::details {
 
 template <typename T>
 struct ArrayTraits {
@@ -42,7 +42,7 @@
   using const_reverse_iterator = std::reverse_iterator<const_iterator>;
 
   template <typename... Args>
-  static pointer construct_at(const_iterator it, Args&&... args) {
+  static constexpr pointer construct_at(const_iterator it, Args&&... args) {
     void* const ptr = const_cast<void*>(static_cast<const void*>(it));
     if constexpr (std::is_constructible_v<value_type, Args...>) {
       // TODO: Replace with std::construct_at in C++20.
@@ -52,6 +52,42 @@
       return new (ptr) value_type{std::forward<Args>(args)...};
     }
   }
+
+  // TODO: Make constexpr in C++20.
+  template <typename... Args>
+  static reference replace_at(const_iterator it, Args&&... args) {
+    value_type element{std::forward<Args>(args)...};
+    return replace_at(it, std::move(element));
+  }
+
+  // TODO: Make constexpr in C++20.
+  static reference replace_at(const_iterator it, value_type&& value) {
+    std::destroy_at(it);
+    // This is only safe because exceptions are disabled.
+    return *construct_at(it, std::move(value));
+  }
+
+  // TODO: Make constexpr in C++20.
+  static void in_place_swap(reference a, reference b) {
+    value_type c{std::move(a)};
+    replace_at(&a, std::move(b));
+    replace_at(&b, std::move(c));
+  }
+
+  // TODO: Make constexpr in C++20.
+  static void in_place_swap_ranges(iterator first1, iterator last1, iterator first2) {
+    while (first1 != last1) {
+      in_place_swap(*first1++, *first2++);
+    }
+  }
+
+  // TODO: Replace with std::uninitialized_copy in C++20.
+  template <typename Iterator>
+  static void uninitialized_copy(Iterator first, Iterator last, const_iterator out) {
+    while (first != last) {
+      construct_at(out++, *first++);
+    }
+  }
 };
 
 // CRTP mixin to define iterator functions in terms of non-const Self::begin and Self::end.
@@ -101,35 +137,35 @@
 // TODO: Replace with operator<=> in C++20.
 template <template <typename, std::size_t> class Array>
 struct ArrayComparators {
-  template <typename T, std::size_t N, std::size_t M>
-  friend bool operator==(const Array<T, N>& lhs, const Array<T, M>& rhs) {
+  template <typename T, typename U, std::size_t N, std::size_t M>
+  friend bool operator==(const Array<T, N>& lhs, const Array<U, M>& rhs) {
     return lhs.size() == rhs.size() && std::equal(lhs.begin(), lhs.end(), rhs.begin());
   }
 
-  template <typename T, std::size_t N, std::size_t M>
-  friend bool operator<(const Array<T, N>& lhs, const Array<T, M>& rhs) {
+  template <typename T, typename U, std::size_t N, std::size_t M>
+  friend bool operator<(const Array<T, N>& lhs, const Array<U, M>& rhs) {
     return std::lexicographical_compare(lhs.begin(), lhs.end(), rhs.begin(), rhs.end());
   }
 
-  template <typename T, std::size_t N, std::size_t M>
-  friend bool operator>(const Array<T, N>& lhs, const Array<T, M>& rhs) {
+  template <typename T, typename U, std::size_t N, std::size_t M>
+  friend bool operator>(const Array<T, N>& lhs, const Array<U, M>& rhs) {
     return rhs < lhs;
   }
 
-  template <typename T, std::size_t N, std::size_t M>
-  friend bool operator!=(const Array<T, N>& lhs, const Array<T, M>& rhs) {
+  template <typename T, typename U, std::size_t N, std::size_t M>
+  friend bool operator!=(const Array<T, N>& lhs, const Array<U, M>& rhs) {
     return !(lhs == rhs);
   }
 
-  template <typename T, std::size_t N, std::size_t M>
-  friend bool operator>=(const Array<T, N>& lhs, const Array<T, M>& rhs) {
+  template <typename T, typename U, std::size_t N, std::size_t M>
+  friend bool operator>=(const Array<T, N>& lhs, const Array<U, M>& rhs) {
     return !(lhs < rhs);
   }
 
-  template <typename T, std::size_t N, std::size_t M>
-  friend bool operator<=(const Array<T, N>& lhs, const Array<T, M>& rhs) {
+  template <typename T, typename U, std::size_t N, std::size_t M>
+  friend bool operator<=(const Array<T, N>& lhs, const Array<U, M>& rhs) {
     return !(lhs > rhs);
   }
 };
 
-}  // namespace android::ftl
+}  // namespace android::ftl::details
diff --git a/include/ftl/details/cast.h b/include/ftl/details/cast.h
new file mode 100644
index 0000000..87b9f1e
--- /dev/null
+++ b/include/ftl/details/cast.h
@@ -0,0 +1,57 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <limits>
+#include <type_traits>
+
+namespace android::ftl::details {
+
+// Exponent whose power of 2 is the (exclusive) upper bound of T.
+template <typename T, typename L = std::numeric_limits<T>>
+constexpr int max_exponent = std::is_floating_point_v<T> ? L::max_exponent : L::digits;
+
+// Extension of std::numeric_limits<T> that reduces the maximum for integral types T such that it
+// has an exact representation for floating-point types F. For example, the maximum int32_t value
+// is 2'147'483'647, but casting it to float commonly rounds up to 2'147'483'650.f, which cannot
+// be safely converted back lest the signed overflow invokes undefined behavior. This pitfall is
+// avoided by clearing the lower (31 - 24 =) 7 bits of precision to 2'147'483'520. Note that the
+// minimum is representable.
+template <typename T, typename F>
+struct safe_limits : std::numeric_limits<T> {
+  static constexpr T max() {
+    using Base = std::numeric_limits<T>;
+
+    if constexpr (std::is_integral_v<T> && std::is_floating_point_v<F>) {
+      // Assume the mantissa is 24 bits for float, or 53 bits for double.
+      using Float = std::numeric_limits<F>;
+      static_assert(Float::is_iec559);
+
+      // If the integer is wider than the mantissa, clear the excess bits of precision.
+      constexpr int kShift = Base::digits - Float::digits;
+      if constexpr (kShift > 0) {
+        using U = std::make_unsigned_t<T>;
+        constexpr U kOne = static_cast<U>(1);
+        return static_cast<U>(Base::max()) & ~((kOne << kShift) - kOne);
+      }
+    }
+
+    return Base::max();
+  }
+};
+
+}  // namespace android::ftl::details
diff --git a/include/ftl/details/concat.h b/include/ftl/details/concat.h
new file mode 100644
index 0000000..8ce949e
--- /dev/null
+++ b/include/ftl/details/concat.h
@@ -0,0 +1,62 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <functional>
+#include <string_view>
+
+#include <ftl/string.h>
+
+namespace android::ftl::details {
+
+template <typename T, typename = void>
+struct StaticString;
+
+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>;
+
+  explicit StaticString(T v) : view(to_chars(buffer, v)) {}
+
+  to_chars_buffer_t<T> buffer;
+  const std::string_view view;
+};
+
+template <std::size_t M>
+struct StaticString<const char (&)[M], void> {
+  static constexpr std::size_t N = M - 1;
+
+  explicit constexpr StaticString(const char (&str)[M]) : view(str, N) {}
+
+  const std::string_view view;
+};
+
+template <std::size_t N>
+struct Truncated {
+  std::string_view view;
+};
+
+template <std::size_t M>
+struct StaticString<Truncated<M>, void> {
+  static constexpr std::size_t N = M;
+
+  explicit constexpr StaticString(Truncated<M> str) : view(str.view.substr(0, N)) {}
+
+  const std::string_view view;
+};
+
+}  // namespace android::ftl::details
diff --git a/include/ftl/enum.h b/include/ftl/enum.h
new file mode 100644
index 0000000..5234c05
--- /dev/null
+++ b/include/ftl/enum.h
@@ -0,0 +1,301 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <cstddef>
+#include <limits>
+#include <optional>
+#include <string_view>
+#include <type_traits>
+#include <utility>
+
+#include <ftl/string.h>
+
+// Returns the name of enumerator E::V (i.e. "V") as std::optional<std::string_view> by parsing the
+// compiler-generated string literal for the signature of this function. The function is defined in
+// the global namespace with a short name and inferred return type to reduce bloat in the read-only
+// data segment.
+template <typename E, E V>
+constexpr auto ftl_enum() {
+  static_assert(std::is_enum_v<E>);
+
+  using R = std::optional<std::string_view>;
+  using namespace std::literals;
+
+  // The "pretty" signature has the following format:
+  //
+  //   auto ftl_enum() [E = android::test::Enum, V = android::test::Enum::kValue]
+  //
+  std::string_view view = __PRETTY_FUNCTION__;
+  const auto template_begin = view.rfind('[');
+  const auto template_end = view.rfind(']');
+  if (template_begin == view.npos || template_end == view.npos) return R{};
+
+  // Extract the template parameters without the enclosing brackets. Example (cont'd):
+  //
+  //   E = android::test::Enum, V = android::test::Enum::kValue
+  //
+  view = view.substr(template_begin + 1, template_end - template_begin - 1);
+  const auto value_begin = view.rfind("V = "sv);
+  if (value_begin == view.npos) return R{};
+
+  // Example (cont'd):
+  //
+  //   V = android::test::Enum::kValue
+  //
+  view = view.substr(value_begin);
+  const auto name_begin = view.rfind("::"sv);
+  if (name_begin == view.npos) return R{};
+
+  // Chop off the leading "::".
+  const auto name = view.substr(name_begin + 2);
+
+  // A value that is not enumerated has the format "Enum)42".
+  return name.find(')') == view.npos ? R{name} : R{};
+}
+
+namespace android::ftl {
+
+// Trait for determining whether a type is specifically a scoped enum or not. By definition, a
+// scoped enum is one that is not implicitly convertible to its underlying type.
+//
+// TODO: Replace with std::is_scoped_enum in C++23.
+//
+template <typename T, bool = std::is_enum_v<T>>
+struct is_scoped_enum : std::false_type {};
+
+template <typename T>
+struct is_scoped_enum<T, true> : std::negation<std::is_convertible<T, std::underlying_type_t<T>>> {
+};
+
+template <typename T>
+inline constexpr bool is_scoped_enum_v = is_scoped_enum<T>::value;
+
+// Shorthand for casting an enumerator to its integral value.
+//
+// TODO: Replace with std::to_underlying in C++23.
+//
+//   enum class E { A, B, C };
+//   static_assert(ftl::to_underlying(E::B) == 1);
+//
+template <typename E>
+constexpr auto to_underlying(E v) {
+  return static_cast<std::underlying_type_t<E>>(v);
+}
+
+// Traits for retrieving an enum's range. An enum specifies its range by defining enumerators named
+// ftl_first and ftl_last. If omitted, ftl_first defaults to 0, whereas ftl_last defaults to N - 1
+// where N is the bit width of the underlying type, but only if that type is unsigned, assuming the
+// enumerators are flags. Also, note that unscoped enums must define both bounds, as casting out-of-
+// range values results in undefined behavior if the underlying type is not fixed.
+//
+//   enum class E { A, B, C, F = 5, ftl_last = F };
+//
+//   static_assert(ftl::enum_begin_v<E> == E::A);
+//   static_assert(ftl::enum_last_v<E> == E::F);
+//   static_assert(ftl::enum_size_v<E> == 6);
+//
+//   enum class F : std::uint16_t { X = 0b1, Y = 0b10, Z = 0b100 };
+//
+//   static_assert(ftl::enum_begin_v<F> == F{0});
+//   static_assert(ftl::enum_last_v<F> == F{15});
+//   static_assert(ftl::enum_size_v<F> == 16);
+//
+template <typename E, typename = void>
+struct enum_begin {
+  static_assert(is_scoped_enum_v<E>, "Missing ftl_first enumerator");
+  static constexpr E value{0};
+};
+
+template <typename E>
+struct enum_begin<E, std::void_t<decltype(E::ftl_first)>> {
+  static constexpr E value = E::ftl_first;
+};
+
+template <typename E>
+inline constexpr E enum_begin_v = enum_begin<E>::value;
+
+template <typename E, typename = void>
+struct enum_end {
+  using U = std::underlying_type_t<E>;
+  static_assert(is_scoped_enum_v<E> && std::is_unsigned_v<U>, "Missing ftl_last enumerator");
+
+  static constexpr E value{std::numeric_limits<U>::digits};
+};
+
+template <typename E>
+struct enum_end<E, std::void_t<decltype(E::ftl_last)>> {
+  static constexpr E value = E{to_underlying(E::ftl_last) + 1};
+};
+
+template <typename E>
+inline constexpr E enum_end_v = enum_end<E>::value;
+
+template <typename E>
+inline constexpr E enum_last_v = E{to_underlying(enum_end_v<E>) - 1};
+
+template <typename E>
+struct enum_size {
+  static constexpr auto kBegin = to_underlying(enum_begin_v<E>);
+  static constexpr auto kEnd = to_underlying(enum_end_v<E>);
+  static_assert(kBegin < kEnd, "Invalid range");
+
+  static constexpr std::size_t value = kEnd - kBegin;
+  static_assert(value <= 64, "Excessive range size");
+};
+
+template <typename E>
+inline constexpr std::size_t enum_size_v = enum_size<E>::value;
+
+namespace details {
+
+template <auto V>
+struct Identity {
+  static constexpr auto value = V;
+};
+
+template <typename E>
+using make_enum_sequence = std::make_integer_sequence<std::underlying_type_t<E>, enum_size_v<E>>;
+
+template <typename E, template <E> class = Identity, typename = make_enum_sequence<E>>
+struct EnumRange;
+
+template <typename E, template <E> class F, typename T, T... Vs>
+struct EnumRange<E, F, std::integer_sequence<T, Vs...>> {
+  static constexpr auto kBegin = to_underlying(enum_begin_v<E>);
+  static constexpr auto kSize = enum_size_v<E>;
+
+  using R = decltype(F<E{}>::value);
+  const R values[kSize] = {F<static_cast<E>(Vs + kBegin)>::value...};
+
+  constexpr const auto* begin() const { return values; }
+  constexpr const auto* end() const { return values + kSize; }
+};
+
+template <auto V>
+struct EnumName {
+  static constexpr auto value = ftl_enum<decltype(V), V>();
+};
+
+template <auto I>
+struct FlagName {
+  using E = decltype(I);
+  using U = std::underlying_type_t<E>;
+
+  static constexpr E V{U{1} << to_underlying(I)};
+  static constexpr auto value = ftl_enum<E, V>();
+};
+
+}  // namespace details
+
+// Returns an iterable over the range of an enum.
+//
+//   enum class E { A, B, C, F = 5, ftl_last = F };
+//
+//   std::string string;
+//   for (E v : ftl::enum_range<E>()) {
+//     string += ftl::enum_name(v).value_or("?");
+//   }
+//
+//   assert(string == "ABC??F");
+//
+template <typename E>
+constexpr auto enum_range() {
+  return details::EnumRange<E>{};
+}
+
+// Returns a stringified enumerator at compile time.
+//
+//   enum class E { A, B, C };
+//   static_assert(ftl::enum_name<E::B>() == "B");
+//
+template <auto V>
+constexpr std::string_view enum_name() {
+  constexpr auto kName = ftl_enum<decltype(V), V>();
+  static_assert(kName, "Unknown enumerator");
+  return *kName;
+}
+
+// Returns a stringified enumerator, possibly at compile time.
+//
+//   enum class E { A, B, C, F = 5, ftl_last = F };
+//
+//   static_assert(ftl::enum_name(E::C).value_or("?") == "C");
+//   static_assert(ftl::enum_name(E{3}).value_or("?") == "?");
+//
+template <typename E>
+constexpr std::optional<std::string_view> enum_name(E v) {
+  const auto value = to_underlying(v);
+
+  constexpr auto kBegin = to_underlying(enum_begin_v<E>);
+  constexpr auto kLast = to_underlying(enum_last_v<E>);
+  if (value < kBegin || value > kLast) return {};
+
+  constexpr auto kRange = details::EnumRange<E, details::EnumName>{};
+  return kRange.values[value - kBegin];
+}
+
+// Returns a stringified flag enumerator, possibly at compile time.
+//
+//   enum class F : std::uint16_t { X = 0b1, Y = 0b10, Z = 0b100 };
+//
+//   static_assert(ftl::flag_name(F::Z).value_or("?") == "Z");
+//   static_assert(ftl::flag_name(F{0b111}).value_or("?") == "?");
+//
+template <typename E>
+constexpr std::optional<std::string_view> flag_name(E v) {
+  const auto value = to_underlying(v);
+
+  // TODO: Replace with std::popcount and std::countr_zero in C++20.
+  if (__builtin_popcountl(value) != 1) return {};
+
+  constexpr auto kRange = details::EnumRange<E, details::FlagName>{};
+  return kRange.values[__builtin_ctzl(value)];
+}
+
+// Returns a stringified enumerator, or its integral value if not named.
+//
+//   enum class E { A, B, C, F = 5, ftl_last = F };
+//
+//   assert(ftl::enum_string(E::C) == "C");
+//   assert(ftl::enum_string(E{3}) == "3");
+//
+template <typename E>
+inline std::string enum_string(E v) {
+  if (const auto name = enum_name(v)) {
+    return std::string(*name);
+  }
+  return to_string(to_underlying(v));
+}
+
+// Returns a stringified flag enumerator, or its integral value if not named.
+//
+//   enum class F : std::uint16_t { X = 0b1, Y = 0b10, Z = 0b100 };
+//
+//   assert(ftl::flag_string(F::Z) == "Z");
+//   assert(ftl::flag_string(F{7}) == "0b111");
+//
+template <typename E>
+inline std::string flag_string(E v) {
+  if (const auto name = flag_name(v)) {
+    return std::string(*name);
+  }
+  constexpr auto radix = sizeof(E) == 1 ? Radix::kBin : Radix::kHex;
+  return to_string(to_underlying(v), radix);
+}
+
+}  // namespace android::ftl
diff --git a/include/ftl/initializer_list.h b/include/ftl/initializer_list.h
index 769c09f..2102c25 100644
--- a/include/ftl/initializer_list.h
+++ b/include/ftl/initializer_list.h
@@ -16,6 +16,7 @@
 
 #pragma once
 
+#include <functional>
 #include <tuple>
 #include <utility>
 
@@ -65,18 +66,18 @@
   std::tuple<Types...> tuple;
 };
 
-template <typename K, typename V>
+template <typename K, typename V, typename KeyEqual = std::equal_to<K>>
 struct KeyValue {};
 
 // Shorthand for key-value pairs that assigns the first argument to the key, and the rest to the
 // value. The specialization is on KeyValue rather than std::pair, so that ftl::init::list works
 // with the latter.
-template <typename K, typename V, std::size_t... Sizes, typename... Types>
-struct InitializerList<KeyValue<K, V>, std::index_sequence<Sizes...>, Types...> {
+template <typename K, typename V, typename E, std::size_t... Sizes, typename... Types>
+struct InitializerList<KeyValue<K, V, E>, std::index_sequence<Sizes...>, Types...> {
   // Accumulate the three arguments to std::pair's piecewise constructor.
   template <typename... Args>
   [[nodiscard]] constexpr auto operator()(K&& k, Args&&... args) && -> InitializerList<
-      KeyValue<K, V>, std::index_sequence<Sizes..., 3>, Types..., std::piecewise_construct_t,
+      KeyValue<K, V, E>, std::index_sequence<Sizes..., 3>, Types..., std::piecewise_construct_t,
       std::tuple<K&&>, std::tuple<Args&&...>> {
     return {std::tuple_cat(
         std::move(tuple),
@@ -94,9 +95,9 @@
   return InitializerList<T>{}(std::forward<Args>(args)...);
 }
 
-template <typename K, typename V, typename... Args>
+template <typename K, typename V, typename E = std::equal_to<K>, typename... Args>
 [[nodiscard]] constexpr auto map(Args&&... args) {
-  return list<KeyValue<K, V>>(std::forward<Args>(args)...);
+  return list<KeyValue<K, V, E>>(std::forward<Args>(args)...);
 }
 
 template <typename K, typename V>
diff --git a/include/ftl/small_map.h b/include/ftl/small_map.h
index 84c15eb..5217e76 100644
--- a/include/ftl/small_map.h
+++ b/include/ftl/small_map.h
@@ -19,6 +19,7 @@
 #include <ftl/initializer_list.h>
 #include <ftl/small_vector.h>
 
+#include <algorithm>
 #include <functional>
 #include <optional>
 #include <type_traits>
@@ -28,7 +29,10 @@
 
 // Associative container with unique, unordered keys. Unlike std::unordered_map, key-value pairs are
 // stored in contiguous storage for cache efficiency. The map is allocated statically until its size
-// exceeds N, at which point mappings are relocated to dynamic memory.
+// exceeds N, at which point mappings are relocated to dynamic memory. The try_emplace operation has
+// a non-standard analogue try_replace that destructively emplaces. The API also defines an in-place
+// counterpart to insert_or_assign: emplace_or_replace. Lookup is done not via a subscript operator,
+// but immutable getters that can optionally transform the value.
 //
 // SmallMap<K, V, 0> unconditionally allocates on the heap.
 //
@@ -43,21 +47,27 @@
 //   assert(!map.dynamic());
 //
 //   assert(map.contains(123));
-//   assert(map.find(42, [](const std::string& s) { return s.size(); }) == 3u);
+//   assert(map.get(42, [](const std::string& s) { return s.size(); }) == 3u);
 //
-//   const auto opt = map.find(-1);
+//   const auto opt = map.get(-1);
 //   assert(opt);
 //
 //   std::string& ref = *opt;
 //   assert(ref.empty());
 //   ref = "xyz";
 //
-//   assert(map == SmallMap(ftl::init::map(-1, "xyz")(42, "???")(123, "abc")));
+//   map.emplace_or_replace(0, "vanilla", 2u, 3u);
+//   assert(map.dynamic());
 //
-template <typename K, typename V, std::size_t N>
+//   assert(map == SmallMap(ftl::init::map(-1, "xyz")(0, "nil")(42, "???")(123, "abc")));
+//
+template <typename K, typename V, std::size_t N, typename KeyEqual = std::equal_to<K>>
 class SmallMap final {
   using Map = SmallVector<std::pair<const K, V>, N>;
 
+  template <typename, typename, std::size_t, typename>
+  friend class SmallMap;
+
  public:
   using key_type = K;
   using mapped_type = V;
@@ -80,12 +90,7 @@
   // The syntax for listing pairs is as follows:
   //
   //   ftl::SmallMap map = ftl::init::map<int, std::string>(123, "abc")(-1)(42, 3u, '?');
-  //
   //   static_assert(std::is_same_v<decltype(map), ftl::SmallMap<int, std::string, 3>>);
-  //   assert(map.size() == 3u);
-  //   assert(map.contains(-1) && map.find(-1)->get().empty());
-  //   assert(map.contains(42) && map.find(42)->get() == "???");
-  //   assert(map.contains(123) && map.find(123)->get() == "abc");
   //
   // The types of the key and value are deduced if the first pair contains exactly two arguments:
   //
@@ -95,9 +100,13 @@
   template <typename U, std::size_t... Sizes, typename... Types>
   SmallMap(InitializerList<U, std::index_sequence<Sizes...>, Types...>&& list)
       : map_(std::move(list)) {
-    // TODO: Enforce unique keys.
+    deduplicate();
   }
 
+  // Copies or moves key-value pairs from a convertible map.
+  template <typename Q, typename W, std::size_t M, typename E>
+  SmallMap(SmallMap<Q, W, M, E> other) : map_(std::move(other.map_)) {}
+
   size_type max_size() const { return map_.max_size(); }
   size_type size() const { return map_.size(); }
   bool empty() const { return map_.empty(); }
@@ -115,27 +124,27 @@
 
   // Returns whether a mapping exists for the given key.
   bool contains(const key_type& key) const {
-    return find(key, [](const mapped_type&) {});
+    return get(key, [](const mapped_type&) {});
   }
 
   // Returns a reference to the value for the given key, or std::nullopt if the key was not found.
   //
   //   ftl::SmallMap map = ftl::init::map('a', 'A')('b', 'B')('c', 'C');
   //
-  //   const auto opt = map.find('c');
+  //   const auto opt = map.get('c');
   //   assert(opt == 'C');
   //
   //   char d = 'd';
-  //   const auto ref = map.find('d').value_or(std::ref(d));
+  //   const auto ref = map.get('d').value_or(std::ref(d));
   //   ref.get() = 'D';
   //   assert(d == 'D');
   //
-  auto find(const key_type& key) const -> std::optional<std::reference_wrapper<const mapped_type>> {
-    return find(key, [](const mapped_type& v) { return std::cref(v); });
+  auto get(const key_type& key) const -> std::optional<std::reference_wrapper<const mapped_type>> {
+    return get(key, [](const mapped_type& v) { return std::cref(v); });
   }
 
-  auto find(const key_type& key) -> std::optional<std::reference_wrapper<mapped_type>> {
-    return find(key, [](mapped_type& v) { return std::ref(v); });
+  auto get(const key_type& key) -> std::optional<std::reference_wrapper<mapped_type>> {
+    return get(key, [](mapped_type& v) { return std::ref(v); });
   }
 
   // Returns the result R of a unary operation F on (a constant or mutable reference to) the value
@@ -144,14 +153,14 @@
   //
   //   ftl::SmallMap map = ftl::init::map('a', 'x')('b', 'y')('c', 'z');
   //
-  //   assert(map.find('c', [](char c) { return std::toupper(c); }) == 'Z');
-  //   assert(map.find('c', [](char& c) { c = std::toupper(c); }));
+  //   assert(map.get('c', [](char c) { return std::toupper(c); }) == 'Z');
+  //   assert(map.get('c', [](char& c) { c = std::toupper(c); }));
   //
   template <typename F, typename R = std::invoke_result_t<F, const mapped_type&>>
-  auto find(const key_type& key, F f) const
+  auto get(const key_type& key, F f) const
       -> std::conditional_t<std::is_void_v<R>, bool, std::optional<R>> {
     for (auto& [k, v] : *this) {
-      if (k == key) {
+      if (KeyEqual{}(k, key)) {
         if constexpr (std::is_void_v<R>) {
           f(v);
           return true;
@@ -165,28 +174,119 @@
   }
 
   template <typename F>
-  auto find(const key_type& key, F f) {
-    return std::as_const(*this).find(
+  auto get(const key_type& key, F f) {
+    return std::as_const(*this).get(
         key, [&f](const mapped_type& v) { return f(const_cast<mapped_type&>(v)); });
   }
 
+  // Returns an iterator to an existing mapping for the given key, or the end() iterator otherwise.
+  const_iterator find(const key_type& key) const { return const_cast<SmallMap&>(*this).find(key); }
+  iterator find(const key_type& key) { return find(key, begin()); }
+
+  // Inserts a mapping unless it exists. Returns an iterator to the inserted or existing mapping,
+  // and whether the mapping was inserted.
+  //
+  // On emplace, if the map reaches its static or dynamic capacity, then all iterators are
+  // invalidated. Otherwise, only the end() iterator is invalidated.
+  //
+  template <typename... Args>
+  std::pair<iterator, bool> try_emplace(const key_type& key, Args&&... args) {
+    if (const auto it = find(key); it != end()) {
+      return {it, false};
+    }
+
+    auto& ref = map_.emplace_back(std::piecewise_construct, std::forward_as_tuple(key),
+                                  std::forward_as_tuple(std::forward<Args>(args)...));
+    return {&ref, true};
+  }
+
+  // Replaces a mapping if it exists, and returns an iterator to it. Returns the end() iterator
+  // otherwise.
+  //
+  // The value is replaced via move constructor, so type V does not need to define copy/move
+  // assignment, e.g. its data members may be const.
+  //
+  // The arguments may directly or indirectly refer to the mapping being replaced.
+  //
+  // Iterators to the replaced mapping point to its replacement, and others remain valid.
+  //
+  template <typename... Args>
+  iterator try_replace(const key_type& key, Args&&... args) {
+    const auto it = find(key);
+    if (it == end()) return it;
+    map_.replace(it, std::piecewise_construct, std::forward_as_tuple(key),
+                 std::forward_as_tuple(std::forward<Args>(args)...));
+    return it;
+  }
+
+  // In-place counterpart of std::unordered_map's insert_or_assign. Returns true on emplace, or
+  // false on replace.
+  //
+  // The value is emplaced and replaced via move constructor, so type V does not need to define
+  // copy/move assignment, e.g. its data members may be const.
+  //
+  // On emplace, if the map reaches its static or dynamic capacity, then all iterators are
+  // invalidated. Otherwise, only the end() iterator is invalidated. On replace, iterators
+  // to the replaced mapping point to its replacement, and others remain valid.
+  //
+  template <typename... Args>
+  std::pair<iterator, bool> emplace_or_replace(const key_type& key, Args&&... args) {
+    const auto [it, ok] = try_emplace(key, std::forward<Args>(args)...);
+    if (ok) return {it, ok};
+    map_.replace(it, std::piecewise_construct, std::forward_as_tuple(key),
+                 std::forward_as_tuple(std::forward<Args>(args)...));
+    return {it, ok};
+  }
+
+  // Removes a mapping if it exists, and returns whether it did.
+  //
+  // The last() and end() iterators, as well as those to the erased mapping, are invalidated.
+  //
+  bool erase(const key_type& key) { return erase(key, begin()); }
+
+  // Removes all mappings.
+  //
+  // All iterators are invalidated.
+  //
+  void clear() { map_.clear(); }
+
  private:
+  iterator find(const key_type& key, iterator first) {
+    return std::find_if(first, end(),
+                        [&key](const auto& pair) { return KeyEqual{}(pair.first, key); });
+  }
+
+  bool erase(const key_type& key, iterator first) {
+    const auto it = find(key, first);
+    if (it == end()) return false;
+    map_.unstable_erase(it);
+    return true;
+  }
+
+  void deduplicate() {
+    for (auto it = begin(); it != end();) {
+      if (const auto key = it->first; ++it != end()) {
+        while (erase(key, it));
+      }
+    }
+  }
+
   Map map_;
 };
 
 // Deduction guide for in-place constructor.
-template <typename K, typename V, std::size_t... Sizes, typename... Types>
-SmallMap(InitializerList<KeyValue<K, V>, std::index_sequence<Sizes...>, Types...>&&)
-    -> SmallMap<K, V, sizeof...(Sizes)>;
+template <typename K, typename V, typename E, std::size_t... Sizes, typename... Types>
+SmallMap(InitializerList<KeyValue<K, V, E>, std::index_sequence<Sizes...>, Types...>&&)
+    -> SmallMap<K, V, sizeof...(Sizes), E>;
 
 // Returns whether the key-value pairs of two maps are equal.
-template <typename K, typename V, std::size_t N, typename Q, typename W, std::size_t M>
-bool operator==(const SmallMap<K, V, N>& lhs, const SmallMap<Q, W, M>& rhs) {
+template <typename K, typename V, std::size_t N, typename Q, typename W, std::size_t M, typename E>
+bool operator==(const SmallMap<K, V, N, E>& lhs, const SmallMap<Q, W, M, E>& rhs) {
   if (lhs.size() != rhs.size()) return false;
 
   for (const auto& [k, v] : lhs) {
     const auto& lv = v;
-    if (!rhs.find(k, [&lv](const auto& rv) { return lv == rv; }).value_or(false)) {
+    if (!rhs.get(k, [&lv](const auto& rv) { return lv == rv; }).value_or(false)) {
       return false;
     }
   }
@@ -195,8 +295,8 @@
 }
 
 // TODO: Remove in C++20.
-template <typename K, typename V, std::size_t N, typename Q, typename W, std::size_t M>
-inline bool operator!=(const SmallMap<K, V, N>& lhs, const SmallMap<Q, W, M>& rhs) {
+template <typename K, typename V, std::size_t N, typename Q, typename W, std::size_t M, typename E>
+inline bool operator!=(const SmallMap<K, V, N, E>& lhs, const SmallMap<Q, W, M, E>& rhs) {
   return !(lhs == rhs);
 }
 
diff --git a/include/ftl/small_vector.h b/include/ftl/small_vector.h
index cb0ae35..339726e 100644
--- a/include/ftl/small_vector.h
+++ b/include/ftl/small_vector.h
@@ -16,7 +16,7 @@
 
 #pragma once
 
-#include <ftl/array_traits.h>
+#include <ftl/details/array_traits.h>
 #include <ftl/static_vector.h>
 
 #include <algorithm>
@@ -37,6 +37,9 @@
 // augmented by an unstable_erase operation that does not preserve order, and a replace operation
 // that destructively emplaces.
 //
+// Unlike std::vector, T does not require copy/move assignment, so may be an object with const data
+// members, or be const itself.
+//
 // SmallVector<T, 0> is a specialization that thinly wraps std::vector.
 //
 // Example usage:
@@ -73,7 +76,7 @@
 //   assert(strings[2] == "???");
 //
 template <typename T, std::size_t N>
-class SmallVector final : ArrayTraits<T>, ArrayComparators<SmallVector> {
+class SmallVector final : details::ArrayTraits<T>, details::ArrayComparators<SmallVector> {
   using Static = StaticVector<T, N>;
   using Dynamic = SmallVector<T, 0>;
 
@@ -105,10 +108,9 @@
   SmallVector(Arg&& arg, Args&&... args)
       : vector_(std::in_place_type<Static>, std::forward<Arg>(arg), std::forward<Args>(args)...) {}
 
-  // Copies at most N elements from a smaller convertible vector.
-  template <typename U, std::size_t M, typename = std::enable_if_t<M <= N>>
-  SmallVector(const SmallVector<U, M>& other)
-      : SmallVector(kIteratorRange, other.begin(), other.end()) {}
+  // Copies or moves elements from a smaller convertible vector.
+  template <typename U, std::size_t M, typename = std::enable_if_t<(M > 0)>>
+  SmallVector(SmallVector<U, M> other) : vector_(convert(std::move(other))) {}
 
   void swap(SmallVector& other) { vector_.swap(other.vector_); }
 
@@ -151,8 +153,6 @@
   DISPATCH(reference, back, noexcept)
   DISPATCH(const_reference, back, const)
 
-#undef DISPATCH
-
   reference operator[](size_type i) {
     return dynamic() ? std::get<Dynamic>(vector_)[i] : std::get<Static>(vector_)[i];
   }
@@ -214,13 +214,15 @@
   //
   // The last() and end() iterators are invalidated.
   //
-  void pop_back() {
-    if (dynamic()) {
-      std::get<Dynamic>(vector_).pop_back();
-    } else {
-      std::get<Static>(vector_).pop_back();
-    }
-  }
+  DISPATCH(void, pop_back, noexcept)
+
+  // Removes all elements.
+  //
+  // All iterators are invalidated.
+  //
+  DISPATCH(void, clear, noexcept)
+
+#undef DISPATCH
 
   // Erases an element, but does not preserve order. Rather than shifting subsequent elements,
   // this moves the last element to the slot of the erased element.
@@ -235,7 +237,30 @@
     }
   }
 
+  // Extracts the elements as std::vector.
+  std::vector<T> promote() && {
+    if (dynamic()) {
+      return std::get<Dynamic>(std::move(vector_)).promote();
+    } else {
+      return {std::make_move_iterator(begin()), std::make_move_iterator(end())};
+    }
+  }
+
  private:
+  template <typename, std::size_t>
+  friend class SmallVector;
+
+  template <typename U, std::size_t M>
+  static std::variant<Static, Dynamic> convert(SmallVector<U, M>&& other) {
+    using Other = SmallVector<U, M>;
+
+    if (other.dynamic()) {
+      return std::get<typename Other::Dynamic>(std::move(other.vector_));
+    } else {
+      return std::get<typename Other::Static>(std::move(other.vector_));
+    }
+  }
+
   template <auto InsertStatic, auto InsertDynamic, typename... Args>
   auto insert(Args&&... args) {
     if (Dynamic* const vector = std::get_if<Dynamic>(&vector_)) {
@@ -266,12 +291,13 @@
 
 // Partial specialization without static storage.
 template <typename T>
-class SmallVector<T, 0> final : ArrayTraits<T>,
-                                ArrayIterators<SmallVector<T, 0>, T>,
+class SmallVector<T, 0> final : details::ArrayTraits<T>,
+                                details::ArrayComparators<SmallVector>,
+                                details::ArrayIterators<SmallVector<T, 0>, T>,
                                 std::vector<T> {
-  using ArrayTraits<T>::construct_at;
+  using details::ArrayTraits<T>::replace_at;
 
-  using Iter = ArrayIterators<SmallVector, T>;
+  using Iter = details::ArrayIterators<SmallVector, T>;
   using Impl = std::vector<T>;
 
   friend Iter;
@@ -291,8 +317,30 @@
   FTL_ARRAY_TRAIT(T, const_iterator);
   FTL_ARRAY_TRAIT(T, const_reverse_iterator);
 
+  // See std::vector for underlying constructors.
   using Impl::Impl;
 
+  // Copies and moves a vector, respectively.
+  SmallVector(const SmallVector&) = default;
+  SmallVector(SmallVector&&) = default;
+
+  // Constructs elements in place. See StaticVector for underlying constructor.
+  template <typename U, std::size_t... Sizes, typename... Types>
+  SmallVector(InitializerList<U, std::index_sequence<Sizes...>, Types...>&& list)
+      : SmallVector(SmallVector<T, sizeof...(Sizes)>(std::move(list))) {}
+
+  // Copies or moves elements from a convertible vector.
+  template <typename U, std::size_t M>
+  SmallVector(SmallVector<U, M> other) : Impl(convert(std::move(other))) {}
+
+  SmallVector& operator=(SmallVector other) {
+    // Define copy/move assignment in terms of copy/move construction.
+    swap(other);
+    return *this;
+  }
+
+  void swap(SmallVector& other) { Impl::swap(other); }
+
   using Impl::empty;
   using Impl::max_size;
   using Impl::size;
@@ -324,10 +372,7 @@
 
   template <typename... Args>
   reference replace(const_iterator it, Args&&... args) {
-    value_type element{std::forward<Args>(args)...};
-    std::destroy_at(it);
-    // This is only safe because exceptions are disabled.
-    return *construct_at(it, std::move(element));
+    return replace_at(it, std::forward<Args>(args)...);
   }
 
   template <typename... Args>
@@ -345,14 +390,34 @@
     return true;
   }
 
+  using Impl::clear;
   using Impl::pop_back;
 
   void unstable_erase(iterator it) {
-    if (it != last()) std::iter_swap(it, last());
+    if (it != last()) replace(it, std::move(back()));
     pop_back();
   }
 
-  void swap(SmallVector& other) { Impl::swap(other); }
+  std::vector<T> promote() && { return std::move(*this); }
+
+ private:
+  template <typename U, std::size_t M>
+  static Impl convert(SmallVector<U, M>&& other) {
+    if constexpr (std::is_constructible_v<Impl, std::vector<U>&&>) {
+      return std::move(other).promote();
+    } else {
+      SmallVector vector(other.size());
+
+      // Consistently with StaticVector, T only requires copy/move construction from U, rather than
+      // copy/move assignment.
+      auto it = vector.begin();
+      for (auto& element : other) {
+        vector.replace(it++, std::move(element));
+      }
+
+      return vector;
+    }
+  }
 };
 
 template <typename>
diff --git a/include/ftl/static_vector.h b/include/ftl/static_vector.h
index 96a1ae8..eb83b85 100644
--- a/include/ftl/static_vector.h
+++ b/include/ftl/static_vector.h
@@ -16,7 +16,7 @@
 
 #pragma once
 
-#include <ftl/array_traits.h>
+#include <ftl/details/array_traits.h>
 #include <ftl/initializer_list.h>
 
 #include <algorithm>
@@ -39,6 +39,9 @@
 // adheres to standard containers, except the unstable_erase operation that does not preserve order,
 // and the replace operation that destructively emplaces.
 //
+// Unlike std::vector, T does not require copy/move assignment, so may be an object with const data
+// members, or be const itself.
+//
 // StaticVector<T, 1> is analogous to an iterable std::optional.
 // StaticVector<T, 0> is an error.
 //
@@ -73,14 +76,21 @@
 //   assert(strings[2] == "???");
 //
 template <typename T, std::size_t N>
-class StaticVector final : ArrayTraits<T>,
-                           ArrayIterators<StaticVector<T, N>, T>,
-                           ArrayComparators<StaticVector> {
+class StaticVector final : details::ArrayTraits<T>,
+                           details::ArrayIterators<StaticVector<T, N>, T>,
+                           details::ArrayComparators<StaticVector> {
   static_assert(N > 0);
 
-  using ArrayTraits<T>::construct_at;
+  // For constructor that moves from a smaller convertible vector.
+  template <typename, std::size_t>
+  friend class StaticVector;
 
-  using Iter = ArrayIterators<StaticVector, T>;
+  using details::ArrayTraits<T>::construct_at;
+  using details::ArrayTraits<T>::replace_at;
+  using details::ArrayTraits<T>::in_place_swap_ranges;
+  using details::ArrayTraits<T>::uninitialized_copy;
+
+  using Iter = details::ArrayIterators<StaticVector, T>;
   friend Iter;
 
   // There is ambiguity when constructing from two iterator-like elements like pointers:
@@ -117,14 +127,18 @@
   StaticVector(StaticVector&& other) { swap<true>(other); }
 
   // Copies at most N elements from a smaller convertible vector.
-  template <typename U, std::size_t M, typename = std::enable_if_t<M <= N>>
+  template <typename U, std::size_t M>
   StaticVector(const StaticVector<U, M>& other)
-      : StaticVector(kIteratorRange, other.begin(), other.end()) {}
+      : StaticVector(kIteratorRange, other.begin(), other.end()) {
+    static_assert(N >= M, "Insufficient capacity");
+  }
 
-  // Copies at most N elements from an array.
+  // Copies at most N elements from a smaller convertible array.
   template <typename U, std::size_t M>
   explicit StaticVector(U (&array)[M])
-      : StaticVector(kIteratorRange, std::begin(array), std::end(array)) {}
+      : StaticVector(kIteratorRange, std::begin(array), std::end(array)) {
+    static_assert(N >= M, "Insufficient capacity");
+  }
 
   // Copies at most N elements from the range [first, last).
   //
@@ -139,7 +153,18 @@
   template <typename Iterator>
   StaticVector(IteratorRangeTag, Iterator first, Iterator last)
       : size_(std::min(max_size(), static_cast<size_type>(std::distance(first, last)))) {
-    std::uninitialized_copy(first, first + size_, begin());
+    uninitialized_copy(first, first + size_, begin());
+  }
+
+  // Moves at most N elements from a smaller convertible vector.
+  template <typename U, std::size_t M>
+  StaticVector(StaticVector<U, M>&& other) {
+    static_assert(N >= M, "Insufficient capacity");
+
+    // Same logic as swap<true>, though M need not be equal to N.
+    std::uninitialized_move(other.begin(), other.end(), begin());
+    std::destroy(other.begin(), other.end());
+    std::swap(size_, other.size_);
   }
 
   // Constructs at most N elements. The template arguments T and N are inferred using the
@@ -178,7 +203,9 @@
   template <typename U, std::size_t Size, std::size_t... Sizes, typename... Types>
   StaticVector(InitializerList<U, std::index_sequence<Size, Sizes...>, Types...>&& list)
       : StaticVector(std::index_sequence<0, 0, Size>{}, std::make_index_sequence<Size>{},
-                     std::index_sequence<Sizes...>{}, list.tuple) {}
+                     std::index_sequence<Sizes...>{}, list.tuple) {
+    static_assert(sizeof...(Sizes) < N, "Too many elements");
+  }
 
   ~StaticVector() { std::destroy(begin(), end()); }
 
@@ -189,8 +216,7 @@
   }
 
   StaticVector& operator=(StaticVector&& other) {
-    std::destroy(begin(), end());
-    size_ = 0;
+    clear();
     swap<true>(other);
     return *this;
   }
@@ -239,10 +265,7 @@
   //
   template <typename... Args>
   reference replace(const_iterator it, Args&&... args) {
-    value_type element{std::forward<Args>(args)...};
-    std::destroy_at(it);
-    // This is only safe because exceptions are disabled.
-    return *construct_at(it, std::move(element));
+    return replace_at(it, std::forward<Args>(args)...);
   }
 
   // Appends an element, and returns an iterator to it. If the vector is full, the element is not
@@ -280,6 +303,15 @@
   //
   void pop_back() { unstable_erase(last()); }
 
+  // Removes all elements.
+  //
+  // All iterators are invalidated.
+  //
+  void clear() {
+    std::destroy(begin(), end());
+    size_ = 0;
+  }
+
   // Erases an element, but does not preserve order. Rather than shifting subsequent elements,
   // this moves the last element to the slot of the erased element.
   //
@@ -372,7 +404,7 @@
     }
 
     // Swap elements [0, min).
-    std::swap_ranges(begin(), begin() + min, other.begin());
+    in_place_swap_ranges(begin(), begin() + min, other.begin());
 
     // No elements to move if sizes are equal.
     if (min == max) return;
diff --git a/include/ftl/string.h b/include/ftl/string.h
new file mode 100644
index 0000000..2d96b06
--- /dev/null
+++ b/include/ftl/string.h
@@ -0,0 +1,101 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <cassert>
+#include <charconv>
+#include <limits>
+#include <string>
+#include <string_view>
+#include <type_traits>
+
+namespace android::ftl {
+
+enum class Radix { kBin = 2, kDec = 10, kHex = 16 };
+
+template <typename T>
+struct to_chars_length {
+  static_assert(std::is_integral_v<T>);
+  // Maximum binary digits, plus minus sign and radix prefix.
+  static constexpr std::size_t value = std::numeric_limits<std::make_unsigned_t<T>>::digits + 3;
+};
+
+template <typename T>
+constexpr std::size_t to_chars_length_v = to_chars_length<T>::value;
+
+template <typename T = std::int64_t>
+using to_chars_buffer_t = char[to_chars_length_v<T>];
+
+// Lightweight (not allocating nor sprintf-based) alternative to std::to_string for integers, with
+// optional radix. See also ftl::to_string below.
+//
+//   ftl::to_chars_buffer_t<> buffer;
+//
+//   assert(ftl::to_chars(buffer, 123u) == "123");
+//   assert(ftl::to_chars(buffer, -42, ftl::Radix::kBin) == "-0b101010");
+//   assert(ftl::to_chars(buffer, 0xcafe, ftl::Radix::kHex) == "0xcafe");
+//   assert(ftl::to_chars(buffer, '*', ftl::Radix::kHex) == "0x2a");
+//
+template <typename T, std::size_t N>
+std::string_view to_chars(char (&buffer)[N], T v, Radix radix = Radix::kDec) {
+  static_assert(N >= to_chars_length_v<T>);
+
+  auto begin = buffer + 2;
+  const auto [end, err] = std::to_chars(begin, buffer + N, v, static_cast<int>(radix));
+  assert(err == std::errc());
+
+  if (radix == Radix::kDec) {
+    // TODO: Replace with {begin, end} in C++20.
+    return {begin, static_cast<std::size_t>(end - begin)};
+  }
+
+  const auto prefix = radix == Radix::kBin ? 'b' : 'x';
+  if constexpr (std::is_unsigned_v<T>) {
+    buffer[0] = '0';
+    buffer[1] = prefix;
+  } else {
+    if (*begin == '-') {
+      *buffer = '-';
+    } else {
+      --begin;
+    }
+
+    *begin-- = prefix;
+    *begin = '0';
+  }
+
+  // TODO: Replace with {buffer, end} in C++20.
+  return {buffer, static_cast<std::size_t>(end - buffer)};
+}
+
+// Lightweight (not sprintf-based) alternative to std::to_string for integers, with optional radix.
+//
+//   assert(ftl::to_string(123u) == "123");
+//   assert(ftl::to_string(-42, ftl::Radix::kBin) == "-0b101010");
+//   assert(ftl::to_string(0xcafe, ftl::Radix::kHex) == "0xcafe");
+//   assert(ftl::to_string('*', ftl::Radix::kHex) == "0x2a");
+//
+template <typename T>
+inline std::string to_string(T v, Radix radix = Radix::kDec) {
+  to_chars_buffer_t<T> buffer;
+  return std::string(to_chars(buffer, v, radix));
+}
+
+std::string to_string(bool) = delete;
+std::string to_string(bool, Radix) = delete;
+
+}  // namespace android::ftl
diff --git a/include/input/DisplayViewport.h b/include/input/DisplayViewport.h
index a6213f3..9148fee 100644
--- a/include/input/DisplayViewport.h
+++ b/include/input/DisplayViewport.h
@@ -18,7 +18,8 @@
 #define _LIBINPUT_DISPLAY_VIEWPORT_H
 
 #include <android-base/stringprintf.h>
-#include <ftl/NamedEnum.h>
+#include <ftl/enum.h>
+#include <ftl/string.h>
 #include <gui/constants.h>
 #include <input/Input.h>
 
@@ -44,6 +45,8 @@
     INTERNAL = 1,
     EXTERNAL = 2,
     VIRTUAL = 3,
+
+    ftl_last = VIRTUAL
 };
 
 /*
@@ -132,9 +135,8 @@
                             "physicalFrame=[%d, %d, %d, %d], "
                             "deviceSize=[%d, %d], "
                             "isActive=[%d]",
-                            NamedEnum::string(type).c_str(), displayId, uniqueId.c_str(),
-                            physicalPort ? StringPrintf("%" PRIu8, *physicalPort).c_str()
-                                         : "<none>",
+                            ftl::enum_string(type).c_str(), displayId, uniqueId.c_str(),
+                            physicalPort ? ftl::to_string(*physicalPort).c_str() : "<none>",
                             orientation, logicalLeft, logicalTop, logicalRight, logicalBottom,
                             physicalLeft, physicalTop, physicalRight, physicalBottom, deviceWidth,
                             deviceHeight, isActive);
diff --git a/include/input/Input.h b/include/input/Input.h
index 4adaa5b..b23a951 100644
--- a/include/input/Input.h
+++ b/include/input/Input.h
@@ -31,10 +31,8 @@
 #include <stdint.h>
 #include <ui/Transform.h>
 #include <utils/BitSet.h>
-#include <utils/KeyedVector.h>
 #include <utils/RefBase.h>
 #include <utils/Timers.h>
-#include <utils/Vector.h>
 #include <array>
 #include <limits>
 #include <queue>
@@ -88,7 +86,7 @@
      */
     AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE = 0x40,
 
-#ifdef __linux__
+#if defined(__linux__)
     /**
      * This event was generated or modified by accessibility service.
      */
@@ -166,7 +164,7 @@
  * (We want at least 10 but some touch controllers obstensibly configured for 10 pointers
  * will occasionally emit 11.  There is not much harm making this constant bigger.)
  */
-#define MAX_POINTERS 16
+static constexpr size_t MAX_POINTERS = 16;
 
 /*
  * Maximum number of samples supported per motion event.
@@ -201,8 +199,17 @@
 class Parcel;
 #endif
 
+/*
+ * Apply the given transform to the point without applying any translation/offset.
+ */
+vec2 transformWithoutTranslation(const ui::Transform& transform, const vec2& xy);
+
 const char* inputEventTypeToString(int32_t type);
 
+std::string inputEventSourceToString(int32_t source);
+
+bool isFromSource(uint32_t source, uint32_t test);
+
 /*
  * Flags that flow alongside events in the input dispatch system to help with certain
  * policy decisions such as waking from device sleep.
@@ -268,23 +275,21 @@
 
 /**
  * Classifications of the current gesture, if available.
- *
- * The following values must be kept in sync with MotionEvent.java
  */
 enum class MotionClassification : uint8_t {
     /**
      * No classification is available.
      */
-    NONE = 0,
+    NONE = AMOTION_EVENT_CLASSIFICATION_NONE,
     /**
      * Too early to classify the current gesture. Need more events. Look for changes in the
      * upcoming motion events.
      */
-    AMBIGUOUS_GESTURE = 1,
+    AMBIGUOUS_GESTURE = AMOTION_EVENT_CLASSIFICATION_AMBIGUOUS_GESTURE,
     /**
      * The current gesture likely represents a user intentionally exerting force on the touchscreen.
      */
-    DEEP_PRESS = 2,
+    DEEP_PRESS = AMOTION_EVENT_CLASSIFICATION_DEEP_PRESS,
 };
 
 /**
@@ -369,13 +374,10 @@
     float getAxisValue(int32_t axis) const;
     status_t setAxisValue(int32_t axis, float value);
 
-    void scale(float globalScale);
-
     // Scale the pointer coordinates according to a global scale and a
     // window scale. The global scale will be applied to TOUCH/TOOL_MAJOR/MINOR
     // axes, however the window scaling will not.
     void scale(float globalScale, float windowXScale, float windowYScale);
-    void applyOffset(float xOffset, float yOffset);
 
     void transform(const ui::Transform& transform);
 
@@ -526,13 +528,17 @@
 
     inline int32_t getAction() const { return mAction; }
 
-    inline int32_t getActionMasked() const { return mAction & AMOTION_EVENT_ACTION_MASK; }
+    static int32_t getActionMasked(int32_t action) { return action & AMOTION_EVENT_ACTION_MASK; }
 
-    inline int32_t getActionIndex() const {
-        return (mAction & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK)
-                >> AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
+    inline int32_t getActionMasked() const { return getActionMasked(mAction); }
+
+    static uint8_t getActionIndex(int32_t action) {
+        return (action & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK) >>
+                AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
     }
 
+    inline int32_t getActionIndex() const { return getActionIndex(mAction); }
+
     inline void setAction(int32_t action) { mAction = action; }
 
     inline int32_t getFlags() const { return mFlags; }
@@ -561,7 +567,9 @@
 
     inline float getYOffset() const { return mTransform.ty(); }
 
-    inline ui::Transform getTransform() const { return mTransform; }
+    inline const ui::Transform& getTransform() const { return mTransform; }
+
+    int getSurfaceRotation() const;
 
     inline float getXPrecision() const { return mXPrecision; }
 
@@ -577,9 +585,7 @@
 
     void setCursorPosition(float x, float y);
 
-    uint32_t getDisplayOrientation() const { return mDisplayOrientation; }
-
-    int2 getDisplaySize() const { return {mDisplayWidth, mDisplayHeight}; }
+    inline const ui::Transform& getRawTransform() const { return mRawTransform; }
 
     static inline bool isValidCursorPosition(float x, float y) { return !isnan(x) && !isnan(y); }
 
@@ -755,8 +761,8 @@
                     int32_t flags, int32_t edgeFlags, int32_t metaState, int32_t buttonState,
                     MotionClassification classification, const ui::Transform& transform,
                     float xPrecision, float yPrecision, float rawXCursorPosition,
-                    float rawYCursorPosition, uint32_t displayOrientation, int32_t displayWidth,
-                    int32_t displayHeight, nsecs_t downTime, nsecs_t eventTime, size_t pointerCount,
+                    float rawYCursorPosition, const ui::Transform& rawTransform, nsecs_t downTime,
+                    nsecs_t eventTime, size_t pointerCount,
                     const PointerProperties* pointerProperties, const PointerCoords* pointerCoords);
 
     void copyFrom(const MotionEvent* other, bool keepHistory);
@@ -789,11 +795,11 @@
 
     // Low-level accessors.
     inline const PointerProperties* getPointerProperties() const {
-        return mPointerProperties.array();
+        return mPointerProperties.data();
     }
     inline const nsecs_t* getSampleEventTimes() const { return mSampleEventTimes.data(); }
     inline const PointerCoords* getSamplePointerCoords() const {
-            return mSamplePointerCoords.array();
+        return mSamplePointerCoords.data();
     }
 
     static const char* getLabel(int32_t axis);
@@ -801,6 +807,16 @@
 
     static std::string actionToString(int32_t action);
 
+    // MotionEvent will transform various axes in different ways, based on the source. For
+    // example, the x and y axes will not have any offsets/translations applied if it comes from a
+    // relative mouse device (since SOURCE_RELATIVE_MOUSE is a non-pointer source). These methods
+    // are used to apply these transformations for different axes.
+    static vec2 calculateTransformedXY(uint32_t source, const ui::Transform&, const vec2& xy);
+    static float calculateTransformedAxisValue(int32_t axis, uint32_t source, const ui::Transform&,
+                                               const PointerCoords&);
+    static PointerCoords calculateTransformedCoords(uint32_t source, const ui::Transform&,
+                                                    const PointerCoords&);
+
 protected:
     int32_t mAction;
     int32_t mActionButton;
@@ -814,13 +830,11 @@
     float mYPrecision;
     float mRawXCursorPosition;
     float mRawYCursorPosition;
-    uint32_t mDisplayOrientation;
-    int32_t mDisplayWidth;
-    int32_t mDisplayHeight;
+    ui::Transform mRawTransform;
     nsecs_t mDownTime;
-    Vector<PointerProperties> mPointerProperties;
+    std::vector<PointerProperties> mPointerProperties;
     std::vector<nsecs_t> mSampleEventTimes;
-    Vector<PointerCoords> mSamplePointerCoords;
+    std::vector<PointerCoords> mSamplePointerCoords;
 };
 
 /*
@@ -834,15 +848,12 @@
 
     inline bool getHasFocus() const { return mHasFocus; }
 
-    inline bool getInTouchMode() const { return mInTouchMode; }
-
-    void initialize(int32_t id, bool hasFocus, bool inTouchMode);
+    void initialize(int32_t id, bool hasFocus);
 
     void initialize(const FocusEvent& from);
 
 protected:
     bool mHasFocus;
-    bool mInTouchMode;
 };
 
 /*
@@ -888,6 +899,25 @@
     float mX, mY;
 };
 
+/*
+ * Touch mode events.
+ */
+class TouchModeEvent : public InputEvent {
+public:
+    virtual ~TouchModeEvent() {}
+
+    virtual int32_t getType() const override { return AINPUT_EVENT_TYPE_TOUCH_MODE; }
+
+    inline bool isInTouchMode() const { return mIsInTouchMode; }
+
+    void initialize(int32_t id, bool isInTouchMode);
+
+    void initialize(const TouchModeEvent& from);
+
+protected:
+    bool mIsInTouchMode;
+};
+
 /**
  * Base class for verified events.
  * Do not create a VerifiedInputEvent explicitly.
@@ -912,8 +942,8 @@
  */
 struct __attribute__((__packed__)) VerifiedKeyEvent : public VerifiedInputEvent {
     int32_t action;
-    nsecs_t downTimeNanos;
     int32_t flags;
+    nsecs_t downTimeNanos;
     int32_t keyCode;
     int32_t scanCode;
     int32_t metaState;
@@ -928,8 +958,8 @@
     float rawX;
     float rawY;
     int32_t actionMasked;
-    nsecs_t downTimeNanos;
     int32_t flags;
+    nsecs_t downTimeNanos;
     int32_t metaState;
     int32_t buttonState;
 };
@@ -952,6 +982,7 @@
     virtual FocusEvent* createFocusEvent() = 0;
     virtual CaptureEvent* createCaptureEvent() = 0;
     virtual DragEvent* createDragEvent() = 0;
+    virtual TouchModeEvent* createTouchModeEvent() = 0;
 };
 
 /*
@@ -968,6 +999,7 @@
     virtual FocusEvent* createFocusEvent() override { return &mFocusEvent; }
     virtual CaptureEvent* createCaptureEvent() override { return &mCaptureEvent; }
     virtual DragEvent* createDragEvent() override { return &mDragEvent; }
+    virtual TouchModeEvent* createTouchModeEvent() override { return &mTouchModeEvent; }
 
 private:
     KeyEvent mKeyEvent;
@@ -975,6 +1007,7 @@
     FocusEvent mFocusEvent;
     CaptureEvent mCaptureEvent;
     DragEvent mDragEvent;
+    TouchModeEvent mTouchModeEvent;
 };
 
 /*
@@ -990,6 +1023,7 @@
     virtual FocusEvent* createFocusEvent() override;
     virtual CaptureEvent* createCaptureEvent() override;
     virtual DragEvent* createDragEvent() override;
+    virtual TouchModeEvent* createTouchModeEvent() override;
 
     void recycle(InputEvent* event);
 
@@ -1001,6 +1035,7 @@
     std::queue<std::unique_ptr<FocusEvent>> mFocusEventPool;
     std::queue<std::unique_ptr<CaptureEvent>> mCaptureEventPool;
     std::queue<std::unique_ptr<DragEvent>> mDragEventPool;
+    std::queue<std::unique_ptr<TouchModeEvent>> mTouchModeEventPool;
 };
 
 /*
diff --git a/include/input/InputDevice.h b/include/input/InputDevice.h
index 7f0324a..c4f03c9 100644
--- a/include/input/InputDevice.h
+++ b/include/input/InputDevice.h
@@ -84,6 +84,9 @@
     GAME_ROTATION_VECTOR = ASENSOR_TYPE_GAME_ROTATION_VECTOR,
     GYROSCOPE_UNCALIBRATED = ASENSOR_TYPE_GYROSCOPE_UNCALIBRATED,
     SIGNIFICANT_MOTION = ASENSOR_TYPE_SIGNIFICANT_MOTION,
+
+    ftl_first = ACCELEROMETER,
+    ftl_last = SIGNIFICANT_MOTION
 };
 
 enum class InputDeviceSensorAccuracy : int32_t {
@@ -105,6 +108,8 @@
     PLAYER_ID = 1,
     RGB = 2,
     MULTI_COLOR = 3,
+
+    ftl_last = MULTI_COLOR
 };
 
 struct InputDeviceSensorInfo {
@@ -230,7 +235,7 @@
     void addBatteryInfo(const InputDeviceBatteryInfo& info);
     void addLightInfo(const InputDeviceLightInfo& info);
 
-    inline void setKeyboardType(int32_t keyboardType) { mKeyboardType = keyboardType; }
+    void setKeyboardType(int32_t keyboardType);
     inline int32_t getKeyboardType() const { return mKeyboardType; }
 
     inline void setKeyCharacterMap(const std::shared_ptr<KeyCharacterMap> value) {
diff --git a/include/input/InputTransport.h b/include/input/InputTransport.h
index a790b56..5f9a37d 100644
--- a/include/input/InputTransport.h
+++ b/include/input/InputTransport.h
@@ -72,6 +72,9 @@
         CAPTURE,
         DRAG,
         TIMELINE,
+        TOUCH_MODE,
+
+        ftl_last = TOUCH_MODE
     };
 
     struct Header {
@@ -111,7 +114,7 @@
 
         struct Motion {
             int32_t eventId;
-            uint32_t empty1;
+            uint32_t pointerCount;
             nsecs_t eventTime __attribute__((aligned(8)));
             int32_t deviceId;
             int32_t source;
@@ -126,20 +129,22 @@
             uint8_t empty2[3];                   // 3 bytes to fill gap created by classification
             int32_t edgeFlags;
             nsecs_t downTime __attribute__((aligned(8)));
-            float dsdx;
-            float dtdx;
-            float dtdy;
-            float dsdy;
-            float tx;
-            float ty;
+            float dsdx; // Begin window transform
+            float dtdx; //
+            float dtdy; //
+            float dsdy; //
+            float tx;   //
+            float ty;   // End window transform
             float xPrecision;
             float yPrecision;
             float xCursorPosition;
             float yCursorPosition;
-            uint32_t displayOrientation;
-            int32_t displayWidth;
-            int32_t displayHeight;
-            uint32_t pointerCount;
+            float dsdxRaw; // Begin raw transform
+            float dtdxRaw; //
+            float dtdyRaw; //
+            float dsdyRaw; //
+            float txRaw;   //
+            float tyRaw;   // End raw transform
             /**
              * The "pointers" field must be the last field of the struct InputMessage.
              * When we send the struct InputMessage across the socket, we are not
@@ -173,10 +178,9 @@
 
         struct Focus {
             int32_t eventId;
-            // The following 3 fields take up 4 bytes total
+            // The following 2 fields take up 4 bytes total
             bool hasFocus;
-            bool inTouchMode;
-            uint8_t empty[2];
+            uint8_t empty[3];
 
             inline size_t size() const { return sizeof(Focus); }
         } focus;
@@ -206,6 +210,15 @@
 
             inline size_t size() const { return sizeof(Timeline); }
         } timeline;
+
+        struct TouchMode {
+            int32_t eventId;
+            // The following 2 fields take up 4 bytes total
+            bool isInTouchMode;
+            uint8_t empty[3];
+
+            inline size_t size() const { return sizeof(TouchMode); }
+        } touchMode;
     } __attribute__((aligned(8))) body;
 
     bool isValid(size_t actualSize) const;
@@ -355,9 +368,8 @@
                                 int32_t metaState, int32_t buttonState,
                                 MotionClassification classification, const ui::Transform& transform,
                                 float xPrecision, float yPrecision, float xCursorPosition,
-                                float yCursorPosition, uint32_t displayOrientation,
-                                int32_t displayWidth, int32_t displayHeight, nsecs_t downTime,
-                                nsecs_t eventTime, uint32_t pointerCount,
+                                float yCursorPosition, const ui::Transform& rawTransform,
+                                nsecs_t downTime, nsecs_t eventTime, uint32_t pointerCount,
                                 const PointerProperties* pointerProperties,
                                 const PointerCoords* pointerCoords);
 
@@ -368,7 +380,7 @@
      * Returns DEAD_OBJECT if the channel's peer has been closed.
      * Other errors probably indicate that the channel is broken.
      */
-    status_t publishFocusEvent(uint32_t seq, int32_t eventId, bool hasFocus, bool inTouchMode);
+    status_t publishFocusEvent(uint32_t seq, int32_t eventId, bool hasFocus);
 
     /* Publishes a capture event to the input channel.
      *
@@ -388,6 +400,15 @@
      */
     status_t publishDragEvent(uint32_t seq, int32_t eventId, float x, float y, bool isExiting);
 
+    /* Publishes a touch mode event to the input channel.
+     *
+     * Returns OK on success.
+     * Returns WOULD_BLOCK if the channel is full.
+     * Returns DEAD_OBJECT if the channel's peer has been closed.
+     * Other errors probably indicate that the channel is broken.
+     */
+    status_t publishTouchModeEvent(uint32_t seq, int32_t eventId, bool isInTouchMode);
+
     struct Finished {
         uint32_t seq;
         bool handled;
@@ -479,24 +500,6 @@
     status_t sendTimeline(int32_t inputEventId,
                           std::array<nsecs_t, GraphicsTimeline::SIZE> timeline);
 
-    /* Returns true if there is a deferred event waiting.
-     *
-     * Should be called after calling consume() to determine whether the consumer
-     * has a deferred event to be processed.  Deferred events are somewhat special in
-     * that they have already been removed from the input channel.  If the input channel
-     * becomes empty, the client may need to do extra work to ensure that it processes
-     * the deferred event despite the fact that the input channel's file descriptor
-     * is not readable.
-     *
-     * One option is simply to call consume() in a loop until it returns WOULD_BLOCK.
-     * This guarantees that all deferred events will be processed.
-     *
-     * Alternately, the caller can call hasDeferredEvent() to determine whether there is
-     * a deferred event waiting and then ensure that its event loop wakes up at least
-     * one more time to consume the deferred event.
-     */
-    bool hasDeferredEvent() const;
-
     /* Returns true if there is a pending batch.
      *
      * Should be called after calling consume() with consumeBatches == false to determine
@@ -658,6 +661,7 @@
     static void initializeFocusEvent(FocusEvent* event, const InputMessage* msg);
     static void initializeCaptureEvent(CaptureEvent* event, const InputMessage* msg);
     static void initializeDragEvent(DragEvent* event, const InputMessage* msg);
+    static void initializeTouchModeEvent(TouchModeEvent* event, const InputMessage* msg);
     static void addSample(MotionEvent* event, const InputMessage* msg);
     static bool canAddSample(const Batch& batch, const InputMessage* msg);
     static ssize_t findSampleNoLaterThan(const Batch& batch, nsecs_t time);
diff --git a/include/powermanager/PowerHalWrapper.h b/include/powermanager/PowerHalWrapper.h
index 2c6eacb..dfb0ff5 100644
--- a/include/powermanager/PowerHalWrapper.h
+++ b/include/powermanager/PowerHalWrapper.h
@@ -201,10 +201,8 @@
     std::array<std::atomic<HalSupport>,
                static_cast<int32_t>(hardware::power::Boost::DISPLAY_UPDATE_IMMINENT) + 1>
             mBoostSupportedArray GUARDED_BY(mBoostMutex) = {HalSupport::UNKNOWN};
-    // Android framework only sends mode upto DISPLAY_INACTIVE.
-    // Need to increase the array if more mode supported.
     std::array<std::atomic<HalSupport>,
-               static_cast<int32_t>(hardware::power::Mode::DISPLAY_INACTIVE) + 1>
+               static_cast<int32_t>(*(android::enum_range<hardware::power::Mode>().end() - 1)) + 1>
             mModeSupportedArray GUARDED_BY(mModeMutex) = {HalSupport::UNKNOWN};
 };
 
diff --git a/include/private/performance_hint_private.h b/include/private/performance_hint_private.h
index 5832bf4..f27f5f1 100644
--- a/include/private/performance_hint_private.h
+++ b/include/private/performance_hint_private.h
@@ -17,124 +17,8 @@
 #ifndef ANDROID_PRIVATE_NATIVE_PERFORMANCE_HINT_PRIVATE_H
 #define ANDROID_PRIVATE_NATIVE_PERFORMANCE_HINT_PRIVATE_H
 
-#include <stdint.h>
-
 __BEGIN_DECLS
 
-struct APerformanceHintManager;
-struct APerformanceHintSession;
-
-/**
- * An opaque type representing a handle to a performance hint manager.
- * It must be released after use.
- *
- * <p>To use:<ul>
- *    <li>Obtain the performance hint manager instance by calling
- *        {@link APerformanceHint_getManager} function.</li>
- *    <li>Create an {@link APerformanceHintSession} with
- *        {@link APerformanceHint_createSession}.</li>
- *    <li>Get the preferred update rate in nanoseconds with
- *        {@link APerformanceHint_getPreferredUpdateRateNanos}.</li>
- */
-typedef struct APerformanceHintManager APerformanceHintManager;
-
-/**
- * An opaque type representing a handle to a performance hint session.
- * A session can only be acquired from a {@link APerformanceHintManager}
- * with {@link APerformanceHint_getPreferredUpdateRateNanos}. It must be
- * freed with {@link APerformanceHint_closeSession} after use.
- *
- * A Session represents a group of threads with an inter-related workload such that hints for
- * their performance should be considered as a unit. The threads in a given session should be
- * long-life and not created or destroyed dynamically.
- *
- * <p>Each session is expected to have a periodic workload with a target duration for each
- * cycle. The cycle duration is likely greater than the target work duration to allow other
- * parts of the pipeline to run within the available budget. For example, a renderer thread may
- * work at 60hz in order to produce frames at the display's frame but have a target work
- * duration of only 6ms.</p>
- *
- * <p>After each cycle of work, the client is expected to use
- * {@link APerformanceHint_reportActualWorkDuration} to report the actual time taken to
- * complete.</p>
- *
- * <p>To use:<ul>
- *    <li>Update a sessions target duration for each cycle of work
- *        with  {@link APerformanceHint_updateTargetWorkDuration}.</li>
- *    <li>Report the actual duration for the last cycle of work with
- *        {@link APerformanceHint_reportActualWorkDuration}.</li>
- *    <li>Release the session instance with
- *        {@link APerformanceHint_closeSession}.</li></ul></p>
- */
-typedef struct APerformanceHintSession APerformanceHintSession;
-
-/**
-  * Acquire an instance of the performance hint manager.
-  *
-  * @return manager instance on success, nullptr on failure.
-  */
-APerformanceHintManager* APerformanceHint_getManager();
-
-/**
- * Creates a session for the given set of threads and sets their initial target work
- * duration.
- * @param manager The performance hint manager instance.
- * @param threadIds The list of threads to be associated with this session. They must be part of
- *     this app's thread group.
- * @param size the size of threadIds.
- * @param initialTargetWorkDurationNanos The desired duration in nanoseconds for the new session.
- *     This must be positive.
- * @return manager instance on success, nullptr on failure.
- */
-APerformanceHintSession* APerformanceHint_createSession(APerformanceHintManager* manager,
-                                                        const int32_t* threadIds, size_t size,
-                                                        int64_t initialTargetWorkDurationNanos);
-
-/**
- * Get preferred update rate information for this device.
- *
- * @param manager The performance hint manager instance.
- * @return the preferred update rate supported by device software.
- */
-int64_t APerformanceHint_getPreferredUpdateRateNanos(APerformanceHintManager* manager);
-
-/**
- * Updates this session's target duration for each cycle of work.
- *
- * @param session The performance hint session instance to update.
- * @param targetDurationNanos the new desired duration in nanoseconds. This must be positive.
- * @return 0 on success
- *         EINVAL if targetDurationNanos is not positive.
- *         EPIPE if communication with the system service has failed.
- */
-int APerformanceHint_updateTargetWorkDuration(APerformanceHintSession* session,
-                                               int64_t targetDurationNanos);
-
-/**
- * Reports the actual duration for the last cycle of work.
- *
- * <p>The system will attempt to adjust the core placement of the threads within the thread
- * group and/or the frequency of the core on which they are run to bring the actual duration
- * close to the target duration.</p>
- *
- * @param session The performance hint session instance to update.
- * @param actualDurationNanos how long the thread group took to complete its last task in
- *     nanoseconds. This must be positive.
- * @return 0 on success
- *         EINVAL if actualDurationNanos is not positive.
- *         EPIPE if communication with the system service has failed.
- */
-int APerformanceHint_reportActualWorkDuration(APerformanceHintSession* session,
-                                               int64_t actualDurationNanos);
-
-/**
- * Release the performance hint manager pointer acquired via
- * {@link APerformanceHint_createSession}.
- *
- * @param session The performance hint session instance to release.
- */
-void APerformanceHint_closeSession(APerformanceHintSession* session);
-
 /**
  * For testing only.
  */
diff --git a/include/private/surface_control_private.h b/include/private/surface_control_private.h
index 37a476e..7e6c515 100644
--- a/include/private/surface_control_private.h
+++ b/include/private/surface_control_private.h
@@ -29,8 +29,8 @@
 /**
  * Callback to be notified when surface stats for a specific surface control are available.
  */
-typedef void (*ASurfaceControl_SurfaceStatsListener)(void* context,
-        ASurfaceControl* control, ASurfaceControlStats* stats);
+typedef void (*ASurfaceControl_SurfaceStatsListener)(void* context, int32_t id,
+        ASurfaceControlStats* stats);
 
 /**
  * Registers a callback to be invoked when surface stats from a specific surface are available.
@@ -42,7 +42,7 @@
  *
  * \param func The callback to be invoked when surface stats are available.
  */
-void ASurfaceControl_registerSurfaceStatsListener(ASurfaceControl* control, void* context,
+void ASurfaceControl_registerSurfaceStatsListener(ASurfaceControl* control, int32_t id, void* context,
         ASurfaceControl_SurfaceStatsListener func);
 
 /**
diff --git a/libs/arect/Android.bp b/libs/arect/Android.bp
index bb40f51..41b3460 100644
--- a/libs/arect/Android.bp
+++ b/libs/arect/Android.bp
@@ -57,4 +57,11 @@
         },
     },
     min_sdk_version: "29",
+    // static link, so it won't straddle a module boundary at runtime.
+    apex_available: [
+        "//apex_available:platform",
+        "com.android.media",
+        "com.android.media.swcodec",
+    ],
+
 }
diff --git a/libs/battery/Android.bp b/libs/battery/Android.bp
new file mode 100644
index 0000000..c860324
--- /dev/null
+++ b/libs/battery/Android.bp
@@ -0,0 +1,37 @@
+package {
+    // See: http://go/android-license-faq
+    default_applicable_licenses: ["frameworks_native_license"],
+}
+
+cc_library {
+    name: "libbattery",
+    srcs: [
+        "LongArrayMultiStateCounter.cpp",
+    ],
+    shared_libs: [
+        "liblog",
+    ],
+    cflags: [
+        "-Werror",
+        "-Wall",
+        "-Wextra",
+    ],
+    export_include_dirs: ["."],
+}
+
+cc_test {
+    name: "libbattery_test",
+    srcs: [
+        "MultiStateCounterTest.cpp",
+        "LongArrayMultiStateCounterTest.cpp",
+    ],
+    static_libs: ["libbattery"],
+    shared_libs: [
+        "liblog",
+    ],
+    cflags: [
+        "-Werror",
+        "-Wall",
+        "-Wextra",
+    ],
+}
diff --git a/libs/battery/LongArrayMultiStateCounter.cpp b/libs/battery/LongArrayMultiStateCounter.cpp
new file mode 100644
index 0000000..125cfaf
--- /dev/null
+++ b/libs/battery/LongArrayMultiStateCounter.cpp
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ * Android BPF library - public API
+ *
+ * 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 "LongArrayMultiStateCounter.h"
+#include <log/log.h>
+
+namespace android {
+namespace battery {
+
+template <>
+bool LongArrayMultiStateCounter::delta(const std::vector<uint64_t>& previousValue,
+                                       const std::vector<uint64_t>& newValue,
+                                       std::vector<uint64_t>* outValue) const {
+    size_t size = previousValue.size();
+    if (newValue.size() != size) {
+        ALOGE("Incorrect array size: %d, should be %d", (int)newValue.size(), (int)size);
+        return false;
+    }
+
+    bool is_delta_valid = true;
+    for (int i = size - 1; i >= 0; i--) {
+        if (newValue[i] >= previousValue[i]) {
+            (*outValue)[i] = newValue[i] - previousValue[i];
+        } else {
+            (*outValue)[i] = 0;
+            is_delta_valid = false;
+        }
+    }
+    return is_delta_valid;
+}
+
+template <>
+void LongArrayMultiStateCounter::add(std::vector<uint64_t>* value1,
+                                     const std::vector<uint64_t>& value2, const uint64_t numerator,
+                                     const uint64_t denominator) const {
+    if (numerator != denominator) {
+        for (int i = value2.size() - 1; i >= 0; i--) {
+            // The caller ensures that denominator != 0
+            (*value1)[i] += value2[i] * numerator / denominator;
+        }
+    } else {
+        for (int i = value2.size() - 1; i >= 0; i--) {
+            (*value1)[i] += value2[i];
+        }
+    }
+}
+
+template <>
+std::string LongArrayMultiStateCounter::valueToString(const std::vector<uint64_t>& v) const {
+    std::stringstream s;
+    s << "{";
+    bool first = true;
+    for (uint64_t n : v) {
+        if (!first) {
+            s << ", ";
+        }
+        s << n;
+        first = false;
+    }
+    s << "}";
+    return s.str();
+}
+
+} // namespace battery
+} // namespace android
diff --git a/services/surfaceflinger/tests/unittests/DisplayIdentificationTest.h b/libs/battery/LongArrayMultiStateCounter.h
similarity index 70%
rename from services/surfaceflinger/tests/unittests/DisplayIdentificationTest.h
rename to libs/battery/LongArrayMultiStateCounter.h
index 1c8e5cc..f3439f6 100644
--- a/services/surfaceflinger/tests/unittests/DisplayIdentificationTest.h
+++ b/libs/battery/LongArrayMultiStateCounter.h
@@ -1,5 +1,6 @@
 /*
- * Copyright 2018 The Android Open Source Project
+ * Copyright (C) 2021 The Android Open Source Project
+ * Android BPF library - public API
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -16,12 +17,13 @@
 
 #pragma once
 
-#include "DisplayHardware/DisplayIdentification.h"
+#include <vector>
+#include "MultiStateCounter.h"
 
 namespace android {
+namespace battery {
 
-const DisplayIdentificationData& getInternalEdid();
-const DisplayIdentificationData& getExternalEdid();
-const DisplayIdentificationData& getExternalEedid();
+typedef MultiStateCounter<std::vector<uint64_t>> LongArrayMultiStateCounter;
 
+} // namespace battery
 } // namespace android
diff --git a/libs/battery/LongArrayMultiStateCounterTest.cpp b/libs/battery/LongArrayMultiStateCounterTest.cpp
new file mode 100644
index 0000000..e4e6b2a
--- /dev/null
+++ b/libs/battery/LongArrayMultiStateCounterTest.cpp
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ * Android BPF library - public API
+ *
+ * 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 <gtest/gtest.h>
+#include "LongArrayMultiStateCounter.h"
+
+namespace android {
+namespace battery {
+
+class LongArrayMultiStateCounterTest : public testing::Test {};
+
+TEST_F(LongArrayMultiStateCounterTest, stateChange) {
+    LongArrayMultiStateCounter testCounter(2, std::vector<uint64_t>(4));
+    testCounter.updateValue(std::vector<uint64_t>({0, 0, 0, 0}), 1000);
+    testCounter.setState(0, 1000);
+    testCounter.setState(1, 2000);
+    testCounter.updateValue(std::vector<uint64_t>({100, 200, 300, 400}), 3000);
+
+    // Time was split in half between the two states, so the counts will be split 50:50 too
+    EXPECT_EQ(std::vector<uint64_t>({50, 100, 150, 200}), testCounter.getCount(0));
+    EXPECT_EQ(std::vector<uint64_t>({50, 100, 150, 200}), testCounter.getCount(1));
+}
+
+TEST_F(LongArrayMultiStateCounterTest, accumulation) {
+    LongArrayMultiStateCounter testCounter(2, std::vector<uint64_t>(4));
+    testCounter.updateValue(std::vector<uint64_t>({0, 0, 0, 0}), 1000);
+    testCounter.setState(0, 1000);
+    testCounter.setState(1, 2000);
+    testCounter.updateValue(std::vector<uint64_t>({100, 200, 300, 400}), 3000);
+    testCounter.setState(0, 4000);
+    testCounter.updateValue(std::vector<uint64_t>({200, 300, 400, 500}), 8000);
+
+    // The first delta is split 50:50:
+    //   0: {50, 100, 150, 200}
+    //   1: {50, 100, 150, 200}
+    // The second delta is split 4:1
+    //   0: {80, 80, 80, 80}
+    //   1: {20, 20, 20, 20}
+    EXPECT_EQ(std::vector<uint64_t>({130, 180, 230, 280}), testCounter.getCount(0));
+    EXPECT_EQ(std::vector<uint64_t>({70, 120, 170, 220}), testCounter.getCount(1));
+}
+
+TEST_F(LongArrayMultiStateCounterTest, toString) {
+    LongArrayMultiStateCounter testCounter(2, std::vector<uint64_t>(4));
+    testCounter.updateValue(std::vector<uint64_t>({0, 0, 0, 0}), 1000);
+    testCounter.setState(0, 1000);
+    testCounter.setState(1, 2000);
+    testCounter.updateValue(std::vector<uint64_t>({100, 200, 300, 400}), 3000);
+
+    EXPECT_STREQ("[0: {50, 100, 150, 200}, 1: {50, 100, 150, 200}] updated: 3000 currentState: 1",
+                 testCounter.toString().c_str());
+}
+
+} // namespace battery
+} // namespace android
diff --git a/libs/battery/MultiStateCounter.h b/libs/battery/MultiStateCounter.h
new file mode 100644
index 0000000..0caf005
--- /dev/null
+++ b/libs/battery/MultiStateCounter.h
@@ -0,0 +1,297 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ * Android BPF library - public API
+ *
+ * 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 <inttypes.h>
+#include <log/log.h>
+#include <time.h>
+#include <sstream>
+#include <string>
+
+/**
+ * An object that can track changes of some value over time, taking into account an additional
+ * dimension: the object's state.  As the tracked value changes, the deltas are distributed
+ * among the object states in accordance with the time spent in those states.
+ */
+namespace android {
+namespace battery {
+
+typedef uint16_t state_t;
+
+template <class T>
+class MultiStateCounter {
+    uint16_t stateCount;
+    state_t currentState;
+    time_t lastStateChangeTimestamp;
+    T emptyValue;
+    T lastValue;
+    time_t lastUpdateTimestamp;
+    T deltaValue;
+    bool isEnabled;
+
+    struct State {
+        time_t timeInStateSinceUpdate;
+        T counter;
+    };
+
+    State* states;
+
+public:
+    MultiStateCounter(uint16_t stateCount, const T& emptyValue);
+
+    virtual ~MultiStateCounter();
+
+    void setEnabled(bool enabled, time_t timestamp);
+
+    void setState(state_t state, time_t timestamp);
+
+    void setValue(state_t state, const T& value);
+
+    /**
+     * Updates the value by distributing the delta from the previously set value
+     * among states according to their respective time-in-state.
+     * Returns the delta from the previously set value.
+     */
+    const T& updateValue(const T& value, time_t timestamp);
+
+    /**
+     * Updates the value by distributing the specified increment among states according
+     * to their respective time-in-state.
+     */
+    void incrementValue(const T& increment, time_t timestamp);
+
+    /**
+     * Adds the specified increment to the value for the current state, without affecting
+     * the last updated value or timestamp.  Ignores partial time-in-state: the entirety of
+     * the increment is given to the current state.
+     */
+    void addValue(const T& increment);
+
+    void reset();
+
+    uint16_t getStateCount();
+
+    const T& getCount(state_t state);
+
+    std::string toString();
+
+private:
+    /**
+     * Subtracts previousValue from newValue and returns the result in outValue.
+     * Returns true iff the combination of previousValue and newValue is valid
+     * (newValue >= prevValue)
+     */
+    bool delta(const T& previousValue, const T& newValue, T* outValue) const;
+
+    /**
+     * Adds value2 to value1 and stores the result in value1.  Denominator is
+     * guaranteed to be non-zero.
+     */
+    void add(T* value1, const T& value2, const uint64_t numerator,
+             const uint64_t denominator) const;
+
+    std::string valueToString(const T& value) const;
+};
+
+// ---------------------- MultiStateCounter Implementation -------------------------
+// Since MultiStateCounter is a template, the implementation must be inlined.
+
+template <class T>
+MultiStateCounter<T>::MultiStateCounter(uint16_t stateCount, const T& emptyValue)
+      : stateCount(stateCount),
+        currentState(0),
+        lastStateChangeTimestamp(-1),
+        emptyValue(emptyValue),
+        lastValue(emptyValue),
+        lastUpdateTimestamp(-1),
+        deltaValue(emptyValue),
+        isEnabled(true) {
+    states = new State[stateCount];
+    for (int i = 0; i < stateCount; i++) {
+        states[i].timeInStateSinceUpdate = 0;
+        states[i].counter = emptyValue;
+    }
+}
+
+template <class T>
+MultiStateCounter<T>::~MultiStateCounter() {
+    delete[] states;
+};
+
+template <class T>
+void MultiStateCounter<T>::setEnabled(bool enabled, time_t timestamp) {
+    if (enabled == isEnabled) {
+        return;
+    }
+
+    if (!enabled) {
+        // Confirm the current state for the side-effect of updating the time-in-state
+        // counter for the current state.
+        setState(currentState, timestamp);
+    }
+
+    isEnabled = enabled;
+
+    if (lastStateChangeTimestamp >= 0) {
+        lastStateChangeTimestamp = timestamp;
+    }
+}
+
+template <class T>
+void MultiStateCounter<T>::setState(state_t state, time_t timestamp) {
+    if (isEnabled && lastStateChangeTimestamp >= 0) {
+        if (timestamp >= lastStateChangeTimestamp) {
+            states[currentState].timeInStateSinceUpdate += timestamp - lastStateChangeTimestamp;
+        } else {
+            ALOGE("setState is called with an earlier timestamp: %lu, previous timestamp: %lu\n",
+                  (unsigned long)timestamp, (unsigned long)lastStateChangeTimestamp);
+            // The accumulated durations have become unreliable. For example, if the timestamp
+            // sequence was 1000, 2000, 1000, 3000, if we accumulated the positive deltas,
+            // we would get 4000, which is greater than (last - first). This could lead to
+            // counts exceeding 100%.
+            for (int i = 0; i < stateCount; i++) {
+                states[i].timeInStateSinceUpdate = 0;
+            }
+        }
+    }
+    currentState = state;
+    lastStateChangeTimestamp = timestamp;
+}
+
+template <class T>
+void MultiStateCounter<T>::setValue(state_t state, const T& value) {
+    states[state].counter = value;
+}
+
+template <class T>
+const T& MultiStateCounter<T>::updateValue(const T& value, time_t timestamp) {
+    T* returnValue = &emptyValue;
+
+    // If the counter is disabled, we ignore the update, except when the counter got disabled after
+    // the previous update, in which case we still need to pick up the residual delta.
+    if (isEnabled || lastUpdateTimestamp < lastStateChangeTimestamp) {
+        // Confirm the current state for the side-effect of updating the time-in-state
+        // counter for the current state.
+        setState(currentState, timestamp);
+
+        if (lastUpdateTimestamp >= 0) {
+            if (timestamp > lastUpdateTimestamp) {
+                if (delta(lastValue, value, &deltaValue)) {
+                    returnValue = &deltaValue;
+                    time_t timeSinceUpdate = timestamp - lastUpdateTimestamp;
+                    for (int i = 0; i < stateCount; i++) {
+                        time_t timeInState = states[i].timeInStateSinceUpdate;
+                        if (timeInState) {
+                            add(&states[i].counter, deltaValue, timeInState, timeSinceUpdate);
+                            states[i].timeInStateSinceUpdate = 0;
+                        }
+                    }
+                } else {
+                    std::stringstream str;
+                    str << "updateValue is called with a value " << valueToString(value)
+                        << ", which is lower than the previous value " << valueToString(lastValue)
+                        << "\n";
+                    ALOGE("%s", str.str().c_str());
+
+                    for (int i = 0; i < stateCount; i++) {
+                        states[i].timeInStateSinceUpdate = 0;
+                    }
+                }
+            } else if (timestamp < lastUpdateTimestamp) {
+                ALOGE("updateValue is called with an earlier timestamp: %lu, previous: %lu\n",
+                      (unsigned long)timestamp, (unsigned long)lastUpdateTimestamp);
+
+                for (int i = 0; i < stateCount; i++) {
+                    states[i].timeInStateSinceUpdate = 0;
+                }
+            }
+        }
+    }
+    lastValue = value;
+    lastUpdateTimestamp = timestamp;
+    return *returnValue;
+}
+
+template <class T>
+void MultiStateCounter<T>::incrementValue(const T& increment, time_t timestamp) {
+    T newValue = lastValue;
+    add(&newValue, increment, 1 /* numerator */, 1 /* denominator */);
+    updateValue(newValue, timestamp);
+}
+
+template <class T>
+void MultiStateCounter<T>::addValue(const T& value) {
+    if (!isEnabled) {
+        return;
+    }
+    add(&states[currentState].counter, value, 1 /* numerator */, 1 /* denominator */);
+}
+
+template <class T>
+void MultiStateCounter<T>::reset() {
+    lastStateChangeTimestamp = -1;
+    lastUpdateTimestamp = -1;
+    for (int i = 0; i < stateCount; i++) {
+        states[i].timeInStateSinceUpdate = 0;
+        states[i].counter = emptyValue;
+    }
+}
+
+template <class T>
+uint16_t MultiStateCounter<T>::getStateCount() {
+    return stateCount;
+}
+
+template <class T>
+const T& MultiStateCounter<T>::getCount(state_t state) {
+    return states[state].counter;
+}
+
+template <class T>
+std::string MultiStateCounter<T>::toString() {
+    std::stringstream str;
+    str << "[";
+    for (int i = 0; i < stateCount; i++) {
+        if (i != 0) {
+            str << ", ";
+        }
+        str << i << ": " << valueToString(states[i].counter);
+        if (states[i].timeInStateSinceUpdate > 0) {
+            str << " timeInStateSinceUpdate: " << states[i].timeInStateSinceUpdate;
+        }
+    }
+    str << "]";
+    if (lastUpdateTimestamp >= 0) {
+        str << " updated: " << lastUpdateTimestamp;
+    }
+    if (lastStateChangeTimestamp >= 0) {
+        str << " currentState: " << currentState;
+        if (lastStateChangeTimestamp > lastUpdateTimestamp) {
+            str << " stateChanged: " << lastStateChangeTimestamp;
+        }
+    } else {
+        str << " currentState: none";
+    }
+    if (!isEnabled) {
+        str << " disabled";
+    }
+    return str.str();
+}
+
+} // namespace battery
+} // namespace android
diff --git a/libs/battery/MultiStateCounterTest.cpp b/libs/battery/MultiStateCounterTest.cpp
new file mode 100644
index 0000000..cb11a54
--- /dev/null
+++ b/libs/battery/MultiStateCounterTest.cpp
@@ -0,0 +1,268 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ * Android BPF library - public API
+ *
+ * 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 <gtest/gtest.h>
+#include "MultiStateCounter.h"
+
+namespace android {
+namespace battery {
+
+typedef MultiStateCounter<double> DoubleMultiStateCounter;
+
+template <>
+bool DoubleMultiStateCounter::delta(const double& previousValue, const double& newValue,
+                                    double* outValue) const {
+    *outValue = newValue - previousValue;
+    return *outValue >= 0;
+}
+
+template <>
+void DoubleMultiStateCounter::add(double* value1, const double& value2, const uint64_t numerator,
+                                  const uint64_t denominator) const {
+    if (numerator != denominator) {
+        // The caller ensures that denominator != 0
+        *value1 += value2 * numerator / denominator;
+    } else {
+        *value1 += value2;
+    }
+}
+
+template <>
+std::string DoubleMultiStateCounter::valueToString(const double& v) const {
+    return std::to_string(v);
+}
+
+class MultiStateCounterTest : public testing::Test {};
+
+TEST_F(MultiStateCounterTest, constructor) {
+    DoubleMultiStateCounter testCounter(3, 0);
+    testCounter.updateValue(0, 0);
+    testCounter.setState(1, 0);
+    double delta = testCounter.updateValue(3.14, 3000);
+
+    EXPECT_DOUBLE_EQ(0, testCounter.getCount(0));
+    EXPECT_DOUBLE_EQ(3.14, testCounter.getCount(1));
+    EXPECT_DOUBLE_EQ(0, testCounter.getCount(2));
+    EXPECT_DOUBLE_EQ(3.14, delta);
+}
+
+TEST_F(MultiStateCounterTest, stateChange) {
+    DoubleMultiStateCounter testCounter(3, 0);
+    testCounter.updateValue(0, 0);
+    testCounter.setState(1, 0);
+    testCounter.setState(2, 1000);
+    testCounter.updateValue(6.0, 3000);
+
+    EXPECT_DOUBLE_EQ(0, testCounter.getCount(0));
+    EXPECT_DOUBLE_EQ(2.0, testCounter.getCount(1));
+    EXPECT_DOUBLE_EQ(4.0, testCounter.getCount(2));
+}
+
+TEST_F(MultiStateCounterTest, setEnabled) {
+    DoubleMultiStateCounter testCounter(3, 0);
+    testCounter.updateValue(0, 0);
+    testCounter.setState(1, 0);
+    testCounter.setEnabled(false, 1000);
+    testCounter.setState(2, 2000);
+    testCounter.updateValue(6.0, 3000);
+
+    // In state 1: accumulated 1000 before disabled, that's 6.0 * 1000/3000 = 2.0
+    // In state 2: 0, since it is still disabled
+    EXPECT_DOUBLE_EQ(0, testCounter.getCount(0));
+    EXPECT_DOUBLE_EQ(2.0, testCounter.getCount(1));
+    EXPECT_DOUBLE_EQ(0, testCounter.getCount(2));
+
+    // Should have no effect since the counter is disabled
+    testCounter.setState(0, 3500);
+
+    // Should have no effect since the counter is disabled
+    testCounter.updateValue(10.0, 4000);
+
+    EXPECT_DOUBLE_EQ(0, testCounter.getCount(0));
+    EXPECT_DOUBLE_EQ(2.0, testCounter.getCount(1));
+    EXPECT_DOUBLE_EQ(0, testCounter.getCount(2));
+
+    testCounter.setState(2, 4500);
+
+    // Enable the counter to partially accumulate deltas for the current state, 2
+    testCounter.setEnabled(true, 5000);
+    testCounter.setEnabled(false, 6000);
+    testCounter.setEnabled(true, 7000);
+    testCounter.updateValue(20.0, 8000);
+
+    // The delta is 10.0 over 5000-3000=2000.
+    // Counter has been enabled in state 2 for (6000-5000)+(8000-7000) = 2000,
+    // so its share is (20.0-10.0) * 2000/(8000-4000) = 5.0
+    EXPECT_DOUBLE_EQ(0, testCounter.getCount(0));
+    EXPECT_DOUBLE_EQ(2.0, testCounter.getCount(1));
+    EXPECT_DOUBLE_EQ(5.0, testCounter.getCount(2));
+
+    testCounter.reset();
+    testCounter.setState(0, 0);
+    testCounter.updateValue(0, 0);
+    testCounter.setState(1, 2000);
+    testCounter.setEnabled(false, 3000);
+    testCounter.updateValue(200, 5000);
+
+    // 200 over 5000 = 40 per second
+    // Counter was in state 0 from 0 to 2000, so 2 sec, so the count should be 40 * 2 = 80
+    // It stayed in state 1 from 2000 to 3000, at which point the counter was disabled,
+    // so the count for state 1 should be 40 * 1 = 40.
+    // The remaining 2 seconds from 3000 to 5000 don't count because the counter was disabled.
+    EXPECT_DOUBLE_EQ(80.0, testCounter.getCount(0));
+    EXPECT_DOUBLE_EQ(40.0, testCounter.getCount(1));
+    EXPECT_DOUBLE_EQ(0, testCounter.getCount(2));
+}
+
+TEST_F(MultiStateCounterTest, reset) {
+    DoubleMultiStateCounter testCounter(3, 0);
+    testCounter.updateValue(0, 0);
+    testCounter.setState(1, 0);
+    testCounter.updateValue(2.72, 3000);
+
+    testCounter.reset();
+
+    EXPECT_DOUBLE_EQ(0, testCounter.getCount(0));
+    EXPECT_DOUBLE_EQ(0, testCounter.getCount(1));
+    EXPECT_DOUBLE_EQ(0, testCounter.getCount(2));
+
+    // Assert that we can still continue accumulating after a reset
+    testCounter.updateValue(0, 4000);
+    testCounter.updateValue(3.14, 5000);
+
+    EXPECT_DOUBLE_EQ(0, testCounter.getCount(0));
+    EXPECT_DOUBLE_EQ(3.14, testCounter.getCount(1));
+    EXPECT_DOUBLE_EQ(0, testCounter.getCount(2));
+}
+
+TEST_F(MultiStateCounterTest, timeAdjustment_setState) {
+    DoubleMultiStateCounter testCounter(3, 0);
+    testCounter.updateValue(0, 0);
+    testCounter.setState(1, 0);
+    testCounter.setState(2, 2000);
+
+    // Time moves back
+    testCounter.setState(1, 1000);
+    testCounter.updateValue(6.0, 3000);
+
+    EXPECT_DOUBLE_EQ(0, testCounter.getCount(0));
+
+    // We were in state 1 from 0 to 2000, which was erased because the time moved back.
+    // Then from 1000 to 3000, so we expect the count to be 6 * (2000/3000)
+    EXPECT_DOUBLE_EQ(4.0, testCounter.getCount(1));
+
+    // No time was effectively accumulated for state 2, because the timestamp moved back
+    // while we were in state 2.
+    EXPECT_DOUBLE_EQ(0, testCounter.getCount(2));
+}
+
+TEST_F(MultiStateCounterTest, timeAdjustment_updateValue) {
+    DoubleMultiStateCounter testCounter(1, 0);
+    testCounter.updateValue(0, 0);
+    testCounter.setState(0, 0);
+    testCounter.updateValue(6.0, 2000);
+
+    // Time moves back. The delta over the negative interval from 2000 to 1000 is ignored
+    testCounter.updateValue(8.0, 1000);
+    double delta = testCounter.updateValue(11.0, 3000);
+
+    // The total accumulated count is:
+    //  6.0          // For the period 0-2000
+    //  +(11.0-8.0)  // For the period 1000-3000
+    EXPECT_DOUBLE_EQ(9.0, testCounter.getCount(0));
+
+    //  11.0-8.0
+    EXPECT_DOUBLE_EQ(3.0, delta);
+}
+
+TEST_F(MultiStateCounterTest, updateValue_nonmonotonic) {
+    DoubleMultiStateCounter testCounter(2, 0);
+    testCounter.updateValue(0, 0);
+    testCounter.setState(0, 0);
+    testCounter.updateValue(6.0, 2000);
+
+    // Value goes down. The negative delta from 6.0 to 4.0 is ignored
+    testCounter.updateValue(4.0, 3000);
+
+    // Value goes up again. The positive delta from 4.0 to 7.0 is accumulated.
+    double delta = testCounter.updateValue(7.0, 4000);
+
+    // The total accumulated count is:
+    //  6.0          // For the period 0-2000
+    //  +(7.0-4.0)   // For the period 3000-4000
+    EXPECT_DOUBLE_EQ(9.0, testCounter.getCount(0));
+
+    //  7.0-4.0
+    EXPECT_DOUBLE_EQ(3.0, delta);
+}
+
+TEST_F(MultiStateCounterTest, incrementValue) {
+    DoubleMultiStateCounter testCounter(2, 0);
+    testCounter.updateValue(0, 0);
+    testCounter.setState(0, 0);
+    testCounter.updateValue(6.0, 2000);
+
+    testCounter.setState(1, 3000);
+
+    testCounter.incrementValue(8.0, 6000);
+
+    // The total accumulated count is:
+    //  6.0             // For the period 0-2000
+    //  +(8.0 * 0.25)   // For the period 3000-4000
+    EXPECT_DOUBLE_EQ(8.0, testCounter.getCount(0));
+
+    // 0                // For the period 0-3000
+    // +(8.0 * 0.75)    // For the period 3000-4000
+    EXPECT_DOUBLE_EQ(6.0, testCounter.getCount(1));
+}
+
+TEST_F(MultiStateCounterTest, addValue) {
+    DoubleMultiStateCounter testCounter(1, 0);
+    testCounter.updateValue(0, 0);
+    testCounter.setState(0, 0);
+    testCounter.updateValue(6.0, 2000);
+
+    testCounter.addValue(8.0);
+
+    EXPECT_DOUBLE_EQ(14.0, testCounter.getCount(0));
+
+    testCounter.setEnabled(false, 3000);
+    testCounter.addValue(888.0);
+
+    EXPECT_DOUBLE_EQ(14.0, testCounter.getCount(0));
+}
+
+TEST_F(MultiStateCounterTest, toString) {
+    DoubleMultiStateCounter testCounter(2, 0);
+
+    EXPECT_STREQ("[0: 0.000000, 1: 0.000000] currentState: none", testCounter.toString().c_str());
+
+    testCounter.updateValue(0, 0);
+    testCounter.setState(1, 0);
+    testCounter.setState(1, 2000);
+    EXPECT_STREQ("[0: 0.000000, 1: 0.000000 timeInStateSinceUpdate: 2000]"
+                 " updated: 0 currentState: 1 stateChanged: 2000",
+                 testCounter.toString().c_str());
+
+    testCounter.updateValue(3.14, 3000);
+
+    EXPECT_STREQ("[0: 0.000000, 1: 3.140000] updated: 3000 currentState: 1",
+                 testCounter.toString().c_str());
+}
+
+} // namespace battery
+} // namespace android
diff --git a/libs/binder/include/binder/IInterface.h b/libs/binder/include/binder/IInterface.h
index f295417..7067830 100644
--- a/libs/binder/include/binder/IInterface.h
+++ b/libs/binder/include/binder/IInterface.h
@@ -228,10 +228,8 @@
   "android.gfx.tests.IIPCTest",
   "android.gfx.tests.ISafeInterfaceTest",
   "android.graphicsenv.IGpuService",
-  "android.gui.DisplayEventConnection",
   "android.gui.IConsumerListener",
   "android.gui.IGraphicBufferConsumer",
-  "android.gui.IRegionSamplingListener",
   "android.gui.ITransactionComposerListener",
   "android.gui.SensorEventConnection",
   "android.gui.SensorServer",
diff --git a/libs/binder/ndk/include_ndk/android/binder_ibinder.h b/libs/binder/ndk/include_ndk/android/binder_ibinder.h
index 565542b..4163897 100644
--- a/libs/binder/ndk/include_ndk/android/binder_ibinder.h
+++ b/libs/binder/ndk/include_ndk/android/binder_ibinder.h
@@ -289,7 +289,7 @@
 /**
  * Built-in transaction for all binder objects. This sends a transaction that will immediately
  * return. Usually this is used to make sure that a binder is alive, as a placeholder call, or as a
- * sanity check.
+ * consistency check.
  *
  * Available since API level 29.
  *
diff --git a/libs/binderthreadstate/Android.bp b/libs/binderthreadstate/Android.bp
index 0a82463..4860613 100644
--- a/libs/binderthreadstate/Android.bp
+++ b/libs/binderthreadstate/Android.bp
@@ -31,7 +31,7 @@
     target: {
         darwin: {
             enabled: false,
-        }
+        },
     },
 
     shared_libs: [
@@ -46,6 +46,11 @@
         "-Werror",
     ],
     min_sdk_version: "29",
+    // static link, so it won't straddle a module boundary at runtime.
+    apex_available: [
+        "//apex_available:platform",
+        "com.android.media.swcodec",
+    ],
 }
 
 hidl_package_root {
diff --git a/libs/ftl/Android.bp b/libs/ftl/Android.bp
index 2524c5f..bc2eb23 100644
--- a/libs/ftl/Android.bp
+++ b/libs/ftl/Android.bp
@@ -15,11 +15,14 @@
     },
     srcs: [
         "Flags_test.cpp",
+        "cast_test.cpp",
+        "concat_test.cpp",
+        "enum_test.cpp",
         "future_test.cpp",
-        "NamedEnum_test.cpp",
         "small_map_test.cpp",
         "small_vector_test.cpp",
         "static_vector_test.cpp",
+        "string_test.cpp",
     ],
     cflags: [
         "-Wall",
diff --git a/libs/ftl/Flags_test.cpp b/libs/ftl/Flags_test.cpp
index 8c00b52..d241fa2 100644
--- a/libs/ftl/Flags_test.cpp
+++ b/libs/ftl/Flags_test.cpp
@@ -23,7 +23,7 @@
 
 using namespace android::flag_operators;
 
-enum class TestFlags { ONE = 0x1, TWO = 0x2, THREE = 0x4 };
+enum class TestFlags : uint8_t { ONE = 0x1, TWO = 0x2, THREE = 0x4 };
 
 TEST(Flags, Test) {
     Flags<TestFlags> flags = TestFlags::ONE;
@@ -165,7 +165,7 @@
 
 TEST(Flags, String_UnknownValues) {
     auto flags = Flags<TestFlags>(0b1011);
-    ASSERT_EQ(flags.string(), "ONE | TWO | 0x00000008");
+    ASSERT_EQ(flags.string(), "ONE | TWO | 0b1000");
 }
 
 TEST(FlagsIterator, IteratesOverAllFlags) {
@@ -210,18 +210,4 @@
     ASSERT_EQ(++iter, flags.end());
 }
 
-TEST(FlagNames, RuntimeFlagName) {
-    TestFlags f = TestFlags::ONE;
-    ASSERT_EQ(flag_name(f), "ONE");
-}
-
-TEST(FlagNames, RuntimeUnknownFlagName) {
-    TestFlags f = static_cast<TestFlags>(0x8);
-    ASSERT_EQ(flag_name(f), std::nullopt);
-}
-
-TEST(FlagNames, CompileTimeFlagName) {
-    static_assert(flag_name<TestFlags::TWO>() == "TWO");
-}
-
-} // namespace android::test
\ No newline at end of file
+} // namespace android::test
diff --git a/libs/ftl/NamedEnum_test.cpp b/libs/ftl/NamedEnum_test.cpp
deleted file mode 100644
index dff2b8a..0000000
--- a/libs/ftl/NamedEnum_test.cpp
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * 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.
- */
-
-#include <gtest/gtest.h>
-#include <ftl/NamedEnum.h>
-
-namespace android {
-
-// Test enum class maximum enum value smaller than default maximum of 8.
-enum class TestEnums { ZERO = 0x0, ONE = 0x1, TWO = 0x2, THREE = 0x3, SEVEN = 0x7 };
-// Big enum contains enum values greater than default maximum of 8.
-enum class TestBigEnums { ZERO = 0x0, FIFTEEN = 0xF };
-
-// Declared to specialize the maximum enum since the enum size exceeds 8 by default.
-template <>
-constexpr size_t NamedEnum::max<TestBigEnums> = 16;
-
-namespace test {
-using android::TestBigEnums;
-using android::TestEnums;
-
-TEST(NamedEnum, RuntimeNamedEnum) {
-    TestEnums e = TestEnums::ZERO;
-    ASSERT_EQ(NamedEnum::enum_name(e), "ZERO");
-
-    e = TestEnums::ONE;
-    ASSERT_EQ(NamedEnum::enum_name(e), "ONE");
-
-    e = TestEnums::THREE;
-    ASSERT_EQ(NamedEnum::enum_name(e), "THREE");
-
-    e = TestEnums::SEVEN;
-    ASSERT_EQ(NamedEnum::enum_name(e), "SEVEN");
-}
-
-// Test big enum
-TEST(NamedEnum, RuntimeBigNamedEnum) {
-    TestBigEnums e = TestBigEnums::ZERO;
-    ASSERT_EQ(NamedEnum::enum_name(e), "ZERO");
-
-    e = TestBigEnums::FIFTEEN;
-    ASSERT_EQ(NamedEnum::enum_name(e), "FIFTEEN");
-}
-
-TEST(NamedEnum, RuntimeNamedEnumAsString) {
-    TestEnums e = TestEnums::ZERO;
-    ASSERT_EQ(NamedEnum::string(e), "ZERO");
-
-    e = TestEnums::ONE;
-    ASSERT_EQ(NamedEnum::string(e), "ONE");
-
-    e = TestEnums::THREE;
-    ASSERT_EQ(NamedEnum::string(e), "THREE");
-
-    e = TestEnums::SEVEN;
-    ASSERT_EQ(NamedEnum::string(e), "SEVEN");
-}
-
-TEST(NamedEnum, RuntimeBigNamedEnumAsString) {
-    TestBigEnums e = TestBigEnums::ZERO;
-    ASSERT_EQ(NamedEnum::string(e), "ZERO");
-
-    e = TestBigEnums::FIFTEEN;
-    ASSERT_EQ(NamedEnum::string(e), "FIFTEEN");
-}
-
-TEST(NamedEnum, RuntimeUnknownNamedEnum) {
-    TestEnums e = static_cast<TestEnums>(0x5);
-    ASSERT_EQ(NamedEnum::enum_name(e), std::nullopt);
-    e = static_cast<TestEnums>(0x9);
-    ASSERT_EQ(NamedEnum::enum_name(e), std::nullopt);
-}
-
-TEST(NamedEnum, RuntimeUnknownNamedEnumAsString) {
-    TestEnums e = static_cast<TestEnums>(0x5);
-    ASSERT_EQ(NamedEnum::string(e), "05");
-    e = static_cast<TestEnums>(0x9);
-    ASSERT_EQ(NamedEnum::string(e, "0x%08x"), "0x00000009");
-}
-
-TEST(NamedEnum, CompileTimeFlagName) {
-    static_assert(NamedEnum::enum_name<TestEnums::TWO>() == "TWO");
-    static_assert(NamedEnum::enum_name<TestEnums::THREE>() == "THREE");
-}
-
-} // namespace test
-
-} // namespace android
diff --git a/libs/ftl/cast_test.cpp b/libs/ftl/cast_test.cpp
new file mode 100644
index 0000000..2abcb8f
--- /dev/null
+++ b/libs/ftl/cast_test.cpp
@@ -0,0 +1,200 @@
+/*
+ * 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.
+ */
+
+#include <ftl/cast.h>
+#include <gtest/gtest.h>
+
+#include <cfloat>
+#include <cmath>
+#include <limits>
+
+namespace android::test {
+
+using ftl::cast_safety;
+using ftl::CastSafety;
+
+template <typename T>
+constexpr T min = std::numeric_limits<T>::lowest();
+
+template <typename T>
+constexpr T max = std::numeric_limits<T>::max();
+
+template <typename T>
+constexpr T inf = std::numeric_limits<T>::infinity();
+
+template <typename T>
+constexpr T NaN = std::numeric_limits<T>::quiet_NaN();
+
+// Keep in sync with example usage in header file.
+
+static_assert(cast_safety<uint8_t>(-1) == CastSafety::kUnderflow);
+static_assert(cast_safety<int8_t>(128u) == CastSafety::kOverflow);
+
+static_assert(cast_safety<uint32_t>(-.1f) == CastSafety::kUnderflow);
+static_assert(cast_safety<int32_t>(static_cast<float>(INT32_MAX)) == CastSafety::kOverflow);
+
+static_assert(cast_safety<float>(-DBL_MAX) == CastSafety::kUnderflow);
+
+// Unsigned to unsigned.
+
+static_assert(cast_safety<uint8_t>(0u) == CastSafety::kSafe);
+static_assert(cast_safety<uint16_t>(max<uint8_t>) == CastSafety::kSafe);
+static_assert(cast_safety<uint8_t>(static_cast<uint32_t>(max<uint8_t>)) == CastSafety::kSafe);
+
+static_assert(cast_safety<uint32_t>(max<uint64_t>) == CastSafety::kOverflow);
+static_assert(cast_safety<uint8_t>(static_cast<uint32_t>(max<uint8_t>) + 1) ==
+              CastSafety::kOverflow);
+
+// Unsigned to signed.
+
+static_assert(cast_safety<int16_t>(0u) == CastSafety::kSafe);
+static_assert(cast_safety<int16_t>(max<uint8_t>) == CastSafety::kSafe);
+static_assert(cast_safety<int16_t>(max<uint16_t>) == CastSafety::kOverflow);
+
+static_assert(cast_safety<int64_t>(static_cast<uint64_t>(max<int64_t>) - 1) == CastSafety::kSafe);
+static_assert(cast_safety<int64_t>(static_cast<uint64_t>(max<int64_t>)) == CastSafety::kSafe);
+static_assert(cast_safety<int64_t>(static_cast<uint64_t>(max<int64_t>) + 1) ==
+              CastSafety::kOverflow);
+
+// Signed to unsigned.
+
+static_assert(cast_safety<uint16_t>(0) == CastSafety::kSafe);
+static_assert(cast_safety<uint16_t>(max<int8_t>) == CastSafety::kSafe);
+static_assert(cast_safety<uint16_t>(max<int16_t>) == CastSafety::kSafe);
+
+static_assert(cast_safety<uint32_t>(-1) == CastSafety::kUnderflow);
+static_assert(cast_safety<uint32_t>(max<int64_t>) == CastSafety::kOverflow);
+
+static_assert(cast_safety<uint32_t>(static_cast<int64_t>(max<uint32_t>) - 1) == CastSafety::kSafe);
+static_assert(cast_safety<uint32_t>(static_cast<int64_t>(max<uint32_t>)) == CastSafety::kSafe);
+static_assert(cast_safety<uint32_t>(static_cast<int64_t>(max<uint32_t>) + 1) ==
+              CastSafety::kOverflow);
+
+// Signed to signed.
+
+static_assert(cast_safety<int8_t>(-129) == CastSafety::kUnderflow);
+static_assert(cast_safety<int8_t>(-128) == CastSafety::kSafe);
+static_assert(cast_safety<int8_t>(127) == CastSafety::kSafe);
+static_assert(cast_safety<int8_t>(128) == CastSafety::kOverflow);
+
+static_assert(cast_safety<int32_t>(static_cast<int64_t>(min<int32_t>)) == CastSafety::kSafe);
+static_assert(cast_safety<int32_t>(static_cast<int64_t>(max<int32_t>)) == CastSafety::kSafe);
+
+static_assert(cast_safety<int16_t>(min<int32_t>) == CastSafety::kUnderflow);
+static_assert(cast_safety<int32_t>(max<int64_t>) == CastSafety::kOverflow);
+
+// Float to float.
+
+static_assert(cast_safety<double>(max<float>) == CastSafety::kSafe);
+static_assert(cast_safety<double>(min<float>) == CastSafety::kSafe);
+
+static_assert(cast_safety<float>(min<double>) == CastSafety::kUnderflow);
+static_assert(cast_safety<float>(max<double>) == CastSafety::kOverflow);
+
+TEST(CastSafety, FloatToFloat) {
+  EXPECT_EQ(cast_safety<float>(std::nexttoward(static_cast<double>(min<float>), min<double>)),
+            CastSafety::kUnderflow);
+  EXPECT_EQ(cast_safety<float>(std::nexttoward(static_cast<double>(max<float>), max<double>)),
+            CastSafety::kOverflow);
+}
+
+// Unsigned to float.
+
+static_assert(cast_safety<float>(0u) == CastSafety::kSafe);
+static_assert(cast_safety<float>(max<uint64_t>) == CastSafety::kSafe);
+
+static_assert(cast_safety<double>(0u) == CastSafety::kSafe);
+static_assert(cast_safety<double>(max<uint64_t>) == CastSafety::kSafe);
+
+// Signed to float.
+
+static_assert(cast_safety<float>(min<int64_t>) == CastSafety::kSafe);
+static_assert(cast_safety<float>(max<int64_t>) == CastSafety::kSafe);
+
+static_assert(cast_safety<double>(min<int64_t>) == CastSafety::kSafe);
+static_assert(cast_safety<double>(max<int64_t>) == CastSafety::kSafe);
+
+// Float to unsigned.
+
+static_assert(cast_safety<uint32_t>(0.f) == CastSafety::kSafe);
+static_assert(cast_safety<uint32_t>(min<float>) == CastSafety::kUnderflow);
+static_assert(cast_safety<uint32_t>(max<float>) == CastSafety::kOverflow);
+static_assert(cast_safety<uint32_t>(-.1f) == CastSafety::kUnderflow);
+
+static_assert(cast_safety<uint16_t>(-inf<float>) == CastSafety::kUnderflow);
+static_assert(cast_safety<uint32_t>(inf<float>) == CastSafety::kOverflow);
+static_assert(cast_safety<uint64_t>(NaN<float>) == CastSafety::kOverflow);
+
+static_assert(cast_safety<uint32_t>(static_cast<float>(max<int32_t>)) == CastSafety::kSafe);
+static_assert(cast_safety<uint32_t>(static_cast<float>(max<uint32_t>)) == CastSafety::kOverflow);
+static_assert(cast_safety<uint32_t>(static_cast<double>(max<int32_t>)) == CastSafety::kSafe);
+static_assert(cast_safety<uint32_t>(static_cast<double>(max<uint32_t>)) == CastSafety::kSafe);
+
+static_assert(cast_safety<uint64_t>(0.0) == CastSafety::kSafe);
+static_assert(cast_safety<uint64_t>(min<double>) == CastSafety::kUnderflow);
+static_assert(cast_safety<uint64_t>(max<double>) == CastSafety::kOverflow);
+static_assert(cast_safety<uint64_t>(-.1) == CastSafety::kUnderflow);
+
+static_assert(cast_safety<uint64_t>(static_cast<float>(max<int64_t>)) == CastSafety::kSafe);
+static_assert(cast_safety<uint64_t>(static_cast<float>(max<uint64_t>)) == CastSafety::kOverflow);
+static_assert(cast_safety<uint64_t>(static_cast<double>(max<int64_t>)) == CastSafety::kSafe);
+static_assert(cast_safety<uint64_t>(static_cast<double>(max<uint64_t>)) == CastSafety::kOverflow);
+
+// Float to signed.
+
+static_assert(cast_safety<int32_t>(0.f) == CastSafety::kSafe);
+static_assert(cast_safety<int32_t>(min<float>) == CastSafety::kUnderflow);
+static_assert(cast_safety<int32_t>(max<float>) == CastSafety::kOverflow);
+
+static_assert(cast_safety<int16_t>(-inf<double>) == CastSafety::kUnderflow);
+static_assert(cast_safety<int32_t>(inf<double>) == CastSafety::kOverflow);
+static_assert(cast_safety<int64_t>(NaN<double>) == CastSafety::kOverflow);
+
+static_assert(cast_safety<int32_t>(static_cast<float>(min<int32_t>)) == CastSafety::kSafe);
+static_assert(cast_safety<int32_t>(static_cast<float>(max<int32_t>)) == CastSafety::kOverflow);
+static_assert(cast_safety<int32_t>(static_cast<double>(min<int32_t>)) == CastSafety::kSafe);
+static_assert(cast_safety<int32_t>(static_cast<double>(max<int32_t>)) == CastSafety::kSafe);
+
+static_assert(cast_safety<int64_t>(0.0) == CastSafety::kSafe);
+static_assert(cast_safety<int64_t>(min<double>) == CastSafety::kUnderflow);
+static_assert(cast_safety<int64_t>(max<double>) == CastSafety::kOverflow);
+
+static_assert(cast_safety<int64_t>(static_cast<float>(min<int64_t>)) == CastSafety::kSafe);
+static_assert(cast_safety<int64_t>(static_cast<float>(max<int64_t>)) == CastSafety::kOverflow);
+static_assert(cast_safety<int64_t>(static_cast<double>(min<int64_t>)) == CastSafety::kSafe);
+static_assert(cast_safety<int64_t>(static_cast<double>(max<int64_t>)) == CastSafety::kOverflow);
+
+TEST(CastSafety, FloatToSigned) {
+  constexpr int32_t kMax = ftl::details::safe_limits<int32_t, float>::max();
+  static_assert(kMax == 2'147'483'520);
+  EXPECT_EQ(kMax, static_cast<int32_t>(std::nexttowardf(max<int32_t>, 0)));
+
+  EXPECT_EQ(cast_safety<int32_t>(std::nexttowardf(min<int32_t>, 0)), CastSafety::kSafe);
+  EXPECT_EQ(cast_safety<int32_t>(std::nexttowardf(max<int32_t>, 0)), CastSafety::kSafe);
+  EXPECT_EQ(cast_safety<int64_t>(std::nexttoward(min<int64_t>, 0)), CastSafety::kSafe);
+  EXPECT_EQ(cast_safety<int64_t>(std::nexttoward(max<int64_t>, 0)), CastSafety::kSafe);
+
+  EXPECT_EQ(cast_safety<int32_t>(std::nexttowardf(min<int32_t>, min<float>)),
+            CastSafety::kUnderflow);
+  EXPECT_EQ(cast_safety<int32_t>(std::nexttowardf(max<int32_t>, max<float>)),
+            CastSafety::kOverflow);
+  EXPECT_EQ(cast_safety<int64_t>(std::nexttoward(min<int64_t>, min<double>)),
+            CastSafety::kUnderflow);
+  EXPECT_EQ(cast_safety<int64_t>(std::nexttoward(max<int64_t>, max<double>)),
+            CastSafety::kOverflow);
+}
+
+}  // namespace android::test
diff --git a/libs/ftl/concat_test.cpp b/libs/ftl/concat_test.cpp
new file mode 100644
index 0000000..8ecb1b2
--- /dev/null
+++ b/libs/ftl/concat_test.cpp
@@ -0,0 +1,55 @@
+/*
+ * 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.
+ */
+
+#include <ftl/concat.h>
+#include <gtest/gtest.h>
+
+namespace android::test {
+
+// Keep in sync with example usage in header file.
+TEST(Concat, Example) {
+  std::string_view name = "Volume";
+  ftl::Concat string(ftl::truncated<3>(name), ": ", -3, " dB");
+
+  EXPECT_EQ(string.str(), "Vol: -3 dB");
+  EXPECT_EQ(string.c_str()[string.size()], '\0');
+}
+
+namespace {
+
+static_assert(ftl::Concat{"foo"}.str() == "foo");
+static_assert(ftl::Concat{ftl::truncated<3>("foobar")}.str() == "foo");
+
+constexpr ftl::Concat kConcat{"po", "trz", "ebie"};
+
+static_assert(kConcat.size() == 9);
+static_assert(kConcat.max_size() == 9);
+static_assert(kConcat.str() == "potrzebie");
+static_assert(kConcat.str() == std::string_view(kConcat.c_str()));
+
+constexpr auto concat() {
+  return ftl::Concat{ftl::truncated<1>("v???"), ftl::truncated<2>("ee??"),
+                     ftl::truncated<3>("ble?"), ftl::truncated<4>("fetz"),
+                     ftl::truncated<90>("er")};
+}
+
+static_assert(concat().size() == 12);
+static_assert(concat().max_size() == 100);
+static_assert(concat().str() == "veeblefetzer");
+static_assert(concat().str() == std::string_view(concat().c_str()));
+
+}  // namespace
+}  // namespace android::test
diff --git a/libs/ftl/enum_test.cpp b/libs/ftl/enum_test.cpp
new file mode 100644
index 0000000..d8ce7a5
--- /dev/null
+++ b/libs/ftl/enum_test.cpp
@@ -0,0 +1,164 @@
+/*
+ * 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.
+ */
+
+#include <ftl/enum.h>
+#include <gtest/gtest.h>
+
+namespace android::test {
+
+// Keep in sync with example usage in header file.
+namespace {
+
+enum class E { A, B, C, F = 5, ftl_last = F };
+
+static_assert(ftl::enum_begin_v<E> == E::A);
+static_assert(ftl::enum_last_v<E> == E::F);
+static_assert(ftl::enum_size_v<E> == 6);
+
+static_assert(ftl::enum_name<E::B>() == "B");
+static_assert(ftl::enum_name<E::ftl_last>() == "F");
+static_assert(ftl::enum_name(E::C).value_or("?") == "C");
+static_assert(ftl::enum_name(E{3}).value_or("?") == "?");
+
+enum class F : std::uint16_t { X = 0b1, Y = 0b10, Z = 0b100 };
+
+static_assert(ftl::enum_begin_v<F> == F{0});
+static_assert(ftl::enum_last_v<F> == F{15});
+static_assert(ftl::enum_size_v<F> == 16);
+
+static_assert(ftl::flag_name(F::Z).value_or("?") == "Z");
+static_assert(ftl::flag_name(F{0b111}).value_or("?") == "?");
+
+// If a scoped enum is unsigned, its implicit range corresponds to its bit indices.
+enum class Flags : std::uint8_t {
+  kNone = 0,
+  kFlag1 = 0b0000'0010,
+  kFlag4 = 0b0001'0000,
+  kFlag7 = 0b1000'0000,
+  kMask = kFlag1 | kFlag4 | kFlag7,
+  kAll = 0b1111'1111
+};
+
+static_assert(ftl::enum_begin_v<Flags> == Flags{0});
+static_assert(ftl::enum_last_v<Flags> == Flags{7});
+static_assert(ftl::enum_size_v<Flags> == 8);
+
+static_assert(ftl::enum_name<Flags::kNone>() == "kNone");
+static_assert(ftl::enum_name<Flags::kFlag4>() == "kFlag4");
+static_assert(ftl::enum_name<Flags::kFlag7>() == "kFlag7");
+
+// Though not flags, the enumerators are within the implicit range of bit indices.
+enum class Planet : std::uint8_t {
+  kMercury,
+  kVenus,
+  kEarth,
+  kMars,
+  kJupiter,
+  kSaturn,
+  kUranus,
+  kNeptune
+};
+
+constexpr Planet kPluto{ftl::to_underlying(Planet::kNeptune) + 1};  // Honorable mention.
+
+static_assert(ftl::enum_begin_v<Planet> == Planet::kMercury);
+static_assert(ftl::enum_last_v<Planet> == Planet::kNeptune);
+static_assert(ftl::enum_size_v<Planet> == 8);
+
+static_assert(ftl::enum_name<Planet::kMercury>() == "kMercury");
+static_assert(ftl::enum_name<Planet::kSaturn>() == "kSaturn");
+
+// Unscoped enum must define explicit range, even if the underlying type is fixed.
+enum Temperature : int {
+  kRoom = 20,
+  kFridge = 4,
+  kFreezer = -18,
+
+  ftl_first = kFreezer,
+  ftl_last = kRoom
+};
+
+static_assert(ftl::enum_begin_v<Temperature> == kFreezer);
+static_assert(ftl::enum_last_v<Temperature> == kRoom);
+static_assert(ftl::enum_size_v<Temperature> == 39);
+
+static_assert(ftl::enum_name<kFreezer>() == "kFreezer");
+static_assert(ftl::enum_name<kFridge>() == "kFridge");
+static_assert(ftl::enum_name<kRoom>() == "kRoom");
+
+}  // namespace
+
+TEST(Enum, Range) {
+  std::string string;
+  for (E v : ftl::enum_range<E>()) {
+    string += ftl::enum_name(v).value_or("?");
+  }
+  EXPECT_EQ(string, "ABC??F");
+}
+
+TEST(Enum, Name) {
+  {
+    EXPECT_EQ(ftl::flag_name(Flags::kFlag1), "kFlag1");
+    EXPECT_EQ(ftl::flag_name(Flags::kFlag7), "kFlag7");
+
+    EXPECT_EQ(ftl::flag_name(Flags::kNone), std::nullopt);
+    EXPECT_EQ(ftl::flag_name(Flags::kMask), std::nullopt);
+    EXPECT_EQ(ftl::flag_name(Flags::kAll), std::nullopt);
+  }
+  {
+    EXPECT_EQ(ftl::enum_name(Planet::kEarth), "kEarth");
+    EXPECT_EQ(ftl::enum_name(Planet::kNeptune), "kNeptune");
+
+    EXPECT_EQ(ftl::enum_name(kPluto), std::nullopt);
+  }
+  {
+    EXPECT_EQ(ftl::enum_name(kRoom), "kRoom");
+    EXPECT_EQ(ftl::enum_name(kFridge), "kFridge");
+    EXPECT_EQ(ftl::enum_name(kFreezer), "kFreezer");
+
+    EXPECT_EQ(ftl::enum_name(static_cast<Temperature>(-30)), std::nullopt);
+    EXPECT_EQ(ftl::enum_name(static_cast<Temperature>(0)), std::nullopt);
+    EXPECT_EQ(ftl::enum_name(static_cast<Temperature>(100)), std::nullopt);
+  }
+}
+
+TEST(Enum, String) {
+  {
+    EXPECT_EQ(ftl::flag_string(Flags::kFlag1), "kFlag1");
+    EXPECT_EQ(ftl::flag_string(Flags::kFlag7), "kFlag7");
+
+    EXPECT_EQ(ftl::flag_string(Flags::kNone), "0b0");
+    EXPECT_EQ(ftl::flag_string(Flags::kMask), "0b10010010");
+    EXPECT_EQ(ftl::flag_string(Flags::kAll), "0b11111111");
+  }
+  {
+    EXPECT_EQ(ftl::enum_string(Planet::kEarth), "kEarth");
+    EXPECT_EQ(ftl::enum_string(Planet::kNeptune), "kNeptune");
+
+    EXPECT_EQ(ftl::enum_string(kPluto), "8");
+  }
+  {
+    EXPECT_EQ(ftl::enum_string(kRoom), "kRoom");
+    EXPECT_EQ(ftl::enum_string(kFridge), "kFridge");
+    EXPECT_EQ(ftl::enum_string(kFreezer), "kFreezer");
+
+    EXPECT_EQ(ftl::enum_string(static_cast<Temperature>(-30)), "-30");
+    EXPECT_EQ(ftl::enum_string(static_cast<Temperature>(0)), "0");
+    EXPECT_EQ(ftl::enum_string(static_cast<Temperature>(100)), "100");
+  }
+}
+
+}  // namespace android::test
diff --git a/libs/ftl/small_map_test.cpp b/libs/ftl/small_map_test.cpp
index 323b9f9..1740a2b 100644
--- a/libs/ftl/small_map_test.cpp
+++ b/libs/ftl/small_map_test.cpp
@@ -18,6 +18,9 @@
 #include <gtest/gtest.h>
 
 #include <cctype>
+#include <string>
+
+using namespace std::string_literals;
 
 namespace android::test {
 
@@ -35,16 +38,19 @@
 
   EXPECT_TRUE(map.contains(123));
 
-  EXPECT_EQ(map.find(42, [](const std::string& s) { return s.size(); }), 3u);
+  EXPECT_EQ(map.get(42, [](const std::string& s) { return s.size(); }), 3u);
 
-  const auto opt = map.find(-1);
+  const auto opt = map.get(-1);
   ASSERT_TRUE(opt);
 
   std::string& ref = *opt;
   EXPECT_TRUE(ref.empty());
   ref = "xyz";
 
-  EXPECT_EQ(map, SmallMap(ftl::init::map(-1, "xyz")(42, "???")(123, "abc")));
+  map.emplace_or_replace(0, "vanilla", 2u, 3u);
+  EXPECT_TRUE(map.dynamic());
+
+  EXPECT_EQ(map, SmallMap(ftl::init::map(-1, "xyz")(0, "nil")(42, "???")(123, "abc")));
 }
 
 TEST(SmallMap, Construct) {
@@ -90,42 +96,317 @@
   }
 }
 
+TEST(SmallMap, Assign) {
+  {
+    // Same types; smaller capacity.
+    SmallMap map1 = ftl::init::map<char, std::string>('k', "kilo")('M', "mega")('G', "giga");
+    const SmallMap map2 = ftl::init::map('T', "tera"s)('P', "peta"s);
+
+    map1 = map2;
+    EXPECT_EQ(map1, map2);
+  }
+  {
+    // Convertible types; same capacity.
+    SmallMap map1 = ftl::init::map<char, std::string>('M', "mega")('G', "giga");
+    const SmallMap map2 = ftl::init::map('T', "tera")('P', "peta");
+
+    map1 = map2;
+    EXPECT_EQ(map1, map2);
+  }
+  {
+    // Convertible types; zero capacity.
+    SmallMap<char, std::string, 0> map1 = ftl::init::map('M', "mega")('G', "giga");
+    const SmallMap<char, std::string, 0> map2 = ftl::init::map('T', "tera")('P', "peta");
+
+    map1 = map2;
+    EXPECT_EQ(map1, map2);
+  }
+}
+
+TEST(SmallMap, UniqueKeys) {
+  {
+    // Duplicate mappings are discarded.
+    const SmallMap map = ftl::init::map<int, float>(1)(2)(3)(2)(3)(1)(3)(2)(1);
+
+    EXPECT_EQ(map.size(), 3u);
+    EXPECT_EQ(map.max_size(), 9u);
+
+    using Map = decltype(map);
+    EXPECT_EQ(map, Map(ftl::init::map(1, 0.f)(2, 0.f)(3, 0.f)));
+  }
+  {
+    // Duplicate mappings may be reordered.
+    const SmallMap map = ftl::init::map('a', 'A')(
+        'b', 'B')('b')('b')('c', 'C')('a')('d')('c')('e', 'E')('d', 'D')('a')('f', 'F');
+
+    EXPECT_EQ(map.size(), 6u);
+    EXPECT_EQ(map.max_size(), 12u);
+
+    using Map = decltype(map);
+    EXPECT_EQ(map, Map(ftl::init::map('a', 'A')('b', 'B')('c', 'C')('d', 'D')('e', 'E')('f', 'F')));
+  }
+}
+
 TEST(SmallMap, Find) {
   {
     // Constant reference.
-    const ftl::SmallMap map = ftl::init::map('a', 'A')('b', 'B')('c', 'C');
+    const SmallMap map = ftl::init::map('a', 'A')('b', 'B')('c', 'C');
 
-    const auto opt = map.find('b');
+    const auto opt = map.get('b');
     EXPECT_EQ(opt, 'B');
 
     const char d = 'D';
-    const auto ref = map.find('d').value_or(std::cref(d));
+    const auto ref = map.get('d').value_or(std::cref(d));
     EXPECT_EQ(ref.get(), 'D');
   }
   {
     // Mutable reference.
-    ftl::SmallMap map = ftl::init::map('a', 'A')('b', 'B')('c', 'C');
+    SmallMap map = ftl::init::map('a', 'A')('b', 'B')('c', 'C');
 
-    const auto opt = map.find('c');
+    const auto opt = map.get('c');
     EXPECT_EQ(opt, 'C');
 
     char d = 'd';
-    const auto ref = map.find('d').value_or(std::ref(d));
+    const auto ref = map.get('d').value_or(std::ref(d));
     ref.get() = 'D';
     EXPECT_EQ(d, 'D');
   }
   {
     // Constant unary operation.
-    const ftl::SmallMap map = ftl::init::map('a', 'x')('b', 'y')('c', 'z');
-    EXPECT_EQ(map.find('c', [](char c) { return std::toupper(c); }), 'Z');
+    const SmallMap map = ftl::init::map('a', 'x')('b', 'y')('c', 'z');
+    EXPECT_EQ(map.get('c', [](char c) { return std::toupper(c); }), 'Z');
   }
   {
     // Mutable unary operation.
-    ftl::SmallMap map = ftl::init::map('a', 'x')('b', 'y')('c', 'z');
-    EXPECT_TRUE(map.find('c', [](char& c) { c = std::toupper(c); }));
+    SmallMap map = ftl::init::map('a', 'x')('b', 'y')('c', 'z');
+    EXPECT_TRUE(map.get('c', [](char& c) { c = std::toupper(c); }));
 
     EXPECT_EQ(map, SmallMap(ftl::init::map('c', 'Z')('b', 'y')('a', 'x')));
   }
 }
 
+TEST(SmallMap, TryEmplace) {
+  SmallMap<int, std::string, 3> map;
+  using Pair = decltype(map)::value_type;
+
+  {
+    const auto [it, ok] = map.try_emplace(123, "abc");
+    ASSERT_TRUE(ok);
+    EXPECT_EQ(*it, Pair(123, "abc"s));
+  }
+  {
+    const auto [it, ok] = map.try_emplace(42, 3u, '?');
+    ASSERT_TRUE(ok);
+    EXPECT_EQ(*it, Pair(42, "???"s));
+  }
+  {
+    const auto [it, ok] = map.try_emplace(-1);
+    ASSERT_TRUE(ok);
+    EXPECT_EQ(*it, Pair(-1, std::string()));
+    EXPECT_FALSE(map.dynamic());
+  }
+  {
+    // Insertion fails if mapping exists.
+    const auto [it, ok] = map.try_emplace(42, "!!!");
+    EXPECT_FALSE(ok);
+    EXPECT_EQ(*it, Pair(42, "???"));
+    EXPECT_FALSE(map.dynamic());
+  }
+  {
+    // Insertion at capacity promotes the map.
+    const auto [it, ok] = map.try_emplace(999, "xyz");
+    ASSERT_TRUE(ok);
+    EXPECT_EQ(*it, Pair(999, "xyz"));
+    EXPECT_TRUE(map.dynamic());
+  }
+
+  EXPECT_EQ(map, SmallMap(ftl::init::map(-1, ""s)(42, "???"s)(123, "abc"s)(999, "xyz"s)));
+}
+
+namespace {
+
+// The mapped type does not require a copy/move assignment operator.
+struct String {
+  template <typename... Args>
+  String(Args... args) : str(args...) {}
+  const std::string str;
+
+  bool operator==(const String& other) const { return other.str == str; }
+};
+
+}  // namespace
+
+TEST(SmallMap, TryReplace) {
+  SmallMap<int, String, 3> map = ftl::init::map(1, "a")(2, "B");
+  using Pair = decltype(map)::value_type;
+
+  {
+    // Replacing fails unless mapping exists.
+    const auto it = map.try_replace(3, "c");
+    EXPECT_EQ(it, map.end());
+  }
+  {
+    // Replacement arguments can refer to the replaced mapping.
+    const auto ref = map.get(2, [](const auto& s) { return s.str[0]; });
+    ASSERT_TRUE(ref);
+
+    // Construct std::string from one character.
+    const auto it = map.try_replace(2, 1u, static_cast<char>(std::tolower(*ref)));
+    ASSERT_NE(it, map.end());
+    EXPECT_EQ(*it, Pair(2, "b"));
+  }
+
+  EXPECT_FALSE(map.dynamic());
+  EXPECT_TRUE(map.try_emplace(3, "abc").second);
+  EXPECT_TRUE(map.try_emplace(4, "d").second);
+  EXPECT_TRUE(map.dynamic());
+
+  {
+    // Replacing fails unless mapping exists.
+    const auto it = map.try_replace(5, "e");
+    EXPECT_EQ(it, map.end());
+  }
+  {
+    // Replacement arguments can refer to the replaced mapping.
+    const auto ref = map.get(3);
+    ASSERT_TRUE(ref);
+
+    // Construct std::string from substring.
+    const auto it = map.try_replace(3, ref->get().str, 2u, 1u);
+    ASSERT_NE(it, map.end());
+    EXPECT_EQ(*it, Pair(3, "c"));
+  }
+
+  EXPECT_EQ(map, SmallMap(ftl::init::map(4, "d"s)(3, "c"s)(2, "b"s)(1, "a"s)));
+}
+
+TEST(SmallMap, EmplaceOrReplace) {
+  SmallMap<int, String, 3> map = ftl::init::map(1, "a")(2, "B");
+  using Pair = decltype(map)::value_type;
+
+  {
+    // New mapping is emplaced.
+    const auto [it, emplace] = map.emplace_or_replace(3, "c");
+    EXPECT_TRUE(emplace);
+    EXPECT_EQ(*it, Pair(3, "c"));
+  }
+  {
+    // Replacement arguments can refer to the replaced mapping.
+    const auto ref = map.get(2, [](const auto& s) { return s.str[0]; });
+    ASSERT_TRUE(ref);
+
+    // Construct std::string from one character.
+    const auto [it, emplace] = map.emplace_or_replace(2, 1u, static_cast<char>(std::tolower(*ref)));
+    EXPECT_FALSE(emplace);
+    EXPECT_EQ(*it, Pair(2, "b"));
+  }
+
+  EXPECT_FALSE(map.dynamic());
+  EXPECT_FALSE(map.emplace_or_replace(3, "abc").second);  // Replace.
+  EXPECT_TRUE(map.emplace_or_replace(4, "d").second);     // Emplace.
+  EXPECT_TRUE(map.dynamic());
+
+  {
+    // New mapping is emplaced.
+    const auto [it, emplace] = map.emplace_or_replace(5, "e");
+    EXPECT_TRUE(emplace);
+    EXPECT_EQ(*it, Pair(5, "e"));
+  }
+  {
+    // Replacement arguments can refer to the replaced mapping.
+    const auto ref = map.get(3);
+    ASSERT_TRUE(ref);
+
+    // Construct std::string from substring.
+    const auto [it, emplace] = map.emplace_or_replace(3, ref->get().str, 2u, 1u);
+    EXPECT_FALSE(emplace);
+    EXPECT_EQ(*it, Pair(3, "c"));
+  }
+
+  EXPECT_EQ(map, SmallMap(ftl::init::map(5, "e"s)(4, "d"s)(3, "c"s)(2, "b"s)(1, "a"s)));
+}
+
+TEST(SmallMap, Erase) {
+  {
+    SmallMap map = ftl::init::map(1, '1')(2, '2')(3, '3')(4, '4');
+    EXPECT_FALSE(map.dynamic());
+
+    EXPECT_FALSE(map.erase(0));  // Key not found.
+
+    EXPECT_TRUE(map.erase(2));
+    EXPECT_EQ(map, SmallMap(ftl::init::map(1, '1')(3, '3')(4, '4')));
+
+    EXPECT_TRUE(map.erase(1));
+    EXPECT_EQ(map, SmallMap(ftl::init::map(3, '3')(4, '4')));
+
+    EXPECT_TRUE(map.erase(4));
+    EXPECT_EQ(map, SmallMap(ftl::init::map(3, '3')));
+
+    EXPECT_TRUE(map.erase(3));
+    EXPECT_FALSE(map.erase(3));  // Key not found.
+
+    EXPECT_TRUE(map.empty());
+    EXPECT_FALSE(map.dynamic());
+  }
+  {
+    SmallMap map = ftl::init::map(1, '1')(2, '2')(3, '3');
+    map.try_emplace(4, '4');
+    EXPECT_TRUE(map.dynamic());
+
+    EXPECT_FALSE(map.erase(0));  // Key not found.
+
+    EXPECT_TRUE(map.erase(2));
+    EXPECT_EQ(map, SmallMap(ftl::init::map(1, '1')(3, '3')(4, '4')));
+
+    EXPECT_TRUE(map.erase(1));
+    EXPECT_EQ(map, SmallMap(ftl::init::map(3, '3')(4, '4')));
+
+    EXPECT_TRUE(map.erase(4));
+    EXPECT_EQ(map, SmallMap(ftl::init::map(3, '3')));
+
+    EXPECT_TRUE(map.erase(3));
+    EXPECT_FALSE(map.erase(3));  // Key not found.
+
+    EXPECT_TRUE(map.empty());
+    EXPECT_TRUE(map.dynamic());
+  }
+}
+
+TEST(SmallMap, Clear) {
+  SmallMap map = ftl::init::map(1, '1')(2, '2')(3, '3');
+
+  map.clear();
+
+  EXPECT_TRUE(map.empty());
+  EXPECT_FALSE(map.dynamic());
+
+  map = ftl::init::map(1, '1')(2, '2')(3, '3');
+  map.try_emplace(4, '4');
+
+  map.clear();
+
+  EXPECT_TRUE(map.empty());
+  EXPECT_TRUE(map.dynamic());
+}
+
+TEST(SmallMap, KeyEqual) {
+  struct KeyEqual {
+    bool operator()(int lhs, int rhs) const { return lhs % 10 == rhs % 10; }
+  };
+
+  SmallMap<int, char, 1, KeyEqual> map;
+
+  EXPECT_TRUE(map.try_emplace(3, '3').second);
+  EXPECT_FALSE(map.try_emplace(13, '3').second);
+
+  EXPECT_TRUE(map.try_emplace(22, '2').second);
+  EXPECT_TRUE(map.contains(42));
+
+  EXPECT_TRUE(map.try_emplace(111, '1').second);
+  EXPECT_EQ(map.get(321), '1');
+
+  map.erase(123);
+  EXPECT_EQ(map, SmallMap(ftl::init::map<int, char, KeyEqual>(1, '1')(2, '2')));
+}
+
 }  // namespace android::test
diff --git a/libs/ftl/small_vector_test.cpp b/libs/ftl/small_vector_test.cpp
index 3a03e69..b662a81 100644
--- a/libs/ftl/small_vector_test.cpp
+++ b/libs/ftl/small_vector_test.cpp
@@ -138,6 +138,116 @@
   }
 }
 
+TEST(SmallVector, Copy) {
+  {
+    // Same capacity.
+    const SmallVector vector = {"snow"s, "cone"s};
+
+    SmallVector<const std::string, 2> copy(vector);
+    EXPECT_EQ(copy, vector);
+
+    // The vector is assignable even if T is const.
+    const SmallVector<std::string, 2> other = {"tiramisu"s};
+    copy = other;
+    EXPECT_EQ(copy, other);
+  }
+  {
+    // From smaller capacity.
+    const SmallVector vector = {"snow"s, "cone"s};
+
+    SmallVector<const std::string, 3> copy(vector);
+    EXPECT_EQ(copy, vector);
+
+    // The vector is assignable even if T is const.
+    const SmallVector other = {"tiramisu"s};
+    copy = other;
+    EXPECT_EQ(copy, other);
+  }
+  {
+    // To zero capacity.
+    const SmallVector vector = {"snow"s, "cone"s};
+
+    SmallVector<const std::string, 0> copy(vector);
+    EXPECT_EQ(copy, vector);
+
+    // The vector is assignable even if T is const.
+    const SmallVector other = {"tiramisu"s};
+    copy = other;
+    EXPECT_EQ(copy, other);
+  }
+  {
+    // From/to zero capacity.
+    const SmallVector<std::string, 0> vector = {"snow"s, "cone"s};
+
+    SmallVector<const std::string, 0> copy(vector);
+    EXPECT_EQ(copy, vector);
+
+    // The vector is assignable even if T is const.
+    const SmallVector<std::string, 0> other = {"tiramisu"s};
+    copy = other;
+    EXPECT_EQ(copy, other);
+  }
+}
+
+TEST(SmallVector, Move) {
+  {
+    // Same capacity.
+    SmallVector vector = {"snow"s, "cone"s};
+
+    SmallVector<const std::string, 2> move(std::move(vector));
+    EXPECT_TRUE(vector.empty());
+    EXPECT_EQ(move, (SmallVector{"snow"s, "cone"s}));
+
+    // The vector is assignable even if T is const.
+    SmallVector<std::string, 2> other = {"tiramisu"s};
+    move = std::move(other);
+    EXPECT_TRUE(other.empty());
+    EXPECT_EQ(move, (SmallVector{"tiramisu"s}));
+  }
+  {
+    // From smaller capacity.
+    SmallVector vector = {"snow"s, "cone"s};
+
+    SmallVector<const std::string, 3> move(std::move(vector));
+    EXPECT_TRUE(vector.empty());
+    EXPECT_EQ(move, (SmallVector{"snow"s, "cone"s}));
+
+    // The vector is assignable even if T is const.
+    SmallVector other = {"tiramisu"s};
+    move = std::move(other);
+    EXPECT_TRUE(other.empty());
+    EXPECT_EQ(move, (SmallVector{"tiramisu"s}));
+  }
+  {
+    // To zero capacity.
+    SmallVector vector = {"snow"s, "cone"s};
+
+    SmallVector<const std::string, 0> move(std::move(vector));
+    EXPECT_TRUE(vector.empty());
+    EXPECT_EQ(move, (SmallVector{"snow"s, "cone"s}));
+
+    // The vector is assignable even if T is const.
+    SmallVector other = {"tiramisu"s};
+    move = std::move(other);
+    EXPECT_TRUE(other.empty());
+    EXPECT_EQ(move, (SmallVector{"tiramisu"s}));
+  }
+  {
+    // From/to zero capacity.
+    SmallVector<std::string, 0> vector = {"snow"s, "cone"s};
+
+    SmallVector<const std::string, 0> move(std::move(vector));
+    EXPECT_TRUE(vector.empty());
+    EXPECT_EQ(move, (SmallVector{"snow"s, "cone"s}));
+
+    // The vector is assignable even if T is const.
+    SmallVector<std::string, 0> other = {"tiramisu"s};
+    move = std::move(other);
+    EXPECT_TRUE(other.empty());
+    EXPECT_EQ(move, (SmallVector{"tiramisu"s}));
+  }
+}
+
 TEST(SmallVector, String) {
   SmallVector<char, 10> chars;
   char c = 'a';
@@ -366,21 +476,20 @@
   bool alive = true;
 };
 
-void swap(DestroyCounts& lhs, DestroyCounts& rhs) {
-  std::swap(lhs.alive, rhs.alive);
-}
-
 }  // namespace
 
 TEST(SmallVector, Destroy) {
   int live = 0;
   int dead = 0;
-
-  { SmallVector<DestroyCounts, 3> counts; }
+  {
+    // Empty.
+    SmallVector<DestroyCounts, 3> counts;
+  }
   EXPECT_EQ(0, live);
   EXPECT_EQ(0, dead);
 
   {
+    // Static.
     SmallVector<DestroyCounts, 3> counts;
     counts.emplace_back(live, dead);
     counts.emplace_back(live, dead);
@@ -393,6 +502,7 @@
 
   live = 0;
   {
+    // Dynamic.
     SmallVector<DestroyCounts, 3> counts;
     counts.emplace_back(live, dead);
     counts.emplace_back(live, dead);
@@ -406,12 +516,13 @@
 
   live = dead = 0;
   {
+    // Copy.
     SmallVector<DestroyCounts, 2> counts;
     counts.emplace_back(live, dead);
     counts.emplace_back(live, dead);
     counts.emplace_back(live, dead);
 
-    auto copy = counts;
+    const auto copy = counts;
     EXPECT_TRUE(copy.dynamic());
   }
   EXPECT_EQ(6, live);
@@ -419,12 +530,13 @@
 
   live = dead = 0;
   {
+    // Move.
     SmallVector<DestroyCounts, 2> counts;
     counts.emplace_back(live, dead);
     counts.emplace_back(live, dead);
     counts.emplace_back(live, dead);
 
-    auto move = std::move(counts);
+    const auto move = std::move(counts);
     EXPECT_TRUE(move.dynamic());
   }
   EXPECT_EQ(3, live);
@@ -432,6 +544,7 @@
 
   live = dead = 0;
   {
+    // Swap.
     SmallVector<DestroyCounts, 2> counts1;
     counts1.emplace_back(live, dead);
     counts1.emplace_back(live, dead);
@@ -448,7 +561,10 @@
 
     swap(counts1, counts2);
 
+    EXPECT_EQ(1u, counts1.size());
     EXPECT_FALSE(counts1.dynamic());
+
+    EXPECT_EQ(3u, counts2.size());
     EXPECT_TRUE(counts2.dynamic());
 
     EXPECT_EQ(0, live);
@@ -460,4 +576,87 @@
   EXPECT_EQ(0, dead);
 }
 
+TEST(SmallVector, Clear) {
+  int live = 0;
+  int dead = 0;
+
+  SmallVector<DestroyCounts, 2> counts;
+  {
+    // Static.
+    counts.emplace_back(live, dead);
+    counts.emplace_back(live, dead);
+
+    counts.clear();
+
+    EXPECT_TRUE(counts.empty());
+    EXPECT_FALSE(counts.dynamic());
+  }
+  EXPECT_EQ(2, live);
+  EXPECT_EQ(0, dead);
+
+  live = 0;
+  {
+    // Dynamic.
+    counts.emplace_back(live, dead);
+    counts.emplace_back(live, dead);
+    counts.emplace_back(live, dead);
+
+    counts.clear();
+
+    EXPECT_TRUE(counts.empty());
+    EXPECT_TRUE(counts.dynamic());
+  }
+  EXPECT_EQ(3, live);
+  EXPECT_EQ(2, dead);
+}
+
+TEST(SmallVector, Promote) {
+  {
+    const std::vector vector = {"snow"s, "cone"s};
+    EXPECT_EQ(vector, SmallVector("snow"s, "cone"s).promote());
+    EXPECT_EQ(vector, (SmallVector<std::string, 0>({"snow"s, "cone"s}).promote()));
+  }
+
+  int live = 0;
+  int dead = 0;
+
+  std::vector<DestroyCounts> vector;
+  {
+    // Static.
+    SmallVector<DestroyCounts, 3> counts;
+    counts.emplace_back(live, dead);
+    counts.emplace_back(live, dead);
+
+    vector = std::move(counts).promote();
+
+    ASSERT_EQ(2u, vector.size());
+    EXPECT_TRUE(vector[0].alive);
+    EXPECT_TRUE(vector[1].alive);
+  }
+  EXPECT_EQ(0, live);
+  EXPECT_EQ(2, dead);
+
+  vector.clear();
+  live = dead = 0;
+  {
+    // Dynamic.
+    SmallVector<DestroyCounts, 2> counts;
+    counts.emplace_back(live, dead);
+    counts.emplace_back(live, dead);
+    counts.emplace_back(live, dead);
+
+    EXPECT_EQ(2, dead);
+    dead = 0;
+
+    vector = std::move(counts).promote();
+
+    ASSERT_EQ(3u, vector.size());
+    EXPECT_TRUE(vector[0].alive);
+    EXPECT_TRUE(vector[1].alive);
+    EXPECT_TRUE(vector[2].alive);
+  }
+  EXPECT_EQ(0, live);
+  EXPECT_EQ(0, dead);
+}
+
 }  // namespace android::test
diff --git a/libs/ftl/static_vector_test.cpp b/libs/ftl/static_vector_test.cpp
index cbe8dff..0e10a5d 100644
--- a/libs/ftl/static_vector_test.cpp
+++ b/libs/ftl/static_vector_test.cpp
@@ -144,6 +144,64 @@
   }
 }
 
+TEST(StaticVector, Copy) {
+  {
+    // Same capacity.
+    const StaticVector vector = {"snow"s, "cone"s};
+
+    StaticVector<const std::string, 2> copy(vector);
+    EXPECT_EQ(copy, vector);
+
+    // The vector is assignable even if T is const.
+    const StaticVector<std::string, 2> other = {"tiramisu"s};
+    copy = other;
+    EXPECT_EQ(copy, other);
+  }
+  {
+    // From smaller capacity.
+    const StaticVector vector = {"snow"s, "cone"s};
+
+    StaticVector<const std::string, 3> copy(vector);
+    EXPECT_EQ(copy, vector);
+
+    // The vector is assignable even if T is const.
+    const StaticVector other = {"tiramisu"s};
+    copy = other;
+    EXPECT_EQ(copy, other);
+  }
+}
+
+TEST(StaticVector, Move) {
+  {
+    // Same capacity.
+    StaticVector vector = {"snow"s, "cone"s};
+
+    StaticVector<const std::string, 2> move(std::move(vector));
+    EXPECT_TRUE(vector.empty());
+    EXPECT_EQ(move, (StaticVector{"snow"s, "cone"s}));
+
+    // The vector is assignable even if T is const.
+    StaticVector<std::string, 2> other = {"tiramisu"s};
+    move = std::move(other);
+    EXPECT_TRUE(other.empty());
+    EXPECT_EQ(move, (StaticVector{"tiramisu"s}));
+  }
+  {
+    // From smaller capacity.
+    StaticVector vector = {"snow"s, "cone"s};
+
+    StaticVector<const std::string, 3> move(std::move(vector));
+    EXPECT_TRUE(vector.empty());
+    EXPECT_EQ(move, (StaticVector{"snow"s, "cone"s}));
+
+    // The vector is assignable even if T is const.
+    StaticVector other = {"tiramisu"s};
+    move = std::move(other);
+    EXPECT_TRUE(other.empty());
+    EXPECT_EQ(move, (StaticVector{"tiramisu"s}));
+  }
+}
+
 TEST(StaticVector, String) {
   StaticVector<char, 10> chars;
   char c = 'a';
@@ -328,21 +386,20 @@
   bool alive = true;
 };
 
-void swap(DestroyCounts& lhs, DestroyCounts& rhs) {
-  std::swap(lhs.alive, rhs.alive);
-}
-
 }  // namespace
 
 TEST(StaticVector, Destroy) {
   int live = 0;
   int dead = 0;
-
-  { StaticVector<DestroyCounts, 5> counts; }
+  {
+    // Empty.
+    StaticVector<DestroyCounts, 5> counts;
+  }
   EXPECT_EQ(0, live);
   EXPECT_EQ(0, dead);
 
   {
+    // Non-empty.
     StaticVector<DestroyCounts, 5> counts;
     counts.emplace_back(live, dead);
     counts.emplace_back(live, dead);
@@ -353,30 +410,33 @@
 
   live = 0;
   {
+    // Copy.
     StaticVector<DestroyCounts, 5> counts;
     counts.emplace_back(live, dead);
     counts.emplace_back(live, dead);
     counts.emplace_back(live, dead);
 
-    auto copy = counts;
+    const auto copy = counts;
   }
   EXPECT_EQ(6, live);
   EXPECT_EQ(0, dead);
 
   live = 0;
   {
+    // Move.
     StaticVector<DestroyCounts, 5> counts;
     counts.emplace_back(live, dead);
     counts.emplace_back(live, dead);
     counts.emplace_back(live, dead);
 
-    auto move = std::move(counts);
+    const auto move = std::move(counts);
   }
   EXPECT_EQ(3, live);
   EXPECT_EQ(3, dead);
 
   live = dead = 0;
   {
+    // Swap.
     StaticVector<DestroyCounts, 5> counts1;
     counts1.emplace_back(live, dead);
     counts1.emplace_back(live, dead);
@@ -384,15 +444,35 @@
 
     StaticVector<DestroyCounts, 5> counts2;
     counts2.emplace_back(live, dead);
+    counts2.emplace_back(live, dead);
 
     swap(counts1, counts2);
 
+    EXPECT_EQ(2u, counts1.size());
+    EXPECT_EQ(3u, counts2.size());
+
     EXPECT_EQ(0, live);
-    EXPECT_EQ(2, dead);
+    EXPECT_EQ(7, dead);  // 3 moves per swap, plus 1 move.
 
     dead = 0;
   }
-  EXPECT_EQ(4, live);
+  EXPECT_EQ(5, live);
+  EXPECT_EQ(0, dead);
+}
+
+TEST(StaticVector, Clear) {
+  int live = 0;
+  int dead = 0;
+  {
+    StaticVector<DestroyCounts, 5> counts;
+    counts.emplace_back(live, dead);
+    counts.emplace_back(live, dead);
+
+    counts.clear();
+
+    EXPECT_TRUE(counts.empty());
+  }
+  EXPECT_EQ(2, live);
   EXPECT_EQ(0, dead);
 }
 
diff --git a/libs/ftl/string_test.cpp b/libs/ftl/string_test.cpp
new file mode 100644
index 0000000..f3d85c8
--- /dev/null
+++ b/libs/ftl/string_test.cpp
@@ -0,0 +1,187 @@
+/*
+ * 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.
+ */
+
+#include <ftl/string.h>
+#include <gtest/gtest.h>
+
+#include <algorithm>
+#include <cstdint>
+#include <iterator>
+#include <limits>
+#include <sstream>
+#include <type_traits>
+
+namespace android::test {
+
+// Keep in sync with example usage in header file.
+TEST(String, ToChars) {
+  ftl::to_chars_buffer_t<> buffer;
+
+  EXPECT_EQ(ftl::to_chars(buffer, 123u), "123");
+  EXPECT_EQ(ftl::to_chars(buffer, -42, ftl::Radix::kBin), "-0b101010");
+  EXPECT_EQ(ftl::to_chars(buffer, 0xcafe, ftl::Radix::kHex), "0xcafe");
+  EXPECT_EQ(ftl::to_chars(buffer, '*', ftl::Radix::kHex), "0x2a");
+}
+
+namespace {
+
+template <typename F, typename T>
+void ToCharsTest() {
+  constexpr auto kRadix = F::kRadix;
+
+  using Limits = std::numeric_limits<T>;
+  constexpr auto kMin = Limits::min();
+  constexpr auto kMax = Limits::max();
+  constexpr auto kNeg = static_cast<T>(-42);
+  constexpr auto kPos = static_cast<T>(123);
+
+  ftl::to_chars_buffer_t<T> buffer;
+
+  EXPECT_EQ(ftl::to_chars(buffer, kMin, kRadix), F{}(kMin));
+  EXPECT_EQ(ftl::to_chars(buffer, kMax, kRadix), F{}(kMax));
+  EXPECT_EQ(ftl::to_chars(buffer, kNeg, kRadix), F{}(kNeg));
+  EXPECT_EQ(ftl::to_chars(buffer, kPos, kRadix), F{}(kPos));
+}
+
+template <typename...>
+struct Types {};
+
+template <typename F, typename Types>
+struct ToCharsTests;
+
+template <typename F, typename T, typename... Ts>
+struct ToCharsTests<F, Types<T, Ts...>> {
+  static void test() {
+    ToCharsTest<F, T>();
+    ToCharsTests<F, Types<Ts...>>::test();
+  }
+};
+
+template <typename F>
+struct ToCharsTests<F, Types<>> {
+  static void test() {}
+};
+
+template <typename T, typename U = std::make_unsigned_t<T>>
+U to_unsigned(std::ostream& stream, T v) {
+  if (std::is_same_v<T, U>) return v;
+
+  if (v < 0) {
+    stream << '-';
+    return std::numeric_limits<U>::max() - static_cast<U>(v) + 1;
+  } else {
+    return static_cast<U>(v);
+  }
+}
+
+struct Bin {
+  static constexpr auto kRadix = ftl::Radix::kBin;
+
+  template <typename T>
+  std::string operator()(T v) const {
+    std::ostringstream stream;
+    auto u = to_unsigned(stream, v);
+    stream << "0b";
+
+    if (u == 0) {
+      stream << 0;
+    } else {
+      std::ostringstream digits;
+      do {
+        digits << (u & 1);
+      } while (u >>= 1);
+
+      const auto str = digits.str();
+      std::copy(str.rbegin(), str.rend(), std::ostream_iterator<char>(stream));
+    }
+
+    return stream.str();
+  }
+};
+
+struct Dec {
+  static constexpr auto kRadix = ftl::Radix::kDec;
+
+  template <typename T>
+  std::string operator()(T v) const {
+    return std::to_string(v);
+  }
+};
+
+struct Hex {
+  static constexpr auto kRadix = ftl::Radix::kHex;
+
+  template <typename T>
+  std::string operator()(T v) const {
+    std::ostringstream stream;
+    const auto u = to_unsigned(stream, v);
+    stream << "0x" << std::hex << std::nouppercase;
+    stream << (sizeof(T) == 1 ? static_cast<unsigned>(u) : u);
+    return stream.str();
+  }
+};
+
+using IntegerTypes =
+    Types<char, unsigned char, signed char, std::uint8_t, std::uint16_t, std::uint32_t,
+          std::uint64_t, std::int8_t, std::int16_t, std::int32_t, std::int64_t>;
+
+}  // namespace
+
+TEST(String, ToCharsBin) {
+  ToCharsTests<Bin, IntegerTypes>::test();
+
+  {
+    const std::uint8_t x = 0b1111'1111;
+    ftl::to_chars_buffer_t<decltype(x)> buffer;
+    EXPECT_EQ(ftl::to_chars(buffer, x, ftl::Radix::kBin), "0b11111111");
+  }
+  {
+    const std::int16_t x = -0b1000'0000'0000'0000;
+    ftl::to_chars_buffer_t<decltype(x)> buffer;
+    EXPECT_EQ(ftl::to_chars(buffer, x, ftl::Radix::kBin), "-0b1000000000000000");
+  }
+}
+
+TEST(String, ToCharsDec) {
+  ToCharsTests<Dec, IntegerTypes>::test();
+
+  {
+    const std::uint32_t x = UINT32_MAX;
+    ftl::to_chars_buffer_t<decltype(x)> buffer;
+    EXPECT_EQ(ftl::to_chars(buffer, x), "4294967295");
+  }
+  {
+    const std::int32_t x = INT32_MIN;
+    ftl::to_chars_buffer_t<decltype(x)> buffer;
+    EXPECT_EQ(ftl::to_chars(buffer, x), "-2147483648");
+  }
+}
+
+TEST(String, ToCharsHex) {
+  ToCharsTests<Hex, IntegerTypes>::test();
+
+  {
+    const std::uint16_t x = 0xfade;
+    ftl::to_chars_buffer_t<decltype(x)> buffer;
+    EXPECT_EQ(ftl::to_chars(buffer, x, ftl::Radix::kHex), "0xfade");
+  }
+  {
+    ftl::to_chars_buffer_t<> buffer;
+    EXPECT_EQ(ftl::to_chars(buffer, INT64_MIN, ftl::Radix::kHex), "-0x8000000000000000");
+  }
+}
+
+}  // namespace android::test
diff --git a/libs/gralloc/types/Android.bp b/libs/gralloc/types/Android.bp
index 9a7bad4..bd21fba 100644
--- a/libs/gralloc/types/Android.bp
+++ b/libs/gralloc/types/Android.bp
@@ -51,14 +51,14 @@
     ],
 
     shared_libs: [
-        "android.hardware.graphics.common-V2-ndk",
+        "android.hardware.graphics.common-V3-ndk",
         "android.hardware.graphics.mapper@4.0",
         "libhidlbase",
         "liblog",
     ],
 
     export_shared_lib_headers: [
-        "android.hardware.graphics.common-V2-ndk",
+        "android.hardware.graphics.common-V3-ndk",
         "android.hardware.graphics.mapper@4.0",
         "libhidlbase",
     ],
diff --git a/libs/gralloc/types/Gralloc4.cpp b/libs/gralloc/types/Gralloc4.cpp
index 81a529d..68b99b0 100644
--- a/libs/gralloc/types/Gralloc4.cpp
+++ b/libs/gralloc/types/Gralloc4.cpp
@@ -1189,6 +1189,18 @@
                           decodeByteVector);
 }
 
+status_t encodeSmpte2094_10(const std::optional<std::vector<uint8_t>>& smpte2094_10,
+                            hidl_vec<uint8_t>* outSmpte2094_10) {
+    return encodeOptionalMetadata(MetadataType_Smpte2094_10, smpte2094_10, outSmpte2094_10,
+                                  encodeByteVector);
+}
+
+status_t decodeSmpte2094_10(const hidl_vec<uint8_t>& smpte2094_10,
+                            std::optional<std::vector<uint8_t>>* outSmpte2094_10) {
+    return decodeOptionalMetadata(MetadataType_Smpte2094_10, smpte2094_10, outSmpte2094_10,
+                                  decodeByteVector);
+}
+
 status_t encodeUint32(const MetadataType& metadataType, uint32_t input,
                       hidl_vec<uint8_t>* output) {
     return encodeMetadata(metadataType, input, output, encodeInteger);
diff --git a/libs/gralloc/types/include/gralloctypes/Gralloc4.h b/libs/gralloc/types/include/gralloctypes/Gralloc4.h
index 2f418ac..deaffad 100644
--- a/libs/gralloc/types/include/gralloctypes/Gralloc4.h
+++ b/libs/gralloc/types/include/gralloctypes/Gralloc4.h
@@ -134,6 +134,12 @@
                                              aidl::android::hardware::graphics::common::
                                                      StandardMetadataType::SMPTE2094_40)};
 
+static const android::hardware::graphics::mapper::V4_0::IMapper::MetadataType
+        MetadataType_Smpte2094_10 = {GRALLOC4_STANDARD_METADATA_TYPE,
+                                     static_cast<int64_t>(
+                                             aidl::android::hardware::graphics::common::
+                                                     StandardMetadataType::SMPTE2094_10)};
+
 /*---------------------------------------------------------------------------------------------*/
 
 /**
@@ -327,6 +333,11 @@
 status_t decodeSmpte2094_40(const android::hardware::hidl_vec<uint8_t>& smpte2094_40,
                             std::optional<std::vector<uint8_t>>* outSmpte2094_40);
 
+status_t encodeSmpte2094_10(const std::optional<std::vector<uint8_t>>& smpte2094_10,
+                            android::hardware::hidl_vec<uint8_t>* outSmpte2094_10);
+status_t decodeSmpte2094_10(const android::hardware::hidl_vec<uint8_t>& smpte2094_10,
+                            std::optional<std::vector<uint8_t>>* outSmpte2094_10);
+
 /**
  * The functions below can be used to encode and decode vendor metadata types.
  */
diff --git a/libs/gralloc/types/tests/Gralloc4_test.cpp b/libs/gralloc/types/tests/Gralloc4_test.cpp
index 89cbf4a..94e344f 100644
--- a/libs/gralloc/types/tests/Gralloc4_test.cpp
+++ b/libs/gralloc/types/tests/Gralloc4_test.cpp
@@ -455,6 +455,32 @@
     ASSERT_NO_FATAL_FAILURE(testHelperStableAidlTypeOptional(GetParam(), gralloc4::encodeSmpte2094_40, gralloc4::decodeSmpte2094_40));
 }
 
+class Gralloc4TestSmpte2094_10
+      : public testing::TestWithParam<std::optional<std::vector<uint8_t>>> {};
+
+INSTANTIATE_TEST_CASE_P(
+        Gralloc4TestSmpte2094_10Params, Gralloc4TestSmpte2094_10,
+        ::testing::Values(
+                std::optional<std::vector<uint8_t>>({}),
+                std::optional<std::vector<uint8_t>>({0, 1, 2, 3, 4, 5, 6, 7, 8, 9}),
+                std::optional<std::vector<uint8_t>>({std::numeric_limits<uint8_t>::min(),
+                                                     std::numeric_limits<uint8_t>::min() + 1,
+                                                     std::numeric_limits<uint8_t>::min() + 2,
+                                                     std::numeric_limits<uint8_t>::min() + 3,
+                                                     std::numeric_limits<uint8_t>::min() + 4}),
+                std::optional<std::vector<uint8_t>>({std::numeric_limits<uint8_t>::max(),
+                                                     std::numeric_limits<uint8_t>::max() - 1,
+                                                     std::numeric_limits<uint8_t>::max() - 2,
+                                                     std::numeric_limits<uint8_t>::max() - 3,
+                                                     std::numeric_limits<uint8_t>::max() - 4}),
+                std::nullopt));
+
+TEST_P(Gralloc4TestSmpte2094_10, Smpte2094_10) {
+    ASSERT_NO_FATAL_FAILURE(testHelperStableAidlTypeOptional(GetParam(),
+                                                             gralloc4::encodeSmpte2094_10,
+                                                             gralloc4::decodeSmpte2094_10));
+}
+
 class Gralloc4TestBufferDescriptorInfo : public testing::TestWithParam<BufferDescriptorInfo> { };
 
 INSTANTIATE_TEST_CASE_P(
@@ -491,6 +517,7 @@
     ASSERT_NE(NO_ERROR, gralloc4::encodeSmpte2086({{}}, nullptr));
     ASSERT_NE(NO_ERROR, gralloc4::encodeCta861_3({{}}, nullptr));
     ASSERT_NE(NO_ERROR, gralloc4::encodeSmpte2094_40({{}}, nullptr));
+    ASSERT_NE(NO_ERROR, gralloc4::encodeSmpte2094_10({{}}, nullptr));
 }
 
 TEST_F(Gralloc4TestErrors, Gralloc4TestDecodeNull) {
@@ -516,6 +543,7 @@
     ASSERT_NE(NO_ERROR, gralloc4::decodeSmpte2086(vec, nullptr));
     ASSERT_NE(NO_ERROR, gralloc4::decodeCta861_3(vec, nullptr));
     ASSERT_NE(NO_ERROR, gralloc4::decodeSmpte2094_40(vec, nullptr));
+    ASSERT_NE(NO_ERROR, gralloc4::decodeSmpte2094_10(vec, nullptr));
 }
 
 TEST_F(Gralloc4TestErrors, Gralloc4TestDecodeBadVec) {
diff --git a/libs/gui/Android.bp b/libs/gui/Android.bp
index bd9abc1..d634c58 100644
--- a/libs/gui/Android.bp
+++ b/libs/gui/Android.bp
@@ -37,6 +37,12 @@
         "android.hardware.graphics.bufferqueue@2.0",
     ],
     min_sdk_version: "29",
+    // TODO(b/218719284) can media use be constrained to libgui_bufferqueue_static?
+    apex_available: [
+        "//apex_available:platform",
+        "com.android.media.swcodec",
+        "test_com.android.media.swcodec",
+    ],
 }
 
 cc_library_headers {
@@ -55,7 +61,7 @@
 filegroup {
     name: "guiconstants_aidl",
     srcs: [
-        "android/**/DropInputMode.aidl",
+        "android/gui/DropInputMode.aidl",
         "android/**/TouchOcclusionMode.aidl",
     ],
 }
@@ -66,11 +72,14 @@
     host_supported: true,
     srcs: [
         ":guiconstants_aidl",
+        ":inputconstants_aidl",
+        "android/gui/DisplayInfo.aidl",
         "android/gui/FocusRequest.aidl",
         "android/gui/InputApplicationInfo.aidl",
         "android/gui/IWindowInfosListener.aidl",
         "android/gui/IWindowInfosReportedListener.aidl",
         "android/gui/WindowInfo.aidl",
+        "DisplayInfo.cpp",
         "WindowInfo.cpp",
     ],
 
@@ -91,7 +100,7 @@
     ],
 
     aidl: {
-        export_aidl_headers: true
+        export_aidl_headers: true,
     },
 
     include_dirs: [
@@ -178,11 +187,9 @@
         "FrameTimelineInfo.cpp",
         "GLConsumer.cpp",
         "IConsumerListener.cpp",
-        "IDisplayEventConnection.cpp",
         "IGraphicBufferConsumer.cpp",
         "IGraphicBufferProducer.cpp",
         "IProducerListener.cpp",
-        "IRegionSamplingListener.cpp",
         "ISurfaceComposer.cpp",
         "ISurfaceComposerClient.cpp",
         "ITransactionCompletedListener.cpp",
@@ -197,6 +204,7 @@
         "SurfaceComposerClient.cpp",
         "SyncFeatures.cpp",
         "TransactionTracing.cpp",
+        "VsyncEventData.cpp",
         "view/Surface.cpp",
         "WindowInfosListenerReporter.cpp",
         "bufferqueue/1.0/B2HProducerListener.cpp",
@@ -337,6 +345,7 @@
         "android.hardware.graphics.bufferqueue@2.0",
         "android.hardware.graphics.common@1.1",
         "android.hardware.graphics.common@1.2",
+        "android.hardware.graphics.common-V3-ndk",
         "android.hidl.token@1.0-utils",
         "libbase",
         "libcutils",
diff --git a/libs/gui/BLASTBufferQueue.cpp b/libs/gui/BLASTBufferQueue.cpp
index f034642..c2793ac 100644
--- a/libs/gui/BLASTBufferQueue.cpp
+++ b/libs/gui/BLASTBufferQueue.cpp
@@ -28,6 +28,7 @@
 #include <gui/GLConsumer.h>
 #include <gui/IProducerListener.h>
 #include <gui/Surface.h>
+#include <gui/TraceUtils.h>
 #include <utils/Singleton.h>
 #include <utils/Trace.h>
 
@@ -57,6 +58,10 @@
 #define BQA_LOGE(x, ...) \
     ALOGE("[%s](f:%u,a:%u) " x, mName.c_str(), mNumFrameAvailable, mNumAcquired, ##__VA_ARGS__)
 
+#define BBQ_TRACE(x, ...)                                                                  \
+    ATRACE_FORMAT("%s - %s(f:%u,a:%u)" x, __FUNCTION__, mName.c_str(), mNumFrameAvailable, \
+                  mNumAcquired, ##__VA_ARGS__)
+
 void BLASTBufferItemConsumer::onDisconnect() {
     Mutex::Autolock lock(mMutex);
     mPreviouslyConnected = mCurrentlyConnected;
@@ -127,12 +132,14 @@
     }
 }
 
-BLASTBufferQueue::BLASTBufferQueue(const std::string& name)
+BLASTBufferQueue::BLASTBufferQueue(const std::string& name, bool updateDestinationFrame)
       : mSurfaceControl(nullptr),
         mSize(1, 1),
         mRequestedSize(mSize),
         mFormat(PIXEL_FORMAT_RGBA_8888),
-        mNextTransaction(nullptr) {
+        mTransactionReadyCallback(nullptr),
+        mSyncTransaction(nullptr),
+        mUpdateDestinationFrame(updateDestinationFrame) {
     createBufferQueue(&mProducer, &mConsumer);
     // since the adapter is in the client process, set dequeue timeout
     // explicitly so that dequeueBuffer will block
@@ -155,6 +162,7 @@
 
     ComposerService::getComposerService()->getMaxAcquiredBufferCount(&mMaxAcquiredBuffers);
     mBufferItemConsumer->setMaxAcquiredBufferCount(mMaxAcquiredBuffers);
+    mCurrentMaxAcquiredBufferCount = mMaxAcquiredBuffers;
     mNumAcquired = 0;
     mNumFrameAvailable = 0;
     BQA_LOGV("BLASTBufferQueue created");
@@ -173,36 +181,42 @@
     BQA_LOGE("Applying pending transactions on dtor %d",
              static_cast<uint32_t>(mPendingTransactions.size()));
     SurfaceComposerClient::Transaction t;
-    for (auto& [targetFrameNumber, transaction] : mPendingTransactions) {
-        t.merge(std::move(transaction));
+    mergePendingTransactions(&t, std::numeric_limits<uint64_t>::max() /* frameNumber */);
+    // All transactions on our apply token are one-way. See comment on mAppliedLastTransaction
+    t.setApplyToken(mApplyToken).apply(false, true);
+
+    if (mTransactionReadyCallback) {
+        mTransactionReadyCallback(mSyncTransaction);
     }
-    t.apply();
 }
 
 void BLASTBufferQueue::update(const sp<SurfaceControl>& surface, uint32_t width, uint32_t height,
-                              int32_t format, SurfaceComposerClient::Transaction* outTransaction) {
+                              int32_t format) {
+    LOG_ALWAYS_FATAL_IF(surface == nullptr, "BLASTBufferQueue: mSurfaceControl must not be NULL");
+
     std::unique_lock _lock{mMutex};
     if (mFormat != format) {
         mFormat = format;
         mBufferItemConsumer->setDefaultBufferFormat(convertBufferFormat(format));
     }
 
-    SurfaceComposerClient::Transaction t;
-    const bool setBackpressureFlag = !SurfaceControl::isSameSurface(mSurfaceControl, surface);
+    const bool surfaceControlChanged = !SurfaceControl::isSameSurface(mSurfaceControl, surface);
+    if (surfaceControlChanged && mSurfaceControl != nullptr) {
+        BQA_LOGD("Updating SurfaceControl without recreating BBQ");
+    }
     bool applyTransaction = false;
 
     // Always update the native object even though they might have the same layer handle, so we can
     // get the updated transform hint from WM.
     mSurfaceControl = surface;
-    if (mSurfaceControl != nullptr) {
-        if (setBackpressureFlag) {
-            t.setFlags(mSurfaceControl, layer_state_t::eEnableBackpressure,
-                       layer_state_t::eEnableBackpressure);
-            applyTransaction = true;
-        }
-        mTransformHint = mSurfaceControl->getTransformHint();
-        mBufferItemConsumer->setTransformHint(mTransformHint);
+    SurfaceComposerClient::Transaction t;
+    if (surfaceControlChanged) {
+        t.setFlags(mSurfaceControl, layer_state_t::eEnableBackpressure,
+                   layer_state_t::eEnableBackpressure);
+        applyTransaction = true;
     }
+    mTransformHint = mSurfaceControl->getTransformHint();
+    mBufferItemConsumer->setTransformHint(mTransformHint);
     BQA_LOGV("update width=%d height=%d format=%d mTransformHint=%d", width, height, format,
              mTransformHint);
 
@@ -214,18 +228,15 @@
             // If the buffer supports scaling, update the frame immediately since the client may
             // want to scale the existing buffer to the new size.
             mSize = mRequestedSize;
-            SurfaceComposerClient::Transaction* destFrameTransaction =
-                    (outTransaction) ? outTransaction : &t;
-            if (mSurfaceControl != nullptr) {
-                destFrameTransaction->setDestinationFrame(mSurfaceControl,
-                                                          Rect(0, 0, newSize.getWidth(),
-                                                               newSize.getHeight()));
+            if (mUpdateDestinationFrame) {
+                t.setDestinationFrame(mSurfaceControl, Rect(newSize));
+                applyTransaction = true;
             }
-            applyTransaction = true;
         }
     }
     if (applyTransaction) {
-        t.setApplyToken(mApplyToken).apply();
+        // All transactions on our apply token are one-way. See comment on mAppliedLastTransaction
+        t.setApplyToken(mApplyToken).apply(false, true);
     }
 }
 
@@ -254,7 +265,7 @@
                                                     const std::vector<SurfaceControlStats>& stats) {
     {
         std::unique_lock _lock{mMutex};
-        ATRACE_CALL();
+        BBQ_TRACE();
         BQA_LOGV("transactionCommittedCallback");
         if (!mSurfaceControlsWithPendingCallback.empty()) {
             sp<SurfaceControl> pendingSC = mSurfaceControlsWithPendingCallback.front();
@@ -273,10 +284,10 @@
                 // case, we don't actually want to flush the frames in between since they will get
                 // processed and merged with the sync transaction and released earlier than if they
                 // were sent to SF
-                if (mWaitForTransactionCallback && mNextTransaction == nullptr &&
+                if (mWaitForTransactionCallback && mSyncTransaction == nullptr &&
                     currFrameNumber >= mLastAcquiredFrameNumber) {
                     mWaitForTransactionCallback = false;
-                    flushShadowQueueLocked();
+                    flushShadowQueue();
                 }
             } else {
                 BQA_LOGE("Failed to find matching SurfaceControl in transactionCommittedCallback");
@@ -302,12 +313,9 @@
 
 void BLASTBufferQueue::transactionCallback(nsecs_t /*latchTime*/, const sp<Fence>& /*presentFence*/,
                                            const std::vector<SurfaceControlStats>& stats) {
-    std::function<void(int64_t)> transactionCompleteCallback = nullptr;
-    uint64_t currFrameNumber = 0;
-
     {
         std::unique_lock _lock{mMutex};
-        ATRACE_CALL();
+        BBQ_TRACE();
         BQA_LOGV("transactionCallback");
 
         if (!mSurfaceControlsWithPendingCallback.empty()) {
@@ -331,28 +339,6 @@
                                                     stat.latchTime,
                                                     stat.frameEventStats.dequeueReadyTime);
                 }
-                currFrameNumber = stat.frameEventStats.frameNumber;
-
-                if (mTransactionCompleteCallback &&
-                    currFrameNumber >= mTransactionCompleteFrameNumber) {
-                    if (currFrameNumber > mTransactionCompleteFrameNumber) {
-                        BQA_LOGE("transactionCallback received for a newer framenumber=%" PRIu64
-                                 " than expected=%" PRIu64,
-                                 currFrameNumber, mTransactionCompleteFrameNumber);
-                    }
-                    transactionCompleteCallback = std::move(mTransactionCompleteCallback);
-                    mTransactionCompleteFrameNumber = 0;
-                }
-                std::vector<ReleaseCallbackId> staleReleases;
-                for (const auto& [key, value]: mSubmitted) {
-                    if (currFrameNumber > key.framenumber) {
-                        staleReleases.push_back(key);
-                    }
-                }
-                for (const auto& staleRelease : staleReleases) {
-                    releaseBufferCallbackLocked(staleRelease, stat.previousReleaseFence ? stat.previousReleaseFence : Fence::NO_FENCE,
-                                                stat.transformHint, stat.currentMaxAcquiredBufferCount);
-                }
             } else {
                 BQA_LOGE("Failed to find matching SurfaceControl in transactionCallback");
             }
@@ -361,13 +347,8 @@
                      "empty.");
         }
 
-
         decStrong((void*)transactionCallbackThunk);
     }
-
-    if (transactionCompleteCallback) {
-        transactionCompleteCallback(currFrameNumber);
-    }
 }
 
 // Unlike transactionCallbackThunk the release buffer callback does not extend the life of the
@@ -375,19 +356,18 @@
 // So we pass in a weak pointer to the BBQ and if it still alive, then we release the buffer.
 // Otherwise, this is a no-op.
 static void releaseBufferCallbackThunk(wp<BLASTBufferQueue> context, const ReleaseCallbackId& id,
-                                       const sp<Fence>& releaseFence, uint32_t transformHint,
-                                       uint32_t currentMaxAcquiredBufferCount) {
+                                       const sp<Fence>& releaseFence,
+                                       std::optional<uint32_t> currentMaxAcquiredBufferCount) {
     sp<BLASTBufferQueue> blastBufferQueue = context.promote();
     if (blastBufferQueue) {
-        blastBufferQueue->releaseBufferCallback(id, releaseFence, transformHint,
-                                                currentMaxAcquiredBufferCount);
+        blastBufferQueue->releaseBufferCallback(id, releaseFence, currentMaxAcquiredBufferCount);
     } else {
         ALOGV("releaseBufferCallbackThunk %s blastBufferQueue is dead", id.to_string().c_str());
     }
 }
 
-void BLASTBufferQueue::flushShadowQueueLocked() {
-    BQA_LOGV("flushShadowQueueLocked");
+void BLASTBufferQueue::flushShadowQueue() {
+    BQA_LOGV("flushShadowQueue");
     int numFramesToFlush = mNumFrameAvailable;
     while (numFramesToFlush > 0) {
         acquireNextBufferLocked(std::nullopt);
@@ -395,31 +375,13 @@
     }
 }
 
-void BLASTBufferQueue::flushShadowQueue() {
+void BLASTBufferQueue::releaseBufferCallback(
+        const ReleaseCallbackId& id, const sp<Fence>& releaseFence,
+        std::optional<uint32_t> currentMaxAcquiredBufferCount) {
+    BBQ_TRACE();
     std::unique_lock _lock{mMutex};
-    flushShadowQueueLocked();
-}
-
-void BLASTBufferQueue::releaseBufferCallback(const ReleaseCallbackId& id,
-                                             const sp<Fence>& releaseFence, uint32_t transformHint,
-                                             uint32_t currentMaxAcquiredBufferCount) {
-    std::unique_lock _lock{mMutex};
-    releaseBufferCallbackLocked(id, releaseFence, transformHint, currentMaxAcquiredBufferCount);
-}
-
-void BLASTBufferQueue::releaseBufferCallbackLocked(const ReleaseCallbackId& id,
-                                                   const sp<Fence>& releaseFence, uint32_t transformHint,
-                                                   uint32_t currentMaxAcquiredBufferCount) {
-    ATRACE_CALL();
     BQA_LOGV("releaseBufferCallback %s", id.to_string().c_str());
 
-    if (mSurfaceControl != nullptr) {
-        mTransformHint = transformHint;
-        mSurfaceControl->setTransformHint(transformHint);
-        mBufferItemConsumer->setTransformHint(mTransformHint);
-        BQA_LOGV("updated mTransformHint=%d", mTransformHint);
-    }
-
     // Calculate how many buffers we need to hold before we release them back
     // to the buffer queue. This will prevent higher latency when we are running
     // on a lower refresh rate than the max supported. We only do that for EGL
@@ -429,29 +391,21 @@
         return it != mSubmitted.end() && it->second.mApi == NATIVE_WINDOW_API_EGL;
     }();
 
-    const auto numPendingBuffersToHold =
-            isEGL ? std::max(0u, mMaxAcquiredBuffers - currentMaxAcquiredBufferCount) : 0;
-    auto rb = ReleasedBuffer{id, releaseFence};
-    if (std::find(mPendingRelease.begin(), mPendingRelease.end(), rb) == mPendingRelease.end()) {
-        mPendingRelease.emplace_back(rb);
+    if (currentMaxAcquiredBufferCount) {
+        mCurrentMaxAcquiredBufferCount = *currentMaxAcquiredBufferCount;
     }
 
+    const auto numPendingBuffersToHold =
+            isEGL ? std::max(0u, mMaxAcquiredBuffers - mCurrentMaxAcquiredBufferCount) : 0;
+    mPendingRelease.emplace_back(ReleasedBuffer{id, releaseFence});
+
     // Release all buffers that are beyond the ones that we need to hold
     while (mPendingRelease.size() > numPendingBuffersToHold) {
-        const auto releaseBuffer = mPendingRelease.front();
+        const auto releasedBuffer = mPendingRelease.front();
         mPendingRelease.pop_front();
-        auto it = mSubmitted.find(releaseBuffer.callbackId);
-        if (it == mSubmitted.end()) {
-            BQA_LOGE("ERROR: releaseBufferCallback without corresponding submitted buffer %s",
-                     releaseBuffer.callbackId.to_string().c_str());
-            return;
-        }
-        mNumAcquired--;
-        BQA_LOGV("released %s", id.to_string().c_str());
-        mBufferItemConsumer->releaseBuffer(it->second, releaseBuffer.releaseFence);
-        mSubmitted.erase(it);
+        releaseBuffer(releasedBuffer.callbackId, releasedBuffer.releaseFence);
         // Don't process the transactions here if mWaitForTransactionCallback is set. Instead, let
-        // onFrameAvailable handle processing them since it will merge with the nextTransaction.
+        // onFrameAvailable handle processing them since it will merge with the syncTransaction.
         if (!mWaitForTransactionCallback) {
             acquireNextBufferLocked(std::nullopt);
         }
@@ -463,9 +417,23 @@
     mCallbackCV.notify_all();
 }
 
+void BLASTBufferQueue::releaseBuffer(const ReleaseCallbackId& callbackId,
+                                     const sp<Fence>& releaseFence) {
+    auto it = mSubmitted.find(callbackId);
+    if (it == mSubmitted.end()) {
+        BQA_LOGE("ERROR: releaseBufferCallback without corresponding submitted buffer %s",
+                 callbackId.to_string().c_str());
+        return;
+    }
+    mNumAcquired--;
+    BBQ_TRACE("frame=%" PRIu64, callbackId.framenumber);
+    BQA_LOGV("released %s", callbackId.to_string().c_str());
+    mBufferItemConsumer->releaseBuffer(it->second, releaseFence);
+    mSubmitted.erase(it);
+}
+
 void BLASTBufferQueue::acquireNextBufferLocked(
         const std::optional<SurfaceComposerClient::Transaction*> transaction) {
-    ATRACE_CALL();
     // If the next transaction is set, we want to guarantee the our acquire will not fail, so don't
     // include the extra buffer when checking if we can acquire the next buffer.
     const bool includeExtraAcquire = !transaction;
@@ -499,8 +467,10 @@
         BQA_LOGE("Failed to acquire a buffer, err=%s", statusToString(status).c_str());
         return;
     }
+
     auto buffer = bufferItem.mGraphicBuffer;
     mNumFrameAvailable--;
+    BBQ_TRACE("frame=%" PRIu64, bufferItem.mFrameNumber);
 
     if (buffer == nullptr) {
         mBufferItemConsumer->releaseBuffer(bufferItem, Fence::NO_FENCE);
@@ -534,7 +504,6 @@
     // Ensure BLASTBufferQueue stays alive until we receive the transaction complete callback.
     incStrong((void*)transactionCallbackThunk);
 
-    const bool updateDestinationFrame = mRequestedSize != mSize;
     mSize = mRequestedSize;
     Rect crop = computeCrop(bufferItem);
     mLastBufferInfo.update(true /* hasBuffer */, bufferItem.mGraphicBuffer->getWidth(),
@@ -543,38 +512,38 @@
 
     auto releaseBufferCallback =
             std::bind(releaseBufferCallbackThunk, wp<BLASTBufferQueue>(this) /* callbackContext */,
-                      std::placeholders::_1, std::placeholders::_2, std::placeholders::_3,
-                      std::placeholders::_4);
-    t->setBuffer(mSurfaceControl, buffer, releaseCallbackId, releaseBufferCallback);
+                      std::placeholders::_1, std::placeholders::_2, std::placeholders::_3);
+    sp<Fence> fence = bufferItem.mFence ? new Fence(bufferItem.mFence->dup()) : Fence::NO_FENCE;
+    t->setBuffer(mSurfaceControl, buffer, fence, bufferItem.mFrameNumber, releaseBufferCallback);
     t->setDataspace(mSurfaceControl, static_cast<ui::Dataspace>(bufferItem.mDataSpace));
     t->setHdrMetadata(mSurfaceControl, bufferItem.mHdrMetadata);
     t->setSurfaceDamageRegion(mSurfaceControl, bufferItem.mSurfaceDamage);
-    t->setAcquireFence(mSurfaceControl,
-                       bufferItem.mFence ? new Fence(bufferItem.mFence->dup()) : Fence::NO_FENCE);
     t->addTransactionCompletedCallback(transactionCallbackThunk, static_cast<void*>(this));
 
     mSurfaceControlsWithPendingCallback.push(mSurfaceControl);
 
-    if (updateDestinationFrame) {
-        t->setDestinationFrame(mSurfaceControl, Rect(0, 0, mSize.getWidth(), mSize.getHeight()));
+    if (mUpdateDestinationFrame) {
+        t->setDestinationFrame(mSurfaceControl, Rect(mSize));
+    } else {
+        const bool ignoreDestinationFrame =
+                bufferItem.mScalingMode == NATIVE_WINDOW_SCALING_MODE_FREEZE;
+        t->setFlags(mSurfaceControl,
+                    ignoreDestinationFrame ? layer_state_t::eIgnoreDestinationFrame : 0,
+                    layer_state_t::eIgnoreDestinationFrame);
     }
     t->setBufferCrop(mSurfaceControl, crop);
     t->setTransform(mSurfaceControl, bufferItem.mTransform);
     t->setTransformToDisplayInverse(mSurfaceControl, bufferItem.mTransformToDisplayInverse);
+    t->setAutoRefresh(mSurfaceControl, bufferItem.mAutoRefresh);
     if (!bufferItem.mIsAutoTimestamp) {
         t->setDesiredPresentTime(bufferItem.mTimestamp);
     }
-    t->setFrameNumber(mSurfaceControl, bufferItem.mFrameNumber);
 
     if (!mNextFrameTimelineInfoQueue.empty()) {
         t->setFrameTimelineInfo(mNextFrameTimelineInfoQueue.front());
         mNextFrameTimelineInfoQueue.pop();
     }
 
-    if (mAutoRefresh != bufferItem.mAutoRefresh) {
-        t->setAutoRefresh(mSurfaceControl, bufferItem.mAutoRefresh);
-        mAutoRefresh = bufferItem.mAutoRefresh;
-    }
     {
         std::unique_lock _lock{mTimestampMutex};
         auto dequeueTime = mDequeueTimestamps.find(buffer->getId());
@@ -586,23 +555,15 @@
         }
     }
 
-    auto mergeTransaction =
-            [&t, currentFrameNumber = bufferItem.mFrameNumber](
-                    std::tuple<uint64_t, SurfaceComposerClient::Transaction> pendingTransaction) {
-                auto& [targetFrameNumber, transaction] = pendingTransaction;
-                if (currentFrameNumber < targetFrameNumber) {
-                    return false;
-                }
-                t->merge(std::move(transaction));
-                return true;
-            };
-
-    mPendingTransactions.erase(std::remove_if(mPendingTransactions.begin(),
-                                              mPendingTransactions.end(), mergeTransaction),
-                               mPendingTransactions.end());
-
+    mergePendingTransactions(t, bufferItem.mFrameNumber);
     if (applyTransaction) {
-        t->setApplyToken(mApplyToken).apply();
+        // All transactions on our apply token are one-way. See comment on mAppliedLastTransaction
+        t->setApplyToken(mApplyToken).apply(false, true);
+        mAppliedLastTransaction = true;
+        mLastAppliedFrameNumber = bufferItem.mFrameNumber;
+    } else {
+        t->setBufferHasBarrier(mSurfaceControl, mLastAppliedFrameNumber);
+        mAppliedLastTransaction = false;
     }
 
     BQA_LOGV("acquireNextBufferLocked size=%dx%d mFrameNumber=%" PRIu64
@@ -634,61 +595,95 @@
     mBufferItemConsumer->releaseBuffer(bufferItem, bufferItem.mFence);
 }
 
+void BLASTBufferQueue::flushAndWaitForFreeBuffer(std::unique_lock<std::mutex>& lock) {
+    if (mWaitForTransactionCallback && mNumFrameAvailable > 0) {
+        // We are waiting on a previous sync's transaction callback so allow another sync
+        // transaction to proceed.
+        //
+        // We need to first flush out the transactions that were in between the two syncs.
+        // We do this by merging them into mSyncTransaction so any buffer merging will get
+        // a release callback invoked. The release callback will be async so we need to wait
+        // on max acquired to make sure we have the capacity to acquire another buffer.
+        if (maxBuffersAcquired(false /* includeExtraAcquire */)) {
+            BQA_LOGD("waiting to flush shadow queue...");
+            mCallbackCV.wait(lock);
+        }
+        while (mNumFrameAvailable > 0) {
+            // flush out the shadow queue
+            acquireAndReleaseBuffer();
+        }
+    }
+
+    while (maxBuffersAcquired(false /* includeExtraAcquire */)) {
+        BQA_LOGD("waiting for free buffer.");
+        mCallbackCV.wait(lock);
+    }
+}
+
 void BLASTBufferQueue::onFrameAvailable(const BufferItem& item) {
-    ATRACE_CALL();
-    std::unique_lock _lock{mMutex};
+    std::function<void(SurfaceComposerClient::Transaction*)> prevCallback = nullptr;
+    SurfaceComposerClient::Transaction* prevTransaction = nullptr;
+    {
+        BBQ_TRACE();
+        std::unique_lock _lock{mMutex};
+        const bool syncTransactionSet = mTransactionReadyCallback != nullptr;
+        BQA_LOGV("onFrameAvailable-start syncTransactionSet=%s", boolToString(syncTransactionSet));
 
-    const bool nextTransactionSet = mNextTransaction != nullptr;
-    BQA_LOGV("onFrameAvailable-start nextTransactionSet=%s", boolToString(nextTransactionSet));
-    if (nextTransactionSet) {
-        if (mWaitForTransactionCallback) {
-            // We are waiting on a previous sync's transaction callback so allow another sync
-            // transaction to proceed.
-            //
-            // We need to first flush out the transactions that were in between the two syncs.
-            // We do this by merging them into mNextTransaction so any buffer merging will get
-            // a release callback invoked. The release callback will be async so we need to wait
-            // on max acquired to make sure we have the capacity to acquire another buffer.
-            if (maxBuffersAcquired(false /* includeExtraAcquire */)) {
-                BQA_LOGD("waiting to flush shadow queue...");
-                mCallbackCV.wait(_lock);
+        if (syncTransactionSet) {
+            bool mayNeedToWaitForBuffer = true;
+            // If we are going to re-use the same mSyncTransaction, release the buffer that may
+            // already be set in the Transaction. This is to allow us a free slot early to continue
+            // processing a new buffer.
+            if (!mAcquireSingleBuffer) {
+                auto bufferData = mSyncTransaction->getAndClearBuffer(mSurfaceControl);
+                if (bufferData) {
+                    BQA_LOGD("Releasing previous buffer when syncing: framenumber=%" PRIu64,
+                             bufferData->frameNumber);
+                    releaseBuffer(bufferData->generateReleaseCallbackId(),
+                                  bufferData->acquireFence);
+                    // Because we just released a buffer, we know there's no need to wait for a free
+                    // buffer.
+                    mayNeedToWaitForBuffer = false;
+                }
             }
-            while (mNumFrameAvailable > 0) {
-                // flush out the shadow queue
-                acquireAndReleaseBuffer();
+
+            if (mayNeedToWaitForBuffer) {
+                flushAndWaitForFreeBuffer(_lock);
             }
         }
 
-        while (maxBuffersAcquired(false /* includeExtraAcquire */)) {
-            BQA_LOGD("waiting for free buffer.");
-            mCallbackCV.wait(_lock);
+        // add to shadow queue
+        mNumFrameAvailable++;
+        if (mWaitForTransactionCallback && mNumFrameAvailable >= 2) {
+            acquireAndReleaseBuffer();
+        }
+        ATRACE_INT(mQueuedBufferTrace.c_str(),
+                   mNumFrameAvailable + mNumAcquired - mPendingRelease.size());
+
+        BQA_LOGV("onFrameAvailable framenumber=%" PRIu64 " syncTransactionSet=%s",
+                 item.mFrameNumber, boolToString(syncTransactionSet));
+
+        if (syncTransactionSet) {
+            acquireNextBufferLocked(mSyncTransaction);
+
+            // Only need a commit callback when syncing to ensure the buffer that's synced has been
+            // sent to SF
+            incStrong((void*)transactionCommittedCallbackThunk);
+            mSyncTransaction->addTransactionCommittedCallback(transactionCommittedCallbackThunk,
+                                                              static_cast<void*>(this));
+            mWaitForTransactionCallback = true;
+            if (mAcquireSingleBuffer) {
+                prevCallback = mTransactionReadyCallback;
+                prevTransaction = mSyncTransaction;
+                mTransactionReadyCallback = nullptr;
+                mSyncTransaction = nullptr;
+            }
+        } else if (!mWaitForTransactionCallback) {
+            acquireNextBufferLocked(std::nullopt);
         }
     }
-
-    // add to shadow queue
-    mNumFrameAvailable++;
-    if (mWaitForTransactionCallback && mNumFrameAvailable == 2) {
-        acquireAndReleaseBuffer();
-    }
-    ATRACE_INT(mQueuedBufferTrace.c_str(),
-               mNumFrameAvailable + mNumAcquired - mPendingRelease.size());
-
-    BQA_LOGV("onFrameAvailable framenumber=%" PRIu64 " nextTransactionSet=%s", item.mFrameNumber,
-             boolToString(nextTransactionSet));
-
-    if (nextTransactionSet) {
-        acquireNextBufferLocked(std::move(mNextTransaction));
-
-        // Only need a commit callback when syncing to ensure the buffer that's synced has been sent
-        // to SF
-        incStrong((void*)transactionCommittedCallbackThunk);
-        mNextTransaction->addTransactionCommittedCallback(transactionCommittedCallbackThunk,
-                                                          static_cast<void*>(this));
-
-        mNextTransaction = nullptr;
-        mWaitForTransactionCallback = true;
-    } else if (!mWaitForTransactionCallback) {
-        acquireNextBufferLocked(std::nullopt);
+    if (prevCallback) {
+        prevCallback(prevTransaction);
     }
 }
 
@@ -707,9 +702,54 @@
     mDequeueTimestamps.erase(bufferId);
 };
 
-void BLASTBufferQueue::setNextTransaction(SurfaceComposerClient::Transaction* t) {
-    std::lock_guard _lock{mMutex};
-    mNextTransaction = t;
+void BLASTBufferQueue::syncNextTransaction(
+        std::function<void(SurfaceComposerClient::Transaction*)> callback,
+        bool acquireSingleBuffer) {
+    BBQ_TRACE();
+
+    std::function<void(SurfaceComposerClient::Transaction*)> prevCallback = nullptr;
+    SurfaceComposerClient::Transaction* prevTransaction = nullptr;
+
+    {
+        std::lock_guard _lock{mMutex};
+        // We're about to overwrite the previous call so we should invoke that callback
+        // immediately.
+        if (mTransactionReadyCallback) {
+            prevCallback = mTransactionReadyCallback;
+            prevTransaction = mSyncTransaction;
+        }
+
+        mTransactionReadyCallback = callback;
+        if (callback) {
+            mSyncTransaction = new SurfaceComposerClient::Transaction();
+        } else {
+            mSyncTransaction = nullptr;
+        }
+        mAcquireSingleBuffer = mTransactionReadyCallback ? acquireSingleBuffer : true;
+    }
+
+    if (prevCallback) {
+        prevCallback(prevTransaction);
+    }
+}
+
+void BLASTBufferQueue::stopContinuousSyncTransaction() {
+    std::function<void(SurfaceComposerClient::Transaction*)> prevCallback = nullptr;
+    SurfaceComposerClient::Transaction* prevTransaction = nullptr;
+    {
+        std::lock_guard _lock{mMutex};
+        bool invokeCallback = mTransactionReadyCallback && !mAcquireSingleBuffer;
+        if (invokeCallback) {
+            prevCallback = mTransactionReadyCallback;
+            prevTransaction = mSyncTransaction;
+        }
+        mTransactionReadyCallback = nullptr;
+        mSyncTransaction = nullptr;
+        mAcquireSingleBuffer = true;
+    }
+    if (prevCallback) {
+        prevCallback(prevTransaction);
+    }
 }
 
 bool BLASTBufferQueue::rejectBuffer(const BufferItem& item) {
@@ -734,24 +774,13 @@
     return mSize != bufferSize;
 }
 
-void BLASTBufferQueue::setTransactionCompleteCallback(
-        uint64_t frameNumber, std::function<void(int64_t)>&& transactionCompleteCallback) {
-    std::lock_guard _lock{mMutex};
-    if (transactionCompleteCallback == nullptr) {
-        mTransactionCompleteCallback = nullptr;
-    } else {
-        mTransactionCompleteCallback = std::move(transactionCompleteCallback);
-        mTransactionCompleteFrameNumber = frameNumber;
-    }
-}
-
 // Check if we have acquired the maximum number of buffers.
 // Consumer can acquire an additional buffer if that buffer is not droppable. Set
 // includeExtraAcquire is true to include this buffer to the count. Since this depends on the state
 // of the buffer, the next acquire may return with NO_BUFFER_AVAILABLE.
 bool BLASTBufferQueue::maxBuffersAcquired(bool includeExtraAcquire) const {
     int maxAcquiredBuffers = mMaxAcquiredBuffers + (includeExtraAcquire ? 2 : 1);
-    return mNumAcquired == maxAcquiredBuffers;
+    return mNumAcquired >= maxAcquiredBuffers;
 }
 
 class BBQSurface : public Surface {
@@ -852,6 +881,41 @@
     }
 }
 
+void BLASTBufferQueue::applyPendingTransactions(uint64_t frameNumber) {
+    std::lock_guard _lock{mMutex};
+
+    SurfaceComposerClient::Transaction t;
+    mergePendingTransactions(&t, frameNumber);
+    // All transactions on our apply token are one-way. See comment on mAppliedLastTransaction
+    t.setApplyToken(mApplyToken).apply(false, true);
+}
+
+void BLASTBufferQueue::mergePendingTransactions(SurfaceComposerClient::Transaction* t,
+                                                uint64_t frameNumber) {
+    auto mergeTransaction =
+            [&t, currentFrameNumber = frameNumber](
+                    std::tuple<uint64_t, SurfaceComposerClient::Transaction> pendingTransaction) {
+                auto& [targetFrameNumber, transaction] = pendingTransaction;
+                if (currentFrameNumber < targetFrameNumber) {
+                    return false;
+                }
+                t->merge(std::move(transaction));
+                return true;
+            };
+
+    mPendingTransactions.erase(std::remove_if(mPendingTransactions.begin(),
+                                              mPendingTransactions.end(), mergeTransaction),
+                               mPendingTransactions.end());
+}
+
+SurfaceComposerClient::Transaction* BLASTBufferQueue::gatherPendingTransactions(
+        uint64_t frameNumber) {
+    std::lock_guard _lock{mMutex};
+    SurfaceComposerClient::Transaction* t = new SurfaceComposerClient::Transaction();
+    mergePendingTransactions(t, frameNumber);
+    return t;
+}
+
 // Maintains a single worker thread per process that services a list of runnables.
 class AsyncWorker : public Singleton<AsyncWorker> {
 private:
@@ -999,4 +1063,54 @@
     return mLastAcquiredFrameNumber;
 }
 
+void BLASTBufferQueue::abandon() {
+    std::unique_lock _lock{mMutex};
+    // flush out the shadow queue
+    while (mNumFrameAvailable > 0) {
+        acquireAndReleaseBuffer();
+    }
+
+    // Clear submitted buffer states
+    mNumAcquired = 0;
+    mSubmitted.clear();
+    mPendingRelease.clear();
+
+    if (!mPendingTransactions.empty()) {
+        BQA_LOGD("Applying pending transactions on abandon %d",
+                 static_cast<uint32_t>(mPendingTransactions.size()));
+        SurfaceComposerClient::Transaction t;
+        mergePendingTransactions(&t, std::numeric_limits<uint64_t>::max() /* frameNumber */);
+        // All transactions on our apply token are one-way. See comment on mAppliedLastTransaction
+        t.setApplyToken(mApplyToken).apply(false, true);
+    }
+
+    // Clear sync states
+    if (mWaitForTransactionCallback) {
+        BQA_LOGD("mWaitForTransactionCallback cleared");
+        mWaitForTransactionCallback = false;
+    }
+
+    if (mSyncTransaction != nullptr) {
+        BQA_LOGD("mSyncTransaction cleared mAcquireSingleBuffer=%s",
+                 mAcquireSingleBuffer ? "true" : "false");
+        mSyncTransaction = nullptr;
+        mAcquireSingleBuffer = false;
+    }
+
+    // abandon buffer queue
+    if (mBufferItemConsumer != nullptr) {
+        mBufferItemConsumer->abandon();
+        mBufferItemConsumer->setFrameAvailableListener(nullptr);
+        mBufferItemConsumer->setBufferFreedListener(nullptr);
+    }
+    mBufferItemConsumer = nullptr;
+    mConsumer = nullptr;
+    mProducer = nullptr;
+}
+
+bool BLASTBufferQueue::isSameSurfaceControl(const sp<SurfaceControl>& surfaceControl) const {
+    std::unique_lock _lock{mMutex};
+    return SurfaceControl::isSameSurface(mSurfaceControl, surfaceControl);
+}
+
 } // namespace android
diff --git a/libs/gui/DisplayEventDispatcher.cpp b/libs/gui/DisplayEventDispatcher.cpp
index 46800f2..dfdce20 100644
--- a/libs/gui/DisplayEventDispatcher.cpp
+++ b/libs/gui/DisplayEventDispatcher.cpp
@@ -126,7 +126,7 @@
         ALOGV("dispatcher %p ~ Vsync pulse: timestamp=%" PRId64
               ", displayId=%s, count=%d, vsyncId=%" PRId64,
               this, ns2ms(vsyncTimestamp), to_string(vsyncDisplayId).c_str(), vsyncCount,
-              vsyncEventData.id);
+              vsyncEventData.preferredVsyncId());
         mWaitingForVsync = false;
         mLastVsyncCount = vsyncCount;
         dispatchVsync(vsyncTimestamp, vsyncDisplayId, vsyncCount, vsyncEventData);
@@ -166,9 +166,7 @@
                     *outTimestamp = ev.header.timestamp;
                     *outDisplayId = ev.header.displayId;
                     *outCount = ev.vsync.count;
-                    outVsyncEventData->id = ev.vsync.vsyncId;
-                    outVsyncEventData->deadlineTimestamp = ev.vsync.deadlineTimestamp;
-                    outVsyncEventData->frameInterval = ev.vsync.frameInterval;
+                    *outVsyncEventData = ev.vsync.vsyncData;
                     break;
                 case DisplayEventReceiver::DISPLAY_EVENT_HOTPLUG:
                     dispatchHotplug(ev.header.timestamp, ev.header.displayId, ev.hotplug.connected);
@@ -199,4 +197,9 @@
     return gotVsync;
 }
 
+status_t DisplayEventDispatcher::getLatestVsyncEventData(
+        ParcelableVsyncEventData* outVsyncEventData) const {
+    return mReceiver.getLatestVsyncEventData(outVsyncEventData);
+}
+
 } // namespace android
diff --git a/libs/gui/DisplayEventReceiver.cpp b/libs/gui/DisplayEventReceiver.cpp
index 03b33c7..bfb7769 100644
--- a/libs/gui/DisplayEventReceiver.cpp
+++ b/libs/gui/DisplayEventReceiver.cpp
@@ -19,8 +19,8 @@
 #include <utils/Errors.h>
 
 #include <gui/DisplayEventReceiver.h>
-#include <gui/IDisplayEventConnection.h>
 #include <gui/ISurfaceComposer.h>
+#include <gui/VsyncEventData.h>
 
 #include <private/gui/ComposerService.h>
 
@@ -40,7 +40,13 @@
         mEventConnection = sf->createDisplayEventConnection(vsyncSource, eventRegistration);
         if (mEventConnection != nullptr) {
             mDataChannel = std::make_unique<gui::BitTube>();
-            mEventConnection->stealReceiveChannel(mDataChannel.get());
+            const auto status = mEventConnection->stealReceiveChannel(mDataChannel.get());
+            if (!status.isOk()) {
+                ALOGE("stealReceiveChannel failed: %s", status.toString8().c_str());
+                mInitError = std::make_optional<status_t>(status.transactionError());
+                mDataChannel.reset();
+                mEventConnection.clear();
+            }
         }
     }
 }
@@ -51,12 +57,11 @@
 status_t DisplayEventReceiver::initCheck() const {
     if (mDataChannel != nullptr)
         return NO_ERROR;
-    return NO_INIT;
+    return mInitError.has_value() ? mInitError.value() : NO_INIT;
 }
 
 int DisplayEventReceiver::getFd() const {
-    if (mDataChannel == nullptr)
-        return NO_INIT;
+    if (mDataChannel == nullptr) return mInitError.has_value() ? mInitError.value() : NO_INIT;
 
     return mDataChannel->getFd();
 }
@@ -69,7 +74,7 @@
         mEventConnection->setVsyncRate(count);
         return NO_ERROR;
     }
-    return NO_INIT;
+    return mInitError.has_value() ? mInitError.value() : NO_INIT;
 }
 
 status_t DisplayEventReceiver::requestNextVsync() {
@@ -77,6 +82,19 @@
         mEventConnection->requestNextVsync();
         return NO_ERROR;
     }
+    return mInitError.has_value() ? mInitError.value() : NO_INIT;
+}
+
+status_t DisplayEventReceiver::getLatestVsyncEventData(
+        ParcelableVsyncEventData* outVsyncEventData) const {
+    if (mEventConnection != nullptr) {
+        auto status = mEventConnection->getLatestVsyncEventData(outVsyncEventData);
+        if (!status.isOk()) {
+            ALOGE("Failed to get latest vsync event data: %s", status.exceptionMessage().c_str());
+            return status.transactionError();
+        }
+        return NO_ERROR;
+    }
     return NO_INIT;
 }
 
diff --git a/libs/gui/DisplayInfo.cpp b/libs/gui/DisplayInfo.cpp
new file mode 100644
index 0000000..52d9540
--- /dev/null
+++ b/libs/gui/DisplayInfo.cpp
@@ -0,0 +1,70 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "DisplayInfo"
+
+#include <binder/Parcel.h>
+#include <gui/DisplayInfo.h>
+#include <private/gui/ParcelUtils.h>
+
+#include <log/log.h>
+
+namespace android::gui {
+
+// --- DisplayInfo ---
+
+status_t DisplayInfo::readFromParcel(const android::Parcel* parcel) {
+    if (parcel == nullptr) {
+        ALOGE("%s: Null parcel", __func__);
+        return BAD_VALUE;
+    }
+
+    float dsdx, dtdx, tx, dtdy, dsdy, ty;
+    SAFE_PARCEL(parcel->readInt32, &displayId);
+    SAFE_PARCEL(parcel->readInt32, &logicalWidth);
+    SAFE_PARCEL(parcel->readInt32, &logicalHeight);
+    SAFE_PARCEL(parcel->readFloat, &dsdx);
+    SAFE_PARCEL(parcel->readFloat, &dtdx);
+    SAFE_PARCEL(parcel->readFloat, &tx);
+    SAFE_PARCEL(parcel->readFloat, &dtdy);
+    SAFE_PARCEL(parcel->readFloat, &dsdy);
+    SAFE_PARCEL(parcel->readFloat, &ty);
+
+    transform.set({dsdx, dtdx, tx, dtdy, dsdy, ty, 0, 0, 1});
+
+    return OK;
+}
+
+status_t DisplayInfo::writeToParcel(android::Parcel* parcel) const {
+    if (parcel == nullptr) {
+        ALOGE("%s: Null parcel", __func__);
+        return BAD_VALUE;
+    }
+
+    SAFE_PARCEL(parcel->writeInt32, displayId);
+    SAFE_PARCEL(parcel->writeInt32, logicalWidth);
+    SAFE_PARCEL(parcel->writeInt32, logicalHeight);
+    SAFE_PARCEL(parcel->writeFloat, transform.dsdx());
+    SAFE_PARCEL(parcel->writeFloat, transform.dtdx());
+    SAFE_PARCEL(parcel->writeFloat, transform.tx());
+    SAFE_PARCEL(parcel->writeFloat, transform.dtdy());
+    SAFE_PARCEL(parcel->writeFloat, transform.dsdy());
+    SAFE_PARCEL(parcel->writeFloat, transform.ty());
+
+    return OK;
+}
+
+} // namespace android::gui
diff --git a/libs/gui/FrameTimelineInfo.cpp b/libs/gui/FrameTimelineInfo.cpp
index 9231a57..3800b88 100644
--- a/libs/gui/FrameTimelineInfo.cpp
+++ b/libs/gui/FrameTimelineInfo.cpp
@@ -33,12 +33,14 @@
 status_t FrameTimelineInfo::write(Parcel& output) const {
     SAFE_PARCEL(output.writeInt64, vsyncId);
     SAFE_PARCEL(output.writeInt32, inputEventId);
+    SAFE_PARCEL(output.writeInt64, startTimeNanos);
     return NO_ERROR;
 }
 
 status_t FrameTimelineInfo::read(const Parcel& input) {
     SAFE_PARCEL(input.readInt64, &vsyncId);
     SAFE_PARCEL(input.readInt32, &inputEventId);
+    SAFE_PARCEL(input.readInt64, &startTimeNanos);
     return NO_ERROR;
 }
 
@@ -48,16 +50,19 @@
         if (other.vsyncId > vsyncId) {
             vsyncId = other.vsyncId;
             inputEventId = other.inputEventId;
+            startTimeNanos = other.startTimeNanos;
         }
     } else if (vsyncId == INVALID_VSYNC_ID) {
         vsyncId = other.vsyncId;
         inputEventId = other.inputEventId;
+        startTimeNanos = other.startTimeNanos;
     }
 }
 
 void FrameTimelineInfo::clear() {
     vsyncId = INVALID_VSYNC_ID;
     inputEventId = IInputConstants::INVALID_INPUT_EVENT_ID;
+    startTimeNanos = 0;
 }
 
 }; // namespace android
diff --git a/libs/gui/IDisplayEventConnection.cpp b/libs/gui/IDisplayEventConnection.cpp
deleted file mode 100644
index c0e246f..0000000
--- a/libs/gui/IDisplayEventConnection.cpp
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <gui/IDisplayEventConnection.h>
-
-#include <private/gui/BitTube.h>
-
-namespace android {
-
-namespace { // Anonymous
-
-enum class Tag : uint32_t {
-    STEAL_RECEIVE_CHANNEL = IBinder::FIRST_CALL_TRANSACTION,
-    SET_VSYNC_RATE,
-    REQUEST_NEXT_VSYNC,
-    LAST = REQUEST_NEXT_VSYNC,
-};
-
-} // Anonymous namespace
-
-class BpDisplayEventConnection : public SafeBpInterface<IDisplayEventConnection> {
-public:
-    explicit BpDisplayEventConnection(const sp<IBinder>& impl)
-          : SafeBpInterface<IDisplayEventConnection>(impl, "BpDisplayEventConnection") {}
-
-    ~BpDisplayEventConnection() override;
-
-    status_t stealReceiveChannel(gui::BitTube* outChannel) override {
-        return callRemote<decltype(
-                &IDisplayEventConnection::stealReceiveChannel)>(Tag::STEAL_RECEIVE_CHANNEL,
-                                                                outChannel);
-    }
-
-    status_t setVsyncRate(uint32_t count) override {
-        return callRemote<decltype(&IDisplayEventConnection::setVsyncRate)>(Tag::SET_VSYNC_RATE,
-                                                                            count);
-    }
-
-    void requestNextVsync() override {
-        callRemoteAsync<decltype(&IDisplayEventConnection::requestNextVsync)>(
-                Tag::REQUEST_NEXT_VSYNC);
-    }
-};
-
-// Out-of-line virtual method definition to trigger vtable emission in this translation unit (see
-// clang warning -Wweak-vtables)
-BpDisplayEventConnection::~BpDisplayEventConnection() = default;
-
-IMPLEMENT_META_INTERFACE(DisplayEventConnection, "android.gui.DisplayEventConnection");
-
-status_t BnDisplayEventConnection::onTransact(uint32_t code, const Parcel& data, Parcel* reply,
-                                              uint32_t flags) {
-    if (code < IBinder::FIRST_CALL_TRANSACTION || code > static_cast<uint32_t>(Tag::LAST)) {
-        return BBinder::onTransact(code, data, reply, flags);
-    }
-    auto tag = static_cast<Tag>(code);
-    switch (tag) {
-        case Tag::STEAL_RECEIVE_CHANNEL:
-            return callLocal(data, reply, &IDisplayEventConnection::stealReceiveChannel);
-        case Tag::SET_VSYNC_RATE:
-            return callLocal(data, reply, &IDisplayEventConnection::setVsyncRate);
-        case Tag::REQUEST_NEXT_VSYNC:
-            return callLocalAsync(data, reply, &IDisplayEventConnection::requestNextVsync);
-    }
-}
-
-} // namespace android
diff --git a/libs/gui/IRegionSamplingListener.cpp b/libs/gui/IRegionSamplingListener.cpp
deleted file mode 100644
index 40cbfce..0000000
--- a/libs/gui/IRegionSamplingListener.cpp
+++ /dev/null
@@ -1,64 +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_TAG "IRegionSamplingListener"
-//#define LOG_NDEBUG 0
-
-#include <gui/IRegionSamplingListener.h>
-
-namespace android {
-
-namespace { // Anonymous
-
-enum class Tag : uint32_t {
-    ON_SAMPLE_COLLECTED = IBinder::FIRST_CALL_TRANSACTION,
-    LAST = ON_SAMPLE_COLLECTED,
-};
-
-} // Anonymous namespace
-
-class BpRegionSamplingListener : public SafeBpInterface<IRegionSamplingListener> {
-public:
-    explicit BpRegionSamplingListener(const sp<IBinder>& impl)
-          : SafeBpInterface<IRegionSamplingListener>(impl, "BpRegionSamplingListener") {}
-
-    ~BpRegionSamplingListener() override;
-
-    void onSampleCollected(float medianLuma) override {
-        callRemoteAsync<decltype(
-                &IRegionSamplingListener::onSampleCollected)>(Tag::ON_SAMPLE_COLLECTED, medianLuma);
-    }
-};
-
-// Out-of-line virtual method definitions to trigger vtable emission in this translation unit (see
-// clang warning -Wweak-vtables)
-BpRegionSamplingListener::~BpRegionSamplingListener() = default;
-
-IMPLEMENT_META_INTERFACE(RegionSamplingListener, "android.gui.IRegionSamplingListener");
-
-status_t BnRegionSamplingListener::onTransact(uint32_t code, const Parcel& data, Parcel* reply,
-                                              uint32_t flags) {
-    if (code < IBinder::FIRST_CALL_TRANSACTION || code > static_cast<uint32_t>(Tag::LAST)) {
-        return BBinder::onTransact(code, data, reply, flags);
-    }
-    auto tag = static_cast<Tag>(code);
-    switch (tag) {
-        case Tag::ON_SAMPLE_COLLECTED:
-            return callLocalAsync(data, reply, &IRegionSamplingListener::onSampleCollected);
-    }
-}
-
-} // namespace android
diff --git a/libs/gui/ISurfaceComposer.cpp b/libs/gui/ISurfaceComposer.cpp
index 1726761..5532c6e 100644
--- a/libs/gui/ISurfaceComposer.cpp
+++ b/libs/gui/ISurfaceComposer.cpp
@@ -17,13 +17,13 @@
 // tag as surfaceflinger
 #define LOG_TAG "SurfaceFlinger"
 
+#include <android/gui/IDisplayEventConnection.h>
+#include <android/gui/IRegionSamplingListener.h>
 #include <android/gui/ITransactionTraceListener.h>
 #include <binder/IPCThreadState.h>
 #include <binder/IServiceManager.h>
 #include <binder/Parcel.h>
-#include <gui/IDisplayEventConnection.h>
 #include <gui/IGraphicBufferProducer.h>
-#include <gui/IRegionSamplingListener.h>
 #include <gui/ISurfaceComposer.h>
 #include <gui/ISurfaceComposerClient.h>
 #include <gui/LayerDebugInfo.h>
@@ -42,9 +42,15 @@
 
 // ---------------------------------------------------------------------------
 
+using namespace aidl::android::hardware::graphics;
+
 namespace android {
 
+using gui::DisplayCaptureArgs;
+using gui::IDisplayEventConnection;
+using gui::IRegionSamplingListener;
 using gui::IWindowInfosListener;
+using gui::LayerCaptureArgs;
 using ui::ColorMode;
 
 class BpSurfaceComposer : public BpInterface<ISurfaceComposer>
@@ -105,7 +111,13 @@
 
         SAFE_PARCEL(data.writeUint64, transactionId);
 
-        return remote()->transact(BnSurfaceComposer::SET_TRANSACTION_STATE, data, &reply);
+        if (flags & ISurfaceComposer::eOneWay) {
+            return remote()->transact(BnSurfaceComposer::SET_TRANSACTION_STATE,
+                    data, &reply, IBinder::FLAG_ONEWAY);
+        } else {
+            return remote()->transact(BnSurfaceComposer::SET_TRANSACTION_STATE,
+                    data, &reply);
+        }
     }
 
     void bootFinished() override {
@@ -114,36 +126,6 @@
         remote()->transact(BnSurfaceComposer::BOOT_FINISHED, data, &reply);
     }
 
-    status_t captureDisplay(const DisplayCaptureArgs& args,
-                            const sp<IScreenCaptureListener>& captureListener) override {
-        Parcel data, reply;
-        data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor());
-        SAFE_PARCEL(args.write, data);
-        SAFE_PARCEL(data.writeStrongBinder, IInterface::asBinder(captureListener));
-
-        return remote()->transact(BnSurfaceComposer::CAPTURE_DISPLAY, data, &reply);
-    }
-
-    status_t captureDisplay(uint64_t displayOrLayerStack,
-                            const sp<IScreenCaptureListener>& captureListener) override {
-        Parcel data, reply;
-        data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor());
-        SAFE_PARCEL(data.writeUint64, displayOrLayerStack);
-        SAFE_PARCEL(data.writeStrongBinder, IInterface::asBinder(captureListener));
-
-        return remote()->transact(BnSurfaceComposer::CAPTURE_DISPLAY_BY_ID, data, &reply);
-    }
-
-    status_t captureLayers(const LayerCaptureArgs& args,
-                           const sp<IScreenCaptureListener>& captureListener) override {
-        Parcel data, reply;
-        data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor());
-        SAFE_PARCEL(args.write, data);
-        SAFE_PARCEL(data.writeStrongBinder, IInterface::asBinder(captureListener));
-
-        return remote()->transact(BnSurfaceComposer::CAPTURE_LAYERS, data, &reply);
-    }
-
     bool authenticateSurfaceTexture(
             const sp<IGraphicBufferProducer>& bufferProducer) const override {
         Parcel data, reply;
@@ -282,9 +264,14 @@
             NO_ERROR) {
             std::vector<uint64_t> rawIds;
             if (reply.readUint64Vector(&rawIds) == NO_ERROR) {
-                std::vector<PhysicalDisplayId> displayIds(rawIds.size());
-                std::transform(rawIds.begin(), rawIds.end(), displayIds.begin(),
-                               [](uint64_t rawId) { return PhysicalDisplayId(rawId); });
+                std::vector<PhysicalDisplayId> displayIds;
+                displayIds.reserve(rawIds.size());
+
+                for (const uint64_t rawId : rawIds) {
+                    if (const auto id = DisplayId::fromValue<PhysicalDisplayId>(rawId)) {
+                        displayIds.push_back(*id);
+                    }
+                }
                 return displayIds;
             }
         }
@@ -299,8 +286,11 @@
                     &reply);
         uint64_t rawId;
         SAFE_PARCEL(reply.readUint64, &rawId);
-        *displayId = PhysicalDisplayId(rawId);
-        return NO_ERROR;
+        if (const auto id = DisplayId::fromValue<PhysicalDisplayId>(rawId)) {
+            *displayId = *id;
+            return NO_ERROR;
+        }
+        return NAME_NOT_FOUND;
     }
 
     sp<IBinder> getPhysicalDisplayToken(PhysicalDisplayId displayId) const override {
@@ -418,6 +408,73 @@
         return static_cast<status_t>(reply.readInt32());
     }
 
+    // TODO(b/213909104) : Add unit tests to verify surface flinger boot time APIs
+    status_t getBootDisplayModeSupport(bool* outSupport) const override {
+        Parcel data, reply;
+        status_t error = data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor());
+        if (error != NO_ERROR) {
+            ALOGE("getBootDisplayModeSupport: failed to write interface token: %d", error);
+            return error;
+        }
+        error = remote()->transact(BnSurfaceComposer::GET_BOOT_DISPLAY_MODE_SUPPORT, data, &reply);
+        if (error != NO_ERROR) {
+            ALOGE("getBootDisplayModeSupport: failed to transact: %d", error);
+            return error;
+        }
+        bool support;
+        error = reply.readBool(&support);
+        if (error != NO_ERROR) {
+            ALOGE("getBootDisplayModeSupport: failed to read support: %d", error);
+            return error;
+        }
+        *outSupport = support;
+        return NO_ERROR;
+    }
+
+    status_t setBootDisplayMode(const sp<IBinder>& display,
+                                ui::DisplayModeId displayModeId) override {
+        Parcel data, reply;
+        status_t result = data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor());
+        if (result != NO_ERROR) {
+            ALOGE("setBootDisplayMode failed to writeInterfaceToken: %d", result);
+            return result;
+        }
+        result = data.writeStrongBinder(display);
+        if (result != NO_ERROR) {
+            ALOGE("setBootDisplayMode failed to writeStrongBinder: %d", result);
+            return result;
+        }
+        result = data.writeInt32(displayModeId);
+        if (result != NO_ERROR) {
+            ALOGE("setBootDisplayMode failed to writeIint32: %d", result);
+            return result;
+        }
+        result = remote()->transact(BnSurfaceComposer::SET_BOOT_DISPLAY_MODE, data, &reply);
+        if (result != NO_ERROR) {
+            ALOGE("setBootDisplayMode failed to transact: %d", result);
+        }
+        return result;
+    }
+
+    status_t clearBootDisplayMode(const sp<IBinder>& display) override {
+        Parcel data, reply;
+        status_t result = data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor());
+        if (result != NO_ERROR) {
+            ALOGE("clearBootDisplayMode failed to writeInterfaceToken: %d", result);
+            return result;
+        }
+        result = data.writeStrongBinder(display);
+        if (result != NO_ERROR) {
+            ALOGE("clearBootDisplayMode failed to writeStrongBinder: %d", result);
+            return result;
+        }
+        result = remote()->transact(BnSurfaceComposer::CLEAR_BOOT_DISPLAY_MODE, data, &reply);
+        if (result != NO_ERROR) {
+            ALOGE("clearBootDisplayMode failed to transact: %d", result);
+        }
+        return result;
+    }
+
     void setAutoLowLatencyMode(const sp<IBinder>& display, bool on) override {
         Parcel data, reply;
         status_t result = data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor());
@@ -1124,6 +1181,54 @@
         return NO_ERROR;
     }
 
+    status_t getDisplayDecorationSupport(
+            const sp<IBinder>& displayToken,
+            std::optional<common::DisplayDecorationSupport>* outSupport) const override {
+        Parcel data, reply;
+        status_t error = data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor());
+        if (error != NO_ERROR) {
+            ALOGE("getDisplayDecorationSupport: failed to write interface token: %d", error);
+            return error;
+        }
+        error = data.writeStrongBinder(displayToken);
+        if (error != NO_ERROR) {
+            ALOGE("getDisplayDecorationSupport: failed to write display token: %d", error);
+            return error;
+        }
+        error = remote()->transact(BnSurfaceComposer::GET_DISPLAY_DECORATION_SUPPORT, data, &reply);
+        if (error != NO_ERROR) {
+            ALOGE("getDisplayDecorationSupport: failed to transact: %d", error);
+            return error;
+        }
+        bool support;
+        error = reply.readBool(&support);
+        if (error != NO_ERROR) {
+            ALOGE("getDisplayDecorationSupport: failed to read support: %d", error);
+            return error;
+        }
+
+        if (support) {
+            int32_t format, alphaInterpretation;
+            error = reply.readInt32(&format);
+            if (error != NO_ERROR) {
+                ALOGE("getDisplayDecorationSupport: failed to read format: %d", error);
+                return error;
+            }
+            error = reply.readInt32(&alphaInterpretation);
+            if (error != NO_ERROR) {
+                ALOGE("getDisplayDecorationSupport: failed to read alphaInterpretation: %d", error);
+                return error;
+            }
+            outSupport->emplace();
+            outSupport->value().format = static_cast<common::PixelFormat>(format);
+            outSupport->value().alphaInterpretation =
+                    static_cast<common::AlphaInterpretation>(alphaInterpretation);
+        } else {
+            outSupport->reset();
+        }
+        return NO_ERROR;
+    }
+
     status_t setFrameRate(const sp<IGraphicBufferProducer>& surface, float frameRate,
                           int8_t compatibility, int8_t changeFrameRateStrategy) override {
         Parcel data, reply;
@@ -1142,41 +1247,6 @@
         return reply.readInt32();
     }
 
-    status_t acquireFrameRateFlexibilityToken(sp<IBinder>* outToken) override {
-        if (!outToken) return BAD_VALUE;
-
-        Parcel data, reply;
-        status_t err = data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor());
-        if (err != NO_ERROR) {
-            ALOGE("acquireFrameRateFlexibilityToken: failed writing interface token: %s (%d)",
-                  strerror(-err), -err);
-            return err;
-        }
-
-        err = remote()->transact(BnSurfaceComposer::ACQUIRE_FRAME_RATE_FLEXIBILITY_TOKEN, data,
-                                 &reply);
-        if (err != NO_ERROR) {
-            ALOGE("acquireFrameRateFlexibilityToken: failed to transact: %s (%d)", strerror(-err),
-                  err);
-            return err;
-        }
-
-        err = reply.readInt32();
-        if (err != NO_ERROR) {
-            ALOGE("acquireFrameRateFlexibilityToken: call failed: %s (%d)", strerror(-err), err);
-            return err;
-        }
-
-        err = reply.readStrongBinder(outToken);
-        if (err != NO_ERROR) {
-            ALOGE("acquireFrameRateFlexibilityToken: failed reading binder token: %s (%d)",
-                  strerror(-err), err);
-            return err;
-        }
-
-        return NO_ERROR;
-    }
-
     status_t setFrameTimelineInfo(const sp<IGraphicBufferProducer>& surface,
                                   const FrameTimelineInfo& frameTimelineInfo) override {
         Parcel data, reply;
@@ -1255,6 +1325,21 @@
         SAFE_PARCEL(data.writeStrongBinder, IInterface::asBinder(windowInfosListener));
         return remote()->transact(BnSurfaceComposer::REMOVE_WINDOW_INFOS_LISTENER, data, &reply);
     }
+
+    status_t setOverrideFrameRate(uid_t uid, float frameRate) override {
+        Parcel data, reply;
+        SAFE_PARCEL(data.writeInterfaceToken, ISurfaceComposer::getInterfaceDescriptor());
+        SAFE_PARCEL(data.writeUint32, uid);
+        SAFE_PARCEL(data.writeFloat, frameRate);
+
+        status_t err = remote()->transact(BnSurfaceComposer::SET_OVERRIDE_FRAME_RATE, data, &reply);
+        if (err != NO_ERROR) {
+            ALOGE("setOverrideFrameRate: failed to transact %s (%d)", strerror(-err), err);
+            return err;
+        }
+
+        return NO_ERROR;
+    }
 };
 
 // Out-of-line virtual method definition to trigger vtable emission in this
@@ -1344,33 +1429,6 @@
             bootFinished();
             return NO_ERROR;
         }
-        case CAPTURE_DISPLAY: {
-            CHECK_INTERFACE(ISurfaceComposer, data, reply);
-            DisplayCaptureArgs args;
-            sp<IScreenCaptureListener> captureListener;
-            SAFE_PARCEL(args.read, data);
-            SAFE_PARCEL(data.readStrongBinder, &captureListener);
-
-            return captureDisplay(args, captureListener);
-        }
-        case CAPTURE_DISPLAY_BY_ID: {
-            CHECK_INTERFACE(ISurfaceComposer, data, reply);
-            uint64_t displayOrLayerStack = 0;
-            sp<IScreenCaptureListener> captureListener;
-            SAFE_PARCEL(data.readUint64, &displayOrLayerStack);
-            SAFE_PARCEL(data.readStrongBinder, &captureListener);
-
-            return captureDisplay(displayOrLayerStack, captureListener);
-        }
-        case CAPTURE_LAYERS: {
-            CHECK_INTERFACE(ISurfaceComposer, data, reply);
-            LayerCaptureArgs args;
-            sp<IScreenCaptureListener> captureListener;
-            SAFE_PARCEL(args.read, data);
-            SAFE_PARCEL(data.readStrongBinder, &captureListener);
-
-            return captureLayers(args, captureListener);
-        }
         case AUTHENTICATE_SURFACE: {
             CHECK_INTERFACE(ISurfaceComposer, data, reply);
             sp<IGraphicBufferProducer> bufferProducer =
@@ -1427,9 +1485,9 @@
         }
         case GET_PHYSICAL_DISPLAY_TOKEN: {
             CHECK_INTERFACE(ISurfaceComposer, data, reply);
-            PhysicalDisplayId displayId(data.readUint64());
-            sp<IBinder> display = getPhysicalDisplayToken(displayId);
-            reply->writeStrongBinder(display);
+            const auto id = DisplayId::fromValue<PhysicalDisplayId>(data.readUint64());
+            if (!id) return BAD_VALUE;
+            reply->writeStrongBinder(getPhysicalDisplayToken(*id));
             return NO_ERROR;
         }
         case GET_DISPLAY_STATE: {
@@ -1515,6 +1573,41 @@
             result = reply->writeInt32(result);
             return result;
         }
+        case GET_BOOT_DISPLAY_MODE_SUPPORT: {
+            CHECK_INTERFACE(ISurfaceComposer, data, reply);
+            bool support = false;
+            status_t result = getBootDisplayModeSupport(&support);
+            if (result == NO_ERROR) {
+                reply->writeBool(support);
+            }
+            return result;
+        }
+        case SET_BOOT_DISPLAY_MODE: {
+            CHECK_INTERFACE(ISurfaceComposer, data, reply);
+            sp<IBinder> display = nullptr;
+            status_t result = data.readStrongBinder(&display);
+            if (result != NO_ERROR) {
+                ALOGE("setBootDisplayMode failed to readStrongBinder: %d", result);
+                return result;
+            }
+            ui::DisplayModeId displayModeId;
+            result = data.readInt32(&displayModeId);
+            if (result != NO_ERROR) {
+                ALOGE("setBootDisplayMode failed to readInt32: %d", result);
+                return result;
+            }
+            return setBootDisplayMode(display, displayModeId);
+        }
+        case CLEAR_BOOT_DISPLAY_MODE: {
+            CHECK_INTERFACE(ISurfaceComposer, data, reply);
+            sp<IBinder> display = nullptr;
+            status_t result = data.readStrongBinder(&display);
+            if (result != NO_ERROR) {
+                ALOGE("clearBootDisplayMode failed to readStrongBinder: %d", result);
+                return result;
+            }
+            return clearBootDisplayMode(display);
+        }
         case SET_AUTO_LOW_LATENCY_MODE: {
             CHECK_INTERFACE(ISurfaceComposer, data, reply);
             sp<IBinder> display = nullptr;
@@ -2038,6 +2131,23 @@
             return setGlobalShadowSettings(ambientColor, spotColor, lightPosY, lightPosZ,
                                            lightRadius);
         }
+        case GET_DISPLAY_DECORATION_SUPPORT: {
+            CHECK_INTERFACE(ISurfaceComposer, data, reply);
+            sp<IBinder> displayToken;
+            SAFE_PARCEL(data.readNullableStrongBinder, &displayToken);
+            std::optional<common::DisplayDecorationSupport> support;
+            auto error = getDisplayDecorationSupport(displayToken, &support);
+            if (error != NO_ERROR) {
+                ALOGE("getDisplayDecorationSupport failed with error %d", error);
+                return error;
+            }
+            reply->writeBool(support.has_value());
+            if (support) {
+                reply->writeInt32(static_cast<int32_t>(support.value().format));
+                reply->writeInt32(static_cast<int32_t>(support.value().alphaInterpretation));
+            }
+            return error;
+        }
         case SET_FRAME_RATE: {
             CHECK_INTERFACE(ISurfaceComposer, data, reply);
             sp<IBinder> binder;
@@ -2062,16 +2172,6 @@
             reply->writeInt32(result);
             return NO_ERROR;
         }
-        case ACQUIRE_FRAME_RATE_FLEXIBILITY_TOKEN: {
-            CHECK_INTERFACE(ISurfaceComposer, data, reply);
-            sp<IBinder> token;
-            status_t result = acquireFrameRateFlexibilityToken(&token);
-            reply->writeInt32(result);
-            if (result == NO_ERROR) {
-                reply->writeStrongBinder(token);
-            }
-            return NO_ERROR;
-        }
         case SET_FRAME_TIMELINE_INFO: {
             CHECK_INTERFACE(ISurfaceComposer, data, reply);
             sp<IBinder> binder;
@@ -2159,6 +2259,17 @@
 
             return removeWindowInfosListener(listener);
         }
+        case SET_OVERRIDE_FRAME_RATE: {
+            CHECK_INTERFACE(ISurfaceComposer, data, reply);
+
+            uid_t uid;
+            SAFE_PARCEL(data.readUint32, &uid);
+
+            float frameRate;
+            SAFE_PARCEL(data.readFloat, &frameRate);
+
+            return setOverrideFrameRate(uid, frameRate);
+        }
         default: {
             return BBinder::onTransact(code, data, reply, flags);
         }
diff --git a/libs/gui/ITransactionCompletedListener.cpp b/libs/gui/ITransactionCompletedListener.cpp
index 98e8b54..f7392d4 100644
--- a/libs/gui/ITransactionCompletedListener.cpp
+++ b/libs/gui/ITransactionCompletedListener.cpp
@@ -111,7 +111,14 @@
 
 status_t SurfaceStats::writeToParcel(Parcel* output) const {
     SAFE_PARCEL(output->writeStrongBinder, surfaceControl);
-    SAFE_PARCEL(output->writeInt64, acquireTime);
+    if (const auto* acquireFence = std::get_if<sp<Fence>>(&acquireTimeOrFence)) {
+        SAFE_PARCEL(output->writeBool, true);
+        SAFE_PARCEL(output->write, **acquireFence);
+    } else {
+        SAFE_PARCEL(output->writeBool, false);
+        SAFE_PARCEL(output->writeInt64, std::get<nsecs_t>(acquireTimeOrFence));
+    }
+
     if (previousReleaseFence) {
         SAFE_PARCEL(output->writeBool, true);
         SAFE_PARCEL(output->write, *previousReleaseFence);
@@ -131,10 +138,20 @@
 
 status_t SurfaceStats::readFromParcel(const Parcel* input) {
     SAFE_PARCEL(input->readStrongBinder, &surfaceControl);
-    SAFE_PARCEL(input->readInt64, &acquireTime);
+
     bool hasFence = false;
     SAFE_PARCEL(input->readBool, &hasFence);
     if (hasFence) {
+        acquireTimeOrFence = sp<Fence>::make();
+        SAFE_PARCEL(input->read, *std::get<sp<Fence>>(acquireTimeOrFence));
+    } else {
+        nsecs_t acquireTime;
+        SAFE_PARCEL(input->readInt64, &acquireTime);
+        acquireTimeOrFence = acquireTime;
+    }
+
+    SAFE_PARCEL(input->readBool, &hasFence);
+    if (hasFence) {
         previousReleaseFence = new Fence();
         SAFE_PARCEL(input->read, *previousReleaseFence);
     }
@@ -254,11 +271,10 @@
     }
 
     void onReleaseBuffer(ReleaseCallbackId callbackId, sp<Fence> releaseFence,
-                         uint32_t transformHint, uint32_t currentMaxAcquiredBufferCount) override {
+                         uint32_t currentMaxAcquiredBufferCount) override {
         callRemoteAsync<decltype(
                 &ITransactionCompletedListener::onReleaseBuffer)>(Tag::ON_RELEASE_BUFFER,
                                                                   callbackId, releaseFence,
-                                                                  transformHint,
                                                                   currentMaxAcquiredBufferCount);
     }
 };
diff --git a/libs/gui/LayerDebugInfo.cpp b/libs/gui/LayerDebugInfo.cpp
index 0827bbe..ea5fb29 100644
--- a/libs/gui/LayerDebugInfo.cpp
+++ b/libs/gui/LayerDebugInfo.cpp
@@ -58,7 +58,6 @@
     RETURN_ON_ERROR(parcel->writeInt32(mActiveBufferStride));
     RETURN_ON_ERROR(parcel->writeInt32(mActiveBufferFormat));
     RETURN_ON_ERROR(parcel->writeInt32(mNumQueuedFrames));
-    RETURN_ON_ERROR(parcel->writeBool(mRefreshPending));
     RETURN_ON_ERROR(parcel->writeBool(mIsOpaque));
     RETURN_ON_ERROR(parcel->writeBool(mContentDirty));
     RETURN_ON_ERROR(parcel->write(mStretchEffect));
@@ -103,7 +102,6 @@
     RETURN_ON_ERROR(parcel->readInt32(&mActiveBufferStride));
     RETURN_ON_ERROR(parcel->readInt32(&mActiveBufferFormat));
     RETURN_ON_ERROR(parcel->readInt32(&mNumQueuedFrames));
-    RETURN_ON_ERROR(parcel->readBool(&mRefreshPending));
     RETURN_ON_ERROR(parcel->readBool(&mIsOpaque));
     RETURN_ON_ERROR(parcel->readBool(&mContentDirty));
     RETURN_ON_ERROR(parcel->read(mStretchEffect));
@@ -146,8 +144,7 @@
     StringAppendF(&result, "      activeBuffer=[%4ux%4u:%4u,%s],", info.mActiveBufferWidth,
                   info.mActiveBufferHeight, info.mActiveBufferStride,
                   decodePixelFormat(info.mActiveBufferFormat).c_str());
-    StringAppendF(&result, " queued-frames=%d, mRefreshPending=%d", info.mNumQueuedFrames,
-                  info.mRefreshPending);
+    StringAppendF(&result, " queued-frames=%d", info.mNumQueuedFrames);
     result.append("\n");
     return result;
 }
diff --git a/libs/gui/LayerState.cpp b/libs/gui/LayerState.cpp
index 77a883b..34db5b1 100644
--- a/libs/gui/LayerState.cpp
+++ b/libs/gui/LayerState.cpp
@@ -35,13 +35,14 @@
 using gui::WindowInfoHandle;
 
 layer_state_t::layer_state_t()
-      : what(0),
+      : surface(nullptr),
+        layerId(-1),
+        what(0),
         x(0),
         y(0),
         z(0),
         w(0),
         h(0),
-        layerStack(0),
         alpha(0),
         flags(0),
         mask(0),
@@ -51,7 +52,6 @@
         transform(0),
         transformToDisplayInverse(false),
         crop(Rect::INVALID_RECT),
-        orientedDisplaySpaceRect(Rect::INVALID_RECT),
         dataspace(ui::Dataspace::UNKNOWN),
         surfaceDamageRegion(),
         api(-1),
@@ -65,12 +65,10 @@
         frameRateCompatibility(ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT),
         changeFrameRateStrategy(ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS),
         fixedTransformHint(ui::Transform::ROT_INVALID),
-        frameNumber(0),
         autoRefresh(false),
         isTrustedOverlay(false),
         bufferCrop(Rect::INVALID_RECT),
         destinationFrame(Rect::INVALID_RECT),
-        releaseBufferListener(nullptr),
         dropInputMode(gui::DropInputMode::NONE) {
     matrix.dsdx = matrix.dtdy = 1.0f;
     matrix.dsdy = matrix.dtdx = 0.0f;
@@ -87,7 +85,7 @@
     SAFE_PARCEL(output.writeInt32, z);
     SAFE_PARCEL(output.writeUint32, w);
     SAFE_PARCEL(output.writeUint32, h);
-    SAFE_PARCEL(output.writeUint32, layerStack);
+    SAFE_PARCEL(output.writeUint32, layerStack.id);
     SAFE_PARCEL(output.writeFloat, alpha);
     SAFE_PARCEL(output.writeUint32, flags);
     SAFE_PARCEL(output.writeUint32, mask);
@@ -103,21 +101,6 @@
     SAFE_PARCEL(output.write, transparentRegion);
     SAFE_PARCEL(output.writeUint32, transform);
     SAFE_PARCEL(output.writeBool, transformToDisplayInverse);
-    SAFE_PARCEL(output.write, orientedDisplaySpaceRect);
-
-    if (buffer) {
-        SAFE_PARCEL(output.writeBool, true);
-        SAFE_PARCEL(output.write, *buffer);
-    } else {
-        SAFE_PARCEL(output.writeBool, false);
-    }
-
-    if (acquireFence) {
-        SAFE_PARCEL(output.writeBool, true);
-        SAFE_PARCEL(output.write, *acquireFence);
-    } else {
-        SAFE_PARCEL(output.writeBool, false);
-    }
 
     SAFE_PARCEL(output.writeUint32, static_cast<uint32_t>(dataspace));
     SAFE_PARCEL(output.write, hdrMetadata);
@@ -134,8 +117,6 @@
     SAFE_PARCEL(output.write, colorTransform.asArray(), 16 * sizeof(float));
     SAFE_PARCEL(output.writeFloat, cornerRadius);
     SAFE_PARCEL(output.writeUint32, backgroundBlurRadius);
-    SAFE_PARCEL(output.writeStrongBinder, cachedBuffer.token.promote());
-    SAFE_PARCEL(output.writeUint64, cachedBuffer.id);
     SAFE_PARCEL(output.writeParcelable, metadata);
     SAFE_PARCEL(output.writeFloat, bgColorAlpha);
     SAFE_PARCEL(output.writeUint32, static_cast<uint32_t>(bgColorDataspace));
@@ -152,9 +133,7 @@
     SAFE_PARCEL(output.writeByte, frameRateCompatibility);
     SAFE_PARCEL(output.writeByte, changeFrameRateStrategy);
     SAFE_PARCEL(output.writeUint32, fixedTransformHint);
-    SAFE_PARCEL(output.writeUint64, frameNumber);
     SAFE_PARCEL(output.writeBool, autoRefresh);
-    SAFE_PARCEL(output.writeStrongBinder, IInterface::asBinder(releaseBufferListener));
 
     SAFE_PARCEL(output.writeUint32, blurRegions.size());
     for (auto region : blurRegions) {
@@ -175,8 +154,13 @@
     SAFE_PARCEL(output.write, destinationFrame);
     SAFE_PARCEL(output.writeBool, isTrustedOverlay);
 
-    SAFE_PARCEL(output.writeStrongBinder, releaseBufferEndpoint);
     SAFE_PARCEL(output.writeUint32, static_cast<uint32_t>(dropInputMode));
+
+    const bool hasBufferData = (bufferData != nullptr);
+    SAFE_PARCEL(output.writeBool, hasBufferData);
+    if (hasBufferData) {
+        SAFE_PARCEL(output.writeParcelable, *bufferData);
+    }
     return NO_ERROR;
 }
 
@@ -190,7 +174,7 @@
     SAFE_PARCEL(input.readInt32, &z);
     SAFE_PARCEL(input.readUint32, &w);
     SAFE_PARCEL(input.readUint32, &h);
-    SAFE_PARCEL(input.readUint32, &layerStack);
+    SAFE_PARCEL(input.readUint32, &layerStack.id);
     SAFE_PARCEL(input.readFloat, &alpha);
 
     SAFE_PARCEL(input.readUint32, &flags);
@@ -216,20 +200,6 @@
     SAFE_PARCEL(input.read, transparentRegion);
     SAFE_PARCEL(input.readUint32, &transform);
     SAFE_PARCEL(input.readBool, &transformToDisplayInverse);
-    SAFE_PARCEL(input.read, orientedDisplaySpaceRect);
-
-    bool tmpBool = false;
-    SAFE_PARCEL(input.readBool, &tmpBool);
-    if (tmpBool) {
-        buffer = new GraphicBuffer();
-        SAFE_PARCEL(input.read, *buffer);
-    }
-
-    SAFE_PARCEL(input.readBool, &tmpBool);
-    if (tmpBool) {
-        acquireFence = new Fence();
-        SAFE_PARCEL(input.read, *acquireFence);
-    }
 
     uint32_t tmpUint32 = 0;
     SAFE_PARCEL(input.readUint32, &tmpUint32);
@@ -238,6 +208,8 @@
     SAFE_PARCEL(input.read, hdrMetadata);
     SAFE_PARCEL(input.read, surfaceDamageRegion);
     SAFE_PARCEL(input.readInt32, &api);
+
+    bool tmpBool = false;
     SAFE_PARCEL(input.readBool, &tmpBool);
     if (tmpBool) {
         sidebandStream = NativeHandle::create(input.readNativeHandle(), true);
@@ -246,10 +218,6 @@
     SAFE_PARCEL(input.read, &colorTransform, 16 * sizeof(float));
     SAFE_PARCEL(input.readFloat, &cornerRadius);
     SAFE_PARCEL(input.readUint32, &backgroundBlurRadius);
-    sp<IBinder> tmpBinder;
-    SAFE_PARCEL(input.readNullableStrongBinder, &tmpBinder);
-    cachedBuffer.token = tmpBinder;
-    SAFE_PARCEL(input.readUint64, &cachedBuffer.id);
     SAFE_PARCEL(input.readParcelable, &metadata);
 
     SAFE_PARCEL(input.readFloat, &bgColorAlpha);
@@ -274,15 +242,8 @@
     SAFE_PARCEL(input.readByte, &changeFrameRateStrategy);
     SAFE_PARCEL(input.readUint32, &tmpUint32);
     fixedTransformHint = static_cast<ui::Transform::RotationFlags>(tmpUint32);
-    SAFE_PARCEL(input.readUint64, &frameNumber);
     SAFE_PARCEL(input.readBool, &autoRefresh);
 
-    tmpBinder = nullptr;
-    SAFE_PARCEL(input.readNullableStrongBinder, &tmpBinder);
-    if (tmpBinder) {
-        releaseBufferListener = checked_interface_cast<ITransactionCompletedListener>(tmpBinder);
-    }
-
     uint32_t numRegions = 0;
     SAFE_PARCEL(input.readUint32, &numRegions);
     blurRegions.clear();
@@ -306,11 +267,18 @@
     SAFE_PARCEL(input.read, destinationFrame);
     SAFE_PARCEL(input.readBool, &isTrustedOverlay);
 
-    SAFE_PARCEL(input.readNullableStrongBinder, &releaseBufferEndpoint);
-
     uint32_t mode;
     SAFE_PARCEL(input.readUint32, &mode);
     dropInputMode = static_cast<gui::DropInputMode>(mode);
+
+    bool hasBufferData;
+    SAFE_PARCEL(input.readBool, &hasBufferData);
+    if (hasBufferData) {
+        bufferData = std::make_shared<BufferData>();
+        SAFE_PARCEL(input.readParcelable, bufferData.get());
+    } else {
+        bufferData = nullptr;
+    }
     return NO_ERROR;
 }
 
@@ -322,21 +290,14 @@
     return state.read(input);
 }
 
-DisplayState::DisplayState()
-      : what(0),
-        layerStack(0),
-        flags(0),
-        layerStackSpaceRect(Rect::EMPTY_RECT),
-        orientedDisplaySpaceRect(Rect::EMPTY_RECT),
-        width(0),
-        height(0) {}
+DisplayState::DisplayState() = default;
 
 status_t DisplayState::write(Parcel& output) const {
     SAFE_PARCEL(output.writeStrongBinder, token);
     SAFE_PARCEL(output.writeStrongBinder, IInterface::asBinder(surface));
     SAFE_PARCEL(output.writeUint32, what);
-    SAFE_PARCEL(output.writeUint32, layerStack);
     SAFE_PARCEL(output.writeUint32, flags);
+    SAFE_PARCEL(output.writeUint32, layerStack.id);
     SAFE_PARCEL(output.writeUint32, toRotationInt(orientation));
     SAFE_PARCEL(output.write, layerStackSpaceRect);
     SAFE_PARCEL(output.write, orientedDisplaySpaceRect);
@@ -352,8 +313,8 @@
     surface = interface_cast<IGraphicBufferProducer>(tmpBinder);
 
     SAFE_PARCEL(input.readUint32, &what);
-    SAFE_PARCEL(input.readUint32, &layerStack);
     SAFE_PARCEL(input.readUint32, &flags);
+    SAFE_PARCEL(input.readUint32, &layerStack.id);
     uint32_t tmpUint = 0;
     SAFE_PARCEL(input.readUint32, &tmpUint);
     orientation = ui::toRotation(tmpUint);
@@ -391,6 +352,79 @@
     }
 }
 
+void layer_state_t::sanitize(int32_t permissions) {
+    // TODO: b/109894387
+    //
+    // SurfaceFlinger's renderer is not prepared to handle cropping in the face of arbitrary
+    // rotation. To see the problem observe that if we have a square parent, and a child
+    // of the same size, then we rotate the child 45 degrees around its center, the child
+    // must now be cropped to a non rectangular 8 sided region.
+    //
+    // Of course we can fix this in the future. For now, we are lucky, SurfaceControl is
+    // private API, and arbitrary rotation is used in limited use cases, for instance:
+    // - WindowManager only uses rotation in one case, which is on a top level layer in which
+    //   cropping is not an issue.
+    // - Launcher, as a privileged app, uses this to transition an application to PiP
+    //   (picture-in-picture) mode.
+    //
+    // However given that abuse of rotation matrices could lead to surfaces extending outside
+    // of cropped areas, we need to prevent non-root clients without permission
+    // ACCESS_SURFACE_FLINGER nor ROTATE_SURFACE_FLINGER
+    // (a.k.a. everyone except WindowManager / tests / Launcher) from setting non rectangle
+    // preserving transformations.
+    if (what & eMatrixChanged) {
+        if (!(permissions & Permission::ROTATE_SURFACE_FLINGER)) {
+            ui::Transform t;
+            t.set(matrix.dsdx, matrix.dtdy, matrix.dtdx, matrix.dsdy);
+            if (!t.preserveRects()) {
+                what &= ~eMatrixChanged;
+                ALOGE("Stripped non rect preserving matrix in sanitize");
+            }
+        }
+    }
+
+    if (what & eFlagsChanged) {
+        if ((flags & eLayerIsDisplayDecoration) &&
+            !(permissions & Permission::INTERNAL_SYSTEM_WINDOW)) {
+            flags &= ~eLayerIsDisplayDecoration;
+            ALOGE("Stripped attempt to set LayerIsDisplayDecoration in sanitize");
+        }
+    }
+
+    if (what & layer_state_t::eInputInfoChanged) {
+        if (!(permissions & Permission::ACCESS_SURFACE_FLINGER)) {
+            what &= ~eInputInfoChanged;
+            ALOGE("Stripped attempt to set eInputInfoChanged in sanitize");
+        }
+    }
+    if (what & layer_state_t::eTrustedOverlayChanged) {
+        if (!(permissions & Permission::ACCESS_SURFACE_FLINGER)) {
+            what &= ~eTrustedOverlayChanged;
+            ALOGE("Stripped attempt to set eTrustedOverlay in sanitize");
+        }
+    }
+    if (what & layer_state_t::eDropInputModeChanged) {
+        if (!(permissions & Permission::ACCESS_SURFACE_FLINGER)) {
+            what &= ~eDropInputModeChanged;
+            ALOGE("Stripped attempt to set eDropInputModeChanged in sanitize");
+        }
+    }
+    if (what & layer_state_t::eFrameRateSelectionPriority) {
+        if (!(permissions & Permission::ACCESS_SURFACE_FLINGER)) {
+            what &= ~eFrameRateSelectionPriority;
+            ALOGE("Stripped attempt to set eFrameRateSelectionPriority in sanitize");
+        }
+    }
+    if (what & layer_state_t::eFrameRateChanged) {
+        if (!ValidateFrameRate(frameRate, frameRateCompatibility,
+                               changeFrameRateStrategy,
+                               "layer_state_t::sanitize",
+                               permissions & Permission::ACCESS_SURFACE_FLINGER)) {
+            what &= ~eFrameRateChanged; // logged in ValidateFrameRate
+        }
+    }
+}
+
 void layer_state_t::merge(const layer_state_t& other) {
     if (other.what & ePositionChanged) {
         what |= ePositionChanged;
@@ -468,12 +502,7 @@
     }
     if (other.what & eBufferChanged) {
         what |= eBufferChanged;
-        buffer = other.buffer;
-        releaseBufferEndpoint = other.releaseBufferEndpoint;
-    }
-    if (other.what & eAcquireFenceChanged) {
-        what |= eAcquireFenceChanged;
-        acquireFence = other.acquireFence;
+        bufferData = other.bufferData;
     }
     if (other.what & eDataspaceChanged) {
         what |= eDataspaceChanged;
@@ -506,11 +535,6 @@
         what |= eInputInfoChanged;
         windowInfoHandle = new WindowInfoHandle(*other.windowInfoHandle);
     }
-    if (other.what & eCachedBufferChanged) {
-        what |= eCachedBufferChanged;
-        cachedBuffer = other.cachedBuffer;
-        releaseBufferEndpoint = other.releaseBufferEndpoint;
-    }
     if (other.what & eBackgroundColorChanged) {
         what |= eBackgroundColorChanged;
         color = other.color;
@@ -539,10 +563,6 @@
         what |= eFixedTransformHintChanged;
         fixedTransformHint = other.fixedTransformHint;
     }
-    if (other.what & eFrameNumberChanged) {
-        what |= eFrameNumberChanged;
-        frameNumber = other.frameNumber;
-    }
     if (other.what & eAutoRefreshChanged) {
         what |= eAutoRefreshChanged;
         autoRefresh = other.autoRefresh;
@@ -551,13 +571,6 @@
         what |= eTrustedOverlayChanged;
         isTrustedOverlay = other.isTrustedOverlay;
     }
-    if (other.what & eReleaseBufferListenerChanged) {
-        if (releaseBufferListener) {
-            ALOGW("Overriding releaseBufferListener");
-        }
-        what |= eReleaseBufferListenerChanged;
-        releaseBufferListener = other.releaseBufferListener;
-    }
     if (other.what & eStretchChanged) {
         what |= eStretchChanged;
         stretchEffect = other.stretchEffect;
@@ -570,6 +583,9 @@
         what |= eDestinationFrameChanged;
         destinationFrame = other.destinationFrame;
     }
+    if (other.what & eProducerDisconnect) {
+        what |= eProducerDisconnect;
+    }
     if (other.what & eDropInputModeChanged) {
         what |= eDropInputModeChanged;
         dropInputMode = other.dropInputMode;
@@ -578,19 +594,23 @@
         what |= eColorChanged;
         color = other.color;
     }
+    if (other.what & eColorSpaceAgnosticChanged) {
+        what |= eColorSpaceAgnosticChanged;
+        colorSpaceAgnostic = other.colorSpaceAgnostic;
+    }
     if ((other.what & what) != other.what) {
         ALOGE("Unmerged SurfaceComposer Transaction properties. LayerState::merge needs updating? "
-              "other.what=0x%" PRIu64 " what=0x%" PRIu64,
-              other.what, what);
+              "other.what=0x%" PRIX64 " what=0x%" PRIX64 " unmerged flags=0x%" PRIX64,
+              other.what, what, (other.what & what) ^ other.what);
     }
 }
 
 bool layer_state_t::hasBufferChanges() const {
-    return (what & layer_state_t::eBufferChanged) || (what & layer_state_t::eCachedBufferChanged);
+    return what & layer_state_t::eBufferChanged;
 }
 
 bool layer_state_t::hasValidBuffer() const {
-    return buffer || cachedBuffer.isValid();
+    return bufferData && (bufferData->buffer || bufferData->cachedBuffer.isValid());
 }
 
 status_t layer_state_t::matrix22_t::write(Parcel& output) const {
@@ -622,9 +642,7 @@
 }
 
 bool InputWindowCommands::empty() const {
-    bool empty = true;
-    empty = focusRequests.empty() && !syncInputWindows;
-    return empty;
+    return focusRequests.empty() && !syncInputWindows;
 }
 
 void InputWindowCommands::clear() {
@@ -672,82 +690,163 @@
 
 // ----------------------------------------------------------------------------
 
-status_t CaptureArgs::write(Parcel& output) const {
-    SAFE_PARCEL(output.writeInt32, static_cast<int32_t>(pixelFormat));
-    SAFE_PARCEL(output.write, sourceCrop);
-    SAFE_PARCEL(output.writeFloat, frameScaleX);
-    SAFE_PARCEL(output.writeFloat, frameScaleY);
-    SAFE_PARCEL(output.writeBool, captureSecureLayers);
-    SAFE_PARCEL(output.writeInt32, uid);
-    SAFE_PARCEL(output.writeInt32, static_cast<int32_t>(dataspace));
-    SAFE_PARCEL(output.writeBool, allowProtected);
-    SAFE_PARCEL(output.writeBool, grayscale);
+namespace gui {
+
+status_t CaptureArgs::writeToParcel(Parcel* output) const {
+    SAFE_PARCEL(output->writeInt32, static_cast<int32_t>(pixelFormat));
+    SAFE_PARCEL(output->write, sourceCrop);
+    SAFE_PARCEL(output->writeFloat, frameScaleX);
+    SAFE_PARCEL(output->writeFloat, frameScaleY);
+    SAFE_PARCEL(output->writeBool, captureSecureLayers);
+    SAFE_PARCEL(output->writeInt32, uid);
+    SAFE_PARCEL(output->writeInt32, static_cast<int32_t>(dataspace));
+    SAFE_PARCEL(output->writeBool, allowProtected);
+    SAFE_PARCEL(output->writeBool, grayscale);
     return NO_ERROR;
 }
 
-status_t CaptureArgs::read(const Parcel& input) {
+status_t CaptureArgs::readFromParcel(const Parcel* input) {
     int32_t value = 0;
-    SAFE_PARCEL(input.readInt32, &value);
+    SAFE_PARCEL(input->readInt32, &value);
     pixelFormat = static_cast<ui::PixelFormat>(value);
-    SAFE_PARCEL(input.read, sourceCrop);
-    SAFE_PARCEL(input.readFloat, &frameScaleX);
-    SAFE_PARCEL(input.readFloat, &frameScaleY);
-    SAFE_PARCEL(input.readBool, &captureSecureLayers);
-    SAFE_PARCEL(input.readInt32, &uid);
-    SAFE_PARCEL(input.readInt32, &value);
+    SAFE_PARCEL(input->read, sourceCrop);
+    SAFE_PARCEL(input->readFloat, &frameScaleX);
+    SAFE_PARCEL(input->readFloat, &frameScaleY);
+    SAFE_PARCEL(input->readBool, &captureSecureLayers);
+    SAFE_PARCEL(input->readInt32, &uid);
+    SAFE_PARCEL(input->readInt32, &value);
     dataspace = static_cast<ui::Dataspace>(value);
-    SAFE_PARCEL(input.readBool, &allowProtected);
-    SAFE_PARCEL(input.readBool, &grayscale);
+    SAFE_PARCEL(input->readBool, &allowProtected);
+    SAFE_PARCEL(input->readBool, &grayscale);
     return NO_ERROR;
 }
 
-status_t DisplayCaptureArgs::write(Parcel& output) const {
-    SAFE_PARCEL(CaptureArgs::write, output);
+status_t DisplayCaptureArgs::writeToParcel(Parcel* output) const {
+    SAFE_PARCEL(CaptureArgs::writeToParcel, output);
 
-    SAFE_PARCEL(output.writeStrongBinder, displayToken);
-    SAFE_PARCEL(output.writeUint32, width);
-    SAFE_PARCEL(output.writeUint32, height);
-    SAFE_PARCEL(output.writeBool, useIdentityTransform);
+    SAFE_PARCEL(output->writeStrongBinder, displayToken);
+    SAFE_PARCEL(output->writeUint32, width);
+    SAFE_PARCEL(output->writeUint32, height);
+    SAFE_PARCEL(output->writeBool, useIdentityTransform);
     return NO_ERROR;
 }
 
-status_t DisplayCaptureArgs::read(const Parcel& input) {
-    SAFE_PARCEL(CaptureArgs::read, input);
+status_t DisplayCaptureArgs::readFromParcel(const Parcel* input) {
+    SAFE_PARCEL(CaptureArgs::readFromParcel, input);
 
-    SAFE_PARCEL(input.readStrongBinder, &displayToken);
-    SAFE_PARCEL(input.readUint32, &width);
-    SAFE_PARCEL(input.readUint32, &height);
-    SAFE_PARCEL(input.readBool, &useIdentityTransform);
+    SAFE_PARCEL(input->readStrongBinder, &displayToken);
+    SAFE_PARCEL(input->readUint32, &width);
+    SAFE_PARCEL(input->readUint32, &height);
+    SAFE_PARCEL(input->readBool, &useIdentityTransform);
     return NO_ERROR;
 }
 
-status_t LayerCaptureArgs::write(Parcel& output) const {
-    SAFE_PARCEL(CaptureArgs::write, output);
+status_t LayerCaptureArgs::writeToParcel(Parcel* output) const {
+    SAFE_PARCEL(CaptureArgs::writeToParcel, output);
 
-    SAFE_PARCEL(output.writeStrongBinder, layerHandle);
-    SAFE_PARCEL(output.writeInt32, excludeHandles.size());
+    SAFE_PARCEL(output->writeStrongBinder, layerHandle);
+    SAFE_PARCEL(output->writeInt32, excludeHandles.size());
     for (auto el : excludeHandles) {
-        SAFE_PARCEL(output.writeStrongBinder, el);
+        SAFE_PARCEL(output->writeStrongBinder, el);
     }
-    SAFE_PARCEL(output.writeBool, childrenOnly);
+    SAFE_PARCEL(output->writeBool, childrenOnly);
     return NO_ERROR;
 }
 
-status_t LayerCaptureArgs::read(const Parcel& input) {
-    SAFE_PARCEL(CaptureArgs::read, input);
+status_t LayerCaptureArgs::readFromParcel(const Parcel* input) {
+    SAFE_PARCEL(CaptureArgs::readFromParcel, input);
 
-    SAFE_PARCEL(input.readStrongBinder, &layerHandle);
+    SAFE_PARCEL(input->readStrongBinder, &layerHandle);
 
     int32_t numExcludeHandles = 0;
-    SAFE_PARCEL_READ_SIZE(input.readInt32, &numExcludeHandles, input.dataSize());
+    SAFE_PARCEL_READ_SIZE(input->readInt32, &numExcludeHandles, input->dataSize());
     excludeHandles.reserve(numExcludeHandles);
     for (int i = 0; i < numExcludeHandles; i++) {
         sp<IBinder> binder;
-        SAFE_PARCEL(input.readStrongBinder, &binder);
+        SAFE_PARCEL(input->readStrongBinder, &binder);
         excludeHandles.emplace(binder);
     }
 
-    SAFE_PARCEL(input.readBool, &childrenOnly);
+    SAFE_PARCEL(input->readBool, &childrenOnly);
+    return NO_ERROR;
+}
+
+}; // namespace gui
+
+ReleaseCallbackId BufferData::generateReleaseCallbackId() const {
+    uint64_t bufferId;
+    if (buffer) {
+        bufferId = buffer->getId();
+    } else {
+        bufferId = cachedBuffer.id;
+    }
+    return {bufferId, frameNumber};
+}
+
+status_t BufferData::writeToParcel(Parcel* output) const {
+    SAFE_PARCEL(output->writeInt32, flags.get());
+
+    if (buffer) {
+        SAFE_PARCEL(output->writeBool, true);
+        SAFE_PARCEL(output->write, *buffer);
+    } else {
+        SAFE_PARCEL(output->writeBool, false);
+    }
+
+    if (acquireFence) {
+        SAFE_PARCEL(output->writeBool, true);
+        SAFE_PARCEL(output->write, *acquireFence);
+    } else {
+        SAFE_PARCEL(output->writeBool, false);
+    }
+
+    SAFE_PARCEL(output->writeUint64, frameNumber);
+    SAFE_PARCEL(output->writeStrongBinder, IInterface::asBinder(releaseBufferListener));
+    SAFE_PARCEL(output->writeStrongBinder, releaseBufferEndpoint);
+
+    SAFE_PARCEL(output->writeStrongBinder, cachedBuffer.token.promote());
+    SAFE_PARCEL(output->writeUint64, cachedBuffer.id);
+    SAFE_PARCEL(output->writeBool, hasBarrier);
+    SAFE_PARCEL(output->writeUint64, barrierFrameNumber);
+
+    return NO_ERROR;
+}
+
+status_t BufferData::readFromParcel(const Parcel* input) {
+    int32_t tmpInt32;
+    SAFE_PARCEL(input->readInt32, &tmpInt32);
+    flags = Flags<BufferDataChange>(tmpInt32);
+
+    bool tmpBool = false;
+    SAFE_PARCEL(input->readBool, &tmpBool);
+    if (tmpBool) {
+        buffer = new GraphicBuffer();
+        SAFE_PARCEL(input->read, *buffer);
+    }
+
+    SAFE_PARCEL(input->readBool, &tmpBool);
+    if (tmpBool) {
+        acquireFence = new Fence();
+        SAFE_PARCEL(input->read, *acquireFence);
+    }
+
+    SAFE_PARCEL(input->readUint64, &frameNumber);
+
+    sp<IBinder> tmpBinder = nullptr;
+    SAFE_PARCEL(input->readNullableStrongBinder, &tmpBinder);
+    if (tmpBinder) {
+        releaseBufferListener = checked_interface_cast<ITransactionCompletedListener>(tmpBinder);
+    }
+    SAFE_PARCEL(input->readNullableStrongBinder, &releaseBufferEndpoint);
+
+    tmpBinder = nullptr;
+    SAFE_PARCEL(input->readNullableStrongBinder, &tmpBinder);
+    cachedBuffer.token = tmpBinder;
+    SAFE_PARCEL(input->readUint64, &cachedBuffer.id);
+
+    SAFE_PARCEL(input->readBool, &hasBarrier);
+    SAFE_PARCEL(input->readUint64, &barrierFrameNumber);
+
     return NO_ERROR;
 }
 
diff --git a/libs/gui/Surface.cpp b/libs/gui/Surface.cpp
index 353a91d..20c4146 100644
--- a/libs/gui/Surface.cpp
+++ b/libs/gui/Surface.cpp
@@ -1846,9 +1846,10 @@
     ATRACE_CALL();
     auto frameTimelineVsyncId = static_cast<int64_t>(va_arg(args, int64_t));
     auto inputEventId = static_cast<int32_t>(va_arg(args, int32_t));
+    auto startTimeNanos = static_cast<int64_t>(va_arg(args, int64_t));
 
     ALOGV("Surface::%s", __func__);
-    return setFrameTimelineInfo({frameTimelineVsyncId, inputEventId});
+    return setFrameTimelineInfo({frameTimelineVsyncId, inputEventId, startTimeNanos});
 }
 
 bool Surface::transformToDisplayInverse() const {
diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp
index 725ea65..52a22a7 100644
--- a/libs/gui/SurfaceComposerClient.cpp
+++ b/libs/gui/SurfaceComposerClient.cpp
@@ -46,13 +46,16 @@
 #include <ui/DynamicDisplayInfo.h>
 
 #include <private/gui/ComposerService.h>
+#include <private/gui/ComposerServiceAIDL.h>
 
 // This server size should always be smaller than the server cache size
 #define BUFFER_CACHE_MAX_SIZE 64
 
 namespace android {
 
+using aidl::android::hardware::graphics::common::DisplayDecorationSupport;
 using gui::FocusRequest;
+using gui::IRegionSamplingListener;
 using gui::WindowInfo;
 using gui::WindowInfoHandle;
 using gui::WindowInfosListener;
@@ -60,6 +63,15 @@
 // ---------------------------------------------------------------------------
 
 ANDROID_SINGLETON_STATIC_INSTANCE(ComposerService);
+ANDROID_SINGLETON_STATIC_INSTANCE(ComposerServiceAIDL);
+
+namespace {
+// Initialize transaction id counter used to generate transaction ids
+std::atomic<uint32_t> idCounter = 0;
+int64_t generateId() {
+    return (((int64_t)getpid()) << 32) | ++idCounter;
+}
+} // namespace
 
 ComposerService::ComposerService()
 : Singleton<ComposerService>() {
@@ -110,6 +122,52 @@
     mDeathObserver = nullptr;
 }
 
+ComposerServiceAIDL::ComposerServiceAIDL() : Singleton<ComposerServiceAIDL>() {
+    std::scoped_lock lock(mMutex);
+    connectLocked();
+}
+
+bool ComposerServiceAIDL::connectLocked() {
+    const String16 name("SurfaceFlingerAIDL");
+    mComposerService = waitForService<gui::ISurfaceComposer>(name);
+    if (mComposerService == nullptr) {
+        return false; // fatal error or permission problem
+    }
+
+    // Create the death listener.
+    class DeathObserver : public IBinder::DeathRecipient {
+        ComposerServiceAIDL& mComposerService;
+        virtual void binderDied(const wp<IBinder>& who) {
+            ALOGW("ComposerService aidl remote (surfaceflinger) died [%p]", who.unsafe_get());
+            mComposerService.composerServiceDied();
+        }
+
+    public:
+        explicit DeathObserver(ComposerServiceAIDL& mgr) : mComposerService(mgr) {}
+    };
+
+    mDeathObserver = new DeathObserver(*const_cast<ComposerServiceAIDL*>(this));
+    IInterface::asBinder(mComposerService)->linkToDeath(mDeathObserver);
+    return true;
+}
+
+/*static*/ sp<gui::ISurfaceComposer> ComposerServiceAIDL::getComposerService() {
+    ComposerServiceAIDL& instance = ComposerServiceAIDL::getInstance();
+    std::scoped_lock lock(instance.mMutex);
+    if (instance.mComposerService == nullptr) {
+        if (ComposerServiceAIDL::getInstance().connectLocked()) {
+            ALOGD("ComposerServiceAIDL reconnected");
+        }
+    }
+    return instance.mComposerService;
+}
+
+void ComposerServiceAIDL::composerServiceDied() {
+    std::scoped_lock lock(mMutex);
+    mComposerService = nullptr;
+    mDeathObserver = nullptr;
+}
+
 class DefaultComposerClient: public Singleton<DefaultComposerClient> {
     Mutex mLock;
     sp<SurfaceComposerClient> mClient;
@@ -214,12 +272,6 @@
     mReleaseBufferCallbacks[callbackId] = listener;
 }
 
-void TransactionCompletedListener::removeReleaseBufferCallback(
-        const ReleaseCallbackId& callbackId) {
-    std::scoped_lock<std::mutex> lock(mMutex);
-    mReleaseBufferCallbacks.erase(callbackId);
-}
-
 void TransactionCompletedListener::addSurfaceStatsListener(void* context, void* cookie,
         sp<SurfaceControl> surfaceControl, SurfaceStatsCallback listener) {
     std::scoped_lock<std::recursive_mutex> lock(mSurfaceStatsListenerMutex);
@@ -293,10 +345,10 @@
                 surfaceControlStats
                         .emplace_back(callbacksMap[callbackId]
                                               .surfaceControls[surfaceStats.surfaceControl],
-                                      transactionStats.latchTime, surfaceStats.acquireTime,
+                                      transactionStats.latchTime, surfaceStats.acquireTimeOrFence,
                                       transactionStats.presentFence,
                                       surfaceStats.previousReleaseFence, surfaceStats.transformHint,
-                                      surfaceStats.eventStats, surfaceStats.currentMaxAcquiredBufferCount);
+                                      surfaceStats.eventStats);
             }
 
             callbackFunction(transactionStats.latchTime, transactionStats.presentFence,
@@ -318,10 +370,10 @@
                 surfaceControlStats
                         .emplace_back(callbacksMap[callbackId]
                                               .surfaceControls[surfaceStats.surfaceControl],
-                                      transactionStats.latchTime, surfaceStats.acquireTime,
+                                      transactionStats.latchTime, surfaceStats.acquireTimeOrFence,
                                       transactionStats.presentFence,
                                       surfaceStats.previousReleaseFence, surfaceStats.transformHint,
-                                      surfaceStats.eventStats, surfaceStats.currentMaxAcquiredBufferCount);
+                                      surfaceStats.eventStats);
                 if (callbacksMap[callbackId].surfaceControls[surfaceStats.surfaceControl]) {
                     callbacksMap[callbackId]
                             .surfaceControls[surfaceStats.surfaceControl]
@@ -343,7 +395,6 @@
                                  surfaceStats.previousReleaseFence
                                          ? surfaceStats.previousReleaseFence
                                          : Fence::NO_FENCE,
-                                 surfaceStats.transformHint,
                                  surfaceStats.currentMaxAcquiredBufferCount);
                     }
                 }
@@ -359,6 +410,10 @@
             // through all until the SC is found.
             int32_t layerId = -1;
             for (auto callbackId : transactionStats.callbackIds) {
+                if (callbackId.type != CallbackId::Type::ON_COMPLETE) {
+                    // We only want to run the stats callback for ON_COMPLETE
+                    continue;
+                }
                 sp<SurfaceControl> sc =
                         callbacksMap[callbackId].surfaceControls[surfaceStats.surfaceControl];
                 if (sc != nullptr) {
@@ -367,7 +422,7 @@
                 }
             }
 
-            {
+            if (layerId != -1) {
                 // Acquire surface stats listener lock such that we guarantee that after calling
                 // unregister, there won't be any further callback.
                 std::scoped_lock<std::recursive_mutex> lock(mSurfaceStatsListenerMutex);
@@ -389,7 +444,7 @@
 }
 
 void TransactionCompletedListener::onReleaseBuffer(ReleaseCallbackId callbackId,
-                                                   sp<Fence> releaseFence, uint32_t transformHint,
+                                                   sp<Fence> releaseFence,
                                                    uint32_t currentMaxAcquiredBufferCount) {
     ReleaseBufferCallback callback;
     {
@@ -401,7 +456,11 @@
               callbackId.to_string().c_str());
         return;
     }
-    callback(callbackId, releaseFence, transformHint, currentMaxAcquiredBufferCount);
+    std::optional<uint32_t> optionalMaxAcquiredBufferCount =
+            currentMaxAcquiredBufferCount == UINT_MAX
+            ? std::nullopt
+            : std::make_optional<uint32_t>(currentMaxAcquiredBufferCount);
+    callback(callbackId, releaseFence, optionalMaxAcquiredBufferCount);
 }
 
 ReleaseBufferCallback TransactionCompletedListener::popReleaseBufferCallbackLocked(
@@ -416,6 +475,14 @@
     return callback;
 }
 
+void TransactionCompletedListener::removeReleaseBufferCallback(
+        const ReleaseCallbackId& callbackId) {
+    {
+        std::scoped_lock<std::mutex> lock(mMutex);
+        popReleaseBufferCallbackLocked(callbackId);
+    }
+}
+
 // ---------------------------------------------------------------------------
 
 void removeDeadBufferCallback(void* /*context*/, uint64_t graphicBufferId);
@@ -525,10 +592,6 @@
 
 // ---------------------------------------------------------------------------
 
-// Initialize transaction id counter used to generate transaction ids
-// Transactions will start counting at 1, 0 is used for invalid transactions
-std::atomic<uint32_t> SurfaceComposerClient::Transaction::idCounter = 1;
-
 SurfaceComposerClient::Transaction::Transaction() {
     mId = generateId();
 }
@@ -551,6 +614,13 @@
     mListenerCallbacks = other.mListenerCallbacks;
 }
 
+void SurfaceComposerClient::Transaction::sanitize() {
+    for (auto & [handle, composerState] : mComposerStates) {
+        composerState.state.sanitize(0 /* permissionMask */);
+    }
+    mInputWindowCommands.clear();
+}
+
 std::unique_ptr<SurfaceComposerClient::Transaction>
 SurfaceComposerClient::Transaction::createFromParcel(const Parcel* parcel) {
     auto transaction = std::make_unique<Transaction>();
@@ -560,9 +630,6 @@
     return nullptr;
 }
 
-int64_t SurfaceComposerClient::Transaction::generateId() {
-    return (((int64_t)getpid()) << 32) | idCounter++;
-}
 
 status_t SurfaceComposerClient::Transaction::readFromParcel(const Parcel* parcel) {
     const uint32_t forceSynchronous = parcel->readUint32();
@@ -635,7 +702,6 @@
         if (composerState.read(*parcel) == BAD_VALUE) {
             return BAD_VALUE;
         }
-
         composerStates[surfaceControlHandle] = composerState;
     }
 
@@ -712,11 +778,34 @@
     return NO_ERROR;
 }
 
+void SurfaceComposerClient::Transaction::releaseBufferIfOverwriting(const layer_state_t& state) {
+    if (!(state.what & layer_state_t::eBufferChanged)) {
+        return;
+    }
+
+    auto listener = state.bufferData->releaseBufferListener;
+    sp<Fence> fence =
+            state.bufferData->acquireFence ? state.bufferData->acquireFence : Fence::NO_FENCE;
+    if (state.bufferData->releaseBufferEndpoint ==
+        IInterface::asBinder(TransactionCompletedListener::getIInstance())) {
+        // if the callback is in process, run on a different thread to avoid any lock contigency
+        // issues in the client.
+        SurfaceComposerClient::getDefault()
+                ->mReleaseCallbackThread
+                .addReleaseCallback(state.bufferData->generateReleaseCallbackId(), fence);
+    } else {
+        listener->onReleaseBuffer(state.bufferData->generateReleaseCallbackId(), fence, UINT_MAX);
+    }
+}
+
 SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::merge(Transaction&& other) {
     for (auto const& [handle, composerState] : other.mComposerStates) {
         if (mComposerStates.count(handle) == 0) {
             mComposerStates[handle] = composerState;
         } else {
+            if (composerState.state.what & layer_state_t::eBufferChanged) {
+                releaseBufferIfOverwriting(mComposerStates[handle].state);
+            }
             mComposerStates[handle].state.merge(composerState.state);
         }
     }
@@ -792,7 +881,7 @@
 
     sp<IBinder> applyToken = IInterface::asBinder(TransactionCompletedListener::getIInstance());
     sf->setTransactionState(FrameTimelineInfo{}, {}, {}, 0, applyToken, {}, systemTime(), true,
-                            uncacheBuffer, false, {}, 0 /* Undefined transactionId */);
+                            uncacheBuffer, false, {}, generateId());
 }
 
 void SurfaceComposerClient::Transaction::cacheBuffers() {
@@ -805,7 +894,8 @@
         layer_state_t* s = &(mComposerStates[handle].state);
         if (!(s->what & layer_state_t::eBufferChanged)) {
             continue;
-        } else if (s->what & layer_state_t::eCachedBufferChanged) {
+        } else if (s->bufferData &&
+                   s->bufferData->flags.test(BufferData::BufferDataChange::cachedBufferChanged)) {
             // If eBufferChanged and eCachedBufferChanged are both trued then that means
             // we already cached the buffer in a previous call to cacheBuffers, perhaps
             // from writeToParcel on a Transaction that was merged in to this one.
@@ -814,23 +904,22 @@
 
         // Don't try to cache a null buffer. Sending null buffers is cheap so we shouldn't waste
         // time trying to cache them.
-        if (!s->buffer) {
+        if (!s->bufferData || !s->bufferData->buffer) {
             continue;
         }
 
         uint64_t cacheId = 0;
-        status_t ret = BufferCache::getInstance().getCacheId(s->buffer, &cacheId);
+        status_t ret = BufferCache::getInstance().getCacheId(s->bufferData->buffer, &cacheId);
         if (ret == NO_ERROR) {
             // Cache-hit. Strip the buffer and send only the id.
-            s->what &= ~static_cast<uint64_t>(layer_state_t::eBufferChanged);
-            s->buffer = nullptr;
+            s->bufferData->buffer = nullptr;
         } else {
             // Cache-miss. Include the buffer and send the new cacheId.
-            cacheId = BufferCache::getInstance().cache(s->buffer);
+            cacheId = BufferCache::getInstance().cache(s->bufferData->buffer);
         }
-        s->what |= layer_state_t::eCachedBufferChanged;
-        s->cachedBuffer.token = BufferCache::getInstance().getToken();
-        s->cachedBuffer.id = cacheId;
+        s->bufferData->flags |= BufferData::BufferDataChange::cachedBufferChanged;
+        s->bufferData->cachedBuffer.token = BufferCache::getInstance().getToken();
+        s->bufferData->cachedBuffer.id = cacheId;
 
         // If we have more buffers than the size of the cache, we should stop caching so we don't
         // evict other buffers in this transaction
@@ -841,7 +930,7 @@
     }
 }
 
-status_t SurfaceComposerClient::Transaction::apply(bool synchronous) {
+status_t SurfaceComposerClient::Transaction::apply(bool synchronous, bool oneWay) {
     if (mStatus != NO_ERROR) {
         return mStatus;
     }
@@ -895,6 +984,14 @@
     if (mAnimation) {
         flags |= ISurfaceComposer::eAnimation;
     }
+    if (oneWay) {
+      if (mForceSynchronous) {
+          ALOGE("Transaction attempted to set synchronous and one way at the same time"
+                " this is an invalid request. Synchronous will win for safety");
+      } else {
+          flags |= ISurfaceComposer::eOneWay;
+      }
+    }
 
     // If both mEarlyWakeupStart and mEarlyWakeupEnd are set
     // it is equivalent for none
@@ -1071,7 +1168,8 @@
     }
     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::eEnableBackpressure) ||
+        (mask & layer_state_t::eLayerIsDisplayDecoration)) {
         s->what |= layer_state_t::eFlagsChanged;
     }
     s->flags &= ~mask;
@@ -1112,7 +1210,7 @@
 }
 
 SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setLayerStack(
-        const sp<SurfaceControl>& sc, uint32_t layerStack) {
+        const sp<SurfaceControl>& sc, ui::LayerStack layerStack) {
     layer_state_t* s = getLayerState(sc);
     if (!s) {
         mStatus = BAD_INDEX;
@@ -1288,73 +1386,89 @@
     return *this;
 }
 
+std::shared_ptr<BufferData> SurfaceComposerClient::Transaction::getAndClearBuffer(
+        const sp<SurfaceControl>& sc) {
+    layer_state_t* s = getLayerState(sc);
+    if (!s) {
+        return nullptr;
+    }
+    if (!(s->what & layer_state_t::eBufferChanged)) {
+        return nullptr;
+    }
+
+    std::shared_ptr<BufferData> bufferData = std::move(s->bufferData);
+
+    TransactionCompletedListener::getInstance()->removeReleaseBufferCallback(
+            bufferData->generateReleaseCallbackId());
+    s->what &= ~layer_state_t::eBufferChanged;
+    s->bufferData = nullptr;
+
+    mContainsBuffer = false;
+    return bufferData;
+}
+
+SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setBufferHasBarrier(
+        const sp<SurfaceControl>& sc, uint64_t barrierFrameNumber) {
+    layer_state_t* s = getLayerState(sc);
+    if (!s) {
+        mStatus = BAD_INDEX;
+        return *this;
+    }
+    s->bufferData->hasBarrier = true;
+    s->bufferData->barrierFrameNumber = barrierFrameNumber;
+    return *this;
+}
+
 SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setBuffer(
-        const sp<SurfaceControl>& sc, const sp<GraphicBuffer>& buffer, const ReleaseCallbackId& id,
+        const sp<SurfaceControl>& sc, const sp<GraphicBuffer>& buffer,
+        const std::optional<sp<Fence>>& fence, const std::optional<uint64_t>& optFrameNumber,
         ReleaseBufferCallback callback) {
     layer_state_t* s = getLayerState(sc);
     if (!s) {
         mStatus = BAD_INDEX;
         return *this;
     }
-    removeReleaseBufferCallback(s);
-    s->what |= layer_state_t::eBufferChanged;
-    s->buffer = buffer;
-    s->releaseBufferEndpoint = IInterface::asBinder(TransactionCompletedListener::getIInstance());
+
+    releaseBufferIfOverwriting(*s);
+
+    std::shared_ptr<BufferData> bufferData = std::make_shared<BufferData>();
+    bufferData->buffer = buffer;
+    uint64_t frameNumber = sc->resolveFrameNumber(optFrameNumber);
+    bufferData->frameNumber = frameNumber;
+    bufferData->flags |= BufferData::BufferDataChange::frameNumberChanged;
+    if (fence) {
+        bufferData->acquireFence = *fence;
+        bufferData->flags |= BufferData::BufferDataChange::fenceChanged;
+    }
+    bufferData->releaseBufferEndpoint =
+            IInterface::asBinder(TransactionCompletedListener::getIInstance());
     if (mIsAutoTimestamp) {
         mDesiredPresentTime = systemTime();
     }
-    setReleaseBufferCallback(s, id, callback);
-
+    setReleaseBufferCallback(bufferData.get(), callback);
+    s->what |= layer_state_t::eBufferChanged;
+    s->bufferData = std::move(bufferData);
     registerSurfaceControlForCallback(sc);
 
     mContainsBuffer = true;
     return *this;
 }
 
-void SurfaceComposerClient::Transaction::removeReleaseBufferCallback(layer_state_t* s) {
-    if (!s->releaseBufferListener) {
-        return;
-    }
-
-    s->what &= ~static_cast<uint64_t>(layer_state_t::eReleaseBufferListenerChanged);
-    s->releaseBufferListener = nullptr;
-    auto listener = TransactionCompletedListener::getInstance();
-    listener->removeReleaseBufferCallback(s->releaseCallbackId);
-    s->releaseCallbackId = ReleaseCallbackId::INVALID_ID;
-}
-
-void SurfaceComposerClient::Transaction::setReleaseBufferCallback(layer_state_t* s,
-                                                                  const ReleaseCallbackId& id,
+void SurfaceComposerClient::Transaction::setReleaseBufferCallback(BufferData* bufferData,
                                                                   ReleaseBufferCallback callback) {
     if (!callback) {
         return;
     }
 
-    if (!s->buffer) {
+    if (!bufferData->buffer) {
         ALOGW("Transaction::setReleaseBufferCallback"
               "ignored trying to set a callback on a null buffer.");
         return;
     }
 
-    s->what |= layer_state_t::eReleaseBufferListenerChanged;
-    s->releaseBufferListener = TransactionCompletedListener::getIInstance();
-    s->releaseCallbackId = id;
+    bufferData->releaseBufferListener = TransactionCompletedListener::getIInstance();
     auto listener = TransactionCompletedListener::getInstance();
-    listener->setReleaseBufferCallback(id, callback);
-}
-
-SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setAcquireFence(
-        const sp<SurfaceControl>& sc, const sp<Fence>& fence) {
-    layer_state_t* s = getLayerState(sc);
-    if (!s) {
-        mStatus = BAD_INDEX;
-        return *this;
-    }
-    s->what |= layer_state_t::eAcquireFenceChanged;
-    s->acquireFence = fence;
-
-    registerSurfaceControlForCallback(sc);
-    return *this;
+    listener->setReleaseBufferCallback(bufferData->generateReleaseCallbackId(), callback);
 }
 
 SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setDataspace(
@@ -1506,20 +1620,6 @@
     return *this;
 }
 
-SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setFrameNumber(
-        const sp<SurfaceControl>& sc, uint64_t frameNumber) {
-    layer_state_t* s = getLayerState(sc);
-    if (!s) {
-        mStatus = BAD_INDEX;
-        return *this;
-    }
-
-    s->what |= layer_state_t::eFrameNumberChanged;
-    s->frameNumber = frameNumber;
-
-    return *this;
-}
-
 SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setInputWindowInfo(
         const sp<SurfaceControl>& sc, const WindowInfo& info) {
     layer_state_t* s = getLayerState(sc);
@@ -1792,7 +1892,7 @@
 }
 
 void SurfaceComposerClient::Transaction::setDisplayLayerStack(const sp<IBinder>& token,
-        uint32_t layerStack) {
+                                                              ui::LayerStack layerStack) {
     DisplayState& s(getDisplayState(token));
     s.layerStack = layerStack;
     s.what |= DisplayState::eLayerStackChanged;
@@ -2049,6 +2149,23 @@
     return ComposerService::getComposerService()->setActiveColorMode(display, colorMode);
 }
 
+status_t SurfaceComposerClient::getBootDisplayModeSupport(bool* support) {
+    return ComposerService::getComposerService()->getBootDisplayModeSupport(support);
+}
+
+status_t SurfaceComposerClient::setBootDisplayMode(const sp<IBinder>& display,
+                                                   ui::DisplayModeId displayModeId) {
+    return ComposerService::getComposerService()->setBootDisplayMode(display, displayModeId);
+}
+
+status_t SurfaceComposerClient::clearBootDisplayMode(const sp<IBinder>& display) {
+    return ComposerService::getComposerService()->clearBootDisplayMode(display);
+}
+
+status_t SurfaceComposerClient::setOverrideFrameRate(uid_t uid, float frameRate) {
+    return ComposerService::getComposerService()->setOverrideFrameRate(uid, frameRate);
+}
+
 void SurfaceComposerClient::setAutoLowLatencyMode(const sp<IBinder>& display, bool on) {
     ComposerService::getComposerService()->setAutoLowLatencyMode(display, on);
 }
@@ -2190,14 +2307,23 @@
                                                                           lightRadius);
 }
 
+std::optional<DisplayDecorationSupport> SurfaceComposerClient::getDisplayDecorationSupport(
+        const sp<IBinder>& displayToken) {
+    std::optional<DisplayDecorationSupport> support;
+    ComposerService::getComposerService()->getDisplayDecorationSupport(displayToken, &support);
+    return support;
+}
+
 int SurfaceComposerClient::getGPUContextPriority() {
     return ComposerService::getComposerService()->getGPUContextPriority();
 }
 
 status_t SurfaceComposerClient::addWindowInfosListener(
-        const sp<WindowInfosListener>& windowInfosListener) {
+        const sp<WindowInfosListener>& windowInfosListener,
+        std::pair<std::vector<gui::WindowInfo>, std::vector<gui::DisplayInfo>>* outInitialInfo) {
     return WindowInfosListenerReporter::getInstance()
-            ->addWindowInfosListener(windowInfosListener, ComposerService::getComposerService());
+            ->addWindowInfosListener(windowInfosListener, ComposerService::getComposerService(),
+                                     outInitialInfo);
 }
 
 status_t SurfaceComposerClient::removeWindowInfosListener(
@@ -2210,26 +2336,68 @@
 
 status_t ScreenshotClient::captureDisplay(const DisplayCaptureArgs& captureArgs,
                                           const sp<IScreenCaptureListener>& captureListener) {
-    sp<ISurfaceComposer> s(ComposerService::getComposerService());
+    sp<gui::ISurfaceComposer> s(ComposerServiceAIDL::getComposerService());
     if (s == nullptr) return NO_INIT;
 
-    return s->captureDisplay(captureArgs, captureListener);
+    binder::Status status = s->captureDisplay(captureArgs, captureListener);
+    return status.transactionError();
 }
 
-status_t ScreenshotClient::captureDisplay(uint64_t displayOrLayerStack,
+status_t ScreenshotClient::captureDisplay(DisplayId displayId,
                                           const sp<IScreenCaptureListener>& captureListener) {
-    sp<ISurfaceComposer> s(ComposerService::getComposerService());
+    sp<gui::ISurfaceComposer> s(ComposerServiceAIDL::getComposerService());
     if (s == nullptr) return NO_INIT;
 
-    return s->captureDisplay(displayOrLayerStack, captureListener);
+    binder::Status status = s->captureDisplayById(displayId.value, captureListener);
+    return status.transactionError();
 }
 
 status_t ScreenshotClient::captureLayers(const LayerCaptureArgs& captureArgs,
                                          const sp<IScreenCaptureListener>& captureListener) {
-    sp<ISurfaceComposer> s(ComposerService::getComposerService());
+    sp<gui::ISurfaceComposer> s(ComposerServiceAIDL::getComposerService());
     if (s == nullptr) return NO_INIT;
 
-    return s->captureLayers(captureArgs, captureListener);
+    binder::Status status = s->captureLayers(captureArgs, captureListener);
+    return status.transactionError();
+}
+
+// ---------------------------------------------------------------------------------
+
+void ReleaseCallbackThread::addReleaseCallback(const ReleaseCallbackId callbackId,
+                                               sp<Fence> releaseFence) {
+    std::scoped_lock<std::mutex> lock(mMutex);
+    if (!mStarted) {
+        mThread = std::thread(&ReleaseCallbackThread::threadMain, this);
+        mStarted = true;
+    }
+
+    mCallbackInfos.emplace(callbackId, std::move(releaseFence));
+    mReleaseCallbackPending.notify_one();
+}
+
+void ReleaseCallbackThread::threadMain() {
+    const auto listener = TransactionCompletedListener::getInstance();
+    std::queue<std::tuple<const ReleaseCallbackId, const sp<Fence>>> callbackInfos;
+    while (true) {
+        {
+            std::unique_lock<std::mutex> lock(mMutex);
+            callbackInfos = std::move(mCallbackInfos);
+            mCallbackInfos = {};
+        }
+
+        while (!callbackInfos.empty()) {
+            auto [callbackId, releaseFence] = callbackInfos.front();
+            listener->onReleaseBuffer(callbackId, std::move(releaseFence), UINT_MAX);
+            callbackInfos.pop();
+        }
+
+        {
+            std::unique_lock<std::mutex> lock(mMutex);
+            if (mCallbackInfos.size() == 0) {
+                mReleaseCallbackPending.wait(lock);
+            }
+        }
+    }
 }
 
 } // namespace android
diff --git a/libs/gui/SurfaceControl.cpp b/libs/gui/SurfaceControl.cpp
index 6529a4e..654fb33 100644
--- a/libs/gui/SurfaceControl.cpp
+++ b/libs/gui/SurfaceControl.cpp
@@ -70,6 +70,7 @@
     mLayerId = other->mLayerId;
     mWidth = other->mWidth;
     mHeight = other->mHeight;
+    mFormat = other->mFormat;
     mCreateFlags = other->mCreateFlags;
 }
 
@@ -280,5 +281,18 @@
     return this;
 }
 
+uint64_t SurfaceControl::resolveFrameNumber(const std::optional<uint64_t>& frameNumber) {
+    if (frameNumber.has_value()) {
+        auto ret = frameNumber.value();
+        // Set the fallback to something far enough ahead that in the unlikely event of mixed
+        // "real" frame numbers and fallback frame numbers, we still won't collide in any
+        // meaningful capacity
+        mFallbackFrameNumber = ret + 100;
+        return ret;
+    } else {
+        return mFallbackFrameNumber++;
+    }
+}
+
 // ----------------------------------------------------------------------------
 }; // namespace android
diff --git a/libs/gui/VsyncEventData.cpp b/libs/gui/VsyncEventData.cpp
new file mode 100644
index 0000000..23f0921
--- /dev/null
+++ b/libs/gui/VsyncEventData.cpp
@@ -0,0 +1,70 @@
+/*
+ * 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 "gui/VsyncEventData.h"
+#include <gui/DisplayEventReceiver.h>
+#include <private/gui/ParcelUtils.h>
+#include <utils/Log.h>
+#include <utils/Looper.h>
+#include <cstdint>
+
+namespace android::gui {
+
+int64_t VsyncEventData::preferredVsyncId() const {
+    return frameTimelines[preferredFrameTimelineIndex].vsyncId;
+}
+
+int64_t VsyncEventData::preferredDeadlineTimestamp() const {
+    return frameTimelines[preferredFrameTimelineIndex].deadlineTimestamp;
+}
+
+int64_t VsyncEventData::preferredExpectedPresentationTime() const {
+    return frameTimelines[preferredFrameTimelineIndex].expectedPresentationTime;
+}
+
+status_t ParcelableVsyncEventData::readFromParcel(const Parcel* parcel) {
+    if (parcel == nullptr) {
+        ALOGE("%s: Null parcel", __func__);
+        return BAD_VALUE;
+    }
+
+    SAFE_PARCEL(parcel->readInt64, &vsync.frameInterval);
+
+    uint64_t uintPreferredFrameTimelineIndex;
+    SAFE_PARCEL(parcel->readUint64, &uintPreferredFrameTimelineIndex);
+    vsync.preferredFrameTimelineIndex = static_cast<size_t>(uintPreferredFrameTimelineIndex);
+
+    for (int i = 0; i < VsyncEventData::kFrameTimelinesLength; i++) {
+        SAFE_PARCEL(parcel->readInt64, &vsync.frameTimelines[i].vsyncId);
+        SAFE_PARCEL(parcel->readInt64, &vsync.frameTimelines[i].deadlineTimestamp);
+        SAFE_PARCEL(parcel->readInt64, &vsync.frameTimelines[i].expectedPresentationTime);
+    }
+
+    return OK;
+}
+status_t ParcelableVsyncEventData::writeToParcel(Parcel* parcel) const {
+    SAFE_PARCEL(parcel->writeInt64, vsync.frameInterval);
+    SAFE_PARCEL(parcel->writeUint64, vsync.preferredFrameTimelineIndex);
+    for (int i = 0; i < VsyncEventData::kFrameTimelinesLength; i++) {
+        SAFE_PARCEL(parcel->writeInt64, vsync.frameTimelines[i].vsyncId);
+        SAFE_PARCEL(parcel->writeInt64, vsync.frameTimelines[i].deadlineTimestamp);
+        SAFE_PARCEL(parcel->writeInt64, vsync.frameTimelines[i].expectedPresentationTime);
+    }
+
+    return OK;
+}
+
+}; // namespace android::gui
diff --git a/libs/gui/WindowInfo.cpp b/libs/gui/WindowInfo.cpp
index b2ef7aa..2312a8c 100644
--- a/libs/gui/WindowInfo.cpp
+++ b/libs/gui/WindowInfo.cpp
@@ -18,7 +18,6 @@
 #define LOG_TAG "WindowInfo"
 #define LOG_NDEBUG 0
 
-#include <android-base/stringprintf.h>
 #include <binder/Parcel.h>
 #include <gui/WindowInfo.h>
 
@@ -27,6 +26,14 @@
 namespace android::gui {
 
 // --- WindowInfo ---
+void WindowInfo::setInputConfig(Flags<InputConfig> config, bool value) {
+    if (value) {
+        inputConfig |= config;
+        return;
+    }
+    inputConfig &= ~config;
+}
+
 void WindowInfo::addTouchableRegion(const Rect& region) {
     touchableRegion.orSelf(region);
 }
@@ -40,30 +47,36 @@
 }
 
 bool WindowInfo::supportsSplitTouch() const {
-    return flags.test(Flag::SPLIT_TOUCH);
+    return !inputConfig.test(InputConfig::PREVENT_SPLITTING);
+}
+
+bool WindowInfo::isSpy() const {
+    return inputConfig.test(InputConfig::SPY);
+}
+
+bool WindowInfo::interceptsStylus() const {
+    return inputConfig.test(InputConfig::INTERCEPTS_STYLUS);
 }
 
 bool WindowInfo::overlaps(const WindowInfo* other) const {
-    return frameLeft < other->frameRight && frameRight > other->frameLeft &&
+    const bool nonEmpty = (frameRight - frameLeft > 0) || (frameBottom - frameTop > 0);
+    return nonEmpty && frameLeft < other->frameRight && frameRight > other->frameLeft &&
             frameTop < other->frameBottom && frameBottom > other->frameTop;
 }
 
 bool WindowInfo::operator==(const WindowInfo& info) const {
-    return info.token == token && info.id == id && info.name == name && info.flags == flags &&
-            info.type == type && info.dispatchingTimeout == dispatchingTimeout &&
-            info.frameLeft == frameLeft && info.frameTop == frameTop &&
-            info.frameRight == frameRight && info.frameBottom == frameBottom &&
-            info.surfaceInset == surfaceInset && info.globalScaleFactor == globalScaleFactor &&
-            info.transform == transform && info.displayOrientation == displayOrientation &&
-            info.displayWidth == displayWidth && info.displayHeight == displayHeight &&
-            info.touchableRegion.hasSameRects(touchableRegion) && info.visible == visible &&
-            info.trustedOverlay == trustedOverlay && info.focusable == focusable &&
-            info.touchOcclusionMode == touchOcclusionMode && info.hasWallpaper == hasWallpaper &&
-            info.paused == paused && info.ownerPid == ownerPid && info.ownerUid == ownerUid &&
-            info.packageName == packageName && info.inputFeatures == inputFeatures &&
-            info.displayId == displayId && info.portalToDisplayId == portalToDisplayId &&
+    return info.token == token && info.id == id && info.name == name &&
+            info.dispatchingTimeout == dispatchingTimeout && info.frameLeft == frameLeft &&
+            info.frameTop == frameTop && info.frameRight == frameRight &&
+            info.frameBottom == frameBottom && info.surfaceInset == surfaceInset &&
+            info.globalScaleFactor == globalScaleFactor && info.transform == transform &&
+            info.touchableRegion.hasSameRects(touchableRegion) &&
+            info.touchOcclusionMode == touchOcclusionMode && info.ownerPid == ownerPid &&
+            info.ownerUid == ownerUid && info.packageName == packageName &&
+            info.inputConfig == inputConfig && info.displayId == displayId &&
             info.replaceTouchableRegionWithCrop == replaceTouchableRegionWithCrop &&
-            info.applicationInfo == applicationInfo;
+            info.applicationInfo == applicationInfo && info.layoutParamsType == layoutParamsType &&
+            info.layoutParamsFlags == layoutParamsFlags;
 }
 
 status_t WindowInfo::writeToParcel(android::Parcel* parcel) const {
@@ -77,13 +90,17 @@
     }
     parcel->writeInt32(1);
 
+    // Ensure that the size of the flags that we use is 32 bits for writing into the parcel.
+    static_assert(sizeof(inputConfig) == 4u);
+
     // clang-format off
     status_t status = parcel->writeStrongBinder(token) ?:
         parcel->writeInt64(dispatchingTimeout.count()) ?:
         parcel->writeInt32(id) ?:
         parcel->writeUtf8AsUtf16(name) ?:
-        parcel->writeInt32(flags.get()) ?:
-        parcel->writeInt32(static_cast<std::underlying_type_t<WindowInfo::Type>>(type)) ?:
+        parcel->writeInt32(layoutParamsFlags.get()) ?:
+        parcel->writeInt32(
+                static_cast<std::underlying_type_t<WindowInfo::Type>>(layoutParamsType)) ?:
         parcel->writeInt32(frameLeft) ?:
         parcel->writeInt32(frameTop) ?:
         parcel->writeInt32(frameRight) ?:
@@ -97,21 +114,12 @@
         parcel->writeFloat(transform.dtdy()) ?:
         parcel->writeFloat(transform.dsdy()) ?:
         parcel->writeFloat(transform.ty()) ?:
-        parcel->writeUint32(displayOrientation) ?:
-        parcel->writeInt32(displayWidth) ?:
-        parcel->writeInt32(displayHeight) ?:
-        parcel->writeBool(visible) ?:
-        parcel->writeBool(focusable) ?:
-        parcel->writeBool(hasWallpaper) ?:
-        parcel->writeBool(paused) ?:
-        parcel->writeBool(trustedOverlay) ?:
         parcel->writeInt32(static_cast<int32_t>(touchOcclusionMode)) ?:
         parcel->writeInt32(ownerPid) ?:
         parcel->writeInt32(ownerUid) ?:
         parcel->writeUtf8AsUtf16(packageName) ?:
-        parcel->writeInt32(inputFeatures.get()) ?:
+        parcel->writeInt32(inputConfig.get()) ?:
         parcel->writeInt32(displayId) ?:
-        parcel->writeInt32(portalToDisplayId) ?:
         applicationInfo.writeToParcel(parcel) ?:
         parcel->write(touchableRegion) ?:
         parcel->writeBool(replaceTouchableRegionWithCrop) ?:
@@ -137,12 +145,14 @@
         return status;
     }
 
-    flags = Flags<Flag>(parcel->readInt32());
-    type = static_cast<Type>(parcel->readInt32());
     float dsdx, dtdx, tx, dtdy, dsdy, ty;
-    int32_t touchOcclusionModeInt;
+    int32_t lpFlags, lpType, touchOcclusionModeInt, inputConfigInt;
+    sp<IBinder> touchableRegionCropHandleSp;
+
     // clang-format off
-    status = parcel->readInt32(&frameLeft) ?:
+    status = parcel->readInt32(&lpFlags) ?:
+        parcel->readInt32(&lpType) ?:
+        parcel->readInt32(&frameLeft) ?:
         parcel->readInt32(&frameTop) ?:
         parcel->readInt32(&frameRight) ?:
         parcel->readInt32(&frameBottom) ?:
@@ -155,44 +165,31 @@
         parcel->readFloat(&dtdy) ?:
         parcel->readFloat(&dsdy) ?:
         parcel->readFloat(&ty) ?:
-        parcel->readUint32(&displayOrientation) ?:
-        parcel->readInt32(&displayWidth) ?:
-        parcel->readInt32(&displayHeight) ?:
-        parcel->readBool(&visible) ?:
-        parcel->readBool(&focusable) ?:
-        parcel->readBool(&hasWallpaper) ?:
-        parcel->readBool(&paused) ?:
-        parcel->readBool(&trustedOverlay) ?:
         parcel->readInt32(&touchOcclusionModeInt) ?:
         parcel->readInt32(&ownerPid) ?:
         parcel->readInt32(&ownerUid) ?:
-        parcel->readUtf8FromUtf16(&packageName);
-    // clang-format on
-
-    if (status != OK) {
-        return status;
-    }
-
-    touchOcclusionMode = static_cast<TouchOcclusionMode>(touchOcclusionModeInt);
-
-    inputFeatures = Flags<Feature>(parcel->readInt32());
-    // clang-format off
-    status = parcel->readInt32(&displayId) ?:
-        parcel->readInt32(&portalToDisplayId) ?:
+        parcel->readUtf8FromUtf16(&packageName) ?:
+        parcel->readInt32(&inputConfigInt) ?:
+        parcel->readInt32(&displayId) ?:
         applicationInfo.readFromParcel(parcel) ?:
         parcel->read(touchableRegion) ?:
-        parcel->readBool(&replaceTouchableRegionWithCrop);
+        parcel->readBool(&replaceTouchableRegionWithCrop) ?:
+        parcel->readNullableStrongBinder(&touchableRegionCropHandleSp) ?:
+        parcel->readNullableStrongBinder(&windowToken);
     // clang-format on
 
     if (status != OK) {
         return status;
     }
 
-    touchableRegionCropHandle = parcel->readStrongBinder();
+    layoutParamsFlags = Flags<Flag>(lpFlags);
+    layoutParamsType = static_cast<Type>(lpType);
     transform.set({dsdx, dtdx, tx, dtdy, dsdy, ty, 0, 0, 1});
+    touchOcclusionMode = static_cast<TouchOcclusionMode>(touchOcclusionModeInt);
+    inputConfig = Flags<InputConfig>(inputConfigInt);
+    touchableRegionCropHandle = touchableRegionCropHandleSp;
 
-    status = parcel->readNullableStrongBinder(&windowToken);
-    return status;
+    return OK;
 }
 
 // --- WindowInfoHandle ---
diff --git a/libs/gui/WindowInfosListenerReporter.cpp b/libs/gui/WindowInfosListenerReporter.cpp
index c00a438..cfc7dbc 100644
--- a/libs/gui/WindowInfosListenerReporter.cpp
+++ b/libs/gui/WindowInfosListenerReporter.cpp
@@ -19,6 +19,7 @@
 
 namespace android {
 
+using gui::DisplayInfo;
 using gui::IWindowInfosReportedListener;
 using gui::WindowInfo;
 using gui::WindowInfosListener;
@@ -30,7 +31,8 @@
 
 status_t WindowInfosListenerReporter::addWindowInfosListener(
         const sp<WindowInfosListener>& windowInfosListener,
-        const sp<ISurfaceComposer>& surfaceComposer) {
+        const sp<ISurfaceComposer>& surfaceComposer,
+        std::pair<std::vector<gui::WindowInfo>, std::vector<gui::DisplayInfo>>* outInitialInfo) {
     status_t status = OK;
     {
         std::scoped_lock lock(mListenersMutex);
@@ -41,6 +43,11 @@
         if (status == OK) {
             mWindowInfosListeners.insert(windowInfosListener);
         }
+
+        if (outInitialInfo != nullptr) {
+            outInitialInfo->first = mLastWindowInfos;
+            outInitialInfo->second = mLastDisplayInfos;
+        }
     }
 
     return status;
@@ -54,6 +61,10 @@
         std::scoped_lock lock(mListenersMutex);
         if (mWindowInfosListeners.size() == 1) {
             status = surfaceComposer->removeWindowInfosListener(this);
+            // Clear the last stored state since we're disabling updates and don't want to hold
+            // stale values
+            mLastWindowInfos.clear();
+            mLastDisplayInfos.clear();
         }
 
         if (status == OK) {
@@ -65,20 +76,22 @@
 }
 
 binder::Status WindowInfosListenerReporter::onWindowInfosChanged(
-        const std::vector<WindowInfo>& windowInfos,
+        const std::vector<WindowInfo>& windowInfos, const std::vector<DisplayInfo>& displayInfos,
         const sp<IWindowInfosReportedListener>& windowInfosReportedListener) {
-    std::unordered_set<sp<WindowInfosListener>, ISurfaceComposer::SpHash<WindowInfosListener>>
-            windowInfosListeners;
+    std::unordered_set<sp<WindowInfosListener>, SpHash<WindowInfosListener>> windowInfosListeners;
 
     {
         std::scoped_lock lock(mListenersMutex);
         for (auto listener : mWindowInfosListeners) {
             windowInfosListeners.insert(listener);
         }
+
+        mLastWindowInfos = windowInfos;
+        mLastDisplayInfos = displayInfos;
     }
 
     for (auto listener : windowInfosListeners) {
-        listener->onWindowInfosChanged(windowInfos);
+        listener->onWindowInfosChanged(windowInfos, displayInfos);
     }
 
     if (windowInfosReportedListener) {
@@ -95,4 +108,4 @@
     }
 }
 
-} // namespace android
\ No newline at end of file
+} // namespace android
diff --git a/libs/ui/Size.cpp b/libs/gui/aidl/android/gui/BitTube.aidl
similarity index 74%
copy from libs/ui/Size.cpp
copy to libs/gui/aidl/android/gui/BitTube.aidl
index d2996d1..6b0595e 100644
--- a/libs/ui/Size.cpp
+++ b/libs/gui/aidl/android/gui/BitTube.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright 2019 The Android Open Source Project
+ * 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.
@@ -14,11 +14,6 @@
  * limitations under the License.
  */
 
-#include <ui/Size.h>
+package android.gui;
 
-namespace android::ui {
-
-const Size Size::INVALID{-1, -1};
-const Size Size::EMPTY{0, 0};
-
-} // namespace android::ui
+parcelable BitTube cpp_header "private/gui/BitTube.h";
diff --git a/libs/ui/Size.cpp b/libs/gui/aidl/android/gui/DisplayCaptureArgs.aidl
similarity index 74%
rename from libs/ui/Size.cpp
rename to libs/gui/aidl/android/gui/DisplayCaptureArgs.aidl
index d2996d1..2caa2b9 100644
--- a/libs/ui/Size.cpp
+++ b/libs/gui/aidl/android/gui/DisplayCaptureArgs.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright 2019 The Android Open Source Project
+ * 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.
@@ -14,11 +14,6 @@
  * limitations under the License.
  */
 
-#include <ui/Size.h>
+package android.gui;
 
-namespace android::ui {
-
-const Size Size::INVALID{-1, -1};
-const Size Size::EMPTY{0, 0};
-
-} // namespace android::ui
+parcelable DisplayCaptureArgs cpp_header "gui/DisplayCaptureArgs.h";
diff --git a/libs/gui/aidl/android/gui/IDisplayEventConnection.aidl b/libs/gui/aidl/android/gui/IDisplayEventConnection.aidl
new file mode 100644
index 0000000..9781ca9
--- /dev/null
+++ b/libs/gui/aidl/android/gui/IDisplayEventConnection.aidl
@@ -0,0 +1,47 @@
+/*
+ * 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.
+ */
+
+package android.gui;
+
+import android.gui.BitTube;
+import android.gui.ParcelableVsyncEventData;
+
+/** @hide */
+interface IDisplayEventConnection {
+    /*
+     * stealReceiveChannel() returns a BitTube to receive events from. Only the receive file
+     * descriptor of outChannel will be initialized, and this effectively "steals" the receive
+     * channel from the remote end (such that the remote end can only use its send channel).
+     */
+    void stealReceiveChannel(out BitTube outChannel);
+
+    /*
+     * setVsyncRate() sets the vsync event delivery rate. A value of 1 returns every vsync event.
+     * A value of 2 returns every other event, etc. A value of 0 returns no event unless
+     * requestNextVsync() has been called.
+     */
+    void setVsyncRate(in int count);
+
+    /*
+     * requestNextVsync() schedules the next vsync event. It has no effect if the vsync rate is > 0.
+     */
+    oneway void requestNextVsync(); // Asynchronous
+
+    /*
+     * getLatestVsyncEventData() gets the latest vsync event data.
+     */
+    ParcelableVsyncEventData getLatestVsyncEventData();
+}
diff --git a/libs/ui/Size.cpp b/libs/gui/aidl/android/gui/IRegionSamplingListener.aidl
similarity index 74%
copy from libs/ui/Size.cpp
copy to libs/gui/aidl/android/gui/IRegionSamplingListener.aidl
index d2996d1..00a3959 100644
--- a/libs/ui/Size.cpp
+++ b/libs/gui/aidl/android/gui/IRegionSamplingListener.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright 2019 The Android Open Source Project
+ * 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.
@@ -14,11 +14,9 @@
  * limitations under the License.
  */
 
-#include <ui/Size.h>
+package android.gui;
 
-namespace android::ui {
-
-const Size Size::INVALID{-1, -1};
-const Size Size::EMPTY{0, 0};
-
-} // namespace android::ui
+/** @hide */
+oneway interface IRegionSamplingListener {
+    void onSampleCollected(float medianLuma);
+}
diff --git a/libs/gui/aidl/android/gui/ISurfaceComposer.aidl b/libs/gui/aidl/android/gui/ISurfaceComposer.aidl
new file mode 100644
index 0000000..07921a5
--- /dev/null
+++ b/libs/gui/aidl/android/gui/ISurfaceComposer.aidl
@@ -0,0 +1,42 @@
+/*
+ * 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.
+ */
+
+package android.gui;
+
+import android.gui.DisplayCaptureArgs;
+import android.gui.LayerCaptureArgs;
+import android.gui.IScreenCaptureListener;
+
+/** @hide */
+interface ISurfaceComposer {
+    /**
+     * Capture the specified screen. This requires READ_FRAME_BUFFER
+     * permission.  This function will fail if there is a secure window on
+     * screen and DisplayCaptureArgs.captureSecureLayers is false.
+     *
+     * This function can capture a subregion (the source crop) of the screen.
+     * The subregion can be optionally rotated.  It will also be scaled to
+     * match the size of the output buffer.
+     */
+    void captureDisplay(in DisplayCaptureArgs args, IScreenCaptureListener listener);
+    void captureDisplayById(long displayId, IScreenCaptureListener listener);
+    /**
+     * Capture a subtree of the layer hierarchy, potentially ignoring the root node.
+     * This requires READ_FRAME_BUFFER permission. This function will fail if there
+     * is a secure window on screen
+     */
+    void captureLayers(in LayerCaptureArgs args, IScreenCaptureListener listener);
+}
diff --git a/libs/ui/Size.cpp b/libs/gui/aidl/android/gui/LayerCaptureArgs.aidl
similarity index 74%
copy from libs/ui/Size.cpp
copy to libs/gui/aidl/android/gui/LayerCaptureArgs.aidl
index d2996d1..f0def50 100644
--- a/libs/ui/Size.cpp
+++ b/libs/gui/aidl/android/gui/LayerCaptureArgs.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright 2019 The Android Open Source Project
+ * 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.
@@ -14,11 +14,6 @@
  * limitations under the License.
  */
 
-#include <ui/Size.h>
+package android.gui;
 
-namespace android::ui {
-
-const Size Size::INVALID{-1, -1};
-const Size Size::EMPTY{0, 0};
-
-} // namespace android::ui
+parcelable LayerCaptureArgs cpp_header "gui/LayerCaptureArgs.h";
diff --git a/libs/ui/Size.cpp b/libs/gui/aidl/android/gui/ParcelableVsyncEventData.aidl
similarity index 74%
copy from libs/ui/Size.cpp
copy to libs/gui/aidl/android/gui/ParcelableVsyncEventData.aidl
index d2996d1..ba76671 100644
--- a/libs/ui/Size.cpp
+++ b/libs/gui/aidl/android/gui/ParcelableVsyncEventData.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright 2019 The Android Open Source Project
+ * 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.
@@ -14,11 +14,6 @@
  * limitations under the License.
  */
 
-#include <ui/Size.h>
+package android.gui;
 
-namespace android::ui {
-
-const Size Size::INVALID{-1, -1};
-const Size Size::EMPTY{0, 0};
-
-} // namespace android::ui
+parcelable ParcelableVsyncEventData cpp_header "gui/VsyncEventData.h";
diff --git a/libs/ui/Size.cpp b/libs/gui/android/gui/DisplayInfo.aidl
similarity index 67%
copy from libs/ui/Size.cpp
copy to libs/gui/android/gui/DisplayInfo.aidl
index d2996d1..30c0885 100644
--- a/libs/ui/Size.cpp
+++ b/libs/gui/android/gui/DisplayInfo.aidl
@@ -1,11 +1,11 @@
 /*
- * Copyright 2019 The Android Open Source Project
+ * 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
+ *     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,
@@ -14,11 +14,6 @@
  * limitations under the License.
  */
 
-#include <ui/Size.h>
+package android.gui;
 
-namespace android::ui {
-
-const Size Size::INVALID{-1, -1};
-const Size Size::EMPTY{0, 0};
-
-} // namespace android::ui
+parcelable DisplayInfo cpp_header "gui/DisplayInfo.h";
diff --git a/libs/gui/android/gui/FocusRequest.aidl b/libs/gui/android/gui/FocusRequest.aidl
index 9018635..b13c600 100644
--- a/libs/gui/android/gui/FocusRequest.aidl
+++ b/libs/gui/android/gui/FocusRequest.aidl
@@ -21,7 +21,7 @@
     /**
      * Input channel token used to identify the window that should gain focus.
      */
-    IBinder token;
+    @nullable IBinder token;
     @utf8InCpp String windowName;
     /**
      * The token that the caller expects currently to be focused. If the
diff --git a/libs/gui/android/gui/IWindowInfosListener.aidl b/libs/gui/android/gui/IWindowInfosListener.aidl
index d4553ca..a5b2762 100644
--- a/libs/gui/android/gui/IWindowInfosListener.aidl
+++ b/libs/gui/android/gui/IWindowInfosListener.aidl
@@ -16,11 +16,12 @@
 
 package android.gui;
 
+import android.gui.DisplayInfo;
 import android.gui.IWindowInfosReportedListener;
 import android.gui.WindowInfo;
 
 /** @hide */
 oneway interface IWindowInfosListener
 {
-    void onWindowInfosChanged(in WindowInfo[] windowInfos, in @nullable IWindowInfosReportedListener windowInfosReportedListener);
+    void onWindowInfosChanged(in WindowInfo[] windowInfos, in DisplayInfo[] displayInfos, in @nullable IWindowInfosReportedListener windowInfosReportedListener);
 }
diff --git a/libs/gui/include/gui/BLASTBufferQueue.h b/libs/gui/include/gui/BLASTBufferQueue.h
index f9e40ec..65fc04d 100644
--- a/libs/gui/include/gui/BLASTBufferQueue.h
+++ b/libs/gui/include/gui/BLASTBufferQueue.h
@@ -73,7 +73,7 @@
     : public ConsumerBase::FrameAvailableListener, public BufferItemConsumer::BufferFreedListener
 {
 public:
-    BLASTBufferQueue(const std::string& name);
+    BLASTBufferQueue(const std::string& name, bool updateDestinationFrame = true);
     BLASTBufferQueue(const std::string& name, const sp<SurfaceControl>& surface, int width,
                      int height, int32_t format);
 
@@ -81,6 +81,7 @@
         return mProducer;
     }
     sp<Surface> getSurface(bool includeSurfaceControlHandle);
+    bool isSameSurfaceControl(const sp<SurfaceControl>& surfaceControl) const;
 
     void onBufferFreed(const wp<GraphicBuffer>&/* graphicBuffer*/) override { /* TODO */ }
     void onFrameReplaced(const BufferItem& item) override;
@@ -90,19 +91,18 @@
 
     void transactionCommittedCallback(nsecs_t latchTime, const sp<Fence>& presentFence,
                                       const std::vector<SurfaceControlStats>& stats);
-    void transactionCallback(nsecs_t latchTime, const sp<Fence>& presentFence,
-            const std::vector<SurfaceControlStats>& stats);
+    virtual void transactionCallback(nsecs_t latchTime, const sp<Fence>& presentFence,
+                                     const std::vector<SurfaceControlStats>& stats);
     void releaseBufferCallback(const ReleaseCallbackId& id, const sp<Fence>& releaseFence,
-                               uint32_t transformHint, uint32_t currentMaxAcquiredBufferCount);
-    void releaseBufferCallbackLocked(const ReleaseCallbackId& id, const sp<Fence>& releaseFence,
-                                     uint32_t transformHint, uint32_t currentMaxAcquiredBufferCount);
-    void setNextTransaction(SurfaceComposerClient::Transaction *t);
+                               std::optional<uint32_t> currentMaxAcquiredBufferCount);
+    void syncNextTransaction(std::function<void(SurfaceComposerClient::Transaction*)> callback,
+                             bool acquireSingleBuffer = true);
+    void stopContinuousSyncTransaction();
     void mergeWithNextTransaction(SurfaceComposerClient::Transaction* t, uint64_t frameNumber);
-    void setTransactionCompleteCallback(uint64_t frameNumber,
-                                        std::function<void(int64_t)>&& transactionCompleteCallback);
+    void applyPendingTransactions(uint64_t frameNumber);
+    SurfaceComposerClient::Transaction* gatherPendingTransactions(uint64_t frameNumber);
 
-    void update(const sp<SurfaceControl>& surface, uint32_t width, uint32_t height, int32_t format,
-                SurfaceComposerClient::Transaction* outTransaction = nullptr);
+    void update(const sp<SurfaceControl>& surface, uint32_t width, uint32_t height, int32_t format);
 
     status_t setFrameRate(float frameRate, int8_t compatibility, bool shouldBeSeamless);
     status_t setFrameTimelineInfo(const FrameTimelineInfo& info);
@@ -110,9 +110,8 @@
     void setSidebandStream(const sp<NativeHandle>& stream);
 
     uint32_t getLastTransformHint() const;
-    void flushShadowQueue();
-
     uint64_t getLastAcquiredFrameNum();
+    void abandon();
 
     virtual ~BLASTBufferQueue();
 
@@ -132,9 +131,14 @@
     bool rejectBuffer(const BufferItem& item) REQUIRES(mMutex);
     bool maxBuffersAcquired(bool includeExtraAcquire) const REQUIRES(mMutex);
     static PixelFormat convertBufferFormat(PixelFormat& format);
+    void mergePendingTransactions(SurfaceComposerClient::Transaction* t, uint64_t frameNumber)
+            REQUIRES(mMutex);
 
-    void flushShadowQueueLocked() REQUIRES(mMutex);
+    void flushShadowQueue() REQUIRES(mMutex);
     void acquireAndReleaseBuffer() REQUIRES(mMutex);
+    void releaseBuffer(const ReleaseCallbackId& callbackId, const sp<Fence>& releaseFence)
+            REQUIRES(mMutex);
+    void flushAndWaitForFreeBuffer(std::unique_lock<std::mutex>& lock);
 
     std::string mName;
     // Represents the queued buffer count from buffer queue,
@@ -144,15 +148,15 @@
     std::string mQueuedBufferTrace;
     sp<SurfaceControl> mSurfaceControl;
 
-    std::mutex mMutex;
+    mutable std::mutex mMutex;
     std::condition_variable mCallbackCV;
 
     // BufferQueue internally allows 1 more than
     // the max to be acquired
     int32_t mMaxAcquiredBuffers = 1;
 
-    int32_t mNumFrameAvailable GUARDED_BY(mMutex);
-    int32_t mNumAcquired GUARDED_BY(mMutex);
+    int32_t mNumFrameAvailable GUARDED_BY(mMutex) = 0;
+    int32_t mNumAcquired GUARDED_BY(mMutex) = 0;
 
     // Keep a reference to the submitted buffers so we can release when surfaceflinger drops the
     // buffer or the buffer has been presented and a new buffer is ready to be presented.
@@ -165,12 +169,6 @@
     struct ReleasedBuffer {
         ReleaseCallbackId callbackId;
         sp<Fence> releaseFence;
-        bool operator==(const ReleasedBuffer& rhs) const {
-            // Only compare Id so if we somehow got two callbacks
-            // with different fences we don't decrement mNumAcquired
-            // too far.
-            return rhs.callbackId == callbackId;
-        }
     };
     std::deque<ReleasedBuffer> mPendingRelease GUARDED_BY(mMutex);
 
@@ -217,23 +215,17 @@
     sp<IGraphicBufferProducer> mProducer;
     sp<BLASTBufferItemConsumer> mBufferItemConsumer;
 
-    SurfaceComposerClient::Transaction* mNextTransaction GUARDED_BY(mMutex);
+    std::function<void(SurfaceComposerClient::Transaction*)> mTransactionReadyCallback
+            GUARDED_BY(mMutex);
+    SurfaceComposerClient::Transaction* mSyncTransaction GUARDED_BY(mMutex);
     std::vector<std::tuple<uint64_t /* framenumber */, SurfaceComposerClient::Transaction>>
             mPendingTransactions GUARDED_BY(mMutex);
 
-    // Last requested auto refresh state set by the producer. The state indicates that the consumer
-    // should acquire the next frame as soon as it can and not wait for a frame to become available.
-    // This is only relevant for shared buffer mode.
-    bool mAutoRefresh GUARDED_BY(mMutex) = false;
-
     std::queue<FrameTimelineInfo> mNextFrameTimelineInfoQueue GUARDED_BY(mMutex);
 
     // Tracks the last acquired frame number
     uint64_t mLastAcquiredFrameNumber GUARDED_BY(mMutex) = 0;
 
-    std::function<void(int64_t)> mTransactionCompleteCallback GUARDED_BY(mMutex) = nullptr;
-    uint64_t mTransactionCompleteFrameNumber GUARDED_BY(mMutex){0};
-
     // Queues up transactions using this token in SurfaceFlinger. This prevents queued up
     // transactions from other parts of the client from blocking this transaction.
     const sp<IBinder> mApplyToken GUARDED_BY(mMutex) = new BBinder();
@@ -251,7 +243,32 @@
     std::queue<sp<SurfaceControl>> mSurfaceControlsWithPendingCallback GUARDED_BY(mMutex);
 
     uint32_t mCurrentMaxAcquiredBufferCount;
-    bool mWaitForTransactionCallback = false;
+    bool mWaitForTransactionCallback GUARDED_BY(mMutex) = false;
+
+    // Flag to determine if syncTransaction should only acquire a single buffer and then clear or
+    // continue to acquire buffers until explicitly cleared
+    bool mAcquireSingleBuffer GUARDED_BY(mMutex) = true;
+
+    // True if BBQ will update the destination frame used to scale the buffer to the requested size.
+    // If false, the caller is responsible for updating the destination frame on the BBQ
+    // surfacecontol. This is useful if the caller wants to synchronize the buffer scale with
+    // additional scales in the hierarchy.
+    bool mUpdateDestinationFrame GUARDED_BY(mMutex) = true;
+
+    // We send all transactions on our apply token over one-way binder calls to avoid blocking
+    // client threads. All of our transactions remain in order, since they are one-way binder calls
+    // from a single process, to a single interface. However once we give up a Transaction for sync
+    // we can start to have ordering issues. When we return from sync to normal frame production,
+    // we wait on the commit callback of sync frames ensuring ordering, however we don't want to
+    // wait on the commit callback for every normal frame (since even emitting them has a
+    // performance cost) this means we need a method to ensure frames are in order when switching
+    // from one-way application on our apply token, to application on some other apply token. We
+    // make use of setBufferHasBarrier to declare this ordering. This boolean simply tracks when we
+    // need to set this flag, notably only in the case where we are transitioning from a previous
+    // transaction applied by us (one way, may not yet have reached server) and an upcoming
+    // transaction that will be applied by some sync consumer.
+    bool mAppliedLastTransaction = false;
+    uint64_t mLastAppliedFrameNumber = 0;
 };
 
 } // namespace android
diff --git a/libs/gui/include/gui/DisplayCaptureArgs.h b/libs/gui/include/gui/DisplayCaptureArgs.h
new file mode 100644
index 0000000..ec884cf
--- /dev/null
+++ b/libs/gui/include/gui/DisplayCaptureArgs.h
@@ -0,0 +1,71 @@
+/*
+ * 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 <stdint.h>
+#include <sys/types.h>
+
+#include <binder/IBinder.h>
+#include <binder/Parcel.h>
+#include <binder/Parcelable.h>
+#include <ui/GraphicTypes.h>
+#include <ui/PixelFormat.h>
+
+namespace android::gui {
+
+struct CaptureArgs : public Parcelable {
+    const static int32_t UNSET_UID = -1;
+    virtual ~CaptureArgs() = default;
+
+    ui::PixelFormat pixelFormat{ui::PixelFormat::RGBA_8888};
+    Rect sourceCrop;
+    float frameScaleX{1};
+    float frameScaleY{1};
+    bool captureSecureLayers{false};
+    int32_t uid{UNSET_UID};
+    // Force capture to be in a color space. If the value is ui::Dataspace::UNKNOWN, the captured
+    // result will be in the display's colorspace.
+    // The display may use non-RGB dataspace (ex. displayP3) that could cause pixel data could be
+    // different from SRGB (byte per color), and failed when checking colors in tests.
+    // NOTE: In normal cases, we want the screen to be captured in display's colorspace.
+    ui::Dataspace dataspace = ui::Dataspace::UNKNOWN;
+
+    // The receiver of the capture can handle protected buffer. A protected buffer has
+    // GRALLOC_USAGE_PROTECTED usage bit and must not be accessed unprotected behaviour.
+    // Any read/write access from unprotected context will result in undefined behaviour.
+    // Protected contents are typically DRM contents. This has no direct implication to the
+    // secure property of the surface, which is specified by the application explicitly to avoid
+    // the contents being accessed/captured by screenshot or unsecure display.
+    bool allowProtected = false;
+
+    bool grayscale = false;
+
+    virtual status_t writeToParcel(Parcel* output) const;
+    virtual status_t readFromParcel(const Parcel* input);
+};
+
+struct DisplayCaptureArgs : CaptureArgs {
+    sp<IBinder> displayToken;
+    uint32_t width{0};
+    uint32_t height{0};
+    bool useIdentityTransform{false};
+
+    status_t writeToParcel(Parcel* output) const override;
+    status_t readFromParcel(const Parcel* input) override;
+};
+
+}; // namespace android::gui
diff --git a/libs/gui/include/gui/DisplayEventDispatcher.h b/libs/gui/include/gui/DisplayEventDispatcher.h
index 08f3597..a342539 100644
--- a/libs/gui/include/gui/DisplayEventDispatcher.h
+++ b/libs/gui/include/gui/DisplayEventDispatcher.h
@@ -21,20 +21,6 @@
 namespace android {
 using FrameRateOverride = DisplayEventReceiver::Event::FrameRateOverride;
 
-struct VsyncEventData {
-    // The Vsync Id corresponsing to this vsync event. This will be used to
-    // populate ISurfaceComposer::setFrameTimelineVsync and
-    // SurfaceComposerClient::setFrameTimelineVsync
-    int64_t id = FrameTimelineInfo::INVALID_VSYNC_ID;
-
-    // The deadline in CLOCK_MONOTONIC that the app needs to complete its
-    // frame by (both on the CPU and the GPU)
-    int64_t deadlineTimestamp = std::numeric_limits<int64_t>::max();
-
-    // The current frame interval in ns when this frame was scheduled.
-    int64_t frameInterval = 0;
-};
-
 class DisplayEventDispatcher : public LooperCallback {
 public:
     explicit DisplayEventDispatcher(
@@ -48,6 +34,7 @@
     void injectEvent(const DisplayEventReceiver::Event& event);
     int getFd() const;
     virtual int handleEvent(int receiveFd, int events, void* data);
+    status_t getLatestVsyncEventData(ParcelableVsyncEventData* outVsyncEventData) const;
 
 protected:
     virtual ~DisplayEventDispatcher() = default;
@@ -76,5 +63,8 @@
 
     bool processPendingEvents(nsecs_t* outTimestamp, PhysicalDisplayId* outDisplayId,
                               uint32_t* outCount, VsyncEventData* outVsyncEventData);
+
+    void populateFrameTimelines(const DisplayEventReceiver::Event& event,
+                                VsyncEventData* outVsyncEventData) const;
 };
 } // namespace android
diff --git a/libs/gui/include/gui/DisplayEventReceiver.h b/libs/gui/include/gui/DisplayEventReceiver.h
index 0dffbde..cf7a4e5 100644
--- a/libs/gui/include/gui/DisplayEventReceiver.h
+++ b/libs/gui/include/gui/DisplayEventReceiver.h
@@ -26,6 +26,7 @@
 
 #include <binder/IInterface.h>
 #include <gui/ISurfaceComposer.h>
+#include <gui/VsyncEventData.h>
 
 // ----------------------------------------------------------------------------
 
@@ -33,7 +34,9 @@
 
 // ----------------------------------------------------------------------------
 
-class IDisplayEventConnection;
+using gui::IDisplayEventConnection;
+using gui::ParcelableVsyncEventData;
+using gui::VsyncEventData;
 
 namespace gui {
 class BitTube;
@@ -49,6 +52,7 @@
 // ----------------------------------------------------------------------------
 class DisplayEventReceiver {
 public:
+
     enum {
         DISPLAY_EVENT_VSYNC = fourcc('v', 's', 'y', 'n'),
         DISPLAY_EVENT_HOTPLUG = fourcc('p', 'l', 'u', 'g'),
@@ -73,10 +77,7 @@
 
         struct VSync {
             uint32_t count;
-            nsecs_t expectedVSyncTimestamp __attribute__((aligned(8)));
-            nsecs_t deadlineTimestamp __attribute__((aligned(8)));
-            nsecs_t frameInterval __attribute__((aligned(8)));
-            int64_t vsyncId;
+            VsyncEventData vsyncData;
         };
 
         struct Hotplug {
@@ -163,11 +164,22 @@
      */
     status_t requestNextVsync();
 
+    /**
+     * getLatestVsyncEventData() gets the latest vsync event data.
+     */
+    status_t getLatestVsyncEventData(ParcelableVsyncEventData* outVsyncEventData) const;
+
 private:
     sp<IDisplayEventConnection> mEventConnection;
     std::unique_ptr<gui::BitTube> mDataChannel;
+    std::optional<status_t> mInitError;
 };
 
+inline bool operator==(DisplayEventReceiver::Event::FrameRateOverride lhs,
+                       DisplayEventReceiver::Event::FrameRateOverride rhs) {
+    return (lhs.uid == rhs.uid) && std::abs(lhs.frameRateHz - rhs.frameRateHz) < 0.001f;
+}
+
 // ----------------------------------------------------------------------------
 }; // namespace android
 
diff --git a/libs/gui/include/gui/DisplayInfo.h b/libs/gui/include/gui/DisplayInfo.h
new file mode 100644
index 0000000..74f33a2
--- /dev/null
+++ b/libs/gui/include/gui/DisplayInfo.h
@@ -0,0 +1,46 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <binder/Parcel.h>
+#include <binder/Parcelable.h>
+#include <gui/constants.h>
+#include <ui/Transform.h>
+
+namespace android::gui {
+
+/*
+ * Describes information about a display that can have windows in it.
+ *
+ * This should only be used by InputFlinger to support raw coordinates in logical display space.
+ */
+struct DisplayInfo : public Parcelable {
+    int32_t displayId = ADISPLAY_ID_NONE;
+
+    // Logical display dimensions.
+    int32_t logicalWidth = 0;
+    int32_t logicalHeight = 0;
+
+    // The display transform. This takes display coordinates to logical display coordinates.
+    ui::Transform transform;
+
+    status_t writeToParcel(android::Parcel*) const override;
+
+    status_t readFromParcel(const android::Parcel*) override;
+};
+
+} // namespace android::gui
\ No newline at end of file
diff --git a/libs/gui/include/gui/FrameTimelineInfo.h b/libs/gui/include/gui/FrameTimelineInfo.h
index a23c202..255ce56 100644
--- a/libs/gui/include/gui/FrameTimelineInfo.h
+++ b/libs/gui/include/gui/FrameTimelineInfo.h
@@ -36,6 +36,9 @@
     // not directly vendor available.
     int32_t inputEventId = 0;
 
+    // The current time in nanoseconds the application started to render the frame.
+    int64_t startTimeNanos = 0;
+
     status_t write(Parcel& output) const;
     status_t read(const Parcel& input);
 
diff --git a/libs/gui/include/gui/HdrMetadata.h b/libs/gui/include/gui/HdrMetadata.h
index 1e9c3e7..0bdffac 100644
--- a/libs/gui/include/gui/HdrMetadata.h
+++ b/libs/gui/include/gui/HdrMetadata.h
@@ -44,6 +44,7 @@
     status_t unflatten(void const* buffer, size_t size);
 
     bool operator==(const HdrMetadata& rhs) const;
+    bool operator!=(const HdrMetadata& rhs) const { return !(*this == rhs); }
 };
 
 } // namespace android
diff --git a/libs/gui/include/gui/IDisplayEventConnection.h b/libs/gui/include/gui/IDisplayEventConnection.h
deleted file mode 100644
index cff22a3..0000000
--- a/libs/gui/include/gui/IDisplayEventConnection.h
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#pragma once
-
-#include <binder/IInterface.h>
-#include <binder/SafeInterface.h>
-#include <gui/ISurfaceComposer.h>
-#include <utils/Errors.h>
-
-#include <cstdint>
-
-namespace android {
-
-namespace gui {
-class BitTube;
-} // namespace gui
-
-class IDisplayEventConnection : public IInterface {
-public:
-    DECLARE_META_INTERFACE(DisplayEventConnection)
-
-    /*
-     * stealReceiveChannel() returns a BitTube to receive events from. Only the receive file
-     * descriptor of outChannel will be initialized, and this effectively "steals" the receive
-     * channel from the remote end (such that the remote end can only use its send channel).
-     */
-    virtual status_t stealReceiveChannel(gui::BitTube* outChannel) = 0;
-
-    /*
-     * setVsyncRate() sets the vsync event delivery rate. A value of 1 returns every vsync event.
-     * A value of 2 returns every other event, etc. A value of 0 returns no event unless
-     * requestNextVsync() has been called.
-     */
-    virtual status_t setVsyncRate(uint32_t count) = 0;
-
-    /*
-     * requestNextVsync() schedules the next vsync event. It has no effect if the vsync rate is > 0.
-     */
-    virtual void requestNextVsync() = 0; // Asynchronous
-};
-
-class BnDisplayEventConnection : public SafeBnInterface<IDisplayEventConnection> {
-public:
-    BnDisplayEventConnection()
-          : SafeBnInterface<IDisplayEventConnection>("BnDisplayEventConnection") {}
-
-    status_t onTransact(uint32_t code, const Parcel& data, Parcel* reply,
-                        uint32_t flags = 0) override;
-};
-
-} // namespace android
diff --git a/libs/gui/include/gui/IRegionSamplingListener.h b/libs/gui/include/gui/IRegionSamplingListener.h
deleted file mode 100644
index 1803d9a..0000000
--- a/libs/gui/include/gui/IRegionSamplingListener.h
+++ /dev/null
@@ -1,43 +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.
- */
-
-#pragma once
-
-#include <cstdint>
-#include <vector>
-
-#include <binder/IInterface.h>
-#include <binder/SafeInterface.h>
-
-namespace android {
-
-class IRegionSamplingListener : public IInterface {
-public:
-    DECLARE_META_INTERFACE(RegionSamplingListener)
-
-    virtual void onSampleCollected(float medianLuma) = 0;
-};
-
-class BnRegionSamplingListener : public SafeBnInterface<IRegionSamplingListener> {
-public:
-    BnRegionSamplingListener()
-          : SafeBnInterface<IRegionSamplingListener>("BnRegionSamplingListener") {}
-
-    status_t onTransact(uint32_t code, const Parcel& data, Parcel* reply,
-                        uint32_t flags = 0) override;
-};
-
-} // namespace android
diff --git a/libs/gui/include/gui/ISurfaceComposer.h b/libs/gui/include/gui/ISurfaceComposer.h
index cd289cb..0a3cc19 100644
--- a/libs/gui/include/gui/ISurfaceComposer.h
+++ b/libs/gui/include/gui/ISurfaceComposer.h
@@ -17,8 +17,10 @@
 #pragma once
 
 #include <android/gui/DisplayBrightness.h>
+#include <android/gui/IDisplayEventConnection.h>
 #include <android/gui/IFpsListener.h>
 #include <android/gui/IHdrLayerInfoListener.h>
+#include <android/gui/IRegionSamplingListener.h>
 #include <android/gui/IScreenCaptureListener.h>
 #include <android/gui/ITransactionTraceListener.h>
 #include <android/gui/ITunnelModeEnabledListener.h>
@@ -28,6 +30,7 @@
 #include <ftl/Flags.h>
 #include <gui/FrameTimelineInfo.h>
 #include <gui/ITransactionCompletedListener.h>
+#include <gui/SpHash.h>
 #include <math/vec4.h>
 #include <stdint.h>
 #include <sys/types.h>
@@ -49,25 +52,33 @@
 #include <unordered_set>
 #include <vector>
 
+#include <aidl/android/hardware/graphics/common/DisplayDecorationSupport.h>
+
 namespace android {
 
 struct client_cache_t;
 struct ComposerState;
-struct DisplayCaptureArgs;
 struct DisplayStatInfo;
 struct DisplayState;
 struct InputWindowCommands;
-struct LayerCaptureArgs;
 class LayerDebugInfo;
 class HdrCapabilities;
-class IDisplayEventConnection;
 class IGraphicBufferProducer;
 class ISurfaceComposerClient;
-class IRegionSamplingListener;
 class Rect;
 enum class FrameEvent;
 
+using gui::IDisplayEventConnection;
+using gui::IRegionSamplingListener;
 using gui::IScreenCaptureListener;
+using gui::SpHash;
+
+namespace gui {
+
+struct DisplayCaptureArgs;
+struct LayerCaptureArgs;
+
+} // namespace gui
 
 namespace ui {
 
@@ -102,6 +113,7 @@
         // android.permission.ACCESS_SURFACE_FLINGER
         eEarlyWakeupStart = 0x08,
         eEarlyWakeupEnd = 0x10,
+        eOneWay = 0x20
     };
 
     enum VsyncSource {
@@ -217,6 +229,29 @@
             ui::ColorMode colorMode) = 0;
 
     /**
+     * Sets the user-preferred display mode that a device should boot in.
+     */
+    virtual status_t setBootDisplayMode(const sp<IBinder>& display, ui::DisplayModeId) = 0;
+
+    /**
+     * Clears the user-preferred display mode. The device should now boot in system preferred
+     * display mode.
+     */
+    virtual status_t clearBootDisplayMode(const sp<IBinder>& display) = 0;
+
+    /**
+     * Gets whether boot time display mode operations are supported on the device.
+     *
+     * outSupport
+     *      An output parameter for whether boot time display mode operations are supported.
+     *
+     * Returns NO_ERROR upon success. Otherwise,
+     *      NAME_NOT_FOUND if the display is invalid, or
+     *      BAD_VALUE      if the output parameter is invalid.
+     */
+    virtual status_t getBootDisplayModeSupport(bool* outSupport) const = 0;
+
+    /**
      * Switches Auto Low Latency Mode on/off on the connected display, if it is
      * available. This should only be called if the display supports Auto Low
      * Latency Mode as reported in #getDynamicDisplayInfo.
@@ -232,34 +267,6 @@
      */
     virtual void setGameContentType(const sp<IBinder>& display, bool on) = 0;
 
-    /**
-     * Capture the specified screen. This requires READ_FRAME_BUFFER
-     * permission.  This function will fail if there is a secure window on
-     * screen and DisplayCaptureArgs.captureSecureLayers is false.
-     *
-     * This function can capture a subregion (the source crop) of the screen.
-     * The subregion can be optionally rotated.  It will also be scaled to
-     * match the size of the output buffer.
-     */
-    virtual status_t captureDisplay(const DisplayCaptureArgs& args,
-                                    const sp<IScreenCaptureListener>& captureListener) = 0;
-
-    virtual status_t captureDisplay(uint64_t displayOrLayerStack,
-                                    const sp<IScreenCaptureListener>& captureListener) = 0;
-
-    template <class AA>
-    struct SpHash {
-        size_t operator()(const sp<AA>& k) const { return std::hash<AA*>()(k.get()); }
-    };
-
-    /**
-     * Capture a subtree of the layer hierarchy, potentially ignoring the root node.
-     * This requires READ_FRAME_BUFFER permission. This function will fail if there
-     * is a secure window on screen
-     */
-    virtual status_t captureLayers(const LayerCaptureArgs& args,
-                                   const sp<IScreenCaptureListener>& captureListener) = 0;
-
     /* Clears the frame statistics for animations.
      *
      * Requires the ACCESS_SURFACE_FLINGER permission.
@@ -508,18 +515,35 @@
                                              float lightRadius) = 0;
 
     /*
+     * Gets whether a display supports DISPLAY_DECORATION layers.
+     *
+     * displayToken
+     *      The token of the display.
+     * outSupport
+     *      An output parameter for whether/how the display supports
+     *      DISPLAY_DECORATION layers.
+     *
+     * Returns NO_ERROR upon success. Otherwise,
+     *      NAME_NOT_FOUND if the display is invalid, or
+     *      BAD_VALUE      if the output parameter is invalid.
+     */
+    virtual status_t getDisplayDecorationSupport(
+            const sp<IBinder>& displayToken,
+            std::optional<aidl::android::hardware::graphics::common::DisplayDecorationSupport>*
+                    outSupport) const = 0;
+
+    /*
      * Sets the intended frame rate for a surface. See ANativeWindow_setFrameRate() for more info.
      */
     virtual status_t setFrameRate(const sp<IGraphicBufferProducer>& surface, float frameRate,
                                   int8_t compatibility, int8_t changeFrameRateStrategy) = 0;
 
     /*
-     * Acquire a frame rate flexibility token from SurfaceFlinger. While this token is acquired,
-     * surface flinger will freely switch between frame rates in any way it sees fit, regardless of
-     * the current restrictions applied by DisplayManager. This is useful to get consistent behavior
-     * for tests. Release the token by releasing the returned IBinder reference.
+     * Set the override frame rate for a specified uid by GameManagerService.
+     * Passing the frame rate and uid to SurfaceFlinger to update the override mapping
+     * in the scheduler.
      */
-    virtual status_t acquireFrameRateFlexibilityToken(sp<IBinder>* outToken) = 0;
+    virtual status_t setOverrideFrameRate(uid_t uid, float frameRate) = 0;
 
     /*
      * Sets the frame timeline vsync info received from choreographer that corresponds to next
@@ -582,8 +606,8 @@
         GET_DISPLAY_MODES,       // Deprecated. Use GET_DYNAMIC_DISPLAY_INFO instead.
         GET_ACTIVE_DISPLAY_MODE, // Deprecated. Use GET_DYNAMIC_DISPLAY_INFO instead.
         GET_DISPLAY_STATE,
-        CAPTURE_DISPLAY,
-        CAPTURE_LAYERS,
+        CAPTURE_DISPLAY, // Deprecated. Autogenerated by .aidl now.
+        CAPTURE_LAYERS,  // Deprecated. Autogenerated by .aidl now.
         CLEAR_ANIMATION_FRAME_STATS,
         GET_ANIMATION_FRAME_STATS,
         SET_POWER_MODE,
@@ -610,7 +634,7 @@
         GET_DESIRED_DISPLAY_MODE_SPECS,
         GET_DISPLAY_BRIGHTNESS_SUPPORT,
         SET_DISPLAY_BRIGHTNESS,
-        CAPTURE_DISPLAY_BY_ID,
+        CAPTURE_DISPLAY_BY_ID, // Deprecated. Autogenerated by .aidl now.
         NOTIFY_POWER_BOOST,
         SET_GLOBAL_SHADOW_SETTINGS,
         GET_AUTO_LOW_LATENCY_MODE_SUPPORT, // Deprecated. Use GET_DYNAMIC_DISPLAY_INFO instead.
@@ -618,6 +642,7 @@
         GET_GAME_CONTENT_TYPE_SUPPORT, // Deprecated. Use GET_DYNAMIC_DISPLAY_INFO instead.
         SET_GAME_CONTENT_TYPE,
         SET_FRAME_RATE,
+        // Deprecated. Use DisplayManager.setShouldAlwaysRespectAppRequestedMode(true);
         ACQUIRE_FRAME_RATE_FLEXIBILITY_TOKEN,
         SET_FRAME_TIMELINE_INFO,
         ADD_TRANSACTION_TRACE_LISTENER,
@@ -635,6 +660,11 @@
         ADD_WINDOW_INFOS_LISTENER,
         REMOVE_WINDOW_INFOS_LISTENER,
         GET_PRIMARY_PHYSICAL_DISPLAY_ID,
+        GET_DISPLAY_DECORATION_SUPPORT,
+        GET_BOOT_DISPLAY_MODE_SUPPORT,
+        SET_BOOT_DISPLAY_MODE,
+        CLEAR_BOOT_DISPLAY_MODE,
+        SET_OVERRIDE_FRAME_RATE,
         // Always append new enum to the end.
     };
 
diff --git a/libs/gui/include/gui/ITransactionCompletedListener.h b/libs/gui/include/gui/ITransactionCompletedListener.h
index 937095c..a791c66 100644
--- a/libs/gui/include/gui/ITransactionCompletedListener.h
+++ b/libs/gui/include/gui/ITransactionCompletedListener.h
@@ -30,6 +30,7 @@
 #include <cstdint>
 #include <unordered_map>
 #include <unordered_set>
+#include <variant>
 
 namespace android {
 
@@ -130,12 +131,12 @@
     status_t readFromParcel(const Parcel* input) override;
 
     SurfaceStats() = default;
-    SurfaceStats(const sp<IBinder>& sc, nsecs_t time, const sp<Fence>& prevReleaseFence,
-                 uint32_t hint, uint32_t currentMaxAcquiredBuffersCount,
-                 FrameEventHistoryStats frameEventStats, std::vector<JankData> jankData,
-                 ReleaseCallbackId previousReleaseCallbackId)
+    SurfaceStats(const sp<IBinder>& sc, std::variant<nsecs_t, sp<Fence>> acquireTimeOrFence,
+                 const sp<Fence>& prevReleaseFence, uint32_t hint,
+                 uint32_t currentMaxAcquiredBuffersCount, FrameEventHistoryStats frameEventStats,
+                 std::vector<JankData> jankData, ReleaseCallbackId previousReleaseCallbackId)
           : surfaceControl(sc),
-            acquireTime(time),
+            acquireTimeOrFence(std::move(acquireTimeOrFence)),
             previousReleaseFence(prevReleaseFence),
             transformHint(hint),
             currentMaxAcquiredBufferCount(currentMaxAcquiredBuffersCount),
@@ -144,7 +145,7 @@
             previousReleaseCallbackId(previousReleaseCallbackId) {}
 
     sp<IBinder> surfaceControl;
-    nsecs_t acquireTime = -1;
+    std::variant<nsecs_t, sp<Fence>> acquireTimeOrFence = -1;
     sp<Fence> previousReleaseFence;
     uint32_t transformHint = 0;
     uint32_t currentMaxAcquiredBufferCount = 0;
@@ -192,7 +193,6 @@
     virtual void onTransactionCompleted(ListenerStats stats) = 0;
 
     virtual void onReleaseBuffer(ReleaseCallbackId callbackId, sp<Fence> releaseFence,
-                                 uint32_t transformHint,
                                  uint32_t currentMaxAcquiredBufferCount) = 0;
 };
 
diff --git a/libs/gui/include/gui/LayerCaptureArgs.h b/libs/gui/include/gui/LayerCaptureArgs.h
new file mode 100644
index 0000000..05ff9d5
--- /dev/null
+++ b/libs/gui/include/gui/LayerCaptureArgs.h
@@ -0,0 +1,37 @@
+/*
+ * 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 <stdint.h>
+#include <sys/types.h>
+
+#include <gui/DisplayCaptureArgs.h>
+#include <gui/SpHash.h>
+#include <unordered_set>
+
+namespace android::gui {
+
+struct LayerCaptureArgs : CaptureArgs {
+    sp<IBinder> layerHandle;
+    std::unordered_set<sp<IBinder>, SpHash<IBinder>> excludeHandles;
+    bool childrenOnly{false};
+
+    status_t writeToParcel(Parcel* output) const override;
+    status_t readFromParcel(const Parcel* input) override;
+};
+
+}; // namespace android::gui
diff --git a/libs/gui/include/gui/LayerDebugInfo.h b/libs/gui/include/gui/LayerDebugInfo.h
index 8b7d32c..af834d7 100644
--- a/libs/gui/include/gui/LayerDebugInfo.h
+++ b/libs/gui/include/gui/LayerDebugInfo.h
@@ -64,7 +64,6 @@
     int32_t mActiveBufferStride = 0;
     PixelFormat mActiveBufferFormat = PIXEL_FORMAT_NONE;
     int32_t mNumQueuedFrames = -1;
-    bool mRefreshPending = false;
     bool mIsOpaque = false;
     bool mContentDirty = false;
     StretchEffect mStretchEffect = {};
diff --git a/libs/gui/include/gui/LayerMetadata.h b/libs/gui/include/gui/LayerMetadata.h
index de14b3d..27f4d37 100644
--- a/libs/gui/include/gui/LayerMetadata.h
+++ b/libs/gui/include/gui/LayerMetadata.h
@@ -59,4 +59,14 @@
     std::string itemToString(uint32_t key, const char* separator) const;
 };
 
+// Keep in sync with the GameManager.java constants.
+enum class GameMode : int32_t {
+    Unsupported = 0,
+    Standard = 1,
+    Performance = 2,
+    Battery = 3,
+
+    ftl_last = Battery
+};
+
 } // namespace android
diff --git a/libs/gui/include/gui/LayerState.h b/libs/gui/include/gui/LayerState.h
index 03e4aac..0f37dab 100644
--- a/libs/gui/include/gui/LayerState.h
+++ b/libs/gui/include/gui/LayerState.h
@@ -29,13 +29,17 @@
 #include <android/gui/DropInputMode.h>
 #include <android/gui/FocusRequest.h>
 
+#include <gui/DisplayCaptureArgs.h>
 #include <gui/ISurfaceComposer.h>
+#include <gui/LayerCaptureArgs.h>
 #include <gui/LayerMetadata.h>
+#include <gui/SpHash.h>
 #include <gui/SurfaceControl.h>
 #include <gui/WindowInfo.h>
 #include <math/vec3.h>
 #include <ui/BlurRegion.h>
 #include <ui/GraphicTypes.h>
+#include <ui/LayerStack.h>
 #include <ui/Rect.h>
 #include <ui/Region.h>
 #include <ui/Rotation.h>
@@ -57,10 +61,69 @@
     bool isValid() const { return token != nullptr; }
 };
 
+class BufferData : public Parcelable {
+public:
+    virtual ~BufferData() = default;
+    virtual bool hasBuffer() const { return buffer != nullptr; }
+    virtual bool hasSameBuffer(const BufferData& other) const {
+        return buffer == other.buffer && frameNumber == other.frameNumber;
+    }
+    virtual uint32_t getWidth() const { return buffer->getWidth(); }
+    virtual uint32_t getHeight() const { return buffer->getHeight(); }
+    Rect getBounds() const {
+        return {0, 0, static_cast<int32_t>(getWidth()), static_cast<int32_t>(getHeight())};
+    }
+    virtual uint64_t getId() const { return buffer->getId(); }
+    virtual PixelFormat getPixelFormat() const { return buffer->getPixelFormat(); }
+    virtual uint64_t getUsage() const { return buffer->getUsage(); }
+
+    enum class BufferDataChange : uint32_t {
+        fenceChanged = 0x01,
+        frameNumberChanged = 0x02,
+        cachedBufferChanged = 0x04,
+    };
+
+    sp<GraphicBuffer> buffer;
+    sp<Fence> acquireFence;
+
+    // Used by BlastBufferQueue to forward the framenumber generated by the
+    // graphics producer.
+    uint64_t frameNumber = 0;
+    bool hasBarrier = false;
+    uint64_t barrierFrameNumber = 0;
+
+    // Listens to when the buffer is safe to be released. This is used for blast
+    // layers only. The callback includes a release fence as well as the graphic
+    // buffer id to identify the buffer.
+    sp<ITransactionCompletedListener> releaseBufferListener = nullptr;
+
+    // Stores which endpoint the release information should be sent to. We don't want to send the
+    // releaseCallbackId and release fence to all listeners so we store which listener the setBuffer
+    // was called with.
+    sp<IBinder> releaseBufferEndpoint;
+
+    Flags<BufferDataChange> flags;
+
+    client_cache_t cachedBuffer;
+
+    // Generates the release callback id based on the buffer id and frame number.
+    // This is used as an identifier when release callbacks are invoked.
+    ReleaseCallbackId generateReleaseCallbackId() const;
+
+    status_t writeToParcel(Parcel* parcel) const override;
+    status_t readFromParcel(const Parcel* parcel) override;
+};
+
 /*
  * Used to communicate layer information between SurfaceFlinger and its clients.
  */
 struct layer_state_t {
+    enum Permission {
+        ACCESS_SURFACE_FLINGER = 0x1,
+        ROTATE_SURFACE_FLINGER = 0x2,
+        INTERNAL_SYSTEM_WINDOW = 0x4,
+    };
+
     enum {
         eLayerHidden = 0x01,         // SURFACE_HIDDEN in SurfaceControl.java
         eLayerOpaque = 0x02,         // SURFACE_OPAQUE
@@ -69,7 +132,13 @@
         // Queue up BufferStateLayer buffers instead of dropping the oldest buffer when this flag is
         // set. This blocks the client until all the buffers have been presented. If the buffers
         // have presentation timestamps, then we may drop buffers.
-        eEnableBackpressure = 0x100, // ENABLE_BACKPRESSURE
+        eEnableBackpressure = 0x100,       // ENABLE_BACKPRESSURE
+        eLayerIsDisplayDecoration = 0x200, // DISPLAY_DECORATION
+        // Ignore any destination frame set on the layer. This is used when the buffer scaling mode
+        // is freeze and the destination frame is applied asynchronously with the buffer submission.
+        // This is needed to maintain compatibility for SurfaceView scaling behavior.
+        // See SurfaceView scaling behavior for more details.
+        eIgnoreDestinationFrame = 0x400,
     };
 
     enum {
@@ -81,9 +150,9 @@
         eTransparentRegionChanged = 0x00000020,
         eFlagsChanged = 0x00000040,
         eLayerStackChanged = 0x00000080,
-        eReleaseBufferListenerChanged = 0x00000400,
+        /* unused 0x00000400, */
         eShadowRadiusChanged = 0x00000800,
-        eLayerCreated = 0x00001000,
+        /* unused 0x00001000, */
         eBufferCropChanged = 0x00002000,
         eRelativeLayerChanged = 0x00004000,
         eReparent = 0x00008000,
@@ -93,7 +162,7 @@
         eTransformToDisplayInverseChanged = 0x00080000,
         eCropChanged = 0x00100000,
         eBufferChanged = 0x00200000,
-        eAcquireFenceChanged = 0x00400000,
+        /* unused 0x00400000, */
         eDataspaceChanged = 0x00800000,
         eHdrMetadataChanged = 0x01000000,
         eSurfaceDamageRegionChanged = 0x02000000,
@@ -104,7 +173,7 @@
         eInputInfoChanged = 0x40000000,
         eCornerRadiusChanged = 0x80000000,
         eDestinationFrameChanged = 0x1'00000000,
-        eCachedBufferChanged = 0x2'00000000,
+        /* unused = 0x2'00000000, */
         eBackgroundColorChanged = 0x4'00000000,
         eMetadataChanged = 0x8'00000000,
         eColorSpaceAgnosticChanged = 0x10'00000000,
@@ -113,7 +182,7 @@
         eBackgroundBlurRadiusChanged = 0x80'00000000,
         eProducerDisconnect = 0x100'00000000,
         eFixedTransformHintChanged = 0x200'00000000,
-        eFrameNumberChanged = 0x400'00000000,
+        /* unused 0x400'00000000, */
         eBlurRegionsChanged = 0x800'00000000,
         eAutoRefreshChanged = 0x1000'00000000,
         eStretchChanged = 0x2000'00000000,
@@ -128,6 +197,7 @@
     status_t read(const Parcel& input);
     bool hasBufferChanges() const;
     bool hasValidBuffer() const;
+    void sanitize(int32_t permissions);
 
     struct matrix22_t {
         float dsdx{0};
@@ -145,7 +215,7 @@
     int32_t z;
     uint32_t w;
     uint32_t h;
-    uint32_t layerStack;
+    ui::LayerStack layerStack = ui::DEFAULT_LAYER_STACK;
     float alpha;
     uint32_t flags;
     uint32_t mask;
@@ -167,9 +237,7 @@
     uint32_t transform;
     bool transformToDisplayInverse;
     Rect crop;
-    Rect orientedDisplaySpaceRect;
-    sp<GraphicBuffer> buffer;
-    sp<Fence> acquireFence;
+    std::shared_ptr<BufferData> bufferData = nullptr;
     ui::Dataspace dataspace;
     HdrMetadata hdrMetadata;
     Region surfaceDamageRegion;
@@ -180,8 +248,6 @@
 
     sp<gui::WindowInfoHandle> windowInfoHandle = new gui::WindowInfoHandle();
 
-    client_cache_t cachedBuffer;
-
     LayerMetadata metadata;
 
     // The following refer to the alpha, and dataspace, respectively of
@@ -215,10 +281,6 @@
     // otherwise the value will be a valid ui::Rotation.
     ui::Transform::RotationFlags fixedTransformHint;
 
-    // Used by BlastBufferQueue to forward the framenumber generated by the
-    // graphics producer.
-    uint64_t frameNumber;
-
     // Indicates that the consumer should acquire the next frame as soon as it
     // can and not wait for a frame to become available. This is only relevant
     // in shared buffer mode.
@@ -234,22 +296,6 @@
     Rect bufferCrop;
     Rect destinationFrame;
 
-    // Listens to when the buffer is safe to be released. This is used for blast
-    // layers only. The callback includes a release fence as well as the graphic
-    // buffer id to identify the buffer.
-    sp<ITransactionCompletedListener> releaseBufferListener;
-
-    // Keeps track of the release callback id associated with the listener. This
-    // is not sent to the server since the id can be reconstructed there. This
-    // is used to remove the old callback from the client process map if it is
-    // overwritten by another setBuffer call.
-    ReleaseCallbackId releaseCallbackId;
-
-    // Stores which endpoint the release information should be sent to. We don't want to send the
-    // releaseCallbackId and release fence to all listeners so we store which listener the setBuffer
-    // was called with.
-    sp<IBinder> releaseBufferEndpoint;
-
     // Force inputflinger to drop all input events for the layer and its children.
     gui::DropInputMode dropInputMode;
 };
@@ -272,11 +318,12 @@
     DisplayState();
     void merge(const DisplayState& other);
 
-    uint32_t what;
+    uint32_t what = 0;
+    uint32_t flags = 0;
     sp<IBinder> token;
     sp<IGraphicBufferProducer> surface;
-    uint32_t layerStack;
-    uint32_t flags;
+
+    ui::LayerStack layerStack = ui::DEFAULT_LAYER_STACK;
 
     // These states define how layers are projected onto the physical display.
     //
@@ -290,10 +337,11 @@
     // will be additionally rotated by 90 degrees around the origin clockwise and translated by (W,
     // 0).
     ui::Rotation orientation = ui::ROTATION_0;
-    Rect layerStackSpaceRect;
-    Rect orientedDisplaySpaceRect;
+    Rect layerStackSpaceRect = Rect::EMPTY_RECT;
+    Rect orientedDisplaySpaceRect = Rect::EMPTY_RECT;
 
-    uint32_t width, height;
+    uint32_t width = 0;
+    uint32_t height = 0;
 
     status_t write(Parcel& output) const;
     status_t read(const Parcel& input);
@@ -331,56 +379,6 @@
 bool ValidateFrameRate(float frameRate, int8_t compatibility, int8_t changeFrameRateStrategy,
                        const char* functionName, bool privileged = false);
 
-struct CaptureArgs {
-    const static int32_t UNSET_UID = -1;
-    virtual ~CaptureArgs() = default;
-
-    ui::PixelFormat pixelFormat{ui::PixelFormat::RGBA_8888};
-    Rect sourceCrop;
-    float frameScaleX{1};
-    float frameScaleY{1};
-    bool captureSecureLayers{false};
-    int32_t uid{UNSET_UID};
-    // Force capture to be in a color space. If the value is ui::Dataspace::UNKNOWN, the captured
-    // result will be in the display's colorspace.
-    // The display may use non-RGB dataspace (ex. displayP3) that could cause pixel data could be
-    // different from SRGB (byte per color), and failed when checking colors in tests.
-    // NOTE: In normal cases, we want the screen to be captured in display's colorspace.
-    ui::Dataspace dataspace = ui::Dataspace::UNKNOWN;
-
-    // The receiver of the capture can handle protected buffer. A protected buffer has
-    // GRALLOC_USAGE_PROTECTED usage bit and must not be accessed unprotected behaviour.
-    // Any read/write access from unprotected context will result in undefined behaviour.
-    // Protected contents are typically DRM contents. This has no direct implication to the
-    // secure property of the surface, which is specified by the application explicitly to avoid
-    // the contents being accessed/captured by screenshot or unsecure display.
-    bool allowProtected = false;
-
-    bool grayscale = false;
-
-    virtual status_t write(Parcel& output) const;
-    virtual status_t read(const Parcel& input);
-};
-
-struct DisplayCaptureArgs : CaptureArgs {
-    sp<IBinder> displayToken;
-    uint32_t width{0};
-    uint32_t height{0};
-    bool useIdentityTransform{false};
-
-    status_t write(Parcel& output) const override;
-    status_t read(const Parcel& input) override;
-};
-
-struct LayerCaptureArgs : CaptureArgs {
-    sp<IBinder> layerHandle;
-    std::unordered_set<sp<IBinder>, ISurfaceComposer::SpHash<IBinder>> excludeHandles;
-    bool childrenOnly{false};
-
-    status_t write(Parcel& output) const override;
-    status_t read(const Parcel& input) override;
-};
-
 }; // namespace android
 
 #endif // ANDROID_SF_LAYER_STATE_H
diff --git a/services/surfaceflinger/DisplayHardware/Hash.h b/libs/gui/include/gui/SpHash.h
similarity index 66%
copy from services/surfaceflinger/DisplayHardware/Hash.h
copy to libs/gui/include/gui/SpHash.h
index a7b6c71..5162f01 100644
--- a/services/surfaceflinger/DisplayHardware/Hash.h
+++ b/libs/gui/include/gui/SpHash.h
@@ -1,5 +1,5 @@
 /*
- * Copyright 2021 The Android Open Source Project
+ * 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.
@@ -16,12 +16,16 @@
 
 #pragma once
 
-#include <cstdint>
-#include <string_view>
+#include <stdint.h>
+#include <sys/types.h>
 
-namespace android {
+#include <functional>
 
-// CityHash64 implementation that only hashes at most the first 16 characters of the given string.
-uint64_t cityHash64Len0To16(std::string_view sv);
+namespace android::gui {
 
-} // namespace android
\ No newline at end of file
+template <typename T>
+struct SpHash {
+    size_t operator()(const sp<T>& k) const { return std::hash<T*>()(k.get()); }
+};
+
+}; // namespace android::gui
diff --git a/libs/gui/include/gui/Surface.h b/libs/gui/include/gui/Surface.h
index e540351..40d096e 100644
--- a/libs/gui/include/gui/Surface.h
+++ b/libs/gui/include/gui/Surface.h
@@ -429,11 +429,11 @@
     uint32_t mReqHeight;
 
     // mReqFormat is the buffer pixel format that will be requested at the next
-    // deuque operation. It is initialized to PIXEL_FORMAT_RGBA_8888.
+    // dequeue operation. It is initialized to PIXEL_FORMAT_RGBA_8888.
     PixelFormat mReqFormat;
 
     // mReqUsage is the set of buffer usage flags that will be requested
-    // at the next deuque operation. It is initialized to 0.
+    // at the next dequeue operation. It is initialized to 0.
     uint64_t mReqUsage;
 
     // mTimestamp is the timestamp that will be used for the next buffer queue
diff --git a/libs/gui/include/gui/SurfaceComposerClient.h b/libs/gui/include/gui/SurfaceComposerClient.h
index 0d1d1a3..9d03f58 100644
--- a/libs/gui/include/gui/SurfaceComposerClient.h
+++ b/libs/gui/include/gui/SurfaceComposerClient.h
@@ -19,11 +19,13 @@
 #include <stdint.h>
 #include <sys/types.h>
 #include <set>
+#include <thread>
 #include <unordered_map>
 #include <unordered_set>
 
 #include <binder/IBinder.h>
 
+#include <utils/Errors.h>
 #include <utils/RefBase.h>
 #include <utils/Singleton.h>
 #include <utils/SortedVector.h>
@@ -45,36 +47,40 @@
 #include <gui/WindowInfosListenerReporter.h>
 #include <math/vec3.h>
 
+#include <aidl/android/hardware/graphics/common/DisplayDecorationSupport.h>
+
 namespace android {
 
 class HdrCapabilities;
 class ISurfaceComposerClient;
 class IGraphicBufferProducer;
-class IRegionSamplingListener;
 class ITunnelModeEnabledListener;
 class Region;
 
+using gui::DisplayCaptureArgs;
+using gui::IRegionSamplingListener;
+using gui::LayerCaptureArgs;
+
 struct SurfaceControlStats {
-    SurfaceControlStats(const sp<SurfaceControl>& sc, nsecs_t latchTime, nsecs_t acquireTime,
+    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, uint32_t currentMaxAcquiredBufferCount)
+                        uint32_t hint, FrameEventHistoryStats eventStats)
           : surfaceControl(sc),
             latchTime(latchTime),
-            acquireTime(acquireTime),
+            acquireTimeOrFence(std::move(acquireTimeOrFence)),
             presentFence(presentFence),
             previousReleaseFence(prevReleaseFence),
             transformHint(hint),
-            frameEventStats(eventStats),
-            currentMaxAcquiredBufferCount(currentMaxAcquiredBufferCount) {}
+            frameEventStats(eventStats) {}
 
     sp<SurfaceControl> surfaceControl;
     nsecs_t latchTime = -1;
-    nsecs_t acquireTime = -1;
+    std::variant<nsecs_t, sp<Fence>> acquireTimeOrFence = -1;
     sp<Fence> presentFence;
     sp<Fence> previousReleaseFence;
     uint32_t transformHint = 0;
     FrameEventHistoryStats frameEventStats;
-    uint32_t currentMaxAcquiredBufferCount = 0;
 };
 
 using TransactionCompletedCallbackTakesContext =
@@ -86,7 +92,7 @@
                            const std::vector<SurfaceControlStats>& /*stats*/)>;
 using ReleaseBufferCallback =
         std::function<void(const ReleaseCallbackId&, const sp<Fence>& /*releaseFence*/,
-                           uint32_t transformHint, uint32_t currentMaxAcquiredBufferCount)>;
+                           std::optional<uint32_t> currentMaxAcquiredBufferCount)>;
 
 using SurfaceStatsCallback =
         std::function<void(void* /*context*/, nsecs_t /*latchTime*/,
@@ -95,6 +101,22 @@
 
 // ---------------------------------------------------------------------------
 
+class ReleaseCallbackThread {
+public:
+    void addReleaseCallback(const ReleaseCallbackId, sp<Fence>);
+    void threadMain();
+
+private:
+    std::thread mThread;
+    std::mutex mMutex;
+    bool mStarted GUARDED_BY(mMutex) = false;
+    std::condition_variable mReleaseCallbackPending;
+    std::queue<std::tuple<const ReleaseCallbackId, const sp<Fence>>> mCallbackInfos
+            GUARDED_BY(mMutex);
+};
+
+// ---------------------------------------------------------------------------
+
 class SurfaceComposerClient : public RefBase
 {
     friend class Composer;
@@ -151,6 +173,17 @@
     static status_t setActiveColorMode(const sp<IBinder>& display,
             ui::ColorMode colorMode);
 
+    // Gets if boot display mode operations are supported on a device
+    static status_t getBootDisplayModeSupport(bool* support);
+    // 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
+    static status_t clearBootDisplayMode(const sp<IBinder>& display);
+
+    // Sets the frame rate of a particular app (uid). This is currently called
+    // by GameManager.
+    static status_t setOverrideFrameRate(uid_t uid, float frameRate);
+
     // Switches on/off Auto Low Latency Mode on the connected display. This should only be
     // called if the connected display supports Auto Low Latency Mode as reported by
     // #getAutoLowLatencyModeSupport
@@ -256,6 +289,18 @@
     static status_t setGlobalShadowSettings(const half4& ambientColor, const half4& spotColor,
                                             float lightPosY, float lightPosZ, float lightRadius);
 
+    /*
+     * Returns whether and how a display supports DISPLAY_DECORATION layers.
+     *
+     * displayToken
+     *      The token of the display.
+     *
+     * Returns how a display supports DISPLAY_DECORATION layers, or nullopt if
+     * it does not.
+     */
+    static std::optional<aidl::android::hardware::graphics::common::DisplayDecorationSupport>
+    getDisplayDecorationSupport(const sp<IBinder>& displayToken);
+
     // ------------------------------------------------------------------------
     // surface creation / destruction
 
@@ -350,8 +395,7 @@
 
     class Transaction : public Parcelable {
     private:
-        static std::atomic<uint32_t> idCounter;
-        int64_t generateId();
+        void releaseBufferIfOverwriting(const layer_state_t& state);
 
     protected:
         std::unordered_map<sp<IBinder>, ComposerState, IBinderHash> mComposerStates;
@@ -401,9 +445,7 @@
 
         void cacheBuffers();
         void registerSurfaceControlForCallback(const sp<SurfaceControl>& sc);
-        void setReleaseBufferCallback(layer_state_t*, const ReleaseCallbackId&,
-                                      ReleaseBufferCallback);
-        void removeReleaseBufferCallback(layer_state_t*);
+        void setReleaseBufferCallback(BufferData*, ReleaseBufferCallback);
 
     public:
         Transaction();
@@ -419,7 +461,7 @@
         // Clears the contents of the transaction without applying it.
         void clear();
 
-        status_t apply(bool synchronous = false);
+        status_t apply(bool synchronous = false, bool oneWay = false);
         // Merge another transaction in to this one, clearing other
         // as if it had been applied.
         Transaction& merge(Transaction&& other);
@@ -459,7 +501,7 @@
                                              int backgroundBlurRadius);
         Transaction& setBlurRegions(const sp<SurfaceControl>& sc,
                                     const std::vector<BlurRegion>& regions);
-        Transaction& setLayerStack(const sp<SurfaceControl>& sc, uint32_t layerStack);
+        Transaction& setLayerStack(const sp<SurfaceControl>&, ui::LayerStack);
         Transaction& setMetadata(const sp<SurfaceControl>& sc, uint32_t key, const Parcel& p);
 
         /// Reparents the current layer to the new parent handle. The new parent must not be null.
@@ -475,10 +517,31 @@
         Transaction& setTransformToDisplayInverse(const sp<SurfaceControl>& sc,
                                                   bool transformToDisplayInverse);
         Transaction& setBuffer(const sp<SurfaceControl>& sc, const sp<GraphicBuffer>& buffer,
-                               const ReleaseCallbackId& id = ReleaseCallbackId::INVALID_ID,
+                               const std::optional<sp<Fence>>& fence = std::nullopt,
+                               const std::optional<uint64_t>& frameNumber = std::nullopt,
                                ReleaseBufferCallback callback = nullptr);
-        Transaction& setCachedBuffer(const sp<SurfaceControl>& sc, int32_t bufferId);
-        Transaction& setAcquireFence(const sp<SurfaceControl>& sc, const sp<Fence>& fence);
+        std::shared_ptr<BufferData> getAndClearBuffer(const sp<SurfaceControl>& sc);
+
+        /**
+         * If this transaction, has a a buffer set for the given SurfaceControl
+         * mark that buffer as ordered after a given barrierFrameNumber.
+         *
+         * SurfaceFlinger will refuse to apply this transaction until after
+         * the frame in barrierFrameNumber has been applied. This transaction may
+         * be applied in the same frame as the barrier buffer or after.
+         *
+         * This is only designed to be used to handle switches between multiple
+         * apply tokens, as explained in the comment for BLASTBufferQueue::mAppliedLastTransaction.
+         *
+         * Has to be called after setBuffer.
+         *
+         * WARNING:
+         * This API is very dangerous to the caller, as if you invoke it without
+         * a frameNumber you have not yet submitted, you can dead-lock your
+         * SurfaceControl's transaction queue.
+         */
+        Transaction& setBufferHasBarrier(const sp<SurfaceControl>& sc,
+                                         uint64_t barrierFrameNumber);
         Transaction& setDataspace(const sp<SurfaceControl>& sc, ui::Dataspace dataspace);
         Transaction& setHdrMetadata(const sp<SurfaceControl>& sc, const HdrMetadata& hdrMetadata);
         Transaction& setSurfaceDamageRegion(const sp<SurfaceControl>& sc,
@@ -503,8 +566,6 @@
 
         // ONLY FOR BLAST ADAPTER
         Transaction& notifyProducerDisconnect(const sp<SurfaceControl>& sc);
-        // Set the framenumber generated by the graphics producer to mimic BufferQueue behaviour.
-        Transaction& setFrameNumber(const sp<SurfaceControl>& sc, uint64_t frameNumber);
 
         Transaction& setInputWindowInfo(const sp<SurfaceControl>& sc, const gui::WindowInfo& info);
         Transaction& setFocusedWindow(const gui::FocusRequest& request);
@@ -570,7 +631,7 @@
         status_t setDisplaySurface(const sp<IBinder>& token,
                 const sp<IGraphicBufferProducer>& bufferProducer);
 
-        void setDisplayLayerStack(const sp<IBinder>& token, uint32_t layerStack);
+        void setDisplayLayerStack(const sp<IBinder>& token, ui::LayerStack);
 
         void setDisplayFlags(const sp<IBinder>& token, uint32_t flags);
 
@@ -590,6 +651,14 @@
         void setAnimationTransaction();
         void setEarlyWakeupStart();
         void setEarlyWakeupEnd();
+
+        /**
+         * Strip the transaction of all permissioned requests, required when
+         * accepting transactions across process boundaries.
+         *
+         * TODO (b/213644870): Remove all permissioned things from Transaction
+         */
+        void sanitize();
     };
 
     status_t clearLayerFrameStats(const sp<IBinder>& token) const;
@@ -627,9 +696,15 @@
     static status_t removeTunnelModeEnabledListener(
             const sp<gui::ITunnelModeEnabledListener>& listener);
 
-    status_t addWindowInfosListener(const sp<gui::WindowInfosListener>& windowInfosListener);
+    status_t addWindowInfosListener(
+            const sp<gui::WindowInfosListener>& windowInfosListener,
+            std::pair<std::vector<gui::WindowInfo>, std::vector<gui::DisplayInfo>>* outInitialInfo =
+                    nullptr);
     status_t removeWindowInfosListener(const sp<gui::WindowInfosListener>& windowInfosListener);
 
+protected:
+    ReleaseCallbackThread mReleaseCallbackThread;
+
 private:
     virtual void onFirstRef();
 
@@ -642,12 +717,9 @@
 
 class ScreenshotClient {
 public:
-    static status_t captureDisplay(const DisplayCaptureArgs& captureArgs,
-                                   const sp<IScreenCaptureListener>& captureListener);
-    static status_t captureDisplay(uint64_t displayOrLayerStack,
-                                   const sp<IScreenCaptureListener>& captureListener);
-    static status_t captureLayers(const LayerCaptureArgs& captureArgs,
-                                  const sp<IScreenCaptureListener>& captureListener);
+    static status_t captureDisplay(const DisplayCaptureArgs&, const sp<IScreenCaptureListener>&);
+    static status_t captureDisplay(DisplayId, const sp<IScreenCaptureListener>&);
+    static status_t captureLayers(const LayerCaptureArgs&, const sp<IScreenCaptureListener>&);
 };
 
 // ---------------------------------------------------------------------------
@@ -733,13 +805,14 @@
     void removeSurfaceStatsListener(void* context, void* cookie);
 
     void setReleaseBufferCallback(const ReleaseCallbackId&, ReleaseBufferCallback);
-    void removeReleaseBufferCallback(const ReleaseCallbackId&);
 
     // BnTransactionCompletedListener overrides
     void onTransactionCompleted(ListenerStats stats) override;
-    void onReleaseBuffer(ReleaseCallbackId, sp<Fence> releaseFence, uint32_t transformHint,
+    void onReleaseBuffer(ReleaseCallbackId, sp<Fence> releaseFence,
                          uint32_t currentMaxAcquiredBufferCount) override;
 
+    void removeReleaseBufferCallback(const ReleaseCallbackId& callbackId);
+
     // For Testing Only
     static void setInstance(const sp<TransactionCompletedListener>&);
 
diff --git a/libs/gui/include/gui/SurfaceControl.h b/libs/gui/include/gui/SurfaceControl.h
index 9ee4636..b72cf83 100644
--- a/libs/gui/include/gui/SurfaceControl.h
+++ b/libs/gui/include/gui/SurfaceControl.h
@@ -19,6 +19,7 @@
 
 #include <stdint.h>
 #include <sys/types.h>
+#include <optional>
 
 #include <utils/RefBase.h>
 #include <utils/threads.h>
@@ -98,6 +99,8 @@
 
     sp<SurfaceControl> getParentingLayer();
 
+    uint64_t resolveFrameNumber(const std::optional<uint64_t>& frameNumber);
+
 private:
     // can't be copied
     SurfaceControl& operator = (SurfaceControl& rhs);
@@ -118,12 +121,13 @@
     mutable sp<Surface>         mSurfaceData;
     mutable sp<BLASTBufferQueue> mBbq;
     mutable sp<SurfaceControl> mBbqChild;
-    int32_t mLayerId;
-    uint32_t mTransformHint;
-    uint32_t mWidth;
-    uint32_t mHeight;
-    PixelFormat mFormat;
-    uint32_t mCreateFlags;
+    int32_t mLayerId = 0;
+    uint32_t mTransformHint = 0;
+    uint32_t mWidth = 0;
+    uint32_t mHeight = 0;
+    PixelFormat mFormat = PIXEL_FORMAT_NONE;
+    uint32_t mCreateFlags = 0;
+    uint64_t mFallbackFrameNumber = 100;
 };
 
 }; // namespace android
diff --git a/libs/gui/include/gui/VsyncEventData.h b/libs/gui/include/gui/VsyncEventData.h
new file mode 100644
index 0000000..8e99539
--- /dev/null
+++ b/libs/gui/include/gui/VsyncEventData.h
@@ -0,0 +1,66 @@
+/*
+ * 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 <gui/FrameTimelineInfo.h>
+
+#include <array>
+
+namespace android::gui {
+// Plain Old Data (POD) vsync data structure. For example, it can be easily used in the
+// DisplayEventReceiver::Event union.
+struct VsyncEventData {
+    // Max amount of frame timelines is arbitrarily set to be reasonable.
+    static constexpr int64_t kFrameTimelinesLength = 7;
+
+    // The current frame interval in ns when this frame was scheduled.
+    int64_t frameInterval;
+
+    // Index into the frameTimelines that represents the platform's preferred frame timeline.
+    uint32_t preferredFrameTimelineIndex;
+
+    struct alignas(8) FrameTimeline {
+        // The Vsync Id corresponsing to this vsync event. This will be used to
+        // populate ISurfaceComposer::setFrameTimelineVsync and
+        // SurfaceComposerClient::setFrameTimelineVsync
+        int64_t vsyncId;
+
+        // The deadline in CLOCK_MONOTONIC in nanos that the app needs to complete its
+        // frame by (both on the CPU and the GPU).
+        int64_t deadlineTimestamp;
+
+        // The anticipated Vsync presentation time in nanos.
+        int64_t expectedPresentationTime;
+    } frameTimelines[kFrameTimelinesLength]; // Sorted possible frame timelines.
+
+    // Gets the preferred frame timeline's vsync ID.
+    int64_t preferredVsyncId() const;
+
+    // Gets the preferred frame timeline's deadline timestamp.
+    int64_t preferredDeadlineTimestamp() const;
+
+    // Gets the preferred frame timeline's expected vsync timestamp.
+    int64_t preferredExpectedPresentationTime() const;
+};
+
+struct ParcelableVsyncEventData : public Parcelable {
+    VsyncEventData vsync;
+
+    status_t readFromParcel(const Parcel*) override;
+    status_t writeToParcel(Parcel*) const override;
+};
+} // namespace android::gui
diff --git a/libs/gui/include/gui/WindowInfo.h b/libs/gui/include/gui/WindowInfo.h
index 4727740..ef0b98b 100644
--- a/libs/gui/include/gui/WindowInfo.h
+++ b/libs/gui/include/gui/WindowInfo.h
@@ -17,6 +17,7 @@
 #pragma once
 
 #include <android/gui/TouchOcclusionMode.h>
+#include <android/os/InputConfig.h>
 #include <binder/Parcel.h>
 #include <binder/Parcelable.h>
 #include <ftl/Flags.h>
@@ -71,8 +72,9 @@
         SLIPPERY = 0x20000000,
         LAYOUT_ATTACHED_IN_DECOR = 0x40000000,
         DRAWS_SYSTEM_BAR_BACKGROUNDS = 0x80000000,
-    }; // Window types from WindowManager.LayoutParams
+    };
 
+    // Window types from WindowManager.LayoutParams
     enum class Type : int32_t {
         UNKNOWN = 0,
         FIRST_APPLICATION_WINDOW = 1,
@@ -87,45 +89,89 @@
         APPLICATION_ATTACHED_DIALOG = FIRST_SUB_WINDOW + 3,
         APPLICATION_MEDIA_OVERLAY = FIRST_SUB_WINDOW + 4,
         LAST_SUB_WINDOW = 1999,
-        FIRST_SYSTEM_WINDOW = 2000,
-        STATUS_BAR = FIRST_SYSTEM_WINDOW,
-        SEARCH_BAR = FIRST_SYSTEM_WINDOW + 1,
-        PHONE = FIRST_SYSTEM_WINDOW + 2,
-        SYSTEM_ALERT = FIRST_SYSTEM_WINDOW + 3,
-        KEYGUARD = FIRST_SYSTEM_WINDOW + 4,
-        TOAST = FIRST_SYSTEM_WINDOW + 5,
-        SYSTEM_OVERLAY = FIRST_SYSTEM_WINDOW + 6,
-        PRIORITY_PHONE = FIRST_SYSTEM_WINDOW + 7,
-        SYSTEM_DIALOG = FIRST_SYSTEM_WINDOW + 8,
-        KEYGUARD_DIALOG = FIRST_SYSTEM_WINDOW + 9,
-        SYSTEM_ERROR = FIRST_SYSTEM_WINDOW + 10,
-        INPUT_METHOD = FIRST_SYSTEM_WINDOW + 11,
-        INPUT_METHOD_DIALOG = FIRST_SYSTEM_WINDOW + 12,
-        WALLPAPER = FIRST_SYSTEM_WINDOW + 13,
-        STATUS_BAR_PANEL = FIRST_SYSTEM_WINDOW + 14,
-        SECURE_SYSTEM_OVERLAY = FIRST_SYSTEM_WINDOW + 15,
-        DRAG = FIRST_SYSTEM_WINDOW + 16,
-        STATUS_BAR_SUB_PANEL = FIRST_SYSTEM_WINDOW + 17,
-        POINTER = FIRST_SYSTEM_WINDOW + 18,
-        NAVIGATION_BAR = FIRST_SYSTEM_WINDOW + 19,
-        VOLUME_OVERLAY = FIRST_SYSTEM_WINDOW + 20,
-        BOOT_PROGRESS = FIRST_SYSTEM_WINDOW + 21,
-        INPUT_CONSUMER = FIRST_SYSTEM_WINDOW + 22,
-        NAVIGATION_BAR_PANEL = FIRST_SYSTEM_WINDOW + 24,
-        MAGNIFICATION_OVERLAY = FIRST_SYSTEM_WINDOW + 27,
-        ACCESSIBILITY_OVERLAY = FIRST_SYSTEM_WINDOW + 32,
-        DOCK_DIVIDER = FIRST_SYSTEM_WINDOW + 34,
-        ACCESSIBILITY_MAGNIFICATION_OVERLAY = FIRST_SYSTEM_WINDOW + 39,
-        NOTIFICATION_SHADE = FIRST_SYSTEM_WINDOW + 40,
+
+#define FIRST_SYSTEM_WINDOW_ 2000
+
+        STATUS_BAR = FIRST_SYSTEM_WINDOW_,
+        SEARCH_BAR = FIRST_SYSTEM_WINDOW_ + 1,
+        PHONE = FIRST_SYSTEM_WINDOW_ + 2,
+        SYSTEM_ALERT = FIRST_SYSTEM_WINDOW_ + 3,
+        KEYGUARD = FIRST_SYSTEM_WINDOW_ + 4,
+        TOAST = FIRST_SYSTEM_WINDOW_ + 5,
+        SYSTEM_OVERLAY = FIRST_SYSTEM_WINDOW_ + 6,
+        PRIORITY_PHONE = FIRST_SYSTEM_WINDOW_ + 7,
+        SYSTEM_DIALOG = FIRST_SYSTEM_WINDOW_ + 8,
+        KEYGUARD_DIALOG = FIRST_SYSTEM_WINDOW_ + 9,
+        SYSTEM_ERROR = FIRST_SYSTEM_WINDOW_ + 10,
+        INPUT_METHOD = FIRST_SYSTEM_WINDOW_ + 11,
+        INPUT_METHOD_DIALOG = FIRST_SYSTEM_WINDOW_ + 12,
+        WALLPAPER = FIRST_SYSTEM_WINDOW_ + 13,
+        STATUS_BAR_PANEL = FIRST_SYSTEM_WINDOW_ + 14,
+        SECURE_SYSTEM_OVERLAY = FIRST_SYSTEM_WINDOW_ + 15,
+        DRAG = FIRST_SYSTEM_WINDOW_ + 16,
+        STATUS_BAR_SUB_PANEL = FIRST_SYSTEM_WINDOW_ + 17,
+        POINTER = FIRST_SYSTEM_WINDOW_ + 18,
+        NAVIGATION_BAR = FIRST_SYSTEM_WINDOW_ + 19,
+        VOLUME_OVERLAY = FIRST_SYSTEM_WINDOW_ + 20,
+        BOOT_PROGRESS = FIRST_SYSTEM_WINDOW_ + 21,
+        INPUT_CONSUMER = FIRST_SYSTEM_WINDOW_ + 22,
+        NAVIGATION_BAR_PANEL = FIRST_SYSTEM_WINDOW_ + 24,
+        MAGNIFICATION_OVERLAY = FIRST_SYSTEM_WINDOW_ + 27,
+        ACCESSIBILITY_OVERLAY = FIRST_SYSTEM_WINDOW_ + 32,
+        DOCK_DIVIDER = FIRST_SYSTEM_WINDOW_ + 34,
+        ACCESSIBILITY_MAGNIFICATION_OVERLAY = FIRST_SYSTEM_WINDOW_ + 39,
+        NOTIFICATION_SHADE = FIRST_SYSTEM_WINDOW_ + 40,
+
+        FIRST_SYSTEM_WINDOW = FIRST_SYSTEM_WINDOW_,
         LAST_SYSTEM_WINDOW = 2999,
+
+#undef FIRST_SYSTEM_WINDOW_
+
+        // Small range to limit LUT size.
+        ftl_first = FIRST_SYSTEM_WINDOW,
+        ftl_last = FIRST_SYSTEM_WINDOW + 15
     };
 
-    enum class Feature {
-        DISABLE_TOUCH_PAD_GESTURES = 1u << 0,
-        NO_INPUT_CHANNEL = 1u << 1,
-        DISABLE_USER_ACTIVITY = 1u << 2,
-        DROP_INPUT = 1u << 3,
-        DROP_INPUT_IF_OBSCURED = 1u << 4,
+    // Flags used to determine configuration of this input window.
+    // This is a conversion of os::InputConfig to an enum backed by an unsigned
+    // type. This indicates that they are flags, so it can be used with ftl/enum.h.
+    enum class InputConfig : uint32_t {
+        // clang-format off
+        DEFAULT =
+                static_cast<uint32_t>(os::InputConfig::DEFAULT),
+        NO_INPUT_CHANNEL =
+                static_cast<uint32_t>(os::InputConfig::NO_INPUT_CHANNEL),
+        NOT_VISIBLE =
+                static_cast<uint32_t>(os::InputConfig::NOT_VISIBLE),
+        NOT_FOCUSABLE =
+                static_cast<uint32_t>(os::InputConfig::NOT_FOCUSABLE),
+        NOT_TOUCHABLE =
+                static_cast<uint32_t>(os::InputConfig::NOT_TOUCHABLE),
+        PREVENT_SPLITTING =
+                static_cast<uint32_t>(os::InputConfig::PREVENT_SPLITTING),
+        DUPLICATE_TOUCH_TO_WALLPAPER =
+                static_cast<uint32_t>(os::InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER),
+        IS_WALLPAPER =
+                static_cast<uint32_t>(os::InputConfig::IS_WALLPAPER),
+        PAUSE_DISPATCHING =
+                static_cast<uint32_t>(os::InputConfig::PAUSE_DISPATCHING),
+        TRUSTED_OVERLAY =
+                static_cast<uint32_t>(os::InputConfig::TRUSTED_OVERLAY),
+        WATCH_OUTSIDE_TOUCH =
+                static_cast<uint32_t>(os::InputConfig::WATCH_OUTSIDE_TOUCH),
+        SLIPPERY =
+                static_cast<uint32_t>(os::InputConfig::SLIPPERY),
+        DISABLE_USER_ACTIVITY =
+                static_cast<uint32_t>(os::InputConfig::DISABLE_USER_ACTIVITY),
+        DROP_INPUT =
+                static_cast<uint32_t>(os::InputConfig::DROP_INPUT),
+        DROP_INPUT_IF_OBSCURED =
+                static_cast<uint32_t>(os::InputConfig::DROP_INPUT_IF_OBSCURED),
+        SPY =
+                static_cast<uint32_t>(os::InputConfig::SPY),
+        INTERCEPTS_STYLUS =
+                static_cast<uint32_t>(os::InputConfig::INTERCEPTS_STYLUS),
+        // clang-format on
     };
 
     /* These values are filled in by the WM and passed through SurfaceFlinger
@@ -141,8 +187,6 @@
     // This uniquely identifies the input window.
     int32_t id = -1;
     std::string name;
-    Flags<Flag> flags;
-    Type type = Type::UNKNOWN;
     std::chrono::nanoseconds dispatchingTimeout = std::chrono::seconds(5);
 
     /* These values are filled in by SurfaceFlinger. */
@@ -170,39 +214,28 @@
     // Transform applied to individual windows.
     ui::Transform transform;
 
-    // Display orientation as ui::Transform::RotationFlags. Used for compatibility raw coordinates.
-    uint32_t displayOrientation = ui::Transform::ROT_0;
-
-    // Display size in its natural rotation. Used to rotate raw coordinates for compatibility.
-    int32_t displayWidth = 0;
-    int32_t displayHeight = 0;
-
     /*
      * This is filled in by the WM relative to the frame and then translated
      * to absolute coordinates by SurfaceFlinger once the frame is computed.
      */
     Region touchableRegion;
-    bool visible = false;
-    bool focusable = false;
-    bool hasWallpaper = false;
-    bool paused = false;
-    /* This flag is set when the window is of a trusted type that is allowed to silently
-     * overlay other windows for the purpose of implementing the secure views feature.
-     * Trusted overlays, such as IME windows, can partly obscure other windows without causing
-     * motion events to be delivered to them with AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED.
-     */
-    bool trustedOverlay = false;
+
     TouchOcclusionMode touchOcclusionMode = TouchOcclusionMode::BLOCK_UNTRUSTED;
     int32_t ownerPid = -1;
     int32_t ownerUid = -1;
     std::string packageName;
-    Flags<Feature> inputFeatures;
+    Flags<InputConfig> inputConfig;
     int32_t displayId = ADISPLAY_ID_NONE;
-    int32_t portalToDisplayId = ADISPLAY_ID_NONE;
     InputApplicationInfo applicationInfo;
     bool replaceTouchableRegionWithCrop = false;
     wp<IBinder> touchableRegionCropHandle;
 
+    // The window's layout params flags and type set by WM.
+    Type layoutParamsType = Type::UNKNOWN;
+    Flags<Flag> layoutParamsFlags;
+
+    void setInputConfig(Flags<InputConfig> config, bool value);
+
     void addTouchableRegion(const Rect& region);
 
     bool touchableRegionContainsPoint(int32_t x, int32_t y) const;
@@ -211,6 +244,10 @@
 
     bool supportsSplitTouch() const;
 
+    bool isSpy() const;
+
+    bool interceptsStylus() const;
+
     bool overlaps(const WindowInfo* other) const;
 
     bool operator==(const WindowInfo& inputChannel) const;
@@ -267,4 +304,4 @@
 
     WindowInfo mInfo;
 };
-} // namespace android::gui
\ No newline at end of file
+} // namespace android::gui
diff --git a/libs/gui/include/gui/WindowInfosListener.h b/libs/gui/include/gui/WindowInfosListener.h
index 8a70b9b..a18a498 100644
--- a/libs/gui/include/gui/WindowInfosListener.h
+++ b/libs/gui/include/gui/WindowInfosListener.h
@@ -16,6 +16,7 @@
 
 #pragma once
 
+#include <gui/DisplayInfo.h>
 #include <gui/WindowInfo.h>
 #include <utils/RefBase.h>
 
@@ -23,6 +24,7 @@
 
 class WindowInfosListener : public virtual RefBase {
 public:
-    virtual void onWindowInfosChanged(const std::vector<WindowInfo>& /*windowInfos*/) = 0;
+    virtual void onWindowInfosChanged(const std::vector<WindowInfo>&,
+                                      const std::vector<DisplayInfo>&) = 0;
 };
 } // namespace android::gui
\ No newline at end of file
diff --git a/libs/gui/include/gui/WindowInfosListenerReporter.h b/libs/gui/include/gui/WindowInfosListenerReporter.h
index 7cb96e0..3b4aed4 100644
--- a/libs/gui/include/gui/WindowInfosListenerReporter.h
+++ b/libs/gui/include/gui/WindowInfosListenerReporter.h
@@ -20,8 +20,8 @@
 #include <android/gui/IWindowInfosReportedListener.h>
 #include <binder/IBinder.h>
 #include <gui/ISurfaceComposer.h>
+#include <gui/SpHash.h>
 #include <gui/WindowInfosListener.h>
-#include <utils/Mutex.h>
 #include <unordered_set>
 
 namespace android {
@@ -30,19 +30,23 @@
 class WindowInfosListenerReporter : public gui::BnWindowInfosListener {
 public:
     static sp<WindowInfosListenerReporter> getInstance();
-    binder::Status onWindowInfosChanged(const std::vector<gui::WindowInfo>& windowInfos,
+    binder::Status onWindowInfosChanged(const std::vector<gui::WindowInfo>&,
+                                        const std::vector<gui::DisplayInfo>&,
                                         const sp<gui::IWindowInfosReportedListener>&) override;
 
-    status_t addWindowInfosListener(const sp<gui::WindowInfosListener>& windowInfosListener,
-                                    const sp<ISurfaceComposer>&);
+    status_t addWindowInfosListener(
+            const sp<gui::WindowInfosListener>& windowInfosListener, const sp<ISurfaceComposer>&,
+            std::pair<std::vector<gui::WindowInfo>, std::vector<gui::DisplayInfo>>* outInitialInfo);
     status_t removeWindowInfosListener(const sp<gui::WindowInfosListener>& windowInfosListener,
-                                       const sp<ISurfaceComposer>&);
+                                       const sp<ISurfaceComposer>& surfaceComposer);
     void reconnect(const sp<ISurfaceComposer>&);
 
 private:
     std::mutex mListenersMutex;
-    std::unordered_set<sp<gui::WindowInfosListener>,
-                       ISurfaceComposer::SpHash<gui::WindowInfosListener>>
+    std::unordered_set<sp<gui::WindowInfosListener>, SpHash<gui::WindowInfosListener>>
             mWindowInfosListeners GUARDED_BY(mListenersMutex);
+
+    std::vector<gui::WindowInfo> mLastWindowInfos GUARDED_BY(mListenersMutex);
+    std::vector<gui::DisplayInfo> mLastDisplayInfos GUARDED_BY(mListenersMutex);
 };
-} // namespace android
\ No newline at end of file
+} // namespace android
diff --git a/libs/gui/include/gui/test/CallbackUtils.h b/libs/gui/include/gui/test/CallbackUtils.h
index d2e0426..62d1496 100644
--- a/libs/gui/include/gui/test/CallbackUtils.h
+++ b/libs/gui/include/gui/test/CallbackUtils.h
@@ -134,12 +134,15 @@
 
         void verifySurfaceControlStats(const SurfaceControlStats& surfaceControlStats,
                                        nsecs_t latchTime) const {
-            const auto& [surfaceControl, latch, acquireTime, presentFence, previousReleaseFence,
-                         transformHint, frameEvents, ignore] = surfaceControlStats;
+            const auto& [surfaceControl, latch, acquireTimeOrFence, presentFence,
+                         previousReleaseFence, transformHint, frameEvents] = surfaceControlStats;
 
-            ASSERT_EQ(acquireTime > 0, mBufferResult == ExpectedResult::Buffer::ACQUIRED)
+            ASSERT_TRUE(std::holds_alternative<nsecs_t>(acquireTimeOrFence));
+            ASSERT_EQ(std::get<nsecs_t>(acquireTimeOrFence) > 0,
+                      mBufferResult == ExpectedResult::Buffer::ACQUIRED)
                     << "bad acquire time";
-            ASSERT_LE(acquireTime, latchTime) << "acquire time should be <= latch time";
+            ASSERT_LE(std::get<nsecs_t>(acquireTimeOrFence), latchTime)
+                    << "acquire time should be <= latch time";
 
             if (mPreviousBufferResult == ExpectedResult::PreviousBuffer::RELEASED) {
                 ASSERT_NE(previousReleaseFence, nullptr)
diff --git a/libs/gui/include/private/gui/ComposerService.h b/libs/gui/include/private/gui/ComposerService.h
index fa1071a..05ed0a0 100644
--- a/libs/gui/include/private/gui/ComposerService.h
+++ b/libs/gui/include/private/gui/ComposerService.h
@@ -37,7 +37,7 @@
 // Users of this class should not retain the value from
 // getComposerService() for an extended period.
 //
-// (It's not clear that using Singleton is useful here anymore.)
+// (TODO: b/219785927, It's not clear that using Singleton is useful here anymore.)
 class ComposerService : public Singleton<ComposerService>
 {
     sp<ISurfaceComposer> mComposerService;
diff --git a/libs/gui/include/private/gui/ComposerServiceAIDL.h b/libs/gui/include/private/gui/ComposerServiceAIDL.h
new file mode 100644
index 0000000..fee37ee
--- /dev/null
+++ b/libs/gui/include/private/gui/ComposerServiceAIDL.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <stdint.h>
+#include <sys/types.h>
+
+#include <android/gui/ISurfaceComposer.h>
+
+#include <utils/Singleton.h>
+#include <utils/StrongPointer.h>
+
+namespace android {
+
+// ---------------------------------------------------------------------------
+
+// ---------------------------------------------------------------------------
+
+// This holds our connection to the composer service (i.e. SurfaceFlinger).
+// If the remote side goes away, we will re-establish the connection.
+// Users of this class should not retain the value from
+// getComposerService() for an extended period.
+//
+// (TODO: b/219785927, It's not clear that using Singleton is useful here anymore.)
+class ComposerServiceAIDL : public Singleton<ComposerServiceAIDL> {
+    sp<gui::ISurfaceComposer> mComposerService;
+    sp<IBinder::DeathRecipient> mDeathObserver;
+    mutable std::mutex mMutex;
+
+    ComposerServiceAIDL();
+    bool connectLocked();
+    void composerServiceDied();
+    friend class Singleton<ComposerServiceAIDL>;
+
+public:
+    // Get a connection to the Composer Service.  This will block until
+    // a connection is established. Returns null if permission is denied.
+    static sp<gui::ISurfaceComposer> getComposerService();
+};
+
+// ---------------------------------------------------------------------------
+}; // namespace android
diff --git a/libs/gui/sysprop/Android.bp b/libs/gui/sysprop/Android.bp
index bddb0ac..cc33e4c 100644
--- a/libs/gui/sysprop/Android.bp
+++ b/libs/gui/sysprop/Android.bp
@@ -16,4 +16,9 @@
     cpp: {
         min_sdk_version: "29",
     },
+    apex_available: [
+        "//apex_available:platform",
+        "com.android.media.swcodec",
+        "test_com.android.media.swcodec",
+    ],
 }
diff --git a/libs/gui/tests/Android.bp b/libs/gui/tests/Android.bp
index 3d26c3d..e58543a 100644
--- a/libs/gui/tests/Android.bp
+++ b/libs/gui/tests/Android.bp
@@ -27,6 +27,7 @@
         "BufferQueue_test.cpp",
         "CpuConsumer_test.cpp",
         "EndToEndNativeInputTest.cpp",
+        "DisplayInfo_test.cpp",
         "DisplayedContentSampling_test.cpp",
         "FillBuffer.cpp",
         "GLTest.cpp",
@@ -43,6 +44,7 @@
         "SurfaceTextureMultiContextGL_test.cpp",
         "Surface_test.cpp",
         "TextureRenderer.cpp",
+        "VsyncEventData_test.cpp",
         "WindowInfo_test.cpp",
     ],
 
@@ -62,7 +64,7 @@
         "libinput",
         "libui",
         "libutils",
-        "libnativewindow"
+        "libnativewindow",
     ],
 
     header_libs: ["libsurfaceflinger_headers"],
@@ -117,7 +119,7 @@
         "libgui",
         "libui",
         "libutils",
-        "libbufferhubqueue",  // TODO(b/70046255): Remove these once BufferHub is integrated into libgui.
+        "libbufferhubqueue", // TODO(b/70046255): Remove these once BufferHub is integrated into libgui.
         "libpdx_default_transport",
     ],
 
@@ -146,5 +148,5 @@
         "liblog",
         "libui",
         "libutils",
-    ]
+    ],
 }
diff --git a/libs/gui/tests/BLASTBufferQueue_test.cpp b/libs/gui/tests/BLASTBufferQueue_test.cpp
index fc75485..cb7e94c 100644
--- a/libs/gui/tests/BLASTBufferQueue_test.cpp
+++ b/libs/gui/tests/BLASTBufferQueue_test.cpp
@@ -29,6 +29,7 @@
 #include <gui/SyncScreenCaptureListener.h>
 #include <gui/test/CallbackUtils.h>
 #include <private/gui/ComposerService.h>
+#include <private/gui/ComposerServiceAIDL.h>
 #include <ui/DisplayMode.h>
 #include <ui/GraphicBuffer.h>
 #include <ui/GraphicTypes.h>
@@ -66,26 +67,70 @@
     int32_t mNumReleased GUARDED_BY(mMutex) = 0;
 };
 
+class TestBLASTBufferQueue : public BLASTBufferQueue {
+public:
+    TestBLASTBufferQueue(const std::string& name, const sp<SurfaceControl>& surface, int width,
+                         int height, int32_t format)
+          : BLASTBufferQueue(name, surface, width, height, format) {}
+
+    void transactionCallback(nsecs_t latchTime, const sp<Fence>& presentFence,
+                             const std::vector<SurfaceControlStats>& stats) override {
+        BLASTBufferQueue::transactionCallback(latchTime, presentFence, stats);
+        uint64_t frameNumber = stats[0].frameEventStats.frameNumber;
+
+        {
+            std::unique_lock lock{frameNumberMutex};
+            mLastTransactionFrameNumber = frameNumber;
+            mWaitForCallbackCV.notify_all();
+        }
+    }
+
+    void waitForCallback(int64_t frameNumber) {
+        std::unique_lock lock{frameNumberMutex};
+        // Wait until all but one of the submitted buffers have been released.
+        while (mLastTransactionFrameNumber < frameNumber) {
+            mWaitForCallbackCV.wait(lock);
+        }
+    }
+
+private:
+    std::mutex frameNumberMutex;
+    std::condition_variable mWaitForCallbackCV;
+    int64_t mLastTransactionFrameNumber = -1;
+};
+
 class BLASTBufferQueueHelper {
 public:
     BLASTBufferQueueHelper(const sp<SurfaceControl>& sc, int width, int height) {
-        mBlastBufferQueueAdapter = new BLASTBufferQueue("TestBLASTBufferQueue", sc, width, height,
-                                                        PIXEL_FORMAT_RGBA_8888);
+        mBlastBufferQueueAdapter = new TestBLASTBufferQueue("TestBLASTBufferQueue", sc, width,
+                                                            height, PIXEL_FORMAT_RGBA_8888);
     }
 
     void update(const sp<SurfaceControl>& sc, int width, int height) {
         mBlastBufferQueueAdapter->update(sc, width, height, PIXEL_FORMAT_RGBA_8888);
     }
 
-    void setNextTransaction(Transaction* next) {
-        mBlastBufferQueueAdapter->setNextTransaction(next);
+    void setSyncTransaction(Transaction& next, bool acquireSingleBuffer = true) {
+        auto callback = [&next](Transaction* t) { next.merge(std::move(*t)); };
+        mBlastBufferQueueAdapter->syncNextTransaction(callback, acquireSingleBuffer);
+    }
+
+    void syncNextTransaction(std::function<void(Transaction*)> callback,
+                             bool acquireSingleBuffer = true) {
+        mBlastBufferQueueAdapter->syncNextTransaction(callback, acquireSingleBuffer);
+    }
+
+    void stopContinuousSyncTransaction() {
+        mBlastBufferQueueAdapter->stopContinuousSyncTransaction();
     }
 
     int getWidth() { return mBlastBufferQueueAdapter->mSize.width; }
 
     int getHeight() { return mBlastBufferQueueAdapter->mSize.height; }
 
-    Transaction* getNextTransaction() { return mBlastBufferQueueAdapter->mNextTransaction; }
+    std::function<void(Transaction*)> getTransactionReadyCallback() {
+        return mBlastBufferQueueAdapter->mTransactionReadyCallback;
+    }
 
     sp<IGraphicBufferProducer> getIGraphicBufferProducer() {
         return mBlastBufferQueueAdapter->getIGraphicBufferProducer();
@@ -107,28 +152,17 @@
         }
     }
 
-    void setTransactionCompleteCallback(int64_t frameNumber) {
-        mBlastBufferQueueAdapter->setTransactionCompleteCallback(frameNumber, [&](int64_t frame) {
-            std::unique_lock lock{mMutex};
-            mLastTransactionCompleteFrameNumber = frame;
-            mCallbackCV.notify_all();
-        });
+    void waitForCallback(int64_t frameNumber) {
+        mBlastBufferQueueAdapter->waitForCallback(frameNumber);
     }
 
-    void waitForCallback(int64_t frameNumber) {
-        std::unique_lock lock{mMutex};
-        // Wait until all but one of the submitted buffers have been released.
-        while (mLastTransactionCompleteFrameNumber < frameNumber) {
-            mCallbackCV.wait(lock);
-        }
+    void validateNumFramesSubmitted(int64_t numFramesSubmitted) {
+        std::unique_lock lock{mBlastBufferQueueAdapter->mMutex};
+        ASSERT_EQ(numFramesSubmitted, mBlastBufferQueueAdapter->mSubmitted.size());
     }
 
 private:
-    sp<BLASTBufferQueue> mBlastBufferQueueAdapter;
-
-    std::mutex mMutex;
-    std::condition_variable mCallbackCV;
-    int64_t mLastTransactionCompleteFrameNumber = -1;
+    sp<TestBLASTBufferQueue> mBlastBufferQueueAdapter;
 };
 
 class BLASTBufferQueueTest : public ::testing::Test {
@@ -152,7 +186,7 @@
         mDisplayToken = mClient->getInternalDisplayToken();
         ASSERT_NE(nullptr, mDisplayToken.get());
         Transaction t;
-        t.setDisplayLayerStack(mDisplayToken, 0);
+        t.setDisplayLayerStack(mDisplayToken, ui::DEFAULT_LAYER_STACK);
         t.apply();
         t.clear();
 
@@ -166,7 +200,7 @@
                                                  mDisplayHeight, PIXEL_FORMAT_RGBA_8888,
                                                  ISurfaceComposerClient::eFXSurfaceBufferState,
                                                  /*parent*/ nullptr);
-        t.setLayerStack(mSurfaceControl, 0)
+        t.setLayerStack(mSurfaceControl, ui::DEFAULT_LAYER_STACK)
                 .setLayer(mSurfaceControl, std::numeric_limits<int32_t>::max())
                 .show(mSurfaceControl)
                 .setDataspace(mSurfaceControl, ui::Dataspace::V0_SRGB)
@@ -262,13 +296,13 @@
 
     static status_t captureDisplay(DisplayCaptureArgs& captureArgs,
                                    ScreenCaptureResults& captureResults) {
-        const auto sf = ComposerService::getComposerService();
+        const auto sf = ComposerServiceAIDL::getComposerService();
         SurfaceComposerClient::Transaction().apply(true);
 
         const sp<SyncScreenCaptureListener> captureListener = new SyncScreenCaptureListener();
-        status_t status = sf->captureDisplay(captureArgs, captureListener);
-        if (status != NO_ERROR) {
-            return status;
+        binder::Status status = sf->captureDisplay(captureArgs, captureListener);
+        if (status.transactionError() != NO_ERROR) {
+            return status.transactionError();
         }
         captureResults = captureListener->waitForResults();
         return captureResults.result;
@@ -282,7 +316,7 @@
         auto ret = igbp->dequeueBuffer(&slot, &fence, mDisplayWidth, mDisplayHeight,
                                        PIXEL_FORMAT_RGBA_8888, GRALLOC_USAGE_SW_WRITE_OFTEN,
                                        nullptr, nullptr);
-        ASSERT_EQ(IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION, ret);
+        ASSERT_TRUE(ret == IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION || ret == NO_ERROR);
         ASSERT_EQ(OK, igbp->requestBuffer(slot, &buf));
 
         uint32_t* bufData;
@@ -321,7 +355,7 @@
     ASSERT_EQ(mSurfaceControl, adapter.getSurfaceControl());
     ASSERT_EQ(mDisplayWidth, adapter.getWidth());
     ASSERT_EQ(mDisplayHeight, adapter.getHeight());
-    ASSERT_EQ(nullptr, adapter.getNextTransaction());
+    ASSERT_EQ(nullptr, adapter.getTransactionReadyCallback());
 }
 
 TEST_F(BLASTBufferQueueTest, Update) {
@@ -342,11 +376,12 @@
     ASSERT_EQ(mDisplayHeight / 2, height);
 }
 
-TEST_F(BLASTBufferQueueTest, SetNextTransaction) {
+TEST_F(BLASTBufferQueueTest, SyncNextTransaction) {
     BLASTBufferQueueHelper adapter(mSurfaceControl, mDisplayWidth, mDisplayHeight);
-    Transaction next;
-    adapter.setNextTransaction(&next);
-    ASSERT_EQ(&next, adapter.getNextTransaction());
+    ASSERT_EQ(nullptr, adapter.getTransactionReadyCallback());
+    auto callback = [](Transaction*) {};
+    adapter.syncNextTransaction(callback);
+    ASSERT_NE(nullptr, adapter.getTransactionReadyCallback());
 }
 
 TEST_F(BLASTBufferQueueTest, DISABLED_onFrameAvailable_ApplyDesiredPresentTime) {
@@ -516,7 +551,7 @@
                                      ISurfaceComposerClient::eFXSurfaceEffect);
     ASSERT_NE(nullptr, bg.get());
     Transaction t;
-    t.setLayerStack(bg, 0)
+    t.setLayerStack(bg, ui::DEFAULT_LAYER_STACK)
             .setCrop(bg, Rect(0, 0, mDisplayWidth, mDisplayHeight))
             .setColor(bg, half3{0, 0, 0})
             .setLayer(bg, 0)
@@ -571,7 +606,7 @@
                                      ISurfaceComposerClient::eFXSurfaceEffect);
     ASSERT_NE(nullptr, bg.get());
     Transaction t;
-    t.setLayerStack(bg, 0)
+    t.setLayerStack(bg, ui::DEFAULT_LAYER_STACK)
             .setCrop(bg, Rect(0, 0, mDisplayWidth, mDisplayHeight))
             .setColor(bg, half3{0, 0, 0})
             .setLayer(bg, 0)
@@ -638,7 +673,7 @@
                                      ISurfaceComposerClient::eFXSurfaceEffect);
     ASSERT_NE(nullptr, bg.get());
     Transaction t;
-    t.setLayerStack(bg, 0)
+    t.setLayerStack(bg, ui::DEFAULT_LAYER_STACK)
             .setCrop(bg, Rect(0, 0, mDisplayWidth, mDisplayHeight))
             .setColor(bg, half3{0, 0, 0})
             .setLayer(bg, 0)
@@ -785,8 +820,8 @@
     sp<IGraphicBufferProducer> igbProducer;
     setUpProducer(adapter, igbProducer);
 
-    Transaction next;
-    adapter.setNextTransaction(&next);
+    Transaction sync;
+    adapter.setSyncTransaction(sync);
     queueBuffer(igbProducer, 0, 255, 0, 0);
 
     // queue non sync buffer, so this one should get blocked
@@ -795,14 +830,14 @@
     queueBuffer(igbProducer, r, g, b, presentTimeDelay);
 
     CallbackHelper transactionCallback;
-    next.addTransactionCompletedCallback(transactionCallback.function,
+    sync.addTransactionCompletedCallback(transactionCallback.function,
                                          transactionCallback.getContext())
             .apply();
 
     CallbackData callbackData;
     transactionCallback.getCallbackData(&callbackData);
 
-    // capture screen and verify that it is red
+    // capture screen and verify that it is green
     ASSERT_EQ(NO_ERROR, captureDisplay(mCaptureArgs, mCaptureResults));
     ASSERT_NO_FATAL_FAILURE(
             checkScreenCapture(0, 255, 0, {0, 0, (int32_t)mDisplayWidth, (int32_t)mDisplayHeight}));
@@ -825,16 +860,16 @@
 
     Transaction mainTransaction;
 
-    Transaction next;
-    adapter.setNextTransaction(&next);
+    Transaction sync;
+    adapter.setSyncTransaction(sync);
     queueBuffer(igbProducer, 0, 255, 0, 0);
 
-    mainTransaction.merge(std::move(next));
+    mainTransaction.merge(std::move(sync));
 
-    adapter.setNextTransaction(&next);
+    adapter.setSyncTransaction(sync);
     queueBuffer(igbProducer, r, g, b, 0);
 
-    mainTransaction.merge(std::move(next));
+    mainTransaction.merge(std::move(sync));
     // Expect 1 buffer to be released even before sending to SurfaceFlinger
     mProducerListener->waitOnNumberReleased(1);
 
@@ -865,24 +900,24 @@
 
     Transaction mainTransaction;
 
-    Transaction next;
+    Transaction sync;
     // queue a sync transaction
-    adapter.setNextTransaction(&next);
+    adapter.setSyncTransaction(sync);
     queueBuffer(igbProducer, 0, 255, 0, 0);
 
-    mainTransaction.merge(std::move(next));
+    mainTransaction.merge(std::move(sync));
 
-    // queue another buffer without setting next transaction
+    // queue another buffer without setting sync transaction
     queueBuffer(igbProducer, 0, 0, 255, 0);
 
     // queue another sync transaction
-    adapter.setNextTransaction(&next);
+    adapter.setSyncTransaction(sync);
     queueBuffer(igbProducer, r, g, b, 0);
     // Expect 1 buffer to be released because the non sync transaction should merge
     // with the sync
     mProducerListener->waitOnNumberReleased(1);
 
-    mainTransaction.merge(std::move(next));
+    mainTransaction.merge(std::move(sync));
     // Expect 2 buffers to be released due to merging the two syncs.
     mProducerListener->waitOnNumberReleased(2);
 
@@ -913,26 +948,26 @@
 
     Transaction mainTransaction;
 
-    Transaction next;
+    Transaction sync;
     // queue a sync transaction
-    adapter.setNextTransaction(&next);
+    adapter.setSyncTransaction(sync);
     queueBuffer(igbProducer, 0, 255, 0, 0);
 
-    mainTransaction.merge(std::move(next));
+    mainTransaction.merge(std::move(sync));
 
-    // queue a few buffers without setting next transaction
+    // queue a few buffers without setting sync transaction
     queueBuffer(igbProducer, 0, 0, 255, 0);
     queueBuffer(igbProducer, 0, 0, 255, 0);
     queueBuffer(igbProducer, 0, 0, 255, 0);
 
     // queue another sync transaction
-    adapter.setNextTransaction(&next);
+    adapter.setSyncTransaction(sync);
     queueBuffer(igbProducer, r, g, b, 0);
     // Expect 3 buffers to be released because the non sync transactions should merge
     // with the sync
     mProducerListener->waitOnNumberReleased(3);
 
-    mainTransaction.merge(std::move(next));
+    mainTransaction.merge(std::move(sync));
     // Expect 4 buffers to be released due to merging the two syncs.
     mProducerListener->waitOnNumberReleased(4);
 
@@ -970,14 +1005,14 @@
     // Send a buffer to SF
     queueBuffer(igbProducer, 0, 255, 0, 0);
 
-    Transaction next;
+    Transaction sync;
     // queue a sync transaction
-    adapter.setNextTransaction(&next);
+    adapter.setSyncTransaction(sync);
     queueBuffer(igbProducer, 0, 255, 0, 0);
 
-    mainTransaction.merge(std::move(next));
+    mainTransaction.merge(std::move(sync));
 
-    // queue a few buffers without setting next transaction
+    // queue a few buffers without setting sync transaction
     queueBuffer(igbProducer, 0, 0, 255, 0);
     queueBuffer(igbProducer, 0, 0, 255, 0);
     queueBuffer(igbProducer, 0, 0, 255, 0);
@@ -986,13 +1021,13 @@
     mainTransaction.apply();
 
     // queue another sync transaction
-    adapter.setNextTransaction(&next);
+    adapter.setSyncTransaction(sync);
     queueBuffer(igbProducer, r, g, b, 0);
     // Expect 2 buffers to be released because the non sync transactions should merge
     // with the sync
     mProducerListener->waitOnNumberReleased(3);
 
-    mainTransaction.merge(std::move(next));
+    mainTransaction.merge(std::move(sync));
 
     CallbackHelper transactionCallback;
     mainTransaction
@@ -1009,6 +1044,161 @@
             checkScreenCapture(r, g, b, {0, 0, (int32_t)mDisplayWidth, (int32_t)mDisplayHeight}));
 }
 
+TEST_F(BLASTBufferQueueTest, SyncNextTransactionAcquireMultipleBuffers) {
+    BLASTBufferQueueHelper adapter(mSurfaceControl, mDisplayWidth, mDisplayHeight);
+
+    sp<IGraphicBufferProducer> igbProducer;
+    setUpProducer(adapter, igbProducer);
+
+    Transaction next;
+    adapter.setSyncTransaction(next, false);
+    queueBuffer(igbProducer, 0, 255, 0, 0);
+    queueBuffer(igbProducer, 0, 0, 255, 0);
+    // There should only be one frame submitted since the first frame will be released.
+    adapter.validateNumFramesSubmitted(1);
+    adapter.stopContinuousSyncTransaction();
+
+    // queue non sync buffer, so this one should get blocked
+    // Add a present delay to allow the first screenshot to get taken.
+    nsecs_t presentTimeDelay = std::chrono::nanoseconds(500ms).count();
+    queueBuffer(igbProducer, 255, 0, 0, presentTimeDelay);
+
+    CallbackHelper transactionCallback;
+    next.addTransactionCompletedCallback(transactionCallback.function,
+                                         transactionCallback.getContext())
+            .apply();
+
+    CallbackData callbackData;
+    transactionCallback.getCallbackData(&callbackData);
+
+    // capture screen and verify that it is blue
+    ASSERT_EQ(NO_ERROR, captureDisplay(mCaptureArgs, mCaptureResults));
+    ASSERT_NO_FATAL_FAILURE(
+            checkScreenCapture(0, 0, 255, {0, 0, (int32_t)mDisplayWidth, (int32_t)mDisplayHeight}));
+
+    mProducerListener->waitOnNumberReleased(2);
+    // capture screen and verify that it is red
+    ASSERT_EQ(NO_ERROR, captureDisplay(mCaptureArgs, mCaptureResults));
+    ASSERT_NO_FATAL_FAILURE(
+            checkScreenCapture(255, 0, 0, {0, 0, (int32_t)mDisplayWidth, (int32_t)mDisplayHeight}));
+}
+
+TEST_F(BLASTBufferQueueTest, SyncNextTransactionOverwrite) {
+    std::mutex mutex;
+    std::condition_variable callbackReceivedCv;
+    bool receivedCallback = false;
+
+    BLASTBufferQueueHelper adapter(mSurfaceControl, mDisplayWidth, mDisplayHeight);
+    ASSERT_EQ(nullptr, adapter.getTransactionReadyCallback());
+    auto callback = [&](Transaction*) {
+        std::unique_lock<std::mutex> lock(mutex);
+        receivedCallback = true;
+        callbackReceivedCv.notify_one();
+    };
+    adapter.syncNextTransaction(callback);
+    ASSERT_NE(nullptr, adapter.getTransactionReadyCallback());
+
+    auto callback2 = [](Transaction*) {};
+    adapter.syncNextTransaction(callback2);
+
+    std::unique_lock<std::mutex> lock(mutex);
+    if (!receivedCallback) {
+        ASSERT_NE(callbackReceivedCv.wait_for(lock, std::chrono::seconds(3)),
+                  std::cv_status::timeout)
+                << "did not receive callback";
+    }
+
+    ASSERT_TRUE(receivedCallback);
+}
+
+// This test will currently fail because the old surfacecontrol will steal the last presented buffer
+// until the old surface control is destroyed. This is not necessarily a bug but to document a
+// limitation with the update API and to test any changes to make the api more robust. The current
+// approach for the client is to recreate the blastbufferqueue when the surfacecontrol updates.
+TEST_F(BLASTBufferQueueTest, DISABLED_DisconnectProducerTest) {
+    BLASTBufferQueueHelper adapter(mSurfaceControl, mDisplayWidth, mDisplayHeight);
+    std::vector<sp<SurfaceControl>> surfaceControls;
+    sp<IGraphicBufferProducer> igbProducer;
+    for (int i = 0; i < 10; i++) {
+        sp<SurfaceControl> sc =
+                mClient->createSurface(String8("TestSurface"), mDisplayWidth, mDisplayHeight,
+                                       PIXEL_FORMAT_RGBA_8888,
+                                       ISurfaceComposerClient::eFXSurfaceBufferState,
+                                       /*parent*/ nullptr);
+        Transaction()
+                .setLayerStack(mSurfaceControl, ui::DEFAULT_LAYER_STACK)
+                .setLayer(mSurfaceControl, std::numeric_limits<int32_t>::max())
+                .show(mSurfaceControl)
+                .setDataspace(mSurfaceControl, ui::Dataspace::V0_SRGB)
+                .apply(true);
+        surfaceControls.push_back(sc);
+        adapter.update(sc, mDisplayWidth, mDisplayHeight);
+
+        setUpProducer(adapter, igbProducer);
+        Transaction next;
+        queueBuffer(igbProducer, 0, 255, 0, 0);
+        queueBuffer(igbProducer, 0, 0, 255, 0);
+        adapter.setSyncTransaction(next, true);
+        queueBuffer(igbProducer, 255, 0, 0, 0);
+
+        CallbackHelper transactionCallback;
+        next.addTransactionCompletedCallback(transactionCallback.function,
+                                             transactionCallback.getContext())
+                .apply();
+
+        CallbackData callbackData;
+        transactionCallback.getCallbackData(&callbackData);
+        // capture screen and verify that it is red
+        ASSERT_EQ(NO_ERROR, captureDisplay(mCaptureArgs, mCaptureResults));
+        ASSERT_NO_FATAL_FAILURE(
+                checkScreenCapture(255, 0, 0,
+                                   {0, 0, (int32_t)mDisplayWidth, (int32_t)mDisplayHeight}));
+        igbProducer->disconnect(NATIVE_WINDOW_API_CPU);
+    }
+}
+
+// See DISABLED_DisconnectProducerTest
+TEST_F(BLASTBufferQueueTest, DISABLED_UpdateSurfaceControlTest) {
+    BLASTBufferQueueHelper adapter(mSurfaceControl, mDisplayWidth, mDisplayHeight);
+    std::vector<sp<SurfaceControl>> surfaceControls;
+    sp<IGraphicBufferProducer> igbProducer;
+    for (int i = 0; i < 10; i++) {
+        sp<SurfaceControl> sc =
+                mClient->createSurface(String8("TestSurface"), mDisplayWidth, mDisplayHeight,
+                                       PIXEL_FORMAT_RGBA_8888,
+                                       ISurfaceComposerClient::eFXSurfaceBufferState,
+                                       /*parent*/ nullptr);
+        Transaction()
+                .setLayerStack(mSurfaceControl, ui::DEFAULT_LAYER_STACK)
+                .setLayer(mSurfaceControl, std::numeric_limits<int32_t>::max())
+                .show(mSurfaceControl)
+                .setDataspace(mSurfaceControl, ui::Dataspace::V0_SRGB)
+                .apply(true);
+        surfaceControls.push_back(sc);
+        adapter.update(sc, mDisplayWidth, mDisplayHeight);
+        setUpProducer(adapter, igbProducer);
+
+        Transaction next;
+        queueBuffer(igbProducer, 0, 255, 0, 0);
+        queueBuffer(igbProducer, 0, 0, 255, 0);
+        adapter.setSyncTransaction(next, true);
+        queueBuffer(igbProducer, 255, 0, 0, 0);
+
+        CallbackHelper transactionCallback;
+        next.addTransactionCompletedCallback(transactionCallback.function,
+                                             transactionCallback.getContext())
+                .apply();
+
+        CallbackData callbackData;
+        transactionCallback.getCallbackData(&callbackData);
+        // capture screen and verify that it is red
+        ASSERT_EQ(NO_ERROR, captureDisplay(mCaptureArgs, mCaptureResults));
+        ASSERT_NO_FATAL_FAILURE(
+                checkScreenCapture(255, 0, 0,
+                                   {0, 0, (int32_t)mDisplayWidth, (int32_t)mDisplayHeight}));
+    }
+}
+
 class TestProducerListener : public BnProducerListener {
 public:
     sp<IGraphicBufferProducer> mIgbp;
@@ -1070,7 +1260,7 @@
                                    ISurfaceComposerClient::eFXSurfaceBufferState);
     ASSERT_NE(nullptr, bgSurface.get());
     Transaction t;
-    t.setLayerStack(bgSurface, 0)
+    t.setLayerStack(bgSurface, ui::DEFAULT_LAYER_STACK)
             .show(bgSurface)
             .setDataspace(bgSurface, ui::Dataspace::V0_SRGB)
             .setLayer(bgSurface, std::numeric_limits<int32_t>::max() - 1)
@@ -1366,7 +1556,6 @@
     IGraphicBufferProducer::QueueBufferOutput qbOutput;
     nsecs_t requestedPresentTimeA = 0;
     nsecs_t postedTimeA = 0;
-    adapter.setTransactionCompleteCallback(1);
     setUpAndQueueBuffer(igbProducer, &requestedPresentTimeA, &postedTimeA, &qbOutput, true);
     history.applyDelta(qbOutput.frameTimestamps);
 
@@ -1435,7 +1624,6 @@
     // queue another buffer so the first can be dropped
     nsecs_t requestedPresentTimeB = 0;
     nsecs_t postedTimeB = 0;
-    adapter.setTransactionCompleteCallback(2);
     presentTime = systemTime() + std::chrono::nanoseconds(1ms).count();
     setUpAndQueueBuffer(igbProducer, &requestedPresentTimeB, &postedTimeB, &qbOutput, true,
                         presentTime);
@@ -1501,7 +1689,6 @@
     IGraphicBufferProducer::QueueBufferOutput qbOutput;
     nsecs_t requestedPresentTimeA = 0;
     nsecs_t postedTimeA = 0;
-    adapter.setTransactionCompleteCallback(1);
     setUpAndQueueBuffer(igbProducer, &requestedPresentTimeA, &postedTimeA, &qbOutput, true);
     history.applyDelta(qbOutput.frameTimestamps);
     adapter.waitForCallback(1);
diff --git a/libs/gui/tests/DisplayEventStructLayout_test.cpp b/libs/gui/tests/DisplayEventStructLayout_test.cpp
index bcd39db..da88463 100644
--- a/libs/gui/tests/DisplayEventStructLayout_test.cpp
+++ b/libs/gui/tests/DisplayEventStructLayout_test.cpp
@@ -20,9 +20,10 @@
 namespace android::test {
 
 #define CHECK_OFFSET(type, member, expected_offset) \
-    static_assert((offsetof(type, member) == (expected_offset)), "")
+    static_assert((offsetof(type, member) == (expected_offset)))
 
 TEST(DisplayEventStructLayoutTest, TestEventAlignment) {
+    static_assert(std::is_pod<DisplayEventReceiver::Event::VSync>::value);
     CHECK_OFFSET(DisplayEventReceiver::Event, vsync, 24);
     CHECK_OFFSET(DisplayEventReceiver::Event, hotplug, 24);
     CHECK_OFFSET(DisplayEventReceiver::Event, modeChange, 24);
@@ -32,10 +33,29 @@
     CHECK_OFFSET(DisplayEventReceiver::Event::Header, timestamp, 16);
 
     CHECK_OFFSET(DisplayEventReceiver::Event::VSync, count, 0);
-    CHECK_OFFSET(DisplayEventReceiver::Event::VSync, expectedVSyncTimestamp, 8);
-    CHECK_OFFSET(DisplayEventReceiver::Event::VSync, deadlineTimestamp, 16);
-    CHECK_OFFSET(DisplayEventReceiver::Event::VSync, frameInterval, 24);
-    CHECK_OFFSET(DisplayEventReceiver::Event::VSync, vsyncId, 32);
+    CHECK_OFFSET(DisplayEventReceiver::Event::VSync, vsyncData.frameInterval, 8);
+    CHECK_OFFSET(DisplayEventReceiver::Event::VSync, vsyncData.preferredFrameTimelineIndex, 16);
+    CHECK_OFFSET(DisplayEventReceiver::Event::VSync, vsyncData.frameTimelines, 24);
+    CHECK_OFFSET(DisplayEventReceiver::Event::VSync, vsyncData.frameTimelines[0].vsyncId, 24);
+    CHECK_OFFSET(DisplayEventReceiver::Event::VSync, vsyncData.frameTimelines[0].deadlineTimestamp,
+                 32);
+    CHECK_OFFSET(DisplayEventReceiver::Event::VSync,
+                 vsyncData.frameTimelines[0].expectedPresentationTime, 40);
+    // Also test the offsets of the last frame timeline. A loop is not used because the non-const
+    // index cannot be used in static_assert.
+    const int lastFrameTimelineOffset = /* Start of array */ 24 +
+            (VsyncEventData::kFrameTimelinesLength - 1) * /* Size of FrameTimeline */ 24;
+    CHECK_OFFSET(DisplayEventReceiver::Event::VSync,
+                 vsyncData.frameTimelines[VsyncEventData::kFrameTimelinesLength - 1].vsyncId,
+                 lastFrameTimelineOffset);
+    CHECK_OFFSET(DisplayEventReceiver::Event::VSync,
+                 vsyncData.frameTimelines[VsyncEventData::kFrameTimelinesLength - 1]
+                         .deadlineTimestamp,
+                 lastFrameTimelineOffset + 8);
+    CHECK_OFFSET(DisplayEventReceiver::Event::VSync,
+                 vsyncData.frameTimelines[VsyncEventData::kFrameTimelinesLength - 1]
+                         .expectedPresentationTime,
+                 lastFrameTimelineOffset + 16);
 
     CHECK_OFFSET(DisplayEventReceiver::Event::Hotplug, connected, 0);
 
diff --git a/libs/gui/tests/DisplayInfo_test.cpp b/libs/gui/tests/DisplayInfo_test.cpp
new file mode 100644
index 0000000..df3329c
--- /dev/null
+++ b/libs/gui/tests/DisplayInfo_test.cpp
@@ -0,0 +1,49 @@
+/*
+ * 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.
+ */
+
+#include <gtest/gtest.h>
+
+#include <binder/Parcel.h>
+
+#include <gui/DisplayInfo.h>
+
+namespace android {
+
+using gui::DisplayInfo;
+
+namespace test {
+
+TEST(DisplayInfo, Parcelling) {
+    DisplayInfo info;
+    info.displayId = 42;
+    info.logicalWidth = 99;
+    info.logicalHeight = 78;
+    info.transform.set({0.4, -1, 100, 0.5, 0, 40, 0, 0, 1});
+
+    Parcel p;
+    info.writeToParcel(&p);
+    p.setDataPosition(0);
+
+    DisplayInfo info2;
+    info2.readFromParcel(&p);
+    ASSERT_EQ(info.displayId, info2.displayId);
+    ASSERT_EQ(info.logicalWidth, info2.logicalWidth);
+    ASSERT_EQ(info.logicalHeight, info2.logicalHeight);
+    ASSERT_EQ(info.transform, info2.transform);
+}
+
+} // namespace test
+} // namespace android
diff --git a/libs/gui/tests/EndToEndNativeInputTest.cpp b/libs/gui/tests/EndToEndNativeInputTest.cpp
index 5daef0d..262987f 100644
--- a/libs/gui/tests/EndToEndNativeInputTest.cpp
+++ b/libs/gui/tests/EndToEndNativeInputTest.cpp
@@ -46,6 +46,8 @@
 #include <ui/Rect.h>
 #include <ui/Region.h>
 
+#include <private/android_filesystem_config.h>
+
 using android::os::IInputFlinger;
 
 using android::hardware::graphics::common::V1_1::BufferUsage;
@@ -74,16 +76,30 @@
 
 class InputSurface {
 public:
-    InputSurface(const sp<SurfaceControl> &sc, int width, int height) {
+    InputSurface(const sp<SurfaceControl> &sc, int width, int height, bool noInputChannel = false) {
         mSurfaceControl = sc;
 
         mInputFlinger = getInputFlinger();
-        mClientChannel = std::make_shared<InputChannel>();
-        mInputFlinger->createInputChannel("testchannels", mClientChannel.get());
+        if (noInputChannel) {
+            mInputInfo.setInputConfig(WindowInfo::InputConfig::NO_INPUT_CHANNEL, true);
+        } else {
+            mClientChannel = std::make_shared<InputChannel>();
+            mInputFlinger->createInputChannel("testchannels", mClientChannel.get());
+            mInputInfo.token = mClientChannel->getConnectionToken();
+            mInputConsumer = new InputConsumer(mClientChannel);
+        }
 
-        populateInputInfo(width, height);
+        mInputInfo.name = "Test info";
+        mInputInfo.dispatchingTimeout = 5s;
+        mInputInfo.globalScaleFactor = 1.0;
+        mInputInfo.touchableRegion.orSelf(Rect(0, 0, width, height));
 
-        mInputConsumer = new InputConsumer(mClientChannel);
+        InputApplicationInfo aInfo;
+        aInfo.token = new BBinder();
+        aInfo.name = "Test app info";
+        aInfo.dispatchingTimeoutMillis =
+                std::chrono::duration_cast<std::chrono::milliseconds>(DISPATCHING_TIMEOUT).count();
+        mInputInfo.applicationInfo = aInfo;
     }
 
     static std::unique_ptr<InputSurface> makeColorInputSurface(const sp<SurfaceComposerClient> &scc,
@@ -112,6 +128,16 @@
         return std::make_unique<InputSurface>(surfaceControl, width, height);
     }
 
+    static std::unique_ptr<InputSurface> makeContainerInputSurfaceNoInputChannel(
+            const sp<SurfaceComposerClient> &scc, int width, int height) {
+        sp<SurfaceControl> surfaceControl =
+                scc->createSurface(String8("Test Container Surface"), 100 /* height */,
+                                   100 /* width */, PIXEL_FORMAT_RGBA_8888,
+                                   ISurfaceComposerClient::eFXSurfaceContainer);
+        return std::make_unique<InputSurface>(surfaceControl, width, height,
+                                              true /* noInputChannel */);
+    }
+
     static std::unique_ptr<InputSurface> makeCursorInputSurface(
             const sp<SurfaceComposerClient> &scc, int width, int height) {
         sp<SurfaceControl> surfaceControl =
@@ -179,6 +205,25 @@
         EXPECT_EQ(flags, mev->getFlags() & flags);
     }
 
+    void expectTapInDisplayCoordinates(int displayX, int displayY) {
+        InputEvent *ev = consumeEvent();
+        ASSERT_NE(ev, nullptr);
+        ASSERT_EQ(AINPUT_EVENT_TYPE_MOTION, ev->getType());
+        MotionEvent *mev = static_cast<MotionEvent *>(ev);
+        EXPECT_EQ(AMOTION_EVENT_ACTION_DOWN, mev->getAction());
+        const PointerCoords &coords = *mev->getRawPointerCoords(0 /*pointerIndex*/);
+        EXPECT_EQ(displayX, coords.getX());
+        EXPECT_EQ(displayY, coords.getY());
+        EXPECT_EQ(0, mev->getFlags() & VERIFIED_MOTION_EVENT_FLAGS);
+
+        ev = consumeEvent();
+        ASSERT_NE(ev, nullptr);
+        ASSERT_EQ(AINPUT_EVENT_TYPE_MOTION, ev->getType());
+        mev = static_cast<MotionEvent *>(ev);
+        EXPECT_EQ(AMOTION_EVENT_ACTION_UP, mev->getAction());
+        EXPECT_EQ(0, mev->getFlags() & VERIFIED_MOTION_EVENT_FLAGS);
+    }
+
     void expectKey(uint32_t keycode) {
         InputEvent *ev = consumeEvent();
         ASSERT_NE(ev, nullptr);
@@ -198,7 +243,9 @@
     }
 
     virtual ~InputSurface() {
-        mInputFlinger->removeInputChannel(mClientChannel->getConnectionToken());
+        if (mClientChannel) {
+            mInputFlinger->removeInputChannel(mClientChannel->getConnectionToken());
+        }
     }
 
     virtual void doTransaction(
@@ -220,7 +267,7 @@
         t.apply(true);
     }
 
-    void requestFocus() {
+    void requestFocus(int displayId = ADISPLAY_ID_DEFAULT) {
         SurfaceComposerClient::Transaction t;
         FocusRequest request;
         request.token = mInputInfo.token;
@@ -228,7 +275,7 @@
         request.focusedToken = nullptr;
         request.focusedWindowName = "";
         request.timestamp = systemTime(SYSTEM_TIME_MONOTONIC);
-        request.displayId = 0;
+        request.displayId = displayId;
         t.setFocusedWindow(request);
         t.apply(true);
     }
@@ -242,32 +289,6 @@
         poll(&fd, 1, timeoutMs);
     }
 
-    void populateInputInfo(int width, int height) {
-        mInputInfo.token = mClientChannel->getConnectionToken();
-        mInputInfo.name = "Test info";
-        mInputInfo.flags = WindowInfo::Flag::NOT_TOUCH_MODAL;
-        mInputInfo.type = WindowInfo::Type::BASE_APPLICATION;
-        mInputInfo.dispatchingTimeout = 5s;
-        mInputInfo.globalScaleFactor = 1.0;
-        mInputInfo.focusable = true;
-        mInputInfo.hasWallpaper = false;
-        mInputInfo.paused = false;
-
-        mInputInfo.touchableRegion.orSelf(Rect(0, 0, width, height));
-
-        // TODO: Fill in from SF?
-        mInputInfo.ownerPid = 11111;
-        mInputInfo.ownerUid = 11111;
-        mInputInfo.displayId = 0;
-
-        InputApplicationInfo aInfo;
-        aInfo.token = new BBinder();
-        aInfo.name = "Test app info";
-        aInfo.dispatchingTimeoutMillis =
-                std::chrono::duration_cast<std::chrono::milliseconds>(DISPATCHING_TIMEOUT).count();
-
-        mInputInfo.applicationInfo = aInfo;
-    }
 public:
     sp<SurfaceControl> mSurfaceControl;
     std::shared_ptr<InputChannel> mClientChannel;
@@ -373,21 +394,31 @@
     int32_t mBufferPostDelay;
 };
 
-void injectTap(int x, int y) {
-    char *buf1, *buf2;
+void injectTapOnDisplay(int x, int y, int displayId) {
+    char *buf1, *buf2, *bufDisplayId;
     asprintf(&buf1, "%d", x);
     asprintf(&buf2, "%d", y);
+    asprintf(&bufDisplayId, "%d", displayId);
     if (fork() == 0) {
-        execlp("input", "input", "tap", buf1, buf2, NULL);
+        execlp("input", "input", "-d", bufDisplayId, "tap", buf1, buf2, NULL);
+    }
+}
+
+void injectTap(int x, int y) {
+    injectTapOnDisplay(x, y, ADISPLAY_ID_DEFAULT);
+}
+
+void injectKeyOnDisplay(uint32_t keycode, int displayId) {
+    char *buf1, *bufDisplayId;
+    asprintf(&buf1, "%d", keycode);
+    asprintf(&bufDisplayId, "%d", displayId);
+    if (fork() == 0) {
+        execlp("input", "input", "-d", bufDisplayId, "keyevent", buf1, NULL);
     }
 }
 
 void injectKey(uint32_t keycode) {
-    char *buf1;
-    asprintf(&buf1, "%d", keycode);
-    if (fork() == 0) {
-        execlp("input", "input", "keyevent", buf1, NULL);
-    }
+    injectKeyOnDisplay(keycode, ADISPLAY_ID_NONE);
 }
 
 TEST_F(InputSurfacesTest, can_receive_input) {
@@ -520,7 +551,10 @@
 }
 
 TEST_F(InputSurfacesTest, input_respects_scaled_surface_insets_overflow) {
+    std::unique_ptr<InputSurface> bgSurface = makeSurface(100, 100);
     std::unique_ptr<InputSurface> fgSurface = makeSurface(100, 100);
+    bgSurface->showAt(100, 100);
+
     // In case we pass the very big inset without any checking.
     fgSurface->mInputInfo.surfaceInset = INT32_MAX;
     fgSurface->showAt(100, 100);
@@ -528,8 +562,8 @@
     fgSurface->doTransaction([&](auto &t, auto &sc) { t.setMatrix(sc, 2.0, 0, 0, 2.0); });
 
     // expect no crash for overflow, and inset size to be clamped to surface size
-    injectTap(202, 202);
-    fgSurface->expectTap(1, 1);
+    injectTap(112, 124);
+    bgSurface->expectTap(12, 24);
 }
 
 // Ensure we ignore transparent region when getting screen bounds when positioning input frame.
@@ -564,7 +598,7 @@
     bufferSurface->expectTap(1, 1);
 }
 
-TEST_F(InputSurfacesTest, input_ignores_buffer_layer_alpha) {
+TEST_F(InputSurfacesTest, input_respects_buffer_layer_alpha) {
     std::unique_ptr<InputSurface> bgSurface = makeSurface(100, 100);
     std::unique_ptr<BlastInputSurface> bufferSurface =
             BlastInputSurface::makeBlastInputSurface(mComposerClient, 100, 100);
@@ -579,7 +613,7 @@
     bufferSurface->doTransaction([](auto &t, auto &sc) { t.setAlpha(sc, 0.0); });
 
     injectTap(11, 11);
-    bufferSurface->expectTap(1, 1);
+    bgSurface->expectTap(1, 1);
 }
 
 TEST_F(InputSurfacesTest, input_ignores_color_layer_alpha) {
@@ -721,7 +755,7 @@
     // Add non touchable window to fully cover touchable window. Window behind gets touch, but
     // with flag AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED
     std::unique_ptr<InputSurface> nonTouchableSurface = makeSurface(100, 100);
-    nonTouchableSurface->mInputInfo.flags = WindowInfo::Flag::NOT_TOUCHABLE;
+    nonTouchableSurface->mInputInfo.setInputConfig(WindowInfo::InputConfig::NOT_TOUCHABLE, true);
     nonTouchableSurface->mInputInfo.ownerUid = 22222;
     // Overriding occlusion mode otherwise the touch would be discarded at InputDispatcher by
     // the default obscured/untrusted touch filter introduced in S.
@@ -741,8 +775,8 @@
     // AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED
     std::unique_ptr<InputSurface> parentSurface = makeSurface(100, 100);
     std::unique_ptr<InputSurface> nonTouchableSurface = makeSurface(100, 100);
-    nonTouchableSurface->mInputInfo.flags = WindowInfo::Flag::NOT_TOUCHABLE;
-    parentSurface->mInputInfo.flags = WindowInfo::Flag::NOT_TOUCHABLE;
+    nonTouchableSurface->mInputInfo.setInputConfig(WindowInfo::InputConfig::NOT_TOUCHABLE, true);
+    parentSurface->mInputInfo.setInputConfig(WindowInfo::InputConfig::NOT_TOUCHABLE, true);
     nonTouchableSurface->mInputInfo.ownerUid = 22222;
     parentSurface->mInputInfo.ownerUid = 22222;
     nonTouchableSurface->showAt(0, 0);
@@ -765,8 +799,8 @@
     // the touchable window. Window behind gets touch with no obscured flags.
     std::unique_ptr<InputSurface> parentSurface = makeSurface(100, 100);
     std::unique_ptr<InputSurface> nonTouchableSurface = makeSurface(100, 100);
-    nonTouchableSurface->mInputInfo.flags = WindowInfo::Flag::NOT_TOUCHABLE;
-    parentSurface->mInputInfo.flags = WindowInfo::Flag::NOT_TOUCHABLE;
+    nonTouchableSurface->mInputInfo.setInputConfig(WindowInfo::InputConfig::NOT_TOUCHABLE, true);
+    parentSurface->mInputInfo.setInputConfig(WindowInfo::InputConfig::NOT_TOUCHABLE, true);
     nonTouchableSurface->mInputInfo.ownerUid = 22222;
     parentSurface->mInputInfo.ownerUid = 22222;
     nonTouchableSurface->showAt(0, 0);
@@ -786,7 +820,7 @@
 
     std::unique_ptr<InputSurface> bufferSurface =
             InputSurface::makeBufferInputSurface(mComposerClient, 0, 0);
-    bufferSurface->mInputInfo.flags = WindowInfo::Flag::NOT_TOUCHABLE;
+    bufferSurface->mInputInfo.setInputConfig(WindowInfo::InputConfig::NOT_TOUCHABLE, true);
     bufferSurface->mInputInfo.ownerUid = 22222;
 
     surface->showAt(10, 10);
@@ -801,7 +835,7 @@
 
     std::unique_ptr<BlastInputSurface> bufferSurface =
             BlastInputSurface::makeBlastInputSurface(mComposerClient, 0, 0);
-    bufferSurface->mInputInfo.flags = WindowInfo::Flag::NOT_TOUCHABLE;
+    bufferSurface->mInputInfo.setInputConfig(WindowInfo::InputConfig::NOT_TOUCHABLE, true);
     bufferSurface->mInputInfo.ownerUid = 22222;
 
     surface->showAt(10, 10);
@@ -854,7 +888,7 @@
             [&](auto &t, auto &sc) { t.setDropInputMode(sc, gui::DropInputMode::OBSCURED); });
     surface->showAt(100, 100);
     std::unique_ptr<InputSurface> obscuringSurface = makeSurface(100, 100);
-    obscuringSurface->mInputInfo.flags = WindowInfo::Flag::NOT_TOUCHABLE;
+    obscuringSurface->mInputInfo.setInputConfig(WindowInfo::InputConfig::NOT_TOUCHABLE, true);
     obscuringSurface->mInputInfo.ownerUid = 22222;
     obscuringSurface->showAt(100, 100);
     injectTap(101, 101);
@@ -873,7 +907,7 @@
             [&](auto &t, auto &sc) { t.setDropInputMode(sc, gui::DropInputMode::OBSCURED); });
     surface->showAt(100, 100);
     std::unique_ptr<InputSurface> obscuringSurface = makeSurface(100, 100);
-    obscuringSurface->mInputInfo.flags = WindowInfo::Flag::NOT_TOUCHABLE;
+    obscuringSurface->mInputInfo.setInputConfig(WindowInfo::InputConfig::NOT_TOUCHABLE, true);
     obscuringSurface->mInputInfo.ownerUid = 22222;
     obscuringSurface->showAt(190, 190);
 
@@ -960,4 +994,248 @@
     injectKey(AKEYCODE_V);
     EXPECT_EQ(surface->consumeEvent(100), nullptr);
 }
+
+TEST_F(InputSurfacesTest, layer_with_valid_crop_can_be_focused) {
+    std::unique_ptr<InputSurface> bufferSurface =
+            InputSurface::makeBufferInputSurface(mComposerClient, 100, 100);
+
+    bufferSurface->showAt(50, 50, Rect{0, 0, 100, 100});
+
+    bufferSurface->requestFocus();
+    bufferSurface->assertFocusChange(true);
+}
+
+/**
+ * If a cropped layer's touchable region is replaced with a null crop, it should receive input in
+ * its own crop.
+ */
+TEST_F(InputSurfacesTest, cropped_container_replaces_touchable_region_with_null_crop) {
+    std::unique_ptr<InputSurface> parentContainer =
+            InputSurface::makeContainerInputSurface(mComposerClient, 0, 0);
+    std::unique_ptr<InputSurface> containerSurface =
+            InputSurface::makeContainerInputSurface(mComposerClient, 100, 100);
+    containerSurface->doTransaction(
+            [&](auto &t, auto &sc) { t.reparent(sc, parentContainer->mSurfaceControl); });
+    containerSurface->mInputInfo.replaceTouchableRegionWithCrop = true;
+    containerSurface->mInputInfo.touchableRegionCropHandle = nullptr;
+    parentContainer->showAt(10, 10, Rect(0, 0, 20, 20));
+    containerSurface->showAt(10, 10, Rect(0, 0, 5, 5));
+
+    // Receives events inside its own crop
+    injectTap(21, 21);
+    containerSurface->expectTap(1, 1); // Event is in layer space
+
+    // Does not receive events outside its crop
+    injectTap(26, 26);
+    EXPECT_EQ(containerSurface->consumeEvent(100), nullptr);
+}
+
+/**
+ * If an un-cropped layer's touchable region is replaced with a null crop, it should receive input
+ * in its parent's touchable region. The input events should be in the layer's coordinate space.
+ */
+TEST_F(InputSurfacesTest, uncropped_container_replaces_touchable_region_with_null_crop) {
+    std::unique_ptr<InputSurface> parentContainer =
+            InputSurface::makeContainerInputSurface(mComposerClient, 0, 0);
+    std::unique_ptr<InputSurface> containerSurface =
+            InputSurface::makeContainerInputSurface(mComposerClient, 100, 100);
+    containerSurface->doTransaction(
+            [&](auto &t, auto &sc) { t.reparent(sc, parentContainer->mSurfaceControl); });
+    containerSurface->mInputInfo.replaceTouchableRegionWithCrop = true;
+    containerSurface->mInputInfo.touchableRegionCropHandle = nullptr;
+    parentContainer->showAt(10, 10, Rect(0, 0, 20, 20));
+    containerSurface->showAt(10, 10, Rect::INVALID_RECT);
+
+    // Receives events inside parent bounds
+    injectTap(21, 21);
+    containerSurface->expectTap(1, 1); // Event is in layer space
+
+    // Does not receive events outside parent bounds
+    injectTap(31, 31);
+    EXPECT_EQ(containerSurface->consumeEvent(100), nullptr);
+}
+
+/**
+ * If a layer's touchable region is replaced with a layer crop, it should receive input in the crop
+ * layer's bounds. The input events should be in the layer's coordinate space.
+ */
+TEST_F(InputSurfacesTest, replace_touchable_region_with_crop) {
+    std::unique_ptr<InputSurface> cropLayer =
+            InputSurface::makeContainerInputSurface(mComposerClient, 0, 0);
+    cropLayer->showAt(50, 50, Rect(0, 0, 20, 20));
+
+    std::unique_ptr<InputSurface> containerSurface =
+            InputSurface::makeContainerInputSurface(mComposerClient, 100, 100);
+    containerSurface->mInputInfo.replaceTouchableRegionWithCrop = true;
+    containerSurface->mInputInfo.touchableRegionCropHandle =
+            cropLayer->mSurfaceControl->getHandle();
+    containerSurface->showAt(10, 10, Rect::INVALID_RECT);
+
+    // Receives events inside crop layer bounds
+    injectTap(51, 51);
+    containerSurface->expectTap(41, 41); // Event is in layer space
+
+    // Does not receive events outside crop layer bounds
+    injectTap(21, 21);
+    injectTap(71, 71);
+    EXPECT_EQ(containerSurface->consumeEvent(100), nullptr);
+}
+
+TEST_F(InputSurfacesTest, child_container_with_no_input_channel_blocks_parent) {
+    std::unique_ptr<InputSurface> parent = makeSurface(100, 100);
+
+    parent->showAt(100, 100);
+    injectTap(101, 101);
+    parent->expectTap(1, 1);
+
+    std::unique_ptr<InputSurface> childContainerSurface =
+            InputSurface::makeContainerInputSurfaceNoInputChannel(mComposerClient, 100, 100);
+    childContainerSurface->showAt(0, 0);
+    childContainerSurface->doTransaction(
+            [&](auto &t, auto &sc) { t.reparent(sc, parent->mSurfaceControl); });
+    injectTap(101, 101);
+
+    EXPECT_EQ(parent->consumeEvent(100), nullptr);
+}
+
+class MultiDisplayTests : public InputSurfacesTest {
+public:
+    MultiDisplayTests() : InputSurfacesTest() { ProcessState::self()->startThreadPool(); }
+    void TearDown() override {
+        for (auto &token : mVirtualDisplays) {
+            SurfaceComposerClient::destroyDisplay(token);
+        }
+        InputSurfacesTest::TearDown();
+    }
+
+    void createDisplay(int32_t width, int32_t height, bool isSecure, ui::LayerStack layerStack,
+                       bool receivesInput = true, int32_t offsetX = 0, int32_t offsetY = 0) {
+        sp<IGraphicBufferConsumer> consumer;
+        sp<IGraphicBufferProducer> producer;
+        BufferQueue::createBufferQueue(&producer, &consumer);
+        consumer->setConsumerName(String8("Virtual disp consumer"));
+        consumer->setDefaultBufferSize(width, height);
+        mProducers.push_back(producer);
+
+        std::string name = "VirtualDisplay";
+        name += std::to_string(mVirtualDisplays.size());
+        sp<IBinder> token = SurfaceComposerClient::createDisplay(String8(name.c_str()), isSecure);
+        SurfaceComposerClient::Transaction t;
+        t.setDisplaySurface(token, producer);
+        t.setDisplayFlags(token, receivesInput ? 0x01 /* DisplayDevice::eReceivesInput */ : 0);
+        t.setDisplayLayerStack(token, layerStack);
+        t.setDisplayProjection(token, ui::ROTATION_0, {0, 0, width, height},
+                               {offsetX, offsetY, offsetX + width, offsetY + height});
+        t.apply(true);
+
+        mVirtualDisplays.push_back(token);
+    }
+
+    std::vector<sp<IBinder>> mVirtualDisplays;
+    std::vector<sp<IGraphicBufferProducer>> mProducers;
+};
+
+TEST_F(MultiDisplayTests, drop_input_if_layer_on_invalid_display) {
+    ui::LayerStack layerStack = ui::LayerStack::fromValue(42);
+    // Do not create a display associated with the LayerStack.
+    std::unique_ptr<InputSurface> surface = makeSurface(100, 100);
+    surface->doTransaction([&](auto &t, auto &sc) { t.setLayerStack(sc, layerStack); });
+    surface->showAt(100, 100);
+
+    injectTapOnDisplay(101, 101, layerStack.id);
+    surface->requestFocus(layerStack.id);
+    injectKeyOnDisplay(AKEYCODE_V, layerStack.id);
+
+    EXPECT_EQ(surface->consumeEvent(100), nullptr);
+}
+
+TEST_F(MultiDisplayTests, virtual_display_receives_input) {
+    ui::LayerStack layerStack = ui::LayerStack::fromValue(42);
+    createDisplay(1000, 1000, false /*isSecure*/, layerStack);
+    std::unique_ptr<InputSurface> surface = makeSurface(100, 100);
+    surface->doTransaction([&](auto &t, auto &sc) { t.setLayerStack(sc, layerStack); });
+    surface->showAt(100, 100);
+
+    injectTapOnDisplay(101, 101, layerStack.id);
+    surface->expectTap(1, 1);
+
+    surface->requestFocus(layerStack.id);
+    surface->assertFocusChange(true);
+    injectKeyOnDisplay(AKEYCODE_V, layerStack.id);
+    surface->expectKey(AKEYCODE_V);
+}
+
+/**
+ * When multiple DisplayDevices are mapped to the same layerStack, use the configuration for the
+ * display that can receive input.
+ */
+TEST_F(MultiDisplayTests, many_to_one_display_mapping) {
+    ui::LayerStack layerStack = ui::LayerStack::fromValue(42);
+    createDisplay(1000, 1000, false /*isSecure*/, layerStack, false /*receivesInput*/,
+                  100 /*offsetX*/, 100 /*offsetY*/);
+    createDisplay(1000, 1000, false /*isSecure*/, layerStack, true /*receivesInput*/,
+                  200 /*offsetX*/, 200 /*offsetY*/);
+    createDisplay(1000, 1000, false /*isSecure*/, layerStack, false /*receivesInput*/,
+                  300 /*offsetX*/, 300 /*offsetY*/);
+    std::unique_ptr<InputSurface> surface = makeSurface(100, 100);
+    surface->doTransaction([&](auto &t, auto &sc) { t.setLayerStack(sc, layerStack); });
+    surface->showAt(10, 10);
+
+    // Input injection happens in logical display coordinates.
+    injectTapOnDisplay(11, 11, layerStack.id);
+    // Expect that the display transform for the display that receives input was used.
+    surface->expectTapInDisplayCoordinates(211, 211);
+
+    surface->requestFocus(layerStack.id);
+    surface->assertFocusChange(true);
+    injectKeyOnDisplay(AKEYCODE_V, layerStack.id);
+}
+
+TEST_F(MultiDisplayTests, drop_input_for_secure_layer_on_nonsecure_display) {
+    ui::LayerStack layerStack = ui::LayerStack::fromValue(42);
+    createDisplay(1000, 1000, false /*isSecure*/, layerStack);
+    std::unique_ptr<InputSurface> surface = makeSurface(100, 100);
+    surface->doTransaction([&](auto &t, auto &sc) {
+        t.setFlags(sc, layer_state_t::eLayerSecure, layer_state_t::eLayerSecure);
+        t.setLayerStack(sc, layerStack);
+    });
+    surface->showAt(100, 100);
+
+    injectTapOnDisplay(101, 101, layerStack.id);
+
+    EXPECT_EQ(surface->consumeEvent(100), nullptr);
+
+    surface->requestFocus(layerStack.id);
+    surface->assertFocusChange(true);
+    injectKeyOnDisplay(AKEYCODE_V, layerStack.id);
+    EXPECT_EQ(surface->consumeEvent(100), nullptr);
+}
+
+TEST_F(MultiDisplayTests, dont_drop_input_for_secure_layer_on_secure_display) {
+    ui::LayerStack layerStack = ui::LayerStack::fromValue(42);
+
+    // Create the secure display as system, because only certain users can create secure displays.
+    seteuid(AID_SYSTEM);
+    createDisplay(1000, 1000, true /*isSecure*/, layerStack);
+    // Change the uid back to root.
+    seteuid(AID_ROOT);
+
+    std::unique_ptr<InputSurface> surface = makeSurface(100, 100);
+    surface->doTransaction([&](auto &t, auto &sc) {
+        t.setFlags(sc, layer_state_t::eLayerSecure, layer_state_t::eLayerSecure);
+        t.setLayerStack(sc, layerStack);
+    });
+    surface->showAt(100, 100);
+
+    injectTapOnDisplay(101, 101, layerStack.id);
+    EXPECT_NE(surface->consumeEvent(), nullptr);
+    EXPECT_NE(surface->consumeEvent(), nullptr);
+
+    surface->requestFocus(layerStack.id);
+    surface->assertFocusChange(true);
+    injectKeyOnDisplay(AKEYCODE_V, layerStack.id);
+
+    surface->expectKey(AKEYCODE_V);
+}
+
 } // namespace android::test
diff --git a/libs/gui/tests/RegionSampling_test.cpp b/libs/gui/tests/RegionSampling_test.cpp
index 6746b0a..c9106be 100644
--- a/libs/gui/tests/RegionSampling_test.cpp
+++ b/libs/gui/tests/RegionSampling_test.cpp
@@ -17,9 +17,9 @@
 #include <gtest/gtest.h>
 #include <thread>
 
+#include <android/gui/BnRegionSamplingListener.h>
 #include <binder/ProcessState.h>
 #include <gui/DisplayEventReceiver.h>
-#include <gui/IRegionSamplingListener.h>
 #include <gui/ISurfaceComposer.h>
 #include <gui/Surface.h>
 #include <gui/SurfaceComposerClient.h>
@@ -135,12 +135,13 @@
     std::atomic<bool> poll_{true};
 };
 
-struct Listener : BnRegionSamplingListener {
-    void onSampleCollected(float medianLuma) override {
+struct Listener : android::gui::BnRegionSamplingListener {
+    binder::Status onSampleCollected(float medianLuma) override {
         std::unique_lock<decltype(mutex)> lk(mutex);
         received = true;
         mLuma = medianLuma;
         cv.notify_all();
+        return binder::Status::ok();
     };
     bool wait_event(std::chrono::milliseconds timeout) {
         std::unique_lock<decltype(mutex)> lk(mutex);
diff --git a/libs/gui/tests/SamplingDemo.cpp b/libs/gui/tests/SamplingDemo.cpp
index 0cd150d..a083a22 100644
--- a/libs/gui/tests/SamplingDemo.cpp
+++ b/libs/gui/tests/SamplingDemo.cpp
@@ -20,9 +20,9 @@
 #include <chrono>
 #include <thread>
 
+#include <android/gui/BnRegionSamplingListener.h>
 #include <binder/IPCThreadState.h>
 #include <binder/ProcessState.h>
-#include <gui/IRegionSamplingListener.h>
 #include <gui/ISurfaceComposer.h>
 #include <gui/SurfaceComposerClient.h>
 #include <gui/SurfaceControl.h>
@@ -33,7 +33,7 @@
 
 namespace android {
 
-class Button : public BnRegionSamplingListener {
+class Button : public gui::BnRegionSamplingListener {
 public:
     Button(const char* name, const Rect& samplingArea) {
         sp<SurfaceComposerClient> client = new SurfaceComposerClient;
@@ -99,9 +99,10 @@
                 .apply();
     }
 
-    void onSampleCollected(float medianLuma) override {
+    binder::Status onSampleCollected(float medianLuma) override {
         ATRACE_CALL();
         setColor(medianLuma);
+        return binder::Status::ok();
     }
 
     sp<SurfaceComposerClient> mClient;
diff --git a/libs/gui/tests/Surface_test.cpp b/libs/gui/tests/Surface_test.cpp
index c745505..a885e92 100644
--- a/libs/gui/tests/Surface_test.cpp
+++ b/libs/gui/tests/Surface_test.cpp
@@ -19,11 +19,11 @@
 #include <gtest/gtest.h>
 
 #include <SurfaceFlingerProperties.h>
+#include <android/gui/IDisplayEventConnection.h>
 #include <android/hardware/configstore/1.0/ISurfaceFlingerConfigs.h>
 #include <binder/ProcessState.h>
 #include <configstore/Utils.h>
 #include <gui/BufferItemConsumer.h>
-#include <gui/IDisplayEventConnection.h>
 #include <gui/IProducerListener.h>
 #include <gui/ISurfaceComposer.h>
 #include <gui/Surface.h>
@@ -31,9 +31,12 @@
 #include <gui/SyncScreenCaptureListener.h>
 #include <inttypes.h>
 #include <private/gui/ComposerService.h>
+#include <private/gui/ComposerServiceAIDL.h>
+#include <sys/types.h>
 #include <ui/BufferQueueDefs.h>
 #include <ui/DisplayMode.h>
 #include <ui/Rect.h>
+#include <utils/Errors.h>
 #include <utils/String8.h>
 
 #include <limits>
@@ -45,6 +48,9 @@
 // retrieve wide-color and hdr settings from configstore
 using namespace android::hardware::configstore;
 using namespace android::hardware::configstore::V1_0;
+using aidl::android::hardware::graphics::common::DisplayDecorationSupport;
+using gui::IDisplayEventConnection;
+using gui::IRegionSamplingListener;
 using ui::ColorMode;
 
 using Transaction = SurfaceComposerClient::Transaction;
@@ -200,13 +206,13 @@
 
     static status_t captureDisplay(DisplayCaptureArgs& captureArgs,
                                    ScreenCaptureResults& captureResults) {
-        const auto sf = ComposerService::getComposerService();
+        const auto sf = ComposerServiceAIDL::getComposerService();
         SurfaceComposerClient::Transaction().apply(true);
 
         const sp<SyncScreenCaptureListener> captureListener = new SyncScreenCaptureListener();
-        status_t status = sf->captureDisplay(captureArgs, captureListener);
-        if (status != NO_ERROR) {
-            return status;
+        binder::Status status = sf->captureDisplay(captureArgs, captureListener);
+        if (status.transactionError() != NO_ERROR) {
+            return status.transactionError();
         }
         captureResults = captureListener->waitForResults();
         return captureResults.result;
@@ -753,21 +759,14 @@
     }
     status_t setActiveColorMode(const sp<IBinder>& /*display*/,
         ColorMode /*colorMode*/) override { return NO_ERROR; }
-    status_t captureDisplay(const DisplayCaptureArgs& /* captureArgs */,
-                            const sp<IScreenCaptureListener>& /* captureListener */) override {
+    status_t getBootDisplayModeSupport(bool* /*outSupport*/) const override { return NO_ERROR; }
+    status_t setBootDisplayMode(const sp<IBinder>& /*display*/, ui::DisplayModeId /*id*/) override {
         return NO_ERROR;
     }
+    status_t clearBootDisplayMode(const sp<IBinder>& /*display*/) override { return NO_ERROR; }
     void setAutoLowLatencyMode(const sp<IBinder>& /*display*/, bool /*on*/) override {}
     void setGameContentType(const sp<IBinder>& /*display*/, bool /*on*/) override {}
-    status_t captureDisplay(uint64_t /*displayOrLayerStack*/,
-                            const sp<IScreenCaptureListener>& /* captureListener */) override {
-        return NO_ERROR;
-    }
-    virtual status_t captureLayers(
-            const LayerCaptureArgs& /* captureArgs */,
-            const sp<IScreenCaptureListener>& /* captureListener */) override {
-        return NO_ERROR;
-    }
+
     status_t clearAnimationFrameStats() override { return NO_ERROR; }
     status_t getAnimationFrameStats(FrameStats* /*outStats*/) const override {
         return NO_ERROR;
@@ -882,12 +881,14 @@
         return NO_ERROR;
     }
 
-    status_t setFrameRate(const sp<IGraphicBufferProducer>& /*surface*/, float /*frameRate*/,
-                          int8_t /*compatibility*/, int8_t /*changeFrameRateStrategy*/) override {
+    status_t getDisplayDecorationSupport(
+            const sp<IBinder>& /*displayToken*/,
+            std::optional<DisplayDecorationSupport>* /*outSupport*/) const override {
         return NO_ERROR;
     }
 
-    status_t acquireFrameRateFlexibilityToken(sp<IBinder>* /*outToken*/) override {
+    status_t setFrameRate(const sp<IGraphicBufferProducer>& /*surface*/, float /*frameRate*/,
+                          int8_t /*compatibility*/, int8_t /*changeFrameRateStrategy*/) override {
         return NO_ERROR;
     }
 
@@ -915,6 +916,8 @@
         return NO_ERROR;
     }
 
+    status_t setOverrideFrameRate(uid_t /*uid*/, float /*frameRate*/) override { return NO_ERROR; }
+
 protected:
     IBinder* onAsBinder() override { return nullptr; }
 
diff --git a/libs/gui/tests/VsyncEventData_test.cpp b/libs/gui/tests/VsyncEventData_test.cpp
new file mode 100644
index 0000000..f114522
--- /dev/null
+++ b/libs/gui/tests/VsyncEventData_test.cpp
@@ -0,0 +1,58 @@
+/*
+ * 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 <gtest/gtest.h>
+
+#include <binder/Parcel.h>
+
+#include <gui/VsyncEventData.h>
+
+namespace android {
+
+using gui::ParcelableVsyncEventData;
+using gui::VsyncEventData;
+using FrameTimeline = gui::VsyncEventData::FrameTimeline;
+
+namespace test {
+
+TEST(ParcelableVsyncEventData, Parcelling) {
+    ParcelableVsyncEventData data;
+    data.vsync.frameInterval = 789;
+    data.vsync.preferredFrameTimelineIndex = 1;
+    FrameTimeline timeline0 = FrameTimeline{1, 2, 3};
+    FrameTimeline timeline1 = FrameTimeline{4, 5, 6};
+    data.vsync.frameTimelines[0] = timeline0;
+    data.vsync.frameTimelines[1] = timeline1;
+
+    Parcel p;
+    data.writeToParcel(&p);
+    p.setDataPosition(0);
+
+    ParcelableVsyncEventData data2;
+    data2.readFromParcel(&p);
+    ASSERT_EQ(data.vsync.frameInterval, data2.vsync.frameInterval);
+    ASSERT_EQ(data.vsync.preferredFrameTimelineIndex, data2.vsync.preferredFrameTimelineIndex);
+    for (int i = 0; i < VsyncEventData::kFrameTimelinesLength; i++) {
+        ASSERT_EQ(data.vsync.frameTimelines[i].vsyncId, data2.vsync.frameTimelines[i].vsyncId);
+        ASSERT_EQ(data.vsync.frameTimelines[i].deadlineTimestamp,
+                  data2.vsync.frameTimelines[i].deadlineTimestamp);
+        ASSERT_EQ(data.vsync.frameTimelines[i].expectedPresentationTime,
+                  data2.vsync.frameTimelines[i].expectedPresentationTime);
+    }
+}
+
+} // namespace test
+} // namespace android
diff --git a/libs/gui/tests/WindowInfo_test.cpp b/libs/gui/tests/WindowInfo_test.cpp
index a4f436c..c51b244 100644
--- a/libs/gui/tests/WindowInfo_test.cpp
+++ b/libs/gui/tests/WindowInfo_test.cpp
@@ -49,8 +49,8 @@
     i.windowToken = new BBinder();
     i.id = 1;
     i.name = "Foobar";
-    i.flags = WindowInfo::Flag::SLIPPERY;
-    i.type = WindowInfo::Type::INPUT_METHOD;
+    i.layoutParamsFlags = WindowInfo::Flag::SLIPPERY;
+    i.layoutParamsType = WindowInfo::Type::INPUT_METHOD;
     i.dispatchingTimeout = 12s;
     i.frameLeft = 93;
     i.frameTop = 34;
@@ -60,20 +60,12 @@
     i.globalScaleFactor = 0.3;
     i.alpha = 0.7;
     i.transform.set({0.4, -1, 100, 0.5, 0, 40, 0, 0, 1});
-    i.displayOrientation = ui::Transform::ROT_0;
-    i.displayWidth = 1000;
-    i.displayHeight = 2000;
-    i.visible = false;
-    i.focusable = false;
-    i.hasWallpaper = false;
-    i.paused = false;
     i.touchOcclusionMode = TouchOcclusionMode::ALLOW;
     i.ownerPid = 19;
     i.ownerUid = 24;
     i.packageName = "com.example.package";
-    i.inputFeatures = WindowInfo::Feature::DISABLE_USER_ACTIVITY;
+    i.inputConfig = WindowInfo::InputConfig::NOT_FOCUSABLE;
     i.displayId = 34;
-    i.portalToDisplayId = 2;
     i.replaceTouchableRegionWithCrop = true;
     i.touchableRegionCropHandle = touchableRegionCropHandle;
     i.applicationInfo.name = "ApplicationFooBar";
@@ -89,8 +81,8 @@
     ASSERT_EQ(i.windowToken, i2.windowToken);
     ASSERT_EQ(i.id, i2.id);
     ASSERT_EQ(i.name, i2.name);
-    ASSERT_EQ(i.flags, i2.flags);
-    ASSERT_EQ(i.type, i2.type);
+    ASSERT_EQ(i.layoutParamsFlags, i2.layoutParamsFlags);
+    ASSERT_EQ(i.layoutParamsType, i2.layoutParamsType);
     ASSERT_EQ(i.dispatchingTimeout, i2.dispatchingTimeout);
     ASSERT_EQ(i.frameLeft, i2.frameLeft);
     ASSERT_EQ(i.frameTop, i2.frameTop);
@@ -100,19 +92,12 @@
     ASSERT_EQ(i.globalScaleFactor, i2.globalScaleFactor);
     ASSERT_EQ(i.alpha, i2.alpha);
     ASSERT_EQ(i.transform, i2.transform);
-    ASSERT_EQ(i.displayWidth, i2.displayWidth);
-    ASSERT_EQ(i.displayHeight, i2.displayHeight);
-    ASSERT_EQ(i.visible, i2.visible);
-    ASSERT_EQ(i.focusable, i2.focusable);
-    ASSERT_EQ(i.hasWallpaper, i2.hasWallpaper);
-    ASSERT_EQ(i.paused, i2.paused);
     ASSERT_EQ(i.touchOcclusionMode, i2.touchOcclusionMode);
     ASSERT_EQ(i.ownerPid, i2.ownerPid);
     ASSERT_EQ(i.ownerUid, i2.ownerUid);
     ASSERT_EQ(i.packageName, i2.packageName);
-    ASSERT_EQ(i.inputFeatures, i2.inputFeatures);
+    ASSERT_EQ(i.inputConfig, i2.inputConfig);
     ASSERT_EQ(i.displayId, i2.displayId);
-    ASSERT_EQ(i.portalToDisplayId, i2.portalToDisplayId);
     ASSERT_EQ(i.replaceTouchableRegionWithCrop, i2.replaceTouchableRegionWithCrop);
     ASSERT_EQ(i.touchableRegionCropHandle, i2.touchableRegionCropHandle);
     ASSERT_EQ(i.applicationInfo, i2.applicationInfo);
diff --git a/libs/input/Android.bp b/libs/input/Android.bp
index 71a1291..18fb7c1 100644
--- a/libs/input/Android.bp
+++ b/libs/input/Android.bp
@@ -30,11 +30,13 @@
         "android/os/IInputConstants.aidl",
         "android/os/InputEventInjectionResult.aidl",
         "android/os/InputEventInjectionSync.aidl",
+        "android/os/InputConfig.aidl",
     ],
 }
 
 cc_library {
     name: "libinput",
+    cpp_std: "c++20",
     host_supported: true,
     cflags: [
         "-Wall",
@@ -78,11 +80,8 @@
         android: {
             srcs: [
                 "InputTransport.cpp",
-                "android/os/BlockUntrustedTouchesMode.aidl",
-                "android/os/IInputConstants.aidl",
                 "android/os/IInputFlinger.aidl",
-                "android/os/InputEventInjectionResult.aidl",
-                "android/os/InputEventInjectionSync.aidl",
+                ":inputconstants_aidl",
             ],
 
             export_shared_lib_headers: ["libbinder"],
@@ -118,6 +117,7 @@
                 "InputTransport.cpp",
                 "android/os/IInputConstants.aidl",
                 "android/os/IInputFlinger.aidl",
+                "android/os/InputConfig.aidl",
             ],
             static_libs: [
                 "libhostgraphics",
diff --git a/libs/input/Input.cpp b/libs/input/Input.cpp
index 5f440b7..fe1754c 100644
--- a/libs/input/Input.cpp
+++ b/libs/input/Input.cpp
@@ -20,12 +20,11 @@
 #include <attestation/HmacKeyManager.h>
 #include <cutils/compiler.h>
 #include <inttypes.h>
-#include <limits.h>
 #include <string.h>
 
-#include <android-base/properties.h>
 #include <android-base/stringprintf.h>
 #include <gui/constants.h>
+#include <input/DisplayViewport.h>
 #include <input/Input.h>
 #include <input/InputDevice.h>
 #include <input/InputEventLabels.h>
@@ -43,15 +42,6 @@
 
 namespace {
 
-// When per-window-input-rotation is enabled, InputFlinger works in the un-rotated display
-// coordinates and SurfaceFlinger includes the display rotation in the input window transforms.
-bool isPerWindowInputRotationEnabled() {
-    static const bool PER_WINDOW_INPUT_ROTATION =
-            base::GetBoolProperty("persist.debug.per_window_input_rotation", false);
-
-    return PER_WINDOW_INPUT_ROTATION;
-}
-
 float transformAngle(const ui::Transform& transform, float angleRadians) {
     // Construct and transform a vector oriented at the specified clockwise angle from vertical.
     // Coordinate system: down is increasing Y, right is increasing X.
@@ -71,40 +61,17 @@
     return atan2f(transformedPoint.x, -transformedPoint.y);
 }
 
-// Rotates the given point to the specified orientation. If the display width and height are
-// provided, the point is rotated in the screen space. Otherwise, the point is rotated about the
-// origin. This helper is used to avoid the extra overhead of creating new Transforms.
-vec2 rotatePoint(uint32_t orientation, float x, float y, int32_t displayWidth = 0,
-                 int32_t displayHeight = 0) {
-    if (orientation == ui::Transform::ROT_0) {
-        return {x, y};
-    }
-
-    vec2 xy(x, y);
-    if (orientation == ui::Transform::ROT_90) {
-        xy.x = displayHeight - y;
-        xy.y = x;
-    } else if (orientation == ui::Transform::ROT_180) {
-        xy.x = displayWidth - x;
-        xy.y = displayHeight - y;
-    } else if (orientation == ui::Transform::ROT_270) {
-        xy.x = y;
-        xy.y = displayWidth - x;
-    }
-    return xy;
+bool shouldDisregardTransformation(uint32_t source) {
+    // Do not apply any transformations to axes from joysticks or touchpads.
+    return isFromSource(source, AINPUT_SOURCE_CLASS_JOYSTICK) ||
+            isFromSource(source, AINPUT_SOURCE_CLASS_POSITION);
 }
 
-vec2 applyTransformWithoutTranslation(const ui::Transform& transform, float x, float y) {
-    const vec2 transformedXy = transform.transform(x, y);
-    const vec2 transformedOrigin = transform.transform(0, 0);
-    return transformedXy - transformedOrigin;
-}
-
-bool shouldDisregardWindowTranslation(uint32_t source) {
+bool shouldDisregardOffset(uint32_t source) {
     // Pointer events are the only type of events that refer to absolute coordinates on the display,
     // so we should apply the entire window transform. For other types of events, we should make
     // sure to not apply the window translation/offset.
-    return (source & AINPUT_SOURCE_CLASS_POINTER) == 0;
+    return !isFromSource(source, AINPUT_SOURCE_CLASS_POINTER);
 }
 
 } // namespace
@@ -148,6 +115,12 @@
 
 // --- InputEvent ---
 
+vec2 transformWithoutTranslation(const ui::Transform& transform, const vec2& xy) {
+    const vec2 transformedXy = transform.transform(xy);
+    const vec2 transformedOrigin = transform.transform(0, 0);
+    return transformedXy - transformedOrigin;
+}
+
 const char* inputEventTypeToString(int32_t type) {
     switch (type) {
         case AINPUT_EVENT_TYPE_KEY: {
@@ -165,16 +138,62 @@
         case AINPUT_EVENT_TYPE_DRAG: {
             return "DRAG";
         }
+        case AINPUT_EVENT_TYPE_TOUCH_MODE: {
+            return "TOUCH_MODE";
+        }
     }
     return "UNKNOWN";
 }
 
+std::string inputEventSourceToString(int32_t source) {
+    if (source == AINPUT_SOURCE_UNKNOWN) {
+        return "UNKNOWN";
+    }
+    if (source == static_cast<int32_t>(AINPUT_SOURCE_ANY)) {
+        return "ANY";
+    }
+    static const std::map<int32_t, const char*> SOURCES{
+            {AINPUT_SOURCE_KEYBOARD, "KEYBOARD"},
+            {AINPUT_SOURCE_DPAD, "DPAD"},
+            {AINPUT_SOURCE_GAMEPAD, "GAMEPAD"},
+            {AINPUT_SOURCE_TOUCHSCREEN, "TOUCHSCREEN"},
+            {AINPUT_SOURCE_MOUSE, "MOUSE"},
+            {AINPUT_SOURCE_STYLUS, "STYLUS"},
+            {AINPUT_SOURCE_BLUETOOTH_STYLUS, "BLUETOOTH_STYLUS"},
+            {AINPUT_SOURCE_TRACKBALL, "TRACKBALL"},
+            {AINPUT_SOURCE_MOUSE_RELATIVE, "MOUSE_RELATIVE"},
+            {AINPUT_SOURCE_TOUCHPAD, "TOUCHPAD"},
+            {AINPUT_SOURCE_TOUCH_NAVIGATION, "TOUCH_NAVIGATION"},
+            {AINPUT_SOURCE_JOYSTICK, "JOYSTICK"},
+            {AINPUT_SOURCE_HDMI, "HDMI"},
+            {AINPUT_SOURCE_SENSOR, "SENSOR"},
+            {AINPUT_SOURCE_ROTARY_ENCODER, "ROTARY_ENCODER"},
+    };
+    std::string result;
+    for (const auto& [source_entry, str] : SOURCES) {
+        if ((source & source_entry) == source_entry) {
+            if (!result.empty()) {
+                result += " | ";
+            }
+            result += str;
+        }
+    }
+    if (result.empty()) {
+        result = StringPrintf("0x%08x", source);
+    }
+    return result;
+}
+
+bool isFromSource(uint32_t source, uint32_t test) {
+    return (source & test) == test;
+}
+
 VerifiedKeyEvent verifiedKeyEventFromKeyEvent(const KeyEvent& event) {
     return {{VerifiedInputEvent::Type::KEY, event.getDeviceId(), event.getEventTime(),
              event.getSource(), event.getDisplayId()},
             event.getAction(),
-            event.getDownTime(),
             event.getFlags() & VERIFIED_KEY_EVENT_FLAGS,
+            event.getDownTime(),
             event.getKeyCode(),
             event.getScanCode(),
             event.getMetaState(),
@@ -187,8 +206,8 @@
             event.getRawX(0),
             event.getRawY(0),
             event.getActionMasked(),
-            event.getDownTime(),
             event.getFlags() & VERIFIED_MOTION_EVENT_FLAGS,
+            event.getDownTime(),
             event.getMetaState(),
             event.getButtonState()};
 }
@@ -325,15 +344,6 @@
     scaleAxisValue(*this, AMOTION_EVENT_AXIS_RELATIVE_Y, windowYScale);
 }
 
-void PointerCoords::scale(float globalScaleFactor) {
-    scale(globalScaleFactor, globalScaleFactor, globalScaleFactor);
-}
-
-void PointerCoords::applyOffset(float xOffset, float yOffset) {
-    setAxisValue(AMOTION_EVENT_AXIS_X, getX() + xOffset);
-    setAxisValue(AMOTION_EVENT_AXIS_Y, getY() + yOffset);
-}
-
 #ifdef __linux__
 status_t PointerCoords::readFromParcel(Parcel* parcel) {
     bits = parcel->readInt64();
@@ -427,8 +437,7 @@
                              int32_t buttonState, MotionClassification classification,
                              const ui::Transform& transform, float xPrecision, float yPrecision,
                              float rawXCursorPosition, float rawYCursorPosition,
-                             uint32_t displayOrientation, int32_t displayWidth,
-                             int32_t displayHeight, nsecs_t downTime, nsecs_t eventTime,
+                             const ui::Transform& rawTransform, nsecs_t downTime, nsecs_t eventTime,
                              size_t pointerCount, const PointerProperties* pointerProperties,
                              const PointerCoords* pointerCoords) {
     InputEvent::initialize(id, deviceId, source, displayId, hmac);
@@ -444,12 +453,11 @@
     mYPrecision = yPrecision;
     mRawXCursorPosition = rawXCursorPosition;
     mRawYCursorPosition = rawYCursorPosition;
-    mDisplayOrientation = displayOrientation;
-    mDisplayWidth = displayWidth;
-    mDisplayHeight = displayHeight;
+    mRawTransform = rawTransform;
     mDownTime = downTime;
     mPointerProperties.clear();
-    mPointerProperties.appendArray(pointerProperties, pointerCount);
+    mPointerProperties.insert(mPointerProperties.end(), &pointerProperties[0],
+                              &pointerProperties[pointerCount]);
     mSampleEventTimes.clear();
     mSamplePointerCoords.clear();
     addSample(eventTime, pointerCoords);
@@ -470,9 +478,7 @@
     mYPrecision = other->mYPrecision;
     mRawXCursorPosition = other->mRawXCursorPosition;
     mRawYCursorPosition = other->mRawYCursorPosition;
-    mDisplayOrientation = other->mDisplayOrientation;
-    mDisplayWidth = other->mDisplayWidth;
-    mDisplayHeight = other->mDisplayHeight;
+    mRawTransform = other->mRawTransform;
     mDownTime = other->mDownTime;
     mPointerProperties = other->mPointerProperties;
 
@@ -485,8 +491,10 @@
         mSamplePointerCoords.clear();
         size_t pointerCount = other->getPointerCount();
         size_t historySize = other->getHistorySize();
-        mSamplePointerCoords.appendArray(other->mSamplePointerCoords.array()
-                + (historySize * pointerCount), pointerCount);
+        mSamplePointerCoords
+                .insert(mSamplePointerCoords.end(),
+                        &other->mSamplePointerCoords[historySize * pointerCount],
+                        &other->mSamplePointerCoords[historySize * pointerCount + pointerCount]);
     }
 }
 
@@ -494,7 +502,26 @@
         int64_t eventTime,
         const PointerCoords* pointerCoords) {
     mSampleEventTimes.push_back(eventTime);
-    mSamplePointerCoords.appendArray(pointerCoords, getPointerCount());
+    mSamplePointerCoords.insert(mSamplePointerCoords.end(), &pointerCoords[0],
+                                &pointerCoords[getPointerCount()]);
+}
+
+int 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;
+        case ui::Transform::ROT_90:
+            return DISPLAY_ORIENTATION_270;
+        case ui::Transform::ROT_180:
+            return DISPLAY_ORIENTATION_180;
+        case ui::Transform::ROT_270:
+            return DISPLAY_ORIENTATION_90;
+        default:
+            return -1;
+    }
 }
 
 float MotionEvent::getXCursorPosition() const {
@@ -533,62 +560,20 @@
 
 float MotionEvent::getHistoricalRawAxisValue(int32_t axis, size_t pointerIndex,
                                              size_t historicalIndex) const {
-    const PointerCoords* coords = getHistoricalRawPointerCoords(pointerIndex, historicalIndex);
-
-    if (!isPerWindowInputRotationEnabled()) return coords->getAxisValue(axis);
-
-    if (axis == AMOTION_EVENT_AXIS_X || axis == AMOTION_EVENT_AXIS_Y) {
-        // For compatibility, convert raw coordinates into "oriented screen space". Once app
-        // developers are educated about getRaw, we can consider removing this.
-        const vec2 xy = shouldDisregardWindowTranslation(mSource)
-                ? rotatePoint(mDisplayOrientation, coords->getX(), coords->getY())
-                : rotatePoint(mDisplayOrientation, coords->getX(), coords->getY(), mDisplayWidth,
-                              mDisplayHeight);
-        static_assert(AMOTION_EVENT_AXIS_X == 0 && AMOTION_EVENT_AXIS_Y == 1);
-        return xy[axis];
-    }
-
-    if (axis == AMOTION_EVENT_AXIS_RELATIVE_X || axis == AMOTION_EVENT_AXIS_RELATIVE_Y) {
-        // For compatibility, since we convert raw coordinates into "oriented screen space", we
-        // need to convert the relative axes into the same orientation for consistency.
-        const vec2 relativeXy = rotatePoint(mDisplayOrientation,
-                                            coords->getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X),
-                                            coords->getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y));
-        return axis == AMOTION_EVENT_AXIS_RELATIVE_X ? relativeXy.x : relativeXy.y;
-    }
-
-    return coords->getAxisValue(axis);
+    const PointerCoords& coords = *getHistoricalRawPointerCoords(pointerIndex, historicalIndex);
+    return calculateTransformedAxisValue(axis, mSource, mRawTransform, coords);
 }
 
 float MotionEvent::getHistoricalAxisValue(int32_t axis, size_t pointerIndex,
                                           size_t historicalIndex) const {
-    const PointerCoords* coords = getHistoricalRawPointerCoords(pointerIndex, historicalIndex);
-
-    if (axis == AMOTION_EVENT_AXIS_X || axis == AMOTION_EVENT_AXIS_Y) {
-        const vec2 xy = shouldDisregardWindowTranslation(mSource)
-                ? applyTransformWithoutTranslation(mTransform, coords->getX(), coords->getY())
-                : mTransform.transform(coords->getXYValue());
-        static_assert(AMOTION_EVENT_AXIS_X == 0 && AMOTION_EVENT_AXIS_Y == 1);
-        return xy[axis];
-    }
-
-    if (axis == AMOTION_EVENT_AXIS_RELATIVE_X || axis == AMOTION_EVENT_AXIS_RELATIVE_Y) {
-        const vec2 relativeXy =
-                applyTransformWithoutTranslation(mTransform,
-                                                 coords->getAxisValue(
-                                                         AMOTION_EVENT_AXIS_RELATIVE_X),
-                                                 coords->getAxisValue(
-                                                         AMOTION_EVENT_AXIS_RELATIVE_Y));
-        return axis == AMOTION_EVENT_AXIS_RELATIVE_X ? relativeXy.x : relativeXy.y;
-    }
-
-    return coords->getAxisValue(axis);
+    const PointerCoords& coords = *getHistoricalRawPointerCoords(pointerIndex, historicalIndex);
+    return calculateTransformedAxisValue(axis, mSource, mTransform, coords);
 }
 
 ssize_t MotionEvent::findPointerIndex(int32_t pointerId) const {
     size_t pointerCount = mPointerProperties.size();
     for (size_t i = 0; i < pointerCount; i++) {
-        if (mPointerProperties.itemAt(i).id == pointerId) {
+        if (mPointerProperties[i].id == pointerId) {
             return i;
         }
     }
@@ -603,13 +588,14 @@
 
 void MotionEvent::scale(float globalScaleFactor) {
     mTransform.set(mTransform.tx() * globalScaleFactor, mTransform.ty() * globalScaleFactor);
+    mRawTransform.set(mRawTransform.tx() * globalScaleFactor,
+                      mRawTransform.ty() * globalScaleFactor);
     mXPrecision *= globalScaleFactor;
     mYPrecision *= globalScaleFactor;
 
     size_t numSamples = mSamplePointerCoords.size();
     for (size_t i = 0; i < numSamples; i++) {
-        mSamplePointerCoords.editItemAt(i).scale(globalScaleFactor, globalScaleFactor,
-                                                 globalScaleFactor);
+        mSamplePointerCoords[i].scale(globalScaleFactor, globalScaleFactor, globalScaleFactor);
     }
 }
 
@@ -619,15 +605,6 @@
     ui::Transform newTransform;
     newTransform.set(matrix);
     mTransform = newTransform * mTransform;
-
-    // We need to update the AXIS_ORIENTATION value here to maintain the old behavior where the
-    // orientation angle is not affected by the initial transformation set in the MotionEvent.
-    std::for_each(mSamplePointerCoords.begin(), mSamplePointerCoords.end(),
-                  [&newTransform](PointerCoords& c) {
-                      float orientation = c.getAxisValue(AMOTION_EVENT_AXIS_ORIENTATION);
-                      c.setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION,
-                                     transformAngle(newTransform, orientation));
-                  });
 }
 
 void MotionEvent::applyTransform(const std::array<float, 9>& matrix) {
@@ -704,21 +681,23 @@
     mYPrecision = parcel->readFloat();
     mRawXCursorPosition = parcel->readFloat();
     mRawYCursorPosition = parcel->readFloat();
-    mDisplayOrientation = parcel->readUint32();
-    mDisplayWidth = parcel->readInt32();
-    mDisplayHeight = parcel->readInt32();
+
+    result = android::readFromParcel(mRawTransform, *parcel);
+    if (result != OK) {
+        return result;
+    }
     mDownTime = parcel->readInt64();
 
     mPointerProperties.clear();
-    mPointerProperties.setCapacity(pointerCount);
+    mPointerProperties.reserve(pointerCount);
     mSampleEventTimes.clear();
     mSampleEventTimes.reserve(sampleCount);
     mSamplePointerCoords.clear();
-    mSamplePointerCoords.setCapacity(sampleCount * pointerCount);
+    mSamplePointerCoords.reserve(sampleCount * pointerCount);
 
     for (size_t i = 0; i < pointerCount; i++) {
-        mPointerProperties.push();
-        PointerProperties& properties = mPointerProperties.editTop();
+        mPointerProperties.push_back({});
+        PointerProperties& properties = mPointerProperties.back();
         properties.id = parcel->readInt32();
         properties.toolType = parcel->readInt32();
     }
@@ -727,8 +706,8 @@
         sampleCount--;
         mSampleEventTimes.push_back(parcel->readInt64());
         for (size_t i = 0; i < pointerCount; i++) {
-            mSamplePointerCoords.push();
-            status_t status = mSamplePointerCoords.editTop().readFromParcel(parcel);
+            mSamplePointerCoords.push_back({});
+            status_t status = mSamplePointerCoords.back().readFromParcel(parcel);
             if (status) {
                 return status;
             }
@@ -766,18 +745,20 @@
     parcel->writeFloat(mYPrecision);
     parcel->writeFloat(mRawXCursorPosition);
     parcel->writeFloat(mRawYCursorPosition);
-    parcel->writeUint32(mDisplayOrientation);
-    parcel->writeInt32(mDisplayWidth);
-    parcel->writeInt32(mDisplayHeight);
+
+    result = android::writeToParcel(mRawTransform, *parcel);
+    if (result != OK) {
+        return result;
+    }
     parcel->writeInt64(mDownTime);
 
     for (size_t i = 0; i < pointerCount; i++) {
-        const PointerProperties& properties = mPointerProperties.itemAt(i);
+        const PointerProperties& properties = mPointerProperties[i];
         parcel->writeInt32(properties.id);
         parcel->writeInt32(properties.toolType);
     }
 
-    const PointerCoords* pc = mSamplePointerCoords.array();
+    const PointerCoords* pc = mSamplePointerCoords.data();
     for (size_t h = 0; h < sampleCount; h++) {
         parcel->writeInt64(mSampleEventTimes[h]);
         for (size_t i = 0; i < pointerCount; i++) {
@@ -792,7 +773,7 @@
 #endif
 
 bool MotionEvent::isTouchEvent(uint32_t source, int32_t action) {
-    if (source & AINPUT_SOURCE_CLASS_POINTER) {
+    if (isFromSource(source, AINPUT_SOURCE_CLASS_POINTER)) {
         // Specifically excludes HOVER_MOVE and SCROLL.
         switch (action & AMOTION_EVENT_ACTION_MASK) {
         case AMOTION_EVENT_ACTION_DOWN:
@@ -830,9 +811,9 @@
         case AMOTION_EVENT_ACTION_OUTSIDE:
             return "OUTSIDE";
         case AMOTION_EVENT_ACTION_POINTER_DOWN:
-            return "POINTER_DOWN";
+            return StringPrintf("POINTER_DOWN(%" PRId32 ")", MotionEvent::getActionIndex(action));
         case AMOTION_EVENT_ACTION_POINTER_UP:
-            return "POINTER_UP";
+            return StringPrintf("POINTER_UP(%" PRId32 ")", MotionEvent::getActionIndex(action));
         case AMOTION_EVENT_ACTION_HOVER_MOVE:
             return "HOVER_MOVE";
         case AMOTION_EVENT_ACTION_SCROLL:
@@ -849,19 +830,90 @@
     return android::base::StringPrintf("%" PRId32, action);
 }
 
+// Apply the given transformation to the point without checking whether the entire transform
+// should be disregarded altogether for the provided source.
+static inline vec2 calculateTransformedXYUnchecked(uint32_t source, const ui::Transform& transform,
+                                                   const vec2& xy) {
+    return shouldDisregardOffset(source) ? transformWithoutTranslation(transform, xy)
+                                         : transform.transform(xy);
+}
+
+vec2 MotionEvent::calculateTransformedXY(uint32_t source, const ui::Transform& transform,
+                                         const vec2& xy) {
+    if (shouldDisregardTransformation(source)) {
+        return xy;
+    }
+    return calculateTransformedXYUnchecked(source, transform, xy);
+}
+
+// Keep in sync with calculateTransformedCoords.
+float MotionEvent::calculateTransformedAxisValue(int32_t axis, uint32_t source,
+                                                 const ui::Transform& transform,
+                                                 const PointerCoords& coords) {
+    if (shouldDisregardTransformation(source)) {
+        return coords.getAxisValue(axis);
+    }
+
+    if (axis == AMOTION_EVENT_AXIS_X || axis == AMOTION_EVENT_AXIS_Y) {
+        const vec2 xy = calculateTransformedXYUnchecked(source, transform, coords.getXYValue());
+        static_assert(AMOTION_EVENT_AXIS_X == 0 && AMOTION_EVENT_AXIS_Y == 1);
+        return xy[axis];
+    }
+
+    if (axis == AMOTION_EVENT_AXIS_RELATIVE_X || axis == AMOTION_EVENT_AXIS_RELATIVE_Y) {
+        const vec2 relativeXy =
+                transformWithoutTranslation(transform,
+                                            {coords.getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X),
+                                             coords.getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y)});
+        return axis == AMOTION_EVENT_AXIS_RELATIVE_X ? relativeXy.x : relativeXy.y;
+    }
+
+    if (axis == AMOTION_EVENT_AXIS_ORIENTATION) {
+        return transformAngle(transform, coords.getAxisValue(AMOTION_EVENT_AXIS_ORIENTATION));
+    }
+
+    return coords.getAxisValue(axis);
+}
+
+// Keep in sync with calculateTransformedAxisValue. This is an optimization of
+// calculateTransformedAxisValue for all PointerCoords axes.
+PointerCoords MotionEvent::calculateTransformedCoords(uint32_t source,
+                                                      const ui::Transform& transform,
+                                                      const PointerCoords& coords) {
+    if (shouldDisregardTransformation(source)) {
+        return coords;
+    }
+    PointerCoords out = coords;
+
+    const vec2 xy = calculateTransformedXYUnchecked(source, transform, coords.getXYValue());
+    out.setAxisValue(AMOTION_EVENT_AXIS_X, xy.x);
+    out.setAxisValue(AMOTION_EVENT_AXIS_Y, xy.y);
+
+    const vec2 relativeXy =
+            transformWithoutTranslation(transform,
+                                        {coords.getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X),
+                                         coords.getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y)});
+    out.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X, relativeXy.x);
+    out.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y, relativeXy.y);
+
+    out.setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION,
+                     transformAngle(transform,
+                                    coords.getAxisValue(AMOTION_EVENT_AXIS_ORIENTATION)));
+
+    return out;
+}
+
 // --- FocusEvent ---
 
-void FocusEvent::initialize(int32_t id, bool hasFocus, bool inTouchMode) {
+void FocusEvent::initialize(int32_t id, bool hasFocus) {
     InputEvent::initialize(id, ReservedInputDeviceId::VIRTUAL_KEYBOARD_ID, AINPUT_SOURCE_UNKNOWN,
                            ADISPLAY_ID_NONE, INVALID_HMAC);
     mHasFocus = hasFocus;
-    mInTouchMode = inTouchMode;
 }
 
 void FocusEvent::initialize(const FocusEvent& from) {
     InputEvent::initialize(from);
     mHasFocus = from.mHasFocus;
-    mInTouchMode = from.mInTouchMode;
 }
 
 // --- CaptureEvent ---
@@ -894,6 +946,19 @@
     mY = from.mY;
 }
 
+// --- TouchModeEvent ---
+
+void TouchModeEvent::initialize(int32_t id, bool isInTouchMode) {
+    InputEvent::initialize(id, ReservedInputDeviceId::VIRTUAL_KEYBOARD_ID, AINPUT_SOURCE_UNKNOWN,
+                           ADISPLAY_ID_NONE, INVALID_HMAC);
+    mIsInTouchMode = isInTouchMode;
+}
+
+void TouchModeEvent::initialize(const TouchModeEvent& from) {
+    InputEvent::initialize(from);
+    mIsInTouchMode = from.mIsInTouchMode;
+}
+
 // --- PooledInputEventFactory ---
 
 PooledInputEventFactory::PooledInputEventFactory(size_t maxPoolSize) :
@@ -948,6 +1013,15 @@
     return event;
 }
 
+TouchModeEvent* PooledInputEventFactory::createTouchModeEvent() {
+    if (mTouchModeEventPool.empty()) {
+        return new TouchModeEvent();
+    }
+    TouchModeEvent* event = mTouchModeEventPool.front().release();
+    mTouchModeEventPool.pop();
+    return event;
+}
+
 void PooledInputEventFactory::recycle(InputEvent* event) {
     switch (event->getType()) {
     case AINPUT_EVENT_TYPE_KEY:
@@ -981,6 +1055,13 @@
             return;
         }
         break;
+    case AINPUT_EVENT_TYPE_TOUCH_MODE:
+        if (mTouchModeEventPool.size() < mMaxPoolSize) {
+            mTouchModeEventPool.push(
+                    std::unique_ptr<TouchModeEvent>(static_cast<TouchModeEvent*>(event)));
+            return;
+        }
+        break;
     }
     delete event;
 }
diff --git a/libs/input/InputDevice.cpp b/libs/input/InputDevice.cpp
index 1aec477..0bee1b6 100644
--- a/libs/input/InputDevice.cpp
+++ b/libs/input/InputDevice.cpp
@@ -21,7 +21,7 @@
 #include <ctype.h>
 
 #include <android-base/stringprintf.h>
-#include <ftl/NamedEnum.h>
+#include <ftl/enum.h>
 #include <input/InputDevice.h>
 #include <input/InputEventLabels.h>
 
@@ -208,10 +208,8 @@
 
 const InputDeviceInfo::MotionRange* InputDeviceInfo::getMotionRange(
         int32_t axis, uint32_t source) const {
-    size_t numRanges = mMotionRanges.size();
-    for (size_t i = 0; i < numRanges; i++) {
-        const MotionRange& range = mMotionRanges[i];
-        if (range.axis == axis && range.source == source) {
+    for (const MotionRange& range : mMotionRanges) {
+        if (range.axis == axis && isFromSource(range.source, source)) {
             return &range;
         }
     }
@@ -235,7 +233,7 @@
 void InputDeviceInfo::addSensorInfo(const InputDeviceSensorInfo& info) {
     if (mSensors.find(info.type) != mSensors.end()) {
         ALOGW("Sensor type %s already exists, will be replaced by new sensor added.",
-              NamedEnum::string(info.type).c_str());
+              ftl::enum_string(info.type).c_str());
     }
     mSensors.insert_or_assign(info.type, info);
 }
@@ -254,6 +252,13 @@
     mLights.insert_or_assign(info.id, info);
 }
 
+void InputDeviceInfo::setKeyboardType(int32_t keyboardType) {
+    static_assert(AINPUT_KEYBOARD_TYPE_NONE < AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC);
+    static_assert(AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC < AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+    // There can be multiple subdevices with different keyboard types, set it to the highest type
+    mKeyboardType = std::max(mKeyboardType, keyboardType);
+}
+
 std::vector<InputDeviceSensorInfo> InputDeviceInfo::getSensors() {
     std::vector<InputDeviceSensorInfo> infos;
     infos.reserve(mSensors.size());
diff --git a/libs/input/InputTransport.cpp b/libs/input/InputTransport.cpp
index ea8b9a7..6195052 100644
--- a/libs/input/InputTransport.cpp
+++ b/libs/input/InputTransport.cpp
@@ -30,10 +30,10 @@
 #include <android-base/stringprintf.h>
 #include <binder/Parcel.h>
 #include <cutils/properties.h>
+#include <ftl/enum.h>
 #include <log/log.h>
 #include <utils/Trace.h>
 
-#include <ftl/NamedEnum.h>
 #include <input/InputTransport.h>
 
 using android::base::StringPrintf;
@@ -116,6 +116,7 @@
         case Type::FOCUS:
         case Type::CAPTURE:
         case Type::DRAG:
+        case Type::TOUCH_MODE:
             return true;
         case Type::TIMELINE: {
             const nsecs_t gpuCompletedTime =
@@ -151,6 +152,8 @@
             return sizeof(Header) + body.drag.size();
         case Type::TIMELINE:
             return sizeof(Header) + body.timeline.size();
+        case Type::TOUCH_MODE:
+            return sizeof(Header) + body.touchMode.size();
     }
     return sizeof(Header);
 }
@@ -200,6 +203,8 @@
         case InputMessage::Type::MOTION: {
             // int32_t eventId
             msg->body.motion.eventId = body.motion.eventId;
+            // uint32_t pointerCount
+            msg->body.motion.pointerCount = body.motion.pointerCount;
             // nsecs_t eventTime
             msg->body.motion.eventTime = body.motion.eventTime;
             // int32_t deviceId
@@ -242,14 +247,14 @@
             msg->body.motion.xCursorPosition = body.motion.xCursorPosition;
             // float yCursorPosition
             msg->body.motion.yCursorPosition = body.motion.yCursorPosition;
-            // uint32_t displayOrientation
-            msg->body.motion.displayOrientation = body.motion.displayOrientation;
-            // int32_t displayWidth
-            msg->body.motion.displayWidth = body.motion.displayWidth;
-            // int32_t displayHeight
-            msg->body.motion.displayHeight = body.motion.displayHeight;
-            // uint32_t pointerCount
-            msg->body.motion.pointerCount = body.motion.pointerCount;
+
+            msg->body.motion.dsdxRaw = body.motion.dsdxRaw;
+            msg->body.motion.dtdxRaw = body.motion.dtdxRaw;
+            msg->body.motion.dtdyRaw = body.motion.dtdyRaw;
+            msg->body.motion.dsdyRaw = body.motion.dsdyRaw;
+            msg->body.motion.txRaw = body.motion.txRaw;
+            msg->body.motion.tyRaw = body.motion.tyRaw;
+
             //struct Pointer pointers[MAX_POINTERS]
             for (size_t i = 0; i < body.motion.pointerCount; i++) {
                 // PointerProperties properties
@@ -273,7 +278,6 @@
         case InputMessage::Type::FOCUS: {
             msg->body.focus.eventId = body.focus.eventId;
             msg->body.focus.hasFocus = body.focus.hasFocus;
-            msg->body.focus.inTouchMode = body.focus.inTouchMode;
             break;
         }
         case InputMessage::Type::CAPTURE: {
@@ -293,6 +297,10 @@
             msg->body.timeline.graphicsTimeline = body.timeline.graphicsTimeline;
             break;
         }
+        case InputMessage::Type::TOUCH_MODE: {
+            msg->body.touchMode.eventId = body.touchMode.eventId;
+            msg->body.touchMode.isInTouchMode = body.touchMode.isInTouchMode;
+        }
     }
 }
 
@@ -535,8 +543,8 @@
         std::array<uint8_t, 32> hmac, int32_t action, int32_t actionButton, int32_t flags,
         int32_t edgeFlags, int32_t metaState, int32_t buttonState,
         MotionClassification classification, const ui::Transform& transform, float xPrecision,
-        float yPrecision, float xCursorPosition, float yCursorPosition, uint32_t displayOrientation,
-        int32_t displayWidth, int32_t displayHeight, nsecs_t downTime, nsecs_t eventTime,
+        float yPrecision, float xCursorPosition, float yCursorPosition,
+        const ui::Transform& rawTransform, nsecs_t downTime, nsecs_t eventTime,
         uint32_t pointerCount, const PointerProperties* pointerProperties,
         const PointerCoords* pointerCoords) {
     if (ATRACE_ENABLED()) {
@@ -596,9 +604,12 @@
     msg.body.motion.yPrecision = yPrecision;
     msg.body.motion.xCursorPosition = xCursorPosition;
     msg.body.motion.yCursorPosition = yCursorPosition;
-    msg.body.motion.displayOrientation = displayOrientation;
-    msg.body.motion.displayWidth = displayWidth;
-    msg.body.motion.displayHeight = displayHeight;
+    msg.body.motion.dsdxRaw = rawTransform.dsdx();
+    msg.body.motion.dtdxRaw = rawTransform.dtdx();
+    msg.body.motion.dtdyRaw = rawTransform.dtdy();
+    msg.body.motion.dsdyRaw = rawTransform.dsdy();
+    msg.body.motion.txRaw = rawTransform.tx();
+    msg.body.motion.tyRaw = rawTransform.ty();
     msg.body.motion.downTime = downTime;
     msg.body.motion.eventTime = eventTime;
     msg.body.motion.pointerCount = pointerCount;
@@ -610,13 +621,10 @@
     return mChannel->sendMessage(&msg);
 }
 
-status_t InputPublisher::publishFocusEvent(uint32_t seq, int32_t eventId, bool hasFocus,
-                                           bool inTouchMode) {
+status_t InputPublisher::publishFocusEvent(uint32_t seq, int32_t eventId, bool hasFocus) {
     if (ATRACE_ENABLED()) {
-        std::string message =
-                StringPrintf("publishFocusEvent(inputChannel=%s, hasFocus=%s, inTouchMode=%s)",
-                             mChannel->getName().c_str(), toString(hasFocus),
-                             toString(inTouchMode));
+        std::string message = StringPrintf("publishFocusEvent(inputChannel=%s, hasFocus=%s)",
+                                           mChannel->getName().c_str(), toString(hasFocus));
         ATRACE_NAME(message.c_str());
     }
 
@@ -625,7 +633,6 @@
     msg.header.seq = seq;
     msg.body.focus.eventId = eventId;
     msg.body.focus.hasFocus = hasFocus;
-    msg.body.focus.inTouchMode = inTouchMode;
     return mChannel->sendMessage(&msg);
 }
 
@@ -665,6 +672,22 @@
     return mChannel->sendMessage(&msg);
 }
 
+status_t InputPublisher::publishTouchModeEvent(uint32_t seq, int32_t eventId, bool isInTouchMode) {
+    if (ATRACE_ENABLED()) {
+        std::string message =
+                StringPrintf("publishTouchModeEvent(inputChannel=%s, isInTouchMode=%s)",
+                             mChannel->getName().c_str(), toString(isInTouchMode));
+        ATRACE_NAME(message.c_str());
+    }
+
+    InputMessage msg;
+    msg.header.type = InputMessage::Type::TOUCH_MODE;
+    msg.header.seq = seq;
+    msg.body.touchMode.eventId = eventId;
+    msg.body.touchMode.isInTouchMode = isInTouchMode;
+    return mChannel->sendMessage(&msg);
+}
+
 android::base::Result<InputPublisher::ConsumerResponse> InputPublisher::receiveConsumerResponse() {
     if (DEBUG_TRANSPORT_ACTIONS) {
         ALOGD("channel '%s' publisher ~ %s", mChannel->getName().c_str(), __func__);
@@ -691,7 +714,7 @@
     }
 
     ALOGE("channel '%s' publisher ~ Received unexpected %s message from consumer",
-          mChannel->getName().c_str(), NamedEnum::string(msg.header.type).c_str());
+          mChannel->getName().c_str(), ftl::enum_string(msg.header.type).c_str());
     return android::base::Error(UNKNOWN_ERROR);
 }
 
@@ -833,7 +856,7 @@
             case InputMessage::Type::TIMELINE: {
                 LOG_ALWAYS_FATAL("Consumed a %s message, which should never be seen by "
                                  "InputConsumer!",
-                                 NamedEnum::string(mMsg.header.type).c_str());
+                                 ftl::enum_string(mMsg.header.type).c_str());
                 break;
             }
 
@@ -866,6 +889,16 @@
                 *outEvent = dragEvent;
                 break;
             }
+
+            case InputMessage::Type::TOUCH_MODE: {
+                TouchModeEvent* touchModeEvent = factory->createTouchModeEvent();
+                if (!touchModeEvent) return NO_MEMORY;
+
+                initializeTouchModeEvent(touchModeEvent, &mMsg);
+                *outSeq = mMsg.header.seq;
+                *outEvent = touchModeEvent;
+                break;
+            }
         }
     }
     return OK;
@@ -1285,10 +1318,6 @@
     return result;
 }
 
-bool InputConsumer::hasDeferredEvent() const {
-    return mMsgDeferred;
-}
-
 bool InputConsumer::hasPendingBatch() const {
     return !mBatches.empty();
 }
@@ -1333,8 +1362,7 @@
 }
 
 void InputConsumer::initializeFocusEvent(FocusEvent* event, const InputMessage* msg) {
-    event->initialize(msg->body.focus.eventId, msg->body.focus.hasFocus,
-                      msg->body.focus.inTouchMode);
+    event->initialize(msg->body.focus.eventId, msg->body.focus.hasFocus);
 }
 
 void InputConsumer::initializeCaptureEvent(CaptureEvent* event, const InputMessage* msg) {
@@ -1358,6 +1386,10 @@
     ui::Transform transform;
     transform.set({msg->body.motion.dsdx, msg->body.motion.dtdx, msg->body.motion.tx,
                    msg->body.motion.dtdy, msg->body.motion.dsdy, msg->body.motion.ty, 0, 0, 1});
+    ui::Transform displayTransform;
+    displayTransform.set({msg->body.motion.dsdxRaw, msg->body.motion.dtdxRaw,
+                          msg->body.motion.txRaw, msg->body.motion.dtdyRaw,
+                          msg->body.motion.dsdyRaw, msg->body.motion.tyRaw, 0, 0, 1});
     event->initialize(msg->body.motion.eventId, msg->body.motion.deviceId, msg->body.motion.source,
                       msg->body.motion.displayId, msg->body.motion.hmac, msg->body.motion.action,
                       msg->body.motion.actionButton, msg->body.motion.flags,
@@ -1365,9 +1397,12 @@
                       msg->body.motion.buttonState, msg->body.motion.classification, transform,
                       msg->body.motion.xPrecision, msg->body.motion.yPrecision,
                       msg->body.motion.xCursorPosition, msg->body.motion.yCursorPosition,
-                      msg->body.motion.displayOrientation, msg->body.motion.displayWidth,
-                      msg->body.motion.displayHeight, msg->body.motion.downTime,
-                      msg->body.motion.eventTime, pointerCount, pointerProperties, pointerCoords);
+                      displayTransform, msg->body.motion.downTime, msg->body.motion.eventTime,
+                      pointerCount, pointerProperties, pointerCoords);
+}
+
+void InputConsumer::initializeTouchModeEvent(TouchModeEvent* event, const InputMessage* msg) {
+    event->initialize(msg->body.touchMode.eventId, msg->body.touchMode.isInTouchMode);
 }
 
 void InputConsumer::addSample(MotionEvent* event, const InputMessage* msg) {
@@ -1412,14 +1447,14 @@
     out = out + "mChannel = " + mChannel->getName() + "\n";
     out = out + "mMsgDeferred: " + toString(mMsgDeferred) + "\n";
     if (mMsgDeferred) {
-        out = out + "mMsg : " + NamedEnum::string(mMsg.header.type) + "\n";
+        out = out + "mMsg : " + ftl::enum_string(mMsg.header.type) + "\n";
     }
     out += "Batches:\n";
     for (const Batch& batch : mBatches) {
         out += "    Batch:\n";
         for (const InputMessage& msg : batch.samples) {
             out += android::base::StringPrintf("        Message %" PRIu32 ": %s ", msg.header.seq,
-                                               NamedEnum::string(msg.header.type).c_str());
+                                               ftl::enum_string(msg.header.type).c_str());
             switch (msg.header.type) {
                 case InputMessage::Type::KEY: {
                     out += android::base::StringPrintf("action=%s keycode=%" PRId32,
@@ -1446,9 +1481,8 @@
                     break;
                 }
                 case InputMessage::Type::FOCUS: {
-                    out += android::base::StringPrintf("hasFocus=%s inTouchMode=%s",
-                                                       toString(msg.body.focus.hasFocus),
-                                                       toString(msg.body.focus.inTouchMode));
+                    out += android::base::StringPrintf("hasFocus=%s",
+                                                       toString(msg.body.focus.hasFocus));
                     break;
                 }
                 case InputMessage::Type::CAPTURE: {
@@ -1476,6 +1510,11 @@
                                                        presentTime);
                     break;
                 }
+                case InputMessage::Type::TOUCH_MODE: {
+                    out += android::base::StringPrintf("isInTouchMode=%s",
+                                                       toString(msg.body.touchMode.isInTouchMode));
+                    break;
+                }
             }
             out += "\n";
         }
diff --git a/libs/input/KeyLayoutMap.cpp b/libs/input/KeyLayoutMap.cpp
index c365ab0..7c25cda 100644
--- a/libs/input/KeyLayoutMap.cpp
+++ b/libs/input/KeyLayoutMap.cpp
@@ -16,10 +16,8 @@
 
 #define LOG_TAG "KeyLayoutMap"
 
-#include <stdlib.h>
-
 #include <android/keycodes.h>
-#include <ftl/NamedEnum.h>
+#include <ftl/enum.h>
 #include <input/InputEventLabels.h>
 #include <input/KeyLayoutMap.h>
 #include <input/Keyboard.h>
@@ -28,6 +26,10 @@
 #include <utils/Timers.h>
 #include <utils/Tokenizer.h>
 
+#include <cstdlib>
+#include <string_view>
+#include <unordered_map>
+
 // Enables debug output for the parser.
 #define DEBUG_PARSER 0
 
@@ -39,36 +41,38 @@
 
 
 namespace android {
+namespace {
 
-static const char* WHITESPACE = " \t\r";
+constexpr const char* WHITESPACE = " \t\r";
 
-#define SENSOR_ENTRY(type) NamedEnum::string(type), type
-static const std::unordered_map<std::string, InputDeviceSensorType> SENSOR_LIST =
-        {{SENSOR_ENTRY(InputDeviceSensorType::ACCELEROMETER)},
-         {SENSOR_ENTRY(InputDeviceSensorType::MAGNETIC_FIELD)},
-         {SENSOR_ENTRY(InputDeviceSensorType::ORIENTATION)},
-         {SENSOR_ENTRY(InputDeviceSensorType::GYROSCOPE)},
-         {SENSOR_ENTRY(InputDeviceSensorType::LIGHT)},
-         {SENSOR_ENTRY(InputDeviceSensorType::PRESSURE)},
-         {SENSOR_ENTRY(InputDeviceSensorType::TEMPERATURE)},
-         {SENSOR_ENTRY(InputDeviceSensorType::PROXIMITY)},
-         {SENSOR_ENTRY(InputDeviceSensorType::GRAVITY)},
-         {SENSOR_ENTRY(InputDeviceSensorType::LINEAR_ACCELERATION)},
-         {SENSOR_ENTRY(InputDeviceSensorType::ROTATION_VECTOR)},
-         {SENSOR_ENTRY(InputDeviceSensorType::RELATIVE_HUMIDITY)},
-         {SENSOR_ENTRY(InputDeviceSensorType::AMBIENT_TEMPERATURE)},
-         {SENSOR_ENTRY(InputDeviceSensorType::MAGNETIC_FIELD_UNCALIBRATED)},
-         {SENSOR_ENTRY(InputDeviceSensorType::GAME_ROTATION_VECTOR)},
-         {SENSOR_ENTRY(InputDeviceSensorType::GYROSCOPE_UNCALIBRATED)},
-         {SENSOR_ENTRY(InputDeviceSensorType::SIGNIFICANT_MOTION)}};
-
-// --- KeyLayoutMap ---
-
-KeyLayoutMap::KeyLayoutMap() {
+template <InputDeviceSensorType S>
+constexpr auto sensorPair() {
+    return std::make_pair(ftl::enum_name<S>(), S);
 }
 
-KeyLayoutMap::~KeyLayoutMap() {
-}
+static const std::unordered_map<std::string_view, InputDeviceSensorType> SENSOR_LIST =
+        {sensorPair<InputDeviceSensorType::ACCELEROMETER>(),
+         sensorPair<InputDeviceSensorType::MAGNETIC_FIELD>(),
+         sensorPair<InputDeviceSensorType::ORIENTATION>(),
+         sensorPair<InputDeviceSensorType::GYROSCOPE>(),
+         sensorPair<InputDeviceSensorType::LIGHT>(),
+         sensorPair<InputDeviceSensorType::PRESSURE>(),
+         sensorPair<InputDeviceSensorType::TEMPERATURE>(),
+         sensorPair<InputDeviceSensorType::PROXIMITY>(),
+         sensorPair<InputDeviceSensorType::GRAVITY>(),
+         sensorPair<InputDeviceSensorType::LINEAR_ACCELERATION>(),
+         sensorPair<InputDeviceSensorType::ROTATION_VECTOR>(),
+         sensorPair<InputDeviceSensorType::RELATIVE_HUMIDITY>(),
+         sensorPair<InputDeviceSensorType::AMBIENT_TEMPERATURE>(),
+         sensorPair<InputDeviceSensorType::MAGNETIC_FIELD_UNCALIBRATED>(),
+         sensorPair<InputDeviceSensorType::GAME_ROTATION_VECTOR>(),
+         sensorPair<InputDeviceSensorType::GYROSCOPE_UNCALIBRATED>(),
+         sensorPair<InputDeviceSensorType::SIGNIFICANT_MOTION>()};
+
+} // namespace
+
+KeyLayoutMap::KeyLayoutMap() = default;
+KeyLayoutMap::~KeyLayoutMap() = default;
 
 base::Result<std::shared_ptr<KeyLayoutMap>> KeyLayoutMap::loadContents(const std::string& filename,
                                                                        const char* contents) {
@@ -160,8 +164,8 @@
     const Sensor& sensor = it->second;
 
 #if DEBUG_MAPPING
-    ALOGD("mapSensor: absCode=%d, sensorType=0x%0x, sensorDataIndex=0x%x.", absCode,
-          NamedEnum::string(sensor.sensorType), sensor.sensorDataIndex);
+    ALOGD("mapSensor: absCode=%d, sensorType=%s, sensorDataIndex=0x%x.", absCode,
+          ftl::enum_string(sensor.sensorType).c_str(), sensor.sensorDataIndex);
 #endif
     return std::make_pair(sensor.sensorType, sensor.sensorDataIndex);
 }
@@ -513,7 +517,7 @@
 }
 
 static std::optional<InputDeviceSensorType> getSensorType(const char* token) {
-    auto it = SENSOR_LIST.find(std::string(token));
+    auto it = SENSOR_LIST.find(token);
     if (it == SENSOR_LIST.end()) {
         return std::nullopt;
     }
@@ -581,8 +585,8 @@
     int32_t sensorDataIndex = indexOpt.value();
 
 #if DEBUG_PARSER
-    ALOGD("Parsed sensor: abs code=%d, sensorType=%d, sensorDataIndex=%d.", code,
-          NamedEnum::string(sensorType).c_str(), sensorDataIndex);
+    ALOGD("Parsed sensor: abs code=%d, sensorType=%s, sensorDataIndex=%d.", code,
+          ftl::enum_string(sensorType).c_str(), sensorDataIndex);
 #endif
 
     Sensor sensor;
@@ -591,4 +595,5 @@
     map.emplace(code, sensor);
     return NO_ERROR;
 }
-};
+
+} // namespace android
diff --git a/libs/input/VelocityTracker.cpp b/libs/input/VelocityTracker.cpp
index a44f0b7..a6465ee 100644
--- a/libs/input/VelocityTracker.cpp
+++ b/libs/input/VelocityTracker.cpp
@@ -18,10 +18,10 @@
 //#define LOG_NDEBUG 0
 
 // Log debug messages about velocity tracking.
-#define DEBUG_VELOCITY 0
+static constexpr bool DEBUG_VELOCITY = false;
 
 // Log debug messages about the progress of the algorithm itself.
-#define DEBUG_STRATEGY 0
+static constexpr bool DEBUG_STRATEGY = false;
 
 #include <array>
 #include <inttypes.h>
@@ -30,7 +30,6 @@
 #include <optional>
 
 #include <android-base/stringprintf.h>
-#include <cutils/properties.h>
 #include <input/VelocityTracker.h>
 #include <utils/BitSet.h>
 #include <utils/Timers.h>
@@ -64,7 +63,6 @@
     return sqrtf(r);
 }
 
-#if DEBUG_STRATEGY || DEBUG_VELOCITY
 static std::string vectorToString(const float* a, uint32_t m) {
     std::string str;
     str += "[";
@@ -77,9 +75,11 @@
     str += " ]";
     return str;
 }
-#endif
 
-#if DEBUG_STRATEGY
+static std::string vectorToString(const std::vector<float>& v) {
+    return vectorToString(v.data(), v.size());
+}
+
 static std::string matrixToString(const float* a, uint32_t m, uint32_t n, bool rowMajor) {
     std::string str;
     str = "[";
@@ -99,7 +99,6 @@
     str += " ]";
     return str;
 }
-#endif
 
 
 // --- VelocityTracker ---
@@ -133,12 +132,18 @@
         VelocityTracker::Strategy strategy) {
     switch (strategy) {
         case VelocityTracker::Strategy::IMPULSE:
+            if (DEBUG_STRATEGY) {
+                ALOGI("Initializing impulse strategy");
+            }
             return std::make_unique<ImpulseVelocityTrackerStrategy>();
 
         case VelocityTracker::Strategy::LSQ1:
             return std::make_unique<LeastSquaresVelocityTrackerStrategy>(1);
 
         case VelocityTracker::Strategy::LSQ2:
+            if (DEBUG_STRATEGY) {
+                ALOGI("Initializing lsq2 strategy");
+            }
             return std::make_unique<LeastSquaresVelocityTrackerStrategy>(2);
 
         case VelocityTracker::Strategy::LSQ3:
@@ -204,10 +209,10 @@
 
     if ((mCurrentPointerIdBits.value & idBits.value)
             && eventTime >= mLastEventTime + ASSUME_POINTER_STOPPED_TIME) {
-#if DEBUG_VELOCITY
-        ALOGD("VelocityTracker: stopped for %0.3f ms, clearing state.",
-                (eventTime - mLastEventTime) * 0.000001f);
-#endif
+        if (DEBUG_VELOCITY) {
+            ALOGD("VelocityTracker: stopped for %0.3f ms, clearing state.",
+                  (eventTime - mLastEventTime) * 0.000001f);
+        }
         // We have not received any movements for too long.  Assume that all pointers
         // have stopped.
         mStrategy->clear();
@@ -221,24 +226,24 @@
 
     mStrategy->addMovement(eventTime, idBits, positions);
 
-#if DEBUG_VELOCITY
-    ALOGD("VelocityTracker: addMovement eventTime=%" PRId64 ", idBits=0x%08x, activePointerId=%d",
-            eventTime, idBits.value, mActivePointerId);
-    for (BitSet32 iterBits(idBits); !iterBits.isEmpty(); ) {
-        uint32_t id = iterBits.firstMarkedBit();
-        uint32_t index = idBits.getIndexOfBit(id);
-        iterBits.clearBit(id);
-        Estimator estimator;
-        getEstimator(id, &estimator);
-        ALOGD("  %d: position (%0.3f, %0.3f), "
-                "estimator (degree=%d, xCoeff=%s, yCoeff=%s, confidence=%f)",
-                id, positions[index].x, positions[index].y,
-                int(estimator.degree),
-                vectorToString(estimator.xCoeff, estimator.degree + 1).c_str(),
-                vectorToString(estimator.yCoeff, estimator.degree + 1).c_str(),
-                estimator.confidence);
+    if (DEBUG_VELOCITY) {
+        ALOGD("VelocityTracker: addMovement eventTime=%" PRId64
+              ", idBits=0x%08x, activePointerId=%d",
+              eventTime, idBits.value, mActivePointerId);
+        for (BitSet32 iterBits(idBits); !iterBits.isEmpty();) {
+            uint32_t id = iterBits.firstMarkedBit();
+            uint32_t index = idBits.getIndexOfBit(id);
+            iterBits.clearBit(id);
+            Estimator estimator;
+            getEstimator(id, &estimator);
+            ALOGD("  %d: position (%0.3f, %0.3f), "
+                  "estimator (degree=%d, xCoeff=%s, yCoeff=%s, confidence=%f)",
+                  id, positions[index].x, positions[index].y, int(estimator.degree),
+                  vectorToString(estimator.xCoeff, estimator.degree + 1).c_str(),
+                  vectorToString(estimator.yCoeff, estimator.degree + 1).c_str(),
+                  estimator.confidence);
+        }
     }
-#endif
 }
 
 void VelocityTracker::addMovement(const MotionEvent* event) {
@@ -419,11 +424,10 @@
 static bool solveLeastSquares(const std::vector<float>& x, const std::vector<float>& y,
                               const std::vector<float>& w, uint32_t n, float* outB, float* outDet) {
     const size_t m = x.size();
-#if DEBUG_STRATEGY
-    ALOGD("solveLeastSquares: m=%d, n=%d, x=%s, y=%s, w=%s", int(m), int(n),
-            vectorToString(x, m).c_str(), vectorToString(y, m).c_str(),
-            vectorToString(w, m).c_str());
-#endif
+    if (DEBUG_STRATEGY) {
+        ALOGD("solveLeastSquares: m=%d, n=%d, x=%s, y=%s, w=%s", int(m), int(n),
+              vectorToString(x).c_str(), vectorToString(y).c_str(), vectorToString(w).c_str());
+    }
     LOG_ALWAYS_FATAL_IF(m != y.size() || m != w.size(), "Mismatched vector sizes");
 
     // Expand the X vector to a matrix A, pre-multiplied by the weights.
@@ -434,9 +438,9 @@
             a[i][h] = a[i - 1][h] * x[h];
         }
     }
-#if DEBUG_STRATEGY
-    ALOGD("  - a=%s", matrixToString(&a[0][0], m, n, false /*rowMajor*/).c_str());
-#endif
+    if (DEBUG_STRATEGY) {
+        ALOGD("  - a=%s", matrixToString(&a[0][0], m, n, false /*rowMajor*/).c_str());
+    }
 
     // Apply the Gram-Schmidt process to A to obtain its QR decomposition.
     float q[n][m]; // orthonormal basis, column-major order
@@ -455,9 +459,9 @@
         float norm = vectorNorm(&q[j][0], m);
         if (norm < 0.000001f) {
             // vectors are linearly dependent or zero so no solution
-#if DEBUG_STRATEGY
-            ALOGD("  - no solution, norm=%f", norm);
-#endif
+            if (DEBUG_STRATEGY) {
+                ALOGD("  - no solution, norm=%f", norm);
+            }
             return false;
         }
 
@@ -469,22 +473,22 @@
             r[j][i] = i < j ? 0 : vectorDot(&q[j][0], &a[i][0], m);
         }
     }
-#if DEBUG_STRATEGY
-    ALOGD("  - q=%s", matrixToString(&q[0][0], m, n, false /*rowMajor*/).c_str());
-    ALOGD("  - r=%s", matrixToString(&r[0][0], n, n, true /*rowMajor*/).c_str());
+    if (DEBUG_STRATEGY) {
+        ALOGD("  - q=%s", matrixToString(&q[0][0], m, n, false /*rowMajor*/).c_str());
+        ALOGD("  - r=%s", matrixToString(&r[0][0], n, n, true /*rowMajor*/).c_str());
 
-    // calculate QR, if we factored A correctly then QR should equal A
-    float qr[n][m];
-    for (uint32_t h = 0; h < m; h++) {
-        for (uint32_t i = 0; i < n; i++) {
-            qr[i][h] = 0;
-            for (uint32_t j = 0; j < n; j++) {
-                qr[i][h] += q[j][h] * r[j][i];
+        // calculate QR, if we factored A correctly then QR should equal A
+        float qr[n][m];
+        for (uint32_t h = 0; h < m; h++) {
+            for (uint32_t i = 0; i < n; i++) {
+                qr[i][h] = 0;
+                for (uint32_t j = 0; j < n; j++) {
+                    qr[i][h] += q[j][h] * r[j][i];
+                }
             }
         }
+        ALOGD("  - qr=%s", matrixToString(&qr[0][0], m, n, false /*rowMajor*/).c_str());
     }
-    ALOGD("  - qr=%s", matrixToString(&qr[0][0], m, n, false /*rowMajor*/).c_str());
-#endif
 
     // Solve R B = Qt W Y to find B.  This is easy because R is upper triangular.
     // We just work from bottom-right to top-left calculating B's coefficients.
@@ -500,9 +504,9 @@
         }
         outB[i] /= r[i][i];
     }
-#if DEBUG_STRATEGY
-    ALOGD("  - b=%s", vectorToString(outB, n).c_str());
-#endif
+    if (DEBUG_STRATEGY) {
+        ALOGD("  - b=%s", vectorToString(outB, n).c_str());
+    }
 
     // Calculate the coefficient of determination as 1 - (SSerr / SStot) where
     // SSerr is the residual sum of squares (variance of the error),
@@ -528,11 +532,11 @@
         sstot += w[h] * w[h] * var * var;
     }
     *outDet = sstot > 0.000001f ? 1.0f - (sserr / sstot) : 1;
-#if DEBUG_STRATEGY
-    ALOGD("  - sserr=%f", sserr);
-    ALOGD("  - sstot=%f", sstot);
-    ALOGD("  - det=%f", *outDet);
-#endif
+    if (DEBUG_STRATEGY) {
+        ALOGD("  - sserr=%f", sserr);
+        ALOGD("  - sstot=%f", sstot);
+        ALOGD("  - det=%f", *outDet);
+    }
     return true;
 }
 
@@ -655,13 +659,11 @@
             outEstimator->time = newestMovement.eventTime;
             outEstimator->degree = degree;
             outEstimator->confidence = xdet * ydet;
-#if DEBUG_STRATEGY
-            ALOGD("estimate: degree=%d, xCoeff=%s, yCoeff=%s, confidence=%f",
-                    int(outEstimator->degree),
-                    vectorToString(outEstimator->xCoeff, n).c_str(),
-                    vectorToString(outEstimator->yCoeff, n).c_str(),
-                    outEstimator->confidence);
-#endif
+            if (DEBUG_STRATEGY) {
+                ALOGD("estimate: degree=%d, xCoeff=%s, yCoeff=%s, confidence=%f",
+                      int(outEstimator->degree), vectorToString(outEstimator->xCoeff, n).c_str(),
+                      vectorToString(outEstimator->yCoeff, n).c_str(), outEstimator->confidence);
+            }
             return true;
         }
     }
@@ -1169,9 +1171,9 @@
     outEstimator->time = newestMovement.eventTime;
     outEstimator->degree = 2; // similar results to 2nd degree fit
     outEstimator->confidence = 1;
-#if DEBUG_STRATEGY
-    ALOGD("velocity: (%f, %f)", outEstimator->xCoeff[1], outEstimator->yCoeff[1]);
-#endif
+    if (DEBUG_STRATEGY) {
+        ALOGD("velocity: (%f, %f)", outEstimator->xCoeff[1], outEstimator->yCoeff[1]);
+    }
     return true;
 }
 
diff --git a/libs/input/android/os/IInputConstants.aidl b/libs/input/android/os/IInputConstants.aidl
index 474a1e4..5ce10a4 100644
--- a/libs/input/android/os/IInputConstants.aidl
+++ b/libs/input/android/os/IInputConstants.aidl
@@ -25,13 +25,6 @@
     // android.os.InputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS.
     const int UNMULTIPLIED_DEFAULT_DISPATCHING_TIMEOUT_MILLIS = 5000; // 5 seconds
 
-    // Compatibility changes.
-    /**
-      * TODO(b/157929241): remove this before closing the bug. This is needed temporarily
-      * to identify apps that are using this flag.
-      */
-    const long BLOCK_FLAG_SLIPPERY = 157929241;
-
     // Indicate invalid battery capacity
     const int INVALID_BATTERY_CAPACITY = -1;
 
@@ -53,4 +46,7 @@
      * set of flags, including in input/Input.h and in android/input.h.
      */
     const int INPUT_EVENT_FLAG_IS_ACCESSIBILITY_EVENT = 0x800;
+
+    /* The default pointer acceleration value. */
+    const int DEFAULT_POINTER_ACCELERATION = 3;
 }
diff --git a/libs/input/android/os/InputConfig.aidl b/libs/input/android/os/InputConfig.aidl
new file mode 100644
index 0000000..6d1b396
--- /dev/null
+++ b/libs/input/android/os/InputConfig.aidl
@@ -0,0 +1,147 @@
+/**
+ * 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.
+ */
+
+package android.os;
+
+
+/**
+ * Input configurations flags used to determine the behavior of input windows.
+ * @hide
+ */
+@Backing(type="int")
+enum InputConfig {
+
+    /**
+     * The default InputConfig value with no flags set.
+     */
+    DEFAULT                      = 0,
+
+    /**
+     * Does not construct an input channel for this window.  The channel will therefore
+     * be incapable of receiving input.
+     */
+    NO_INPUT_CHANNEL             = 1 << 0,
+
+    /**
+     * Indicates that this input window is not visible, and thus will not be considered as
+     * an input target and will not obscure other windows.
+     */
+    NOT_VISIBLE                  = 1 << 1,
+
+    /**
+     * Indicates that this input window cannot be a focus target, and this will not
+     * receive any input events that can only be directed for the focused window, such
+     * as key events.
+     */
+    NOT_FOCUSABLE                = 1 << 2,
+
+    /**
+     * Indicates that this input window cannot receive any events directed at a
+     * specific location on the screen, such as touchscreen, mouse, and stylus events.
+     * The window will not be considered as a touch target, but can still obscure other
+     * windows.
+     */
+    NOT_TOUCHABLE                = 1 << 3,
+
+    /**
+     * Indicates that this window will not accept a touch event that is split between
+     * more than one window. When set:
+     *  - If this window receives a DOWN event with the first pointer, all successive
+     *    pointers that go down, regardless of their location on the screen, will be
+     *    directed to this window;
+     *  - If the DOWN event lands outside the touchable bounds of this window, no
+     *    successive pointers that go down, regardless of their location on the screen,
+     *    will be directed to this window.
+     */
+    PREVENT_SPLITTING            = 1 << 4,
+
+    /**
+     * Indicates that this window shows the wallpaper behind it, so all touch events
+     * that it receives should also be sent to the wallpaper.
+     */
+    DUPLICATE_TOUCH_TO_WALLPAPER = 1 << 5,
+
+    /** Indicates that this the wallpaper's input window. */
+    IS_WALLPAPER                 = 1 << 6,
+
+    /**
+     * Indicates that input events should not be dispatched to this window. When set,
+     * input events directed towards this window will simply be dropped, and will not
+     * be dispatched to windows behind it.
+     */
+    PAUSE_DISPATCHING            = 1 << 7,
+
+    /**
+     * This flag is set when the window is of a trusted type that is allowed to silently
+     * overlay other windows for the purpose of implementing the secure views feature.
+     * Trusted overlays, such as IME windows, can partly obscure other windows without causing
+     * motion events to be delivered to them with AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED.
+     */
+    TRUSTED_OVERLAY              = 1 << 8,
+
+    /**
+     * Indicates that this window wants to listen for when there is a touch DOWN event
+     * that occurs outside its touchable bounds. When such an event occurs, this window
+     * will receive a MotionEvent with ACTION_OUTSIDE.
+     */
+    WATCH_OUTSIDE_TOUCH          = 1 << 9,
+
+    /**
+     * When set, this flag allows touches to leave the current window whenever the finger
+     * moves above another window. When this happens, the window that touch has just left
+     * (the current window) will receive ACTION_CANCEL, and the window that touch has entered
+     * will receive ACTION_DOWN, and the remainder of the touch gesture will only go to the
+     * new window. Without this flag, the entire gesture is sent to the current window, even
+     * if the touch leaves the window's bounds.
+     */
+    SLIPPERY                     = 1 << 10,
+
+    /**
+     * When this window has focus, does not call user activity for all input events so
+     * the application will have to do it itself.
+     */
+    DISABLE_USER_ACTIVITY        = 1 << 11,
+
+    /**
+     * Internal flag used to indicate that input should be dropped on this window.
+     */
+    DROP_INPUT                   = 1 << 12,
+
+    /**
+     * Internal flag used to indicate that input should be dropped on this window if this window
+     * is obscured.
+     */
+    DROP_INPUT_IF_OBSCURED       = 1 << 13,
+
+    /**
+     * An input spy window. This window will receive all pointer events within its touchable
+     * area, but will not stop events from being sent to other windows below it in z-order.
+     * An input event will be dispatched to all spy windows above the top non-spy window at the
+     * event's coordinates.
+     */
+    SPY                          = 1 << 14,
+
+    /**
+     * When used with {@link #NOT_TOUCHABLE}, this window will continue to receive events from
+     * a stylus device within its touchable region. All other pointer events, such as from a
+     * mouse or touchscreen, will be dispatched to the windows behind it.
+     *
+     * This configuration has no effect when the config {@link #NOT_TOUCHABLE} is not set.
+     *
+     * It is not valid to set this configuration if {@link #TRUSTED_OVERLAY} is not set.
+     */
+    INTERCEPTS_STYLUS            = 1 << 15,
+}
diff --git a/libs/input/tests/InputEvent_test.cpp b/libs/input/tests/InputEvent_test.cpp
index b1ef753..a92016b 100644
--- a/libs/input/tests/InputEvent_test.cpp
+++ b/libs/input/tests/InputEvent_test.cpp
@@ -226,39 +226,23 @@
     static constexpr float Y_SCALE = 3.0;
     static constexpr float X_OFFSET = 1;
     static constexpr float Y_OFFSET = 1.1;
-
-    static const std::optional<bool> INITIAL_PER_WINDOW_INPUT_ROTATION_FLAG_VALUE;
+    static constexpr float RAW_X_SCALE = 4.0;
+    static constexpr float RAW_Y_SCALE = -5.0;
+    static constexpr float RAW_X_OFFSET = 12;
+    static constexpr float RAW_Y_OFFSET = -41.1;
 
     int32_t mId;
     ui::Transform mTransform;
-
-    void SetUp() override;
-    void TearDown() override;
+    ui::Transform mRawTransform;
 
     void initializeEventWithHistory(MotionEvent* event);
     void assertEqualsEventWithHistory(const MotionEvent* event);
 };
 
-const std::optional<bool> MotionEventTest::INITIAL_PER_WINDOW_INPUT_ROTATION_FLAG_VALUE =
-        !base::GetProperty("persist.debug.per_window_input_rotation", "").empty()
-        ? std::optional(base::GetBoolProperty("persist.debug.per_window_input_rotation", false))
-        : std::nullopt;
-
-void MotionEventTest::SetUp() {
-    // Ensure per_window_input_rotation is enabled.
-    base::SetProperty("persist.debug.per_window_input_rotation", "true");
-}
-
-void MotionEventTest::TearDown() {
-    const auto val = INITIAL_PER_WINDOW_INPUT_ROTATION_FLAG_VALUE.has_value()
-            ? (*INITIAL_PER_WINDOW_INPUT_ROTATION_FLAG_VALUE ? "true" : "false")
-            : "";
-    base::SetProperty("persist.debug.per_window_input_rotation", val);
-}
-
 void MotionEventTest::initializeEventWithHistory(MotionEvent* event) {
     mId = InputEvent::nextId();
     mTransform.set({X_SCALE, 0, X_OFFSET, 0, Y_SCALE, Y_OFFSET, 0, 0, 1});
+    mRawTransform.set({RAW_X_SCALE, 0, RAW_X_OFFSET, 0, RAW_Y_SCALE, RAW_Y_OFFSET, 0, 0, 1});
 
     PointerProperties pointerProperties[2];
     pointerProperties[0].clear();
@@ -294,9 +278,8 @@
                       AMOTION_EVENT_EDGE_FLAG_TOP, AMETA_ALT_ON, AMOTION_EVENT_BUTTON_PRIMARY,
                       MotionClassification::NONE, mTransform, 2.0f, 2.1f,
                       AMOTION_EVENT_INVALID_CURSOR_POSITION, AMOTION_EVENT_INVALID_CURSOR_POSITION,
-                      ui::Transform::ROT_0, INVALID_DISPLAY_SIZE, INVALID_DISPLAY_SIZE,
-                      ARBITRARY_DOWN_TIME, ARBITRARY_EVENT_TIME, 2, pointerProperties,
-                      pointerCoords);
+                      mRawTransform, ARBITRARY_DOWN_TIME, ARBITRARY_EVENT_TIME, 2,
+                      pointerProperties, pointerCoords);
 
     pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, 110);
     pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, 111);
@@ -373,39 +356,37 @@
     ASSERT_EQ(ARBITRARY_EVENT_TIME + 1, event->getHistoricalEventTime(1));
     ASSERT_EQ(ARBITRARY_EVENT_TIME + 2, event->getEventTime());
 
-    ASSERT_EQ(11, event->getHistoricalRawPointerCoords(0, 0)->
-            getAxisValue(AMOTION_EVENT_AXIS_Y));
-    ASSERT_EQ(21, event->getHistoricalRawPointerCoords(1, 0)->
-            getAxisValue(AMOTION_EVENT_AXIS_Y));
-    ASSERT_EQ(111, event->getHistoricalRawPointerCoords(0, 1)->
-            getAxisValue(AMOTION_EVENT_AXIS_Y));
-    ASSERT_EQ(121, event->getHistoricalRawPointerCoords(1, 1)->
-            getAxisValue(AMOTION_EVENT_AXIS_Y));
-    ASSERT_EQ(211, event->getRawPointerCoords(0)->
-            getAxisValue(AMOTION_EVENT_AXIS_Y));
-    ASSERT_EQ(221, event->getRawPointerCoords(1)->
-            getAxisValue(AMOTION_EVENT_AXIS_Y));
+    ASSERT_EQ(11, event->getHistoricalRawPointerCoords(0, 0)->getAxisValue(AMOTION_EVENT_AXIS_Y));
+    ASSERT_EQ(21, event->getHistoricalRawPointerCoords(1, 0)->getAxisValue(AMOTION_EVENT_AXIS_Y));
+    ASSERT_EQ(111, event->getHistoricalRawPointerCoords(0, 1)->getAxisValue(AMOTION_EVENT_AXIS_Y));
+    ASSERT_EQ(121, event->getHistoricalRawPointerCoords(1, 1)->getAxisValue(AMOTION_EVENT_AXIS_Y));
+    ASSERT_EQ(211, event->getRawPointerCoords(0)->getAxisValue(AMOTION_EVENT_AXIS_Y));
+    ASSERT_EQ(221, event->getRawPointerCoords(1)->getAxisValue(AMOTION_EVENT_AXIS_Y));
 
-    ASSERT_EQ(11, event->getHistoricalRawAxisValue(AMOTION_EVENT_AXIS_Y, 0, 0));
-    ASSERT_EQ(21, event->getHistoricalRawAxisValue(AMOTION_EVENT_AXIS_Y, 1, 0));
-    ASSERT_EQ(111, event->getHistoricalRawAxisValue(AMOTION_EVENT_AXIS_Y, 0, 1));
-    ASSERT_EQ(121, event->getHistoricalRawAxisValue(AMOTION_EVENT_AXIS_Y, 1, 1));
-    ASSERT_EQ(211, event->getRawAxisValue(AMOTION_EVENT_AXIS_Y, 0));
-    ASSERT_EQ(221, event->getRawAxisValue(AMOTION_EVENT_AXIS_Y, 1));
+    ASSERT_EQ(RAW_Y_OFFSET + 11 * RAW_Y_SCALE,
+              event->getHistoricalRawAxisValue(AMOTION_EVENT_AXIS_Y, 0, 0));
+    ASSERT_EQ(RAW_Y_OFFSET + 21 * RAW_Y_SCALE,
+              event->getHistoricalRawAxisValue(AMOTION_EVENT_AXIS_Y, 1, 0));
+    ASSERT_EQ(RAW_Y_OFFSET + 111 * RAW_Y_SCALE,
+              event->getHistoricalRawAxisValue(AMOTION_EVENT_AXIS_Y, 0, 1));
+    ASSERT_EQ(RAW_Y_OFFSET + 121 * RAW_Y_SCALE,
+              event->getHistoricalRawAxisValue(AMOTION_EVENT_AXIS_Y, 1, 1));
+    ASSERT_EQ(RAW_Y_OFFSET + 211 * RAW_Y_SCALE, event->getRawAxisValue(AMOTION_EVENT_AXIS_Y, 0));
+    ASSERT_EQ(RAW_Y_OFFSET + 221 * RAW_Y_SCALE, event->getRawAxisValue(AMOTION_EVENT_AXIS_Y, 1));
 
-    ASSERT_EQ(10, event->getHistoricalRawX(0, 0));
-    ASSERT_EQ(20, event->getHistoricalRawX(1, 0));
-    ASSERT_EQ(110, event->getHistoricalRawX(0, 1));
-    ASSERT_EQ(120, event->getHistoricalRawX(1, 1));
-    ASSERT_EQ(210, event->getRawX(0));
-    ASSERT_EQ(220, event->getRawX(1));
+    ASSERT_EQ(RAW_X_OFFSET + 10 * RAW_X_SCALE, event->getHistoricalRawX(0, 0));
+    ASSERT_EQ(RAW_X_OFFSET + 20 * RAW_X_SCALE, event->getHistoricalRawX(1, 0));
+    ASSERT_EQ(RAW_X_OFFSET + 110 * RAW_X_SCALE, event->getHistoricalRawX(0, 1));
+    ASSERT_EQ(RAW_X_OFFSET + 120 * RAW_X_SCALE, event->getHistoricalRawX(1, 1));
+    ASSERT_EQ(RAW_X_OFFSET + 210 * RAW_X_SCALE, event->getRawX(0));
+    ASSERT_EQ(RAW_X_OFFSET + 220 * RAW_X_SCALE, event->getRawX(1));
 
-    ASSERT_EQ(11, event->getHistoricalRawY(0, 0));
-    ASSERT_EQ(21, event->getHistoricalRawY(1, 0));
-    ASSERT_EQ(111, event->getHistoricalRawY(0, 1));
-    ASSERT_EQ(121, event->getHistoricalRawY(1, 1));
-    ASSERT_EQ(211, event->getRawY(0));
-    ASSERT_EQ(221, event->getRawY(1));
+    ASSERT_EQ(RAW_Y_OFFSET + 11 * RAW_Y_SCALE, event->getHistoricalRawY(0, 0));
+    ASSERT_EQ(RAW_Y_OFFSET + 21 * RAW_Y_SCALE, event->getHistoricalRawY(1, 0));
+    ASSERT_EQ(RAW_Y_OFFSET + 111 * RAW_Y_SCALE, event->getHistoricalRawY(0, 1));
+    ASSERT_EQ(RAW_Y_OFFSET + 121 * RAW_Y_SCALE, event->getHistoricalRawY(1, 1));
+    ASSERT_EQ(RAW_Y_OFFSET + 211 * RAW_Y_SCALE, event->getRawY(0));
+    ASSERT_EQ(RAW_Y_OFFSET + 221 * RAW_Y_SCALE, event->getRawY(1));
 
     ASSERT_EQ(X_OFFSET + 10 * X_SCALE, event->getHistoricalX(0, 0));
     ASSERT_EQ(X_OFFSET + 20 * X_SCALE, event->getHistoricalX(1, 0));
@@ -463,12 +444,19 @@
     ASSERT_EQ(217, event->getToolMinor(0));
     ASSERT_EQ(227, event->getToolMinor(1));
 
-    ASSERT_EQ(18, event->getHistoricalOrientation(0, 0));
-    ASSERT_EQ(28, event->getHistoricalOrientation(1, 0));
-    ASSERT_EQ(118, event->getHistoricalOrientation(0, 1));
-    ASSERT_EQ(128, event->getHistoricalOrientation(1, 1));
-    ASSERT_EQ(218, event->getOrientation(0));
-    ASSERT_EQ(228, event->getOrientation(1));
+    // Calculate the orientation after scaling, keeping in mind that an orientation of 0 is "up",
+    // and the positive y direction is "down".
+    auto toScaledOrientation = [](float angle) {
+        const float x = sinf(angle) * X_SCALE;
+        const float y = -cosf(angle) * Y_SCALE;
+        return atan2f(x, -y);
+    };
+    ASSERT_EQ(toScaledOrientation(18), event->getHistoricalOrientation(0, 0));
+    ASSERT_EQ(toScaledOrientation(28), event->getHistoricalOrientation(1, 0));
+    ASSERT_EQ(toScaledOrientation(118), event->getHistoricalOrientation(0, 1));
+    ASSERT_EQ(toScaledOrientation(128), event->getHistoricalOrientation(1, 1));
+    ASSERT_EQ(toScaledOrientation(218), event->getOrientation(0));
+    ASSERT_EQ(toScaledOrientation(228), event->getOrientation(1));
 }
 
 TEST_F(MotionEventTest, Properties) {
@@ -537,14 +525,15 @@
 TEST_F(MotionEventTest, Scale) {
     MotionEvent event;
     initializeEventWithHistory(&event);
+    const float unscaledOrientation = event.getOrientation(0);
 
     event.scale(2.0f);
 
     ASSERT_EQ(X_OFFSET * 2, event.getXOffset());
     ASSERT_EQ(Y_OFFSET * 2, event.getYOffset());
 
-    ASSERT_EQ(210 * 2, event.getRawX(0));
-    ASSERT_EQ(211 * 2, event.getRawY(0));
+    ASSERT_EQ((RAW_X_OFFSET + 210 * RAW_X_SCALE) * 2, event.getRawX(0));
+    ASSERT_EQ((RAW_Y_OFFSET + 211 * RAW_Y_SCALE) * 2, event.getRawY(0));
     ASSERT_EQ((X_OFFSET + 210 * X_SCALE) * 2, event.getX(0));
     ASSERT_EQ((Y_OFFSET + 211 * Y_SCALE) * 2, event.getY(0));
     ASSERT_EQ(212, event.getPressure(0));
@@ -553,7 +542,7 @@
     ASSERT_EQ(215 * 2, event.getTouchMinor(0));
     ASSERT_EQ(216 * 2, event.getToolMajor(0));
     ASSERT_EQ(217 * 2, event.getToolMinor(0));
-    ASSERT_EQ(218, event.getOrientation(0));
+    ASSERT_EQ(unscaledOrientation, event.getOrientation(0));
 }
 
 TEST_F(MotionEventTest, Parcel) {
@@ -592,10 +581,10 @@
     // The geometrical representation is irrelevant to the test, it's just easy to generate
     // and check rotation.  We set the orientation to the same angle.
     // Coordinate system: down is increasing Y, right is increasing X.
-    const float PI_180 = float(M_PI / 180);
-    const float RADIUS = 10;
-    const float ARC = 36;
-    const float ROTATION = ARC * 2;
+    static constexpr float PI_180 = float(M_PI / 180);
+    static constexpr float RADIUS = 10;
+    static constexpr float ARC = 36;
+    static constexpr float ROTATION = ARC * 2;
 
     const size_t pointerCount = 11;
     PointerProperties pointerProperties[pointerCount];
@@ -616,9 +605,8 @@
                      AMOTION_EVENT_EDGE_FLAG_NONE, AMETA_NONE, 0 /*buttonState*/,
                      MotionClassification::NONE, identityTransform, 0 /*xPrecision*/,
                      0 /*yPrecision*/, 3 + RADIUS /*xCursorPosition*/, 2 /*yCursorPosition*/,
-                     ui::Transform::ROT_0, INVALID_DISPLAY_SIZE, INVALID_DISPLAY_SIZE,
-                     0 /*downTime*/, 0 /*eventTime*/, pointerCount, pointerProperties,
-                     pointerCoords);
+                     identityTransform, 0 /*downTime*/, 0 /*eventTime*/, pointerCount,
+                     pointerProperties, pointerCoords);
     float originalRawX = 0 + 3;
     float originalRawY = -RADIUS + 2;
 
@@ -659,9 +647,8 @@
     ASSERT_NEAR(originalRawY, event.getRawY(0), 0.001);
 }
 
-MotionEvent createTouchDownEvent(float x, float y, float dx, float dy,
-                                 const ui::Transform& transform,
-                                 uint32_t displayOrientation = ui::Transform::ROT_0) {
+MotionEvent createMotionEvent(int32_t source, uint32_t action, float x, float y, float dx, float dy,
+                              const ui::Transform& transform, const ui::Transform& rawTransform) {
     std::vector<PointerProperties> pointerProperties;
     pointerProperties.push_back(PointerProperties{/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER});
     std::vector<PointerCoords> pointerCoords;
@@ -672,24 +659,30 @@
     pointerCoords.back().setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y, dy);
     nsecs_t eventTime = systemTime(SYSTEM_TIME_MONOTONIC);
     MotionEvent event;
-    event.initialize(InputEvent::nextId(), /* deviceId */ 1, AINPUT_SOURCE_TOUCHSCREEN,
-                     /* displayId */ 0, INVALID_HMAC, AMOTION_EVENT_ACTION_DOWN,
+    event.initialize(InputEvent::nextId(), /* deviceId */ 1, source,
+                     /* displayId */ 0, INVALID_HMAC, action,
                      /* actionButton */ 0, /* flags */ 0, /* edgeFlags */ 0, AMETA_NONE,
                      /* buttonState */ 0, MotionClassification::NONE, transform,
                      /* xPrecision */ 0, /* yPrecision */ 0, AMOTION_EVENT_INVALID_CURSOR_POSITION,
-                     AMOTION_EVENT_INVALID_CURSOR_POSITION, displayOrientation,
-                     /* displayWidth */ 400,
-                     /* displayHeight */ 800, eventTime, eventTime, pointerCoords.size(),
-                     pointerProperties.data(), pointerCoords.data());
+                     AMOTION_EVENT_INVALID_CURSOR_POSITION, rawTransform, eventTime, eventTime,
+                     pointerCoords.size(), pointerProperties.data(), pointerCoords.data());
     return event;
 }
 
+MotionEvent createTouchDownEvent(float x, float y, float dx, float dy,
+                                 const ui::Transform& transform,
+                                 const ui::Transform& rawTransform) {
+    return createMotionEvent(AINPUT_SOURCE_TOUCHSCREEN, AMOTION_EVENT_ACTION_DOWN, x, y, dx, dy,
+                             transform, rawTransform);
+}
+
 TEST_F(MotionEventTest, ApplyTransform) {
     // Create a rotate-90 transform with an offset (like a window which isn't fullscreen).
     ui::Transform identity;
-    ui::Transform xform(ui::Transform::ROT_90, 800, 400);
-    xform.set(xform.tx() + 20, xform.ty() + 40);
-    MotionEvent event = createTouchDownEvent(60, 100, 42, 96, xform, ui::Transform::ROT_90);
+    ui::Transform transform(ui::Transform::ROT_90, 800, 400);
+    transform.set(transform.tx() + 20, transform.ty() + 40);
+    ui::Transform rawTransform(ui::Transform::ROT_90, 800, 400);
+    MotionEvent event = createTouchDownEvent(60, 100, 42, 96, transform, rawTransform);
     ASSERT_EQ(700, event.getRawX(0));
     ASSERT_EQ(60, event.getRawY(0));
     ASSERT_NE(event.getRawX(0), event.getX(0));
@@ -698,10 +691,10 @@
     ASSERT_EQ(-96, event.getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X, 0));
     ASSERT_EQ(42, event.getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y, 0));
 
-    MotionEvent changedEvent = createTouchDownEvent(60, 100, 42, 96, identity);
-    const std::array<float, 9> rowMajor{xform[0][0], xform[1][0], xform[2][0],
-                                        xform[0][1], xform[1][1], xform[2][1],
-                                        xform[0][2], xform[1][2], xform[2][2]};
+    MotionEvent changedEvent = createTouchDownEvent(60, 100, 42, 96, identity, identity);
+    const std::array<float, 9> rowMajor{transform[0][0], transform[1][0], transform[2][0],
+                                        transform[0][1], transform[1][1], transform[2][1],
+                                        transform[0][2], transform[1][2], transform[2][2]};
     changedEvent.applyTransform(rowMajor);
 
     // transformContent effectively rotates the raw coordinates, so those should now include
@@ -721,16 +714,39 @@
                 changedEvent.getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y, 0), 0.001);
 }
 
+TEST_F(MotionEventTest, JoystickAndTouchpadAreNotTransformed) {
+    constexpr static std::array kNonTransformedSources = {std::pair(AINPUT_SOURCE_TOUCHPAD,
+                                                                    AMOTION_EVENT_ACTION_DOWN),
+                                                          std::pair(AINPUT_SOURCE_JOYSTICK,
+                                                                    AMOTION_EVENT_ACTION_MOVE)};
+    // Create a rotate-90 transform with an offset (like a window which isn't fullscreen).
+    ui::Transform transform(ui::Transform::ROT_90, 800, 400);
+    transform.set(transform.tx() + 20, transform.ty() + 40);
+
+    for (const auto& [source, action] : kNonTransformedSources) {
+        const MotionEvent event =
+                createMotionEvent(source, action, 60, 100, 0, 0, transform, transform);
+
+        // These events should not be transformed in any way.
+        ASSERT_EQ(60, event.getX(0));
+        ASSERT_EQ(100, event.getY(0));
+        ASSERT_EQ(event.getRawX(0), event.getX(0));
+        ASSERT_EQ(event.getRawY(0), event.getY(0));
+    }
+}
+
 TEST_F(MotionEventTest, NonPointerSourcesAreNotTranslated) {
-    constexpr static auto NON_POINTER_SOURCES = {AINPUT_SOURCE_TRACKBALL,
-                                                 AINPUT_SOURCE_MOUSE_RELATIVE,
-                                                 AINPUT_SOURCE_JOYSTICK};
-    for (uint32_t source : NON_POINTER_SOURCES) {
-        // Create a rotate-90 transform with an offset (like a window which isn't fullscreen).
-        ui::Transform xform(ui::Transform::ROT_90, 800, 400);
-        xform.set(xform.tx() + 20, xform.ty() + 40);
-        MotionEvent event = createTouchDownEvent(60, 100, 42, 96, xform, ui::Transform::ROT_90);
-        event.setSource(source);
+    constexpr static std::array kNonPointerSources = {std::pair(AINPUT_SOURCE_TRACKBALL,
+                                                                AMOTION_EVENT_ACTION_DOWN),
+                                                      std::pair(AINPUT_SOURCE_MOUSE_RELATIVE,
+                                                                AMOTION_EVENT_ACTION_MOVE)};
+    // Create a rotate-90 transform with an offset (like a window which isn't fullscreen).
+    ui::Transform transform(ui::Transform::ROT_90, 800, 400);
+    transform.set(transform.tx() + 20, transform.ty() + 40);
+
+    for (const auto& [source, action] : kNonPointerSources) {
+        const MotionEvent event =
+                createMotionEvent(source, action, 60, 100, 42, 96, transform, transform);
 
         // Since this event comes from a non-pointer source, it should include rotation but not
         // translation/offset.
@@ -741,72 +757,34 @@
     }
 }
 
-TEST_F(MotionEventTest, RawCompatTransform) {
-    {
-        // Make sure raw is raw regardless of transform translation.
-        ui::Transform xform;
-        xform.set(20, 40);
-        MotionEvent event = createTouchDownEvent(60, 100, 42, 96, xform);
-        ASSERT_EQ(60, event.getRawX(0));
-        ASSERT_EQ(100, event.getRawY(0));
-        ASSERT_NE(event.getRawX(0), event.getX(0));
-        ASSERT_NE(event.getRawY(0), event.getY(0));
-        // Relative values should not be modified.
-        ASSERT_EQ(42, event.getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X, 0));
-        ASSERT_EQ(96, event.getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y, 0));
-    }
+TEST_F(MotionEventTest, AxesAreCorrectlyTransformed) {
+    const ui::Transform identity;
+    ui::Transform transform;
+    transform.set({1.1, -2.2, 3.3, -4.4, 5.5, -6.6, 0, 0, 1});
+    ui::Transform rawTransform;
+    rawTransform.set({-6.6, 5.5, -4.4, 3.3, -2.2, 1.1, 0, 0, 1});
+    auto transformWithoutTranslation = [](const ui::Transform& t, float x, float y) {
+        auto newPoint = t.transform(x, y);
+        auto newOrigin = t.transform(0, 0);
+        return newPoint - newOrigin;
+    };
 
-    // Next check that getRaw contains rotation (for compatibility) but otherwise is still
-    // "Screen-space". The following tests check all 3 rotations.
-    {
-        // Create a rotate-90 transform with an offset (like a window which isn't fullscreen).
-        ui::Transform xform(ui::Transform::ROT_90, 800, 400);
-        xform.set(xform.tx() + 20, xform.ty() + 40);
-        MotionEvent event = createTouchDownEvent(60, 100, 42, 96, xform, ui::Transform::ROT_90);
-        ASSERT_EQ(700, event.getRawX(0));
-        ASSERT_EQ(60, event.getRawY(0));
-        ASSERT_NE(event.getRawX(0), event.getX(0));
-        ASSERT_NE(event.getRawY(0), event.getY(0));
-        // Relative values should be rotated but not translated.
-        ASSERT_EQ(-96, event.getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X, 0));
-        ASSERT_EQ(42, event.getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y, 0));
-    }
+    const MotionEvent event = createTouchDownEvent(60, 100, 42, 96, transform, rawTransform);
 
-    {
-        // Same as above, but check rotate-180.
-        ui::Transform xform(ui::Transform::ROT_180, 400, 800);
-        xform.set(xform.tx() + 20, xform.ty() + 40);
-        MotionEvent event = createTouchDownEvent(60, 100, 42, 96, xform, ui::Transform::ROT_180);
-        ASSERT_EQ(340, event.getRawX(0));
-        ASSERT_EQ(700, event.getRawY(0));
-        // Relative values should be rotated but not translated.
-        ASSERT_EQ(-42, event.getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X, 0));
-        ASSERT_EQ(-96, event.getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y, 0));
-    }
+    // The x and y axes should have the window transform applied.
+    const auto newPoint = transform.transform(60, 100);
+    ASSERT_EQ(newPoint.x, event.getX(0));
+    ASSERT_EQ(newPoint.y, event.getY(0));
 
-    {
-        // Same as above, but check rotate-270.
-        ui::Transform xform(ui::Transform::ROT_270, 800, 400);
-        xform.set(xform.tx() + 20, xform.ty() + 40);
-        MotionEvent event = createTouchDownEvent(60, 100, 42, 96, xform, ui::Transform::ROT_270);
-        ASSERT_EQ(100, event.getRawX(0));
-        ASSERT_EQ(340, event.getRawY(0));
-        // Relative values should be rotated but not translated.
-        ASSERT_EQ(96, event.getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X, 0));
-        ASSERT_EQ(-42, event.getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y, 0));
-    }
+    // The raw values should have the display transform applied.
+    const auto raw = rawTransform.transform(60, 100);
+    ASSERT_EQ(raw.x, event.getRawX(0));
+    ASSERT_EQ(raw.y, event.getRawY(0));
 
-    {
-        // Finally, check that raw isn't effected by transform
-        ui::Transform xform(ui::Transform::ROT_270, 800, 400);
-        xform.set(xform.tx() + 20, xform.ty() + 40);
-        MotionEvent event = createTouchDownEvent(60, 100, 42, 96, xform, ui::Transform::ROT_90);
-        ASSERT_EQ(700, event.getRawX(0));
-        ASSERT_EQ(60, event.getRawY(0));
-        // Relative values should be rotated but not translated.
-        ASSERT_EQ(96, event.getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X, 0));
-        ASSERT_EQ(-42, event.getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y, 0));
-    }
+    // Relative values should have the window transform applied without any translation.
+    const auto rel = transformWithoutTranslation(transform, 42, 96);
+    ASSERT_EQ(rel.x, event.getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X, 0));
+    ASSERT_EQ(rel.y, event.getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y, 0));
 }
 
 TEST_F(MotionEventTest, Initialize_SetsClassification) {
@@ -832,8 +810,7 @@
                          DISPLAY_ID, INVALID_HMAC, AMOTION_EVENT_ACTION_DOWN, 0, 0,
                          AMOTION_EVENT_EDGE_FLAG_NONE, AMETA_NONE, 0, classification,
                          identityTransform, 0, 0, AMOTION_EVENT_INVALID_CURSOR_POSITION,
-                         AMOTION_EVENT_INVALID_CURSOR_POSITION, ui::Transform::ROT_0,
-                         INVALID_DISPLAY_SIZE, INVALID_DISPLAY_SIZE, 0 /*downTime*/,
+                         AMOTION_EVENT_INVALID_CURSOR_POSITION, identityTransform, 0 /*downTime*/,
                          0 /*eventTime*/, pointerCount, pointerProperties, pointerCoords);
         ASSERT_EQ(classification, event.getClassification());
     }
@@ -854,9 +831,9 @@
     event.initialize(InputEvent::nextId(), 0 /*deviceId*/, AINPUT_SOURCE_MOUSE, DISPLAY_ID,
                      INVALID_HMAC, AMOTION_EVENT_ACTION_DOWN, 0, 0, AMOTION_EVENT_EDGE_FLAG_NONE,
                      AMETA_NONE, 0, MotionClassification::NONE, identityTransform, 0, 0,
-                     280 /*xCursorPosition*/, 540 /*yCursorPosition*/, ui::Transform::ROT_0,
-                     INVALID_DISPLAY_SIZE, INVALID_DISPLAY_SIZE, 0 /*downTime*/, 0 /*eventTime*/,
-                     pointerCount, pointerProperties, pointerCoords);
+                     280 /*xCursorPosition*/, 540 /*yCursorPosition*/, identityTransform,
+                     0 /*downTime*/, 0 /*eventTime*/, pointerCount, pointerProperties,
+                     pointerCoords);
     event.offsetLocation(20, 60);
     ASSERT_EQ(280, event.getRawXCursorPosition());
     ASSERT_EQ(540, event.getRawYCursorPosition());
diff --git a/libs/input/tests/InputPublisherAndConsumer_test.cpp b/libs/input/tests/InputPublisherAndConsumer_test.cpp
index 5d1f2c3..05bc0bc 100644
--- a/libs/input/tests/InputPublisherAndConsumer_test.cpp
+++ b/libs/input/tests/InputPublisherAndConsumer_test.cpp
@@ -56,6 +56,7 @@
     void PublishAndConsumeFocusEvent();
     void PublishAndConsumeCaptureEvent();
     void PublishAndConsumeDragEvent();
+    void PublishAndConsumeTouchModeEvent();
 };
 
 TEST_F(InputPublisherAndConsumerTest, GetChannel_ReturnsTheChannel) {
@@ -159,13 +160,14 @@
     constexpr float yScale = 3;
     constexpr float xOffset = -10;
     constexpr float yOffset = -20;
+    constexpr float rawXScale = 4;
+    constexpr float rawYScale = -5;
+    constexpr float rawXOffset = -11;
+    constexpr float rawYOffset = 42;
     constexpr float xPrecision = 0.25;
     constexpr float yPrecision = 0.5;
     constexpr float xCursorPosition = 1.3;
     constexpr float yCursorPosition = 50.6;
-    constexpr uint32_t displayOrientation = ui::Transform::ROT_0;
-    constexpr int32_t displayWidth = 1000;
-    constexpr int32_t displayHeight = 2000;
     constexpr nsecs_t downTime = 3;
     constexpr size_t pointerCount = 3;
     constexpr nsecs_t eventTime = 4;
@@ -191,12 +193,14 @@
 
     ui::Transform transform;
     transform.set({xScale, 0, xOffset, 0, yScale, yOffset, 0, 0, 1});
+    ui::Transform rawTransform;
+    rawTransform.set({rawXScale, 0, rawXOffset, 0, rawYScale, rawYOffset, 0, 0, 1});
     status = mPublisher->publishMotionEvent(seq, eventId, deviceId, source, displayId, hmac, action,
                                             actionButton, flags, edgeFlags, metaState, buttonState,
                                             classification, transform, xPrecision, yPrecision,
-                                            xCursorPosition, yCursorPosition, displayOrientation,
-                                            displayWidth, displayHeight, downTime, eventTime,
-                                            pointerCount, pointerProperties, pointerCoords);
+                                            xCursorPosition, yCursorPosition, rawTransform,
+                                            downTime, eventTime, pointerCount, pointerProperties,
+                                            pointerCoords);
     ASSERT_EQ(OK, status)
             << "publisher publishMotionEvent should return OK";
 
@@ -233,9 +237,7 @@
     EXPECT_EQ(yCursorPosition, motionEvent->getRawYCursorPosition());
     EXPECT_EQ(xCursorPosition * xScale + xOffset, motionEvent->getXCursorPosition());
     EXPECT_EQ(yCursorPosition * yScale + yOffset, motionEvent->getYCursorPosition());
-    EXPECT_EQ(displayOrientation, motionEvent->getDisplayOrientation());
-    EXPECT_EQ(displayWidth, motionEvent->getDisplaySize().x);
-    EXPECT_EQ(displayHeight, motionEvent->getDisplaySize().y);
+    EXPECT_EQ(rawTransform, motionEvent->getRawTransform());
     EXPECT_EQ(downTime, motionEvent->getDownTime());
     EXPECT_EQ(eventTime, motionEvent->getEventTime());
     EXPECT_EQ(pointerCount, motionEvent->getPointerCount());
@@ -246,28 +248,24 @@
         EXPECT_EQ(pointerProperties[i].id, motionEvent->getPointerId(i));
         EXPECT_EQ(pointerProperties[i].toolType, motionEvent->getToolType(i));
 
-        EXPECT_EQ(pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_X),
-                motionEvent->getRawX(i));
-        EXPECT_EQ(pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_Y),
-                motionEvent->getRawY(i));
-        EXPECT_EQ(pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_X) * xScale + xOffset,
-                  motionEvent->getX(i));
-        EXPECT_EQ(pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_Y) * yScale + yOffset,
-                  motionEvent->getY(i));
-        EXPECT_EQ(pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_PRESSURE),
-                motionEvent->getPressure(i));
-        EXPECT_EQ(pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_SIZE),
-                motionEvent->getSize(i));
-        EXPECT_EQ(pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR),
-                motionEvent->getTouchMajor(i));
-        EXPECT_EQ(pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR),
-                motionEvent->getTouchMinor(i));
-        EXPECT_EQ(pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR),
-                motionEvent->getToolMajor(i));
-        EXPECT_EQ(pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR),
-                motionEvent->getToolMinor(i));
-        EXPECT_EQ(pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_ORIENTATION),
-                motionEvent->getOrientation(i));
+        const auto& pc = pointerCoords[i];
+        EXPECT_EQ(pc.getX() * rawXScale + rawXOffset, motionEvent->getRawX(i));
+        EXPECT_EQ(pc.getY() * rawYScale + rawYOffset, motionEvent->getRawY(i));
+        EXPECT_EQ(pc.getX() * xScale + xOffset, motionEvent->getX(i));
+        EXPECT_EQ(pc.getY() * yScale + yOffset, motionEvent->getY(i));
+        EXPECT_EQ(pc.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE), motionEvent->getPressure(i));
+        EXPECT_EQ(pc.getAxisValue(AMOTION_EVENT_AXIS_SIZE), motionEvent->getSize(i));
+        EXPECT_EQ(pc.getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR), motionEvent->getTouchMajor(i));
+        EXPECT_EQ(pc.getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR), motionEvent->getTouchMinor(i));
+        EXPECT_EQ(pc.getAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR), motionEvent->getToolMajor(i));
+        EXPECT_EQ(pc.getAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR), motionEvent->getToolMinor(i));
+
+        // Calculate the orientation after scaling, keeping in mind that an orientation of 0 is
+        // "up", and the positive y direction is "down".
+        const float unscaledOrientation = pc.getAxisValue(AMOTION_EVENT_AXIS_ORIENTATION);
+        const float x = sinf(unscaledOrientation) * xScale;
+        const float y = -cosf(unscaledOrientation) * yScale;
+        EXPECT_EQ(atan2f(x, -y), motionEvent->getOrientation(i));
     }
 
     status = mConsumer->sendFinishedSignal(seq, false);
@@ -292,10 +290,9 @@
     constexpr uint32_t seq = 15;
     int32_t eventId = InputEvent::nextId();
     constexpr bool hasFocus = true;
-    constexpr bool inTouchMode = true;
     const nsecs_t publishTime = systemTime(SYSTEM_TIME_MONOTONIC);
 
-    status = mPublisher->publishFocusEvent(seq, eventId, hasFocus, inTouchMode);
+    status = mPublisher->publishFocusEvent(seq, eventId, hasFocus);
     ASSERT_EQ(OK, status) << "publisher publishFocusEvent should return OK";
 
     uint32_t consumeSeq;
@@ -311,7 +308,6 @@
     EXPECT_EQ(seq, consumeSeq);
     EXPECT_EQ(eventId, focusEvent->getId());
     EXPECT_EQ(hasFocus, focusEvent->getHasFocus());
-    EXPECT_EQ(inTouchMode, focusEvent->getInTouchMode());
 
     status = mConsumer->sendFinishedSignal(seq, true);
     ASSERT_EQ(OK, status) << "consumer sendFinishedSignal should return OK";
@@ -413,6 +409,46 @@
             << "finished signal's consume time should be greater than publish time";
 }
 
+void InputPublisherAndConsumerTest::PublishAndConsumeTouchModeEvent() {
+    status_t status;
+
+    constexpr uint32_t seq = 15;
+    int32_t eventId = InputEvent::nextId();
+    constexpr bool touchModeEnabled = true;
+    const nsecs_t publishTime = systemTime(SYSTEM_TIME_MONOTONIC);
+
+    status = mPublisher->publishTouchModeEvent(seq, eventId, touchModeEnabled);
+    ASSERT_EQ(OK, status) << "publisher publishTouchModeEvent should return OK";
+
+    uint32_t consumeSeq;
+    InputEvent* event;
+    status = mConsumer->consume(&mEventFactory, true /*consumeBatches*/, -1, &consumeSeq, &event);
+    ASSERT_EQ(OK, status) << "consumer consume should return OK";
+
+    ASSERT_TRUE(event != nullptr) << "consumer should have returned non-NULL event";
+    ASSERT_EQ(AINPUT_EVENT_TYPE_TOUCH_MODE, event->getType())
+            << "consumer should have returned a touch mode event";
+
+    const TouchModeEvent& touchModeEvent = static_cast<const TouchModeEvent&>(*event);
+    EXPECT_EQ(seq, consumeSeq);
+    EXPECT_EQ(eventId, touchModeEvent.getId());
+    EXPECT_EQ(touchModeEnabled, touchModeEvent.isInTouchMode());
+
+    status = mConsumer->sendFinishedSignal(seq, true);
+    ASSERT_EQ(OK, status) << "consumer sendFinishedSignal should return OK";
+
+    Result<InputPublisher::ConsumerResponse> result = mPublisher->receiveConsumerResponse();
+    ASSERT_TRUE(result.ok()) << "receiveConsumerResponse should return OK";
+    ASSERT_TRUE(std::holds_alternative<InputPublisher::Finished>(*result));
+    const InputPublisher::Finished& finish = std::get<InputPublisher::Finished>(*result);
+    ASSERT_EQ(seq, finish.seq)
+            << "receiveConsumerResponse should have returned the original sequence number";
+    ASSERT_TRUE(finish.handled)
+            << "receiveConsumerResponse should have set handled to consumer's reply";
+    ASSERT_GE(finish.consumeTime, publishTime)
+            << "finished signal's consume time should be greater than publish time";
+}
+
 TEST_F(InputPublisherAndConsumerTest, SendTimeline) {
     const int32_t inputEventId = 20;
     std::array<nsecs_t, GraphicsTimeline::SIZE> graphicsTimeline;
@@ -449,6 +485,10 @@
     ASSERT_NO_FATAL_FAILURE(PublishAndConsumeDragEvent());
 }
 
+TEST_F(InputPublisherAndConsumerTest, PublishTouchModeEvent_EndToEnd) {
+    ASSERT_NO_FATAL_FAILURE(PublishAndConsumeTouchModeEvent());
+}
+
 TEST_F(InputPublisherAndConsumerTest, PublishMotionEvent_WhenSequenceNumberIsZero_ReturnsError) {
     status_t status;
     const size_t pointerCount = 1;
@@ -460,12 +500,12 @@
     }
 
     ui::Transform identityTransform;
-    status = mPublisher->publishMotionEvent(0, InputEvent::nextId(), 0, 0, 0, INVALID_HMAC, 0, 0, 0,
-                                            0, 0, 0, MotionClassification::NONE, identityTransform,
-                                            0, 0, AMOTION_EVENT_INVALID_CURSOR_POSITION,
-                                            AMOTION_EVENT_INVALID_CURSOR_POSITION,
-                                            ui::Transform::ROT_0, 0, 0, 0, 0, pointerCount,
-                                            pointerProperties, pointerCoords);
+    status =
+            mPublisher->publishMotionEvent(0, InputEvent::nextId(), 0, 0, 0, INVALID_HMAC, 0, 0, 0,
+                                           0, 0, 0, MotionClassification::NONE, identityTransform,
+                                           0, 0, AMOTION_EVENT_INVALID_CURSOR_POSITION,
+                                           AMOTION_EVENT_INVALID_CURSOR_POSITION, identityTransform,
+                                           0, 0, pointerCount, pointerProperties, pointerCoords);
     ASSERT_EQ(BAD_VALUE, status)
             << "publisher publishMotionEvent should return BAD_VALUE";
 }
@@ -477,12 +517,12 @@
     PointerCoords pointerCoords[pointerCount];
 
     ui::Transform identityTransform;
-    status = mPublisher->publishMotionEvent(1, InputEvent::nextId(), 0, 0, 0, INVALID_HMAC, 0, 0, 0,
-                                            0, 0, 0, MotionClassification::NONE, identityTransform,
-                                            0, 0, AMOTION_EVENT_INVALID_CURSOR_POSITION,
-                                            AMOTION_EVENT_INVALID_CURSOR_POSITION,
-                                            ui::Transform::ROT_0, 0, 0, 0, 0, pointerCount,
-                                            pointerProperties, pointerCoords);
+    status =
+            mPublisher->publishMotionEvent(1, InputEvent::nextId(), 0, 0, 0, INVALID_HMAC, 0, 0, 0,
+                                           0, 0, 0, MotionClassification::NONE, identityTransform,
+                                           0, 0, AMOTION_EVENT_INVALID_CURSOR_POSITION,
+                                           AMOTION_EVENT_INVALID_CURSOR_POSITION, identityTransform,
+                                           0, 0, pointerCount, pointerProperties, pointerCoords);
     ASSERT_EQ(BAD_VALUE, status)
             << "publisher publishMotionEvent should return BAD_VALUE";
 }
@@ -499,12 +539,12 @@
     }
 
     ui::Transform identityTransform;
-    status = mPublisher->publishMotionEvent(1, InputEvent::nextId(), 0, 0, 0, INVALID_HMAC, 0, 0, 0,
-                                            0, 0, 0, MotionClassification::NONE, identityTransform,
-                                            0, 0, AMOTION_EVENT_INVALID_CURSOR_POSITION,
-                                            AMOTION_EVENT_INVALID_CURSOR_POSITION,
-                                            ui::Transform::ROT_0, 0, 0, 0, 0, pointerCount,
-                                            pointerProperties, pointerCoords);
+    status =
+            mPublisher->publishMotionEvent(1, InputEvent::nextId(), 0, 0, 0, INVALID_HMAC, 0, 0, 0,
+                                           0, 0, 0, MotionClassification::NONE, identityTransform,
+                                           0, 0, AMOTION_EVENT_INVALID_CURSOR_POSITION,
+                                           AMOTION_EVENT_INVALID_CURSOR_POSITION, identityTransform,
+                                           0, 0, pointerCount, pointerProperties, pointerCoords);
     ASSERT_EQ(BAD_VALUE, status)
             << "publisher publishMotionEvent should return BAD_VALUE";
 }
@@ -520,6 +560,7 @@
     ASSERT_NO_FATAL_FAILURE(PublishAndConsumeDragEvent());
     ASSERT_NO_FATAL_FAILURE(PublishAndConsumeMotionEvent());
     ASSERT_NO_FATAL_FAILURE(PublishAndConsumeKeyEvent());
+    ASSERT_NO_FATAL_FAILURE(PublishAndConsumeTouchModeEvent());
 }
 
 } // namespace android
diff --git a/libs/input/tests/StructLayout_test.cpp b/libs/input/tests/StructLayout_test.cpp
index 59fed1f..1c8658b 100644
--- a/libs/input/tests/StructLayout_test.cpp
+++ b/libs/input/tests/StructLayout_test.cpp
@@ -49,7 +49,7 @@
   CHECK_OFFSET(InputMessage::Body::Key, downTime, 88);
 
   CHECK_OFFSET(InputMessage::Body::Motion, eventId, 0);
-  CHECK_OFFSET(InputMessage::Body::Motion, empty1, 4);
+  CHECK_OFFSET(InputMessage::Body::Motion, pointerCount, 4);
   CHECK_OFFSET(InputMessage::Body::Motion, eventTime, 8);
   CHECK_OFFSET(InputMessage::Body::Motion, deviceId, 16);
   CHECK_OFFSET(InputMessage::Body::Motion, source, 20);
@@ -74,16 +74,17 @@
   CHECK_OFFSET(InputMessage::Body::Motion, yPrecision, 124);
   CHECK_OFFSET(InputMessage::Body::Motion, xCursorPosition, 128);
   CHECK_OFFSET(InputMessage::Body::Motion, yCursorPosition, 132);
-  CHECK_OFFSET(InputMessage::Body::Motion, displayOrientation, 136);
-  CHECK_OFFSET(InputMessage::Body::Motion, displayWidth, 140);
-  CHECK_OFFSET(InputMessage::Body::Motion, displayHeight, 144);
-  CHECK_OFFSET(InputMessage::Body::Motion, pointerCount, 148);
-  CHECK_OFFSET(InputMessage::Body::Motion, pointers, 152);
+  CHECK_OFFSET(InputMessage::Body::Motion, dsdxRaw, 136);
+  CHECK_OFFSET(InputMessage::Body::Motion, dtdxRaw, 140);
+  CHECK_OFFSET(InputMessage::Body::Motion, dtdyRaw, 144);
+  CHECK_OFFSET(InputMessage::Body::Motion, dsdyRaw, 148);
+  CHECK_OFFSET(InputMessage::Body::Motion, txRaw, 152);
+  CHECK_OFFSET(InputMessage::Body::Motion, tyRaw, 156);
+  CHECK_OFFSET(InputMessage::Body::Motion, pointers, 160);
 
   CHECK_OFFSET(InputMessage::Body::Focus, eventId, 0);
   CHECK_OFFSET(InputMessage::Body::Focus, hasFocus, 4);
-  CHECK_OFFSET(InputMessage::Body::Focus, inTouchMode, 5);
-  CHECK_OFFSET(InputMessage::Body::Focus, empty, 6);
+  CHECK_OFFSET(InputMessage::Body::Focus, empty, 5);
 
   CHECK_OFFSET(InputMessage::Body::Capture, eventId, 0);
   CHECK_OFFSET(InputMessage::Body::Capture, pointerCaptureEnabled, 4);
@@ -102,6 +103,10 @@
   CHECK_OFFSET(InputMessage::Body::Timeline, eventId, 0);
   CHECK_OFFSET(InputMessage::Body::Timeline, empty, 4);
   CHECK_OFFSET(InputMessage::Body::Timeline, graphicsTimeline, 8);
+
+  CHECK_OFFSET(InputMessage::Body::TouchMode, eventId, 0);
+  CHECK_OFFSET(InputMessage::Body::TouchMode, isInTouchMode, 4);
+  CHECK_OFFSET(InputMessage::Body::TouchMode, empty, 5);
 }
 
 void TestHeaderSize() {
@@ -110,12 +115,9 @@
     static_assert(sizeof(InputMessage::Header) == 8);
 }
 
-/**
- * We cannot use the Body::size() method here because it is not static for
- * the Motion type, where "pointerCount" variable affects the size and can change at runtime.
- */
 void TestBodySize() {
     static_assert(sizeof(InputMessage::Body::Key) == 96);
+    static_assert(sizeof(InputMessage::Body::Motion::Pointer) == 136);
     static_assert(sizeof(InputMessage::Body::Motion) ==
                   offsetof(InputMessage::Body::Motion, pointers) +
                           sizeof(InputMessage::Body::Motion::Pointer) * MAX_POINTERS);
@@ -123,9 +125,42 @@
     static_assert(sizeof(InputMessage::Body::Focus) == 8);
     static_assert(sizeof(InputMessage::Body::Capture) == 8);
     static_assert(sizeof(InputMessage::Body::Drag) == 16);
+    static_assert(sizeof(InputMessage::Body::TouchMode) == 8);
     // Timeline
     static_assert(GraphicsTimeline::SIZE == 2);
     static_assert(sizeof(InputMessage::Body::Timeline) == 24);
+
+    /**
+     * We cannot use the Body::size() method here because it is not static for
+     * the Motion type, where "pointerCount" variable affects the size and can change at runtime.
+     */
+    static_assert(sizeof(InputMessage::Body) ==
+                  offsetof(InputMessage::Body::Motion, pointers) +
+                          sizeof(InputMessage::Body::Motion::Pointer) * MAX_POINTERS);
+    static_assert(sizeof(InputMessage::Body) == 160 + 136 * 16);
+    static_assert(sizeof(InputMessage::Body) == 2336);
+}
+
+/**
+ * In general, we are sending a variable-length message across the socket, because the number of
+ * pointers varies. When we receive the message, we still need to allocate enough memory for the
+ * entire InputMessage struct. This size is, therefore, the worst case scenario. However, it is
+ * still helpful to compute to get an idea of the sizes that are involved.
+ */
+void TestWorstCaseInputMessageSize() {
+    static_assert(sizeof(InputMessage) == /*header*/ 8 + /*body*/ 2336);
+    static_assert(sizeof(InputMessage) == 2344);
+}
+
+/**
+ * Assuming a single pointer, how big is the message that we are sending across the socket?
+ */
+void CalculateSinglePointerInputMessageSize() {
+    constexpr size_t pointerCount = 1;
+    constexpr size_t bodySize = offsetof(InputMessage::Body::Motion, pointers) +
+            sizeof(InputMessage::Body::Motion::Pointer) * pointerCount;
+    static_assert(bodySize == 160 + 136);
+    static_assert(bodySize == 296); // For the total message size, add the small header
 }
 
 // --- VerifiedInputEvent ---
diff --git a/libs/input/tests/VelocityTracker_test.cpp b/libs/input/tests/VelocityTracker_test.cpp
index 13e2b02..a87b187 100644
--- a/libs/input/tests/VelocityTracker_test.cpp
+++ b/libs/input/tests/VelocityTracker_test.cpp
@@ -184,8 +184,7 @@
                          AMOTION_EVENT_EDGE_FLAG_NONE, AMETA_NONE, 0 /*buttonState*/,
                          MotionClassification::NONE, identityTransform, 0 /*xPrecision*/,
                          0 /*yPrecision*/, AMOTION_EVENT_INVALID_CURSOR_POSITION,
-                         AMOTION_EVENT_INVALID_CURSOR_POSITION, ui::Transform::ROT_0,
-                         INVALID_DISPLAY_SIZE, INVALID_DISPLAY_SIZE, 0 /*downTime*/,
+                         AMOTION_EVENT_INVALID_CURSOR_POSITION, identityTransform, 0 /*downTime*/,
                          entry.eventTime.count(), pointerCount, properties, coords);
 
         events.emplace_back(event);
@@ -344,7 +343,7 @@
         { 235089162955851ns, {{560.66, 843.82}} }, // ACTION_UP
     };
     computeAndCheckVelocity(VelocityTracker::Strategy::IMPULSE, motions, AMOTION_EVENT_AXIS_X,
-                            872.794617);
+                            764.345703);
     computeAndCheckVelocity(VelocityTracker::Strategy::LSQ2, motions, AMOTION_EVENT_AXIS_X,
                             951.698181);
     computeAndCheckVelocity(VelocityTracker::Strategy::IMPULSE, motions, AMOTION_EVENT_AXIS_Y,
diff --git a/libs/input/tests/VerifiedInputEvent_test.cpp b/libs/input/tests/VerifiedInputEvent_test.cpp
index b29c9a4..f2b59ea 100644
--- a/libs/input/tests/VerifiedInputEvent_test.cpp
+++ b/libs/input/tests/VerifiedInputEvent_test.cpp
@@ -43,12 +43,12 @@
 
     ui::Transform transform;
     transform.set({2, 0, 4, 0, 3, 5, 0, 0, 1});
+    ui::Transform identity;
     event.initialize(InputEvent::nextId(), 0 /*deviceId*/, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_DEFAULT,
                      INVALID_HMAC, AMOTION_EVENT_ACTION_DOWN, 0 /*actionButton*/, flags,
                      AMOTION_EVENT_EDGE_FLAG_NONE, AMETA_NONE, 0 /*buttonState*/,
                      MotionClassification::NONE, transform, 0.1 /*xPrecision*/, 0.2 /*yPrecision*/,
-                     280 /*xCursorPosition*/, 540 /*yCursorPosition*/, ui::Transform::ROT_0,
-                     INVALID_DISPLAY_SIZE, INVALID_DISPLAY_SIZE, 100 /*downTime*/,
+                     280 /*xCursorPosition*/, 540 /*yCursorPosition*/, identity, 100 /*downTime*/,
                      200 /*eventTime*/, pointerCount, pointerProperties, pointerCoords);
     return event;
 }
diff --git a/libs/nativebase/Android.bp b/libs/nativebase/Android.bp
index 1a4729c..77b23db 100644
--- a/libs/nativebase/Android.bp
+++ b/libs/nativebase/Android.bp
@@ -45,5 +45,9 @@
             enabled: true,
         },
     },
+    apex_available: [
+        "//apex_available:platform",
+        "com.android.media.swcodec",
+    ],
     min_sdk_version: "29",
 }
diff --git a/libs/nativedisplay/AChoreographer.cpp b/libs/nativedisplay/AChoreographer.cpp
index 2dd6c4f..8240b08 100644
--- a/libs/nativedisplay/AChoreographer.cpp
+++ b/libs/nativedisplay/AChoreographer.cpp
@@ -73,10 +73,12 @@
 } // namespace
 
 namespace android {
+using gui::VsyncEventData;
 
 struct FrameCallback {
     AChoreographer_frameCallback callback;
     AChoreographer_frameCallback64 callback64;
+    AChoreographer_vsyncCallback vsyncCallback;
     void* data;
     nsecs_t dueTime;
 
@@ -95,9 +97,21 @@
 
 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;
@@ -107,7 +121,9 @@
 public:
     explicit Choreographer(const sp<Looper>& looper) EXCLUDES(gChoreographers.lock);
     void postFrameCallbackDelayed(AChoreographer_frameCallback cb,
-                                  AChoreographer_frameCallback64 cb64, void* data, nsecs_t delay);
+                                  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);
@@ -127,9 +143,8 @@
 
     static Choreographer* getForThread();
     virtual ~Choreographer() override EXCLUDES(gChoreographers.lock);
-    int64_t getVsyncId() const;
-    int64_t getFrameDeadline() const;
     int64_t getFrameInterval() const;
+    bool inCallback() const;
 
 private:
     Choreographer(const Choreographer&) = delete;
@@ -145,6 +160,9 @@
 
     void scheduleCallbacks();
 
+    ChoreographerFrameCallbackDataImpl createFrameCallbackData(nsecs_t timestamp) const;
+    void registerStartTime() const;
+
     std::mutex mLock;
     // Protected by mLock
     std::priority_queue<FrameCallback> mFrameCallbacks;
@@ -152,9 +170,13 @@
 
     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;
@@ -192,6 +214,7 @@
     // 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");
@@ -210,10 +233,12 @@
     }
 }
 
-void Choreographer::postFrameCallbackDelayed(
-        AChoreographer_frameCallback cb, AChoreographer_frameCallback64 cb64, void* data, nsecs_t delay) {
+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, data, now + delay};
+    FrameCallback callback{cb, cb64, vsyncCallback, data, now + delay};
     {
         std::lock_guard<std::mutex> _l{mLock};
         mFrameCallbacks.push(callback);
@@ -305,8 +330,9 @@
         // 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(0), systemTime()};
+        event.header =
+                DisplayEventReceiver::Event::Header{DisplayEventReceiver::DISPLAY_EVENT_NULL,
+                                                    PhysicalDisplayId::fromPort(0), systemTime()};
         injectEvent(event);
     }
 }
@@ -368,7 +394,16 @@
     }
     mLastVsyncEventData = vsyncEventData;
     for (const auto& cb : callbacks) {
-        if (cb.callback64 != nullptr) {
+        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);
@@ -377,8 +412,8 @@
 }
 
 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));
+    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) {
@@ -397,30 +432,42 @@
 
 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;
+        case MSG_SCHEDULE_CALLBACKS:
+            scheduleCallbacks();
+            break;
+        case MSG_SCHEDULE_VSYNC:
+            scheduleVsync();
+            break;
+        case MSG_HANDLE_REFRESH_RATE_UPDATES:
+            handleRefreshRateUpdates();
+            break;
     }
 }
 
-int64_t Choreographer::getVsyncId() const {
-    return mLastVsyncEventData.id;
-}
-
-int64_t Choreographer::getFrameDeadline() const {
-    return mLastVsyncEventData.deadlineTimestamp;
-}
-
 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;
 
@@ -433,6 +480,12 @@
     return reinterpret_cast<const Choreographer*>(choreographer);
 }
 
+static inline const ChoreographerFrameCallbackDataImpl*
+AChoreographerFrameCallbackData_to_ChoreographerFrameCallbackDataImpl(
+        const AChoreographerFrameCallbackData* data) {
+    return reinterpret_cast<const ChoreographerFrameCallbackDataImpl*>(data);
+}
+
 // Glue for private C api
 namespace android {
 void AChoreographer_signalRefreshRateCallbacks(nsecs_t vsyncPeriod) EXCLUDES(gChoreographers.lock) {
@@ -485,6 +538,10 @@
                                                     void* data, uint32_t delayMillis) {
     return AChoreographer_postFrameCallbackDelayed64(choreographer, callback, data, delayMillis);
 }
+void AChoreographer_routePostVsyncCallback(AChoreographer* choreographer,
+                                           AChoreographer_vsyncCallback callback, void* data) {
+    return AChoreographer_postVsyncCallback(choreographer, callback, data);
+}
 void AChoreographer_routeRegisterRefreshRateCallback(AChoreographer* choreographer,
                                                      AChoreographer_refreshRateCallback callback,
                                                      void* data) {
@@ -495,19 +552,46 @@
                                                        void* data) {
     return AChoreographer_unregisterRefreshRateCallback(choreographer, callback, data);
 }
-
-int64_t AChoreographer_getVsyncId(const AChoreographer* choreographer) {
-    return AChoreographer_to_Choreographer(choreographer)->getVsyncId();
+int64_t AChoreographerFrameCallbackData_routeGetFrameTimeNanos(
+        const AChoreographerFrameCallbackData* data) {
+    return AChoreographerFrameCallbackData_getFrameTimeNanos(data);
 }
-
-int64_t AChoreographer_getFrameDeadline(const AChoreographer* choreographer) {
-    return AChoreographer_to_Choreographer(choreographer)->getFrameDeadline();
+size_t AChoreographerFrameCallbackData_routeGetFrameTimelinesLength(
+        const AChoreographerFrameCallbackData* data) {
+    return AChoreographerFrameCallbackData_getFrameTimelinesLength(data);
+}
+size_t AChoreographerFrameCallbackData_routeGetPreferredFrameTimelineIndex(
+        const AChoreographerFrameCallbackData* data) {
+    return AChoreographerFrameCallbackData_getPreferredFrameTimelineIndex(data);
+}
+AVsyncId AChoreographerFrameCallbackData_routeGetFrameTimelineVsyncId(
+        const AChoreographerFrameCallbackData* data, size_t index) {
+    return AChoreographerFrameCallbackData_getFrameTimelineVsyncId(data, index);
+}
+int64_t AChoreographerFrameCallbackData_routeGetFrameTimelineExpectedPresentationTimeNanos(
+        const AChoreographerFrameCallbackData* data, size_t index) {
+    return AChoreographerFrameCallbackData_getFrameTimelineExpectedPresentationTimeNanos(data,
+                                                                                         index);
+}
+int64_t AChoreographerFrameCallbackData_routeGetFrameTimelineDeadlineNanos(
+        const AChoreographerFrameCallbackData* data, size_t index) {
+    return AChoreographerFrameCallbackData_getFrameTimelineDeadlineNanos(data, index);
 }
 
 int64_t AChoreographer_getFrameInterval(const AChoreographer* choreographer) {
     return AChoreographer_to_Choreographer(choreographer)->getFrameInterval();
 }
 
+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;
+}
+
 } // namespace android
 
 /* Glue for the NDK interface */
@@ -521,24 +605,31 @@
 }
 
 void AChoreographer_postFrameCallback(AChoreographer* choreographer,
-        AChoreographer_frameCallback callback, void* data) {
-    AChoreographer_to_Choreographer(choreographer)->postFrameCallbackDelayed(
-            callback, nullptr, data, 0);
+                                      AChoreographer_frameCallback callback, void* data) {
+    AChoreographer_to_Choreographer(choreographer)
+            ->postFrameCallbackDelayed(callback, nullptr, nullptr, data, 0);
 }
 void AChoreographer_postFrameCallbackDelayed(AChoreographer* choreographer,
-        AChoreographer_frameCallback callback, void* data, long delayMillis) {
-    AChoreographer_to_Choreographer(choreographer)->postFrameCallbackDelayed(
-            callback, nullptr, data, ms2ns(delayMillis));
+                                             AChoreographer_frameCallback callback, void* data,
+                                             long delayMillis) {
+    AChoreographer_to_Choreographer(choreographer)
+            ->postFrameCallbackDelayed(callback, nullptr, nullptr, data, ms2ns(delayMillis));
+}
+void AChoreographer_postVsyncCallback(AChoreographer* choreographer,
+                                      AChoreographer_vsyncCallback callback, void* data) {
+    AChoreographer_to_Choreographer(choreographer)
+            ->postFrameCallbackDelayed(nullptr, nullptr, callback, data, 0);
 }
 void AChoreographer_postFrameCallback64(AChoreographer* choreographer,
-        AChoreographer_frameCallback64 callback, void* data) {
-    AChoreographer_to_Choreographer(choreographer)->postFrameCallbackDelayed(
-            nullptr, callback, data, 0);
+                                        AChoreographer_frameCallback64 callback, void* data) {
+    AChoreographer_to_Choreographer(choreographer)
+            ->postFrameCallbackDelayed(nullptr, callback, nullptr, data, 0);
 }
 void AChoreographer_postFrameCallbackDelayed64(AChoreographer* choreographer,
-        AChoreographer_frameCallback64 callback, void* data, uint32_t delayMillis) {
-    AChoreographer_to_Choreographer(choreographer)->postFrameCallbackDelayed(
-            nullptr, callback, data, ms2ns(delayMillis));
+                                               AChoreographer_frameCallback64 callback, void* data,
+                                               uint32_t delayMillis) {
+    AChoreographer_to_Choreographer(choreographer)
+            ->postFrameCallbackDelayed(nullptr, callback, nullptr, data, ms2ns(delayMillis));
 }
 void AChoreographer_registerRefreshRateCallback(AChoreographer* choreographer,
                                                 AChoreographer_refreshRateCallback callback,
@@ -551,6 +642,58 @@
     AChoreographer_to_Choreographer(choreographer)->unregisterRefreshRateCallback(callback, data);
 }
 
+int64_t AChoreographerFrameCallbackData_getFrameTimeNanos(
+        const AChoreographerFrameCallbackData* data) {
+    const ChoreographerFrameCallbackDataImpl* frameCallbackData =
+            AChoreographerFrameCallbackData_to_ChoreographerFrameCallbackDataImpl(data);
+    LOG_ALWAYS_FATAL_IF(!frameCallbackData->choreographer->inCallback(),
+                        "Data is only valid in callback");
+    return frameCallbackData->frameTimeNanos;
+}
+size_t AChoreographerFrameCallbackData_getFrameTimelinesLength(
+        const AChoreographerFrameCallbackData* data) {
+    const ChoreographerFrameCallbackDataImpl* frameCallbackData =
+            AChoreographerFrameCallbackData_to_ChoreographerFrameCallbackDataImpl(data);
+    LOG_ALWAYS_FATAL_IF(!frameCallbackData->choreographer->inCallback(),
+                        "Data is only valid in callback");
+    return VsyncEventData::kFrameTimelinesLength;
+}
+size_t AChoreographerFrameCallbackData_getPreferredFrameTimelineIndex(
+        const AChoreographerFrameCallbackData* data) {
+    const ChoreographerFrameCallbackDataImpl* frameCallbackData =
+            AChoreographerFrameCallbackData_to_ChoreographerFrameCallbackDataImpl(data);
+    LOG_ALWAYS_FATAL_IF(!frameCallbackData->choreographer->inCallback(),
+                        "Data is only valid in callback");
+    return frameCallbackData->vsyncEventData.preferredFrameTimelineIndex;
+}
+AVsyncId AChoreographerFrameCallbackData_getFrameTimelineVsyncId(
+        const AChoreographerFrameCallbackData* data, size_t index) {
+    const ChoreographerFrameCallbackDataImpl* frameCallbackData =
+            AChoreographerFrameCallbackData_to_ChoreographerFrameCallbackDataImpl(data);
+    LOG_ALWAYS_FATAL_IF(!frameCallbackData->choreographer->inCallback(),
+                        "Data is only valid in callback");
+    LOG_ALWAYS_FATAL_IF(index >= VsyncEventData::kFrameTimelinesLength, "Index out of bounds");
+    return frameCallbackData->vsyncEventData.frameTimelines[index].vsyncId;
+}
+int64_t AChoreographerFrameCallbackData_getFrameTimelineExpectedPresentationTimeNanos(
+        const AChoreographerFrameCallbackData* data, size_t index) {
+    const ChoreographerFrameCallbackDataImpl* frameCallbackData =
+            AChoreographerFrameCallbackData_to_ChoreographerFrameCallbackDataImpl(data);
+    LOG_ALWAYS_FATAL_IF(!frameCallbackData->choreographer->inCallback(),
+                        "Data is only valid in callback");
+    LOG_ALWAYS_FATAL_IF(index >= VsyncEventData::kFrameTimelinesLength, "Index out of bounds");
+    return frameCallbackData->vsyncEventData.frameTimelines[index].expectedPresentationTime;
+}
+int64_t AChoreographerFrameCallbackData_getFrameTimelineDeadlineNanos(
+        const AChoreographerFrameCallbackData* data, size_t index) {
+    const ChoreographerFrameCallbackDataImpl* frameCallbackData =
+            AChoreographerFrameCallbackData_to_ChoreographerFrameCallbackDataImpl(data);
+    LOG_ALWAYS_FATAL_IF(!frameCallbackData->choreographer->inCallback(),
+                        "Data is only valid in callback");
+    LOG_ALWAYS_FATAL_IF(index >= VsyncEventData::kFrameTimelinesLength, "Index out of bounds");
+    return frameCallbackData->vsyncEventData.frameTimelines[index].deadlineTimestamp;
+}
+
 AChoreographer* AChoreographer_create() {
     Choreographer* choreographer = new Choreographer(nullptr);
     status_t result = choreographer->initialize();
diff --git a/libs/nativedisplay/ADisplay.cpp b/libs/nativedisplay/ADisplay.cpp
index 6288194..76b85d6 100644
--- a/libs/nativedisplay/ADisplay.cpp
+++ b/libs/nativedisplay/ADisplay.cpp
@@ -50,11 +50,6 @@
     int32_t height{0};
 
     /**
-     * The display density.
-     */
-    float density{0};
-
-    /**
      * The refresh rate of the display configuration, in frames per second.
      */
     float fps{0.0};
@@ -168,8 +163,8 @@
             const ui::DisplayMode& mode = modes[j];
             modesPerDisplay[i].emplace_back(
                     DisplayConfigImpl{static_cast<size_t>(mode.id), mode.resolution.getWidth(),
-                                      mode.resolution.getHeight(), staticInfo.density,
-                                      mode.refreshRate, mode.sfVsyncOffset, mode.appVsyncOffset});
+                                      mode.resolution.getHeight(), mode.refreshRate,
+                                      mode.sfVsyncOffset, mode.appVsyncOffset});
         }
     }
 
@@ -283,12 +278,6 @@
     return NAME_NOT_FOUND;
 }
 
-float ADisplayConfig_getDensity(ADisplayConfig* config) {
-    CHECK_NOT_NULL(config);
-
-    return reinterpret_cast<DisplayConfigImpl*>(config)->density;
-}
-
 int32_t ADisplayConfig_getWidth(ADisplayConfig* config) {
     CHECK_NOT_NULL(config);
 
diff --git a/libs/nativedisplay/include-private/private/android/choreographer.h b/libs/nativedisplay/include-private/private/android/choreographer.h
index 7d25ce8..cdf7e66 100644
--- a/libs/nativedisplay/include-private/private/android/choreographer.h
+++ b/libs/nativedisplay/include-private/private/android/choreographer.h
@@ -29,19 +29,6 @@
 // for consumption by callbacks.
 void AChoreographer_signalRefreshRateCallbacks(int64_t vsyncPeriod);
 
-// Returns the vsync id of the last frame callback. Client are expected to call
-// this function from their frame callback function to get the vsyncId and pass
-// it together with a buffer or transaction to the Surface Composer. Calling
-// this function from anywhere else will return an undefined value.
-int64_t AChoreographer_getVsyncId(const AChoreographer* choreographer);
-
-// Returns the deadline timestamp (in CLOCK_MONOTONIC) of the last frame callback.
-// Client are expected to call this function from their frame callback function
-// to get the deadline and use it to know whether a frame is likely to miss
-// presentation. Calling this function from anywhere else will return an undefined
-// value.
-int64_t AChoreographer_getFrameDeadline(const AChoreographer* choreographer);
-
 // Returns the current interval in ns between frames.
 // Client are expected to call this function from their frame callback function.
 // Calling this function from anywhere else will return an undefined value.
@@ -63,11 +50,30 @@
 void AChoreographer_routePostFrameCallbackDelayed64(AChoreographer* choreographer,
                                                     AChoreographer_frameCallback64 callback,
                                                     void* data, uint32_t delayMillis);
+void AChoreographer_routePostVsyncCallback(AChoreographer* choreographer,
+                                           AChoreographer_vsyncCallback callback, void* data);
 void AChoreographer_routeRegisterRefreshRateCallback(AChoreographer* choreographer,
                                                      AChoreographer_refreshRateCallback callback,
                                                      void* data);
 void AChoreographer_routeUnregisterRefreshRateCallback(AChoreographer* choreographer,
                                                        AChoreographer_refreshRateCallback callback,
                                                        void* data);
+int64_t AChoreographerFrameCallbackData_routeGetFrameTimeNanos(
+        const AChoreographerFrameCallbackData* data);
+size_t AChoreographerFrameCallbackData_routeGetFrameTimelinesLength(
+        const AChoreographerFrameCallbackData* data);
+size_t AChoreographerFrameCallbackData_routeGetPreferredFrameTimelineIndex(
+        const AChoreographerFrameCallbackData* data);
+AVsyncId AChoreographerFrameCallbackData_routeGetFrameTimelineVsyncId(
+        const AChoreographerFrameCallbackData* data, size_t index);
+int64_t AChoreographerFrameCallbackData_routeGetFrameTimelineExpectedPresentationTimeNanos(
+        const AChoreographerFrameCallbackData* data, size_t index);
+int64_t AChoreographerFrameCallbackData_routeGetFrameTimelineDeadlineNanos(
+        const AChoreographerFrameCallbackData* data, size_t index);
+
+// Gets the start time (dispatch time) in nanos of the callback, given a vsync ID. This does not
+// account for clients that use multiple choreographers, because callbacks that give the same vsync
+// ID may be dispatched at different times.
+int64_t AChoreographer_getStartTimeNanosForVsyncId(AVsyncId vsyncId);
 
 } // namespace android
diff --git a/libs/nativedisplay/include/apex/display.h b/libs/nativedisplay/include/apex/display.h
index bd94b55..0f44902 100644
--- a/libs/nativedisplay/include/apex/display.h
+++ b/libs/nativedisplay/include/apex/display.h
@@ -107,11 +107,6 @@
 int ADisplay_getCurrentConfig(ADisplay* display, ADisplayConfig** outConfig);
 
 /**
- * Queries the density for a given display configuration.
- */
-float ADisplayConfig_getDensity(ADisplayConfig* config);
-
-/**
  * Queries the width in pixels for a given display configuration.
  */
 int32_t ADisplayConfig_getWidth(ADisplayConfig* config);
diff --git a/libs/nativedisplay/include/surfacetexture/ImageConsumer.h b/libs/nativedisplay/include/surfacetexture/ImageConsumer.h
index 35ae3d2..6fd4b8f 100644
--- a/libs/nativedisplay/include/surfacetexture/ImageConsumer.h
+++ b/libs/nativedisplay/include/surfacetexture/ImageConsumer.h
@@ -42,7 +42,8 @@
     typedef status_t (*SurfaceTexture_fenceWait)(int fence, void* fencePassThroughHandle);
 
     sp<GraphicBuffer> dequeueBuffer(int* outSlotid, android_dataspace* outDataspace,
-                                    bool* outQueueEmpty, SurfaceTexture& cb,
+                                    HdrMetadata* outHdrMetadata, bool* outQueueEmpty,
+                                    SurfaceTexture& cb,
                                     SurfaceTexture_createReleaseFence createFence,
                                     SurfaceTexture_fenceWait fenceWait,
                                     void* fencePassThroughHandle);
diff --git a/libs/nativedisplay/include/surfacetexture/SurfaceTexture.h b/libs/nativedisplay/include/surfacetexture/SurfaceTexture.h
index 6eaa84e..0f119f3 100644
--- a/libs/nativedisplay/include/surfacetexture/SurfaceTexture.h
+++ b/libs/nativedisplay/include/surfacetexture/SurfaceTexture.h
@@ -272,10 +272,11 @@
     status_t attachToContext(uint32_t tex);
 
     sp<GraphicBuffer> dequeueBuffer(int* outSlotid, android_dataspace* outDataspace,
-                                    float* outTransformMatrix, bool* outQueueEmpty,
+                                    HdrMetadata* outHdrMetadata, float* outTransformMatrix,
+                                    uint32_t* outTransform, bool* outQueueEmpty,
                                     SurfaceTexture_createReleaseFence createFence,
                                     SurfaceTexture_fenceWait fenceWait,
-                                    void* fencePassThroughHandle);
+                                    void* fencePassThroughHandle, ARect* currentCrop);
 
     /**
      * takeConsumerOwnership attaches a SurfaceTexture that is currently in the
diff --git a/libs/nativedisplay/include/surfacetexture/surface_texture_platform.h b/libs/nativedisplay/include/surfacetexture/surface_texture_platform.h
index 85fe42f..2987f3a 100644
--- a/libs/nativedisplay/include/surfacetexture/surface_texture_platform.h
+++ b/libs/nativedisplay/include/surfacetexture/surface_texture_platform.h
@@ -19,6 +19,7 @@
 
 #include <EGL/egl.h>
 #include <EGL/eglext.h>
+#include <android/hdr_metadata.h>
 #include <jni.h>
 #include <system/graphics.h>
 
@@ -82,12 +83,12 @@
  * The caller gets ownership of the buffer and need to release it with
  * AHardwareBuffer_release.
  */
-AHardwareBuffer* ASurfaceTexture_dequeueBuffer(ASurfaceTexture* st, int* outSlotid,
-                                               android_dataspace* outDataspace,
-                                               float* outTransformMatrix, bool* outNewContent,
-                                               ASurfaceTexture_createReleaseFence createFence,
-                                               ASurfaceTexture_fenceWait fenceWait,
-                                               void* fencePassThroughHandle);
+AHardwareBuffer* ASurfaceTexture_dequeueBuffer(
+        ASurfaceTexture* st, int* outSlotid, android_dataspace* outDataspace,
+        AHdrMetadataType* outHdrType, android_cta861_3_metadata* outCta861_3,
+        android_smpte2086_metadata* outSmpte2086, float* outTransformMatrix, uint32_t* outTransform,
+        bool* outNewContent, ASurfaceTexture_createReleaseFence createFence,
+        ASurfaceTexture_fenceWait fenceWait, void* fencePassThroughHandle, ARect* currentCrop);
 
 } // namespace android
 
diff --git a/libs/nativedisplay/libnativedisplay.map.txt b/libs/nativedisplay/libnativedisplay.map.txt
index 9ed4915..969d937 100644
--- a/libs/nativedisplay/libnativedisplay.map.txt
+++ b/libs/nativedisplay/libnativedisplay.map.txt
@@ -7,6 +7,13 @@
     AChoreographer_postFrameCallbackDelayed64; # apex # introduced=30
     AChoreographer_registerRefreshRateCallback; # apex # introduced=30
     AChoreographer_unregisterRefreshRateCallback; # apex # introduced=30
+    AChoreographer_postVsyncCallback; # apex # introduced=33
+    AChoreographerFrameCallbackData_getFrameTimeNanos; # apex # introduced=33
+    AChoreographerFrameCallbackData_getFrameTimelinesLength; # apex # introduced=33
+    AChoreographerFrameCallbackData_getPreferredFrameTimelineIndex; # apex # introduced=33
+    AChoreographerFrameCallbackData_getFrameTimelineVsyncId; # apex # introduced=33
+    AChoreographerFrameCallbackData_getFrameTimelineExpectedPresentationTimeNanos; # apex # introduced=33
+    AChoreographerFrameCallbackData_getFrameTimelineDeadlineNanos; # apex # introduced=33
     AChoreographer_create; # apex # introduced=30
     AChoreographer_destroy; # apex # introduced=30
     AChoreographer_getFd; # apex # introduced=30
@@ -28,9 +35,15 @@
       android::AChoreographer_routePostFrameCallbackDelayed64*;
       android::AChoreographer_routeRegisterRefreshRateCallback*;
       android::AChoreographer_routeUnregisterRefreshRateCallback*;
+      android::AChoreographer_routePostVsyncCallback*;
+      android::AChoreographerFrameCallbackData_routeGetFrameTimeNanos*;
+      android::AChoreographerFrameCallbackData_routeGetFrameTimelinesLength*;
+      android::AChoreographerFrameCallbackData_routeGetPreferredFrameTimelineIndex*;
+      android::AChoreographerFrameCallbackData_routeGetFrameTimelineVsyncId*;
+      android::AChoreographerFrameCallbackData_routeGetFrameTimelineExpectedPresentationTimeNanos*;
+      android::AChoreographerFrameCallbackData_routeGetFrameTimelineDeadlineNanos*;
+      android::AChoreographer_getStartTimeNanosForVsyncId*;
       android::AChoreographer_signalRefreshRateCallbacks*;
-      android::AChoreographer_getVsyncId*;
-      android::AChoreographer_getFrameDeadline*;
       android::AChoreographer_getFrameInterval*;
       android::ADisplay_acquirePhysicalDisplays*;
       android::ADisplay_release*;
@@ -38,7 +51,6 @@
       android::ADisplay_getDisplayType*;
       android::ADisplay_getPreferredWideColorFormat*;
       android::ADisplay_getCurrentConfig*;
-      android::ADisplayConfig_getDensity*;
       android::ADisplayConfig_getWidth*;
       android::ADisplayConfig_getHeight*;
       android::ADisplayConfig_getFps*;
diff --git a/libs/nativedisplay/surfacetexture/ImageConsumer.cpp b/libs/nativedisplay/surfacetexture/ImageConsumer.cpp
index 365e788..cf16739 100644
--- a/libs/nativedisplay/surfacetexture/ImageConsumer.cpp
+++ b/libs/nativedisplay/surfacetexture/ImageConsumer.cpp
@@ -28,7 +28,8 @@
 }
 
 sp<GraphicBuffer> ImageConsumer::dequeueBuffer(int* outSlotid, android_dataspace* outDataspace,
-                                               bool* outQueueEmpty, SurfaceTexture& st,
+                                               HdrMetadata* outHdrMetadata, bool* outQueueEmpty,
+                                               SurfaceTexture& st,
                                                SurfaceTexture_createReleaseFence createFence,
                                                SurfaceTexture_fenceWait fenceWait,
                                                void* fencePassThroughHandle) {
@@ -121,6 +122,7 @@
     st.computeCurrentTransformMatrixLocked();
 
     *outDataspace = item.mDataSpace;
+    *outHdrMetadata = item.mHdrMetadata;
     *outSlotid = slot;
     return st.mSlots[slot].mGraphicBuffer;
 }
diff --git a/libs/nativedisplay/surfacetexture/SurfaceTexture.cpp b/libs/nativedisplay/surfacetexture/SurfaceTexture.cpp
index 62db6d0..d3d4cba 100644
--- a/libs/nativedisplay/surfacetexture/SurfaceTexture.cpp
+++ b/libs/nativedisplay/surfacetexture/SurfaceTexture.cpp
@@ -464,10 +464,12 @@
 }
 
 sp<GraphicBuffer> SurfaceTexture::dequeueBuffer(int* outSlotid, android_dataspace* outDataspace,
-                                                float* outTransformMatrix, bool* outQueueEmpty,
+                                                HdrMetadata* outHdrMetadata,
+                                                float* outTransformMatrix, uint32_t* outTransform,
+                                                bool* outQueueEmpty,
                                                 SurfaceTexture_createReleaseFence createFence,
                                                 SurfaceTexture_fenceWait fenceWait,
-                                                void* fencePassThroughHandle) {
+                                                void* fencePassThroughHandle, ARect* currentCrop) {
     Mutex::Autolock _l(mMutex);
     sp<GraphicBuffer> buffer;
 
@@ -481,9 +483,11 @@
         return buffer;
     }
 
-    buffer = mImageConsumer.dequeueBuffer(outSlotid, outDataspace, outQueueEmpty, *this,
-                                          createFence, fenceWait, fencePassThroughHandle);
+    buffer = mImageConsumer.dequeueBuffer(outSlotid, outDataspace, outHdrMetadata, outQueueEmpty,
+                                          *this, createFence, fenceWait, fencePassThroughHandle);
     memcpy(outTransformMatrix, mCurrentTransformMatrix, sizeof(mCurrentTransformMatrix));
+    *outTransform = mCurrentTransform;
+    *currentCrop = mCurrentCrop;
     return buffer;
 }
 
diff --git a/libs/nativedisplay/surfacetexture/surface_texture.cpp b/libs/nativedisplay/surfacetexture/surface_texture.cpp
index c214ab7..39a925f 100644
--- a/libs/nativedisplay/surfacetexture/surface_texture.cpp
+++ b/libs/nativedisplay/surfacetexture/surface_texture.cpp
@@ -192,17 +192,23 @@
     texture->consumer->releaseConsumerOwnership();
 }
 
-AHardwareBuffer* ASurfaceTexture_dequeueBuffer(ASurfaceTexture* st, int* outSlotid,
-                                               android_dataspace* outDataspace,
-                                               float* outTransformMatrix, bool* outNewContent,
-                                               ASurfaceTexture_createReleaseFence createFence,
-                                               ASurfaceTexture_fenceWait fenceWait, void* handle) {
+AHardwareBuffer* ASurfaceTexture_dequeueBuffer(
+        ASurfaceTexture* st, int* outSlotid, android_dataspace* outDataspace,
+        AHdrMetadataType* outHdrType, android_cta861_3_metadata* outCta861_3,
+        android_smpte2086_metadata* outSmpte2086, float* outTransformMatrix, uint32_t* outTransform,
+        bool* outNewContent, ASurfaceTexture_createReleaseFence createFence,
+        ASurfaceTexture_fenceWait fenceWait, void* handle, ARect* currentCrop) {
     sp<GraphicBuffer> buffer;
     *outNewContent = false;
     bool queueEmpty;
     do {
-        buffer = st->consumer->dequeueBuffer(outSlotid, outDataspace, outTransformMatrix,
-                                             &queueEmpty, createFence, fenceWait, handle);
+        HdrMetadata metadata;
+        buffer = st->consumer->dequeueBuffer(outSlotid, outDataspace, &metadata, outTransformMatrix,
+                                             outTransform, &queueEmpty, createFence, fenceWait,
+                                             handle, currentCrop);
+        *outHdrType = static_cast<AHdrMetadataType>(metadata.validTypes);
+        *outCta861_3 = metadata.cta8613;
+        *outSmpte2086 = metadata.smpte2086;
         if (!queueEmpty) {
             *outNewContent = true;
         }
diff --git a/libs/nativewindow/AHardwareBuffer.cpp b/libs/nativewindow/AHardwareBuffer.cpp
index ef7602f..381900e 100644
--- a/libs/nativewindow/AHardwareBuffer.cpp
+++ b/libs/nativewindow/AHardwareBuffer.cpp
@@ -30,7 +30,7 @@
 
 #include <private/android/AHardwareBufferHelpers.h>
 #include <android/hardware/graphics/common/1.1/types.h>
-
+#include <aidl/android/hardware/graphics/common/PixelFormat.h>
 
 static constexpr int kFdBufferSize = 128 * sizeof(int);  // 128 ints
 
@@ -370,7 +370,7 @@
     if (!AHardwareBuffer_isValidDescription(desc, /*log=*/false)) return 0;
 
     bool supported = false;
-    GraphicBuffer* gBuffer = new GraphicBuffer();
+    sp<GraphicBuffer> gBuffer(new GraphicBuffer());
     status_t err = gBuffer->isSupported(desc->width, desc->height, desc->format, desc->layers,
                                         desc->usage, &supported);
 
@@ -509,10 +509,6 @@
             ALOGE_IF(log, "AHARDWAREBUFFER_USAGE_SENSOR_DIRECT_DATA requires AHARDWAREBUFFER_FORMAT_BLOB");
             return false;
         }
-        if (desc->usage & AHARDWAREBUFFER_USAGE_GPU_DATA_BUFFER) {
-            ALOGE_IF(log, "AHARDWAREBUFFER_USAGE_GPU_DATA_BUFFER requires AHARDWAREBUFFER_FORMAT_BLOB");
-            return false;
-        }
     }
 
     if ((desc->usage & (AHARDWAREBUFFER_USAGE_CPU_READ_MASK | AHARDWAREBUFFER_USAGE_CPU_WRITE_MASK)) &&
@@ -590,8 +586,12 @@
             "HAL and AHardwareBuffer pixel format don't match");
     static_assert(HAL_PIXEL_FORMAT_YCBCR_P010 == AHARDWAREBUFFER_FORMAT_YCbCr_P010,
             "HAL and AHardwareBuffer pixel format don't match");
+    static_assert(static_cast<int>(aidl::android::hardware::graphics::common::PixelFormat::R_8) ==
+                          AHARDWAREBUFFER_FORMAT_R8_UNORM,
+            "HAL and AHardwareBuffer pixel format don't match");
 
     switch (format) {
+        case AHARDWAREBUFFER_FORMAT_R8_UNORM:
         case AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM:
         case AHARDWAREBUFFER_FORMAT_R8G8B8X8_UNORM:
         case AHARDWAREBUFFER_FORMAT_R5G6B5_UNORM:
@@ -645,6 +645,8 @@
 
 uint32_t AHardwareBuffer_bytesPerPixel(uint32_t format) {
   switch (format) {
+      case AHARDWAREBUFFER_FORMAT_R8_UNORM:
+          return 1;
       case AHARDWAREBUFFER_FORMAT_R5G6B5_UNORM:
       case AHARDWAREBUFFER_FORMAT_D16_UNORM:
           return 2;
diff --git a/libs/nativewindow/ANativeWindow.cpp b/libs/nativewindow/ANativeWindow.cpp
index 75f2385..18a4b2d 100644
--- a/libs/nativewindow/ANativeWindow.cpp
+++ b/libs/nativewindow/ANativeWindow.cpp
@@ -133,16 +133,51 @@
 
 int32_t ANativeWindow_setBuffersDataSpace(ANativeWindow* window, int32_t dataSpace) {
     static_assert(static_cast<int>(ADATASPACE_UNKNOWN) == static_cast<int>(HAL_DATASPACE_UNKNOWN));
-    static_assert(static_cast<int>(ADATASPACE_SCRGB_LINEAR) == static_cast<int>(HAL_DATASPACE_V0_SCRGB_LINEAR));
+    static_assert(static_cast<int>(STANDARD_MASK) == static_cast<int>(HAL_DATASPACE_STANDARD_MASK));
+    static_assert(static_cast<int>(STANDARD_UNSPECIFIED) == static_cast<int>(HAL_DATASPACE_STANDARD_UNSPECIFIED));
+    static_assert(static_cast<int>(STANDARD_BT709) == static_cast<int>(HAL_DATASPACE_STANDARD_BT709));
+    static_assert(static_cast<int>(STANDARD_BT601_625) == static_cast<int>(HAL_DATASPACE_STANDARD_BT601_625));
+    static_assert(static_cast<int>(STANDARD_BT601_625_UNADJUSTED) == static_cast<int>(HAL_DATASPACE_STANDARD_BT601_625_UNADJUSTED));
+    static_assert(static_cast<int>(STANDARD_BT601_525) == static_cast<int>(HAL_DATASPACE_STANDARD_BT601_525));
+    static_assert(static_cast<int>(STANDARD_BT601_525_UNADJUSTED) == static_cast<int>(HAL_DATASPACE_STANDARD_BT601_525_UNADJUSTED));
+    static_assert(static_cast<int>(STANDARD_BT470M) == static_cast<int>(HAL_DATASPACE_STANDARD_BT470M));
+    static_assert(static_cast<int>(STANDARD_FILM) == static_cast<int>(HAL_DATASPACE_STANDARD_FILM));
+    static_assert(static_cast<int>(STANDARD_DCI_P3) == static_cast<int>(HAL_DATASPACE_STANDARD_DCI_P3));
+    static_assert(static_cast<int>(STANDARD_ADOBE_RGB) == static_cast<int>(HAL_DATASPACE_STANDARD_ADOBE_RGB));
+    static_assert(static_cast<int>(TRANSFER_MASK) == static_cast<int>(HAL_DATASPACE_TRANSFER_MASK));
+    static_assert(static_cast<int>(TRANSFER_UNSPECIFIED) == static_cast<int>(HAL_DATASPACE_TRANSFER_UNSPECIFIED));
+    static_assert(static_cast<int>(TRANSFER_LINEAR) == static_cast<int>(HAL_DATASPACE_TRANSFER_LINEAR));
+    static_assert(static_cast<int>(TRANSFER_SMPTE_170M) == static_cast<int>(HAL_DATASPACE_TRANSFER_SMPTE_170M));
+    static_assert(static_cast<int>(TRANSFER_GAMMA2_2) == static_cast<int>(HAL_DATASPACE_TRANSFER_GAMMA2_2));
+    static_assert(static_cast<int>(TRANSFER_GAMMA2_6) == static_cast<int>(HAL_DATASPACE_TRANSFER_GAMMA2_6));
+    static_assert(static_cast<int>(TRANSFER_GAMMA2_8) == static_cast<int>(HAL_DATASPACE_TRANSFER_GAMMA2_8));
+    static_assert(static_cast<int>(TRANSFER_ST2084) == static_cast<int>(HAL_DATASPACE_TRANSFER_ST2084));
+    static_assert(static_cast<int>(TRANSFER_HLG) == static_cast<int>(HAL_DATASPACE_TRANSFER_HLG));
+    static_assert(static_cast<int>(RANGE_MASK) == static_cast<int>(HAL_DATASPACE_RANGE_MASK));
+    static_assert(static_cast<int>(RANGE_UNSPECIFIED) == static_cast<int>(HAL_DATASPACE_RANGE_UNSPECIFIED));
+    static_assert(static_cast<int>(RANGE_FULL) == static_cast<int>(HAL_DATASPACE_RANGE_FULL));
+    static_assert(static_cast<int>(RANGE_LIMITED) == static_cast<int>(HAL_DATASPACE_RANGE_LIMITED));
+    static_assert(static_cast<int>(RANGE_EXTENDED) == static_cast<int>(HAL_DATASPACE_RANGE_EXTENDED));
     static_assert(static_cast<int>(ADATASPACE_SRGB) == static_cast<int>(HAL_DATASPACE_V0_SRGB));
     static_assert(static_cast<int>(ADATASPACE_SCRGB) == static_cast<int>(HAL_DATASPACE_V0_SCRGB));
     static_assert(static_cast<int>(ADATASPACE_DISPLAY_P3) == static_cast<int>(HAL_DATASPACE_DISPLAY_P3));
     static_assert(static_cast<int>(ADATASPACE_BT2020_PQ) == static_cast<int>(HAL_DATASPACE_BT2020_PQ));
+    static_assert(static_cast<int>(ADATASPACE_BT2020_ITU_PQ) ==
+        static_cast<int>(HAL_DATASPACE_BT2020_ITU_PQ));
     static_assert(static_cast<int>(ADATASPACE_ADOBE_RGB) == static_cast<int>(HAL_DATASPACE_ADOBE_RGB));
+    static_assert(static_cast<int>(ADATASPACE_JFIF) == static_cast<int>(HAL_DATASPACE_V0_JFIF));
+    static_assert(static_cast<int>(ADATASPACE_BT601_625) == static_cast<int>(HAL_DATASPACE_V0_BT601_625));
+    static_assert(static_cast<int>(ADATASPACE_BT601_525) == static_cast<int>(HAL_DATASPACE_V0_BT601_525));
     static_assert(static_cast<int>(ADATASPACE_BT2020) == static_cast<int>(HAL_DATASPACE_BT2020));
     static_assert(static_cast<int>(ADATASPACE_BT709) == static_cast<int>(HAL_DATASPACE_V0_BT709));
     static_assert(static_cast<int>(ADATASPACE_DCI_P3) == static_cast<int>(HAL_DATASPACE_DCI_P3));
     static_assert(static_cast<int>(ADATASPACE_SRGB_LINEAR) == static_cast<int>(HAL_DATASPACE_V0_SRGB_LINEAR));
+    static_assert(static_cast<int>(ADATASPACE_BT2020_HLG) ==
+        static_cast<int>(HAL_DATASPACE_BT2020_HLG));
+    static_assert(static_cast<int>(ADATASPACE_BT2020_ITU_HLG) ==
+        static_cast<int>(HAL_DATASPACE_BT2020_ITU_HLG));
+    static_assert(static_cast<int>(DEPTH) == static_cast<int>(HAL_DATASPACE_DEPTH));
+    static_assert(static_cast<int>(DYNAMIC_DEPTH) == static_cast<int>(HAL_DATASPACE_DYNAMIC_DEPTH));
 
     if (!window || !query(window, NATIVE_WINDOW_IS_VALID) ||
             !isDataSpaceValid(window, dataSpace)) {
diff --git a/libs/nativewindow/Android.bp b/libs/nativewindow/Android.bp
index 9286009..d30efa1 100644
--- a/libs/nativewindow/Android.bp
+++ b/libs/nativewindow/Android.bp
@@ -47,6 +47,11 @@
     // TODO(b/153609531): remove when no longer needed.
     native_bridge_supported: true,
     min_sdk_version: "29",
+    apex_available: [
+        "//apex_available:platform",
+        "com.android.media.swcodec",
+        "test_com.android.media.swcodec",
+    ],
     host_supported: true,
 }
 
@@ -64,7 +69,7 @@
         symbol_file: "libnativewindow.map.txt",
         unversioned: true,
         override_export_include_dirs: [
-            "include"
+            "include",
         ],
     },
     export_include_dirs: [
diff --git a/libs/nativewindow/include/android/data_space.h b/libs/nativewindow/include/android/data_space.h
index e759513..771844f 100644
--- a/libs/nativewindow/include/android/data_space.h
+++ b/libs/nativewindow/include/android/data_space.h
@@ -15,6 +15,13 @@
  */
 
 /**
+ * @defgroup ADataSpace Data Space
+ *
+ * ADataSpace describes how to interpret colors.
+ * @{
+ */
+
+/**
  * @file data_space.h
  */
 
@@ -43,6 +50,340 @@
     ADATASPACE_UNKNOWN = 0,
 
     /**
+     * Color-description aspects
+     *
+     * The following aspects define various characteristics of the color
+     * specification. These represent bitfields, so that a data space value
+     * can specify each of them independently.
+     */
+
+    /**
+     * Standard aspect
+     *
+     * Defines the chromaticity coordinates of the source primaries in terms of
+     * the CIE 1931 definition of x and y specified in ISO 11664-1.
+     */
+    STANDARD_MASK = 63 << 16,
+
+    /**
+     * Chromacity coordinates are unknown or are determined by the application.
+     * Implementations shall use the following suggested standards:
+     *
+     * All YCbCr formats: BT709 if size is 720p or larger (since most video
+     *                    content is letterboxed this corresponds to width is
+     *                    1280 or greater, or height is 720 or greater).
+     *                    BT601_625 if size is smaller than 720p or is JPEG.
+     * All RGB formats:   BT709.
+     *
+     * For all other formats standard is undefined, and implementations should use
+     * an appropriate standard for the data represented.
+     */
+    STANDARD_UNSPECIFIED = 0 << 16,
+
+    /**
+     * Primaries:       x       y
+     *  green           0.300   0.600
+     *  blue            0.150   0.060
+     *  red             0.640   0.330
+     *  white (D65)     0.3127  0.3290
+     *
+     * Use the unadjusted KR = 0.2126, KB = 0.0722 luminance interpretation
+     * for RGB conversion.
+     */
+    STANDARD_BT709 = 1 << 16,
+
+    /**
+     * Primaries:       x       y
+     *  green           0.290   0.600
+     *  blue            0.150   0.060
+     *  red             0.640   0.330
+     *  white (D65)     0.3127  0.3290
+     *
+     *  KR = 0.299, KB = 0.114. This adjusts the luminance interpretation
+     *  for RGB conversion from the one purely determined by the primaries
+     *  to minimize the color shift into RGB space that uses BT.709
+     *  primaries.
+     */
+    STANDARD_BT601_625 = 2 << 16,
+
+    /**
+     * Primaries:       x       y
+     *  green           0.290   0.600
+     *  blue            0.150   0.060
+     *  red             0.640   0.330
+     *  white (D65)     0.3127  0.3290
+     *
+     * Use the unadjusted KR = 0.222, KB = 0.071 luminance interpretation
+     * for RGB conversion.
+     */
+    STANDARD_BT601_625_UNADJUSTED = 3 << 16,
+
+    /**
+     * Primaries:       x       y
+     *  green           0.310   0.595
+     *  blue            0.155   0.070
+     *  red             0.630   0.340
+     *  white (D65)     0.3127  0.3290
+     *
+     *  KR = 0.299, KB = 0.114. This adjusts the luminance interpretation
+     *  for RGB conversion from the one purely determined by the primaries
+     *  to minimize the color shift into RGB space that uses BT.709
+     *  primaries.
+     */
+    STANDARD_BT601_525 = 4 << 16,
+
+    /**
+     * Primaries:       x       y
+     *  green           0.310   0.595
+     *  blue            0.155   0.070
+     *  red             0.630   0.340
+     *  white (D65)     0.3127  0.3290
+     *
+     * Use the unadjusted KR = 0.212, KB = 0.087 luminance interpretation
+     * for RGB conversion (as in SMPTE 240M).
+     */
+    STANDARD_BT601_525_UNADJUSTED = 5 << 16,
+
+    /**
+     * Primaries:       x       y
+     *  green           0.170   0.797
+     *  blue            0.131   0.046
+     *  red             0.708   0.292
+     *  white (D65)     0.3127  0.3290
+     *
+     * Use the unadjusted KR = 0.2627, KB = 0.0593 luminance interpretation
+     * for RGB conversion.
+     */
+    STANDARD_BT2020 = 6 << 16,
+
+    /**
+     * Primaries:       x       y
+     *  green           0.170   0.797
+     *  blue            0.131   0.046
+     *  red             0.708   0.292
+     *  white (D65)     0.3127  0.3290
+     *
+     * Use the unadjusted KR = 0.2627, KB = 0.0593 luminance interpretation
+     * for RGB conversion using the linear domain.
+     */
+    STANDARD_BT2020_CONSTANT_LUMINANCE = 7 << 16,
+
+    /**
+     * Primaries:       x      y
+     *  green           0.21   0.71
+     *  blue            0.14   0.08
+     *  red             0.67   0.33
+     *  white (C)       0.310  0.316
+     *
+     * Use the unadjusted KR = 0.30, KB = 0.11 luminance interpretation
+     * for RGB conversion.
+     */
+    STANDARD_BT470M = 8 << 16,
+
+    /**
+     * Primaries:       x       y
+     *  green           0.243   0.692
+     *  blue            0.145   0.049
+     *  red             0.681   0.319
+     *  white (C)       0.310   0.316
+     *
+     * Use the unadjusted KR = 0.254, KB = 0.068 luminance interpretation
+     * for RGB conversion.
+     */
+    STANDARD_FILM = 9 << 16,
+
+    /**
+     * SMPTE EG 432-1 and SMPTE RP 431-2. (DCI-P3)
+     * Primaries:       x       y
+     *  green           0.265   0.690
+     *  blue            0.150   0.060
+     *  red             0.680   0.320
+     *  white (D65)     0.3127  0.3290
+     */
+    STANDARD_DCI_P3 = 10 << 16,
+
+    /**
+     * Adobe RGB
+     * Primaries:       x       y
+     *  green           0.210   0.710
+     *  blue            0.150   0.060
+     *  red             0.640   0.330
+     *  white (D65)     0.3127  0.3290
+     */
+    STANDARD_ADOBE_RGB = 11 << 16,
+
+    /**
+     * Transfer aspect
+     *
+     * Transfer characteristics are the opto-electronic transfer characteristic
+     * at the source as a function of linear optical intensity (luminance).
+     *
+     * For digital signals, E corresponds to the recorded value. Normally, the
+     * transfer function is applied in RGB space to each of the R, G and B
+     * components independently. This may result in color shift that can be
+     * minized by applying the transfer function in Lab space only for the L
+     * component. Implementation may apply the transfer function in RGB space
+     * for all pixel formats if desired.
+     */
+    TRANSFER_MASK = 31 << 22,
+
+    /**
+     * Transfer characteristics are unknown or are determined by the
+     * application.
+     *
+     * Implementations should use the following transfer functions:
+     *
+     * For YCbCr formats: use TRANSFER_SMPTE_170M
+     * For RGB formats: use TRANSFER_SRGB
+     *
+     * For all other formats transfer function is undefined, and implementations
+     * should use an appropriate standard for the data represented.
+     */
+    TRANSFER_UNSPECIFIED = 0 << 22,
+
+    /**
+     * Transfer characteristic curve:
+     *  E = L
+     *      L - luminance of image 0 <= L <= 1 for conventional colorimetry
+     *      E - corresponding electrical signal
+     */
+    TRANSFER_LINEAR = 1 << 22,
+
+    /**
+     * Transfer characteristic curve:
+     *
+     * E = 1.055 * L^(1/2.4) - 0.055  for 0.0031308 <= L <= 1
+     *   = 12.92 * L                  for 0 <= L < 0.0031308
+     *     L - luminance of image 0 <= L <= 1 for conventional colorimetry
+     *     E - corresponding electrical signal
+     */
+    TRANSFER_SRGB = 2 << 22,
+
+    /**
+     * BT.601 525, BT.601 625, BT.709, BT.2020
+     *
+     * Transfer characteristic curve:
+     *  E = 1.099 * L ^ 0.45 - 0.099  for 0.018 <= L <= 1
+     *    = 4.500 * L                 for 0 <= L < 0.018
+     *      L - luminance of image 0 <= L <= 1 for conventional colorimetry
+     *      E - corresponding electrical signal
+     */
+    TRANSFER_SMPTE_170M = 3 << 22,
+
+    /**
+     * Assumed display gamma 2.2.
+     *
+     * Transfer characteristic curve:
+     *  E = L ^ (1/2.2)
+     *      L - luminance of image 0 <= L <= 1 for conventional colorimetry
+     *      E - corresponding electrical signal
+     */
+    TRANSFER_GAMMA2_2 = 4 << 22,
+
+    /**
+     *  display gamma 2.6.
+     *
+     * Transfer characteristic curve:
+     *  E = L ^ (1/2.6)
+     *      L - luminance of image 0 <= L <= 1 for conventional colorimetry
+     *      E - corresponding electrical signal
+     */
+    TRANSFER_GAMMA2_6 = 5 << 22,
+
+    /**
+     *  display gamma 2.8.
+     *
+     * Transfer characteristic curve:
+     *  E = L ^ (1/2.8)
+     *      L - luminance of image 0 <= L <= 1 for conventional colorimetry
+     *      E - corresponding electrical signal
+     */
+    TRANSFER_GAMMA2_8 = 6 << 22,
+
+    /**
+     * SMPTE ST 2084 (Dolby Perceptual Quantizer)
+     *
+     * Transfer characteristic curve:
+     *  E = ((c1 + c2 * L^n) / (1 + c3 * L^n)) ^ m
+     *  c1 = c3 - c2 + 1 = 3424 / 4096 = 0.8359375
+     *  c2 = 32 * 2413 / 4096 = 18.8515625
+     *  c3 = 32 * 2392 / 4096 = 18.6875
+     *  m = 128 * 2523 / 4096 = 78.84375
+     *  n = 0.25 * 2610 / 4096 = 0.1593017578125
+     *      L - luminance of image 0 <= L <= 1 for HDR colorimetry.
+     *          L = 1 corresponds to 10000 cd/m2
+     *      E - corresponding electrical signal
+     */
+    TRANSFER_ST2084 = 7 << 22,
+
+    /**
+     * ARIB STD-B67 Hybrid Log Gamma
+     *
+     * Transfer characteristic curve:
+     *  E = r * L^0.5                 for 0 <= L <= 1
+     *    = a * ln(L - b) + c         for 1 < L
+     *  a = 0.17883277
+     *  b = 0.28466892
+     *  c = 0.55991073
+     *  r = 0.5
+     *      L - luminance of image 0 <= L for HDR colorimetry. L = 1 corresponds
+     *          to reference white level of 100 cd/m2
+     *      E - corresponding electrical signal
+     */
+    TRANSFER_HLG = 8 << 22,
+
+    /**
+     * Range aspect
+     *
+     * Defines the range of values corresponding to the unit range of 0-1.
+     * This is defined for YCbCr only, but can be expanded to RGB space.
+     */
+    RANGE_MASK = 7 << 27,
+
+    /**
+     * Range is unknown or are determined by the application.  Implementations
+     * shall use the following suggested ranges:
+     *
+     * All YCbCr formats: limited range.
+     * All RGB or RGBA formats (including RAW and Bayer): full range.
+     * All Y formats: full range
+     *
+     * For all other formats range is undefined, and implementations should use
+     * an appropriate range for the data represented.
+     */
+    RANGE_UNSPECIFIED = 0 << 27,
+
+    /**
+     * Full range uses all values for Y, Cb and Cr from
+     * 0 to 2^b-1, where b is the bit depth of the color format.
+     */
+    RANGE_FULL = 1 << 27,
+
+    /**
+     * Limited range uses values 16/256*2^b to 235/256*2^b for Y, and
+     * 1/16*2^b to 15/16*2^b for Cb, Cr, R, G and B, where b is the bit depth of
+     * the color format.
+     *
+     * E.g. For 8-bit-depth formats:
+     * Luma (Y) samples should range from 16 to 235, inclusive
+     * Chroma (Cb, Cr) samples should range from 16 to 240, inclusive
+     *
+     * For 10-bit-depth formats:
+     * Luma (Y) samples should range from 64 to 940, inclusive
+     * Chroma (Cb, Cr) samples should range from 64 to 960, inclusive
+     */
+    RANGE_LIMITED = 2 << 27,
+
+    /**
+     * Extended range is used for scRGB. Intended for use with
+     * floating point pixel formats. [0.0 - 1.0] is the standard
+     * sRGB space. Values outside the range 0.0 - 1.0 can encode
+     * color outside the sRGB gamut.
+     * Used to blend / merge multiple dataspaces on a single display.
+     */
+    RANGE_EXTENDED = 3 << 27,
+
+    /**
      * scRGB linear encoding:
      *
      * The red, green, and blue components are stored in extended sRGB space,
@@ -103,6 +444,15 @@
     ADATASPACE_BT2020_PQ = 163971072, // STANDARD_BT2020 | TRANSFER_ST2084 | RANGE_FULL
 
     /**
+     * ITU-R Recommendation 2020 (BT.2020)
+     *
+     * Ultra High-definition television
+     *
+     * Use limited range, SMPTE 2084 (PQ) transfer and BT2020 standard
+     */
+    ADATASPACE_BT2020_ITU_PQ = 298188800,  // STANDARD_BT2020 | TRANSFER_ST2084 | RANGE_LIMITED
+
+    /**
      * Adobe RGB
      *
      * Use full range, gamma 2.2 transfer and Adobe RGB primaries
@@ -112,6 +462,33 @@
     ADATASPACE_ADOBE_RGB = 151715840, // STANDARD_ADOBE_RGB | TRANSFER_GAMMA2_2 | RANGE_FULL
 
     /**
+     * JPEG File Interchange Format (JFIF)
+     *
+     * Same model as BT.601-625, but all values (Y, Cb, Cr) range from 0 to 255
+     *
+     * Use full range, SMPTE 170M transfer and BT.601_625 standard.
+     */
+    ADATASPACE_JFIF = 146931712, // STANDARD_BT601_625 | TRANSFER_SMPTE_170M | RANGE_FULL
+
+    /**
+     * ITU-R Recommendation 601 (BT.601) - 525-line
+     *
+     * Standard-definition television, 525 Lines (NTSC)
+     *
+     * Use limited range, SMPTE 170M transfer and BT.601_525 standard.
+     */
+    ADATASPACE_BT601_625 = 281149440, // STANDARD_BT601_625 | TRANSFER_SMPTE_170M | RANGE_LIMITED
+
+    /**
+     * ITU-R Recommendation 709 (BT.709)
+     *
+     * High-definition television
+     *
+     * Use limited range, SMPTE 170M transfer and BT.709 standard.
+     */
+    ADATASPACE_BT601_525 = 281280512, // STANDARD_BT601_525 | TRANSFER_SMPTE_170M | RANGE_LIMITED
+
+    /**
      * ITU-R Recommendation 2020 (BT.2020)
      *
      * Ultra High-definition television
@@ -151,8 +528,38 @@
      * components.
      */
     ADATASPACE_SRGB_LINEAR = 138477568, // STANDARD_BT709 | TRANSFER_LINEAR | RANGE_FULL
+
+    /**
+     * Hybrid Log Gamma encoding:
+     *
+     * Use full range, hybrid log gamma transfer and BT2020 standard.
+     */
+    ADATASPACE_BT2020_HLG = 168165376, // STANDARD_BT2020 | TRANSFER_HLG | RANGE_FULL
+
+    /**
+     * ITU Hybrid Log Gamma encoding:
+     *
+     * Use limited range, hybrid log gamma transfer and BT2020 standard.
+     */
+    ADATASPACE_BT2020_ITU_HLG = 302383104, // STANDARD_BT2020 | TRANSFER_HLG | RANGE_LIMITED
+
+    /**
+     * Depth:
+     *
+     * This value is valid with formats HAL_PIXEL_FORMAT_Y16 and HAL_PIXEL_FORMAT_BLOB.
+     */
+    DEPTH = 4096,
+
+    /**
+     * ISO 16684-1:2011(E) Dynamic Depth:
+     *
+     * Embedded depth metadata following the dynamic depth specification.
+     */
+    DYNAMIC_DEPTH = 4098
 };
 
 __END_DECLS
 
 #endif // ANDROID_DATA_SPACE_H
+
+/** @} */
diff --git a/libs/nativewindow/include/android/hardware_buffer.h b/libs/nativewindow/include/android/hardware_buffer.h
index 78c56d9..c35507b 100644
--- a/libs/nativewindow/include/android/hardware_buffer.h
+++ b/libs/nativewindow/include/android/hardware_buffer.h
@@ -166,6 +166,13 @@
      * cube-maps or multi-layered textures.
      */
     AHARDWAREBUFFER_FORMAT_YCbCr_P010               = 0x36,
+
+    /**
+     * Corresponding formats:
+     *   Vulkan: VK_FORMAT_R8_UNORM
+     *   OpenGL ES: GR_GL_R8
+     */
+    AHARDWAREBUFFER_FORMAT_R8_UNORM                 = 0x38,
 };
 
 /**
@@ -564,6 +571,7 @@
                                    int32_t* _Nonnull outBytesPerPixel,
                                    int32_t* _Nonnull outBytesPerStride) __INTRODUCED_IN(29);
 
+
 /**
  * Get the system wide unique id for an AHardwareBuffer.
  *
diff --git a/libs/nativewindow/include/system/window.h b/libs/nativewindow/include/system/window.h
index 7f01135..a319769 100644
--- a/libs/nativewindow/include/system/window.h
+++ b/libs/nativewindow/include/system/window.h
@@ -1025,10 +1025,11 @@
 }
 
 static inline int native_window_set_frame_timeline_info(struct ANativeWindow* window,
-                                                         int64_t frameTimelineVsyncId,
-                                                         int32_t inputEventId) {
-    return window->perform(window, NATIVE_WINDOW_SET_FRAME_TIMELINE_INFO,
-                           frameTimelineVsyncId, inputEventId);
+                                                        int64_t frameTimelineVsyncId,
+                                                        int32_t inputEventId,
+                                                        int64_t startTimeNanos) {
+    return window->perform(window, NATIVE_WINDOW_SET_FRAME_TIMELINE_INFO, frameTimelineVsyncId,
+                           inputEventId, startTimeNanos);
 }
 
 // ------------------------------------------------------------------------------------------------
@@ -1089,10 +1090,13 @@
 /**
  * Retrieves an identifier for the next frame to be queued by this window.
  *
- * \return the next frame id.
+ * Frame ids start at 1 and are incremented on each new frame until the underlying surface changes,
+ * in which case the frame id is reset to 1.
+ *
+ * \return the next frame id (0 being uninitialized).
  */
-static inline int64_t ANativeWindow_getNextFrameId(ANativeWindow* window) {
-    int64_t value;
+static inline uint64_t ANativeWindow_getNextFrameId(ANativeWindow* window) {
+    uint64_t value;
     window->perform(window, NATIVE_WINDOW_GET_NEXT_FRAME_ID, &value);
     return value;
 }
diff --git a/libs/renderengine/Android.bp b/libs/renderengine/Android.bp
index 570c7bc..84e84dd 100644
--- a/libs/renderengine/Android.bp
+++ b/libs/renderengine/Android.bp
@@ -40,6 +40,11 @@
         "libui",
         "libutils",
     ],
+
+    static_libs: [
+        "libshaders",
+        "libtonemap",
+    ],
     local_include_dirs: ["include"],
     export_include_dirs: ["include"],
 }
@@ -94,8 +99,10 @@
         "skia/debug/SkiaCapture.cpp",
         "skia/debug/SkiaMemoryReporter.cpp",
         "skia/filters/BlurFilter.cpp",
+        "skia/filters/GaussianBlurFilter.cpp",
+        "skia/filters/KawaseBlurFilter.cpp",
         "skia/filters/LinearEffect.cpp",
-        "skia/filters/StretchShaderFactory.cpp"
+        "skia/filters/StretchShaderFactory.cpp",
     ],
 }
 
@@ -115,6 +122,9 @@
         ":librenderengine_threaded_sources",
         ":librenderengine_skia_sources",
     ],
+    header_libs: [
+        "libtonemap_headers",
+    ],
     include_dirs: [
         "external/skia/src/gpu",
     ],
diff --git a/libs/renderengine/ExternalTexture.cpp b/libs/renderengine/ExternalTexture.cpp
index eabff58..84771c0 100644
--- a/libs/renderengine/ExternalTexture.cpp
+++ b/libs/renderengine/ExternalTexture.cpp
@@ -14,30 +14,32 @@
  * limitations under the License.
  */
 
-#include <renderengine/ExternalTexture.h>
 #include <renderengine/RenderEngine.h>
+#include <renderengine/impl/ExternalTexture.h>
 #include <ui/GraphicBuffer.h>
 
 #include "log/log_main.h"
 
-namespace android::renderengine {
+namespace android::renderengine::impl {
 
-ExternalTexture::ExternalTexture(const sp<GraphicBuffer>& buffer, RenderEngine& renderEngine,
-                                 uint32_t usage)
+ExternalTexture::ExternalTexture(const sp<GraphicBuffer>& buffer,
+                                 renderengine::RenderEngine& renderEngine, uint32_t usage)
       : mBuffer(buffer), mRenderEngine(renderEngine) {
     LOG_ALWAYS_FATAL_IF(buffer == nullptr,
                         "Attempted to bind a null buffer to an external texture!");
     // GLESRenderEngine has a separate texture cache for output buffers,
-    if (usage == Usage::WRITEABLE &&
-        (mRenderEngine.getRenderEngineType() == RenderEngine::RenderEngineType::GLES ||
-         mRenderEngine.getRenderEngineType() == RenderEngine::RenderEngineType::THREADED)) {
+    if (usage == WRITEABLE &&
+        (mRenderEngine.getRenderEngineType() ==
+                 renderengine::RenderEngine::RenderEngineType::GLES ||
+         mRenderEngine.getRenderEngineType() ==
+                 renderengine::RenderEngine::RenderEngineType::THREADED)) {
         return;
     }
-    mRenderEngine.mapExternalTextureBuffer(mBuffer, usage & Usage::WRITEABLE);
+    mRenderEngine.mapExternalTextureBuffer(mBuffer, usage & WRITEABLE);
 }
 
 ExternalTexture::~ExternalTexture() {
     mRenderEngine.unmapExternalTextureBuffer(mBuffer);
 }
 
-} // namespace android::renderengine
+} // namespace android::renderengine::impl
diff --git a/libs/renderengine/RenderEngine.cpp b/libs/renderengine/RenderEngine.cpp
index 9e466b6..c7ad058 100644
--- a/libs/renderengine/RenderEngine.cpp
+++ b/libs/renderengine/RenderEngine.cpp
@@ -26,23 +26,7 @@
 namespace android {
 namespace renderengine {
 
-std::unique_ptr<RenderEngine> RenderEngine::create(RenderEngineCreationArgs args) {
-    // Keep the ability to override by PROPERTIES:
-    char prop[PROPERTY_VALUE_MAX];
-    property_get(PROPERTY_DEBUG_RENDERENGINE_BACKEND, prop, "");
-    if (strcmp(prop, "gles") == 0) {
-        args.renderEngineType = RenderEngineType::GLES;
-    }
-    if (strcmp(prop, "threaded") == 0) {
-        args.renderEngineType = RenderEngineType::THREADED;
-    }
-    if (strcmp(prop, "skiagl") == 0) {
-        args.renderEngineType = RenderEngineType::SKIA_GL;
-    }
-    if (strcmp(prop, "skiaglthreaded") == 0) {
-        args.renderEngineType = RenderEngineType::SKIA_GL_THREADED;
-    }
-
+std::unique_ptr<RenderEngine> RenderEngine::create(const RenderEngineCreationArgs& args) {
     switch (args.renderEngineType) {
         case RenderEngineType::THREADED:
             ALOGD("Threaded RenderEngine with GLES Backend");
@@ -79,5 +63,16 @@
                         "output buffer not gpu writeable");
 }
 
+std::future<RenderEngineResult> RenderEngine::drawLayers(
+        const DisplaySettings& display, const std::vector<LayerSettings>& layers,
+        const std::shared_ptr<ExternalTexture>& buffer, const bool useFramebufferCache,
+        base::unique_fd&& bufferFence) {
+    const auto resultPromise = std::make_shared<std::promise<RenderEngineResult>>();
+    std::future<RenderEngineResult> resultFuture = resultPromise->get_future();
+    drawLayersInternal(std::move(resultPromise), display, layers, buffer, useFramebufferCache,
+                       std::move(bufferFence));
+    return resultFuture;
+}
+
 } // namespace renderengine
 } // namespace android
diff --git a/libs/renderengine/benchmark/Android.bp b/libs/renderengine/benchmark/Android.bp
new file mode 100644
index 0000000..471159f
--- /dev/null
+++ b/libs/renderengine/benchmark/Android.bp
@@ -0,0 +1,59 @@
+// 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.
+
+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 {
+    name: "librenderengine_bench",
+    defaults: [
+        "skia_deps",
+        "surfaceflinger_defaults",
+    ],
+    srcs: [
+        "main.cpp",
+        "Codec.cpp",
+        "Flags.cpp",
+        "RenderEngineBench.cpp",
+    ],
+    static_libs: [
+        "librenderengine",
+        "libshaders",
+        "libtonemap",
+    ],
+    cflags: [
+        "-DLOG_TAG=\"RenderEngineBench\"",
+    ],
+
+    shared_libs: [
+        "libbase",
+        "libcutils",
+        "libjnigraphics",
+        "libgui",
+        "liblog",
+        "libnativewindow",
+        "libprocessgroup",
+        "libsync",
+        "libui",
+        "libutils",
+    ],
+
+    data: ["resources/*"],
+}
diff --git a/libs/renderengine/benchmark/Codec.cpp b/libs/renderengine/benchmark/Codec.cpp
new file mode 100644
index 0000000..80e4fc4
--- /dev/null
+++ b/libs/renderengine/benchmark/Codec.cpp
@@ -0,0 +1,136 @@
+/*
+ * 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.
+ */
+
+#include <RenderEngineBench.h>
+#include <android/bitmap.h>
+#include <android/data_space.h>
+#include <android/imagedecoder.h>
+#include <log/log.h>
+#include <renderengine/ExternalTexture.h>
+#include <renderengine/RenderEngine.h>
+#include <sys/types.h>
+
+using namespace android;
+using namespace android::renderengine;
+
+namespace {
+struct DecoderDeleter {
+    void operator()(AImageDecoder* decoder) { AImageDecoder_delete(decoder); }
+};
+
+using AutoDecoderDeleter = std::unique_ptr<AImageDecoder, DecoderDeleter>;
+
+bool ok(int aImageDecoderResult, const char* path, const char* method) {
+    if (aImageDecoderResult == ANDROID_IMAGE_DECODER_SUCCESS) {
+        return true;
+    }
+
+    ALOGE("Failed AImageDecoder_%s on '%s' with error '%s'", method, path,
+          AImageDecoder_resultToString(aImageDecoderResult));
+    return false;
+}
+} // namespace
+
+namespace renderenginebench {
+
+void decode(const char* path, const sp<GraphicBuffer>& buffer) {
+    base::unique_fd fd{open(path, O_RDONLY)};
+    if (fd.get() < 0) {
+        ALOGE("Failed to open %s", path);
+        return;
+    }
+
+    AImageDecoder* decoder{nullptr};
+    auto result = AImageDecoder_createFromFd(fd.get(), &decoder);
+    if (!ok(result, path, "createFromFd")) {
+        return;
+    }
+
+    AutoDecoderDeleter deleter(decoder);
+
+    LOG_ALWAYS_FATAL_IF(buffer->getWidth() <= 0 || buffer->getHeight() <= 0,
+                        "Impossible buffer size!");
+    auto width = static_cast<int32_t>(buffer->getWidth());
+    auto height = static_cast<int32_t>(buffer->getHeight());
+    result = AImageDecoder_setTargetSize(decoder, width, height);
+    if (!ok(result, path, "setTargetSize")) {
+        return;
+    }
+
+    void* pixels{nullptr};
+    int32_t stride{0};
+    if (auto status = buffer->lock(GRALLOC_USAGE_SW_WRITE_OFTEN, &pixels,
+                                   nullptr /*outBytesPerPixel*/, &stride);
+        status < 0) {
+        ALOGE("Failed to lock pixels!");
+        return;
+    }
+
+    result = AImageDecoder_decodeImage(decoder, pixels, static_cast<size_t>(stride),
+                                       static_cast<size_t>(stride * height));
+    if (auto status = buffer->unlock(); status < 0) {
+        ALOGE("Failed to unlock pixels!");
+    }
+
+    // For the side effect of logging.
+    (void)ok(result, path, "decodeImage");
+}
+
+void encodeToJpeg(const char* path, const sp<GraphicBuffer>& buffer) {
+    base::unique_fd fd{open(path, O_WRONLY | O_CREAT, S_IWUSR)};
+    if (fd.get() < 0) {
+        ALOGE("Failed to open %s", path);
+        return;
+    }
+
+    void* pixels{nullptr};
+    int32_t stride{0};
+    if (auto status = buffer->lock(GRALLOC_USAGE_SW_READ_OFTEN, &pixels,
+                                   nullptr /*outBytesPerPixel*/, &stride);
+        status < 0) {
+        ALOGE("Failed to lock pixels!");
+        return;
+    }
+
+    AndroidBitmapInfo info{
+            .width = buffer->getWidth(),
+            .height = buffer->getHeight(),
+            .stride = static_cast<uint32_t>(stride),
+            .format = ANDROID_BITMAP_FORMAT_RGBA_8888,
+            .flags = ANDROID_BITMAP_FLAGS_ALPHA_OPAQUE,
+    };
+    int result = AndroidBitmap_compress(&info, ADATASPACE_SRGB, pixels,
+                                        ANDROID_BITMAP_COMPRESS_FORMAT_JPEG, 80, &fd,
+                                        [](void* fdPtr, const void* data, size_t size) -> bool {
+                                            const ssize_t bytesWritten =
+                                                    write(reinterpret_cast<base::unique_fd*>(fdPtr)
+                                                                  ->get(),
+                                                          data, size);
+                                            return bytesWritten > 0 &&
+                                                    static_cast<size_t>(bytesWritten) == size;
+                                        });
+    if (result == ANDROID_BITMAP_RESULT_SUCCESS) {
+        ALOGD("Successfully encoded to '%s'", path);
+    } else {
+        ALOGE("Failed to encode to %s with error %d", path, result);
+    }
+
+    if (auto status = buffer->unlock(); status < 0) {
+        ALOGE("Failed to unlock pixels!");
+    }
+}
+
+} // namespace renderenginebench
diff --git a/libs/renderengine/benchmark/Flags.cpp b/libs/renderengine/benchmark/Flags.cpp
new file mode 100644
index 0000000..c5d5156
--- /dev/null
+++ b/libs/renderengine/benchmark/Flags.cpp
@@ -0,0 +1,48 @@
+/*
+ * 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.
+ */
+
+#include <log/log.h>
+#include <stdio.h>
+#include <string.h>
+
+namespace {
+bool gSave = false;
+}
+
+namespace renderenginebench {
+
+void parseFlagsForHelp(int argc, char** argv) {
+    for (int i = 0; i < argc; i++) {
+        if (!strcmp(argv[i], "--help")) {
+            printf("RenderEngineBench-specific flags:\n");
+            printf("[--save]: Save the output to the device to confirm drawing result.\n");
+            break;
+        }
+    }
+}
+
+void parseFlags(int argc, char** argv) {
+    for (int i = 0; i < argc; i++) {
+        if (!strcmp(argv[i], "--save")) {
+            gSave = true;
+        }
+    }
+}
+
+bool save() {
+    return gSave;
+}
+} // namespace renderenginebench
diff --git a/libs/renderengine/benchmark/RenderEngineBench.cpp b/libs/renderengine/benchmark/RenderEngineBench.cpp
new file mode 100644
index 0000000..ead97cf
--- /dev/null
+++ b/libs/renderengine/benchmark/RenderEngineBench.cpp
@@ -0,0 +1,260 @@
+/*
+ * 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.
+ */
+
+#include <RenderEngineBench.h>
+#include <android-base/file.h>
+#include <benchmark/benchmark.h>
+#include <gui/SurfaceComposerClient.h>
+#include <log/log.h>
+#include <renderengine/ExternalTexture.h>
+#include <renderengine/LayerSettings.h>
+#include <renderengine/RenderEngine.h>
+#include <renderengine/impl/ExternalTexture.h>
+
+#include <mutex>
+
+using namespace android;
+using namespace android::renderengine;
+
+///////////////////////////////////////////////////////////////////////////////
+//  Helpers for Benchmark::Apply
+///////////////////////////////////////////////////////////////////////////////
+
+std::string RenderEngineTypeName(RenderEngine::RenderEngineType type) {
+    switch (type) {
+        case RenderEngine::RenderEngineType::SKIA_GL_THREADED:
+            return "skiaglthreaded";
+        case RenderEngine::RenderEngineType::SKIA_GL:
+            return "skiagl";
+        case RenderEngine::RenderEngineType::GLES:
+        case RenderEngine::RenderEngineType::THREADED:
+            LOG_ALWAYS_FATAL("GLESRenderEngine is deprecated - why time it?");
+            return "unused";
+    }
+}
+
+/**
+ * Passed (indirectly - see RunSkiaGLThreaded) to Benchmark::Apply to create a
+ * Benchmark which specifies which RenderEngineType it uses.
+ *
+ * This simplifies calling ->Arg(type)->Arg(type) and provides strings to make
+ * it obvious which version is being run.
+ *
+ * @param b The benchmark family
+ * @param type The type of RenderEngine to use.
+ */
+static void AddRenderEngineType(benchmark::internal::Benchmark* b,
+                                RenderEngine::RenderEngineType type) {
+    b->Arg(static_cast<int64_t>(type));
+    b->ArgName(RenderEngineTypeName(type));
+}
+
+/**
+ * Run a benchmark once using SKIA_GL_THREADED.
+ */
+static void RunSkiaGLThreaded(benchmark::internal::Benchmark* b) {
+    AddRenderEngineType(b, RenderEngine::RenderEngineType::SKIA_GL_THREADED);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+//  Helpers for calling drawLayers
+///////////////////////////////////////////////////////////////////////////////
+
+std::pair<uint32_t, uint32_t> getDisplaySize() {
+    // These will be retrieved from a ui::Size, which stores int32_t, but they will be passed
+    // to GraphicBuffer, which wants uint32_t.
+    static uint32_t width, height;
+    std::once_flag once;
+    std::call_once(once, []() {
+        auto surfaceComposerClient = SurfaceComposerClient::getDefault();
+        auto displayToken = surfaceComposerClient->getInternalDisplayToken();
+        ui::DisplayMode displayMode;
+        if (surfaceComposerClient->getActiveDisplayMode(displayToken, &displayMode) < 0) {
+            LOG_ALWAYS_FATAL("Failed to get active display mode!");
+        }
+        auto w = displayMode.resolution.width;
+        auto h = displayMode.resolution.height;
+        LOG_ALWAYS_FATAL_IF(w <= 0 || h <= 0, "Invalid display size!");
+        width = static_cast<uint32_t>(w);
+        height = static_cast<uint32_t>(h);
+    });
+    return std::pair<uint32_t, uint32_t>(width, height);
+}
+
+// This value doesn't matter, as it's not read. TODO(b/199918329): Once we remove
+// GLESRenderEngine we can remove this, too.
+static constexpr const bool kUseFrameBufferCache = false;
+
+static std::unique_ptr<RenderEngine> createRenderEngine(RenderEngine::RenderEngineType type) {
+    auto args = RenderEngineCreationArgs::Builder()
+                        .setPixelFormat(static_cast<int>(ui::PixelFormat::RGBA_8888))
+                        .setImageCacheSize(1)
+                        .setEnableProtectedContext(true)
+                        .setPrecacheToneMapperShaderOnly(false)
+                        .setSupportsBackgroundBlur(true)
+                        .setContextPriority(RenderEngine::ContextPriority::REALTIME)
+                        .setRenderEngineType(type)
+                        .setUseColorManagerment(true)
+                        .build();
+    return RenderEngine::create(args);
+}
+
+static std::shared_ptr<ExternalTexture> allocateBuffer(RenderEngine& re, uint32_t width,
+                                                       uint32_t height,
+                                                       uint64_t extraUsageFlags = 0,
+                                                       std::string name = "output") {
+    return std::make_shared<
+            impl::ExternalTexture>(new GraphicBuffer(width, height, HAL_PIXEL_FORMAT_RGBA_8888, 1,
+                                                     GRALLOC_USAGE_HW_RENDER |
+                                                             GRALLOC_USAGE_HW_TEXTURE |
+                                                             extraUsageFlags,
+                                                     std::move(name)),
+                                   re,
+                                   impl::ExternalTexture::Usage::READABLE |
+                                           impl::ExternalTexture::Usage::WRITEABLE);
+}
+
+static std::shared_ptr<ExternalTexture> copyBuffer(RenderEngine& re,
+                                                   std::shared_ptr<ExternalTexture> original,
+                                                   uint64_t extraUsageFlags, std::string name) {
+    const uint32_t width = original->getBuffer()->getWidth();
+    const uint32_t height = original->getBuffer()->getHeight();
+    auto texture = allocateBuffer(re, width, height, extraUsageFlags, name);
+
+    const Rect displayRect(0, 0, static_cast<int32_t>(width), static_cast<int32_t>(height));
+    DisplaySettings display{
+            .physicalDisplay = displayRect,
+            .clip = displayRect,
+            .maxLuminance = 500,
+    };
+
+    const FloatRect layerRect(0, 0, width, height);
+    LayerSettings layer{
+            .geometry =
+                    Geometry{
+                            .boundaries = layerRect,
+                    },
+            .source =
+                    PixelSource{
+                            .buffer =
+                                    Buffer{
+                                            .buffer = original,
+                                    },
+                    },
+            .alpha = half(1.0f),
+    };
+    auto layers = std::vector<LayerSettings>{layer};
+
+    auto [status, drawFence] =
+            re.drawLayers(display, layers, texture, kUseFrameBufferCache, base::unique_fd()).get();
+    sp<Fence> waitFence = sp<Fence>::make(std::move(drawFence));
+    waitFence->waitForever(LOG_TAG);
+    return texture;
+}
+
+/**
+ * Helper for timing calls to drawLayers.
+ *
+ * Caller needs to create RenderEngine and the LayerSettings, and this takes
+ * care of setting up the display, starting and stopping the timer, calling
+ * drawLayers, and saving (if --save is used).
+ *
+ * This times both the CPU and GPU work initiated by drawLayers. All work done
+ * outside of the for loop is excluded from the timing measurements.
+ */
+static void benchDrawLayers(RenderEngine& re, const std::vector<LayerSettings>& layers,
+                            benchmark::State& benchState, const char* saveFileName) {
+    auto [width, height] = getDisplaySize();
+    auto outputBuffer = allocateBuffer(re, width, height);
+
+    const Rect displayRect(0, 0, static_cast<int32_t>(width), static_cast<int32_t>(height));
+    DisplaySettings display{
+            .physicalDisplay = displayRect,
+            .clip = displayRect,
+            .maxLuminance = 500,
+    };
+
+    // This loop starts and stops the timer.
+    for (auto _ : benchState) {
+        auto [status, drawFence] = re.drawLayers(display, layers, outputBuffer,
+                                                 kUseFrameBufferCache, base::unique_fd())
+                                           .get();
+        sp<Fence> waitFence = sp<Fence>::make(std::move(drawFence));
+        waitFence->waitForever(LOG_TAG);
+    }
+
+    if (renderenginebench::save() && saveFileName) {
+        // Copy to a CPU-accessible buffer so we can encode it.
+        outputBuffer = copyBuffer(re, outputBuffer, GRALLOC_USAGE_SW_READ_OFTEN, "to_encode");
+
+        std::string outFile = base::GetExecutableDirectory();
+        outFile.append("/");
+        outFile.append(saveFileName);
+        outFile.append(".jpg");
+        renderenginebench::encodeToJpeg(outFile.c_str(), outputBuffer->getBuffer());
+    }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+//  Benchmarks
+///////////////////////////////////////////////////////////////////////////////
+
+void BM_blur(benchmark::State& benchState) {
+    auto re = createRenderEngine(static_cast<RenderEngine::RenderEngineType>(benchState.range()));
+
+    // Initially use cpu access so we can decode into it with AImageDecoder.
+    auto [width, height] = getDisplaySize();
+    auto srcBuffer =
+            allocateBuffer(*re, width, height, GRALLOC_USAGE_SW_WRITE_OFTEN, "decoded_source");
+    {
+        std::string srcImage = base::GetExecutableDirectory();
+        srcImage.append("/resources/homescreen.png");
+        renderenginebench::decode(srcImage.c_str(), srcBuffer->getBuffer());
+
+        // Now copy into GPU-only buffer for more realistic timing.
+        srcBuffer = copyBuffer(*re, srcBuffer, 0, "source");
+    }
+
+    const FloatRect layerRect(0, 0, width, height);
+    LayerSettings layer{
+            .geometry =
+                    Geometry{
+                            .boundaries = layerRect,
+                    },
+            .source =
+                    PixelSource{
+                            .buffer =
+                                    Buffer{
+                                            .buffer = srcBuffer,
+                                    },
+                    },
+            .alpha = half(1.0f),
+    };
+    LayerSettings blurLayer{
+            .geometry =
+                    Geometry{
+                            .boundaries = layerRect,
+                    },
+            .alpha = half(1.0f),
+            .skipContentDraw = true,
+            .backgroundBlurRadius = 60,
+    };
+
+    auto layers = std::vector<LayerSettings>{layer, blurLayer};
+    benchDrawLayers(*re, layers, benchState, "blurred");
+}
+
+BENCHMARK(BM_blur)->Apply(RunSkiaGLThreaded);
diff --git a/libs/renderengine/benchmark/RenderEngineBench.h b/libs/renderengine/benchmark/RenderEngineBench.h
new file mode 100644
index 0000000..1a25d77
--- /dev/null
+++ b/libs/renderengine/benchmark/RenderEngineBench.h
@@ -0,0 +1,65 @@
+/*
+ * 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.
+ */
+
+#include <ui/GraphicBuffer.h>
+
+using namespace android;
+
+/**
+ * Utilities for running benchmarks.
+ */
+namespace renderenginebench {
+/**
+ * Parse RenderEngineBench-specific flags from the command line.
+ *
+ * --save Save the output buffer to a file to verify that it drew as
+ *  expected.
+ */
+void parseFlags(int argc, char** argv);
+
+/**
+ * Parse flags for '--help'
+ */
+void parseFlagsForHelp(int argc, char** argv);
+
+/**
+ * Whether to save the drawing result to a file.
+ *
+ * True if --save was used on the command line.
+ */
+bool save();
+
+/**
+ * Decode the image at 'path' into 'buffer'.
+ *
+ * Currently only used for debugging. The image will be scaled to fit the
+ * buffer if necessary.
+ *
+ * This assumes the buffer matches ANDROID_BITMAP_FORMAT_RGBA_8888.
+ *
+ * @param path Relative to the directory holding the executable.
+ */
+void decode(const char* path, const sp<GraphicBuffer>& buffer);
+
+/**
+ * Encode the buffer to a jpeg.
+ *
+ * This assumes the buffer matches ANDROID_BITMAP_FORMAT_RGBA_8888.
+ *
+ * @param path Relative to the directory holding the executable.
+ */
+void encodeToJpeg(const char* path, const sp<GraphicBuffer>& buffer);
+} // namespace renderenginebench
diff --git a/libs/renderengine/benchmark/main.cpp b/libs/renderengine/benchmark/main.cpp
new file mode 100644
index 0000000..7a62853
--- /dev/null
+++ b/libs/renderengine/benchmark/main.cpp
@@ -0,0 +1,32 @@
+/*
+ * 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.
+ */
+
+#include <RenderEngineBench.h>
+#include <benchmark/benchmark.h>
+
+int main(int argc, char** argv) {
+    // Initialize will exit if it sees '--help', so check for it and print info
+    // about our flags first.
+    renderenginebench::parseFlagsForHelp(argc, argv);
+    benchmark::Initialize(&argc, argv);
+
+    // Calling this separately from parseFlagsForHelp prevents collisions with
+    // google-benchmark's flags, since Initialize will consume and remove flags
+    // it recognizes.
+    renderenginebench::parseFlags(argc, argv);
+    benchmark::RunSpecifiedBenchmarks();
+    return 0;
+}
diff --git a/libs/renderengine/benchmark/resources/homescreen.png b/libs/renderengine/benchmark/resources/homescreen.png
new file mode 100644
index 0000000..997b72d
--- /dev/null
+++ b/libs/renderengine/benchmark/resources/homescreen.png
Binary files differ
diff --git a/libs/renderengine/gl/GLESRenderEngine.cpp b/libs/renderengine/gl/GLESRenderEngine.cpp
index 467f848..22dd866 100644
--- a/libs/renderengine/gl/GLESRenderEngine.cpp
+++ b/libs/renderengine/gl/GLESRenderEngine.cpp
@@ -1078,15 +1078,16 @@
     return image;
 }
 
-status_t GLESRenderEngine::drawLayers(const DisplaySettings& display,
-                                      const std::vector<const LayerSettings*>& layers,
-                                      const std::shared_ptr<ExternalTexture>& buffer,
-                                      const bool useFramebufferCache, base::unique_fd&& bufferFence,
-                                      base::unique_fd* drawFence) {
+void GLESRenderEngine::drawLayersInternal(
+        const std::shared_ptr<std::promise<RenderEngineResult>>&& resultPromise,
+        const DisplaySettings& display, const std::vector<LayerSettings>& layers,
+        const std::shared_ptr<ExternalTexture>& buffer, const bool useFramebufferCache,
+        base::unique_fd&& bufferFence) {
     ATRACE_CALL();
     if (layers.empty()) {
         ALOGV("Drawing empty layer stack");
-        return NO_ERROR;
+        resultPromise->set_value({NO_ERROR, base::unique_fd()});
+        return;
     }
 
     if (bufferFence.get() >= 0) {
@@ -1100,7 +1101,8 @@
 
     if (buffer == nullptr) {
         ALOGE("No output buffer provided. Aborting GPU composition.");
-        return BAD_VALUE;
+        resultPromise->set_value({BAD_VALUE, base::unique_fd()});
+        return;
     }
 
     validateOutputBufferUsage(buffer->getBuffer());
@@ -1108,10 +1110,10 @@
     std::unique_ptr<BindNativeBufferAsFramebuffer> fbo;
     // Gathering layers that requested blur, we'll need them to decide when to render to an
     // offscreen buffer, and when to render to the native buffer.
-    std::deque<const LayerSettings*> blurLayers;
+    std::deque<const LayerSettings> blurLayers;
     if (CC_LIKELY(mBlurFilter != nullptr)) {
-        for (auto layer : layers) {
-            if (layer->backgroundBlurRadius > 0) {
+        for (const auto& layer : layers) {
+            if (layer.backgroundBlurRadius > 0) {
                 blurLayers.push_back(layer);
             }
         }
@@ -1128,18 +1130,20 @@
             ALOGE("Failed to bind framebuffer! Aborting GPU composition for buffer (%p).",
                   buffer->getBuffer()->handle);
             checkErrors();
-            return fbo->getStatus();
+            resultPromise->set_value({fbo->getStatus(), base::unique_fd()});
+            return;
         }
         setViewportAndProjection(display.physicalDisplay, display.clip);
     } else {
         setViewportAndProjection(display.physicalDisplay, display.clip);
         auto status =
-                mBlurFilter->setAsDrawTarget(display, blurLayers.front()->backgroundBlurRadius);
+                mBlurFilter->setAsDrawTarget(display, blurLayers.front().backgroundBlurRadius);
         if (status != NO_ERROR) {
             ALOGE("Failed to prepare blur filter! Aborting GPU composition for buffer (%p).",
                   buffer->getBuffer()->handle);
             checkErrors();
-            return status;
+            resultPromise->set_value({status, base::unique_fd()});
+            return;
         }
     }
 
@@ -1156,10 +1160,6 @@
 
     const mat4 projectionMatrix =
             ui::Transform(display.orientation).asMatrix4() * mState.projectionMatrix;
-    if (!display.clearRegion.isEmpty()) {
-        glDisable(GL_BLEND);
-        fillRegionWithColor(display.clearRegion, 0.0, 0.0, 0.0, 1.0);
-    }
 
     Mesh mesh = Mesh::Builder()
                         .setPrimitive(Mesh::TRIANGLE_FAN)
@@ -1167,7 +1167,7 @@
                         .setTexCoords(2 /* size */)
                         .setCropCoords(2 /* size */)
                         .build();
-    for (auto const layer : layers) {
+    for (const auto& layer : layers) {
         if (blurLayers.size() > 0 && blurLayers.front() == layer) {
             blurLayers.pop_front();
 
@@ -1176,7 +1176,8 @@
                 ALOGE("Failed to render blur effect! Aborting GPU composition for buffer (%p).",
                       buffer->getBuffer()->handle);
                 checkErrors("Can't render first blur pass");
-                return status;
+                resultPromise->set_value({status, base::unique_fd()});
+                return;
             }
 
             if (blurLayers.size() == 0) {
@@ -1192,13 +1193,14 @@
                 // There's still something else to blur, so let's keep rendering to our FBO
                 // instead of to the display.
                 status = mBlurFilter->setAsDrawTarget(display,
-                                                      blurLayers.front()->backgroundBlurRadius);
+                                                      blurLayers.front().backgroundBlurRadius);
             }
             if (status != NO_ERROR) {
                 ALOGE("Failed to bind framebuffer! Aborting GPU composition for buffer (%p).",
                       buffer->getBuffer()->handle);
                 checkErrors("Can't bind native framebuffer");
-                return status;
+                resultPromise->set_value({status, base::unique_fd()});
+                return;
             }
 
             status = mBlurFilter->render(blurLayersSize > 1);
@@ -1206,47 +1208,48 @@
                 ALOGE("Failed to render blur effect! Aborting GPU composition for buffer (%p).",
                       buffer->getBuffer()->handle);
                 checkErrors("Can't render blur filter");
-                return status;
+                resultPromise->set_value({status, base::unique_fd()});
+                return;
             }
         }
 
         // Ensure luminance is at least 100 nits to avoid div-by-zero
-        const float maxLuminance = std::max(100.f, layer->source.buffer.maxLuminanceNits);
+        const float maxLuminance = std::max(100.f, layer.source.buffer.maxLuminanceNits);
         mState.maxMasteringLuminance = maxLuminance;
         mState.maxContentLuminance = maxLuminance;
-        mState.projectionMatrix = projectionMatrix * layer->geometry.positionTransform;
+        mState.projectionMatrix = projectionMatrix * layer.geometry.positionTransform;
 
-        const FloatRect bounds = layer->geometry.boundaries;
+        const FloatRect bounds = layer.geometry.boundaries;
         Mesh::VertexArray<vec2> position(mesh.getPositionArray<vec2>());
         position[0] = vec2(bounds.left, bounds.top);
         position[1] = vec2(bounds.left, bounds.bottom);
         position[2] = vec2(bounds.right, bounds.bottom);
         position[3] = vec2(bounds.right, bounds.top);
 
-        setupLayerCropping(*layer, mesh);
-        setColorTransform(layer->colorTransform);
+        setupLayerCropping(layer, mesh);
+        setColorTransform(layer.colorTransform);
 
         bool usePremultipliedAlpha = true;
         bool disableTexture = true;
         bool isOpaque = false;
-        if (layer->source.buffer.buffer != nullptr) {
+        if (layer.source.buffer.buffer != nullptr) {
             disableTexture = false;
-            isOpaque = layer->source.buffer.isOpaque;
+            isOpaque = layer.source.buffer.isOpaque;
 
-            sp<GraphicBuffer> gBuf = layer->source.buffer.buffer->getBuffer();
+            sp<GraphicBuffer> gBuf = layer.source.buffer.buffer->getBuffer();
             validateInputBufferUsage(gBuf);
-            bindExternalTextureBuffer(layer->source.buffer.textureName, gBuf,
-                                      layer->source.buffer.fence);
+            bindExternalTextureBuffer(layer.source.buffer.textureName, gBuf,
+                                      layer.source.buffer.fence);
 
-            usePremultipliedAlpha = layer->source.buffer.usePremultipliedAlpha;
-            Texture texture(Texture::TEXTURE_EXTERNAL, layer->source.buffer.textureName);
-            mat4 texMatrix = layer->source.buffer.textureTransform;
+            usePremultipliedAlpha = layer.source.buffer.usePremultipliedAlpha;
+            Texture texture(Texture::TEXTURE_EXTERNAL, layer.source.buffer.textureName);
+            mat4 texMatrix = layer.source.buffer.textureTransform;
 
             texture.setMatrix(texMatrix.asArray());
-            texture.setFiltering(layer->source.buffer.useTextureFiltering);
+            texture.setFiltering(layer.source.buffer.useTextureFiltering);
 
             texture.setDimensions(gBuf->getWidth(), gBuf->getHeight());
-            setSourceY410BT2020(layer->source.buffer.isY410BT2020);
+            setSourceY410BT2020(layer.source.buffer.isY410BT2020);
 
             renderengine::Mesh::VertexArray<vec2> texCoords(mesh.getTexCoordArray<vec2>());
             texCoords[0] = vec2(0.0, 0.0);
@@ -1261,62 +1264,63 @@
             }
         }
 
-        const half3 solidColor = layer->source.solidColor;
-        const half4 color = half4(solidColor.r, solidColor.g, solidColor.b, layer->alpha);
+        const half3 solidColor = layer.source.solidColor;
+        const half4 color = half4(solidColor.r, solidColor.g, solidColor.b, layer.alpha);
         // Buffer sources will have a black solid color ignored in the shader,
         // so in that scenario the solid color passed here is arbitrary.
         setupLayerBlending(usePremultipliedAlpha, isOpaque, disableTexture, color,
-                           layer->geometry.roundedCornersRadius);
-        if (layer->disableBlending) {
+                           layer.geometry.roundedCornersRadius);
+        if (layer.disableBlending) {
             glDisable(GL_BLEND);
         }
-        setSourceDataSpace(layer->sourceDataspace);
+        setSourceDataSpace(layer.sourceDataspace);
 
-        if (layer->shadow.length > 0.0f) {
-            handleShadow(layer->geometry.boundaries, layer->geometry.roundedCornersRadius,
-                         layer->shadow);
+        if (layer.shadow.length > 0.0f) {
+            handleShadow(layer.geometry.boundaries, layer.geometry.roundedCornersRadius,
+                         layer.shadow);
         }
         // We only want to do a special handling for rounded corners when having rounded corners
         // is the only reason it needs to turn on blending, otherwise, we handle it like the
         // usual way since it needs to turn on blending anyway.
-        else if (layer->geometry.roundedCornersRadius > 0.0 && color.a >= 1.0f && isOpaque) {
-            handleRoundedCorners(display, *layer, mesh);
+        else if (layer.geometry.roundedCornersRadius > 0.0 && color.a >= 1.0f && isOpaque) {
+            handleRoundedCorners(display, layer, mesh);
         } else {
             drawMesh(mesh);
         }
 
         // Cleanup if there's a buffer source
-        if (layer->source.buffer.buffer != nullptr) {
+        if (layer.source.buffer.buffer != nullptr) {
             disableBlending();
             setSourceY410BT2020(false);
             disableTexturing();
         }
     }
 
-    if (drawFence != nullptr) {
-        *drawFence = flush();
-    }
+    base::unique_fd drawFence = flush();
+
     // If flush failed or we don't support native fences, we need to force the
     // gl command stream to be executed.
-    if (drawFence == nullptr || drawFence->get() < 0) {
+    if (drawFence.get() < 0) {
         bool success = finish();
         if (!success) {
             ALOGE("Failed to flush RenderEngine commands");
             checkErrors();
             // Chances are, something illegal happened (either the caller passed
             // us bad parameters, or we messed up our shader generation).
-            return INVALID_OPERATION;
+            resultPromise->set_value({INVALID_OPERATION, std::move(drawFence)});
+            return;
         }
         mLastDrawFence = nullptr;
     } else {
         // The caller takes ownership of drawFence, so we need to duplicate the
         // fd here.
-        mLastDrawFence = new Fence(dup(drawFence->get()));
+        mLastDrawFence = new Fence(dup(drawFence.get()));
     }
     mPriorResourcesCleaned = false;
 
     checkErrors();
-    return NO_ERROR;
+    resultPromise->set_value({NO_ERROR, std::move(drawFence)});
+    return;
 }
 
 void GLESRenderEngine::setViewportAndProjection(Rect viewport, Rect clip) {
diff --git a/libs/renderengine/gl/GLESRenderEngine.h b/libs/renderengine/gl/GLESRenderEngine.h
index 14627ce..1d7c2ca 100644
--- a/libs/renderengine/gl/GLESRenderEngine.h
+++ b/libs/renderengine/gl/GLESRenderEngine.h
@@ -63,11 +63,6 @@
     bool isProtected() const override { return mInProtectedContext; }
     bool supportsProtectedContent() const override;
     void useProtectedContext(bool useProtectedContext) override;
-    status_t drawLayers(const DisplaySettings& display,
-                        const std::vector<const LayerSettings*>& layers,
-                        const std::shared_ptr<ExternalTexture>& buffer,
-                        const bool useFramebufferCache, base::unique_fd&& bufferFence,
-                        base::unique_fd* drawFence) override;
     void cleanupPostRender() override;
     int getContextPriority() override;
     bool supportsBackgroundBlur() override { return mBlurFilter != nullptr; }
@@ -107,6 +102,11 @@
             EXCLUDES(mRenderingMutex);
     void unmapExternalTextureBuffer(const sp<GraphicBuffer>& buffer) EXCLUDES(mRenderingMutex);
     bool canSkipPostRenderCleanup() const override;
+    void drawLayersInternal(const std::shared_ptr<std::promise<RenderEngineResult>>&& resultPromise,
+                            const DisplaySettings& display,
+                            const std::vector<LayerSettings>& layers,
+                            const std::shared_ptr<ExternalTexture>& buffer,
+                            const bool useFramebufferCache, base::unique_fd&& bufferFence) override;
 
 private:
     friend class BindNativeBufferAsFramebuffer;
diff --git a/libs/renderengine/include/renderengine/DisplaySettings.h b/libs/renderengine/include/renderengine/DisplaySettings.h
index 53fa622..40ba5ad 100644
--- a/libs/renderengine/include/renderengine/DisplaySettings.h
+++ b/libs/renderengine/include/renderengine/DisplaySettings.h
@@ -43,6 +43,9 @@
     // Maximum luminance pulled from the display's HDR capabilities.
     float maxLuminance = 1.0f;
 
+    // Current luminance of the display
+    float currentLuminanceNits = -1.f;
+
     // Output dataspace that will be populated if wide color gamut is used, or
     // DataSpace::UNKNOWN otherwise.
     ui::Dataspace outputDataspace = ui::Dataspace::UNKNOWN;
@@ -51,9 +54,9 @@
     // dataspace, in non-linear space.
     mat4 colorTransform = mat4();
 
-    // Region that will be cleared to (0, 0, 0, 1) prior to rendering.
-    // This is specified in layer-stack space.
-    Region clearRegion = Region::INVALID_REGION;
+    // If true, and colorTransform is non-identity, most client draw calls can
+    // ignore it. Some draws (e.g. screen decorations) may need it, though.
+    bool deviceHandlesColorTransform = false;
 
     // An additional orientation flag to be applied after clipping the output.
     // By way of example, this may be used for supporting fullscreen screenshot
@@ -61,15 +64,16 @@
     // orientation.
     uint32_t orientation = ui::Transform::ROT_0;
 
-    // SDR white point, -1f if unknown
-    float sdrWhitePointNits = -1.f;
+    // Target luminance of the display. -1f if unknown.
+    // All layers will be dimmed by (max(layer white points) / targetLuminanceNits).
+    // If the target luminance is unknown, then no display-level dimming occurs.
+    float targetLuminanceNits = -1.f;
 };
 
 static inline bool operator==(const DisplaySettings& lhs, const DisplaySettings& rhs) {
     return lhs.physicalDisplay == rhs.physicalDisplay && lhs.clip == rhs.clip &&
             lhs.maxLuminance == rhs.maxLuminance && lhs.outputDataspace == rhs.outputDataspace &&
-            lhs.colorTransform == rhs.colorTransform &&
-            lhs.clearRegion.hasSameRects(rhs.clearRegion) && lhs.orientation == rhs.orientation;
+            lhs.colorTransform == rhs.colorTransform && lhs.orientation == rhs.orientation;
 }
 
 // Defining PrintTo helps with Google Tests.
@@ -84,9 +88,6 @@
     PrintTo(settings.outputDataspace, os);
     *os << "\n    .colorTransform = " << settings.colorTransform;
     *os << "\n    .clearRegion = ";
-    PrintTo(settings.clearRegion, os);
-    *os << "\n    .orientation = " << settings.orientation;
-    *os << "\n}";
 }
 
 } // namespace renderengine
diff --git a/libs/renderengine/include/renderengine/ExternalTexture.h b/libs/renderengine/include/renderengine/ExternalTexture.h
index 07f0833..621a209 100644
--- a/libs/renderengine/include/renderengine/ExternalTexture.h
+++ b/libs/renderengine/include/renderengine/ExternalTexture.h
@@ -33,28 +33,22 @@
  */
 class ExternalTexture {
 public:
-    // Usage specifies the rendering intent for the buffer.
-    enum Usage : uint32_t {
-        // When a buffer is not READABLE but is WRITEABLE, then GLESRenderEngine will use that as a
-        // hint to load the buffer into a separate cache
-        READABLE = 1 << 0,
+    ExternalTexture() = default;
+    virtual ~ExternalTexture() = default;
 
-        // The buffer needs to be mapped as a 2D texture if set, otherwise must be mapped as an
-        // external texture
-        WRITEABLE = 1 << 1,
-    };
-    // Creates an ExternalTexture for the provided buffer and RenderEngine instance, with the given
-    // usage hint of type Usage.
-    ExternalTexture(const sp<GraphicBuffer>& buffer, RenderEngine& renderEngine, uint32_t usage);
-
-    ~ExternalTexture();
+    virtual bool hasSameBuffer(const ExternalTexture& other) const = 0;
+    virtual uint32_t getWidth() const = 0;
+    virtual uint32_t getHeight() const = 0;
+    virtual uint64_t getId() const = 0;
+    virtual PixelFormat getPixelFormat() const = 0;
+    virtual uint64_t getUsage() const = 0;
 
     // Retrieves the buffer that is bound to this texture.
-    const sp<GraphicBuffer>& getBuffer() const { return mBuffer; }
+    virtual const sp<GraphicBuffer>& getBuffer() const = 0;
 
-private:
-    sp<GraphicBuffer> mBuffer;
-    RenderEngine& mRenderEngine;
+    Rect getBounds() const {
+        return {0, 0, static_cast<int32_t>(getWidth()), static_cast<int32_t>(getHeight())};
+    }
     DISALLOW_COPY_AND_ASSIGN(ExternalTexture);
 };
 
diff --git a/libs/renderengine/include/renderengine/LayerSettings.h b/libs/renderengine/include/renderengine/LayerSettings.h
index 715f3f8..171cbaa 100644
--- a/libs/renderengine/include/renderengine/LayerSettings.h
+++ b/libs/renderengine/include/renderengine/LayerSettings.h
@@ -171,6 +171,12 @@
 
     // Name associated with the layer for debugging purposes.
     std::string name;
+
+    // Luminance of the white point for this layer. Used for linear dimming.
+    // Individual layers will be dimmed by (whitePointNits / maxWhitePoint).
+    // If white point nits are unknown, then this layer is assumed to have the
+    // same luminance as the brightest layer in the scene.
+    float whitePointNits = -1.f;
 };
 
 // Keep in sync with custom comparison function in
@@ -307,6 +313,7 @@
     PrintTo(settings.shadow, os);
     *os << "\n    .stretchEffect = ";
     PrintTo(settings.stretchEffect, os);
+    *os << "\n    .whitePointNits = " << settings.whitePointNits;
     *os << "\n}";
 }
 
diff --git a/libs/renderengine/include/renderengine/RenderEngine.h b/libs/renderengine/include/renderengine/RenderEngine.h
index f555cdb..3e7f69c 100644
--- a/libs/renderengine/include/renderengine/RenderEngine.h
+++ b/libs/renderengine/include/renderengine/RenderEngine.h
@@ -68,6 +68,7 @@
 class Mesh;
 class Texture;
 struct RenderEngineCreationArgs;
+struct RenderEngineResult;
 
 namespace threaded {
 class RenderEngineThreaded;
@@ -75,6 +76,7 @@
 
 namespace impl {
 class RenderEngine;
+class ExternalTexture;
 }
 
 enum class Protection {
@@ -98,7 +100,7 @@
         SKIA_GL_THREADED = 4,
     };
 
-    static std::unique_ptr<RenderEngine> create(RenderEngineCreationArgs args);
+    static std::unique_ptr<RenderEngine> create(const RenderEngineCreationArgs& args);
 
     virtual ~RenderEngine() = 0;
 
@@ -156,17 +158,12 @@
     // parameter does nothing.
     // @param bufferFence Fence signalling that the buffer is ready to be drawn
     // to.
-    // @param drawFence A pointer to a fence, which will fire when the buffer
-    // has been drawn to and is ready to be examined. The fence will be
-    // initialized by this method. The caller will be responsible for owning the
-    // fence.
-    // @return An error code indicating whether drawing was successful. For
-    // now, this always returns NO_ERROR.
-    virtual status_t drawLayers(const DisplaySettings& display,
-                                const std::vector<const LayerSettings*>& layers,
-                                const std::shared_ptr<ExternalTexture>& buffer,
-                                const bool useFramebufferCache, base::unique_fd&& bufferFence,
-                                base::unique_fd* drawFence) = 0;
+    // @return A future object of RenderEngineResult struct indicating whether
+    // drawing was successful in async mode.
+    virtual std::future<RenderEngineResult> drawLayers(
+            const DisplaySettings& display, const std::vector<LayerSettings>& layers,
+            const std::shared_ptr<ExternalTexture>& buffer, const bool useFramebufferCache,
+            base::unique_fd&& bufferFence);
 
     // Clean-up method that should be called on the main thread after the
     // drawFence returned by drawLayers fires. This method will free up
@@ -193,6 +190,12 @@
     static void validateInputBufferUsage(const sp<GraphicBuffer>&);
     static void validateOutputBufferUsage(const sp<GraphicBuffer>&);
 
+    // Allows flinger to get the render engine thread id for power management with ADPF
+    // Returns the tid of the renderengine thread if it's threaded, and std::nullopt otherwise
+    virtual std::optional<pid_t> getRenderEngineTid() const { return std::nullopt; }
+
+    virtual void setEnableTracing(bool /*tracingEnabled*/) {}
+
 protected:
     RenderEngine() : RenderEngine(RenderEngineType::GLES) {}
 
@@ -228,10 +231,16 @@
     // avoid any thread synchronization that may be required by directly calling postRenderCleanup.
     virtual bool canSkipPostRenderCleanup() const = 0;
 
-    friend class ExternalTexture;
+    friend class impl::ExternalTexture;
     friend class threaded::RenderEngineThreaded;
     friend class RenderEngineTest_cleanupPostRender_cleansUpOnce_Test;
     const RenderEngineType mRenderEngineType;
+
+    virtual void drawLayersInternal(
+            const std::shared_ptr<std::promise<RenderEngineResult>>&& resultPromise,
+            const DisplaySettings& display, const std::vector<LayerSettings>& layers,
+            const std::shared_ptr<ExternalTexture>& buffer, const bool useFramebufferCache,
+            base::unique_fd&& bufferFence) = 0;
 };
 
 struct RenderEngineCreationArgs {
@@ -318,6 +327,13 @@
             RenderEngine::RenderEngineType::SKIA_GL_THREADED;
 };
 
+struct RenderEngineResult {
+    // status indicates if drawing is successful
+    status_t status;
+    // drawFence will fire when the buffer has been drawn to and is ready to be examined.
+    base::unique_fd drawFence;
+};
+
 } // namespace renderengine
 } // namespace android
 
diff --git a/libs/renderengine/include/renderengine/impl/ExternalTexture.h b/libs/renderengine/include/renderengine/impl/ExternalTexture.h
new file mode 100644
index 0000000..c0e24f0
--- /dev/null
+++ b/libs/renderengine/include/renderengine/impl/ExternalTexture.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 <android-base/macros.h>
+#include <renderengine/ExternalTexture.h>
+#include <ui/GraphicBuffer.h>
+
+namespace android::renderengine::impl {
+
+class RenderEngine;
+
+class ExternalTexture : public android::renderengine::ExternalTexture {
+public:
+    // Usage specifies the rendering intent for the buffer.
+    enum Usage : uint32_t {
+        // When a buffer is not READABLE but is WRITEABLE, then GLESRenderEngine will use that as a
+        // hint to load the buffer into a separate cache
+        READABLE = 1 << 0,
+
+        // The buffer needs to be mapped as a 2D texture if set, otherwise must be mapped as an
+        // external texture
+        WRITEABLE = 1 << 1,
+    };
+
+    // Creates an ExternalTexture for the provided buffer and RenderEngine instance, with the given
+    // usage hint of type Usage.
+    ExternalTexture(const sp<GraphicBuffer>& buffer,
+                    android::renderengine::RenderEngine& renderEngine, uint32_t usage);
+    ~ExternalTexture();
+    const sp<GraphicBuffer>& getBuffer() const override { return mBuffer; };
+    uint32_t getWidth() const override { return getBuffer()->getWidth(); }
+    uint32_t getHeight() const override { return getBuffer()->getHeight(); }
+    uint64_t getId() const override { return getBuffer()->getId(); }
+    PixelFormat getPixelFormat() const override { return getBuffer()->getPixelFormat(); }
+    uint64_t getUsage() const override { return getBuffer()->getUsage(); }
+    bool hasSameBuffer(const renderengine::ExternalTexture& other) const override {
+        return getBuffer() == other.getBuffer();
+    }
+
+private:
+    sp<GraphicBuffer> mBuffer;
+    android::renderengine::RenderEngine& mRenderEngine;
+};
+
+} // namespace android::renderengine::impl
diff --git a/libs/renderengine/include/renderengine/mock/FakeExternalTexture.h b/libs/renderengine/include/renderengine/mock/FakeExternalTexture.h
new file mode 100644
index 0000000..974e0fd
--- /dev/null
+++ b/libs/renderengine/include/renderengine/mock/FakeExternalTexture.h
@@ -0,0 +1,51 @@
+/*
+ * 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 <renderengine/ExternalTexture.h>
+
+namespace android {
+namespace renderengine {
+namespace mock {
+
+class FakeExternalTexture : public renderengine::ExternalTexture {
+    const sp<GraphicBuffer> mNullBuffer = nullptr;
+    uint32_t mWidth;
+    uint32_t mHeight;
+    uint64_t mId;
+    PixelFormat mPixelFormat;
+    uint64_t mUsage;
+
+public:
+    FakeExternalTexture(uint32_t width, uint32_t height, uint64_t id, PixelFormat pixelFormat,
+                        uint64_t usage)
+          : mWidth(width), mHeight(height), mId(id), mPixelFormat(pixelFormat), mUsage(usage) {}
+    const sp<GraphicBuffer>& getBuffer() const { return mNullBuffer; }
+    bool hasSameBuffer(const renderengine::ExternalTexture& other) const override {
+        return getId() == other.getId();
+    }
+    uint32_t getWidth() const override { return mWidth; }
+    uint32_t getHeight() const override { return mHeight; }
+    uint64_t getId() const override { return mId; }
+    PixelFormat getPixelFormat() const override { return mPixelFormat; }
+    uint64_t getUsage() const override { return mUsage; }
+    ~FakeExternalTexture() = default;
+};
+
+} // namespace mock
+} // namespace renderengine
+} // namespace android
diff --git a/libs/renderengine/include/renderengine/mock/RenderEngine.h b/libs/renderengine/include/renderengine/mock/RenderEngine.h
index 0be3ba6..248bd65 100644
--- a/libs/renderengine/include/renderengine/mock/RenderEngine.h
+++ b/libs/renderengine/include/renderengine/mock/RenderEngine.h
@@ -47,10 +47,15 @@
     MOCK_METHOD1(useProtectedContext, void(bool));
     MOCK_METHOD0(cleanupPostRender, void());
     MOCK_CONST_METHOD0(canSkipPostRenderCleanup, bool());
-    MOCK_METHOD6(drawLayers,
-                 status_t(const DisplaySettings&, const std::vector<const LayerSettings*>&,
-                          const std::shared_ptr<ExternalTexture>&, const bool, base::unique_fd&&,
-                          base::unique_fd*));
+    MOCK_METHOD5(drawLayers,
+                 std::future<RenderEngineResult>(const DisplaySettings&,
+                                                 const std::vector<LayerSettings>&,
+                                                 const std::shared_ptr<ExternalTexture>&,
+                                                 const bool, base::unique_fd&&));
+    MOCK_METHOD6(drawLayersInternal,
+                 void(const std::shared_ptr<std::promise<RenderEngineResult>>&&,
+                      const DisplaySettings&, const std::vector<LayerSettings>&,
+                      const std::shared_ptr<ExternalTexture>&, const bool, base::unique_fd&&));
     MOCK_METHOD0(cleanFramebufferCache, void());
     MOCK_METHOD0(getContextPriority, int());
     MOCK_METHOD0(supportsBackgroundBlur, bool());
diff --git a/libs/renderengine/skia/Cache.cpp b/libs/renderengine/skia/Cache.cpp
index cc627b8..a3a1969 100644
--- a/libs/renderengine/skia/Cache.cpp
+++ b/libs/renderengine/skia/Cache.cpp
@@ -19,6 +19,7 @@
 #include "android-base/unique_fd.h"
 #include "renderengine/DisplaySettings.h"
 #include "renderengine/LayerSettings.h"
+#include "renderengine/impl/ExternalTexture.h"
 #include "ui/GraphicBuffer.h"
 #include "ui/GraphicTypes.h"
 #include "ui/PixelFormat.h"
@@ -95,25 +96,27 @@
             .alpha = 1,
     };
 
-    auto layers = std::vector<const LayerSettings*>{&layer, &caster};
-    // When sourceDataspace matches dest, the general shadow fragment shader doesn't
-    // have color correction added.
-    // independently, when it is not srgb, the *vertex* shader has color correction added.
-    // This may be a bug, but the shader still needs to be cached as it is triggered
-    // during youtube pip.
-    for (auto dataspace : {kDestDataSpace, kOtherDataSpace}) {
-        layer.sourceDataspace = dataspace;
-        // The 2nd matrix, which has different scales for x and y, will
-        // generate the slower (more general case) shadow shader
-        for (auto transform : {mat4(), kScaleAndTranslate, kFlip}) {
-            layer.geometry.positionTransform = transform;
-            caster.geometry.positionTransform = transform;
-            for (bool translucent : {false, true}){
-                layer.shadow.casterIsTranslucent = translucent;
-                renderengine->drawLayers(display, layers, dstTexture, kUseFrameBufferCache,
-                                        base::unique_fd(), nullptr);
-            }
-        }
+    auto layers = std::vector<LayerSettings>{layer, caster};
+    // Four combinations of settings are used (two transforms here, and drawShadowLayers is
+    // called with two different destination data spaces) They're all rounded rect.
+    // Three of these are cache misses that generate new shaders.
+    // The first combination generates a short and simple shadow shader.
+    // The second combination, flip transform, generates two shaders. The first appears to involve
+    //   gaussian_fp. The second is a long and general purpose shadow shader with a device space
+    //   transformation stage.
+    // The third combination is a cache hit, nothing new.
+    // The fourth combination, flip transform with a non-SRGB destination dataspace, is new.
+    //   It is unique in that nearly everything is done in the vertex shader, and that vertex shader
+    //   requires color correction. This is triggered differently from every other instance of color
+    //   correction. All other instances are triggered when src and dst dataspaces differ, while
+    //   this one is triggered by the destination being non-srgb. Apparently since the third
+    //   combination is a cache hit, this color correction is only added when the vertex shader is
+    //   doing something non-trivial.
+    for (auto transform : {mat4(), kFlip}) {
+        layer.geometry.positionTransform = transform;
+        caster.geometry.positionTransform = transform;
+        renderengine->drawLayers(display, layers, dstTexture, kUseFrameBufferCache,
+                                 base::unique_fd());
     }
 }
 
@@ -138,7 +141,7 @@
                                           }},
     };
 
-    auto layers = std::vector<const LayerSettings*>{&layer};
+    auto layers = std::vector<LayerSettings>{layer};
     for (auto dataspace : {kDestDataSpace, kOtherDataSpace}) {
         layer.sourceDataspace = dataspace;
         // Cache shaders for both rects and round rects.
@@ -151,7 +154,7 @@
                 for (auto alpha : {half(.2f), half(1.0f)}) {
                     layer.alpha = alpha;
                     renderengine->drawLayers(display, layers, dstTexture, kUseFrameBufferCache,
-                                             base::unique_fd(), nullptr);
+                                             base::unique_fd());
                 }
             }
         }
@@ -174,13 +177,13 @@
             .alpha = 0.5,
     };
 
-    auto layers = std::vector<const LayerSettings*>{&layer};
+    auto layers = std::vector<LayerSettings>{layer};
     for (auto transform : {mat4(), kScaleAndTranslate}) {
         layer.geometry.positionTransform = transform;
         for (float roundedCornersRadius : {0.0f, 50.f}) {
             layer.geometry.roundedCornersRadius = roundedCornersRadius;
             renderengine->drawLayers(display, layers, dstTexture, kUseFrameBufferCache,
-                                     base::unique_fd(), nullptr);
+                                     base::unique_fd());
         }
     }
 }
@@ -199,12 +202,12 @@
             .skipContentDraw = true,
     };
 
-    auto layers = std::vector<const LayerSettings*>{&layer};
+    auto layers = std::vector<LayerSettings>{layer};
     // Different blur code is invoked for radii less and greater than 30 pixels
     for (int radius : {9, 60}) {
         layer.backgroundBlurRadius = radius;
         renderengine->drawLayers(display, layers, dstTexture, kUseFrameBufferCache,
-                                 base::unique_fd(), nullptr);
+                                 base::unique_fd());
     }
 }
 
@@ -240,7 +243,7 @@
                     },
     };
 
-    auto layers = std::vector<const LayerSettings*>{&layer};
+    auto layers = std::vector<LayerSettings>{layer};
     for (auto pixelSource : {bufferSource, bufferOpaque, colorSource}) {
         layer.source = pixelSource;
         for (auto dataspace : {kDestDataSpace, kOtherDataSpace}) {
@@ -251,7 +254,7 @@
                 for (float alpha : {0.5f, 1.f}) {
                     layer.alpha = alpha,
                     renderengine->drawLayers(display, layers, dstTexture, kUseFrameBufferCache,
-                                             base::unique_fd(), nullptr);
+                                             base::unique_fd());
                 }
             }
         }
@@ -287,9 +290,8 @@
 
     };
 
-    auto layers = std::vector<const LayerSettings*>{&layer};
-    renderengine->drawLayers(display, layers, dstTexture, kUseFrameBufferCache,
-                             base::unique_fd(), nullptr);
+    auto layers = std::vector<LayerSettings>{layer};
+    renderengine->drawLayers(display, layers, dstTexture, kUseFrameBufferCache, base::unique_fd());
 }
 
 static void drawHolePunchLayer(SkiaRenderEngine* renderengine, const DisplaySettings& display,
@@ -316,9 +318,8 @@
 
     };
 
-    auto layers = std::vector<const LayerSettings*>{&layer};
-    renderengine->drawLayers(display, layers, dstTexture, kUseFrameBufferCache,
-                            base::unique_fd(), nullptr);
+    auto layers = std::vector<LayerSettings>{layer};
+    renderengine->drawLayers(display, layers, dstTexture, kUseFrameBufferCache, base::unique_fd());
 }
 
 //
@@ -365,8 +366,8 @@
                                   1, usage, "primeShaderCache_dst");
 
         const auto dstTexture =
-                std::make_shared<ExternalTexture>(dstBuffer, *renderengine,
-                                                  ExternalTexture::Usage::WRITEABLE);
+                std::make_shared<impl::ExternalTexture>(dstBuffer, *renderengine,
+                                                        impl::ExternalTexture::Usage::WRITEABLE);
         // This buffer will be the source for the call to drawImageLayers. Draw
         // something to it as a placeholder for what an app draws. We should draw
         // something, but the details are not important. Make use of the shadow layer drawing step
@@ -375,12 +376,13 @@
                 new GraphicBuffer(displayRect.width(), displayRect.height(), PIXEL_FORMAT_RGBA_8888,
                                   1, usage, "drawImageLayer_src");
 
-        const auto srcTexture =
-                std::make_shared<ExternalTexture>(srcBuffer, *renderengine,
-                                                  ExternalTexture::Usage::READABLE |
-                                                          ExternalTexture::Usage::WRITEABLE);
+        const auto srcTexture = std::make_shared<
+                impl::ExternalTexture>(srcBuffer, *renderengine,
+                                       impl::ExternalTexture::Usage::READABLE |
+                                               impl::ExternalTexture::Usage::WRITEABLE);
         drawHolePunchLayer(renderengine, display, dstTexture);
         drawSolidLayers(renderengine, display, dstTexture);
+
         drawShadowLayers(renderengine, display, srcTexture);
         drawShadowLayers(renderengine, p3Display, srcTexture);
 
@@ -397,8 +399,8 @@
                 new GraphicBuffer(displayRect.width(), displayRect.height(), PIXEL_FORMAT_RGBA_8888,
                                   1, usageExternal, "primeShaderCache_external");
         const auto externalTexture =
-                std::make_shared<ExternalTexture>(externalBuffer, *renderengine,
-                                                  ExternalTexture::Usage::READABLE);
+                std::make_shared<impl::ExternalTexture>(externalBuffer, *renderengine,
+                                                        impl::ExternalTexture::Usage::READABLE);
         std::vector<const std::shared_ptr<ExternalTexture>> textures =
             {srcTexture, externalTexture};
 
@@ -411,8 +413,8 @@
         status_t error = f16ExternalBuffer->initCheck();
         if (!error) {
             const auto f16ExternalTexture =
-                std::make_shared<ExternalTexture>(f16ExternalBuffer, *renderengine,
-                                                  ExternalTexture::Usage::READABLE);
+                    std::make_shared<impl::ExternalTexture>(f16ExternalBuffer, *renderengine,
+                                                            impl::ExternalTexture::Usage::READABLE);
             textures.push_back(f16ExternalTexture);
         }
 
@@ -424,6 +426,16 @@
 
         drawPIPImageLayer(renderengine, display, dstTexture, externalTexture);
 
+        // draw one final layer synchronously to force GL submit
+        LayerSettings layer{
+                .source = PixelSource{.solidColor = half3(0.f, 0.f, 0.f)},
+        };
+        auto layers = std::vector<LayerSettings>{layer};
+        // call get() to make it synchronous
+        renderengine
+                ->drawLayers(display, layers, dstTexture, kUseFrameBufferCache, base::unique_fd())
+                .get();
+
         const nsecs_t timeAfter = systemTime();
         const float compileTimeMs = static_cast<float>(timeAfter - timeBefore) / 1.0E6;
         const int shadersCompiled = renderengine->reportShadersCompiled();
diff --git a/libs/renderengine/skia/ColorSpaces.cpp b/libs/renderengine/skia/ColorSpaces.cpp
index ff4d348..f367a84 100644
--- a/libs/renderengine/skia/ColorSpaces.cpp
+++ b/libs/renderengine/skia/ColorSpaces.cpp
@@ -20,6 +20,7 @@
 namespace renderengine {
 namespace skia {
 
+// please keep in sync with hwui/utils/Color.cpp
 sk_sp<SkColorSpace> toSkColorSpace(ui::Dataspace dataspace) {
     skcms_Matrix3x3 gamut;
     switch (dataspace & HAL_DATASPACE_STANDARD_MASK) {
@@ -32,6 +33,17 @@
         case HAL_DATASPACE_STANDARD_DCI_P3:
             gamut = SkNamedGamut::kDisplayP3;
             break;
+        case HAL_DATASPACE_STANDARD_ADOBE_RGB:
+            gamut = SkNamedGamut::kAdobeRGB;
+            break;
+        case HAL_DATASPACE_STANDARD_BT601_625:
+        case HAL_DATASPACE_STANDARD_BT601_625_UNADJUSTED:
+        case HAL_DATASPACE_STANDARD_BT601_525:
+        case HAL_DATASPACE_STANDARD_BT601_525_UNADJUSTED:
+        case HAL_DATASPACE_STANDARD_BT2020_CONSTANT_LUMINANCE:
+        case HAL_DATASPACE_STANDARD_BT470M:
+        case HAL_DATASPACE_STANDARD_FILM:
+        case HAL_DATASPACE_STANDARD_UNSPECIFIED:
         default:
             gamut = SkNamedGamut::kSRGB;
             break;
@@ -42,10 +54,19 @@
             return SkColorSpace::MakeRGB(SkNamedTransferFn::kLinear, gamut);
         case HAL_DATASPACE_TRANSFER_SRGB:
             return SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB, gamut);
+        case HAL_DATASPACE_TRANSFER_GAMMA2_2:
+            return SkColorSpace::MakeRGB({2.2f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}, gamut);
+        case HAL_DATASPACE_TRANSFER_GAMMA2_6:
+            return SkColorSpace::MakeRGB({2.6f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}, gamut);
+        case HAL_DATASPACE_TRANSFER_GAMMA2_8:
+            return SkColorSpace::MakeRGB({2.8f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}, gamut);
         case HAL_DATASPACE_TRANSFER_ST2084:
             return SkColorSpace::MakeRGB(SkNamedTransferFn::kPQ, gamut);
+        case HAL_DATASPACE_TRANSFER_SMPTE_170M:
+            return SkColorSpace::MakeRGB(SkNamedTransferFn::kRec2020, gamut);
         case HAL_DATASPACE_TRANSFER_HLG:
             return SkColorSpace::MakeRGB(SkNamedTransferFn::kHLG, gamut);
+        case HAL_DATASPACE_TRANSFER_UNSPECIFIED:
         default:
             return SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB, gamut);
     }
diff --git a/libs/renderengine/skia/SkiaGLRenderEngine.cpp b/libs/renderengine/skia/SkiaGLRenderEngine.cpp
index e42b5b9..1665550 100644
--- a/libs/renderengine/skia/SkiaGLRenderEngine.cpp
+++ b/libs/renderengine/skia/SkiaGLRenderEngine.cpp
@@ -46,6 +46,7 @@
 #include <cmath>
 #include <cstdint>
 #include <memory>
+#include <numeric>
 
 #include "../gl/GLExtensions.h"
 #include "Cache.h"
@@ -53,6 +54,8 @@
 #include "SkBlendMode.h"
 #include "SkImageInfo.h"
 #include "filters/BlurFilter.h"
+#include "filters/GaussianBlurFilter.h"
+#include "filters/KawaseBlurFilter.h"
 #include "filters/LinearEffect.h"
 #include "log/log_main.h"
 #include "skia/debug/SkiaCapture.h"
@@ -328,7 +331,7 @@
 
     if (args.supportsBackgroundBlur) {
         ALOGD("Background Blurs Enabled");
-        mBlurFilter = new BlurFilter();
+        mBlurFilter = new KawaseBlurFilter();
     }
     mCapture = std::make_unique<SkiaCapture>();
 }
@@ -611,31 +614,32 @@
 };
 
 sk_sp<SkShader> SkiaGLRenderEngine::createRuntimeEffectShader(
-        sk_sp<SkShader> shader,
-        const LayerSettings* layer, const DisplaySettings& display, bool undoPremultipliedAlpha,
-        bool requiresLinearEffect) {
-    const auto stretchEffect = layer->stretchEffect;
+        const RuntimeEffectShaderParameters& parameters) {
     // The given surface will be stretched by HWUI via matrix transformation
     // which gets similar results for most surfaces
     // Determine later on if we need to leverage the stertch shader within
     // surface flinger
+    const auto& stretchEffect = parameters.layer.stretchEffect;
+    auto shader = parameters.shader;
     if (stretchEffect.hasEffect()) {
-        const auto targetBuffer = layer->source.buffer.buffer;
+        const auto targetBuffer = parameters.layer.source.buffer.buffer;
         const auto graphicBuffer = targetBuffer ? targetBuffer->getBuffer() : nullptr;
-        if (graphicBuffer && shader) {
+        if (graphicBuffer && parameters.shader) {
             shader = mStretchShaderFactory.createSkShader(shader, stretchEffect);
         }
     }
 
-    if (requiresLinearEffect) {
-        const ui::Dataspace inputDataspace =
-                mUseColorManagement ? layer->sourceDataspace : ui::Dataspace::V0_SRGB_LINEAR;
-        const ui::Dataspace outputDataspace =
-                mUseColorManagement ? display.outputDataspace : ui::Dataspace::V0_SRGB_LINEAR;
+    if (parameters.requiresLinearEffect) {
+        const ui::Dataspace inputDataspace = mUseColorManagement ? parameters.layer.sourceDataspace
+                                                                 : ui::Dataspace::V0_SRGB_LINEAR;
+        const ui::Dataspace outputDataspace = mUseColorManagement
+                ? parameters.display.outputDataspace
+                : ui::Dataspace::V0_SRGB_LINEAR;
 
-        LinearEffect effect = LinearEffect{.inputDataspace = inputDataspace,
-                                           .outputDataspace = outputDataspace,
-                                           .undoPremultipliedAlpha = undoPremultipliedAlpha};
+        auto effect =
+                shaders::LinearEffect{.inputDataspace = inputDataspace,
+                                      .outputDataspace = outputDataspace,
+                                      .undoPremultipliedAlpha = parameters.undoPremultipliedAlpha};
 
         auto effectIter = mRuntimeEffects.find(effect);
         sk_sp<SkRuntimeEffect> runtimeEffect = nullptr;
@@ -645,16 +649,21 @@
         } else {
             runtimeEffect = effectIter->second;
         }
-        float maxLuminance = layer->source.buffer.maxLuminanceNits;
-        // If the buffer doesn't have a max luminance, treat it as SDR & use the display's SDR
-        // white point
-        if (maxLuminance <= 0.f) {
-            maxLuminance = display.sdrWhitePointNits;
-        }
-        return createLinearEffectShader(shader, effect, runtimeEffect, layer->colorTransform,
-                                        display.maxLuminance, maxLuminance);
+        mat4 colorTransform = parameters.layer.colorTransform;
+
+        colorTransform *=
+                mat4::scale(vec4(parameters.layerDimmingRatio, parameters.layerDimmingRatio,
+                                 parameters.layerDimmingRatio, 1.f));
+        const auto targetBuffer = parameters.layer.source.buffer.buffer;
+        const auto graphicBuffer = targetBuffer ? targetBuffer->getBuffer() : nullptr;
+        const auto hardwareBuffer = graphicBuffer ? graphicBuffer->toAHardwareBuffer() : nullptr;
+        return createLinearEffectShader(parameters.shader, effect, runtimeEffect, colorTransform,
+                                        parameters.display.maxLuminance,
+                                        parameters.display.currentLuminanceNits,
+                                        parameters.layer.source.buffer.maxLuminanceNits,
+                                        hardwareBuffer);
     }
-    return shader;
+    return parameters.shader;
 }
 
 void SkiaGLRenderEngine::initCanvas(SkCanvas* canvas, const DisplaySettings& display) {
@@ -727,22 +736,29 @@
     return roundedRect;
 }
 
-status_t SkiaGLRenderEngine::drawLayers(const DisplaySettings& display,
-                                        const std::vector<const LayerSettings*>& layers,
-                                        const std::shared_ptr<ExternalTexture>& buffer,
-                                        const bool /*useFramebufferCache*/,
-                                        base::unique_fd&& bufferFence, base::unique_fd* drawFence) {
+static bool equalsWithinMargin(float expected, float value, float margin) {
+    LOG_ALWAYS_FATAL_IF(margin < 0.f, "Margin is negative!");
+    return std::abs(expected - value) < margin;
+}
+
+void SkiaGLRenderEngine::drawLayersInternal(
+        const std::shared_ptr<std::promise<RenderEngineResult>>&& resultPromise,
+        const DisplaySettings& display, const std::vector<LayerSettings>& layers,
+        const std::shared_ptr<ExternalTexture>& buffer, const bool /*useFramebufferCache*/,
+        base::unique_fd&& bufferFence) {
     ATRACE_NAME("SkiaGL::drawLayers");
 
     std::lock_guard<std::mutex> lock(mRenderingMutex);
     if (layers.empty()) {
         ALOGV("Drawing empty layer stack");
-        return NO_ERROR;
+        resultPromise->set_value({NO_ERROR, base::unique_fd()});
+        return;
     }
 
     if (buffer == nullptr) {
         ALOGE("No output buffer provided. Aborting GPU composition.");
-        return BAD_VALUE;
+        resultPromise->set_value({BAD_VALUE, base::unique_fd()});
+        return;
     }
 
     validateOutputBufferUsage(buffer->getBuffer());
@@ -774,17 +790,30 @@
     SkCanvas* dstCanvas = mCapture->tryCapture(dstSurface.get());
     if (dstCanvas == nullptr) {
         ALOGE("Cannot acquire canvas from Skia.");
-        return BAD_VALUE;
+        resultPromise->set_value({BAD_VALUE, base::unique_fd()});
+        return;
     }
 
     // setup color filter if necessary
     sk_sp<SkColorFilter> displayColorTransform;
-    if (display.colorTransform != mat4()) {
+    if (display.colorTransform != mat4() && !display.deviceHandlesColorTransform) {
         displayColorTransform = SkColorFilters::Matrix(toSkColorMatrix(display.colorTransform));
     }
     const bool ctModifiesAlpha =
             displayColorTransform && !displayColorTransform->isAlphaUnchanged();
 
+    // Find the max layer white point to determine the max luminance of the scene...
+    const float maxLayerWhitePoint = std::transform_reduce(
+            layers.cbegin(), layers.cend(), 0.f,
+            [](float left, float right) { return std::max(left, right); },
+            [&](const auto& l) { return l.whitePointNits; });
+
+    // ...and compute the dimming ratio if dimming is requested
+    const float displayDimmingRatio = display.targetLuminanceNits > 0.f &&
+                    maxLayerWhitePoint > 0.f && display.targetLuminanceNits > maxLayerWhitePoint
+            ? maxLayerWhitePoint / display.targetLuminanceNits
+            : 1.f;
+
     // Find if any layers have requested blur, we'll use that info to decide when to render to an
     // offscreen buffer and when to render to the native buffer.
     sk_sp<SkSurface> activeSurface(dstSurface);
@@ -798,19 +827,19 @@
             if (!layerHasBlur(layer, ctModifiesAlpha)) {
                 continue;
             }
-            if (layer->backgroundBlurRadius > 0 &&
-                layer->backgroundBlurRadius < BlurFilter::kMaxCrossFadeRadius) {
+            if (layer.backgroundBlurRadius > 0 &&
+                layer.backgroundBlurRadius < mBlurFilter->getMaxCrossFadeRadius()) {
                 requiresCompositionLayer = true;
             }
-            for (auto region : layer->blurRegions) {
-                if (region.blurRadius < BlurFilter::kMaxCrossFadeRadius) {
+            for (auto region : layer.blurRegions) {
+                if (region.blurRadius < mBlurFilter->getMaxCrossFadeRadius()) {
                     requiresCompositionLayer = true;
                 }
             }
             if (requiresCompositionLayer) {
                 activeSurface = dstSurface->makeSurface(dstSurface->imageInfo());
                 canvas = mCapture->tryOffscreenCapture(activeSurface.get(), &offscreenCaptureState);
-                blurCompositionLayer = layer;
+                blurCompositionLayer = &layer;
                 break;
             }
         }
@@ -821,34 +850,12 @@
     canvas->clear(SK_ColorTRANSPARENT);
     initCanvas(canvas, display);
 
-    // TODO: clearRegion was required for SurfaceView when a buffer is not yet available but the
-    // view is still on-screen. The clear region could be re-specified as a black color layer,
-    // however.
-    if (!display.clearRegion.isEmpty()) {
-        ATRACE_NAME("ClearRegion");
-        size_t numRects = 0;
-        Rect const* rects = display.clearRegion.getArray(&numRects);
-        SkIRect skRects[numRects];
-        for (int i = 0; i < numRects; ++i) {
-            skRects[i] =
-                    SkIRect::MakeLTRB(rects[i].left, rects[i].top, rects[i].right, rects[i].bottom);
-        }
-        SkRegion clearRegion;
-        SkPaint paint;
-        sk_sp<SkShader> shader =
-                SkShaders::Color(SkColor4f{.fR = 0., .fG = 0., .fB = 0., .fA = 1.0},
-                                 toSkColorSpace(dstDataspace));
-        paint.setShader(shader);
-        clearRegion.setRects(skRects, numRects);
-        canvas->drawRegion(clearRegion, paint);
-    }
-
     for (const auto& layer : layers) {
-        ATRACE_FORMAT("DrawLayer: %s", layer->name.c_str());
+        ATRACE_FORMAT("DrawLayer: %s", layer.name.c_str());
 
         if (kPrintLayerSettings) {
             std::stringstream ls;
-            PrintTo(*layer, &ls);
+            PrintTo(layer, &ls);
             auto debugs = ls.str();
             int pos = 0;
             while (pos < debugs.size()) {
@@ -858,25 +865,28 @@
         }
 
         sk_sp<SkImage> blurInput;
-        if (blurCompositionLayer == layer) {
+        if (blurCompositionLayer == &layer) {
             LOG_ALWAYS_FATAL_IF(activeSurface == dstSurface);
             LOG_ALWAYS_FATAL_IF(canvas == dstCanvas);
 
             // save a snapshot of the activeSurface to use as input to the blur shaders
             blurInput = activeSurface->makeImageSnapshot();
 
-            // TODO we could skip this step if we know the blur will cover the entire image
-            //  blit the offscreen framebuffer into the destination AHB
-            SkPaint paint;
-            paint.setBlendMode(SkBlendMode::kSrc);
-            if (CC_UNLIKELY(mCapture->isCaptureRunning())) {
-                uint64_t id = mCapture->endOffscreenCapture(&offscreenCaptureState);
-                dstCanvas->drawAnnotation(SkRect::Make(dstCanvas->imageInfo().dimensions()),
-                                          String8::format("SurfaceID|%" PRId64, id).c_str(),
-                                          nullptr);
-                dstCanvas->drawImage(blurInput, 0, 0, SkSamplingOptions(), &paint);
-            } else {
-                activeSurface->draw(dstCanvas, 0, 0, SkSamplingOptions(), &paint);
+            // blit the offscreen framebuffer into the destination AHB, but only
+            // if there are blur regions. backgroundBlurRadius blurs the entire
+            // image below, so it can skip this step.
+            if (layer.blurRegions.size()) {
+                SkPaint paint;
+                paint.setBlendMode(SkBlendMode::kSrc);
+                if (CC_UNLIKELY(mCapture->isCaptureRunning())) {
+                    uint64_t id = mCapture->endOffscreenCapture(&offscreenCaptureState);
+                    dstCanvas->drawAnnotation(SkRect::Make(dstCanvas->imageInfo().dimensions()),
+                                              String8::format("SurfaceID|%" PRId64, id).c_str(),
+                                              nullptr);
+                    dstCanvas->drawImage(blurInput, 0, 0, SkSamplingOptions(), &paint);
+                } else {
+                    activeSurface->draw(dstCanvas, 0, 0, SkSamplingOptions(), &paint);
+                }
             }
 
             // assign dstCanvas to canvas and ensure that the canvas state is up to date
@@ -897,17 +907,17 @@
         if (CC_UNLIKELY(mCapture->isCaptureRunning())) {
             // Record the name of the layer if the capture is running.
             std::stringstream layerSettings;
-            PrintTo(*layer, &layerSettings);
+            PrintTo(layer, &layerSettings);
             // Store the LayerSettings in additional information.
-            canvas->drawAnnotation(SkRect::MakeEmpty(), layer->name.c_str(),
+            canvas->drawAnnotation(SkRect::MakeEmpty(), layer.name.c_str(),
                                    SkData::MakeWithCString(layerSettings.str().c_str()));
         }
         // Layers have a local transform that should be applied to them
-        canvas->concat(getSkM44(layer->geometry.positionTransform).asM33());
+        canvas->concat(getSkM44(layer.geometry.positionTransform).asM33());
 
         const auto [bounds, roundRectClip] =
-                getBoundsAndClip(layer->geometry.boundaries, layer->geometry.roundedCornersCrop,
-                                 layer->geometry.roundedCornersRadius);
+                getBoundsAndClip(layer.geometry.boundaries, layer.geometry.roundedCornersCrop,
+                                 layer.geometry.roundedCornersRadius);
         if (mBlurFilter && layerHasBlur(layer, ctModifiesAlpha)) {
             std::unordered_map<uint32_t, sk_sp<SkImage>> cachedBlurs;
 
@@ -928,20 +938,19 @@
 
             // TODO(b/182216890): Filter out empty layers earlier
             if (blurRect.width() > 0 && blurRect.height() > 0) {
-                if (layer->backgroundBlurRadius > 0) {
+                if (layer.backgroundBlurRadius > 0) {
                     ATRACE_NAME("BackgroundBlur");
-                    auto blurredImage =
-                            mBlurFilter->generate(grContext, layer->backgroundBlurRadius, blurInput,
-                                                  blurRect);
+                    auto blurredImage = mBlurFilter->generate(grContext, layer.backgroundBlurRadius,
+                                                              blurInput, blurRect);
 
-                    cachedBlurs[layer->backgroundBlurRadius] = blurredImage;
+                    cachedBlurs[layer.backgroundBlurRadius] = blurredImage;
 
-                    mBlurFilter->drawBlurRegion(canvas, bounds, layer->backgroundBlurRadius, 1.0f,
+                    mBlurFilter->drawBlurRegion(canvas, bounds, layer.backgroundBlurRadius, 1.0f,
                                                 blurRect, blurredImage, blurInput);
                 }
 
-                canvas->concat(getSkM44(layer->blurRegionTransform).asM33());
-                for (auto region : layer->blurRegions) {
+                canvas->concat(getSkM44(layer.blurRegionTransform).asM33());
+                for (auto region : layer.blurRegions) {
                     if (cachedBlurs[region.blurRadius] == nullptr) {
                         ATRACE_NAME("BlurRegion");
                         cachedBlurs[region.blurRadius] =
@@ -956,19 +965,18 @@
             }
         }
 
-        if (layer->shadow.length > 0) {
+        if (layer.shadow.length > 0) {
             // This would require a new parameter/flag to SkShadowUtils::DrawShadow
-            LOG_ALWAYS_FATAL_IF(layer->disableBlending, "Cannot disableBlending with a shadow");
+            LOG_ALWAYS_FATAL_IF(layer.disableBlending, "Cannot disableBlending with a shadow");
 
             SkRRect shadowBounds, shadowClip;
-            if (layer->geometry.boundaries == layer->shadow.boundaries) {
+            if (layer.geometry.boundaries == layer.shadow.boundaries) {
                 shadowBounds = bounds;
                 shadowClip = roundRectClip;
             } else {
                 std::tie(shadowBounds, shadowClip) =
-                        getBoundsAndClip(layer->shadow.boundaries,
-                                         layer->geometry.roundedCornersCrop,
-                                         layer->geometry.roundedCornersRadius);
+                        getBoundsAndClip(layer.shadow.boundaries, layer.geometry.roundedCornersCrop,
+                                         layer.geometry.roundedCornersRadius);
             }
 
             // Technically, if bounds is a rect and roundRectClip is not empty,
@@ -979,18 +987,21 @@
             // looks more like the intent.
             const auto& rrect =
                     shadowBounds.isRect() && !shadowClip.isEmpty() ? shadowClip : shadowBounds;
-            drawShadow(canvas, rrect, layer->shadow);
+            drawShadow(canvas, rrect, layer.shadow);
         }
 
-        const bool requiresLinearEffect = layer->colorTransform != mat4() ||
+        const float layerDimmingRatio = layer.whitePointNits <= 0.f
+                ? displayDimmingRatio
+                : (layer.whitePointNits / maxLayerWhitePoint) * displayDimmingRatio;
+
+        const bool requiresLinearEffect = layer.colorTransform != mat4() ||
                 (mUseColorManagement &&
-                 needsToneMapping(layer->sourceDataspace, display.outputDataspace)) ||
-                (display.sdrWhitePointNits > 0.f &&
-                 display.sdrWhitePointNits != display.maxLuminance);
+                 needsToneMapping(layer.sourceDataspace, display.outputDataspace)) ||
+                !equalsWithinMargin(1.f, layerDimmingRatio, 0.001f);
 
         // quick abort from drawing the remaining portion of the layer
-        if (layer->skipContentDraw ||
-            (layer->alpha == 0 && !requiresLinearEffect && !layer->disableBlending &&
+        if (layer.skipContentDraw ||
+            (layer.alpha == 0 && !requiresLinearEffect && !layer.disableBlending &&
              (!displayColorTransform || displayColorTransform->isAlphaUnchanged()))) {
             continue;
         }
@@ -1000,13 +1011,13 @@
         // management is a no-op.
         const ui::Dataspace layerDataspace = (!mUseColorManagement || requiresLinearEffect)
                 ? dstDataspace
-                : layer->sourceDataspace;
+                : layer.sourceDataspace;
 
         SkPaint paint;
-        if (layer->source.buffer.buffer) {
+        if (layer.source.buffer.buffer) {
             ATRACE_NAME("DrawImage");
-            validateInputBufferUsage(layer->source.buffer.buffer->getBuffer());
-            const auto& item = layer->source.buffer;
+            validateInputBufferUsage(layer.source.buffer.buffer->getBuffer());
+            const auto& item = layer.source.buffer;
             std::shared_ptr<AutoBackendTexture::LocalRef> imageTextureRef = nullptr;
 
             if (const auto& iter = cache.find(item.buffer->getBuffer()->getId());
@@ -1025,8 +1036,8 @@
 
             // if the layer's buffer has a fence, then we must must respect the fence prior to using
             // the buffer.
-            if (layer->source.buffer.fence != nullptr) {
-                waitFence(layer->source.buffer.fence->get());
+            if (layer.source.buffer.fence != nullptr) {
+                waitFence(layer.source.buffer.fence->get());
             }
 
             // isOpaque means we need to ignore the alpha in the image,
@@ -1070,7 +1081,7 @@
 
             sk_sp<SkShader> shader;
 
-            if (layer->source.buffer.useTextureFiltering) {
+            if (layer.source.buffer.useTextureFiltering) {
                 shader = image->makeShader(SkTileMode::kClamp, SkTileMode::kClamp,
                                            SkSamplingOptions(
                                                    {SkFilterMode::kLinear, SkMipmapMode::kNone}),
@@ -1085,28 +1096,82 @@
                                                            toSkColorSpace(layerDataspace)));
             }
 
-            paint.setShader(createRuntimeEffectShader(shader, layer, display,
-                                                      !item.isOpaque && item.usePremultipliedAlpha,
-                                                      requiresLinearEffect));
-            paint.setAlphaf(layer->alpha);
+            paint.setShader(createRuntimeEffectShader(
+                    RuntimeEffectShaderParameters{.shader = shader,
+                                                  .layer = layer,
+                                                  .display = display,
+                                                  .undoPremultipliedAlpha = !item.isOpaque &&
+                                                          item.usePremultipliedAlpha,
+                                                  .requiresLinearEffect = requiresLinearEffect,
+                                                  .layerDimmingRatio = layerDimmingRatio}));
+
+            // Turn on dithering when dimming beyond this threshold.
+            static constexpr float kDimmingThreshold = 0.2f;
+            if (layerDimmingRatio <= kDimmingThreshold) {
+                paint.setDither(true);
+            }
+            paint.setAlphaf(layer.alpha);
+
+            if (imageTextureRef->colorType() == kAlpha_8_SkColorType) {
+                LOG_ALWAYS_FATAL_IF(layer.disableBlending, "Cannot disableBlending with A8");
+
+                // SysUI creates the alpha layer as a coverage layer, which is
+                // appropriate for the DPU. Use a color matrix to convert it to
+                // a mask.
+                // TODO (b/219525258): Handle input as a mask.
+                //
+                // The color matrix will convert A8 pixels with no alpha to
+                // black, as described by this vector. If the display handles
+                // the color transform, we need to invert it to find the color
+                // that will result in black after the DPU applies the transform.
+                SkV4 black{0.0f, 0.0f, 0.0f, 1.0f}; // r, g, b, a
+                if (display.colorTransform != mat4() && display.deviceHandlesColorTransform) {
+                    SkM44 colorSpaceMatrix = getSkM44(display.colorTransform);
+                    if (colorSpaceMatrix.invert(&colorSpaceMatrix)) {
+                        black = colorSpaceMatrix * black;
+                    } else {
+                        // We'll just have to use 0,0,0 as black, which should
+                        // be close to correct.
+                        ALOGI("Could not invert colorTransform!");
+                    }
+                }
+                SkColorMatrix colorMatrix(0, 0, 0, 0, black[0],
+                                          0, 0, 0, 0, black[1],
+                                          0, 0, 0, 0, black[2],
+                                          0, 0, 0, -1, 1);
+                if (display.colorTransform != mat4() && !display.deviceHandlesColorTransform) {
+                    // On the other hand, if the device doesn't handle it, we
+                    // have to apply it ourselves.
+                    colorMatrix.postConcat(toSkColorMatrix(display.colorTransform));
+                }
+                paint.setColorFilter(SkColorFilters::Matrix(colorMatrix));
+            }
         } else {
             ATRACE_NAME("DrawColor");
-            const auto color = layer->source.solidColor;
+            const auto color = layer.source.solidColor;
             sk_sp<SkShader> shader = SkShaders::Color(SkColor4f{.fR = color.r,
                                                                 .fG = color.g,
                                                                 .fB = color.b,
-                                                                .fA = layer->alpha},
+                                                                .fA = layer.alpha},
                                                       toSkColorSpace(layerDataspace));
-            paint.setShader(createRuntimeEffectShader(shader, layer, display,
-                                                      /* undoPremultipliedAlpha */ false,
-                                                      requiresLinearEffect));
+            paint.setShader(createRuntimeEffectShader(
+                    RuntimeEffectShaderParameters{.shader = shader,
+                                                  .layer = layer,
+                                                  .display = display,
+                                                  .undoPremultipliedAlpha = false,
+                                                  .requiresLinearEffect = requiresLinearEffect,
+                                                  .layerDimmingRatio = layerDimmingRatio}));
         }
 
-        if (layer->disableBlending) {
+        if (layer.disableBlending) {
             paint.setBlendMode(SkBlendMode::kSrc);
         }
 
-        paint.setColorFilter(displayColorTransform);
+        // An A8 buffer will already have the proper color filter attached to
+        // its paint, including the displayColorTransform as needed.
+        if (!paint.getColorFilter()) {
+            paint.setColorFilter(displayColorTransform);
+        }
 
         if (!roundRectClip.isEmpty()) {
             canvas->clipRRect(roundRectClip, true);
@@ -1131,13 +1196,11 @@
         activeSurface->flush();
     }
 
-    if (drawFence != nullptr) {
-        *drawFence = flush();
-    }
+    base::unique_fd drawFence = flush();
 
     // If flush failed or we don't support native fences, we need to force the
     // gl command stream to be executed.
-    bool requireSync = drawFence == nullptr || drawFence->get() < 0;
+    bool requireSync = drawFence.get() < 0;
     if (requireSync) {
         ATRACE_BEGIN("Submit(sync=true)");
     } else {
@@ -1149,11 +1212,13 @@
         ALOGE("Failed to flush RenderEngine commands");
         // Chances are, something illegal happened (either the caller passed
         // us bad parameters, or we messed up our shader generation).
-        return INVALID_OPERATION;
+        resultPromise->set_value({INVALID_OPERATION, std::move(drawFence)});
+        return;
     }
 
     // checkErrors();
-    return NO_ERROR;
+    resultPromise->set_value({NO_ERROR, std::move(drawFence)});
+    return;
 }
 
 inline SkRect SkiaGLRenderEngine::getSkRect(const FloatRect& rect) {
@@ -1164,6 +1229,73 @@
     return SkRect::MakeLTRB(rect.left, rect.top, rect.right, rect.bottom);
 }
 
+/**
+ *  Verifies that common, simple bounds + clip combinations can be converted into
+ *  a single RRect draw call returning true if possible. If true the radii parameter
+ *  will be filled with the correct radii values that combined with bounds param will
+ *  produce the insected roundRect. If false, the returned state of the radii param is undefined.
+ */
+static bool intersectionIsRoundRect(const SkRect& bounds, const SkRect& crop,
+                                    const SkRect& insetCrop, float cornerRadius,
+                                    SkVector radii[4]) {
+    const bool leftEqual = bounds.fLeft == crop.fLeft;
+    const bool topEqual = bounds.fTop == crop.fTop;
+    const bool rightEqual = bounds.fRight == crop.fRight;
+    const bool bottomEqual = bounds.fBottom == crop.fBottom;
+
+    // In the event that the corners of the bounds only partially align with the crop we
+    // need to ensure that the resulting shape can still be represented as a round rect.
+    // In particular the round rect implementation will scale the value of all corner radii
+    // if the sum of the radius along any edge is greater than the length of that edge.
+    // See https://www.w3.org/TR/css-backgrounds-3/#corner-overlap
+    const bool requiredWidth = bounds.width() > (cornerRadius * 2);
+    const bool requiredHeight = bounds.height() > (cornerRadius * 2);
+    if (!requiredWidth || !requiredHeight) {
+        return false;
+    }
+
+    // Check each cropped corner to ensure that it exactly matches the crop or its corner is
+    // contained within the cropped shape and does not need rounded.
+    // compute the UpperLeft corner radius
+    if (leftEqual && topEqual) {
+        radii[0].set(cornerRadius, cornerRadius);
+    } else if ((leftEqual && bounds.fTop >= insetCrop.fTop) ||
+               (topEqual && bounds.fLeft >= insetCrop.fLeft)) {
+        radii[0].set(0, 0);
+    } else {
+        return false;
+    }
+    // compute the UpperRight corner radius
+    if (rightEqual && topEqual) {
+        radii[1].set(cornerRadius, cornerRadius);
+    } else if ((rightEqual && bounds.fTop >= insetCrop.fTop) ||
+               (topEqual && bounds.fRight <= insetCrop.fRight)) {
+        radii[1].set(0, 0);
+    } else {
+        return false;
+    }
+    // compute the BottomRight corner radius
+    if (rightEqual && bottomEqual) {
+        radii[2].set(cornerRadius, cornerRadius);
+    } else if ((rightEqual && bounds.fBottom <= insetCrop.fBottom) ||
+               (bottomEqual && bounds.fRight <= insetCrop.fRight)) {
+        radii[2].set(0, 0);
+    } else {
+        return false;
+    }
+    // compute the BottomLeft corner radius
+    if (leftEqual && bottomEqual) {
+        radii[3].set(cornerRadius, cornerRadius);
+    } else if ((leftEqual && bounds.fBottom <= insetCrop.fBottom) ||
+               (bottomEqual && bounds.fLeft >= insetCrop.fLeft)) {
+        radii[3].set(0, 0);
+    } else {
+        return false;
+    }
+
+    return true;
+}
+
 inline std::pair<SkRRect, SkRRect> SkiaGLRenderEngine::getBoundsAndClip(const FloatRect& boundsRect,
                                                                         const FloatRect& cropRect,
                                                                         const float cornerRadius) {
@@ -1181,66 +1313,20 @@
         // converting them to a single RRect draw. It is possible there are other cases
         // that can be converted.
         if (crop.contains(bounds)) {
-            bool intersectionIsRoundRect = true;
-            // check each cropped corner to ensure that it exactly matches the crop or is full
-            SkVector radii[4];
-
             const auto insetCrop = crop.makeInset(cornerRadius, cornerRadius);
-
-            const bool leftEqual = bounds.fLeft == crop.fLeft;
-            const bool topEqual = bounds.fTop == crop.fTop;
-            const bool rightEqual = bounds.fRight == crop.fRight;
-            const bool bottomEqual = bounds.fBottom == crop.fBottom;
-
-            // compute the UpperLeft corner radius
-            if (leftEqual && topEqual) {
-                radii[0].set(cornerRadius, cornerRadius);
-            } else if ((leftEqual && bounds.fTop >= insetCrop.fTop) ||
-                       (topEqual && bounds.fLeft >= insetCrop.fLeft) ||
-                       insetCrop.contains(bounds.fLeft, bounds.fTop)) {
-                radii[0].set(0, 0);
-            } else {
-                intersectionIsRoundRect = false;
-            }
-            // compute the UpperRight corner radius
-            if (rightEqual && topEqual) {
-                radii[1].set(cornerRadius, cornerRadius);
-            } else if ((rightEqual && bounds.fTop >= insetCrop.fTop) ||
-                       (topEqual && bounds.fRight <= insetCrop.fRight) ||
-                       insetCrop.contains(bounds.fRight, bounds.fTop)) {
-                radii[1].set(0, 0);
-            } else {
-                intersectionIsRoundRect = false;
-            }
-            // compute the BottomRight corner radius
-            if (rightEqual && bottomEqual) {
-                radii[2].set(cornerRadius, cornerRadius);
-            } else if ((rightEqual && bounds.fBottom <= insetCrop.fBottom) ||
-                       (bottomEqual && bounds.fRight <= insetCrop.fRight) ||
-                       insetCrop.contains(bounds.fRight, bounds.fBottom)) {
-                radii[2].set(0, 0);
-            } else {
-                intersectionIsRoundRect = false;
-            }
-            // compute the BottomLeft corner radius
-            if (leftEqual && bottomEqual) {
-                radii[3].set(cornerRadius, cornerRadius);
-            } else if ((leftEqual && bounds.fBottom <= insetCrop.fBottom) ||
-                       (bottomEqual && bounds.fLeft >= insetCrop.fLeft) ||
-                       insetCrop.contains(bounds.fLeft, bounds.fBottom)) {
-                radii[3].set(0, 0);
-            } else {
-                intersectionIsRoundRect = false;
+            if (insetCrop.contains(bounds)) {
+                return {SkRRect::MakeRect(bounds), clip}; // clip is empty - no rounding required
             }
 
-            if (intersectionIsRoundRect) {
+            SkVector radii[4];
+            if (intersectionIsRoundRect(bounds, crop, insetCrop, cornerRadius, radii)) {
                 SkRRect intersectionBounds;
                 intersectionBounds.setRectRadii(bounds, radii);
                 return {intersectionBounds, clip};
             }
         }
 
-        // we didn't it any of our fast paths so set the clip to the cropRect
+        // we didn't hit any of our fast paths so set the clip to the cropRect
         clip.setRectXY(crop, cornerRadius, cornerRadius);
     }
 
@@ -1249,13 +1335,13 @@
     return {SkRRect::MakeRect(bounds), clip};
 }
 
-inline bool SkiaGLRenderEngine::layerHasBlur(const LayerSettings* layer,
+inline bool SkiaGLRenderEngine::layerHasBlur(const LayerSettings& layer,
                                              bool colorTransformModifiesAlpha) {
-    if (layer->backgroundBlurRadius > 0 || layer->blurRegions.size()) {
+    if (layer.backgroundBlurRadius > 0 || layer.blurRegions.size()) {
         // return false if the content is opaque and would therefore occlude the blur
-        const bool opaqueContent = !layer->source.buffer.buffer || layer->source.buffer.isOpaque;
-        const bool opaqueAlpha = layer->alpha == 1.0f && !colorTransformModifiesAlpha;
-        return layer->skipContentDraw || !(opaqueContent && opaqueAlpha);
+        const bool opaqueContent = !layer.source.buffer.buffer || layer.source.buffer.isOpaque;
+        const bool opaqueAlpha = layer.alpha == 1.0f && !colorTransformModifiesAlpha;
+        return layer.skipContentDraw || !(opaqueContent && opaqueAlpha);
     }
     return false;
 }
diff --git a/libs/renderengine/skia/SkiaGLRenderEngine.h b/libs/renderengine/skia/SkiaGLRenderEngine.h
index e1162f5..a650313 100644
--- a/libs/renderengine/skia/SkiaGLRenderEngine.h
+++ b/libs/renderengine/skia/SkiaGLRenderEngine.h
@@ -54,11 +54,6 @@
     ~SkiaGLRenderEngine() override EXCLUDES(mRenderingMutex);
 
     std::future<void> primeCache() override;
-    status_t drawLayers(const DisplaySettings& display,
-                        const std::vector<const LayerSettings*>& layers,
-                        const std::shared_ptr<ExternalTexture>& buffer,
-                        const bool useFramebufferCache, base::unique_fd&& bufferFence,
-                        base::unique_fd* drawFence) override;
     void cleanupPostRender() override;
     void cleanFramebufferCache() override{};
     int getContextPriority() override;
@@ -77,6 +72,11 @@
     void mapExternalTextureBuffer(const sp<GraphicBuffer>& buffer, bool isRenderable) override;
     void unmapExternalTextureBuffer(const sp<GraphicBuffer>& buffer) override;
     bool canSkipPostRenderCleanup() const override;
+    void drawLayersInternal(const std::shared_ptr<std::promise<RenderEngineResult>>&& resultPromise,
+                            const DisplaySettings& display,
+                            const std::vector<LayerSettings>& layers,
+                            const std::shared_ptr<ExternalTexture>& buffer,
+                            const bool useFramebufferCache, base::unique_fd&& bufferFence) override;
 
 private:
     static EGLConfig chooseEglConfig(EGLDisplay display, int format, bool logConfig);
@@ -92,7 +92,7 @@
     inline SkRect getSkRect(const Rect& layer);
     inline std::pair<SkRRect, SkRRect> getBoundsAndClip(const FloatRect& bounds,
                                                         const FloatRect& crop, float cornerRadius);
-    inline bool layerHasBlur(const LayerSettings* layer, bool colorTransformModifiesAlpha);
+    inline bool layerHasBlur(const LayerSettings& layer, bool colorTransformModifiesAlpha);
     inline SkColor getSkColor(const vec4& color);
     inline SkM44 getSkM44(const mat4& matrix);
     inline SkPoint3 getSkPoint3(const vec3& vector);
@@ -106,13 +106,18 @@
     void initCanvas(SkCanvas* canvas, const DisplaySettings& display);
     void drawShadow(SkCanvas* canvas, const SkRRect& casterRRect,
                     const ShadowSettings& shadowSettings);
+
     // If requiresLinearEffect is true or the layer has a stretchEffect a new shader is returned.
     // Otherwise it returns the input shader.
-    sk_sp<SkShader> createRuntimeEffectShader(sk_sp<SkShader> shader,
-                                              const LayerSettings* layer,
-                                              const DisplaySettings& display,
-                                              bool undoPremultipliedAlpha,
-                                              bool requiresLinearEffect);
+    struct RuntimeEffectShaderParameters {
+        sk_sp<SkShader> shader;
+        const LayerSettings& layer;
+        const DisplaySettings& display;
+        bool undoPremultipliedAlpha;
+        bool requiresLinearEffect;
+        float layerDimmingRatio;
+    };
+    sk_sp<SkShader> createRuntimeEffectShader(const RuntimeEffectShaderParameters&);
 
     EGLDisplay mEGLDisplay;
     EGLContext mEGLContext;
@@ -134,7 +139,8 @@
     // Cache of GL textures that we'll store per GraphicBuffer ID, shared between GPU contexts.
     std::unordered_map<GraphicBufferId, std::shared_ptr<AutoBackendTexture::LocalRef>> mTextureCache
             GUARDED_BY(mRenderingMutex);
-    std::unordered_map<LinearEffect, sk_sp<SkRuntimeEffect>, LinearEffectHasher> mRuntimeEffects;
+    std::unordered_map<shaders::LinearEffect, sk_sp<SkRuntimeEffect>, shaders::LinearEffectHasher>
+            mRuntimeEffects;
     AutoBackendTexture::CleanupManager mTextureCleanupMgr GUARDED_BY(mRenderingMutex);
 
     StretchShaderFactory mStretchShaderFactory;
diff --git a/libs/renderengine/skia/SkiaRenderEngine.cpp b/libs/renderengine/skia/SkiaRenderEngine.cpp
index 29175a2..1fb24f5 100644
--- a/libs/renderengine/skia/SkiaRenderEngine.cpp
+++ b/libs/renderengine/skia/SkiaRenderEngine.cpp
@@ -20,15 +20,15 @@
 
 #include "SkiaRenderEngine.h"
 
-#include <android-base/properties.h>
 #include <src/core/SkTraceEventCommon.h>
 
 namespace android {
 namespace renderengine {
 namespace skia {
-SkiaRenderEngine::SkiaRenderEngine(RenderEngineType type) : RenderEngine(type) {
-    SkAndroidFrameworkTraceUtil::setEnableTracing(
-            base::GetBoolProperty(PROPERTY_SKIA_ATRACE_ENABLED, false));
+SkiaRenderEngine::SkiaRenderEngine(RenderEngineType type) : RenderEngine(type) {}
+
+void SkiaRenderEngine::setEnableTracing(bool tracingEnabled) {
+    SkAndroidFrameworkTraceUtil::setEnableTracing(tracingEnabled);
 }
 } // namespace skia
 } // namespace renderengine
diff --git a/libs/renderengine/skia/SkiaRenderEngine.h b/libs/renderengine/skia/SkiaRenderEngine.h
index 7cd9eca..5d10b6f 100644
--- a/libs/renderengine/skia/SkiaRenderEngine.h
+++ b/libs/renderengine/skia/SkiaRenderEngine.h
@@ -44,22 +44,23 @@
     virtual void deleteTextures(size_t /*count*/, uint32_t const* /*names*/) override{};
     virtual bool isProtected() const override { return false; } // mInProtectedContext; }
     virtual bool supportsProtectedContent() const override { return false; };
-    virtual status_t drawLayers(const DisplaySettings& /*display*/,
-                                const std::vector<const LayerSettings*>& /*layers*/,
-                                const std::shared_ptr<ExternalTexture>& /*buffer*/,
-                                const bool /*useFramebufferCache*/,
-                                base::unique_fd&& /*bufferFence*/,
-                                base::unique_fd* /*drawFence*/) override {
-        return 0;
-    };
     virtual int getContextPriority() override { return 0; }
     virtual void assertShadersCompiled(int numShaders) {}
     virtual int reportShadersCompiled() { return 0; }
+    virtual void setEnableTracing(bool tracingEnabled) override;
 
 protected:
     virtual void mapExternalTextureBuffer(const sp<GraphicBuffer>& /*buffer*/,
                                           bool /*isRenderable*/) override = 0;
     virtual void unmapExternalTextureBuffer(const sp<GraphicBuffer>& /*buffer*/) override = 0;
+
+    virtual void drawLayersInternal(
+            const std::shared_ptr<std::promise<RenderEngineResult>>&& resultPromise,
+            const DisplaySettings& display, const std::vector<LayerSettings>& layers,
+            const std::shared_ptr<ExternalTexture>& buffer, const bool useFramebufferCache,
+            base::unique_fd&& bufferFence) override {
+        resultPromise->set_value({NO_ERROR, base::unique_fd()});
+    };
 };
 
 } // namespace skia
diff --git a/libs/renderengine/skia/filters/BlurFilter.cpp b/libs/renderengine/skia/filters/BlurFilter.cpp
index 7c5bee9..63cc02b 100644
--- a/libs/renderengine/skia/filters/BlurFilter.cpp
+++ b/libs/renderengine/skia/filters/BlurFilter.cpp
@@ -1,5 +1,5 @@
 /*
- * Copyright 2020 The Android Open Source Project
+ * 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.
@@ -15,7 +15,6 @@
  */
 
 #define ATRACE_TAG ATRACE_TAG_GRAPHICS
-
 #include "BlurFilter.h"
 #include <SkCanvas.h>
 #include <SkData.h>
@@ -32,40 +31,14 @@
 namespace renderengine {
 namespace skia {
 
-BlurFilter::BlurFilter() {
-    SkString blurString(R"(
-        uniform shader input;
-        uniform float2 in_blurOffset;
-        uniform float2 in_maxSizeXY;
-
-        half4 main(float2 xy) {
-            half4 c = sample(input, xy);
-            c += sample(input, float2( clamp( in_blurOffset.x + xy.x, 0, in_maxSizeXY.x),
-                                       clamp(in_blurOffset.y + xy.y, 0, in_maxSizeXY.y)));
-            c += sample(input, float2( clamp( in_blurOffset.x + xy.x, 0, in_maxSizeXY.x),
-                                       clamp(-in_blurOffset.y + xy.y, 0, in_maxSizeXY.y)));
-            c += sample(input, float2( clamp( -in_blurOffset.x + xy.x, 0, in_maxSizeXY.x),
-                                       clamp(in_blurOffset.y + xy.y, 0, in_maxSizeXY.y)));
-            c += sample(input, float2( clamp( -in_blurOffset.x + xy.x, 0, in_maxSizeXY.x),
-                                       clamp(-in_blurOffset.y + xy.y, 0, in_maxSizeXY.y)));
-
-            return half4(c.rgb * 0.2, 1.0);
-        }
-    )");
-
-    auto [blurEffect, error] = SkRuntimeEffect::MakeForShader(blurString);
-    if (!blurEffect) {
-        LOG_ALWAYS_FATAL("RuntimeShader error: %s", error.c_str());
-    }
-    mBlurEffect = std::move(blurEffect);
-
+static sk_sp<SkRuntimeEffect> createMixEffect() {
     SkString mixString(R"(
         uniform shader blurredInput;
         uniform shader originalInput;
         uniform float mixFactor;
 
         half4 main(float2 xy) {
-            return half4(mix(sample(originalInput, xy), sample(blurredInput, xy), mixFactor));
+            return half4(mix(originalInput.eval(xy), blurredInput.eval(xy), mixFactor)).rgb1;
         }
     )");
 
@@ -73,58 +46,12 @@
     if (!mixEffect) {
         LOG_ALWAYS_FATAL("RuntimeShader error: %s", mixError.c_str());
     }
-    mMixEffect = std::move(mixEffect);
+    return mixEffect;
 }
 
-sk_sp<SkImage> BlurFilter::generate(GrRecordingContext* context, const uint32_t blurRadius,
-                                    const sk_sp<SkImage> input, const SkRect& blurRect) const {
-    // Kawase is an approximation of Gaussian, but it behaves differently from it.
-    // A radius transformation is required for approximating them, and also to introduce
-    // non-integer steps, necessary to smoothly interpolate large radii.
-    float tmpRadius = (float)blurRadius / 2.0f;
-    float numberOfPasses = std::min(kMaxPasses, (uint32_t)ceil(tmpRadius));
-    float radiusByPasses = tmpRadius / (float)numberOfPasses;
-
-    // create blur surface with the bit depth and colorspace of the original surface
-    SkImageInfo scaledInfo = input->imageInfo().makeWH(std::ceil(blurRect.width() * kInputScale),
-                                                       std::ceil(blurRect.height() * kInputScale));
-
-    const float stepX = radiusByPasses;
-    const float stepY = radiusByPasses;
-
-    // For sampling Skia's API expects the inverse of what logically seems appropriate. In this
-    // case you might expect Translate(blurRect.fLeft, blurRect.fTop) X Scale(kInverseInputScale)
-    // but instead we must do the inverse.
-    SkMatrix blurMatrix = SkMatrix::Translate(-blurRect.fLeft, -blurRect.fTop);
-    blurMatrix.postScale(kInputScale, kInputScale);
-
-    // start by downscaling and doing the first blur pass
-    SkSamplingOptions linear(SkFilterMode::kLinear, SkMipmapMode::kNone);
-    SkRuntimeShaderBuilder blurBuilder(mBlurEffect);
-    blurBuilder.child("input") =
-            input->makeShader(SkTileMode::kClamp, SkTileMode::kClamp, linear, blurMatrix);
-    blurBuilder.uniform("in_blurOffset") = SkV2{stepX * kInputScale, stepY * kInputScale};
-    blurBuilder.uniform("in_maxSizeXY") =
-            SkV2{blurRect.width() * kInputScale, blurRect.height() * kInputScale};
-
-    sk_sp<SkImage> tmpBlur(blurBuilder.makeImage(context, nullptr, scaledInfo, false));
-
-    // And now we'll build our chain of scaled blur stages
-    for (auto i = 1; i < numberOfPasses; i++) {
-        const float stepScale = (float)i * kInputScale;
-        blurBuilder.child("input") =
-                tmpBlur->makeShader(SkTileMode::kClamp, SkTileMode::kClamp, linear);
-        blurBuilder.uniform("in_blurOffset") = SkV2{stepX * stepScale, stepY * stepScale};
-        blurBuilder.uniform("in_maxSizeXY") =
-                SkV2{blurRect.width() * kInputScale, blurRect.height() * kInputScale};
-        tmpBlur = blurBuilder.makeImage(context, nullptr, scaledInfo, false);
-    }
-
-    return tmpBlur;
-}
-
-static SkMatrix getShaderTransform(const SkCanvas* canvas, const SkRect& blurRect, float scale) {
-    // 1. Apply the blur shader matrix, which scales up the blured surface to its real size
+static SkMatrix getShaderTransform(const SkCanvas* canvas, const SkRect& blurRect,
+                                   const float scale) {
+    // 1. Apply the blur shader matrix, which scales up the blurred surface to its real size
     auto matrix = SkMatrix::Scale(scale, scale);
     // 2. Since the blurred surface has the size of the layer, we align it with the
     // top left corner of the layer position.
@@ -139,6 +66,14 @@
     return matrix;
 }
 
+BlurFilter::BlurFilter(const float maxCrossFadeRadius)
+      : mMaxCrossFadeRadius(maxCrossFadeRadius),
+        mMixEffect(maxCrossFadeRadius > 0 ? createMixEffect() : nullptr) {}
+
+float BlurFilter::getMaxCrossFadeRadius() const {
+    return mMaxCrossFadeRadius;
+}
+
 void BlurFilter::drawBlurRegion(SkCanvas* canvas, const SkRRect& effectRegion,
                                 const uint32_t blurRadius, const float blurAlpha,
                                 const SkRect& blurRect, sk_sp<SkImage> blurredImage,
@@ -153,7 +88,7 @@
     const auto blurShader = blurredImage->makeShader(SkTileMode::kClamp, SkTileMode::kClamp,
                                                      linearSampling, &blurMatrix);
 
-    if (blurRadius < kMaxCrossFadeRadius) {
+    if (blurRadius < mMaxCrossFadeRadius) {
         // For sampling Skia's API expects the inverse of what logically seems appropriate. In this
         // case you might expect the matrix to simply be the canvas matrix.
         SkMatrix inputMatrix;
@@ -166,9 +101,9 @@
         blurBuilder.child("originalInput") =
                 input->makeShader(SkTileMode::kClamp, SkTileMode::kClamp, linearSampling,
                                   inputMatrix);
-        blurBuilder.uniform("mixFactor") = blurRadius / kMaxCrossFadeRadius;
+        blurBuilder.uniform("mixFactor") = blurRadius / mMaxCrossFadeRadius;
 
-        paint.setShader(blurBuilder.makeShader(nullptr, true));
+        paint.setShader(blurBuilder.makeShader());
     } else {
         paint.setShader(blurShader);
     }
diff --git a/libs/renderengine/skia/filters/BlurFilter.h b/libs/renderengine/skia/filters/BlurFilter.h
index 7110018..9cddc75 100644
--- a/libs/renderengine/skia/filters/BlurFilter.h
+++ b/libs/renderengine/skia/filters/BlurFilter.h
@@ -27,29 +27,19 @@
 namespace renderengine {
 namespace skia {
 
-/**
- * This is an implementation of a Kawase blur, as described in here:
- * https://community.arm.com/cfs-file/__key/communityserver-blogs-components-weblogfiles/
- * 00-00-00-20-66/siggraph2015_2D00_mmg_2D00_marius_2D00_notes.pdf
- */
 class BlurFilter {
 public:
     // Downsample FBO to improve performance
     static constexpr float kInputScale = 0.25f;
     // Downsample scale factor used to improve performance
     static constexpr float kInverseInputScale = 1.0f / kInputScale;
-    // Maximum number of render passes
-    static constexpr uint32_t kMaxPasses = 4;
-    // To avoid downscaling artifacts, we interpolate the blurred fbo with the full composited
-    // image, up to this radius.
-    static constexpr float kMaxCrossFadeRadius = 10.0f;
 
-    explicit BlurFilter();
-    virtual ~BlurFilter(){};
+    explicit BlurFilter(float maxCrossFadeRadius = 10.0f);
+    virtual ~BlurFilter(){}
 
     // Execute blur, saving it to a texture
-    sk_sp<SkImage> generate(GrRecordingContext* context, const uint32_t radius,
-                            const sk_sp<SkImage> blurInput, const SkRect& blurRect) const;
+    virtual sk_sp<SkImage> generate(GrRecordingContext* context, const uint32_t radius,
+                            const sk_sp<SkImage> blurInput, const SkRect& blurRect) const = 0;
 
     /**
      * Draw the blurred content (from the generate method) into the canvas.
@@ -61,13 +51,20 @@
      * @param blurredImage down-sampled blurred content that was produced by the generate() method
      * @param input original unblurred input that is used to crossfade with the blurredImage
      */
-    void drawBlurRegion(SkCanvas* canvas, const SkRRect& effectRegion, const uint32_t blurRadius,
-                        const float blurAlpha, const SkRect& blurRect, sk_sp<SkImage> blurredImage,
-                        sk_sp<SkImage> input);
+    void drawBlurRegion(SkCanvas* canvas, const SkRRect& effectRegion,
+                                const uint32_t blurRadius, const float blurAlpha,
+                                const SkRect& blurRect, sk_sp<SkImage> blurredImage,
+                                sk_sp<SkImage> input);
+
+    float getMaxCrossFadeRadius() const;
 
 private:
-    sk_sp<SkRuntimeEffect> mBlurEffect;
-    sk_sp<SkRuntimeEffect> mMixEffect;
+    // To avoid downscaling artifacts, we interpolate the blurred fbo with the full composited
+    // image, up to this radius.
+    const float mMaxCrossFadeRadius;
+
+    // Optional blend used for crossfade only if mMaxCrossFadeRadius > 0
+    const sk_sp<SkRuntimeEffect> mMixEffect;
 };
 
 } // namespace skia
diff --git a/libs/renderengine/skia/filters/GaussianBlurFilter.cpp b/libs/renderengine/skia/filters/GaussianBlurFilter.cpp
new file mode 100644
index 0000000..55867a9
--- /dev/null
+++ b/libs/renderengine/skia/filters/GaussianBlurFilter.cpp
@@ -0,0 +1,69 @@
+/*
+ * 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.
+ */
+
+#define ATRACE_TAG ATRACE_TAG_GRAPHICS
+
+#include "GaussianBlurFilter.h"
+#include <SkCanvas.h>
+#include <SkData.h>
+#include <SkPaint.h>
+#include <SkRRect.h>
+#include <SkRuntimeEffect.h>
+#include <SkImageFilters.h>
+#include <SkSize.h>
+#include <SkString.h>
+#include <SkSurface.h>
+#include <log/log.h>
+#include <utils/Trace.h>
+
+namespace android {
+namespace renderengine {
+namespace skia {
+
+// This constant approximates the scaling done in the software path's
+// "high quality" mode, in SkBlurMask::Blur() (1 / sqrt(3)).
+static const float BLUR_SIGMA_SCALE = 0.57735f;
+
+GaussianBlurFilter::GaussianBlurFilter(): BlurFilter(/* maxCrossFadeRadius= */ 0.0f) {}
+
+sk_sp<SkImage> GaussianBlurFilter::generate(GrRecordingContext* context, const uint32_t blurRadius,
+                                            const sk_sp<SkImage> input, const SkRect& blurRect)
+    const {
+    // Create blur surface with the bit depth and colorspace of the original surface
+    SkImageInfo scaledInfo = input->imageInfo().makeWH(std::ceil(blurRect.width() * kInputScale),
+                                                       std::ceil(blurRect.height() * kInputScale));
+    sk_sp<SkSurface> surface = SkSurface::MakeRenderTarget(context, SkBudgeted::kNo, scaledInfo);
+
+    SkPaint paint;
+    paint.setBlendMode(SkBlendMode::kSrc);
+    paint.setImageFilter(SkImageFilters::Blur(
+                blurRadius * kInputScale * BLUR_SIGMA_SCALE,
+                blurRadius * kInputScale * BLUR_SIGMA_SCALE,
+                SkTileMode::kClamp, nullptr));
+
+    surface->getCanvas()->drawImageRect(
+            input,
+            blurRect,
+            SkRect::MakeWH(scaledInfo.width(), scaledInfo.height()),
+            SkSamplingOptions{SkFilterMode::kLinear, SkMipmapMode::kNone},
+            &paint,
+            SkCanvas::SrcRectConstraint::kFast_SrcRectConstraint);
+    return surface->makeImageSnapshot();
+}
+
+} // namespace skia
+} // namespace renderengine
+} // namespace android
diff --git a/libs/renderengine/skia/filters/GaussianBlurFilter.h b/libs/renderengine/skia/filters/GaussianBlurFilter.h
new file mode 100644
index 0000000..a4febd2
--- /dev/null
+++ b/libs/renderengine/skia/filters/GaussianBlurFilter.h
@@ -0,0 +1,47 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include "BlurFilter.h"
+#include <SkCanvas.h>
+#include <SkImage.h>
+#include <SkRuntimeEffect.h>
+#include <SkSurface.h>
+
+using namespace std;
+
+namespace android {
+namespace renderengine {
+namespace skia {
+
+/**
+ * This is an implementation of a Gaussian blur using Skia's built-in GaussianBlur filter.
+ */
+class GaussianBlurFilter: public BlurFilter {
+public:
+    explicit GaussianBlurFilter();
+    virtual ~GaussianBlurFilter(){}
+
+    // Execute blur, saving it to a texture
+    sk_sp<SkImage> generate(GrRecordingContext* context, const uint32_t radius,
+                            const sk_sp<SkImage> blurInput, const SkRect& blurRect) const override;
+
+};
+
+} // namespace skia
+} // namespace renderengine
+} // namespace android
diff --git a/libs/renderengine/skia/filters/KawaseBlurFilter.cpp b/libs/renderengine/skia/filters/KawaseBlurFilter.cpp
new file mode 100644
index 0000000..bfde06f
--- /dev/null
+++ b/libs/renderengine/skia/filters/KawaseBlurFilter.cpp
@@ -0,0 +1,99 @@
+/*
+ * 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.
+ */
+
+#define ATRACE_TAG ATRACE_TAG_GRAPHICS
+
+#include "KawaseBlurFilter.h"
+#include <SkCanvas.h>
+#include <SkData.h>
+#include <SkPaint.h>
+#include <SkRRect.h>
+#include <SkRuntimeEffect.h>
+#include <SkSize.h>
+#include <SkString.h>
+#include <SkSurface.h>
+#include <log/log.h>
+#include <utils/Trace.h>
+
+namespace android {
+namespace renderengine {
+namespace skia {
+
+KawaseBlurFilter::KawaseBlurFilter(): BlurFilter() {
+    SkString blurString(R"(
+        uniform shader child;
+        uniform float in_blurOffset;
+
+        half4 main(float2 xy) {
+            half4 c = child.eval(xy);
+            c += child.eval(xy + float2(+in_blurOffset, +in_blurOffset));
+            c += child.eval(xy + float2(+in_blurOffset, -in_blurOffset));
+            c += child.eval(xy + float2(-in_blurOffset, -in_blurOffset));
+            c += child.eval(xy + float2(-in_blurOffset, +in_blurOffset));
+            return half4(c.rgb * 0.2, 1.0);
+        }
+    )");
+
+    auto [blurEffect, error] = SkRuntimeEffect::MakeForShader(blurString);
+    if (!blurEffect) {
+        LOG_ALWAYS_FATAL("RuntimeShader error: %s", error.c_str());
+    }
+    mBlurEffect = std::move(blurEffect);
+}
+
+sk_sp<SkImage> KawaseBlurFilter::generate(GrRecordingContext* context, const uint32_t blurRadius,
+                                          const sk_sp<SkImage> input, const SkRect& blurRect)
+    const {
+    // Kawase is an approximation of Gaussian, but it behaves differently from it.
+    // A radius transformation is required for approximating them, and also to introduce
+    // non-integer steps, necessary to smoothly interpolate large radii.
+    float tmpRadius = (float)blurRadius / 2.0f;
+    float numberOfPasses = std::min(kMaxPasses, (uint32_t)ceil(tmpRadius));
+    float radiusByPasses = tmpRadius / (float)numberOfPasses;
+
+    // create blur surface with the bit depth and colorspace of the original surface
+    SkImageInfo scaledInfo = input->imageInfo().makeWH(std::ceil(blurRect.width() * kInputScale),
+                                                       std::ceil(blurRect.height() * kInputScale));
+
+    // For sampling Skia's API expects the inverse of what logically seems appropriate. In this
+    // case you might expect Translate(blurRect.fLeft, blurRect.fTop) X Scale(kInverseInputScale)
+    // but instead we must do the inverse.
+    SkMatrix blurMatrix = SkMatrix::Translate(-blurRect.fLeft, -blurRect.fTop);
+    blurMatrix.postScale(kInputScale, kInputScale);
+
+    // start by downscaling and doing the first blur pass
+    SkSamplingOptions linear(SkFilterMode::kLinear, SkMipmapMode::kNone);
+    SkRuntimeShaderBuilder blurBuilder(mBlurEffect);
+    blurBuilder.child("child") =
+            input->makeShader(SkTileMode::kClamp, SkTileMode::kClamp, linear, blurMatrix);
+    blurBuilder.uniform("in_blurOffset") = radiusByPasses * kInputScale;
+
+    sk_sp<SkImage> tmpBlur(blurBuilder.makeImage(context, nullptr, scaledInfo, false));
+
+    // And now we'll build our chain of scaled blur stages
+    for (auto i = 1; i < numberOfPasses; i++) {
+        blurBuilder.child("child") =
+                tmpBlur->makeShader(SkTileMode::kClamp, SkTileMode::kClamp, linear);
+        blurBuilder.uniform("in_blurOffset") = (float) i * radiusByPasses * kInputScale;
+        tmpBlur = blurBuilder.makeImage(context, nullptr, scaledInfo, false);
+    }
+
+    return tmpBlur;
+}
+
+} // namespace skia
+} // namespace renderengine
+} // namespace android
diff --git a/libs/renderengine/skia/filters/KawaseBlurFilter.h b/libs/renderengine/skia/filters/KawaseBlurFilter.h
new file mode 100644
index 0000000..0ac5ac8
--- /dev/null
+++ b/libs/renderengine/skia/filters/KawaseBlurFilter.h
@@ -0,0 +1,54 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include "BlurFilter.h"
+#include <SkCanvas.h>
+#include <SkImage.h>
+#include <SkRuntimeEffect.h>
+#include <SkSurface.h>
+
+using namespace std;
+
+namespace android {
+namespace renderengine {
+namespace skia {
+
+/**
+ * This is an implementation of a Kawase blur, as described in here:
+ * https://community.arm.com/cfs-file/__key/communityserver-blogs-components-weblogfiles/
+ * 00-00-00-20-66/siggraph2015_2D00_mmg_2D00_marius_2D00_notes.pdf
+ */
+class KawaseBlurFilter: public BlurFilter {
+public:
+    // Maximum number of render passes
+    static constexpr uint32_t kMaxPasses = 4;
+
+    explicit KawaseBlurFilter();
+    virtual ~KawaseBlurFilter(){}
+
+    // Execute blur, saving it to a texture
+    sk_sp<SkImage> generate(GrRecordingContext* context, const uint32_t radius,
+                            const sk_sp<SkImage> blurInput, const SkRect& blurRect) const override;
+
+private:
+    sk_sp<SkRuntimeEffect> mBlurEffect;
+};
+
+} // namespace skia
+} // namespace renderengine
+} // namespace android
diff --git a/libs/renderengine/skia/filters/LinearEffect.cpp b/libs/renderengine/skia/filters/LinearEffect.cpp
index fc45af9..d479606 100644
--- a/libs/renderengine/skia/filters/LinearEffect.cpp
+++ b/libs/renderengine/skia/filters/LinearEffect.cpp
@@ -19,423 +19,19 @@
 #define ATRACE_TAG ATRACE_TAG_GRAPHICS
 
 #include <SkString.h>
+#include <log/log.h>
+#include <shaders/shaders.h>
 #include <utils/Trace.h>
 
-#include <optional>
-
-#include "log/log.h"
-#include "math/mat4.h"
-#include "system/graphics-base-v1.0.h"
-#include "ui/ColorSpace.h"
+#include <math/mat4.h>
 
 namespace android {
 namespace renderengine {
 namespace skia {
 
-static void generateEOTF(ui::Dataspace dataspace, SkString& shader) {
-    switch (dataspace & HAL_DATASPACE_TRANSFER_MASK) {
-        case HAL_DATASPACE_TRANSFER_ST2084:
-            shader.append(R"(
-
-                float3 EOTF(float3 color) {
-                    float m1 = (2610.0 / 4096.0) / 4.0;
-                    float m2 = (2523.0 / 4096.0) * 128.0;
-                    float c1 = (3424.0 / 4096.0);
-                    float c2 = (2413.0 / 4096.0) * 32.0;
-                    float c3 = (2392.0 / 4096.0) * 32.0;
-
-                    float3 tmp = pow(clamp(color, 0.0, 1.0), 1.0 / float3(m2));
-                    tmp = max(tmp - c1, 0.0) / (c2 - c3 * tmp);
-                    return pow(tmp, 1.0 / float3(m1));
-                }
-            )");
-            break;
-        case HAL_DATASPACE_TRANSFER_HLG:
-            shader.append(R"(
-                float EOTF_channel(float channel) {
-                    const float a = 0.17883277;
-                    const float b = 0.28466892;
-                    const float c = 0.55991073;
-                    return channel <= 0.5 ? channel * channel / 3.0 :
-                            (exp((channel - c) / a) + b) / 12.0;
-                }
-
-                float3 EOTF(float3 color) {
-                    return float3(EOTF_channel(color.r), EOTF_channel(color.g),
-                            EOTF_channel(color.b));
-                }
-            )");
-            break;
-        case HAL_DATASPACE_TRANSFER_LINEAR:
-            shader.append(R"(
-                float3 EOTF(float3 color) {
-                    return color;
-                }
-            )");
-            break;
-        case HAL_DATASPACE_TRANSFER_SRGB:
-        default:
-            shader.append(R"(
-
-                float EOTF_sRGB(float srgb) {
-                    return srgb <= 0.04045 ? srgb / 12.92 : pow((srgb + 0.055) / 1.055, 2.4);
-                }
-
-                float3 EOTF_sRGB(float3 srgb) {
-                    return float3(EOTF_sRGB(srgb.r), EOTF_sRGB(srgb.g), EOTF_sRGB(srgb.b));
-                }
-
-                float3 EOTF(float3 srgb) {
-                    return sign(srgb.rgb) * EOTF_sRGB(abs(srgb.rgb));
-                }
-            )");
-            break;
-    }
-}
-
-static void generateXYZTransforms(SkString& shader) {
-    shader.append(R"(
-        uniform float4x4 in_rgbToXyz;
-        uniform float4x4 in_xyzToRgb;
-        float3 ToXYZ(float3 rgb) {
-            return clamp((in_rgbToXyz * float4(rgb, 1.0)).rgb, 0.0, 1.0);
-        }
-
-        float3 ToRGB(float3 xyz) {
-            return clamp((in_xyzToRgb * float4(xyz, 1.0)).rgb, 0.0, 1.0);
-        }
-    )");
-}
-
-// Conversion from relative light to absolute light (maps from [0, 1] to [0, maxNits])
-static void generateLuminanceScalesForOOTF(ui::Dataspace inputDataspace, SkString& shader) {
-    switch (inputDataspace & HAL_DATASPACE_TRANSFER_MASK) {
-        case HAL_DATASPACE_TRANSFER_ST2084:
-            shader.append(R"(
-                    float3 ScaleLuminance(float3 xyz) {
-                        return xyz * 10000.0;
-                    }
-                )");
-            break;
-        case HAL_DATASPACE_TRANSFER_HLG:
-            shader.append(R"(
-                    float3 ScaleLuminance(float3 xyz) {
-                        return xyz * 1000.0 * pow(xyz.y, 0.2);
-                    }
-                )");
-            break;
-        default:
-            shader.append(R"(
-                    float3 ScaleLuminance(float3 xyz) {
-                        return xyz * in_inputMaxLuminance;
-                    }
-                )");
-            break;
-    }
-}
-
-static void generateToneMapInterpolation(ui::Dataspace inputDataspace,
-                                         ui::Dataspace outputDataspace, SkString& shader) {
-    switch (inputDataspace & HAL_DATASPACE_TRANSFER_MASK) {
-        case HAL_DATASPACE_TRANSFER_ST2084:
-        case HAL_DATASPACE_TRANSFER_HLG:
-            switch (outputDataspace & HAL_DATASPACE_TRANSFER_MASK) {
-                case HAL_DATASPACE_TRANSFER_ST2084:
-                    shader.append(R"(
-                            float3 ToneMap(float3 xyz) {
-                                return xyz;
-                            }
-                        )");
-                    break;
-                case HAL_DATASPACE_TRANSFER_HLG:
-                    // PQ has a wider luminance range (10,000 nits vs. 1,000 nits) than HLG, so
-                    // we'll clamp the luminance range in case we're mapping from PQ input to HLG
-                    // output.
-                    shader.append(R"(
-                            float3 ToneMap(float3 xyz) {
-                                return clamp(xyz, 0.0, 1000.0);
-                            }
-                        )");
-                    break;
-                default:
-                    // Here we're mapping from HDR to SDR content, so interpolate using a Hermitian
-                    // polynomial onto the smaller luminance range.
-                    shader.append(R"(
-                            float3 ToneMap(float3 xyz) {
-                                float maxInLumi = in_inputMaxLuminance;
-                                float maxOutLumi = in_displayMaxLuminance;
-
-                                float nits = xyz.y;
-
-                                // if the max input luminance is less than what we can output then
-                                // no tone mapping is needed as all color values will be in range.
-                                if (maxInLumi <= maxOutLumi) {
-                                    return xyz;
-                                } else {
-
-                                    // three control points
-                                    const float x0 = 10.0;
-                                    const float y0 = 17.0;
-                                    float x1 = maxOutLumi * 0.75;
-                                    float y1 = x1;
-                                    float x2 = x1 + (maxInLumi - x1) / 2.0;
-                                    float y2 = y1 + (maxOutLumi - y1) * 0.75;
-
-                                    // horizontal distances between the last three control points
-                                    float h12 = x2 - x1;
-                                    float h23 = maxInLumi - x2;
-                                    // tangents at the last three control points
-                                    float m1 = (y2 - y1) / h12;
-                                    float m3 = (maxOutLumi - y2) / h23;
-                                    float m2 = (m1 + m3) / 2.0;
-
-                                    if (nits < x0) {
-                                        // scale [0.0, x0] to [0.0, y0] linearly
-                                        float slope = y0 / x0;
-                                        return xyz * slope;
-                                    } else if (nits < x1) {
-                                        // scale [x0, x1] to [y0, y1] linearly
-                                        float slope = (y1 - y0) / (x1 - x0);
-                                        nits = y0 + (nits - x0) * slope;
-                                    } else if (nits < x2) {
-                                        // scale [x1, x2] to [y1, y2] using Hermite interp
-                                        float t = (nits - x1) / h12;
-                                        nits = (y1 * (1.0 + 2.0 * t) + h12 * m1 * t) * (1.0 - t) * (1.0 - t) +
-                                                (y2 * (3.0 - 2.0 * t) + h12 * m2 * (t - 1.0)) * t * t;
-                                    } else {
-                                        // scale [x2, maxInLumi] to [y2, maxOutLumi] using Hermite interp
-                                        float t = (nits - x2) / h23;
-                                        nits = (y2 * (1.0 + 2.0 * t) + h23 * m2 * t) * (1.0 - t) * (1.0 - t) +
-                                                (maxOutLumi * (3.0 - 2.0 * t) + h23 * m3 * (t - 1.0)) * t * t;
-                                    }
-                                }
-
-                                // color.y is greater than x0 and is thus non-zero
-                                return xyz * (nits / xyz.y);
-                            }
-                        )");
-                    break;
-            }
-            break;
-        default:
-            switch (outputDataspace & HAL_DATASPACE_TRANSFER_MASK) {
-                case HAL_DATASPACE_TRANSFER_ST2084:
-                case HAL_DATASPACE_TRANSFER_HLG:
-                    // Map from SDR onto an HDR output buffer
-                    // Here we use a polynomial curve to map from [0, displayMaxLuminance] onto
-                    // [0, maxOutLumi] which is hard-coded to be 3000 nits.
-                    shader.append(R"(
-                            float3 ToneMap(float3 xyz) {
-                                const float maxOutLumi = 3000.0;
-
-                                const float x0 = 5.0;
-                                const float y0 = 2.5;
-                                float x1 = in_displayMaxLuminance * 0.7;
-                                float y1 = maxOutLumi * 0.15;
-                                float x2 = in_displayMaxLuminance * 0.9;
-                                float y2 = maxOutLumi * 0.45;
-                                float x3 = in_displayMaxLuminance;
-                                float y3 = maxOutLumi;
-
-                                float c1 = y1 / 3.0;
-                                float c2 = y2 / 2.0;
-                                float c3 = y3 / 1.5;
-
-                                float nits = xyz.y;
-
-                                if (nits <= x0) {
-                                    // scale [0.0, x0] to [0.0, y0] linearly
-                                    float slope = y0 / x0;
-                                    return xyz * slope;
-                                } else if (nits <= x1) {
-                                    // scale [x0, x1] to [y0, y1] using a curve
-                                    float t = (nits - x0) / (x1 - x0);
-                                    nits = (1.0 - t) * (1.0 - t) * y0 + 2.0 * (1.0 - t) * t * c1 + t * t * y1;
-                                } else if (nits <= x2) {
-                                    // scale [x1, x2] to [y1, y2] using a curve
-                                    float t = (nits - x1) / (x2 - x1);
-                                    nits = (1.0 - t) * (1.0 - t) * y1 + 2.0 * (1.0 - t) * t * c2 + t * t * y2;
-                                } else {
-                                    // scale [x2, x3] to [y2, y3] using a curve
-                                    float t = (nits - x2) / (x3 - x2);
-                                    nits = (1.0 - t) * (1.0 - t) * y2 + 2.0 * (1.0 - t) * t * c3 + t * t * y3;
-                                }
-
-                                // xyz.y is greater than x0 and is thus non-zero
-                                return xyz * (nits / xyz.y);
-                            }
-                        )");
-                    break;
-                default:
-                    // For completeness, this is tone-mapping from SDR to SDR, where this is just a
-                    // no-op.
-                    shader.append(R"(
-                            float3 ToneMap(float3 xyz) {
-                                return xyz;
-                            }
-                        )");
-                    break;
-            }
-            break;
-    }
-}
-
-// Normalizes from absolute light back to relative light (maps from [0, maxNits] back to [0, 1])
-static void generateLuminanceNormalizationForOOTF(ui::Dataspace outputDataspace, SkString& shader) {
-    switch (outputDataspace & HAL_DATASPACE_TRANSFER_MASK) {
-        case HAL_DATASPACE_TRANSFER_ST2084:
-            shader.append(R"(
-                    float3 NormalizeLuminance(float3 xyz) {
-                        return xyz / 10000.0;
-                    }
-                )");
-            break;
-        case HAL_DATASPACE_TRANSFER_HLG:
-            shader.append(R"(
-                    float3 NormalizeLuminance(float3 xyz) {
-                        return xyz / 1000.0 * pow(xyz.y / 1000.0, -0.2 / 1.2);
-                    }
-                )");
-            break;
-        default:
-            shader.append(R"(
-                    float3 NormalizeLuminance(float3 xyz) {
-                        return xyz / in_displayMaxLuminance;
-                    }
-                )");
-            break;
-    }
-}
-
-static void generateOOTF(ui::Dataspace inputDataspace, ui::Dataspace outputDataspace,
-                         SkString& shader) {
-    // Input uniforms
-    shader.append(R"(
-            uniform float in_displayMaxLuminance;
-            uniform float in_inputMaxLuminance;
-        )");
-
-    generateLuminanceScalesForOOTF(inputDataspace, shader);
-    generateToneMapInterpolation(inputDataspace, outputDataspace, shader);
-    generateLuminanceNormalizationForOOTF(outputDataspace, shader);
-
-    shader.append(R"(
-            float3 OOTF(float3 xyz) {
-                return NormalizeLuminance(ToneMap(ScaleLuminance(xyz)));
-            }
-        )");
-}
-
-static void generateOETF(ui::Dataspace dataspace, SkString& shader) {
-    switch (dataspace & HAL_DATASPACE_TRANSFER_MASK) {
-        case HAL_DATASPACE_TRANSFER_ST2084:
-            shader.append(R"(
-
-                float3 OETF(float3 xyz) {
-                    float m1 = (2610.0 / 4096.0) / 4.0;
-                    float m2 = (2523.0 / 4096.0) * 128.0;
-                    float c1 = (3424.0 / 4096.0);
-                    float c2 = (2413.0 / 4096.0) * 32.0;
-                    float c3 = (2392.0 / 4096.0) * 32.0;
-
-                    float3 tmp = pow(xyz, float3(m1));
-                    tmp = (c1 + c2 * tmp) / (1.0 + c3 * tmp);
-                    return pow(tmp, float3(m2));
-                }
-            )");
-            break;
-        case HAL_DATASPACE_TRANSFER_HLG:
-            shader.append(R"(
-                float OETF_channel(float channel) {
-                    const float a = 0.17883277;
-                    const float b = 0.28466892;
-                    const float c = 0.55991073;
-                    return channel <= 1.0 / 12.0 ? sqrt(3.0 * channel) :
-                            a * log(12.0 * channel - b) + c;
-                }
-
-                float3 OETF(float3 linear) {
-                    return float3(OETF_channel(linear.r), OETF_channel(linear.g),
-                            OETF_channel(linear.b));
-                }
-            )");
-            break;
-        case HAL_DATASPACE_TRANSFER_LINEAR:
-            shader.append(R"(
-                float3 OETF(float3 linear) {
-                    return linear;
-                }
-            )");
-            break;
-        case HAL_DATASPACE_TRANSFER_SRGB:
-        default:
-            shader.append(R"(
-                float OETF_sRGB(float linear) {
-                    return linear <= 0.0031308 ?
-                            linear * 12.92 : (pow(linear, 1.0 / 2.4) * 1.055) - 0.055;
-                }
-
-                float3 OETF_sRGB(float3 linear) {
-                    return float3(OETF_sRGB(linear.r), OETF_sRGB(linear.g), OETF_sRGB(linear.b));
-                }
-
-                float3 OETF(float3 linear) {
-                    return sign(linear.rgb) * OETF_sRGB(abs(linear.rgb));
-                }
-            )");
-            break;
-    }
-}
-
-static void generateEffectiveOOTF(bool undoPremultipliedAlpha, SkString& shader) {
-    shader.append(R"(
-        uniform shader input;
-        half4 main(float2 xy) {
-            float4 c = float4(sample(input, xy));
-    )");
-    if (undoPremultipliedAlpha) {
-        shader.append(R"(
-            c.rgb = c.rgb / (c.a + 0.0019);
-        )");
-    }
-    shader.append(R"(
-        c.rgb = OETF(ToRGB(OOTF(ToXYZ(EOTF(c.rgb)))));
-    )");
-    if (undoPremultipliedAlpha) {
-        shader.append(R"(
-            c.rgb = c.rgb * (c.a + 0.0019);
-        )");
-    }
-    shader.append(R"(
-            return c;
-        }
-    )");
-}
-static ColorSpace toColorSpace(ui::Dataspace dataspace) {
-    switch (dataspace & HAL_DATASPACE_STANDARD_MASK) {
-        case HAL_DATASPACE_STANDARD_BT709:
-            return ColorSpace::sRGB();
-            break;
-        case HAL_DATASPACE_STANDARD_DCI_P3:
-            return ColorSpace::DisplayP3();
-            break;
-        case HAL_DATASPACE_STANDARD_BT2020:
-            return ColorSpace::BT2020();
-            break;
-        default:
-            return ColorSpace::sRGB();
-            break;
-    }
-}
-
-sk_sp<SkRuntimeEffect> buildRuntimeEffect(const LinearEffect& linearEffect) {
+sk_sp<SkRuntimeEffect> buildRuntimeEffect(const shaders::LinearEffect& linearEffect) {
     ATRACE_CALL();
-    SkString shaderString;
-    generateEOTF(linearEffect.inputDataspace, shaderString);
-    generateXYZTransforms(shaderString);
-    generateOOTF(linearEffect.inputDataspace, linearEffect.outputDataspace, shaderString);
-    generateOETF(linearEffect.outputDataspace, shaderString);
-    generateEffectiveOOTF(linearEffect.undoPremultipliedAlpha, shaderString);
+    SkString shaderString = SkString(shaders::buildLinearEffectSkSL(linearEffect));
 
     auto [shader, error] = SkRuntimeEffect::MakeForShader(shaderString);
     if (!shader) {
@@ -444,35 +40,28 @@
     return shader;
 }
 
-sk_sp<SkShader> createLinearEffectShader(sk_sp<SkShader> shader, const LinearEffect& linearEffect,
+sk_sp<SkShader> createLinearEffectShader(sk_sp<SkShader> shader,
+                                         const shaders::LinearEffect& linearEffect,
                                          sk_sp<SkRuntimeEffect> runtimeEffect,
                                          const mat4& colorTransform, float maxDisplayLuminance,
-                                         float maxLuminance) {
+                                         float currentDisplayLuminanceNits, float maxLuminance,
+                                         AHardwareBuffer* buffer) {
     ATRACE_CALL();
     SkRuntimeShaderBuilder effectBuilder(runtimeEffect);
 
-    effectBuilder.child("input") = shader;
+    effectBuilder.child("child") = shader;
 
-    if (linearEffect.inputDataspace == linearEffect.outputDataspace) {
-        effectBuilder.uniform("in_rgbToXyz") = mat4();
-        effectBuilder.uniform("in_xyzToRgb") = colorTransform;
-    } else {
-        ColorSpace inputColorSpace = toColorSpace(linearEffect.inputDataspace);
-        ColorSpace outputColorSpace = toColorSpace(linearEffect.outputDataspace);
+    const auto uniforms =
+            shaders::buildLinearEffectUniforms(linearEffect, colorTransform, maxDisplayLuminance,
+                                               currentDisplayLuminanceNits, maxLuminance, buffer);
 
-        effectBuilder.uniform("in_rgbToXyz") = mat4(inputColorSpace.getRGBtoXYZ());
-        effectBuilder.uniform("in_xyzToRgb") =
-                colorTransform * mat4(outputColorSpace.getXYZtoRGB());
+    for (const auto& uniform : uniforms) {
+        effectBuilder.uniform(uniform.name.c_str()).set(uniform.value.data(), uniform.value.size());
     }
 
-    effectBuilder.uniform("in_displayMaxLuminance") = maxDisplayLuminance;
-    // If the input luminance is unknown, use display luminance (aka, no-op any luminance changes)
-    // This will be the case for eg screenshots in addition to uncalibrated displays
-    effectBuilder.uniform("in_inputMaxLuminance") =
-            maxLuminance > 0 ? maxLuminance : maxDisplayLuminance;
-    return effectBuilder.makeShader(nullptr, false);
+    return effectBuilder.makeShader();
 }
 
 } // namespace skia
 } // namespace renderengine
-} // namespace android
\ No newline at end of file
+} // namespace android
diff --git a/libs/renderengine/skia/filters/LinearEffect.h b/libs/renderengine/skia/filters/LinearEffect.h
index 14a3b61..26bae3b 100644
--- a/libs/renderengine/skia/filters/LinearEffect.h
+++ b/libs/renderengine/skia/filters/LinearEffect.h
@@ -20,6 +20,7 @@
 
 #include <optional>
 
+#include <shaders/shaders.h>
 #include "SkRuntimeEffect.h"
 #include "SkShader.h"
 #include "ui/GraphicTypes.h"
@@ -28,61 +29,7 @@
 namespace renderengine {
 namespace skia {
 
-/**
- * Arguments for creating an effect that applies color transformations in linear XYZ space.
- * A linear effect is decomposed into the following steps when operating on an image:
- * 1. Electrical-Optical Transfer Function (EOTF) maps the input RGB signal into the intended
- * relative display brightness of the scene in nits for each RGB channel
- * 2. Transformation matrix from linear RGB brightness to linear XYZ, to operate on display
- * luminance.
- * 3. Opto-Optical Transfer Function (OOTF) applies a "rendering intent". This can include tone
- * mapping to display SDR content alongside HDR content, or any number of subjective transformations
- * 4. Transformation matrix from linear XYZ back to linear RGB brightness.
- * 5. Opto-Electronic Transfer Function (OETF) maps the display brightness of the scene back to
- * output RGB colors.
- *
- * For further reading, consult the recommendation in ITU-R BT.2390-4:
- * https://www.itu.int/dms_pub/itu-r/opb/rep/R-REP-BT.2390-4-2018-PDF-E.pdf
- *
- * Skia normally attempts to do its own simple tone mapping, i.e., the working color space is
- * intended to be the output surface. However, Skia does not support complex tone mapping such as
- * polynomial interpolation. As such, this filter assumes that tone mapping has not yet been applied
- * to the source colors. so that the tone mapping process is only applied once by this effect. Tone
- * mapping is applied when presenting HDR content (content with HLG or PQ transfer functions)
- * alongside other content, whereby maximum input luminance is mapped to maximum output luminance
- * and intermediate values are interpolated.
- */
-struct LinearEffect {
-    // Input dataspace of the source colors.
-    const ui::Dataspace inputDataspace = ui::Dataspace::SRGB;
-
-    // Working dataspace for the output surface, for conversion from linear space.
-    const ui::Dataspace outputDataspace = ui::Dataspace::SRGB;
-
-    // Sets whether alpha premultiplication must be undone.
-    // This is required if the source colors use premultiplied alpha and is not opaque.
-    const bool undoPremultipliedAlpha = false;
-};
-
-static inline bool operator==(const LinearEffect& lhs, const LinearEffect& rhs) {
-    return lhs.inputDataspace == rhs.inputDataspace && lhs.outputDataspace == rhs.outputDataspace &&
-            lhs.undoPremultipliedAlpha == rhs.undoPremultipliedAlpha;
-}
-
-struct LinearEffectHasher {
-    // Inspired by art/runtime/class_linker.cc
-    // Also this is what boost:hash_combine does
-    static size_t HashCombine(size_t seed, size_t val) {
-        return seed ^ (val + 0x9e3779b9 + (seed << 6) + (seed >> 2));
-    }
-    size_t operator()(const LinearEffect& le) const {
-        size_t result = std::hash<ui::Dataspace>{}(le.inputDataspace);
-        result = HashCombine(result, std::hash<ui::Dataspace>{}(le.outputDataspace));
-        return HashCombine(result, std::hash<bool>{}(le.undoPremultipliedAlpha));
-    }
-};
-
-sk_sp<SkRuntimeEffect> buildRuntimeEffect(const LinearEffect& linearEffect);
+sk_sp<SkRuntimeEffect> buildRuntimeEffect(const shaders::LinearEffect& linearEffect);
 
 // Generates a shader resulting from applying the a linear effect created from
 // LinearEffectArgs::buildEffect to an inputShader.
@@ -90,13 +37,17 @@
 // matrix transforming from linear XYZ to linear RGB immediately before OETF.
 // We also provide additional HDR metadata upon creating the shader:
 // * The max display luminance is the max luminance of the physical display in nits
+// * The current luminance of the physical display in nits
 // * The max luminance is provided as the max luminance for the buffer, either from the SMPTE 2086
 // or as the max light level from the CTA 861.3 standard.
+// * An AHardwareBuffer for implementations that support gralloc4 metadata for
+// communicating any HDR metadata.
 sk_sp<SkShader> createLinearEffectShader(sk_sp<SkShader> inputShader,
-                                         const LinearEffect& linearEffect,
+                                         const shaders::LinearEffect& linearEffect,
                                          sk_sp<SkRuntimeEffect> runtimeEffect,
                                          const mat4& colorTransform, float maxDisplayLuminance,
-                                         float maxLuminance);
+                                         float currentDisplayLuminanceNits, float maxLuminance,
+                                         AHardwareBuffer* buffer);
 } // namespace skia
 } // namespace renderengine
 } // namespace android
diff --git a/libs/renderengine/skia/filters/StretchShaderFactory.cpp b/libs/renderengine/skia/filters/StretchShaderFactory.cpp
index 4ac5c40..beec3ec 100644
--- a/libs/renderengine/skia/filters/StretchShaderFactory.cpp
+++ b/libs/renderengine/skia/filters/StretchShaderFactory.cpp
@@ -184,7 +184,7 @@
         );
         coord.x = (outU - uScrollX) * viewportWidth;
         coord.y = (outV - uScrollY) * viewportHeight;
-        return sample(uContentTexture, coord);
+        return uContentTexture.eval(coord);
     })");
 
 const float INTERPOLATION_STRENGTH_VALUE = 0.7f;
@@ -238,7 +238,7 @@
     mBuilder->uniform("viewportWidth").set(&viewportWidth, 1);
     mBuilder->uniform("viewportHeight").set(&viewportHeight, 1);
 
-    return mBuilder->makeShader(nullptr, false);
+    return mBuilder->makeShader();
 }
 
 } // namespace skia
diff --git a/libs/renderengine/tests/Android.bp b/libs/renderengine/tests/Android.bp
index d0e19dd..d91af1e 100644
--- a/libs/renderengine/tests/Android.bp
+++ b/libs/renderengine/tests/Android.bp
@@ -23,7 +23,10 @@
 
 cc_test {
     name: "librenderengine_test",
-    defaults: ["skia_deps", "surfaceflinger_defaults"],
+    defaults: [
+        "skia_deps",
+        "surfaceflinger_defaults",
+    ],
     test_suites: ["device-tests"],
     srcs: [
         "RenderEngineTest.cpp",
@@ -36,6 +39,11 @@
         "libgmock",
         "librenderengine",
         "librenderengine_mocks",
+        "libshaders",
+        "libtonemap",
+    ],
+    header_libs: [
+        "libtonemap_headers",
     ],
 
     shared_libs: [
diff --git a/libs/renderengine/tests/RenderEngineTest.cpp b/libs/renderengine/tests/RenderEngineTest.cpp
index 33e3773..38ae2fd 100644
--- a/libs/renderengine/tests/RenderEngineTest.cpp
+++ b/libs/renderengine/tests/RenderEngineTest.cpp
@@ -26,7 +26,11 @@
 #include <gtest/gtest.h>
 #include <renderengine/ExternalTexture.h>
 #include <renderengine/RenderEngine.h>
+#include <renderengine/impl/ExternalTexture.h>
 #include <sync/sync.h>
+#include <system/graphics-base-v1.0.h>
+#include <tonemap/tonemap.h>
+#include <ui/ColorSpace.h>
 #include <ui/PixelFormat.h>
 
 #include <chrono>
@@ -45,6 +49,50 @@
 namespace android {
 namespace renderengine {
 
+namespace {
+
+double EOTF_PQ(double channel) {
+    float m1 = (2610.0 / 4096.0) / 4.0;
+    float m2 = (2523.0 / 4096.0) * 128.0;
+    float c1 = (3424.0 / 4096.0);
+    float c2 = (2413.0 / 4096.0) * 32.0;
+    float c3 = (2392.0 / 4096.0) * 32.0;
+
+    float tmp = std::pow(std::clamp(channel, 0.0, 1.0), 1.0 / m2);
+    tmp = std::fmax(tmp - c1, 0.0) / (c2 - c3 * tmp);
+    return std::pow(tmp, 1.0 / m1);
+}
+
+vec3 EOTF_PQ(vec3 color) {
+    return vec3(EOTF_PQ(color.r), EOTF_PQ(color.g), EOTF_PQ(color.b));
+}
+
+double EOTF_HLG(double channel) {
+    const float a = 0.17883277;
+    const float b = 0.28466892;
+    const float c = 0.55991073;
+    return channel <= 0.5 ? channel * channel / 3.0 : (exp((channel - c) / a) + b) / 12.0;
+}
+
+vec3 EOTF_HLG(vec3 color) {
+    return vec3(EOTF_HLG(color.r), EOTF_HLG(color.g), EOTF_HLG(color.b));
+}
+
+double OETF_sRGB(double channel) {
+    return channel <= 0.0031308 ? channel * 12.92 : (pow(channel, 1.0 / 2.4) * 1.055) - 0.055;
+}
+
+int sign(float in) {
+    return in >= 0.0 ? 1 : -1;
+}
+
+vec3 OETF_sRGB(vec3 linear) {
+    return vec3(sign(linear.r) * OETF_sRGB(linear.r), sign(linear.g) * OETF_sRGB(linear.g),
+                sign(linear.b) * OETF_sRGB(linear.b));
+}
+
+} // namespace
+
 class RenderEngineFactory {
 public:
     virtual ~RenderEngineFactory() = default;
@@ -175,7 +223,7 @@
 public:
     std::shared_ptr<renderengine::ExternalTexture> allocateDefaultBuffer() {
         return std::make_shared<
-                renderengine::
+                renderengine::impl::
                         ExternalTexture>(new GraphicBuffer(DEFAULT_DISPLAY_WIDTH,
                                                            DEFAULT_DISPLAY_HEIGHT,
                                                            HAL_PIXEL_FORMAT_RGBA_8888, 1,
@@ -185,15 +233,16 @@
                                                                    GRALLOC_USAGE_HW_TEXTURE,
                                                            "output"),
                                          *mRE,
-                                         renderengine::ExternalTexture::Usage::READABLE |
-                                                 renderengine::ExternalTexture::Usage::WRITEABLE);
+                                         renderengine::impl::ExternalTexture::Usage::READABLE |
+                                                 renderengine::impl::ExternalTexture::Usage::
+                                                         WRITEABLE);
     }
 
     // Allocates a 1x1 buffer to fill with a solid color
     std::shared_ptr<renderengine::ExternalTexture> allocateSourceBuffer(uint32_t width,
                                                                         uint32_t height) {
         return std::make_shared<
-                renderengine::
+                renderengine::impl::
                         ExternalTexture>(new GraphicBuffer(width, height,
                                                            HAL_PIXEL_FORMAT_RGBA_8888, 1,
                                                            GRALLOC_USAGE_SW_READ_OFTEN |
@@ -201,8 +250,45 @@
                                                                    GRALLOC_USAGE_HW_TEXTURE,
                                                            "input"),
                                          *mRE,
-                                         renderengine::ExternalTexture::Usage::READABLE |
-                                                 renderengine::ExternalTexture::Usage::WRITEABLE);
+                                         renderengine::impl::ExternalTexture::Usage::READABLE |
+                                                 renderengine::impl::ExternalTexture::Usage::
+                                                         WRITEABLE);
+    }
+
+    std::shared_ptr<renderengine::ExternalTexture> allocateAndFillSourceBuffer(uint32_t width,
+                                                                               uint32_t height,
+                                                                               ubyte4 color) {
+        const auto buffer = allocateSourceBuffer(width, height);
+        uint8_t* pixels;
+        buffer->getBuffer()->lock(GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN,
+                                  reinterpret_cast<void**>(&pixels));
+        for (uint32_t j = 0; j < height; j++) {
+            uint8_t* dst = pixels + (buffer->getBuffer()->getStride() * j * 4);
+            for (uint32_t i = 0; i < width; i++) {
+                dst[0] = color.r;
+                dst[1] = color.g;
+                dst[2] = color.b;
+                dst[3] = color.a;
+                dst += 4;
+            }
+        }
+        buffer->getBuffer()->unlock();
+        return buffer;
+    }
+
+    std::shared_ptr<renderengine::ExternalTexture> allocateR8Buffer(int width, int height) {
+        auto buffer = new GraphicBuffer(width, height, android::PIXEL_FORMAT_R_8, 1,
+                                        GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN |
+                                                GRALLOC_USAGE_HW_TEXTURE,
+                                        "r8");
+        if (buffer->initCheck() != 0) {
+            // Devices are not required to support R8.
+            return nullptr;
+        }
+        return std::make_shared<
+                renderengine::impl::ExternalTexture>(std::move(buffer), *mRE,
+                                                     renderengine::impl::ExternalTexture::Usage::
+                                                             READABLE);
     }
 
     RenderEngineTest() {
@@ -282,6 +368,13 @@
 
     void expectBufferColor(const Rect& rect, uint8_t r, uint8_t g, uint8_t b, uint8_t a,
                            uint8_t tolerance = 0) {
+        auto generator = [=](Point) { return ubyte4(r, g, b, a); };
+        expectBufferColor(rect, generator, tolerance);
+    }
+
+    using ColorGenerator = std::function<ubyte4(Point location)>;
+
+    void expectBufferColor(const Rect& rect, ColorGenerator generator, uint8_t tolerance = 0) {
         auto colorCompare = [tolerance](const uint8_t* colorA, const uint8_t* colorB) {
             auto colorBitCompare = [tolerance](uint8_t a, uint8_t b) {
                 uint8_t tmp = a >= b ? a - b : b - a;
@@ -290,10 +383,10 @@
             return std::equal(colorA, colorA + 4, colorB, colorBitCompare);
         };
 
-        expectBufferColor(rect, r, g, b, a, colorCompare);
+        expectBufferColor(rect, generator, colorCompare);
     }
 
-    void expectBufferColor(const Rect& region, uint8_t r, uint8_t g, uint8_t b, uint8_t a,
+    void expectBufferColor(const Rect& region, ColorGenerator generator,
                            std::function<bool(const uint8_t* a, const uint8_t* b)> colorCompare) {
         uint8_t* pixels;
         mBuffer->getBuffer()->lock(GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN,
@@ -304,19 +397,22 @@
             const uint8_t* src = pixels +
                     (mBuffer->getBuffer()->getStride() * (region.top + j) + region.left) * 4;
             for (int32_t i = 0; i < region.getWidth(); i++) {
-                const uint8_t expected[4] = {r, g, b, a};
-                bool equal = colorCompare(src, expected);
-                EXPECT_TRUE(equal)
+                const auto location = Point(region.left + i, region.top + j);
+                const ubyte4 colors = generator(location);
+                const uint8_t expected[4] = {colors.r, colors.g, colors.b, colors.a};
+                bool colorMatches = colorCompare(src, expected);
+                EXPECT_TRUE(colorMatches)
                         << GetParam()->name().c_str() << ": "
-                        << "pixel @ (" << region.left + i << ", " << region.top + j << "): "
-                        << "expected (" << static_cast<uint32_t>(r) << ", "
-                        << static_cast<uint32_t>(g) << ", " << static_cast<uint32_t>(b) << ", "
-                        << static_cast<uint32_t>(a) << "), "
+                        << "pixel @ (" << location.x << ", " << location.y << "): "
+                        << "expected (" << static_cast<uint32_t>(colors.r) << ", "
+                        << static_cast<uint32_t>(colors.g) << ", "
+                        << static_cast<uint32_t>(colors.b) << ", "
+                        << static_cast<uint32_t>(colors.a) << "), "
                         << "got (" << static_cast<uint32_t>(src[0]) << ", "
                         << static_cast<uint32_t>(src[1]) << ", " << static_cast<uint32_t>(src[2])
                         << ", " << static_cast<uint32_t>(src[3]) << ")";
                 src += 4;
-                if (!equal && ++fails >= maxFails) {
+                if (!colorMatches && ++fails >= maxFails) {
                     break;
                 }
             }
@@ -328,10 +424,11 @@
     }
 
     void expectAlpha(const Rect& rect, uint8_t a) {
+        auto generator = [=](Point) { return ubyte4(0, 0, 0, a); };
         auto colorCompare = [](const uint8_t* colorA, const uint8_t* colorB) {
             return colorA[3] == colorB[3];
         };
-        expectBufferColor(rect, 0.0f /* r */, 0.0f /*g */, 0.0f /* b */, a, colorCompare);
+        expectBufferColor(rect, generator, colorCompare);
     }
 
     void expectShadowColor(const renderengine::LayerSettings& castingLayer,
@@ -417,19 +514,19 @@
                     DEFAULT_DISPLAY_HEIGHT - DEFAULT_DISPLAY_OFFSET);
     }
 
-    void invokeDraw(renderengine::DisplaySettings settings,
-                    std::vector<const renderengine::LayerSettings*> layers) {
-        base::unique_fd fence;
-        status_t status =
-                mRE->drawLayers(settings, layers, mBuffer, true, base::unique_fd(), &fence);
+    void invokeDraw(const renderengine::DisplaySettings& settings,
+                    const std::vector<renderengine::LayerSettings>& layers) {
+        std::future<renderengine::RenderEngineResult> result =
+                mRE->drawLayers(settings, layers, mBuffer, true, base::unique_fd());
 
-        int fd = fence.release();
-        if (fd >= 0) {
-            sync_wait(fd, -1);
-            close(fd);
-        }
+        ASSERT_TRUE(result.valid());
+        auto [status, fence] = result.get();
 
         ASSERT_EQ(NO_ERROR, status);
+        if (fence.ok()) {
+            sync_wait(fence.get(), -1);
+        }
+
         if (layers.size() > 0 && mGLESRE != nullptr) {
             ASSERT_TRUE(mGLESRE->isFramebufferImageCachedForTesting(mBuffer->getBuffer()->getId()));
         }
@@ -437,7 +534,7 @@
 
     void drawEmptyLayers() {
         renderengine::DisplaySettings settings;
-        std::vector<const renderengine::LayerSettings*> layers;
+        std::vector<renderengine::LayerSettings> layers;
         invokeDraw(settings, layers);
     }
 
@@ -490,6 +587,18 @@
     void fillBufferColorTransform();
 
     template <typename SourceVariant>
+    void fillBufferWithColorTransformAndSourceDataspace(const ui::Dataspace sourceDataspace);
+
+    template <typename SourceVariant>
+    void fillBufferColorTransformAndSourceDataspace();
+
+    template <typename SourceVariant>
+    void fillBufferWithColorTransformAndOutputDataspace(const ui::Dataspace outputDataspace);
+
+    template <typename SourceVariant>
+    void fillBufferColorTransformAndOutputDataspace();
+
+    template <typename SourceVariant>
     void fillBufferWithColorTransformZeroLayerAlpha();
 
     template <typename SourceVariant>
@@ -524,10 +633,6 @@
 
     void fillGreenColorBufferThenClearRegion();
 
-    void clearLeftRegion();
-
-    void clearRegion();
-
     template <typename SourceVariant>
     void drawShadow(const renderengine::LayerSettings& castingLayer,
                     const renderengine::ShadowSettings& shadow, const ubyte4& casterColor,
@@ -537,6 +642,12 @@
                                  const renderengine::ShadowSettings& shadow,
                                  const ubyte4& backgroundColor);
 
+    // Tonemaps grey values from sourceDataspace -> Display P3 and checks that GPU and CPU
+    // implementations are identical Also implicitly checks that the injected tonemap shader
+    // compiles
+    void tonemap(ui::Dataspace sourceDataspace, std::function<vec3(vec3)> eotf,
+                 std::function<vec3(vec3, float)> scaleOotf);
+
     void initializeRenderEngine();
 
     std::unique_ptr<renderengine::RenderEngine> mRE;
@@ -634,7 +745,7 @@
     settings.clip = fullscreenRect();
     settings.outputDataspace = ui::Dataspace::V0_SRGB_LINEAR;
 
-    std::vector<const renderengine::LayerSettings*> layers;
+    std::vector<renderengine::LayerSettings> layers;
 
     renderengine::LayerSettings layer;
     layer.sourceDataspace = ui::Dataspace::V0_SRGB_LINEAR;
@@ -642,7 +753,7 @@
     SourceVariant::fillColor(layer, r, g, b, this);
     layer.alpha = a;
 
-    layers.push_back(&layer);
+    layers.push_back(layer);
 
     invokeDraw(settings, layers);
 }
@@ -678,7 +789,7 @@
     settings.physicalDisplay = offsetRect();
     settings.clip = offsetRectAtZero();
 
-    std::vector<const renderengine::LayerSettings*> layers;
+    std::vector<renderengine::LayerSettings> layers;
 
     renderengine::LayerSettings layer;
     layer.sourceDataspace = ui::Dataspace::V0_SRGB_LINEAR;
@@ -686,7 +797,7 @@
     SourceVariant::fillColor(layer, 1.0f, 0.0f, 0.0f, this);
     layer.alpha = 1.0f;
 
-    layers.push_back(&layer);
+    layers.push_back(layer);
     invokeDraw(settings, layers);
 }
 
@@ -713,7 +824,7 @@
     settings.clip = Rect(2, 2);
     settings.orientation = orientationFlag;
 
-    std::vector<const renderengine::LayerSettings*> layers;
+    std::vector<renderengine::LayerSettings> layers;
 
     renderengine::LayerSettings layerOne;
     layerOne.sourceDataspace = ui::Dataspace::V0_SRGB_LINEAR;
@@ -736,9 +847,9 @@
     SourceVariant::fillColor(layerThree, 0.0f, 0.0f, 1.0f, this);
     layerThree.alpha = 1.0f;
 
-    layers.push_back(&layerOne);
-    layers.push_back(&layerTwo);
-    layers.push_back(&layerThree);
+    layers.push_back(layerOne);
+    layers.push_back(layerTwo);
+    layers.push_back(layerThree);
 
     invokeDraw(settings, layers);
 }
@@ -815,7 +926,7 @@
     settings.clip = Rect(2, 2);
     settings.outputDataspace = ui::Dataspace::V0_SRGB_LINEAR;
 
-    std::vector<const renderengine::LayerSettings*> layers;
+    std::vector<renderengine::LayerSettings> layers;
 
     renderengine::LayerSettings layer;
     layer.sourceDataspace = ui::Dataspace::V0_SRGB_LINEAR;
@@ -826,7 +937,7 @@
     layer.source.solidColor = half3(1.0f, 0.0f, 0.0f);
     layer.alpha = 1.0f;
 
-    layers.push_back(&layer);
+    layers.push_back(layer);
 
     invokeDraw(settings, layers);
 }
@@ -848,7 +959,7 @@
     settings.clip = Rect(1, 1);
     settings.outputDataspace = ui::Dataspace::V0_SRGB_LINEAR;
 
-    std::vector<const renderengine::LayerSettings*> layers;
+    std::vector<renderengine::LayerSettings> layers;
 
     renderengine::LayerSettings layer;
     layer.sourceDataspace = ui::Dataspace::V0_SRGB_LINEAR;
@@ -865,7 +976,37 @@
     layer.alpha = 1.0f;
     layer.geometry.boundaries = Rect(1, 1).toFloatRect();
 
-    layers.push_back(&layer);
+    layers.push_back(layer);
+
+    invokeDraw(settings, layers);
+}
+
+template <typename SourceVariant>
+void RenderEngineTest::fillBufferWithColorTransformAndSourceDataspace(
+        const ui::Dataspace sourceDataspace) {
+    renderengine::DisplaySettings settings;
+    settings.physicalDisplay = fullscreenRect();
+    settings.clip = Rect(1, 1);
+    settings.outputDataspace = ui::Dataspace::V0_SRGB_LINEAR;
+
+    std::vector<renderengine::LayerSettings> layers;
+
+    renderengine::LayerSettings layer;
+    layer.sourceDataspace = sourceDataspace;
+    layer.geometry.boundaries = Rect(1, 1).toFloatRect();
+    SourceVariant::fillColor(layer, 0.5f, 0.25f, 0.125f, this);
+    layer.alpha = 1.0f;
+
+    // construct a fake color matrix
+    // annihilate green and blue channels
+    settings.colorTransform = mat4::scale(vec4(0.9f, 0, 0, 1));
+    // set red channel to red + green
+    layer.colorTransform = mat4(1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
+
+    layer.alpha = 1.0f;
+    layer.geometry.boundaries = Rect(1, 1).toFloatRect();
+
+    layers.push_back(layer);
 
     invokeDraw(settings, layers);
 }
@@ -877,12 +1018,74 @@
 }
 
 template <typename SourceVariant>
+void RenderEngineTest::fillBufferColorTransformAndSourceDataspace() {
+    unordered_map<ui::Dataspace, ubyte4> dataspaceToColorMap;
+    dataspaceToColorMap[ui::Dataspace::V0_BT709] = {172, 0, 0, 255};
+    dataspaceToColorMap[ui::Dataspace::BT2020] = {172, 0, 0, 255};
+    dataspaceToColorMap[ui::Dataspace::ADOBE_RGB] = {172, 0, 0, 255};
+    ui::Dataspace customizedDataspace = static_cast<ui::Dataspace>(
+            ui::Dataspace::STANDARD_BT709 | ui::Dataspace::TRANSFER_GAMMA2_2 |
+            ui::Dataspace::RANGE_FULL);
+    dataspaceToColorMap[customizedDataspace] = {172, 0, 0, 255};
+    for (const auto& [sourceDataspace, color] : dataspaceToColorMap) {
+        fillBufferWithColorTransformAndSourceDataspace<SourceVariant>(sourceDataspace);
+        expectBufferColor(fullscreenRect(), color.r, color.g, color.b, color.a, 1);
+    }
+}
+
+template <typename SourceVariant>
+void RenderEngineTest::fillBufferWithColorTransformAndOutputDataspace(
+        const ui::Dataspace outputDataspace) {
+    renderengine::DisplaySettings settings;
+    settings.physicalDisplay = fullscreenRect();
+    settings.clip = Rect(1, 1);
+    settings.outputDataspace = outputDataspace;
+
+    std::vector<renderengine::LayerSettings> layers;
+
+    renderengine::LayerSettings layer;
+    layer.sourceDataspace = ui::Dataspace::V0_SCRGB_LINEAR;
+    layer.geometry.boundaries = Rect(1, 1).toFloatRect();
+    SourceVariant::fillColor(layer, 0.5f, 0.25f, 0.125f, this);
+    layer.alpha = 1.0f;
+
+    // construct a fake color matrix
+    // annihilate green and blue channels
+    settings.colorTransform = mat4::scale(vec4(0.9f, 0, 0, 1));
+    // set red channel to red + green
+    layer.colorTransform = mat4(1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
+
+    layer.alpha = 1.0f;
+    layer.geometry.boundaries = Rect(1, 1).toFloatRect();
+
+    layers.push_back(layer);
+
+    invokeDraw(settings, layers);
+}
+
+template <typename SourceVariant>
+void RenderEngineTest::fillBufferColorTransformAndOutputDataspace() {
+    unordered_map<ui::Dataspace, ubyte4> dataspaceToColorMap;
+    dataspaceToColorMap[ui::Dataspace::V0_BT709] = {202, 0, 0, 255};
+    dataspaceToColorMap[ui::Dataspace::BT2020] = {192, 0, 0, 255};
+    dataspaceToColorMap[ui::Dataspace::ADOBE_RGB] = {202, 0, 0, 255};
+    ui::Dataspace customizedDataspace = static_cast<ui::Dataspace>(
+            ui::Dataspace::STANDARD_BT709 | ui::Dataspace::TRANSFER_GAMMA2_6 |
+            ui::Dataspace::RANGE_FULL);
+    dataspaceToColorMap[customizedDataspace] = {202, 0, 0, 255};
+    for (const auto& [outputDataspace, color] : dataspaceToColorMap) {
+        fillBufferWithColorTransformAndOutputDataspace<SourceVariant>(outputDataspace);
+        expectBufferColor(fullscreenRect(), color.r, color.g, color.b, color.a, 1);
+    }
+}
+
+template <typename SourceVariant>
 void RenderEngineTest::fillBufferWithColorTransformZeroLayerAlpha() {
     renderengine::DisplaySettings settings;
     settings.physicalDisplay = fullscreenRect();
     settings.clip = Rect(1, 1);
 
-    std::vector<const renderengine::LayerSettings*> layers;
+    std::vector<renderengine::LayerSettings> layers;
 
     renderengine::LayerSettings layer;
     layer.geometry.boundaries = Rect(1, 1).toFloatRect();
@@ -895,7 +1098,7 @@
 
     layer.geometry.boundaries = Rect(1, 1).toFloatRect();
 
-    layers.push_back(&layer);
+    layers.push_back(layer);
 
     invokeDraw(settings, layers);
 }
@@ -913,7 +1116,7 @@
     settings.clip = fullscreenRect();
     settings.outputDataspace = ui::Dataspace::V0_SRGB_LINEAR;
 
-    std::vector<const renderengine::LayerSettings*> layers;
+    std::vector<renderengine::LayerSettings> layers;
 
     renderengine::LayerSettings layer;
     layer.sourceDataspace = ui::Dataspace::V0_SRGB_LINEAR;
@@ -923,7 +1126,7 @@
     SourceVariant::fillColor(layer, 1.0f, 0.0f, 0.0f, this);
     layer.alpha = 1.0f;
 
-    layers.push_back(&layer);
+    layers.push_back(layer);
 
     invokeDraw(settings, layers);
 }
@@ -954,14 +1157,14 @@
     settings.physicalDisplay = fullscreenRect();
     settings.clip = fullscreenRect();
 
-    std::vector<const renderengine::LayerSettings*> layers;
+    std::vector<renderengine::LayerSettings> layers;
 
     renderengine::LayerSettings backgroundLayer;
     backgroundLayer.sourceDataspace = ui::Dataspace::V0_SRGB_LINEAR;
     backgroundLayer.geometry.boundaries = fullscreenRect().toFloatRect();
     SourceVariant::fillColor(backgroundLayer, 0.0f, 1.0f, 0.0f, this);
     backgroundLayer.alpha = 1.0f;
-    layers.push_back(&backgroundLayer);
+    layers.emplace_back(backgroundLayer);
 
     renderengine::LayerSettings leftLayer;
     leftLayer.sourceDataspace = ui::Dataspace::V0_SRGB_LINEAR;
@@ -969,7 +1172,7 @@
             Rect(DEFAULT_DISPLAY_WIDTH / 2, DEFAULT_DISPLAY_HEIGHT).toFloatRect();
     SourceVariant::fillColor(leftLayer, 1.0f, 0.0f, 0.0f, this);
     leftLayer.alpha = 1.0f;
-    layers.push_back(&leftLayer);
+    layers.emplace_back(leftLayer);
 
     renderengine::LayerSettings blurLayer;
     blurLayer.sourceDataspace = ui::Dataspace::V0_SRGB_LINEAR;
@@ -977,7 +1180,7 @@
     blurLayer.backgroundBlurRadius = blurRadius;
     SourceVariant::fillColor(blurLayer, 0.0f, 0.0f, 1.0f, this);
     blurLayer.alpha = 0;
-    layers.push_back(&blurLayer);
+    layers.emplace_back(blurLayer);
 
     invokeDraw(settings, layers);
 
@@ -999,14 +1202,14 @@
     settings.physicalDisplay = fullscreenRect();
     settings.clip = fullscreenRect();
 
-    std::vector<const renderengine::LayerSettings*> layers;
+    std::vector<renderengine::LayerSettings> layers;
 
     renderengine::LayerSettings backgroundLayer;
     backgroundLayer.sourceDataspace = ui::Dataspace::V0_SRGB_LINEAR;
     backgroundLayer.geometry.boundaries = fullscreenRect().toFloatRect();
     SourceVariant::fillColor(backgroundLayer, 1.0f, 0.0f, 0.0f, this);
     backgroundLayer.alpha = 1.0f;
-    layers.push_back(&backgroundLayer);
+    layers.push_back(backgroundLayer);
 
     renderengine::LayerSettings blurLayer;
     blurLayer.sourceDataspace = ui::Dataspace::V0_SRGB_LINEAR;
@@ -1014,7 +1217,7 @@
     blurLayer.backgroundBlurRadius = blurRadius;
     SourceVariant::fillColor(blurLayer, 0.0f, 0.0f, 1.0f, this);
     blurLayer.alpha = 0;
-    layers.push_back(&blurLayer);
+    layers.push_back(blurLayer);
 
     invokeDraw(settings, layers);
 
@@ -1031,7 +1234,7 @@
     settings.clip = fullscreenRect();
     settings.outputDataspace = ui::Dataspace::V0_SRGB_LINEAR;
 
-    std::vector<const renderengine::LayerSettings*> layersFirst;
+    std::vector<renderengine::LayerSettings> layersFirst;
 
     renderengine::LayerSettings layerOne;
     layerOne.sourceDataspace = ui::Dataspace::V0_SRGB_LINEAR;
@@ -1040,14 +1243,14 @@
     SourceVariant::fillColor(layerOne, 1.0f, 0.0f, 0.0f, this);
     layerOne.alpha = 0.2;
 
-    layersFirst.push_back(&layerOne);
+    layersFirst.push_back(layerOne);
     invokeDraw(settings, layersFirst);
     expectBufferColor(Rect(DEFAULT_DISPLAY_WIDTH / 3, DEFAULT_DISPLAY_HEIGHT / 3), 51, 0, 0, 51);
     expectBufferColor(Rect(DEFAULT_DISPLAY_WIDTH / 3 + 1, DEFAULT_DISPLAY_HEIGHT / 3 + 1,
                            DEFAULT_DISPLAY_WIDTH, DEFAULT_DISPLAY_HEIGHT),
                       0, 0, 0, 0);
 
-    std::vector<const renderengine::LayerSettings*> layersSecond;
+    std::vector<renderengine::LayerSettings> layersSecond;
     renderengine::LayerSettings layerTwo;
     layerTwo.sourceDataspace = ui::Dataspace::V0_SRGB_LINEAR;
     layerTwo.geometry.boundaries =
@@ -1056,7 +1259,7 @@
     SourceVariant::fillColor(layerTwo, 0.0f, 1.0f, 0.0f, this);
     layerTwo.alpha = 1.0f;
 
-    layersSecond.push_back(&layerTwo);
+    layersSecond.push_back(layerTwo);
     invokeDraw(settings, layersSecond);
 
     expectBufferColor(Rect(DEFAULT_DISPLAY_WIDTH / 3, DEFAULT_DISPLAY_HEIGHT / 3), 0, 0, 0, 0);
@@ -1071,7 +1274,7 @@
     settings.clip = Rect(1, 1);
     settings.outputDataspace = ui::Dataspace::V0_SRGB_LINEAR;
 
-    std::vector<const renderengine::LayerSettings*> layers;
+    std::vector<renderengine::LayerSettings> layers;
 
     renderengine::LayerSettings layer;
     layer.sourceDataspace = ui::Dataspace::V0_SRGB_LINEAR;
@@ -1103,11 +1306,11 @@
     layer.source.buffer.buffer = buf;
     layer.source.buffer.textureName = texName;
     // Transform coordinates to only be inside the red quadrant.
-    layer.source.buffer.textureTransform = mat4::scale(vec4(0.2, 0.2, 1, 1));
+    layer.source.buffer.textureTransform = mat4::scale(vec4(0.2f, 0.2f, 1.f, 1.f));
     layer.alpha = 1.0f;
     layer.geometry.boundaries = Rect(1, 1).toFloatRect();
 
-    layers.push_back(&layer);
+    layers.push_back(layer);
 
     invokeDraw(settings, layers);
 }
@@ -1123,7 +1326,7 @@
     // Here logical space is 1x1
     settings.clip = Rect(1, 1);
 
-    std::vector<const renderengine::LayerSettings*> layers;
+    std::vector<renderengine::LayerSettings> layers;
 
     renderengine::LayerSettings layer;
     const auto buf = allocateSourceBuffer(1, 1);
@@ -1146,7 +1349,7 @@
     layer.alpha = 0.5f;
     layer.geometry.boundaries = Rect(1, 1).toFloatRect();
 
-    layers.push_back(&layer);
+    layers.push_back(layer);
 
     invokeDraw(settings, layers);
 }
@@ -1162,7 +1365,7 @@
     // Here logical space is 1x1
     settings.clip = Rect(1, 1);
 
-    std::vector<const renderengine::LayerSettings*> layers;
+    std::vector<renderengine::LayerSettings> layers;
 
     renderengine::LayerSettings layer;
     const auto buf = allocateSourceBuffer(1, 1);
@@ -1185,7 +1388,7 @@
     layer.alpha = 0.5f;
     layer.geometry.boundaries = Rect(1, 1).toFloatRect();
 
-    layers.push_back(&layer);
+    layers.push_back(layer);
 
     invokeDraw(settings, layers);
 }
@@ -1195,28 +1398,6 @@
     expectBufferColor(fullscreenRect(), 128, 0, 0, 128, 1);
 }
 
-void RenderEngineTest::clearLeftRegion() {
-    renderengine::DisplaySettings settings;
-    settings.physicalDisplay = fullscreenRect();
-    // Here logical space is 4x4
-    settings.clip = Rect(4, 4);
-    settings.clearRegion = Region(Rect(2, 4));
-    std::vector<const renderengine::LayerSettings*> layers;
-    // fake layer, without bounds should not render anything
-    renderengine::LayerSettings layer;
-    layers.push_back(&layer);
-    invokeDraw(settings, layers);
-}
-
-void RenderEngineTest::clearRegion() {
-    // Reuse mBuffer
-    clearLeftRegion();
-    expectBufferColor(Rect(DEFAULT_DISPLAY_WIDTH / 2, DEFAULT_DISPLAY_HEIGHT), 0, 0, 0, 255);
-    expectBufferColor(Rect(DEFAULT_DISPLAY_WIDTH / 2, 0, DEFAULT_DISPLAY_WIDTH,
-                           DEFAULT_DISPLAY_HEIGHT),
-                      0, 0, 0, 0);
-}
-
 template <typename SourceVariant>
 void RenderEngineTest::drawShadow(const renderengine::LayerSettings& castingLayer,
                                   const renderengine::ShadowSettings& shadow,
@@ -1226,7 +1407,7 @@
     settings.physicalDisplay = fullscreenRect();
     settings.clip = fullscreenRect();
 
-    std::vector<const renderengine::LayerSettings*> layers;
+    std::vector<renderengine::LayerSettings> layers;
 
     // add background layer
     renderengine::LayerSettings bgLayer;
@@ -1235,7 +1416,7 @@
     ColorSourceVariant::fillColor(bgLayer, backgroundColor.r / 255.0f, backgroundColor.g / 255.0f,
                                   backgroundColor.b / 255.0f, this);
     bgLayer.alpha = backgroundColor.a / 255.0f;
-    layers.push_back(&bgLayer);
+    layers.push_back(bgLayer);
 
     // add shadow layer
     renderengine::LayerSettings shadowLayer;
@@ -1243,14 +1424,14 @@
     shadowLayer.geometry.boundaries = castingLayer.geometry.boundaries;
     shadowLayer.alpha = castingLayer.alpha;
     shadowLayer.shadow = shadow;
-    layers.push_back(&shadowLayer);
+    layers.push_back(shadowLayer);
 
     // add layer casting the shadow
     renderengine::LayerSettings layer = castingLayer;
     layer.sourceDataspace = ui::Dataspace::V0_SRGB_LINEAR;
     SourceVariant::fillColor(layer, casterColor.r / 255.0f, casterColor.g / 255.0f,
                              casterColor.b / 255.0f, this);
-    layers.push_back(&layer);
+    layers.push_back(layer);
 
     invokeDraw(settings, layers);
 }
@@ -1263,7 +1444,7 @@
     settings.physicalDisplay = fullscreenRect();
     settings.clip = fullscreenRect();
 
-    std::vector<const renderengine::LayerSettings*> layers;
+    std::vector<renderengine::LayerSettings> layers;
 
     // add background layer
     renderengine::LayerSettings bgLayer;
@@ -1272,7 +1453,7 @@
     ColorSourceVariant::fillColor(bgLayer, backgroundColor.r / 255.0f, backgroundColor.g / 255.0f,
                                   backgroundColor.b / 255.0f, this);
     bgLayer.alpha = backgroundColor.a / 255.0f;
-    layers.push_back(&bgLayer);
+    layers.push_back(bgLayer);
 
     // add shadow layer
     renderengine::LayerSettings shadowLayer;
@@ -1282,11 +1463,130 @@
     shadowLayer.alpha = 1.0f;
     ColorSourceVariant::fillColor(shadowLayer, 0, 0, 0, this);
     shadowLayer.shadow = shadow;
-    layers.push_back(&shadowLayer);
+    layers.push_back(shadowLayer);
 
     invokeDraw(settings, layers);
 }
 
+void RenderEngineTest::tonemap(ui::Dataspace sourceDataspace, std::function<vec3(vec3)> eotf,
+                               std::function<vec3(vec3, float)> scaleOotf) {
+    constexpr int32_t kGreyLevels = 256;
+
+    const auto rect = Rect(0, 0, kGreyLevels, 1);
+
+    constexpr float kMaxLuminance = 750.f;
+    constexpr float kCurrentLuminanceNits = 500.f;
+    const renderengine::DisplaySettings display{
+            .physicalDisplay = rect,
+            .clip = rect,
+            .maxLuminance = kMaxLuminance,
+            .currentLuminanceNits = kCurrentLuminanceNits,
+            .outputDataspace = ui::Dataspace::DISPLAY_P3,
+    };
+
+    auto buf = std::make_shared<
+            renderengine::impl::
+                    ExternalTexture>(new GraphicBuffer(kGreyLevels, 1, HAL_PIXEL_FORMAT_RGBA_8888,
+                                                       1,
+                                                       GRALLOC_USAGE_SW_READ_OFTEN |
+                                                               GRALLOC_USAGE_SW_WRITE_OFTEN |
+                                                               GRALLOC_USAGE_HW_RENDER |
+                                                               GRALLOC_USAGE_HW_TEXTURE,
+                                                       "input"),
+                                     *mRE,
+                                     renderengine::impl::ExternalTexture::Usage::READABLE |
+                                             renderengine::impl::ExternalTexture::Usage::WRITEABLE);
+    ASSERT_EQ(0, buf->getBuffer()->initCheck());
+    {
+        uint8_t* pixels;
+        buf->getBuffer()->lock(GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN,
+                               reinterpret_cast<void**>(&pixels));
+
+        uint8_t color = 0;
+        for (int32_t j = 0; j < buf->getBuffer()->getHeight(); j++) {
+            uint8_t* dest = pixels + (buf->getBuffer()->getStride() * j * 4);
+            for (int32_t i = 0; i < buf->getBuffer()->getWidth(); i++) {
+                dest[0] = color;
+                dest[1] = color;
+                dest[2] = color;
+                dest[3] = 255;
+                color++;
+                dest += 4;
+            }
+        }
+        buf->getBuffer()->unlock();
+    }
+
+    mBuffer = std::make_shared<
+            renderengine::impl::
+                    ExternalTexture>(new GraphicBuffer(kGreyLevels, 1, HAL_PIXEL_FORMAT_RGBA_8888,
+                                                       1,
+                                                       GRALLOC_USAGE_SW_READ_OFTEN |
+                                                               GRALLOC_USAGE_SW_WRITE_OFTEN |
+                                                               GRALLOC_USAGE_HW_RENDER |
+                                                               GRALLOC_USAGE_HW_TEXTURE,
+                                                       "output"),
+                                     *mRE,
+                                     renderengine::impl::ExternalTexture::Usage::READABLE |
+                                             renderengine::impl::ExternalTexture::Usage::WRITEABLE);
+    ASSERT_EQ(0, mBuffer->getBuffer()->initCheck());
+
+    const renderengine::LayerSettings layer{.geometry.boundaries = rect.toFloatRect(),
+                                            .source =
+                                                    renderengine::PixelSource{
+                                                            .buffer =
+                                                                    renderengine::Buffer{
+                                                                            .buffer =
+                                                                                    std::move(buf),
+                                                                            .usePremultipliedAlpha =
+                                                                                    true,
+                                                                    },
+                                                    },
+                                            .alpha = 1.0f,
+                                            .sourceDataspace = sourceDataspace};
+
+    std::vector<renderengine::LayerSettings> layers{layer};
+    invokeDraw(display, layers);
+
+    ColorSpace displayP3 = ColorSpace::DisplayP3();
+    ColorSpace bt2020 = ColorSpace::BT2020();
+
+    tonemap::Metadata metadata{.displayMaxLuminance = 750.0f};
+
+    auto generator = [=](Point location) {
+        const double normColor = static_cast<double>(location.x) / (kGreyLevels - 1);
+        const vec3 rgb = vec3(normColor, normColor, normColor);
+
+        const vec3 linearRGB = eotf(rgb);
+
+        const vec3 xyz = bt2020.getRGBtoXYZ() * linearRGB;
+
+        const vec3 scaledXYZ = scaleOotf(xyz, kCurrentLuminanceNits);
+        const auto gains =
+                tonemap::getToneMapper()
+                        ->lookupTonemapGain(static_cast<aidl::android::hardware::graphics::common::
+                                                                Dataspace>(sourceDataspace),
+                                            static_cast<aidl::android::hardware::graphics::common::
+                                                                Dataspace>(
+                                                    ui::Dataspace::DISPLAY_P3),
+                                            {tonemap::
+                                                     Color{.linearRGB =
+                                                                   scaleOotf(linearRGB,
+                                                                             kCurrentLuminanceNits),
+                                                           .xyz = scaledXYZ}},
+                                            metadata);
+        EXPECT_EQ(1, gains.size());
+        const double gain = gains.front();
+        const vec3 normalizedXYZ = scaledXYZ * gain / metadata.displayMaxLuminance;
+
+        const vec3 targetRGB = OETF_sRGB(displayP3.getXYZtoRGB() * normalizedXYZ) * 255;
+        return ubyte4(static_cast<uint8_t>(targetRGB.r), static_cast<uint8_t>(targetRGB.g),
+                      static_cast<uint8_t>(targetRGB.b), 255);
+    };
+
+    expectBufferColor(Rect(kGreyLevels, 1), generator, 2);
+}
+
 INSTANTIATE_TEST_SUITE_P(PerRenderEngineType, RenderEngineTest,
                          testing::Values(std::make_shared<GLESRenderEngineFactory>(),
                                          std::make_shared<GLESCMRenderEngineFactory>(),
@@ -1307,7 +1607,8 @@
     settings.clip = fullscreenRect();
 
     // 255, 255, 255, 255 is full opaque white.
-    const ubyte4 backgroundColor(255.f, 255.f, 255.f, 255.f);
+    const ubyte4 backgroundColor(static_cast<uint8_t>(255), static_cast<uint8_t>(255),
+                                 static_cast<uint8_t>(255), static_cast<uint8_t>(255));
     // Create layer with given color.
     renderengine::LayerSettings bgLayer;
     bgLayer.sourceDataspace = ui::Dataspace::V0_SRGB_LINEAR;
@@ -1318,8 +1619,8 @@
     // Transform the red color.
     bgLayer.colorTransform = mat4(-1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
 
-    std::vector<const renderengine::LayerSettings*> layers;
-    layers.push_back(&bgLayer);
+    std::vector<renderengine::LayerSettings> layers;
+    layers.push_back(bgLayer);
 
     invokeDraw(settings, layers);
 
@@ -1333,35 +1634,18 @@
 
     renderengine::DisplaySettings settings;
     settings.outputDataspace = ui::Dataspace::V0_SRGB_LINEAR;
-    std::vector<const renderengine::LayerSettings*> layers;
+    std::vector<renderengine::LayerSettings> layers;
     renderengine::LayerSettings layer;
     layer.geometry.boundaries = fullscreenRect().toFloatRect();
     BufferSourceVariant<ForceOpaqueBufferVariant>::fillColor(layer, 1.0f, 0.0f, 0.0f, this);
-    layers.push_back(&layer);
-    base::unique_fd fence;
-    status_t status = mRE->drawLayers(settings, layers, nullptr, true, base::unique_fd(), &fence);
+    layers.push_back(layer);
+    std::future<renderengine::RenderEngineResult> result =
+            mRE->drawLayers(settings, layers, nullptr, true, base::unique_fd());
 
+    ASSERT_TRUE(result.valid());
+    auto [status, fence] = result.get();
     ASSERT_EQ(BAD_VALUE, status);
-}
-
-TEST_P(RenderEngineTest, drawLayers_nullOutputFence) {
-    initializeRenderEngine();
-
-    renderengine::DisplaySettings settings;
-    settings.outputDataspace = ui::Dataspace::V0_SRGB_LINEAR;
-    settings.physicalDisplay = fullscreenRect();
-    settings.clip = fullscreenRect();
-
-    std::vector<const renderengine::LayerSettings*> layers;
-    renderengine::LayerSettings layer;
-    layer.geometry.boundaries = fullscreenRect().toFloatRect();
-    BufferSourceVariant<ForceOpaqueBufferVariant>::fillColor(layer, 1.0f, 0.0f, 0.0f, this);
-    layer.alpha = 1.0;
-    layers.push_back(&layer);
-
-    status_t status = mRE->drawLayers(settings, layers, mBuffer, true, base::unique_fd(), nullptr);
-    ASSERT_EQ(NO_ERROR, status);
-    expectBufferColor(fullscreenRect(), 255, 0, 0, 255);
+    ASSERT_FALSE(fence.ok());
 }
 
 TEST_P(RenderEngineTest, drawLayers_doesNotCacheFramebuffer) {
@@ -1379,15 +1663,23 @@
     settings.physicalDisplay = fullscreenRect();
     settings.clip = fullscreenRect();
 
-    std::vector<const renderengine::LayerSettings*> layers;
+    std::vector<renderengine::LayerSettings> layers;
     renderengine::LayerSettings layer;
     layer.geometry.boundaries = fullscreenRect().toFloatRect();
     BufferSourceVariant<ForceOpaqueBufferVariant>::fillColor(layer, 1.0f, 0.0f, 0.0f, this);
     layer.alpha = 1.0;
-    layers.push_back(&layer);
+    layers.push_back(layer);
 
-    status_t status = mRE->drawLayers(settings, layers, mBuffer, false, base::unique_fd(), nullptr);
+    std::future<renderengine::RenderEngineResult> result =
+            mRE->drawLayers(settings, layers, mBuffer, false, base::unique_fd());
+    ASSERT_TRUE(result.valid());
+    auto [status, fence] = result.get();
+
     ASSERT_EQ(NO_ERROR, status);
+    if (fence.ok()) {
+        sync_wait(fence.get(), -1);
+    }
+
     ASSERT_FALSE(mGLESRE->isFramebufferImageCachedForTesting(mBuffer->getBuffer()->getId()));
     expectBufferColor(fullscreenRect(), 255, 0, 0, 255);
 }
@@ -1447,6 +1739,36 @@
     fillBufferColorTransform<ColorSourceVariant>();
 }
 
+TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransform_sourceDataspace) {
+    const auto& renderEngineFactory = GetParam();
+    // skip for non color management
+    if (!renderEngineFactory->useColorManagement()) {
+        return;
+    }
+    // skip for GLESRenderEngine
+    if (renderEngineFactory->type() != renderengine::RenderEngine::RenderEngineType::GLES) {
+        return;
+    }
+
+    initializeRenderEngine();
+    fillBufferColorTransformAndSourceDataspace<ColorSourceVariant>();
+}
+
+TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransform_outputDataspace) {
+    const auto& renderEngineFactory = GetParam();
+    // skip for non color management
+    if (!renderEngineFactory->useColorManagement()) {
+        return;
+    }
+    // skip for GLESRenderEngine
+    if (renderEngineFactory->type() != renderengine::RenderEngine::RenderEngineType::GLES) {
+        return;
+    }
+
+    initializeRenderEngine();
+    fillBufferColorTransformAndOutputDataspace<ColorSourceVariant>();
+}
+
 TEST_P(RenderEngineTest, drawLayers_fillBufferRoundedCorners_colorSource) {
     initializeRenderEngine();
     fillBufferWithRoundedCorners<ColorSourceVariant>();
@@ -1527,6 +1849,36 @@
     fillBufferColorTransform<BufferSourceVariant<ForceOpaqueBufferVariant>>();
 }
 
+TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransformAndSourceDataspace_opaqueBufferSource) {
+    const auto& renderEngineFactory = GetParam();
+    // skip for non color management
+    if (!renderEngineFactory->useColorManagement()) {
+        return;
+    }
+    // skip for GLESRenderEngine
+    if (renderEngineFactory->type() != renderengine::RenderEngine::RenderEngineType::GLES) {
+        return;
+    }
+
+    initializeRenderEngine();
+    fillBufferColorTransformAndSourceDataspace<BufferSourceVariant<ForceOpaqueBufferVariant>>();
+}
+
+TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransformAndOutputDataspace_opaqueBufferSource) {
+    const auto& renderEngineFactory = GetParam();
+    // skip for non color management
+    if (!renderEngineFactory->useColorManagement()) {
+        return;
+    }
+    // skip for GLESRenderEngine
+    if (renderEngineFactory->type() != renderengine::RenderEngine::RenderEngineType::GLES) {
+        return;
+    }
+
+    initializeRenderEngine();
+    fillBufferColorTransformAndOutputDataspace<BufferSourceVariant<ForceOpaqueBufferVariant>>();
+}
+
 TEST_P(RenderEngineTest, drawLayers_fillBufferRoundedCorners_opaqueBufferSource) {
     initializeRenderEngine();
     fillBufferWithRoundedCorners<BufferSourceVariant<ForceOpaqueBufferVariant>>();
@@ -1607,6 +1959,36 @@
     fillBufferColorTransform<BufferSourceVariant<RelaxOpaqueBufferVariant>>();
 }
 
+TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransformAndSourceDataspace_bufferSource) {
+    const auto& renderEngineFactory = GetParam();
+    // skip for non color management
+    if (!renderEngineFactory->useColorManagement()) {
+        return;
+    }
+    // skip for GLESRenderEngine
+    if (renderEngineFactory->type() != renderengine::RenderEngine::RenderEngineType::GLES) {
+        return;
+    }
+
+    initializeRenderEngine();
+    fillBufferColorTransformAndSourceDataspace<BufferSourceVariant<RelaxOpaqueBufferVariant>>();
+}
+
+TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransformAndOutputDataspace_bufferSource) {
+    const auto& renderEngineFactory = GetParam();
+    // skip for non color management
+    if (!renderEngineFactory->useColorManagement()) {
+        return;
+    }
+    // skip for GLESRenderEngine
+    if (renderEngineFactory->type() != renderengine::RenderEngine::RenderEngineType::GLES) {
+        return;
+    }
+
+    initializeRenderEngine();
+    fillBufferColorTransformAndOutputDataspace<BufferSourceVariant<RelaxOpaqueBufferVariant>>();
+}
+
 TEST_P(RenderEngineTest, drawLayers_fillBufferRoundedCorners_bufferSource) {
     initializeRenderEngine();
     fillBufferWithRoundedCorners<BufferSourceVariant<RelaxOpaqueBufferVariant>>();
@@ -1647,15 +2029,11 @@
     fillBufferWithoutPremultiplyAlpha();
 }
 
-TEST_P(RenderEngineTest, drawLayers_clearRegion) {
-    initializeRenderEngine();
-    clearRegion();
-}
-
 TEST_P(RenderEngineTest, drawLayers_fillShadow_castsWithoutCasterLayer) {
     initializeRenderEngine();
 
-    const ubyte4 backgroundColor(255, 255, 255, 255);
+    const ubyte4 backgroundColor(static_cast<uint8_t>(255), static_cast<uint8_t>(255),
+                                 static_cast<uint8_t>(255), static_cast<uint8_t>(255));
     const float shadowLength = 5.0f;
     Rect casterBounds(DEFAULT_DISPLAY_WIDTH / 3.0f, DEFAULT_DISPLAY_HEIGHT / 3.0f);
     casterBounds.offsetBy(shadowLength + 1, shadowLength + 1);
@@ -1670,8 +2048,10 @@
 TEST_P(RenderEngineTest, drawLayers_fillShadow_casterLayerMinSize) {
     initializeRenderEngine();
 
-    const ubyte4 casterColor(255, 0, 0, 255);
-    const ubyte4 backgroundColor(255, 255, 255, 255);
+    const ubyte4 casterColor(static_cast<uint8_t>(255), static_cast<uint8_t>(0),
+                             static_cast<uint8_t>(0), static_cast<uint8_t>(255));
+    const ubyte4 backgroundColor(static_cast<uint8_t>(255), static_cast<uint8_t>(255),
+                                 static_cast<uint8_t>(255), static_cast<uint8_t>(255));
     const float shadowLength = 5.0f;
     Rect casterBounds(1, 1);
     casterBounds.offsetBy(shadowLength + 1, shadowLength + 1);
@@ -1689,8 +2069,10 @@
 TEST_P(RenderEngineTest, drawLayers_fillShadow_casterColorLayer) {
     initializeRenderEngine();
 
-    const ubyte4 casterColor(255, 0, 0, 255);
-    const ubyte4 backgroundColor(255, 255, 255, 255);
+    const ubyte4 casterColor(static_cast<uint8_t>(255), static_cast<uint8_t>(0),
+                             static_cast<uint8_t>(0), static_cast<uint8_t>(255));
+    const ubyte4 backgroundColor(static_cast<uint8_t>(255), static_cast<uint8_t>(255),
+                                 static_cast<uint8_t>(255), static_cast<uint8_t>(255));
     const float shadowLength = 5.0f;
     Rect casterBounds(DEFAULT_DISPLAY_WIDTH / 3.0f, DEFAULT_DISPLAY_HEIGHT / 3.0f);
     casterBounds.offsetBy(shadowLength + 1, shadowLength + 1);
@@ -1709,8 +2091,10 @@
 TEST_P(RenderEngineTest, drawLayers_fillShadow_casterOpaqueBufferLayer) {
     initializeRenderEngine();
 
-    const ubyte4 casterColor(255, 0, 0, 255);
-    const ubyte4 backgroundColor(255, 255, 255, 255);
+    const ubyte4 casterColor(static_cast<uint8_t>(255), static_cast<uint8_t>(0),
+                             static_cast<uint8_t>(0), static_cast<uint8_t>(255));
+    const ubyte4 backgroundColor(static_cast<uint8_t>(255), static_cast<uint8_t>(255),
+                                 static_cast<uint8_t>(255), static_cast<uint8_t>(255));
     const float shadowLength = 5.0f;
     Rect casterBounds(DEFAULT_DISPLAY_WIDTH / 3.0f, DEFAULT_DISPLAY_HEIGHT / 3.0f);
     casterBounds.offsetBy(shadowLength + 1, shadowLength + 1);
@@ -1730,8 +2114,10 @@
 TEST_P(RenderEngineTest, drawLayers_fillShadow_casterWithRoundedCorner) {
     initializeRenderEngine();
 
-    const ubyte4 casterColor(255, 0, 0, 255);
-    const ubyte4 backgroundColor(255, 255, 255, 255);
+    const ubyte4 casterColor(static_cast<uint8_t>(255), static_cast<uint8_t>(0),
+                             static_cast<uint8_t>(0), static_cast<uint8_t>(255));
+    const ubyte4 backgroundColor(static_cast<uint8_t>(255), static_cast<uint8_t>(255),
+                                 static_cast<uint8_t>(255), static_cast<uint8_t>(255));
     const float shadowLength = 5.0f;
     Rect casterBounds(DEFAULT_DISPLAY_WIDTH / 3.0f, DEFAULT_DISPLAY_HEIGHT / 3.0f);
     casterBounds.offsetBy(shadowLength + 1, shadowLength + 1);
@@ -1784,22 +2170,28 @@
     settings.clip = fullscreenRect();
     settings.outputDataspace = ui::Dataspace::V0_SRGB_LINEAR;
 
-    std::vector<const renderengine::LayerSettings*> layers;
+    std::vector<renderengine::LayerSettings> layers;
     renderengine::LayerSettings layer;
     layer.geometry.boundaries = fullscreenRect().toFloatRect();
     BufferSourceVariant<ForceOpaqueBufferVariant>::fillColor(layer, 1.0f, 0.0f, 0.0f, this);
     layer.alpha = 1.0;
-    layers.push_back(&layer);
+    layers.push_back(layer);
 
-    base::unique_fd fenceOne;
-    mRE->drawLayers(settings, layers, mBuffer, true, base::unique_fd(), &fenceOne);
-    base::unique_fd fenceTwo;
-    mRE->drawLayers(settings, layers, mBuffer, true, std::move(fenceOne), &fenceTwo);
+    std::future<renderengine::RenderEngineResult> resultOne =
+            mRE->drawLayers(settings, layers, mBuffer, true, base::unique_fd());
+    ASSERT_TRUE(resultOne.valid());
+    auto [statusOne, fenceOne] = resultOne.get();
+    ASSERT_EQ(NO_ERROR, statusOne);
 
-    const int fd = fenceTwo.get();
-    if (fd >= 0) {
-        sync_wait(fd, -1);
+    std::future<renderengine::RenderEngineResult> resultTwo =
+            mRE->drawLayers(settings, layers, mBuffer, true, std::move(fenceOne));
+    ASSERT_TRUE(resultTwo.valid());
+    auto [statusTwo, fenceTwo] = resultTwo.get();
+    ASSERT_EQ(NO_ERROR, statusTwo);
+    if (fenceTwo.ok()) {
+        sync_wait(fenceTwo.get(), -1);
     }
+
     // Only cleanup the first time.
     EXPECT_FALSE(mRE->canSkipPostRenderCleanup());
     mRE->cleanupPostRender();
@@ -1814,7 +2206,7 @@
     settings.clip = fullscreenRect();
     settings.outputDataspace = ui::Dataspace::V0_SRGB_LINEAR;
 
-    std::vector<const renderengine::LayerSettings*> layers;
+    std::vector<renderengine::LayerSettings> layers;
 
     renderengine::LayerSettings redLayer;
     redLayer.sourceDataspace = ui::Dataspace::V0_SRGB_LINEAR;
@@ -1825,7 +2217,7 @@
     redLayer.source.solidColor = half3(1.0f, 0.0f, 0.0f);
     redLayer.alpha = 1.0f;
 
-    layers.push_back(&redLayer);
+    layers.push_back(redLayer);
 
     // Green layer with 1/3 size.
     renderengine::LayerSettings greenLayer;
@@ -1840,7 +2232,7 @@
     greenLayer.source.solidColor = half3(0.0f, 1.0f, 0.0f);
     greenLayer.alpha = 1.0f;
 
-    layers.push_back(&greenLayer);
+    layers.push_back(greenLayer);
 
     invokeDraw(settings, layers);
 
@@ -1863,7 +2255,7 @@
     settings.clip = fullscreenRect();
     settings.outputDataspace = ui::Dataspace::V0_SRGB_LINEAR;
 
-    std::vector<const renderengine::LayerSettings*> layers;
+    std::vector<renderengine::LayerSettings> layers;
 
     renderengine::LayerSettings redLayer;
     redLayer.sourceDataspace = ui::Dataspace::V0_SRGB_LINEAR;
@@ -1874,7 +2266,7 @@
     redLayer.source.solidColor = half3(1.0f, 0.0f, 0.0f);
     redLayer.alpha = 1.0f;
 
-    layers.push_back(&redLayer);
+    layers.push_back(redLayer);
 
     // Green layer with 1/2 size with parent crop rect.
     renderengine::LayerSettings greenLayer = redLayer;
@@ -1882,7 +2274,7 @@
             FloatRect(0, 0, DEFAULT_DISPLAY_WIDTH, DEFAULT_DISPLAY_HEIGHT / 2);
     greenLayer.source.solidColor = half3(0.0f, 1.0f, 0.0f);
 
-    layers.push_back(&greenLayer);
+    layers.push_back(greenLayer);
 
     invokeDraw(settings, layers);
 
@@ -1900,6 +2292,40 @@
     expectBufferColor(Point(0, (DEFAULT_DISPLAY_HEIGHT / 2) - 1), 0, 255, 0, 255);
 }
 
+TEST_P(RenderEngineTest, testRoundedCornersParentCropSmallBounds) {
+    initializeRenderEngine();
+
+    renderengine::DisplaySettings settings;
+    settings.physicalDisplay = fullscreenRect();
+    settings.clip = fullscreenRect();
+    settings.outputDataspace = ui::Dataspace::V0_SRGB_LINEAR;
+
+    std::vector<renderengine::LayerSettings> layers;
+
+    renderengine::LayerSettings redLayer;
+    redLayer.sourceDataspace = ui::Dataspace::V0_SRGB_LINEAR;
+    redLayer.geometry.boundaries = FloatRect(0, 0, DEFAULT_DISPLAY_WIDTH, 32);
+    redLayer.geometry.roundedCornersRadius = 64;
+    redLayer.geometry.roundedCornersCrop = FloatRect(0, 0, DEFAULT_DISPLAY_WIDTH, 128);
+    // Red background.
+    redLayer.source.solidColor = half3(1.0f, 0.0f, 0.0f);
+    redLayer.alpha = 1.0f;
+
+    layers.push_back(redLayer);
+    invokeDraw(settings, layers);
+
+    // Due to roundedCornersRadius, the top corners are untouched.
+    expectBufferColor(Point(0, 0), 0, 0, 0, 0);
+    expectBufferColor(Point(DEFAULT_DISPLAY_WIDTH - 1, 0), 0, 0, 0, 0);
+
+    // ensure that the entire height of the red layer was clipped by the rounded corners crop.
+    expectBufferColor(Point(0, 31), 0, 0, 0, 0);
+    expectBufferColor(Point(DEFAULT_DISPLAY_WIDTH - 1, 31), 0, 0, 0, 0);
+
+    // the bottom middle should be red
+    expectBufferColor(Point(DEFAULT_DISPLAY_WIDTH / 2, 31), 255, 0, 0, 255);
+}
+
 TEST_P(RenderEngineTest, testClear) {
     initializeRenderEngine();
 
@@ -1924,7 +2350,7 @@
             .disableBlending = true,
     };
 
-    std::vector<const renderengine::LayerSettings*> layers{&redLayer, &clearLayer};
+    std::vector<renderengine::LayerSettings> layers{redLayer, clearLayer};
     invokeDraw(display, layers);
     expectBufferColor(rect, 0, 0, 0, 0);
 }
@@ -1972,11 +2398,138 @@
             .disableBlending = true,
     };
 
-    std::vector<const renderengine::LayerSettings*> layers{&redLayer, &greenLayer};
+    std::vector<renderengine::LayerSettings> layers{redLayer, greenLayer};
     invokeDraw(display, layers);
     expectBufferColor(rect, 0, 128, 0, 128);
 }
 
+TEST_P(RenderEngineTest, testDimming) {
+    if (GetParam()->type() == renderengine::RenderEngine::RenderEngineType::GLES) {
+        return;
+    }
+    initializeRenderEngine();
+
+    const auto displayRect = Rect(3, 1);
+    const renderengine::DisplaySettings display{
+            .physicalDisplay = displayRect,
+            .clip = displayRect,
+            .outputDataspace = ui::Dataspace::V0_SRGB_LINEAR,
+            .targetLuminanceNits = 1000.f,
+    };
+
+    const auto greenBuffer = allocateAndFillSourceBuffer(1, 1, ubyte4(0, 255, 0, 255));
+    const auto blueBuffer = allocateAndFillSourceBuffer(1, 1, ubyte4(0, 0, 255, 255));
+    const auto redBuffer = allocateAndFillSourceBuffer(1, 1, ubyte4(255, 0, 0, 255));
+
+    const renderengine::LayerSettings greenLayer{
+            .geometry.boundaries = FloatRect(0.f, 0.f, 1.f, 1.f),
+            .source =
+                    renderengine::PixelSource{
+                            .buffer =
+                                    renderengine::Buffer{
+                                            .buffer = greenBuffer,
+                                            .usePremultipliedAlpha = true,
+                                    },
+                    },
+            .alpha = 1.0f,
+            .sourceDataspace = ui::Dataspace::V0_SRGB_LINEAR,
+            .whitePointNits = 200.f,
+    };
+
+    const renderengine::LayerSettings blueLayer{
+            .geometry.boundaries = FloatRect(1.f, 0.f, 2.f, 1.f),
+            .source =
+                    renderengine::PixelSource{
+                            .buffer =
+                                    renderengine::Buffer{
+                                            .buffer = blueBuffer,
+                                            .usePremultipliedAlpha = true,
+                                    },
+                    },
+            .alpha = 1.0f,
+            .sourceDataspace = ui::Dataspace::V0_SRGB_LINEAR,
+            .whitePointNits = 1000.f / 51.f,
+    };
+
+    const renderengine::LayerSettings redLayer{
+            .geometry.boundaries = FloatRect(2.f, 0.f, 3.f, 1.f),
+            .source =
+                    renderengine::PixelSource{
+                            .buffer =
+                                    renderengine::Buffer{
+                                            .buffer = redBuffer,
+                                            .usePremultipliedAlpha = true,
+                                    },
+                    },
+            .alpha = 1.0f,
+            .sourceDataspace = ui::Dataspace::V0_SRGB_LINEAR,
+            // When the white point is not set for a layer, just ignore it and treat it as the same
+            // as the max layer
+            .whitePointNits = -1.f,
+    };
+
+    std::vector<renderengine::LayerSettings> layers{greenLayer, blueLayer, redLayer};
+    invokeDraw(display, layers);
+
+    expectBufferColor(Rect(1, 1), 0, 51, 0, 255, 1);
+    expectBufferColor(Rect(1, 0, 2, 1), 0, 0, 5, 255, 1);
+    expectBufferColor(Rect(2, 0, 3, 1), 51, 0, 0, 255, 1);
+}
+
+TEST_P(RenderEngineTest, testDimming_withoutTargetLuminance) {
+    initializeRenderEngine();
+    if (GetParam()->type() == renderengine::RenderEngine::RenderEngineType::GLES) {
+        return;
+    }
+
+    const auto displayRect = Rect(2, 1);
+    const renderengine::DisplaySettings display{
+            .physicalDisplay = displayRect,
+            .clip = displayRect,
+            .outputDataspace = ui::Dataspace::V0_SRGB_LINEAR,
+            .targetLuminanceNits = -1.f,
+    };
+
+    const auto greenBuffer = allocateAndFillSourceBuffer(1, 1, ubyte4(0, 255, 0, 255));
+    const auto blueBuffer = allocateAndFillSourceBuffer(1, 1, ubyte4(0, 0, 255, 255));
+
+    const renderengine::LayerSettings greenLayer{
+            .geometry.boundaries = FloatRect(0.f, 0.f, 1.f, 1.f),
+            .source =
+                    renderengine::PixelSource{
+                            .buffer =
+                                    renderengine::Buffer{
+                                            .buffer = greenBuffer,
+                                            .usePremultipliedAlpha = true,
+                                    },
+                    },
+            .alpha = 1.0f,
+            .sourceDataspace = ui::Dataspace::V0_SRGB_LINEAR,
+            .whitePointNits = 200.f,
+    };
+
+    const renderengine::LayerSettings blueLayer{
+            .geometry.boundaries = FloatRect(1.f, 0.f, 2.f, 1.f),
+            .source =
+                    renderengine::PixelSource{
+                            .buffer =
+                                    renderengine::Buffer{
+                                            .buffer = blueBuffer,
+                                            .usePremultipliedAlpha = true,
+                                    },
+                    },
+            .alpha = 1.0f,
+            .sourceDataspace = ui::Dataspace::V0_SRGB_LINEAR,
+            .whitePointNits = 1000.f,
+    };
+
+    std::vector<renderengine::LayerSettings> layers{greenLayer, blueLayer};
+    invokeDraw(display, layers);
+
+    expectBufferColor(Rect(1, 1), 0, 51, 0, 255, 1);
+    expectBufferColor(Rect(1, 0, 2, 1), 0, 0, 255, 255);
+}
+
 TEST_P(RenderEngineTest, test_isOpaque) {
     initializeRenderEngine();
 
@@ -2018,7 +2571,7 @@
             .alpha = 1.0f,
     };
 
-    std::vector<const renderengine::LayerSettings*> layers{&greenLayer};
+    std::vector<renderengine::LayerSettings> layers{greenLayer};
     invokeDraw(display, layers);
 
     if (GetParam()->useColorManagement()) {
@@ -2027,6 +2580,247 @@
         expectBufferColor(rect, 0, 255, 0, 255);
     }
 }
+
+TEST_P(RenderEngineTest, test_tonemapPQMatches) {
+    if (!GetParam()->useColorManagement()) {
+        GTEST_SKIP();
+    }
+
+    if (GetParam()->type() == renderengine::RenderEngine::RenderEngineType::GLES) {
+        GTEST_SKIP();
+    }
+
+    initializeRenderEngine();
+
+    tonemap(
+            static_cast<ui::Dataspace>(HAL_DATASPACE_STANDARD_BT2020 |
+                                       HAL_DATASPACE_TRANSFER_ST2084 | HAL_DATASPACE_RANGE_FULL),
+            [](vec3 color) { return EOTF_PQ(color); },
+            [](vec3 color, float) {
+                static constexpr float kMaxPQLuminance = 10000.f;
+                return color * kMaxPQLuminance;
+            });
+}
+
+TEST_P(RenderEngineTest, test_tonemapHLGMatches) {
+    if (!GetParam()->useColorManagement()) {
+        GTEST_SKIP();
+    }
+
+    if (GetParam()->type() == renderengine::RenderEngine::RenderEngineType::GLES) {
+        GTEST_SKIP();
+    }
+
+    initializeRenderEngine();
+
+    tonemap(
+            static_cast<ui::Dataspace>(HAL_DATASPACE_STANDARD_BT2020 | HAL_DATASPACE_TRANSFER_HLG |
+                                       HAL_DATASPACE_RANGE_FULL),
+            [](vec3 color) { return EOTF_HLG(color); },
+            [](vec3 color, float currentLuminaceNits) {
+                static constexpr float kMaxHLGLuminance = 1000.f;
+                return color * kMaxHLGLuminance;
+            });
+}
+
+TEST_P(RenderEngineTest, r8_behaves_as_mask) {
+    if (GetParam()->type() == renderengine::RenderEngine::RenderEngineType::GLES) {
+        return;
+    }
+
+    initializeRenderEngine();
+
+    const auto r8Buffer = allocateR8Buffer(2, 1);
+    if (!r8Buffer) {
+        GTEST_SKIP() << "Test is only necessary on devices that support r8";
+        return;
+    }
+    {
+        uint8_t* pixels;
+        r8Buffer->getBuffer()->lock(GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN,
+                                    reinterpret_cast<void**>(&pixels));
+        // This will be drawn on top of a green buffer. We'll verify that 255
+        // results in keeping the original green and 0 results in black.
+        pixels[0] = 0;
+        pixels[1] = 255;
+        r8Buffer->getBuffer()->unlock();
+    }
+
+    const auto rect = Rect(0, 0, 2, 1);
+    const renderengine::DisplaySettings display{
+            .physicalDisplay = rect,
+            .clip = rect,
+            .outputDataspace = ui::Dataspace::SRGB,
+    };
+
+    const auto greenBuffer = allocateAndFillSourceBuffer(2, 1, ubyte4(0, 255, 0, 255));
+    const renderengine::LayerSettings greenLayer{
+            .geometry.boundaries = rect.toFloatRect(),
+            .source =
+                    renderengine::PixelSource{
+                            .buffer =
+                                    renderengine::Buffer{
+                                            .buffer = greenBuffer,
+                                    },
+                    },
+            .alpha = 1.0f,
+    };
+    const renderengine::LayerSettings r8Layer{
+            .geometry.boundaries = rect.toFloatRect(),
+            .source =
+                    renderengine::PixelSource{
+                            .buffer =
+                                    renderengine::Buffer{
+                                            .buffer = r8Buffer,
+                                    },
+                    },
+            .alpha = 1.0f,
+    };
+
+    std::vector<renderengine::LayerSettings> layers{greenLayer, r8Layer};
+    invokeDraw(display, layers);
+
+    expectBufferColor(Rect(0, 0, 1, 1), 0,   0, 0, 255);
+    expectBufferColor(Rect(1, 0, 2, 1), 0, 255, 0, 255);
+}
+
+TEST_P(RenderEngineTest, r8_respects_color_transform) {
+    if (GetParam()->type() == renderengine::RenderEngine::RenderEngineType::GLES) {
+        return;
+    }
+
+    initializeRenderEngine();
+
+    const auto r8Buffer = allocateR8Buffer(2, 1);
+    if (!r8Buffer) {
+        GTEST_SKIP() << "Test is only necessary on devices that support r8";
+        return;
+    }
+    {
+        uint8_t* pixels;
+        r8Buffer->getBuffer()->lock(GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN,
+                                    reinterpret_cast<void**>(&pixels));
+        pixels[0] = 0;
+        pixels[1] = 255;
+        r8Buffer->getBuffer()->unlock();
+    }
+
+    const auto rect = Rect(0, 0, 2, 1);
+    const renderengine::DisplaySettings display{
+            .physicalDisplay = rect,
+            .clip = rect,
+            .outputDataspace = ui::Dataspace::SRGB,
+            // Verify that the R8 layer respects the color transform when
+            // deviceHandlesColorTransform is false. This transform converts
+            // pure red to pure green. That will occur when the R8 buffer is
+            // 255. When the R8 buffer is 0, it will still change to black, as
+            // with r8_behaves_as_mask.
+            .colorTransform = mat4(0, 1, 0, 0,
+                                   0, 0, 0, 0,
+                                   0, 0, 1, 0,
+                                   0, 0, 0, 1),
+            .deviceHandlesColorTransform = false,
+    };
+
+    const auto redBuffer = allocateAndFillSourceBuffer(2, 1, ubyte4(255, 0, 0, 255));
+    const renderengine::LayerSettings redLayer{
+            .geometry.boundaries = rect.toFloatRect(),
+            .source =
+                    renderengine::PixelSource{
+                            .buffer =
+                                    renderengine::Buffer{
+                                            .buffer = redBuffer,
+                                    },
+                    },
+            .alpha = 1.0f,
+    };
+    const renderengine::LayerSettings r8Layer{
+            .geometry.boundaries = rect.toFloatRect(),
+            .source =
+                    renderengine::PixelSource{
+                            .buffer =
+                                    renderengine::Buffer{
+                                            .buffer = r8Buffer,
+                                    },
+                    },
+            .alpha = 1.0f,
+    };
+
+    std::vector<renderengine::LayerSettings> layers{redLayer, r8Layer};
+    invokeDraw(display, layers);
+
+    expectBufferColor(Rect(0, 0, 1, 1), 0,   0, 0, 255);
+    expectBufferColor(Rect(1, 0, 2, 1), 0, 255, 0, 255);
+}
+
+TEST_P(RenderEngineTest, r8_respects_color_transform_when_device_handles) {
+    if (GetParam()->type() == renderengine::RenderEngine::RenderEngineType::GLES) {
+        return;
+    }
+
+    initializeRenderEngine();
+
+    const auto r8Buffer = allocateR8Buffer(2, 1);
+    if (!r8Buffer) {
+        GTEST_SKIP() << "Test is only necessary on devices that support r8";
+        return;
+    }
+    {
+        uint8_t* pixels;
+        r8Buffer->getBuffer()->lock(GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN,
+                                    reinterpret_cast<void**>(&pixels));
+        pixels[0] = 0;
+        pixels[1] = 255;
+        r8Buffer->getBuffer()->unlock();
+    }
+
+    const auto rect = Rect(0, 0, 2, 1);
+    const renderengine::DisplaySettings display{
+            .physicalDisplay = rect,
+            .clip = rect,
+            .outputDataspace = ui::Dataspace::SRGB,
+            // If deviceHandlesColorTransform is true, pixels where the A8
+            // buffer is opaque are unaffected. If the colorTransform is
+            // invertible, pixels where the A8 buffer are transparent have the
+            // inverse applied to them so that the DPU will convert them back to
+            // black. Test with an arbitrary, invertible matrix.
+            .colorTransform = mat4(1, 0, 0, 2,
+                                   3, 1, 2, 5,
+                                   0, 5, 3, 0,
+                                   0, 1, 0, 2),
+            .deviceHandlesColorTransform = true,
+    };
+
+    const auto redBuffer = allocateAndFillSourceBuffer(2, 1, ubyte4(255, 0, 0, 255));
+    const renderengine::LayerSettings redLayer{
+            .geometry.boundaries = rect.toFloatRect(),
+            .source =
+                    renderengine::PixelSource{
+                            .buffer =
+                                    renderengine::Buffer{
+                                            .buffer = redBuffer,
+                                    },
+                    },
+            .alpha = 1.0f,
+    };
+    const renderengine::LayerSettings r8Layer{
+            .geometry.boundaries = rect.toFloatRect(),
+            .source =
+                    renderengine::PixelSource{
+                            .buffer =
+                                    renderengine::Buffer{
+                                            .buffer = r8Buffer,
+                                    },
+                    },
+            .alpha = 1.0f,
+    };
+
+    std::vector<renderengine::LayerSettings> layers{redLayer, r8Layer};
+    invokeDraw(display, layers);
+
+    expectBufferColor(Rect(1, 0, 2, 1), 255, 0, 0, 255); // Still red.
+    expectBufferColor(Rect(0, 0, 1, 1), 0,  70, 0, 255);
+}
 } // namespace renderengine
 } // namespace android
 
diff --git a/libs/renderengine/tests/RenderEngineThreadedTest.cpp b/libs/renderengine/tests/RenderEngineThreadedTest.cpp
index 830f463..9685189 100644
--- a/libs/renderengine/tests/RenderEngineThreadedTest.cpp
+++ b/libs/renderengine/tests/RenderEngineThreadedTest.cpp
@@ -17,6 +17,7 @@
 #include <cutils/properties.h>
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
+#include <renderengine/impl/ExternalTexture.h>
 #include <renderengine/mock/RenderEngine.h>
 #include "../threaded/RenderEngineThreaded.h"
 
@@ -169,25 +170,33 @@
     status_t result = mThreadedRE->supportsBackgroundBlur();
     ASSERT_EQ(true, result);
 }
+
 TEST_F(RenderEngineThreadedTest, drawLayers) {
     renderengine::DisplaySettings settings;
-    std::vector<const renderengine::LayerSettings*> layers;
+    std::vector<renderengine::LayerSettings> layers;
     std::shared_ptr<renderengine::ExternalTexture> buffer = std::make_shared<
-            renderengine::ExternalTexture>(new GraphicBuffer(), *mRenderEngine,
-                                           renderengine::ExternalTexture::Usage::READABLE |
-                                                   renderengine::ExternalTexture::Usage::WRITEABLE);
+            renderengine::impl::
+                    ExternalTexture>(new GraphicBuffer(), *mRenderEngine,
+                                     renderengine::impl::ExternalTexture::Usage::READABLE |
+                                             renderengine::impl::ExternalTexture::Usage::WRITEABLE);
+
     base::unique_fd bufferFence;
-    base::unique_fd drawFence;
 
-    EXPECT_CALL(*mRenderEngine, drawLayers)
-            .WillOnce([](const renderengine::DisplaySettings&,
-                         const std::vector<const renderengine::LayerSettings*>&,
-                         const std::shared_ptr<renderengine::ExternalTexture>&, const bool,
-                         base::unique_fd&&, base::unique_fd*) -> status_t { return NO_ERROR; });
+    EXPECT_CALL(*mRenderEngine, drawLayersInternal)
+            .WillOnce([&](const std::shared_ptr<std::promise<renderengine::RenderEngineResult>>&&
+                                  resultPromise,
+                          const renderengine::DisplaySettings&,
+                          const std::vector<renderengine::LayerSettings>&,
+                          const std::shared_ptr<renderengine::ExternalTexture>&, const bool,
+                          base::unique_fd&&) -> void {
+                resultPromise->set_value({NO_ERROR, base::unique_fd()});
+            });
 
-    status_t result = mThreadedRE->drawLayers(settings, layers, buffer, false,
-                                              std::move(bufferFence), &drawFence);
-    ASSERT_EQ(NO_ERROR, result);
+    std::future<renderengine::RenderEngineResult> result =
+            mThreadedRE->drawLayers(settings, layers, buffer, false, std::move(bufferFence));
+    ASSERT_TRUE(result.valid());
+    auto [status, _] = result.get();
+    ASSERT_EQ(NO_ERROR, status);
 }
 
 } // namespace android
diff --git a/libs/renderengine/threaded/RenderEngineThreaded.cpp b/libs/renderengine/threaded/RenderEngineThreaded.cpp
index 8e666d5..203bb54 100644
--- a/libs/renderengine/threaded/RenderEngineThreaded.cpp
+++ b/libs/renderengine/threaded/RenderEngineThreaded.cpp
@@ -178,6 +178,10 @@
 
 void RenderEngineThreaded::genTextures(size_t count, uint32_t* names) {
     ATRACE_CALL();
+    // This is a no-op in SkiaRenderEngine.
+    if (getRenderEngineType() != RenderEngineType::THREADED) {
+        return;
+    }
     std::promise<void> resultPromise;
     std::future<void> resultFuture = resultPromise.get_future();
     {
@@ -194,6 +198,10 @@
 
 void RenderEngineThreaded::deleteTextures(size_t count, uint32_t const* names) {
     ATRACE_CALL();
+    // This is a no-op in SkiaRenderEngine.
+    if (getRenderEngineType() != RenderEngineType::THREADED) {
+        return;
+    }
     std::promise<void> resultPromise;
     std::future<void> resultFuture = resultPromise.get_future();
     {
@@ -292,7 +300,7 @@
     {
         std::lock_guard lock(mThreadMutex);
         mFunctionCalls.push([=](renderengine::RenderEngine& instance) {
-            ATRACE_NAME("REThreaded::unmapExternalTextureBuffer");
+            ATRACE_NAME("REThreaded::cleanupPostRender");
             instance.cleanupPostRender();
         });
     }
@@ -304,27 +312,34 @@
     return mRenderEngine->canSkipPostRenderCleanup();
 }
 
-status_t RenderEngineThreaded::drawLayers(const DisplaySettings& display,
-                                          const std::vector<const LayerSettings*>& layers,
-                                          const std::shared_ptr<ExternalTexture>& buffer,
-                                          const bool useFramebufferCache,
-                                          base::unique_fd&& bufferFence,
-                                          base::unique_fd* drawFence) {
+void RenderEngineThreaded::drawLayersInternal(
+        const std::shared_ptr<std::promise<RenderEngineResult>>&& resultPromise,
+        const DisplaySettings& display, const std::vector<LayerSettings>& layers,
+        const std::shared_ptr<ExternalTexture>& buffer, const bool useFramebufferCache,
+        base::unique_fd&& bufferFence) {
+    resultPromise->set_value({NO_ERROR, base::unique_fd()});
+    return;
+}
+
+std::future<RenderEngineResult> RenderEngineThreaded::drawLayers(
+        const DisplaySettings& display, const std::vector<LayerSettings>& layers,
+        const std::shared_ptr<ExternalTexture>& buffer, const bool useFramebufferCache,
+        base::unique_fd&& bufferFence) {
     ATRACE_CALL();
-    std::promise<status_t> resultPromise;
-    std::future<status_t> resultFuture = resultPromise.get_future();
+    const auto resultPromise = std::make_shared<std::promise<RenderEngineResult>>();
+    std::future<RenderEngineResult> resultFuture = resultPromise->get_future();
+    int fd = bufferFence.release();
     {
         std::lock_guard lock(mThreadMutex);
-        mFunctionCalls.push([&resultPromise, &display, &layers, &buffer, useFramebufferCache,
-                             &bufferFence, &drawFence](renderengine::RenderEngine& instance) {
+        mFunctionCalls.push([resultPromise, display, layers, buffer, useFramebufferCache,
+                             fd](renderengine::RenderEngine& instance) {
             ATRACE_NAME("REThreaded::drawLayers");
-            status_t status = instance.drawLayers(display, layers, buffer, useFramebufferCache,
-                                                  std::move(bufferFence), drawFence);
-            resultPromise.set_value(status);
+            instance.drawLayersInternal(std::move(resultPromise), display, layers, buffer,
+                                        useFramebufferCache, base::unique_fd(fd));
         });
     }
     mCondition.notify_one();
-    return resultFuture.get();
+    return resultFuture;
 }
 
 void RenderEngineThreaded::cleanFramebufferCache() {
@@ -374,6 +389,32 @@
     mCondition.notify_one();
 }
 
+std::optional<pid_t> RenderEngineThreaded::getRenderEngineTid() const {
+    std::promise<pid_t> tidPromise;
+    std::future<pid_t> tidFuture = tidPromise.get_future();
+    {
+        std::lock_guard lock(mThreadMutex);
+        mFunctionCalls.push([&tidPromise](renderengine::RenderEngine& instance) {
+            tidPromise.set_value(gettid());
+        });
+    }
+
+    mCondition.notify_one();
+    return std::make_optional(tidFuture.get());
+}
+
+void RenderEngineThreaded::setEnableTracing(bool tracingEnabled) {
+    // This function is designed so it can run asynchronously, so we do not need to wait
+    // for the futures.
+    {
+        std::lock_guard lock(mThreadMutex);
+        mFunctionCalls.push([tracingEnabled](renderengine::RenderEngine& instance) {
+            ATRACE_NAME("REThreaded::setEnableTracing");
+            instance.setEnableTracing(tracingEnabled);
+        });
+    }
+    mCondition.notify_one();
+}
 } // namespace threaded
 } // namespace renderengine
 } // namespace android
diff --git a/libs/renderengine/threaded/RenderEngineThreaded.h b/libs/renderengine/threaded/RenderEngineThreaded.h
index b197df7..1340902 100644
--- a/libs/renderengine/threaded/RenderEngineThreaded.h
+++ b/libs/renderengine/threaded/RenderEngineThreaded.h
@@ -56,21 +56,28 @@
     void useProtectedContext(bool useProtectedContext) override;
     void cleanupPostRender() override;
 
-    status_t drawLayers(const DisplaySettings& display,
-                        const std::vector<const LayerSettings*>& layers,
-                        const std::shared_ptr<ExternalTexture>& buffer,
-                        const bool useFramebufferCache, base::unique_fd&& bufferFence,
-                        base::unique_fd* drawFence) override;
+    std::future<RenderEngineResult> drawLayers(const DisplaySettings& display,
+                                               const std::vector<LayerSettings>& layers,
+                                               const std::shared_ptr<ExternalTexture>& buffer,
+                                               const bool useFramebufferCache,
+                                               base::unique_fd&& bufferFence) override;
 
     void cleanFramebufferCache() override;
     int getContextPriority() override;
     bool supportsBackgroundBlur() override;
     void onActiveDisplaySizeChanged(ui::Size size) override;
+    std::optional<pid_t> getRenderEngineTid() const override;
+    void setEnableTracing(bool tracingEnabled) override;
 
 protected:
     void mapExternalTextureBuffer(const sp<GraphicBuffer>& buffer, bool isRenderable) override;
     void unmapExternalTextureBuffer(const sp<GraphicBuffer>& buffer) override;
     bool canSkipPostRenderCleanup() const override;
+    void drawLayersInternal(const std::shared_ptr<std::promise<RenderEngineResult>>&& resultPromise,
+                            const DisplaySettings& display,
+                            const std::vector<LayerSettings>& layers,
+                            const std::shared_ptr<ExternalTexture>& buffer,
+                            const bool useFramebufferCache, base::unique_fd&& bufferFence) override;
 
 private:
     void threadMain(CreateInstanceFactory factory);
diff --git a/libs/sensor/Sensor.cpp b/libs/sensor/Sensor.cpp
index 0a49008..ec0ced8 100644
--- a/libs/sensor/Sensor.cpp
+++ b/libs/sensor/Sensor.cpp
@@ -276,6 +276,30 @@
         mStringType = SENSOR_STRING_TYPE_HINGE_ANGLE;
         mFlags |= SENSOR_FLAG_ON_CHANGE_MODE;
         break;
+    case SENSOR_TYPE_HEAD_TRACKER:
+        mStringType = SENSOR_STRING_TYPE_HEAD_TRACKER;
+        mFlags |= SENSOR_FLAG_CONTINUOUS_MODE;
+        break;
+    case SENSOR_TYPE_ACCELEROMETER_LIMITED_AXES:
+        mStringType = SENSOR_STRING_TYPE_ACCELEROMETER_LIMITED_AXES;
+        mFlags |= SENSOR_FLAG_CONTINUOUS_MODE;
+        break;
+    case SENSOR_TYPE_GYROSCOPE_LIMITED_AXES:
+        mStringType = SENSOR_STRING_TYPE_GYROSCOPE_LIMITED_AXES;
+        mFlags |= SENSOR_FLAG_CONTINUOUS_MODE;
+        break;
+    case SENSOR_TYPE_ACCELEROMETER_LIMITED_AXES_UNCALIBRATED:
+        mStringType = SENSOR_STRING_TYPE_ACCELEROMETER_LIMITED_AXES_UNCALIBRATED;
+        mFlags |= SENSOR_FLAG_CONTINUOUS_MODE;
+        break;
+    case SENSOR_TYPE_GYROSCOPE_LIMITED_AXES_UNCALIBRATED:
+        mStringType = SENSOR_STRING_TYPE_GYROSCOPE_LIMITED_AXES_UNCALIBRATED;
+        mFlags |= SENSOR_FLAG_CONTINUOUS_MODE;
+        break;
+    case SENSOR_TYPE_HEADING:
+        mStringType = SENSOR_STRING_TYPE_HEADING;
+        mFlags |= SENSOR_FLAG_CONTINUOUS_MODE;
+        break;
     default:
         // Only pipe the stringType, requiredPermission and flags for custom sensors.
         if (halVersion > SENSORS_DEVICE_API_VERSION_1_0 && hwSensor.stringType) {
@@ -468,7 +492,15 @@
 }
 
 void Sensor::setId(int32_t id) {
-    mUuid.i64[0] = id;
+    mId = id;
+}
+
+int32_t Sensor::getId() const {
+    return mId;
+}
+
+void Sensor::anonymizeUuid() {
+    mUuid.i64[0] = mId;
     mUuid.i64[1] = 0;
 }
 
@@ -485,17 +517,14 @@
     }
 }
 
-int32_t Sensor::getId() const {
-    return int32_t(mUuid.i64[0]);
-}
-
 size_t Sensor::getFlattenedSize() const {
     size_t fixedSize =
             sizeof(mVersion) + sizeof(mHandle) + sizeof(mType) +
             sizeof(mMinValue) + sizeof(mMaxValue) + sizeof(mResolution) +
             sizeof(mPower) + sizeof(mMinDelay) + sizeof(mFifoMaxEventCount) +
             sizeof(mFifoMaxEventCount) + sizeof(mRequiredPermissionRuntime) +
-            sizeof(mRequiredAppOp) + sizeof(mMaxDelay) + sizeof(mFlags) + sizeof(mUuid);
+            sizeof(mRequiredAppOp) + sizeof(mMaxDelay) + sizeof(mFlags) +
+            sizeof(mUuid) + sizeof(mId);
 
     size_t variableSize =
             sizeof(uint32_t) + FlattenableUtils::align<4>(mName.length()) +
@@ -529,18 +558,8 @@
     FlattenableUtils::write(buffer, size, mRequiredAppOp);
     FlattenableUtils::write(buffer, size, mMaxDelay);
     FlattenableUtils::write(buffer, size, mFlags);
-    if (mUuid.i64[1] != 0) {
-        // We should never hit this case with our current API, but we
-        // could via a careless API change.  If that happens,
-        // this code will keep us from leaking our UUID (while probably
-        // breaking dynamic sensors).  See b/29547335.
-        ALOGW("Sensor with UUID being flattened; sending 0.  Expect "
-              "bad dynamic sensor behavior");
-        uuid_t tmpUuid;  // default constructor makes this 0.
-        FlattenableUtils::write(buffer, size, tmpUuid);
-    } else {
-        FlattenableUtils::write(buffer, size, mUuid);
-    }
+    FlattenableUtils::write(buffer, size, mUuid);
+    FlattenableUtils::write(buffer, size, mId);
     return NO_ERROR;
 }
 
@@ -580,7 +599,7 @@
 
     size_t fixedSize2 =
             sizeof(mRequiredPermissionRuntime) + sizeof(mRequiredAppOp) + sizeof(mMaxDelay) +
-            sizeof(mFlags) + sizeof(mUuid);
+            sizeof(mFlags) + sizeof(mUuid) + sizeof(mId);
     if (size < fixedSize2) {
         return NO_MEMORY;
     }
@@ -590,6 +609,7 @@
     FlattenableUtils::read(buffer, size, mMaxDelay);
     FlattenableUtils::read(buffer, size, mFlags);
     FlattenableUtils::read(buffer, size, mUuid);
+    FlattenableUtils::read(buffer, size, mId);
     return NO_ERROR;
 }
 
diff --git a/libs/sensor/SensorManager.cpp b/libs/sensor/SensorManager.cpp
index 62f4b4e..0ba9704 100644
--- a/libs/sensor/SensorManager.cpp
+++ b/libs/sensor/SensorManager.cpp
@@ -100,6 +100,7 @@
 
 SensorManager::~SensorManager() {
     free(mSensorList);
+    free(mDynamicSensorList);
 }
 
 status_t SensorManager::waitForSensorService(sp<ISensorServer> *server) {
@@ -130,6 +131,9 @@
     free(mSensorList);
     mSensorList = nullptr;
     mSensors.clear();
+    free(mDynamicSensorList);
+    mDynamicSensorList = nullptr;
+    mDynamicSensors.clear();
 }
 
 status_t SensorManager::assertStateLocked() {
@@ -197,6 +201,35 @@
     return static_cast<ssize_t>(count);
 }
 
+ssize_t SensorManager::getDynamicSensorList(Sensor const* const** list) {
+    Mutex::Autolock _l(mLock);
+    status_t err = assertStateLocked();
+    if (err < 0) {
+        return static_cast<ssize_t>(err);
+    }
+
+    free(mDynamicSensorList);
+    mDynamicSensorList = nullptr;
+    mDynamicSensors = mSensorServer->getDynamicSensorList(mOpPackageName);
+    size_t dynamicCount = mDynamicSensors.size();
+    if (dynamicCount > 0) {
+        mDynamicSensorList = static_cast<Sensor const**>(
+                malloc(dynamicCount * sizeof(Sensor*)));
+        if (mDynamicSensorList == nullptr) {
+          ALOGE("Failed to allocate dynamic sensor list for %zu sensors.",
+                dynamicCount);
+          return static_cast<ssize_t>(NO_MEMORY);
+        }
+
+        for (size_t i = 0; i < dynamicCount; i++) {
+            mDynamicSensorList[i] = mDynamicSensors.array() + i;
+        }
+    }
+
+    *list = mDynamicSensorList;
+    return static_cast<ssize_t>(mDynamicSensors.size());
+}
+
 Sensor const* SensorManager::getDefaultSensor(int type)
 {
     Mutex::Autolock _l(mLock);
diff --git a/libs/sensor/include/sensor/Sensor.h b/libs/sensor/include/sensor/Sensor.h
index 374b68f..bae8a13 100644
--- a/libs/sensor/include/sensor/Sensor.h
+++ b/libs/sensor/include/sensor/Sensor.h
@@ -96,11 +96,8 @@
     bool isDirectChannelTypeSupported(int32_t sharedMemType) const;
     int32_t getReportingMode() const;
 
-    // Note that after setId() has been called, getUuid() no longer
-    // returns the UUID.
-    // TODO(b/29547335): Remove getUuid(), add getUuidIndex(), and
-    //     make sure setId() doesn't change the UuidIndex.
     const uuid_t& getUuid() const;
+    void  anonymizeUuid();
     int32_t getId() const;
     void setId(int32_t id);
 
@@ -132,10 +129,8 @@
     int32_t mRequiredAppOp;
     int32_t mMaxDelay;
     uint32_t mFlags;
-    // TODO(b/29547335): Get rid of this field and replace with an index.
-    //     The index will be into a separate global vector of UUIDs.
-    //     Also add an mId field (and change flatten/unflatten appropriately).
     uuid_t  mUuid;
+    int32_t mId;
     static void flattenString8(void*& buffer, size_t& size, const String8& string8);
     static bool unflattenString8(void const*& buffer, size_t& size, String8& outputString8);
 };
diff --git a/libs/sensor/include/sensor/SensorManager.h b/libs/sensor/include/sensor/SensorManager.h
index 09ac7ed..8d0a8a4 100644
--- a/libs/sensor/include/sensor/SensorManager.h
+++ b/libs/sensor/include/sensor/SensorManager.h
@@ -58,6 +58,7 @@
 
     ssize_t getSensorList(Sensor const* const** list);
     ssize_t getDynamicSensorList(Vector<Sensor>& list);
+    ssize_t getDynamicSensorList(Sensor const* const** list);
     Sensor const* getDefaultSensor(int type);
     sp<SensorEventQueue> createEventQueue(
         String8 packageName = String8(""), int mode = 0, String16 attributionTag = String16(""));
@@ -83,6 +84,8 @@
     sp<ISensorServer> mSensorServer;
     Sensor const** mSensorList;
     Vector<Sensor> mSensors;
+    Sensor const** mDynamicSensorList = nullptr;
+    Vector<Sensor> mDynamicSensors;
     sp<IBinder::DeathRecipient> mDeathObserver;
     const String16 mOpPackageName;
     std::unordered_map<int, sp<ISensorEventConnection>> mDirectConnection;
diff --git a/libs/sensorprivacy/SensorPrivacyManager.cpp b/libs/sensorprivacy/SensorPrivacyManager.cpp
index ef3ceda..2be98e7 100644
--- a/libs/sensorprivacy/SensorPrivacyManager.cpp
+++ b/libs/sensorprivacy/SensorPrivacyManager.cpp
@@ -55,12 +55,12 @@
     return service;
 }
 
-bool SensorPrivacyManager::supportsSensorToggle(int sensor) {
+bool SensorPrivacyManager::supportsSensorToggle(int toggleType, int sensor) {
     if (mSupportedCache.find(sensor) == mSupportedCache.end()) {
         sp<hardware::ISensorPrivacyManager> service = getService();
         if (service != nullptr) {
             bool result;
-            service->supportsSensorToggle(sensor, &result);
+            service->supportsSensorToggle(toggleType, sensor, &result);
             mSupportedCache[sensor] = result;
             return result;
         }
@@ -80,12 +80,12 @@
     }
 }
 
-status_t SensorPrivacyManager::addIndividualSensorPrivacyListener(int userId, int sensor,
+status_t SensorPrivacyManager::addToggleSensorPrivacyListener(
         const sp<hardware::ISensorPrivacyListener>& listener)
 {
     sp<hardware::ISensorPrivacyManager> service = getService();
     if (service != nullptr) {
-        return service->addIndividualSensorPrivacyListener(userId, sensor, listener)
+        return service->addToggleSensorPrivacyListener(listener)
                 .transactionError();
     }
     return UNEXPECTED_NULL;
@@ -100,12 +100,12 @@
     }
 }
 
-void SensorPrivacyManager::removeIndividualSensorPrivacyListener(int sensor,
+void SensorPrivacyManager::removeToggleSensorPrivacyListener(
         const sp<hardware::ISensorPrivacyListener>& listener)
 {
     sp<hardware::ISensorPrivacyManager> service = getService();
     if (service != nullptr) {
-        service->removeIndividualSensorPrivacyListener(sensor, listener);
+        service->removeToggleSensorPrivacyListener(listener);
     }
 }
 
@@ -121,24 +121,36 @@
     return false;
 }
 
-bool SensorPrivacyManager::isIndividualSensorPrivacyEnabled(int userId, int sensor)
+bool SensorPrivacyManager::isToggleSensorPrivacyEnabled(int sensor)
 {
-    sp<hardware::ISensorPrivacyManager> service = getService();
+	sp<hardware::ISensorPrivacyManager> service = getService();
     if (service != nullptr) {
         bool result;
-        service->isIndividualSensorPrivacyEnabled(userId, sensor, &result);
+        service->isCombinedToggleSensorPrivacyEnabled(sensor, &result);
         return result;
     }
     // if the SensorPrivacyManager is not available then assume sensor privacy is disabled
     return false;
 }
 
-status_t SensorPrivacyManager::isIndividualSensorPrivacyEnabled(int userId, int sensor,
+bool SensorPrivacyManager::isToggleSensorPrivacyEnabled(int toggleType, int sensor)
+{
+    sp<hardware::ISensorPrivacyManager> service = getService();
+    if (service != nullptr) {
+        bool result;
+        service->isToggleSensorPrivacyEnabled(toggleType, sensor, &result);
+        return result;
+    }
+    // if the SensorPrivacyManager is not available then assume sensor privacy is disabled
+    return false;
+}
+
+status_t SensorPrivacyManager::isToggleSensorPrivacyEnabled(int toggleType, int sensor,
         bool &returnVal)
 {
     sp<hardware::ISensorPrivacyManager> service = getService();
     if (service != nullptr) {
-        binder::Status res = service->isIndividualSensorPrivacyEnabled(userId, sensor, &returnVal);
+        binder::Status res = service->isToggleSensorPrivacyEnabled(toggleType, sensor, &returnVal);
         return res.transactionError();
     }
     // if the SensorPrivacyManager is not available then assume sensor privacy is disabled
diff --git a/libs/sensorprivacy/aidl/android/hardware/ISensorPrivacyListener.aidl b/libs/sensorprivacy/aidl/android/hardware/ISensorPrivacyListener.aidl
index 58177d8..eccd54c 100644
--- a/libs/sensorprivacy/aidl/android/hardware/ISensorPrivacyListener.aidl
+++ b/libs/sensorprivacy/aidl/android/hardware/ISensorPrivacyListener.aidl
@@ -20,5 +20,5 @@
  * @hide
  */
 oneway interface ISensorPrivacyListener {
-    void onSensorPrivacyChanged(boolean enabled);
+    void onSensorPrivacyChanged(int toggleType, int sensor, boolean enabled);
 }
diff --git a/libs/sensorprivacy/aidl/android/hardware/ISensorPrivacyManager.aidl b/libs/sensorprivacy/aidl/android/hardware/ISensorPrivacyManager.aidl
index 9564cba..49a1e1e 100644
--- a/libs/sensorprivacy/aidl/android/hardware/ISensorPrivacyManager.aidl
+++ b/libs/sensorprivacy/aidl/android/hardware/ISensorPrivacyManager.aidl
@@ -20,23 +20,25 @@
 
 /** @hide */
 interface ISensorPrivacyManager {
-    boolean supportsSensorToggle(int sensor);
+    boolean supportsSensorToggle(int toggleType, int sensor);
 
     void addSensorPrivacyListener(in ISensorPrivacyListener listener);
 
-    void addIndividualSensorPrivacyListener(int userId, int sensor, in ISensorPrivacyListener listener);
+    void addToggleSensorPrivacyListener(in ISensorPrivacyListener listener);
 
     void removeSensorPrivacyListener(in ISensorPrivacyListener listener);
 
-    void removeIndividualSensorPrivacyListener(int sensor, in ISensorPrivacyListener listener);
+    void removeToggleSensorPrivacyListener(in ISensorPrivacyListener listener);
 
     boolean isSensorPrivacyEnabled();
 
-    boolean isIndividualSensorPrivacyEnabled(int userId, int sensor);
+    boolean isCombinedToggleSensorPrivacyEnabled(int sensor);
+
+    boolean isToggleSensorPrivacyEnabled(int toggleType, int sensor);
 
     void setSensorPrivacy(boolean enable);
 
-    void setIndividualSensorPrivacy(int userId, int source, int sensor, boolean enable);
+    void setToggleSensorPrivacy(int userId, int source, int sensor, boolean enable);
 
-    void setIndividualSensorPrivacyForProfileGroup(int userId, int source, int sensor, boolean enable);
+    void setToggleSensorPrivacyForProfileGroup(int userId, int source, int sensor, boolean enable);
 }
diff --git a/libs/sensorprivacy/include/sensorprivacy/SensorPrivacyManager.h b/libs/sensorprivacy/include/sensorprivacy/SensorPrivacyManager.h
index af699d0..fc5fdf7 100644
--- a/libs/sensorprivacy/include/sensorprivacy/SensorPrivacyManager.h
+++ b/libs/sensorprivacy/include/sensorprivacy/SensorPrivacyManager.h
@@ -31,22 +31,26 @@
 {
 public:
     enum {
-        INDIVIDUAL_SENSOR_MICROPHONE = 1,
-        INDIVIDUAL_SENSOR_CAMERA = 2
+        TOGGLE_SENSOR_MICROPHONE = 1,
+        TOGGLE_SENSOR_CAMERA = 2
+    };
+
+    enum {
+        TOGGLE_TYPE_SOFTWARE = 1,
+        TOGGLE_TYPE_HARDWARE = 2
     };
 
     SensorPrivacyManager();
 
-    bool supportsSensorToggle(int sensor);
+    bool supportsSensorToggle(int toggleType, int sensor);
     void addSensorPrivacyListener(const sp<hardware::ISensorPrivacyListener>& listener);
-    status_t addIndividualSensorPrivacyListener(int userId, int sensor,
-            const sp<hardware::ISensorPrivacyListener>& listener);
+    status_t addToggleSensorPrivacyListener(const sp<hardware::ISensorPrivacyListener>& listener);
     void removeSensorPrivacyListener(const sp<hardware::ISensorPrivacyListener>& listener);
-    void removeIndividualSensorPrivacyListener(int sensor,
-            const sp<hardware::ISensorPrivacyListener>& listener);
+    void removeToggleSensorPrivacyListener(const sp<hardware::ISensorPrivacyListener>& listener);
     bool isSensorPrivacyEnabled();
-    bool isIndividualSensorPrivacyEnabled(int userId, int sensor);
-    status_t isIndividualSensorPrivacyEnabled(int userId, int sensor, bool &result);
+    bool isToggleSensorPrivacyEnabled(int sensor);
+    bool isToggleSensorPrivacyEnabled(int toggleType, int sensor);
+    status_t isToggleSensorPrivacyEnabled(int toggleType, int sensor, bool &result);
 
     status_t linkToDeath(const sp<IBinder::DeathRecipient>& recipient);
     status_t unlinkToDeath(const sp<IBinder::DeathRecipient>& recipient);
diff --git a/libs/shaders/Android.bp b/libs/shaders/Android.bp
new file mode 100644
index 0000000..2f8bf49
--- /dev/null
+++ b/libs/shaders/Android.bp
@@ -0,0 +1,50 @@
+// 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.
+
+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: "libshaders",
+
+    export_include_dirs: ["include"],
+    local_include_dirs: ["include"],
+
+    shared_libs: [
+        "android.hardware.graphics.common-V3-ndk",
+        "android.hardware.graphics.common@1.2",
+        "libnativewindow",
+    ],
+
+    static_libs: [
+        "libarect",
+        "libmath",
+        "libtonemap",
+        "libui-types",
+    ],
+
+    header_libs: [
+        "libtonemap_headers",
+    ],
+
+    srcs: [
+        "shaders.cpp",
+    ],
+}
diff --git a/libs/shaders/OWNERS b/libs/shaders/OWNERS
new file mode 100644
index 0000000..6d91da3
--- /dev/null
+++ b/libs/shaders/OWNERS
@@ -0,0 +1,4 @@
+alecmouri@google.com
+jreck@google.com
+sallyqi@google.com
+scroggo@google.com
\ No newline at end of file
diff --git a/libs/shaders/include/shaders/shaders.h b/libs/shaders/include/shaders/shaders.h
new file mode 100644
index 0000000..4ec7594
--- /dev/null
+++ b/libs/shaders/include/shaders/shaders.h
@@ -0,0 +1,107 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <math/mat4.h>
+#include <tonemap/tonemap.h>
+#include <ui/GraphicTypes.h>
+#include <cstddef>
+
+namespace android::shaders {
+
+/**
+ * Arguments for creating an effect that applies color transformations in linear XYZ space.
+ * A linear effect is decomposed into the following steps when operating on an image:
+ * 1. Electrical-Optical Transfer Function (EOTF) maps the input RGB signal into the intended
+ * relative display brightness of the scene in nits for each RGB channel
+ * 2. Transformation matrix from linear RGB brightness to linear XYZ, to operate on display
+ * luminance.
+ * 3. Opto-Optical Transfer Function (OOTF) applies a "rendering intent". This can include tone
+ * mapping to display SDR content alongside HDR content, or any number of subjective transformations
+ * 4. Transformation matrix from linear XYZ back to linear RGB brightness.
+ * 5. Opto-Electronic Transfer Function (OETF) maps the display brightness of the scene back to
+ * output RGB colors.
+ *
+ * For further reading, consult the recommendation in ITU-R BT.2390-4:
+ * https://www.itu.int/dms_pub/itu-r/opb/rep/R-REP-BT.2390-4-2018-PDF-E.pdf
+ *
+ * Skia normally attempts to do its own simple tone mapping, i.e., the working color space is
+ * intended to be the output surface. However, Skia does not support complex tone mapping such as
+ * polynomial interpolation. As such, this filter assumes that tone mapping has not yet been applied
+ * to the source colors. so that the tone mapping process is only applied once by this effect. Tone
+ * mapping is applied when presenting HDR content (content with HLG or PQ transfer functions)
+ * alongside other content, whereby maximum input luminance is mapped to maximum output luminance
+ * and intermediate values are interpolated.
+ */
+struct LinearEffect {
+    // Input dataspace of the source colors.
+    const ui::Dataspace inputDataspace = ui::Dataspace::SRGB;
+
+    // Working dataspace for the output surface, for conversion from linear space.
+    const ui::Dataspace outputDataspace = ui::Dataspace::SRGB;
+
+    // Sets whether alpha premultiplication must be undone.
+    // This is required if the source colors use premultiplied alpha and is not opaque.
+    const bool undoPremultipliedAlpha = false;
+
+    // "Fake" dataspace of the source colors. This is used for applying an EOTF to compute linear
+    // RGB. This is used when Skia is expected to color manage the input image based on the
+    // dataspace of the provided source image and destination surface. SkRuntimeEffects use the
+    // destination color space as the working color space. RenderEngine deliberately sets the color
+    // space for input images and destination surfaces to be the same whenever LinearEffects are
+    // expected to be used so that color-management is controlled by RenderEngine, but other users
+    // of a LinearEffect may not be able to control the color space of the images and surfaces. So
+    // 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;
+};
+
+static inline bool operator==(const LinearEffect& lhs, const LinearEffect& rhs) {
+    return lhs.inputDataspace == rhs.inputDataspace && lhs.outputDataspace == rhs.outputDataspace &&
+            lhs.undoPremultipliedAlpha == rhs.undoPremultipliedAlpha &&
+            lhs.fakeInputDataspace == rhs.fakeInputDataspace;
+}
+
+struct LinearEffectHasher {
+    // Inspired by art/runtime/class_linker.cc
+    // Also this is what boost:hash_combine does
+    static size_t HashCombine(size_t seed, size_t val) {
+        return seed ^ (val + 0x9e3779b9 + (seed << 6) + (seed >> 2));
+    }
+    size_t operator()(const LinearEffect& le) const {
+        size_t result = std::hash<ui::Dataspace>{}(le.inputDataspace);
+        result = HashCombine(result, std::hash<ui::Dataspace>{}(le.outputDataspace));
+        result = HashCombine(result, std::hash<bool>{}(le.undoPremultipliedAlpha));
+        return HashCombine(result, std::hash<ui::Dataspace>{}(le.fakeInputDataspace));
+    }
+};
+
+// Generates a shader string that applies color transforms in linear space.
+// Typical use-cases supported:
+// 1. Apply tone-mapping
+// 2. Apply color transform matrices in linear space
+std::string buildLinearEffectSkSL(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,
+                                                              float currentDisplayLuminanceNits,
+                                                              float maxLuminance,
+                                                              AHardwareBuffer* buffer = nullptr);
+
+} // namespace android::shaders
diff --git a/libs/shaders/shaders.cpp b/libs/shaders/shaders.cpp
new file mode 100644
index 0000000..5935589
--- /dev/null
+++ b/libs/shaders/shaders.cpp
@@ -0,0 +1,507 @@
+/*
+ * 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.
+ */
+
+#include <shaders/shaders.h>
+
+#include <tonemap/tonemap.h>
+
+#include <cmath>
+#include <optional>
+
+#include <math/mat4.h>
+#include <system/graphics-base-v1.0.h>
+#include <ui/ColorSpace.h>
+
+namespace android::shaders {
+
+namespace {
+
+aidl::android::hardware::graphics::common::Dataspace toAidlDataspace(ui::Dataspace dataspace) {
+    return static_cast<aidl::android::hardware::graphics::common::Dataspace>(dataspace);
+}
+
+void generateEOTF(ui::Dataspace dataspace, std::string& shader) {
+    switch (dataspace & HAL_DATASPACE_TRANSFER_MASK) {
+        case HAL_DATASPACE_TRANSFER_ST2084:
+            shader.append(R"(
+
+                float3 EOTF(float3 color) {
+                    float m1 = (2610.0 / 4096.0) / 4.0;
+                    float m2 = (2523.0 / 4096.0) * 128.0;
+                    float c1 = (3424.0 / 4096.0);
+                    float c2 = (2413.0 / 4096.0) * 32.0;
+                    float c3 = (2392.0 / 4096.0) * 32.0;
+
+                    float3 tmp = pow(clamp(color, 0.0, 1.0), 1.0 / float3(m2));
+                    tmp = max(tmp - c1, 0.0) / (c2 - c3 * tmp);
+                    return pow(tmp, 1.0 / float3(m1));
+                }
+            )");
+            break;
+        case HAL_DATASPACE_TRANSFER_HLG:
+            shader.append(R"(
+                float EOTF_channel(float channel) {
+                    const float a = 0.17883277;
+                    const float b = 0.28466892;
+                    const float c = 0.55991073;
+                    return channel <= 0.5 ? channel * channel / 3.0 :
+                            (exp((channel - c) / a) + b) / 12.0;
+                }
+
+                float3 EOTF(float3 color) {
+                    return float3(EOTF_channel(color.r), EOTF_channel(color.g),
+                            EOTF_channel(color.b));
+                }
+            )");
+            break;
+        case HAL_DATASPACE_TRANSFER_LINEAR:
+            shader.append(R"(
+                float3 EOTF(float3 color) {
+                    return color;
+                }
+            )");
+            break;
+        case HAL_DATASPACE_TRANSFER_SMPTE_170M:
+            shader.append(R"(
+
+                float EOTF_sRGB(float srgb) {
+                    return srgb <= 0.08125 ? srgb / 4.50 : pow((srgb + 0.099) / 1.099, 0.45);
+                }
+
+                float3 EOTF_sRGB(float3 srgb) {
+                    return float3(EOTF_sRGB(srgb.r), EOTF_sRGB(srgb.g), EOTF_sRGB(srgb.b));
+                }
+
+                float3 EOTF(float3 srgb) {
+                    return sign(srgb.rgb) * EOTF_sRGB(abs(srgb.rgb));
+                }
+            )");
+            break;
+        case HAL_DATASPACE_TRANSFER_GAMMA2_2:
+            shader.append(R"(
+
+                float EOTF_sRGB(float srgb) {
+                    return pow(srgb, 2.2);
+                }
+
+                float3 EOTF_sRGB(float3 srgb) {
+                    return float3(EOTF_sRGB(srgb.r), EOTF_sRGB(srgb.g), EOTF_sRGB(srgb.b));
+                }
+
+                float3 EOTF(float3 srgb) {
+                    return sign(srgb.rgb) * EOTF_sRGB(abs(srgb.rgb));
+                }
+            )");
+            break;
+        case HAL_DATASPACE_TRANSFER_GAMMA2_6:
+            shader.append(R"(
+
+                float EOTF_sRGB(float srgb) {
+                    return pow(srgb, 2.6);
+                }
+
+                float3 EOTF_sRGB(float3 srgb) {
+                    return float3(EOTF_sRGB(srgb.r), EOTF_sRGB(srgb.g), EOTF_sRGB(srgb.b));
+                }
+
+                float3 EOTF(float3 srgb) {
+                    return sign(srgb.rgb) * EOTF_sRGB(abs(srgb.rgb));
+                }
+            )");
+            break;
+        case HAL_DATASPACE_TRANSFER_GAMMA2_8:
+            shader.append(R"(
+
+                float EOTF_sRGB(float srgb) {
+                    return pow(srgb, 2.8);
+                }
+
+                float3 EOTF_sRGB(float3 srgb) {
+                    return float3(EOTF_sRGB(srgb.r), EOTF_sRGB(srgb.g), EOTF_sRGB(srgb.b));
+                }
+
+                float3 EOTF(float3 srgb) {
+                    return sign(srgb.rgb) * EOTF_sRGB(abs(srgb.rgb));
+                }
+            )");
+            break;
+        case HAL_DATASPACE_TRANSFER_SRGB:
+        default:
+            shader.append(R"(
+
+                float EOTF_sRGB(float srgb) {
+                    return srgb <= 0.04045 ? srgb / 12.92 : pow((srgb + 0.055) / 1.055, 2.4);
+                }
+
+                float3 EOTF_sRGB(float3 srgb) {
+                    return float3(EOTF_sRGB(srgb.r), EOTF_sRGB(srgb.g), EOTF_sRGB(srgb.b));
+                }
+
+                float3 EOTF(float3 srgb) {
+                    return sign(srgb.rgb) * EOTF_sRGB(abs(srgb.rgb));
+                }
+            )");
+            break;
+    }
+}
+
+void generateXYZTransforms(std::string& shader) {
+    shader.append(R"(
+        uniform float4x4 in_rgbToXyz;
+        uniform float4x4 in_xyzToRgb;
+        float3 ToXYZ(float3 rgb) {
+            return (in_rgbToXyz * float4(rgb, 1.0)).rgb;
+        }
+
+        float3 ToRGB(float3 xyz) {
+            return clamp((in_xyzToRgb * float4(xyz, 1.0)).rgb, 0.0, 1.0);
+        }
+    )");
+}
+
+// Conversion from relative light to absolute light (maps from [0, 1] to [0, maxNits])
+void generateLuminanceScalesForOOTF(ui::Dataspace inputDataspace, ui::Dataspace outputDataspace,
+                                    std::string& shader) {
+    switch (inputDataspace & HAL_DATASPACE_TRANSFER_MASK) {
+        case HAL_DATASPACE_TRANSFER_ST2084:
+            shader.append(R"(
+                    float3 ScaleLuminance(float3 xyz) {
+                        return xyz * 10000.0;
+                    }
+                )");
+            break;
+        case HAL_DATASPACE_TRANSFER_HLG:
+            shader.append(R"(
+                    float3 ScaleLuminance(float3 xyz) {
+                        return xyz * 1000.0;
+                    }
+                )");
+            break;
+        default:
+            switch (outputDataspace & HAL_DATASPACE_TRANSFER_MASK) {
+                case HAL_DATASPACE_TRANSFER_ST2084:
+                case HAL_DATASPACE_TRANSFER_HLG:
+                    // SDR -> HDR tonemap
+                    shader.append(R"(
+                            float3 ScaleLuminance(float3 xyz) {
+                                return xyz * in_libtonemap_inputMaxLuminance;
+                            }
+                        )");
+                    break;
+                default:
+                    // Input and output are both SDR, so no tone-mapping is expected so
+                    // no-op the luminance normalization.
+                    shader.append(R"(
+                                float3 ScaleLuminance(float3 xyz) {
+                                    return xyz * in_libtonemap_displayMaxLuminance;
+                                }
+                            )");
+                    break;
+            }
+    }
+}
+
+// Normalizes from absolute light back to relative light (maps from [0, maxNits] back to [0, 1])
+static void generateLuminanceNormalizationForOOTF(ui::Dataspace outputDataspace,
+                                                  std::string& shader) {
+    switch (outputDataspace & HAL_DATASPACE_TRANSFER_MASK) {
+        case HAL_DATASPACE_TRANSFER_ST2084:
+            shader.append(R"(
+                    float3 NormalizeLuminance(float3 xyz) {
+                        return xyz / 10000.0;
+                    }
+                )");
+            break;
+        case HAL_DATASPACE_TRANSFER_HLG:
+            shader.append(R"(
+                    float3 NormalizeLuminance(float3 xyz) {
+                        return xyz / 1000.0;
+                    }
+                )");
+            break;
+        default:
+            shader.append(R"(
+                    float3 NormalizeLuminance(float3 xyz) {
+                        return xyz / in_libtonemap_displayMaxLuminance;
+                    }
+                )");
+            break;
+    }
+}
+
+void generateOOTF(ui::Dataspace inputDataspace, ui::Dataspace outputDataspace,
+                  std::string& shader) {
+    shader.append(tonemap::getToneMapper()
+                          ->generateTonemapGainShaderSkSL(toAidlDataspace(inputDataspace),
+                                                          toAidlDataspace(outputDataspace))
+                          .c_str());
+
+    generateLuminanceScalesForOOTF(inputDataspace, outputDataspace, shader);
+    generateLuminanceNormalizationForOOTF(outputDataspace, shader);
+
+    shader.append(R"(
+            float3 OOTF(float3 linearRGB, float3 xyz) {
+                float3 scaledLinearRGB = ScaleLuminance(linearRGB);
+                float3 scaledXYZ = ScaleLuminance(xyz);
+
+                float gain = libtonemap_LookupTonemapGain(scaledLinearRGB, scaledXYZ);
+
+                return NormalizeLuminance(scaledXYZ * gain);
+            }
+        )");
+}
+
+void generateOETF(ui::Dataspace dataspace, std::string& shader) {
+    switch (dataspace & HAL_DATASPACE_TRANSFER_MASK) {
+        case HAL_DATASPACE_TRANSFER_ST2084:
+            shader.append(R"(
+
+                float3 OETF(float3 xyz) {
+                    float m1 = (2610.0 / 4096.0) / 4.0;
+                    float m2 = (2523.0 / 4096.0) * 128.0;
+                    float c1 = (3424.0 / 4096.0);
+                    float c2 = (2413.0 / 4096.0) * 32.0;
+                    float c3 = (2392.0 / 4096.0) * 32.0;
+
+                    float3 tmp = pow(xyz, float3(m1));
+                    tmp = (c1 + c2 * tmp) / (1.0 + c3 * tmp);
+                    return pow(tmp, float3(m2));
+                }
+            )");
+            break;
+        case HAL_DATASPACE_TRANSFER_HLG:
+            shader.append(R"(
+                float OETF_channel(float channel) {
+                    const float a = 0.17883277;
+                    const float b = 0.28466892;
+                    const float c = 0.55991073;
+                    return channel <= 1.0 / 12.0 ? sqrt(3.0 * channel) :
+                            a * log(12.0 * channel - b) + c;
+                }
+
+                float3 OETF(float3 linear) {
+                    return float3(OETF_channel(linear.r), OETF_channel(linear.g),
+                            OETF_channel(linear.b));
+                }
+            )");
+            break;
+        case HAL_DATASPACE_TRANSFER_LINEAR:
+            shader.append(R"(
+                float3 OETF(float3 linear) {
+                    return linear;
+                }
+            )");
+            break;
+        case HAL_DATASPACE_TRANSFER_SMPTE_170M:
+            shader.append(R"(
+                float OETF_sRGB(float linear) {
+                    return linear <= 0.018 ?
+                            linear * 4.50 : (pow(linear, 0.45) * 1.099) - 0.099;
+                }
+
+                float3 OETF_sRGB(float3 linear) {
+                    return float3(OETF_sRGB(linear.r), OETF_sRGB(linear.g), OETF_sRGB(linear.b));
+                }
+
+                float3 OETF(float3 linear) {
+                    return sign(linear.rgb) * OETF_sRGB(abs(linear.rgb));
+                }
+            )");
+            break;
+        case HAL_DATASPACE_TRANSFER_GAMMA2_2:
+            shader.append(R"(
+                float OETF_sRGB(float linear) {
+                    return pow(linear, (1.0 / 2.2));
+                }
+
+                float3 OETF_sRGB(float3 linear) {
+                    return float3(OETF_sRGB(linear.r), OETF_sRGB(linear.g), OETF_sRGB(linear.b));
+                }
+
+                float3 OETF(float3 linear) {
+                    return sign(linear.rgb) * OETF_sRGB(abs(linear.rgb));
+                }
+            )");
+            break;
+        case HAL_DATASPACE_TRANSFER_GAMMA2_6:
+            shader.append(R"(
+                float OETF_sRGB(float linear) {
+                    return pow(linear, (1.0 / 2.6));
+                }
+
+                float3 OETF_sRGB(float3 linear) {
+                    return float3(OETF_sRGB(linear.r), OETF_sRGB(linear.g), OETF_sRGB(linear.b));
+                }
+
+                float3 OETF(float3 linear) {
+                    return sign(linear.rgb) * OETF_sRGB(abs(linear.rgb));
+                }
+            )");
+            break;
+        case HAL_DATASPACE_TRANSFER_GAMMA2_8:
+            shader.append(R"(
+                float OETF_sRGB(float linear) {
+                    return pow(linear, (1.0 / 2.8));
+                }
+
+                float3 OETF_sRGB(float3 linear) {
+                    return float3(OETF_sRGB(linear.r), OETF_sRGB(linear.g), OETF_sRGB(linear.b));
+                }
+
+                float3 OETF(float3 linear) {
+                    return sign(linear.rgb) * OETF_sRGB(abs(linear.rgb));
+                }
+            )");
+            break;
+        case HAL_DATASPACE_TRANSFER_SRGB:
+        default:
+            shader.append(R"(
+                float OETF_sRGB(float linear) {
+                    return linear <= 0.0031308 ?
+                            linear * 12.92 : (pow(linear, 1.0 / 2.4) * 1.055) - 0.055;
+                }
+
+                float3 OETF_sRGB(float3 linear) {
+                    return float3(OETF_sRGB(linear.r), OETF_sRGB(linear.g), OETF_sRGB(linear.b));
+                }
+
+                float3 OETF(float3 linear) {
+                    return sign(linear.rgb) * OETF_sRGB(abs(linear.rgb));
+                }
+            )");
+            break;
+    }
+}
+
+void generateEffectiveOOTF(bool undoPremultipliedAlpha, std::string& shader) {
+    shader.append(R"(
+        uniform shader child;
+        half4 main(float2 xy) {
+            float4 c = float4(child.eval(xy));
+    )");
+    if (undoPremultipliedAlpha) {
+        shader.append(R"(
+            c.rgb = c.rgb / (c.a + 0.0019);
+        )");
+    }
+    shader.append(R"(
+        float3 linearRGB = EOTF(c.rgb);
+        float3 xyz = ToXYZ(linearRGB);
+        c.rgb = OETF(ToRGB(OOTF(linearRGB, xyz)));
+    )");
+    if (undoPremultipliedAlpha) {
+        shader.append(R"(
+            c.rgb = c.rgb * (c.a + 0.0019);
+        )");
+    }
+    shader.append(R"(
+            return c;
+        }
+    )");
+}
+
+// please keep in sync with toSkColorSpace function in renderengine/skia/ColorSpaces.cpp
+ColorSpace toColorSpace(ui::Dataspace dataspace) {
+    switch (dataspace & HAL_DATASPACE_STANDARD_MASK) {
+        case HAL_DATASPACE_STANDARD_BT709:
+            return ColorSpace::sRGB();
+        case HAL_DATASPACE_STANDARD_DCI_P3:
+            return ColorSpace::DisplayP3();
+        case HAL_DATASPACE_STANDARD_BT2020:
+        case HAL_DATASPACE_STANDARD_BT2020_CONSTANT_LUMINANCE:
+            return ColorSpace::BT2020();
+        case HAL_DATASPACE_STANDARD_ADOBE_RGB:
+            return ColorSpace::AdobeRGB();
+        // TODO(b/208290320): BT601 format and variants return different primaries
+        case HAL_DATASPACE_STANDARD_BT601_625:
+        case HAL_DATASPACE_STANDARD_BT601_625_UNADJUSTED:
+        case HAL_DATASPACE_STANDARD_BT601_525:
+        case HAL_DATASPACE_STANDARD_BT601_525_UNADJUSTED:
+        // TODO(b/208290329): BT407M format returns different primaries
+        case HAL_DATASPACE_STANDARD_BT470M:
+        // TODO(b/208290904): FILM format returns different primaries
+        case HAL_DATASPACE_STANDARD_FILM:
+        case HAL_DATASPACE_STANDARD_UNSPECIFIED:
+        default:
+            return ColorSpace::sRGB();
+    }
+}
+
+template <typename T, std::enable_if_t<std::is_trivially_copyable<T>::value, bool> = true>
+std::vector<uint8_t> buildUniformValue(T value) {
+    std::vector<uint8_t> result;
+    result.resize(sizeof(value));
+    std::memcpy(result.data(), &value, sizeof(value));
+    return result;
+}
+
+} // namespace
+
+std::string buildLinearEffectSkSL(const LinearEffect& linearEffect) {
+    std::string shaderString;
+    generateEOTF(linearEffect.fakeInputDataspace == ui::Dataspace::UNKNOWN
+                         ? linearEffect.inputDataspace
+                         : linearEffect.fakeInputDataspace,
+                 shaderString);
+    generateXYZTransforms(shaderString);
+    generateOOTF(linearEffect.inputDataspace, linearEffect.outputDataspace, shaderString);
+    generateOETF(linearEffect.outputDataspace, shaderString);
+    generateEffectiveOOTF(linearEffect.undoPremultipliedAlpha, shaderString);
+    return shaderString;
+}
+
+// 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,
+                                                              float currentDisplayLuminanceNits,
+                                                              float maxLuminance,
+                                                              AHardwareBuffer* buffer) {
+    std::vector<tonemap::ShaderUniform> uniforms;
+    if (linearEffect.inputDataspace == linearEffect.outputDataspace) {
+        uniforms.push_back({.name = "in_rgbToXyz", .value = buildUniformValue<mat4>(mat4())});
+        uniforms.push_back(
+                {.name = "in_xyzToRgb", .value = buildUniformValue<mat4>(colorTransform)});
+    } else {
+        ColorSpace inputColorSpace = toColorSpace(linearEffect.inputDataspace);
+        ColorSpace outputColorSpace = toColorSpace(linearEffect.outputDataspace);
+        uniforms.push_back({.name = "in_rgbToXyz",
+                            .value = buildUniformValue<mat4>(mat4(inputColorSpace.getRGBtoXYZ()))});
+        uniforms.push_back({.name = "in_xyzToRgb",
+                            .value = buildUniformValue<mat4>(
+                                    colorTransform * mat4(outputColorSpace.getXYZtoRGB()))});
+    }
+
+    tonemap::Metadata metadata{.displayMaxLuminance = maxDisplayLuminance,
+                               // If the input luminance is unknown, use display luminance (aka,
+                               // no-op any luminance changes)
+                               // This will be the case for eg screenshots in addition to
+                               // uncalibrated displays
+                               .contentMaxLuminance =
+                                       maxLuminance > 0 ? maxLuminance : maxDisplayLuminance,
+                               .currentDisplayLuminance = currentDisplayLuminanceNits > 0
+                                       ? currentDisplayLuminanceNits
+                                       : maxDisplayLuminance,
+                               .buffer = buffer};
+
+    for (const auto uniform : tonemap::getToneMapper()->generateShaderSkSLUniforms(metadata)) {
+        uniforms.push_back(uniform);
+    }
+
+    return uniforms;
+}
+
+} // namespace android::shaders
diff --git a/libs/tonemap/Android.bp b/libs/tonemap/Android.bp
new file mode 100644
index 0000000..dc55586
--- /dev/null
+++ b/libs/tonemap/Android.bp
@@ -0,0 +1,50 @@
+// 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.
+
+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: "libtonemap",
+    vendor_available: true,
+
+    local_include_dirs: ["include"],
+
+    shared_libs: [
+        "android.hardware.graphics.common-V3-ndk",
+        "liblog",
+        "libnativewindow",
+    ],
+
+    static_libs: [
+        "libarect",
+        "libmath",
+    ],
+
+    srcs: [
+        "tonemap.cpp",
+    ],
+}
+
+cc_library_headers {
+    name: "libtonemap_headers",
+    vendor_available: true,
+    export_include_dirs: ["include"],
+}
diff --git a/libs/tonemap/OWNERS b/libs/tonemap/OWNERS
new file mode 100644
index 0000000..6d91da3
--- /dev/null
+++ b/libs/tonemap/OWNERS
@@ -0,0 +1,4 @@
+alecmouri@google.com
+jreck@google.com
+sallyqi@google.com
+scroggo@google.com
\ No newline at end of file
diff --git a/libs/tonemap/TEST_MAPPING b/libs/tonemap/TEST_MAPPING
new file mode 100644
index 0000000..00f83ba
--- /dev/null
+++ b/libs/tonemap/TEST_MAPPING
@@ -0,0 +1,10 @@
+{
+  "presubmit": [
+    {
+      "name": "librenderengine_test"
+    },
+    {
+      "name": "libtonemap_test"
+    }
+  ]
+}
diff --git a/libs/tonemap/include/tonemap/tonemap.h b/libs/tonemap/include/tonemap/tonemap.h
new file mode 100644
index 0000000..c51016d
--- /dev/null
+++ b/libs/tonemap/include/tonemap/tonemap.h
@@ -0,0 +1,145 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <aidl/android/hardware/graphics/common/Dataspace.h>
+#include <android/hardware_buffer.h>
+#include <math/vec3.h>
+
+#include <string>
+#include <vector>
+
+namespace android::tonemap {
+
+// Describes a shader uniform
+// The shader uniform is intended to be passed into a SkRuntimeShaderBuilder, i.e.:
+//
+// SkRuntimeShaderBuilder builder;
+// builder.uniform(<uniform name>).set(<uniform value>.data(), <uniform value>.size());
+struct ShaderUniform {
+    // The name of the uniform, used for binding into a shader.
+    // The shader must contain a uniform whose name matches this.
+    std::string name;
+
+    // The value for the uniform, which should be bound to the uniform identified by <name>
+    std::vector<uint8_t> value;
+};
+
+// Describes metadata which may be used for constructing the shader uniforms.
+// This metadata should not be used for manipulating the source code of the shader program directly,
+// as otherwise caching by other system of these shaders may break.
+struct Metadata {
+    // The maximum luminance of the display in nits
+    float displayMaxLuminance = 0.0;
+
+    // The maximum luminance of the content in nits
+    float contentMaxLuminance = 0.0;
+
+    // The current brightness of the display in nits
+    float currentDisplayLuminance = 0.0;
+
+    // Reference to an AHardwareBuffer.
+    // Devices that support gralloc 4.0 and higher may attach metadata onto a
+    // particular frame's buffer, including metadata used by HDR-standards like
+    // SMPTE 2086 or SMPTE 2094-40.
+    // Note that this parameter may be optional if there is no hardware buffer
+    // available, for instance if the source content is generated from a GL
+    // texture that does not have associated metadata. As such, implementations
+    // must support nullptr.
+    AHardwareBuffer* buffer = nullptr;
+};
+
+// Utility class containing pre-processed conversions for a particular color
+struct Color {
+    // RGB color in linear space
+    vec3 linearRGB;
+    // CIE 1931 XYZ representation of the color
+    vec3 xyz;
+};
+
+class ToneMapper {
+public:
+    virtual ~ToneMapper() {}
+    // Constructs a tonemap shader whose shader language is SkSL, which tonemaps from an
+    // input whose dataspace is described by sourceDataspace, to an output whose dataspace
+    // is described by destinationDataspace
+    //
+    // The returned shader string *must* contain a function with the following signature:
+    // float libtonemap_LookupTonemapGain(vec3 linearRGB, vec3 xyz);
+    //
+    // The arguments are:
+    // * linearRGB is the absolute nits of the RGB pixels in linear space
+    // * xyz is linearRGB converted into XYZ
+    //
+    // libtonemap_LookupTonemapGain() returns a float representing the amount by which to scale the
+    // absolute nits of the pixels. This function may be plugged into any existing SkSL shader, and
+    // is expected to look something like this:
+    //
+    // vec3 rgb = ...;
+    // // apply the EOTF based on the incoming dataspace to convert to linear nits.
+    // vec3 linearRGB = applyEOTF(rgb);
+    // // apply a RGB->XYZ matrix float3
+    // vec3 xyz = toXYZ(linearRGB);
+    // // Scale the luminance based on the content standard
+    // vec3 absoluteRGB = ScaleLuminance(linearRGB);
+    // vec3 absoluteXYZ = ScaleLuminance(xyz);
+    // float gain = libtonemap_LookupTonemapGain(absoluteRGB, absoluteXYZ);
+    // // Normalize the luminance back down to a [0, 1] range
+    // xyz = NormalizeLuminance(absoluteXYZ * gain);
+    // // apply a XYZ->RGB matrix and apply the output OETf.
+    // vec3 finalColor = applyOETF(ToRGB(xyz));
+    // ...
+    //
+    // Helper methods in this shader should be prefixed with "libtonemap_". Accordingly, libraries
+    // which consume this shader must *not* contain any methods prefixed with "libtonemap_" to
+    // guarantee that there are no conflicts in name resolution.
+    virtual std::string generateTonemapGainShaderSkSL(
+            aidl::android::hardware::graphics::common::Dataspace sourceDataspace,
+            aidl::android::hardware::graphics::common::Dataspace destinationDataspace) = 0;
+
+    // Constructs uniform descriptions that correspond to those that are generated for the tonemap
+    // shader. Uniforms must be prefixed with "in_libtonemap_". Libraries which consume this shader
+    // must not bind any new uniforms that begin with this prefix.
+    //
+    // Downstream shaders may assume the existence of the uniform in_libtonemap_displayMaxLuminance
+    // and in_libtonemap_inputMaxLuminance, in order to assist with scaling and normalizing
+    // luminance as described in the documentation for generateTonemapGainShaderSkSL(). That is,
+    // shaders plugging in a tone-mapping shader returned by generateTonemapGainShaderSkSL() may
+    // assume that there are predefined floats in_libtonemap_displayMaxLuminance and
+    // in_libtonemap_inputMaxLuminance inside of the body of the tone-mapping shader.
+    virtual std::vector<ShaderUniform> generateShaderSkSLUniforms(const Metadata& metadata) = 0;
+
+    // CPU implementation of the tonemapping gain. This must match the GPU implementation returned
+    // by generateTonemapGainShaderSKSL() above, with some epsilon difference to account for
+    // differences in hardware precision.
+    //
+    // The gain is computed assuming an input described by sourceDataspace, tonemapped to an output
+    // described by destinationDataspace. To compute the gain, the input colors are provided by
+    // linearRGB, which is the RGB colors in linear space. The colors in XYZ space are also
+    // provided. Metadata is also provided for helping to compute the tonemapping curve.
+    using Gain = double;
+    virtual std::vector<Gain> lookupTonemapGain(
+            aidl::android::hardware::graphics::common::Dataspace sourceDataspace,
+            aidl::android::hardware::graphics::common::Dataspace destinationDataspace,
+            const std::vector<Color>& colors, const Metadata& metadata) = 0;
+};
+
+// Retrieves a tonemapper instance.
+// This instance is globally constructed.
+ToneMapper* getToneMapper();
+
+} // namespace android::tonemap
diff --git a/libs/tonemap/tests/Android.bp b/libs/tonemap/tests/Android.bp
new file mode 100644
index 0000000..26a1d79
--- /dev/null
+++ b/libs/tonemap/tests/Android.bp
@@ -0,0 +1,43 @@
+// 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.
+
+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: "libtonemap_test",
+    test_suites: ["device-tests"],
+    srcs: [
+        "tonemap_test.cpp",
+    ],
+    header_libs: [
+        "libtonemap_headers",
+    ],
+    shared_libs: [
+        "android.hardware.graphics.common-V3-ndk",
+        "libnativewindow",
+    ],
+    static_libs: [
+        "libmath",
+        "libgmock",
+        "libgtest",
+        "libtonemap",
+    ],
+}
diff --git a/libs/tonemap/tests/tonemap_test.cpp b/libs/tonemap/tests/tonemap_test.cpp
new file mode 100644
index 0000000..1d46482
--- /dev/null
+++ b/libs/tonemap/tests/tonemap_test.cpp
@@ -0,0 +1,88 @@
+/*
+ * 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.
+ */
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <tonemap/tonemap.h>
+#include <cmath>
+
+namespace android {
+
+using testing::HasSubstr;
+
+struct TonemapTest : public ::testing::Test {};
+
+TEST_F(TonemapTest, generateShaderSkSLUniforms_containsDefaultUniforms) {
+    static const constexpr float kDisplayMaxLuminance = 1.f;
+    static const constexpr float kContentMaxLuminance = 2.f;
+    tonemap::Metadata metadata{.displayMaxLuminance = kDisplayMaxLuminance,
+                               .contentMaxLuminance = kContentMaxLuminance};
+    const auto uniforms = tonemap::getToneMapper()->generateShaderSkSLUniforms(metadata);
+
+    ASSERT_EQ(1, std::count_if(uniforms.cbegin(), uniforms.cend(), [](const auto& data) {
+                  return data.name == "in_libtonemap_displayMaxLuminance";
+              }));
+    ASSERT_EQ(1, std::count_if(uniforms.cbegin(), uniforms.cend(), [](const auto& data) {
+                  return data.name == "in_libtonemap_inputMaxLuminance";
+              }));
+
+    // Smoke check that metadata values are "real", specifically that they're non-zero and actually
+    // numbers. This is to help avoid shaders using these uniforms from dividing by zero or other
+    // catastrophic errors.
+    const auto& displayLum = std::find_if(uniforms.cbegin(), uniforms.cend(), [](const auto& data) {
+                                 return data.name == "in_libtonemap_displayMaxLuminance";
+                             })->value;
+
+    float displayLumFloat = 0.f;
+    std::memcpy(&displayLumFloat, displayLum.data(), displayLum.size());
+    EXPECT_FALSE(std::isnan(displayLumFloat));
+    EXPECT_GT(displayLumFloat, 0);
+
+    const auto& contentLum = std::find_if(uniforms.cbegin(), uniforms.cend(), [](const auto& data) {
+                                 return data.name == "in_libtonemap_inputMaxLuminance";
+                             })->value;
+
+    float contentLumFloat = 0.f;
+    std::memcpy(&contentLumFloat, contentLum.data(), contentLum.size());
+    EXPECT_FALSE(std::isnan(contentLumFloat));
+    EXPECT_GT(contentLumFloat, 0);
+}
+
+TEST_F(TonemapTest, generateTonemapGainShaderSkSL_containsEntryPointForPQ) {
+    const auto shader =
+            tonemap::getToneMapper()
+                    ->generateTonemapGainShaderSkSL(aidl::android::hardware::graphics::common::
+                                                            Dataspace::BT2020_ITU_PQ,
+                                                    aidl::android::hardware::graphics::common::
+                                                            Dataspace::DISPLAY_P3);
+
+    // Other tests such as librenderengine_test will plug in the shader to check compilation.
+    EXPECT_THAT(shader, HasSubstr("float libtonemap_LookupTonemapGain(vec3 linearRGB, vec3 xyz)"));
+}
+
+TEST_F(TonemapTest, generateTonemapGainShaderSkSL_containsEntryPointForHLG) {
+    const auto shader =
+            tonemap::getToneMapper()
+                    ->generateTonemapGainShaderSkSL(aidl::android::hardware::graphics::common::
+                                                            Dataspace::BT2020_ITU_HLG,
+                                                    aidl::android::hardware::graphics::common::
+                                                            Dataspace::DISPLAY_P3);
+
+    // Other tests such as librenderengine_test will plug in the shader to check compilation.
+    EXPECT_THAT(shader, HasSubstr("float libtonemap_LookupTonemapGain(vec3 linearRGB, vec3 xyz)"));
+}
+
+} // namespace android
diff --git a/libs/tonemap/tonemap.cpp b/libs/tonemap/tonemap.cpp
new file mode 100644
index 0000000..19e1eea
--- /dev/null
+++ b/libs/tonemap/tonemap.cpp
@@ -0,0 +1,749 @@
+/*
+ * 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.
+ */
+
+#include <tonemap/tonemap.h>
+
+#include <algorithm>
+#include <cstdint>
+#include <mutex>
+#include <type_traits>
+
+namespace android::tonemap {
+
+namespace {
+
+// Flag containing the variant of tone map algorithm to use.
+enum class ToneMapAlgorithm {
+    AndroidO,  // Default algorithm in place since Android O,
+    Android13, // Algorithm used in Android 13.
+};
+
+static const constexpr auto kToneMapAlgorithm = ToneMapAlgorithm::Android13;
+
+static const constexpr auto kTransferMask =
+        static_cast<int32_t>(aidl::android::hardware::graphics::common::Dataspace::TRANSFER_MASK);
+static const constexpr auto kTransferST2084 =
+        static_cast<int32_t>(aidl::android::hardware::graphics::common::Dataspace::TRANSFER_ST2084);
+static const constexpr auto kTransferHLG =
+        static_cast<int32_t>(aidl::android::hardware::graphics::common::Dataspace::TRANSFER_HLG);
+
+template <typename T, std::enable_if_t<std::is_trivially_copyable<T>::value, bool> = true>
+std::vector<uint8_t> buildUniformValue(T value) {
+    std::vector<uint8_t> result;
+    result.resize(sizeof(value));
+    std::memcpy(result.data(), &value, sizeof(value));
+    return result;
+}
+
+// Refer to BT2100-2
+float computeHlgGamma(float currentDisplayBrightnessNits) {
+    // BT 2100-2's recommendation for taking into account the nominal max
+    // brightness of the display does not work when the current brightness is
+    // very low. For instance, the gamma becomes negative when the current
+    // brightness is between 1 and 2 nits, which would be a bad experience in a
+    // dark environment. Furthermore, BT2100-2 recommends applying
+    // channel^(gamma - 1) as its OOTF, which means that when the current
+    // brightness is lower than 335 nits then channel * channel^(gamma - 1) >
+    // channel, which makes dark scenes very bright. As a workaround for those
+    // problems, lower-bound the brightness to 500 nits.
+    constexpr float minBrightnessNits = 500.f;
+    currentDisplayBrightnessNits = std::max(minBrightnessNits, currentDisplayBrightnessNits);
+    return 1.2 + 0.42 * std::log10(currentDisplayBrightnessNits / 1000);
+}
+
+class ToneMapperO : public ToneMapper {
+public:
+    std::string generateTonemapGainShaderSkSL(
+            aidl::android::hardware::graphics::common::Dataspace sourceDataspace,
+            aidl::android::hardware::graphics::common::Dataspace destinationDataspace) override {
+        const int32_t sourceDataspaceInt = static_cast<int32_t>(sourceDataspace);
+        const int32_t destinationDataspaceInt = static_cast<int32_t>(destinationDataspace);
+
+        std::string program;
+        // Define required uniforms
+        program.append(R"(
+                uniform float in_libtonemap_displayMaxLuminance;
+                uniform float in_libtonemap_inputMaxLuminance;
+            )");
+        switch (sourceDataspaceInt & kTransferMask) {
+            case kTransferST2084:
+            case kTransferHLG:
+                switch (destinationDataspaceInt & kTransferMask) {
+                    case kTransferST2084:
+                        program.append(R"(
+                                    float libtonemap_ToneMapTargetNits(vec3 xyz) {
+                                        return xyz.y;
+                                    }
+                                )");
+                        break;
+                    case kTransferHLG:
+                        // PQ has a wider luminance range (10,000 nits vs. 1,000 nits) than HLG, so
+                        // we'll clamp the luminance range in case we're mapping from PQ input to
+                        // HLG output.
+                        program.append(R"(
+                                    float libtonemap_ToneMapTargetNits(vec3 xyz) {
+                                        float nits = clamp(xyz.y, 0.0, 1000.0);
+                                        return nits * pow(nits / 1000.0, -0.2 / 1.2);
+                                    }
+                                )");
+                        break;
+                    default:
+                        // HLG follows BT2100, but this tonemapping version
+                        // does not take into account current display brightness
+                        if ((sourceDataspaceInt & kTransferMask) == kTransferHLG) {
+                            program.append(R"(
+                                        float libtonemap_applyBaseOOTFGain(float nits) {
+                                            return pow(nits, 0.2);
+                                        }
+                                    )");
+                        } else {
+                            program.append(R"(
+                                        float libtonemap_applyBaseOOTFGain(float nits) {
+                                            return 1.0;
+                                        }
+                                    )");
+                        }
+                        // Here we're mapping from HDR to SDR content, so interpolate using a
+                        // Hermitian polynomial onto the smaller luminance range.
+                        program.append(R"(
+                                    float libtonemap_ToneMapTargetNits(vec3 xyz) {
+                                        float maxInLumi = in_libtonemap_inputMaxLuminance;
+                                        float maxOutLumi = in_libtonemap_displayMaxLuminance;
+
+                                        xyz = xyz * libtonemap_applyBaseOOTFGain(xyz.y);
+
+                                        float nits = xyz.y;
+
+                                        // if the max input luminance is less than what we can
+                                        // output then no tone mapping is needed as all color
+                                        // values will be in range.
+                                        if (maxInLumi <= maxOutLumi) {
+                                            return xyz.y;
+                                        } else {
+
+                                            // three control points
+                                            const float x0 = 10.0;
+                                            const float y0 = 17.0;
+                                            float x1 = maxOutLumi * 0.75;
+                                            float y1 = x1;
+                                            float x2 = x1 + (maxInLumi - x1) / 2.0;
+                                            float y2 = y1 + (maxOutLumi - y1) * 0.75;
+
+                                            // horizontal distances between the last three
+                                            // control points
+                                            float h12 = x2 - x1;
+                                            float h23 = maxInLumi - x2;
+                                            // tangents at the last three control points
+                                            float m1 = (y2 - y1) / h12;
+                                            float m3 = (maxOutLumi - y2) / h23;
+                                            float m2 = (m1 + m3) / 2.0;
+
+                                            if (nits < x0) {
+                                                // scale [0.0, x0] to [0.0, y0] linearly
+                                                float slope = y0 / x0;
+                                                return nits * slope;
+                                            } else if (nits < x1) {
+                                                // scale [x0, x1] to [y0, y1] linearly
+                                                float slope = (y1 - y0) / (x1 - x0);
+                                                nits = y0 + (nits - x0) * slope;
+                                            } else if (nits < x2) {
+                                                // scale [x1, x2] to [y1, y2] using Hermite interp
+                                                float t = (nits - x1) / h12;
+                                                nits = (y1 * (1.0 + 2.0 * t) + h12 * m1 * t) *
+                                                        (1.0 - t) * (1.0 - t) +
+                                                        (y2 * (3.0 - 2.0 * t) +
+                                                        h12 * m2 * (t - 1.0)) * t * t;
+                                            } else {
+                                                // scale [x2, maxInLumi] to [y2, maxOutLumi] using
+                                                // Hermite interp
+                                                float t = (nits - x2) / h23;
+                                                nits = (y2 * (1.0 + 2.0 * t) + h23 * m2 * t) *
+                                                        (1.0 - t) * (1.0 - t) + (maxOutLumi *
+                                                        (3.0 - 2.0 * t) + h23 * m3 *
+                                                        (t - 1.0)) * t * t;
+                                            }
+                                        }
+
+                                        return nits;
+                                    }
+                                )");
+                        break;
+                }
+                break;
+            default:
+                switch (destinationDataspaceInt & kTransferMask) {
+                    case kTransferST2084:
+                    case kTransferHLG:
+                        // HLG follows BT2100, but this tonemapping version
+                        // does not take into account current display brightness
+                        if ((destinationDataspaceInt & kTransferMask) == kTransferHLG) {
+                            program.append(R"(
+                                        float libtonemap_applyBaseOOTFGain(float nits) {
+                                            return pow(nits / 1000.0, -0.2 / 1.2);
+                                        }
+                                    )");
+                        } else {
+                            program.append(R"(
+                                        float libtonemap_applyBaseOOTFGain(float nits) {
+                                            return 1.0;
+                                        }
+                                    )");
+                        }
+                        // Map from SDR onto an HDR output buffer
+                        // Here we use a polynomial curve to map from [0, displayMaxLuminance] onto
+                        // [0, maxOutLumi] which is hard-coded to be 3000 nits.
+                        program.append(R"(
+                                    float libtonemap_ToneMapTargetNits(vec3 xyz) {
+                                        const float maxOutLumi = 3000.0;
+
+                                        const float x0 = 5.0;
+                                        const float y0 = 2.5;
+                                        float x1 = in_libtonemap_displayMaxLuminance * 0.7;
+                                        float y1 = maxOutLumi * 0.15;
+                                        float x2 = in_libtonemap_displayMaxLuminance * 0.9;
+                                        float y2 = maxOutLumi * 0.45;
+                                        float x3 = in_libtonemap_displayMaxLuminance;
+                                        float y3 = maxOutLumi;
+
+                                        float c1 = y1 / 3.0;
+                                        float c2 = y2 / 2.0;
+                                        float c3 = y3 / 1.5;
+
+                                        float nits = xyz.y;
+
+                                        if (nits <= x0) {
+                                            // scale [0.0, x0] to [0.0, y0] linearly
+                                            float slope = y0 / x0;
+                                            nits = nits * slope;
+                                        } else if (nits <= x1) {
+                                            // scale [x0, x1] to [y0, y1] using a curve
+                                            float t = (nits - x0) / (x1 - x0);
+                                            nits = (1.0 - t) * (1.0 - t) * y0 +
+                                                    2.0 * (1.0 - t) * t * c1 + t * t * y1;
+                                        } else if (nits <= x2) {
+                                            // scale [x1, x2] to [y1, y2] using a curve
+                                            float t = (nits - x1) / (x2 - x1);
+                                            nits = (1.0 - t) * (1.0 - t) * y1 +
+                                                    2.0 * (1.0 - t) * t * c2 + t * t * y2;
+                                        } else {
+                                            // scale [x2, x3] to [y2, y3] using a curve
+                                            float t = (nits - x2) / (x3 - x2);
+                                            nits = (1.0 - t) * (1.0 - t) * y2 +
+                                                    2.0 * (1.0 - t) * t * c3 + t * t * y3;
+                                        }
+
+                                        return nits * libtonemap_applyBaseOOTFGain(nits);
+                                    }
+                                )");
+                        break;
+                    default:
+                        // For completeness, this is tone-mapping from SDR to SDR, where this is
+                        // just a no-op.
+                        program.append(R"(
+                                    float libtonemap_ToneMapTargetNits(vec3 xyz) {
+                                        return xyz.y;
+                                    }
+                                )");
+                        break;
+                }
+                break;
+        }
+
+        program.append(R"(
+            float libtonemap_LookupTonemapGain(vec3 linearRGB, vec3 xyz) {
+                if (xyz.y <= 0.0) {
+                    return 1.0;
+                }
+                return libtonemap_ToneMapTargetNits(xyz) / xyz.y;
+            }
+        )");
+        return program;
+    }
+
+    std::vector<ShaderUniform> generateShaderSkSLUniforms(const Metadata& metadata) override {
+        std::vector<ShaderUniform> uniforms;
+
+        uniforms.reserve(2);
+
+        uniforms.push_back({.name = "in_libtonemap_displayMaxLuminance",
+                            .value = buildUniformValue<float>(metadata.displayMaxLuminance)});
+        uniforms.push_back({.name = "in_libtonemap_inputMaxLuminance",
+                            .value = buildUniformValue<float>(metadata.contentMaxLuminance)});
+        return uniforms;
+    }
+
+    std::vector<Gain> lookupTonemapGain(
+            aidl::android::hardware::graphics::common::Dataspace sourceDataspace,
+            aidl::android::hardware::graphics::common::Dataspace destinationDataspace,
+            const std::vector<Color>& colors, const Metadata& metadata) override {
+        std::vector<Gain> gains;
+        gains.reserve(colors.size());
+
+        for (const auto [_, xyz] : colors) {
+            if (xyz.y <= 0.0) {
+                gains.push_back(1.0);
+                continue;
+            }
+            const int32_t sourceDataspaceInt = static_cast<int32_t>(sourceDataspace);
+            const int32_t destinationDataspaceInt = static_cast<int32_t>(destinationDataspace);
+
+            double targetNits = 0.0;
+            switch (sourceDataspaceInt & kTransferMask) {
+                case kTransferST2084:
+                case kTransferHLG:
+                    switch (destinationDataspaceInt & kTransferMask) {
+                        case kTransferST2084:
+                            targetNits = xyz.y;
+                            break;
+                        case kTransferHLG:
+                            // PQ has a wider luminance range (10,000 nits vs. 1,000 nits) than HLG,
+                            // so we'll clamp the luminance range in case we're mapping from PQ
+                            // input to HLG output.
+                            targetNits = std::clamp(xyz.y, 0.0f, 1000.0f);
+                            targetNits *= std::pow(targetNits / 1000.f, -0.2 / 1.2);
+                            break;
+                        default:
+                            // Here we're mapping from HDR to SDR content, so interpolate using a
+                            // Hermitian polynomial onto the smaller luminance range.
+
+                            targetNits = xyz.y;
+
+                            if ((sourceDataspaceInt & kTransferMask) == kTransferHLG) {
+                                targetNits *= std::pow(targetNits, 0.2);
+                            }
+                            // if the max input luminance is less than what we can output then
+                            // no tone mapping is needed as all color values will be in range.
+                            if (metadata.contentMaxLuminance > metadata.displayMaxLuminance) {
+                                // three control points
+                                const double x0 = 10.0;
+                                const double y0 = 17.0;
+                                double x1 = metadata.displayMaxLuminance * 0.75;
+                                double y1 = x1;
+                                double x2 = x1 + (metadata.contentMaxLuminance - x1) / 2.0;
+                                double y2 = y1 + (metadata.displayMaxLuminance - y1) * 0.75;
+
+                                // horizontal distances between the last three control points
+                                double h12 = x2 - x1;
+                                double h23 = metadata.contentMaxLuminance - x2;
+                                // tangents at the last three control points
+                                double m1 = (y2 - y1) / h12;
+                                double m3 = (metadata.displayMaxLuminance - y2) / h23;
+                                double m2 = (m1 + m3) / 2.0;
+
+                                if (targetNits < x0) {
+                                    // scale [0.0, x0] to [0.0, y0] linearly
+                                    double slope = y0 / x0;
+                                    targetNits *= slope;
+                                } else if (targetNits < x1) {
+                                    // scale [x0, x1] to [y0, y1] linearly
+                                    double slope = (y1 - y0) / (x1 - x0);
+                                    targetNits = y0 + (targetNits - x0) * slope;
+                                } else if (targetNits < x2) {
+                                    // scale [x1, x2] to [y1, y2] using Hermite interp
+                                    double t = (targetNits - x1) / h12;
+                                    targetNits = (y1 * (1.0 + 2.0 * t) + h12 * m1 * t) * (1.0 - t) *
+                                                    (1.0 - t) +
+                                            (y2 * (3.0 - 2.0 * t) + h12 * m2 * (t - 1.0)) * t * t;
+                                } else {
+                                    // scale [x2, maxInLumi] to [y2, maxOutLumi] using Hermite
+                                    // interp
+                                    double t = (targetNits - x2) / h23;
+                                    targetNits = (y2 * (1.0 + 2.0 * t) + h23 * m2 * t) * (1.0 - t) *
+                                                    (1.0 - t) +
+                                            (metadata.displayMaxLuminance * (3.0 - 2.0 * t) +
+                                             h23 * m3 * (t - 1.0)) *
+                                                    t * t;
+                                }
+                            }
+                            break;
+                    }
+                    break;
+                default:
+                    // source is SDR
+                    switch (destinationDataspaceInt & kTransferMask) {
+                        case kTransferST2084:
+                        case kTransferHLG: {
+                            // Map from SDR onto an HDR output buffer
+                            // Here we use a polynomial curve to map from [0, displayMaxLuminance]
+                            // onto [0, maxOutLumi] which is hard-coded to be 3000 nits.
+                            const double maxOutLumi = 3000.0;
+
+                            double x0 = 5.0;
+                            double y0 = 2.5;
+                            double x1 = metadata.displayMaxLuminance * 0.7;
+                            double y1 = maxOutLumi * 0.15;
+                            double x2 = metadata.displayMaxLuminance * 0.9;
+                            double y2 = maxOutLumi * 0.45;
+                            double x3 = metadata.displayMaxLuminance;
+                            double y3 = maxOutLumi;
+
+                            double c1 = y1 / 3.0;
+                            double c2 = y2 / 2.0;
+                            double c3 = y3 / 1.5;
+
+                            targetNits = xyz.y;
+
+                            if (targetNits <= x0) {
+                                // scale [0.0, x0] to [0.0, y0] linearly
+                                double slope = y0 / x0;
+                                targetNits *= slope;
+                            } else if (targetNits <= x1) {
+                                // scale [x0, x1] to [y0, y1] using a curve
+                                double t = (targetNits - x0) / (x1 - x0);
+                                targetNits = (1.0 - t) * (1.0 - t) * y0 + 2.0 * (1.0 - t) * t * c1 +
+                                        t * t * y1;
+                            } else if (targetNits <= x2) {
+                                // scale [x1, x2] to [y1, y2] using a curve
+                                double t = (targetNits - x1) / (x2 - x1);
+                                targetNits = (1.0 - t) * (1.0 - t) * y1 + 2.0 * (1.0 - t) * t * c2 +
+                                        t * t * y2;
+                            } else {
+                                // scale [x2, x3] to [y2, y3] using a curve
+                                double t = (targetNits - x2) / (x3 - x2);
+                                targetNits = (1.0 - t) * (1.0 - t) * y2 + 2.0 * (1.0 - t) * t * c3 +
+                                        t * t * y3;
+                            }
+
+                            if ((destinationDataspaceInt & kTransferMask) == kTransferHLG) {
+                                targetNits *= std::pow(targetNits / 1000.0, -0.2 / 1.2);
+                            }
+                        } break;
+                        default:
+                            // For completeness, this is tone-mapping from SDR to SDR, where this is
+                            // just a no-op.
+                            targetNits = xyz.y;
+                            break;
+                    }
+            }
+            gains.push_back(targetNits / xyz.y);
+        }
+        return gains;
+    }
+};
+
+class ToneMapper13 : public ToneMapper {
+private:
+    double OETF_ST2084(double nits) {
+        nits = nits / 10000.0;
+        double m1 = (2610.0 / 4096.0) / 4.0;
+        double m2 = (2523.0 / 4096.0) * 128.0;
+        double c1 = (3424.0 / 4096.0);
+        double c2 = (2413.0 / 4096.0) * 32.0;
+        double c3 = (2392.0 / 4096.0) * 32.0;
+
+        double tmp = std::pow(nits, m1);
+        tmp = (c1 + c2 * tmp) / (1.0 + c3 * tmp);
+        return std::pow(tmp, m2);
+    }
+
+    double OETF_HLG(double nits) {
+        nits = nits / 1000.0;
+        const double a = 0.17883277;
+        const double b = 0.28466892;
+        const double c = 0.55991073;
+        return nits <= 1.0 / 12.0 ? std::sqrt(3.0 * nits) : a * std::log(12.0 * nits - b) + c;
+    }
+
+public:
+    std::string generateTonemapGainShaderSkSL(
+            aidl::android::hardware::graphics::common::Dataspace sourceDataspace,
+            aidl::android::hardware::graphics::common::Dataspace destinationDataspace) override {
+        const int32_t sourceDataspaceInt = static_cast<int32_t>(sourceDataspace);
+        const int32_t destinationDataspaceInt = static_cast<int32_t>(destinationDataspace);
+
+        std::string program;
+        // Input uniforms
+        program.append(R"(
+                uniform float in_libtonemap_displayMaxLuminance;
+                uniform float in_libtonemap_inputMaxLuminance;
+                uniform float in_libtonemap_hlgGamma;
+            )");
+        switch (sourceDataspaceInt & kTransferMask) {
+            case kTransferST2084:
+                switch (destinationDataspaceInt & kTransferMask) {
+                    case kTransferST2084:
+                        program.append(R"(
+                                    float libtonemap_ToneMapTargetNits(float maxRGB) {
+                                        return maxRGB;
+                                    }
+                                )");
+                        break;
+                    case kTransferHLG:
+                        // PQ has a wider luminance range (10,000 nits vs. 1,000 nits) than HLG, so
+                        // we'll clamp the luminance range in case we're mapping from PQ input to
+                        // HLG output.
+                        program.append(R"(
+                                    float libtonemap_ToneMapTargetNits(float maxRGB) {
+                                        float nits = clamp(maxRGB, 0.0, 1000.0);
+                                        float gamma = (1 - in_libtonemap_hlgGamma)
+                                                / in_libtonemap_hlgGamma;
+                                        return nits * pow(nits / 1000.0, gamma);
+                                    }
+                                )");
+                        break;
+
+                    default:
+                        program.append(R"(
+                                float libtonemap_OETFTone(float channel) {
+                                    channel = channel / 10000.0;
+                                    float m1 = (2610.0 / 4096.0) / 4.0;
+                                    float m2 = (2523.0 / 4096.0) * 128.0;
+                                    float c1 = (3424.0 / 4096.0);
+                                    float c2 = (2413.0 / 4096.0) * 32.0;
+                                    float c3 = (2392.0 / 4096.0) * 32.0;
+
+                                    float tmp = pow(channel, float(m1));
+                                    tmp = (c1 + c2 * tmp) / (1.0 + c3 * tmp);
+                                    return pow(tmp, float(m2));
+                                }
+
+                                float libtonemap_ToneMapTargetNits(float maxRGB) {
+                                    float maxInLumi = in_libtonemap_inputMaxLuminance;
+                                    float maxOutLumi = in_libtonemap_displayMaxLuminance;
+
+                                    float nits = maxRGB;
+
+                                    float x1 = maxOutLumi * 0.65;
+                                    float y1 = x1;
+
+                                    float x3 = maxInLumi;
+                                    float y3 = maxOutLumi;
+
+                                    float x2 = x1 + (x3 - x1) * 4.0 / 17.0;
+                                    float y2 = maxOutLumi * 0.9;
+
+                                    float greyNorm1 = libtonemap_OETFTone(x1);
+                                    float greyNorm2 = libtonemap_OETFTone(x2);
+                                    float greyNorm3 = libtonemap_OETFTone(x3);
+
+                                    float slope1 = 0;
+                                    float slope2 = (y2 - y1) / (greyNorm2 - greyNorm1);
+                                    float slope3 = (y3 - y2 ) / (greyNorm3 - greyNorm2);
+
+                                    if (nits < x1) {
+                                        return nits;
+                                    }
+
+                                    if (nits > maxInLumi) {
+                                        return maxOutLumi;
+                                    }
+
+                                    float greyNits = libtonemap_OETFTone(nits);
+
+                                    if (greyNits <= greyNorm2) {
+                                        nits = (greyNits - greyNorm2) * slope2 + y2;
+                                    } else if (greyNits <= greyNorm3) {
+                                        nits = (greyNits - greyNorm3) * slope3 + y3;
+                                    } else {
+                                        nits = maxOutLumi;
+                                    }
+
+                                    return nits;
+                                }
+                                )");
+                        break;
+                }
+                break;
+            case kTransferHLG:
+                switch (destinationDataspaceInt & kTransferMask) {
+                    // HLG uses the OOTF from BT 2100.
+                    case kTransferST2084:
+                        program.append(R"(
+                                    float libtonemap_ToneMapTargetNits(float maxRGB) {
+                                        return maxRGB
+                                                * pow(maxRGB / 1000.0, in_libtonemap_hlgGamma - 1);
+                                    }
+                                )");
+                        break;
+                    case kTransferHLG:
+                        program.append(R"(
+                                    float libtonemap_ToneMapTargetNits(float maxRGB) {
+                                        return maxRGB;
+                                    }
+                                )");
+                        break;
+                    default:
+                        // Follow BT 2100 and renormalize to max display luminance if we're
+                        // tone-mapping down to SDR, as libshaders normalizes all SDR output from
+                        // [0, maxDisplayLumins] -> [0, 1]
+                        program.append(R"(
+                                    float libtonemap_ToneMapTargetNits(float maxRGB) {
+                                        return maxRGB
+                                                * pow(maxRGB / 1000.0, in_libtonemap_hlgGamma - 1)
+                                                * in_libtonemap_displayMaxLuminance / 1000.0;
+                                    }
+                                )");
+                        break;
+                }
+                break;
+            default:
+                // Inverse tone-mapping and SDR-SDR mapping is not supported.
+                program.append(R"(
+                            float libtonemap_ToneMapTargetNits(float maxRGB) {
+                                return maxRGB;
+                            }
+                        )");
+                break;
+        }
+
+        program.append(R"(
+            float libtonemap_LookupTonemapGain(vec3 linearRGB, vec3 xyz) {
+                float maxRGB = max(linearRGB.r, max(linearRGB.g, linearRGB.b));
+                if (maxRGB <= 0.0) {
+                    return 1.0;
+                }
+                return libtonemap_ToneMapTargetNits(maxRGB) / maxRGB;
+            }
+        )");
+        return program;
+    }
+
+    std::vector<ShaderUniform> generateShaderSkSLUniforms(const Metadata& metadata) override {
+        // Hardcode the max content luminance to a "reasonable" level
+        static const constexpr float kContentMaxLuminance = 4000.f;
+        std::vector<ShaderUniform> uniforms;
+        uniforms.reserve(3);
+        uniforms.push_back({.name = "in_libtonemap_displayMaxLuminance",
+                            .value = buildUniformValue<float>(metadata.displayMaxLuminance)});
+        uniforms.push_back({.name = "in_libtonemap_inputMaxLuminance",
+                            .value = buildUniformValue<float>(kContentMaxLuminance)});
+        uniforms.push_back({.name = "in_libtonemap_hlgGamma",
+                            .value = buildUniformValue<float>(
+                                    computeHlgGamma(metadata.currentDisplayLuminance))});
+        return uniforms;
+    }
+
+    std::vector<Gain> lookupTonemapGain(
+            aidl::android::hardware::graphics::common::Dataspace sourceDataspace,
+            aidl::android::hardware::graphics::common::Dataspace destinationDataspace,
+            const std::vector<Color>& colors, const Metadata& metadata) override {
+        std::vector<Gain> gains;
+        gains.reserve(colors.size());
+
+        // Precompute constants for HDR->SDR tonemapping parameters
+        constexpr double maxInLumi = 4000;
+        const double maxOutLumi = metadata.displayMaxLuminance;
+
+        const double x1 = maxOutLumi * 0.65;
+        const double y1 = x1;
+
+        const double x3 = maxInLumi;
+        const double y3 = maxOutLumi;
+
+        const double x2 = x1 + (x3 - x1) * 4.0 / 17.0;
+        const double y2 = maxOutLumi * 0.9;
+
+        const double greyNorm1 = OETF_ST2084(x1);
+        const double greyNorm2 = OETF_ST2084(x2);
+        const double greyNorm3 = OETF_ST2084(x3);
+
+        const double slope2 = (y2 - y1) / (greyNorm2 - greyNorm1);
+        const double slope3 = (y3 - y2) / (greyNorm3 - greyNorm2);
+
+        const double hlgGamma = computeHlgGamma(metadata.currentDisplayLuminance);
+
+        for (const auto [linearRGB, _] : colors) {
+            double maxRGB = std::max({linearRGB.r, linearRGB.g, linearRGB.b});
+
+            if (maxRGB <= 0.0) {
+                gains.push_back(1.0);
+                continue;
+            }
+
+            const int32_t sourceDataspaceInt = static_cast<int32_t>(sourceDataspace);
+            const int32_t destinationDataspaceInt = static_cast<int32_t>(destinationDataspace);
+
+            double targetNits = 0.0;
+            switch (sourceDataspaceInt & kTransferMask) {
+                case kTransferST2084:
+                    switch (destinationDataspaceInt & kTransferMask) {
+                        case kTransferST2084:
+                            targetNits = maxRGB;
+                            break;
+                        case kTransferHLG:
+                            // PQ has a wider luminance range (10,000 nits vs. 1,000 nits) than HLG,
+                            // so we'll clamp the luminance range in case we're mapping from PQ
+                            // input to HLG output.
+                            targetNits = std::clamp(maxRGB, 0.0, 1000.0);
+                            targetNits *= pow(targetNits / 1000.0, (1 - hlgGamma) / (hlgGamma));
+                            break;
+                        default:
+                            targetNits = maxRGB;
+                            if (targetNits < x1) {
+                                break;
+                            }
+
+                            if (targetNits > maxInLumi) {
+                                targetNits = maxOutLumi;
+                                break;
+                            }
+
+                            const double greyNits = OETF_ST2084(targetNits);
+
+                            if (greyNits <= greyNorm2) {
+                                targetNits = (greyNits - greyNorm2) * slope2 + y2;
+                            } else if (greyNits <= greyNorm3) {
+                                targetNits = (greyNits - greyNorm3) * slope3 + y3;
+                            } else {
+                                targetNits = maxOutLumi;
+                            }
+                            break;
+                    }
+                    break;
+                case kTransferHLG:
+                    switch (destinationDataspaceInt & kTransferMask) {
+                        case kTransferST2084:
+                            targetNits = maxRGB * pow(maxRGB / 1000.0, hlgGamma - 1);
+                            break;
+                        case kTransferHLG:
+                            targetNits = maxRGB;
+                            break;
+                        default:
+                            targetNits = maxRGB * pow(maxRGB / 1000.0, hlgGamma - 1) *
+                                    metadata.displayMaxLuminance / 1000.0;
+                            break;
+                    }
+                    break;
+                default:
+                    targetNits = maxRGB;
+                    break;
+            }
+
+            gains.push_back(targetNits / maxRGB);
+        }
+        return gains;
+    }
+};
+
+} // namespace
+
+ToneMapper* getToneMapper() {
+    static std::once_flag sOnce;
+    static std::unique_ptr<ToneMapper> sToneMapper;
+
+    std::call_once(sOnce, [&] {
+        switch (kToneMapAlgorithm) {
+            case ToneMapAlgorithm::AndroidO:
+                sToneMapper = std::unique_ptr<ToneMapper>(new ToneMapperO());
+                break;
+            case ToneMapAlgorithm::Android13:
+                sToneMapper = std::unique_ptr<ToneMapper>(new ToneMapper13());
+        }
+    });
+
+    return sToneMapper.get();
+}
+} // namespace android::tonemap
diff --git a/libs/ui/Android.bp b/libs/ui/Android.bp
index 506e308..a9380c6 100644
--- a/libs/ui/Android.bp
+++ b/libs/ui/Android.bp
@@ -29,6 +29,11 @@
     ],
 }
 
+cc_library_headers {
+    name: "libui_fuzzableDataspaces_headers",
+    export_include_dirs: ["include/ui/fuzzer/"],
+}
+
 cc_defaults {
     name: "libui-defaults",
     clang: true,
@@ -86,6 +91,7 @@
 
     export_include_dirs: [
         "include",
+        "include_mock",
         "include_private",
         "include_types",
     ],
@@ -122,6 +128,7 @@
     srcs: [
         "DebugUtils.cpp",
         "DeviceProductInfo.cpp",
+        "DisplayIdentification.cpp",
         "DisplayMode.cpp",
         "DynamicDisplayInfo.cpp",
         "Fence.cpp",
@@ -137,7 +144,7 @@
         "HdrCapabilities.cpp",
         "PixelFormat.cpp",
         "PublicFormat.cpp",
-        "Size.cpp",
+        "StaticAsserts.cpp",
         "StaticDisplayInfo.cpp",
     ],
 
@@ -159,13 +166,15 @@
         "android.hardware.graphics.allocator@2.0",
         "android.hardware.graphics.allocator@3.0",
         "android.hardware.graphics.allocator@4.0",
-        "android.hardware.graphics.common-V2-ndk",
+        "android.hardware.graphics.allocator-V1-ndk",
+        "android.hardware.graphics.common-V3-ndk",
         "android.hardware.graphics.common@1.2",
         "android.hardware.graphics.mapper@2.0",
         "android.hardware.graphics.mapper@2.1",
         "android.hardware.graphics.mapper@3.0",
         "android.hardware.graphics.mapper@4.0",
         "libbase",
+        "libbinder_ndk",
         "libcutils",
         "libgralloctypes",
         "libhidlbase",
@@ -176,12 +185,13 @@
 
     export_shared_lib_headers: [
         "android.hardware.graphics.common@1.2",
-        "android.hardware.graphics.common-V2-ndk",
+        "android.hardware.graphics.common-V3-ndk",
         "android.hardware.graphics.mapper@4.0",
         "libgralloctypes",
     ],
 
     static_libs: [
+        "libaidlcommonsupport",
         "libarect",
         "libgrallocusage",
         "libmath",
@@ -225,6 +235,12 @@
         "libui_headers",
     ],
     min_sdk_version: "29",
+    // TODO(b/214400477) to remove use of GraphicBuffer
+    apex_available: [
+        "//apex_available:platform",
+        "com.android.media.swcodec",
+        "test_com.android.media.swcodec",
+    ],
 
     afdo: true,
 }
@@ -248,6 +264,11 @@
         "libmath_headers",
     ],
     min_sdk_version: "29",
+    apex_available: [
+        "//apex_available:platform",
+        "com.android.media.swcodec",
+        "test_com.android.media.swcodec",
+    ],
 }
 
 // defaults to enable VALIDATE_REGIONS traces
diff --git a/libs/ui/DebugUtils.cpp b/libs/ui/DebugUtils.cpp
index 1f006ce..073da89 100644
--- a/libs/ui/DebugUtils.cpp
+++ b/libs/ui/DebugUtils.cpp
@@ -302,6 +302,8 @@
             return std::string("RGB_565");
         case android::PIXEL_FORMAT_BGRA_8888:
             return std::string("BGRA_8888");
+        case android::PIXEL_FORMAT_R_8:
+            return std::string("R_8");
         default:
             return StringPrintf("Unknown %#08x", format);
     }
diff --git a/services/surfaceflinger/DisplayHardware/DisplayIdentification.cpp b/libs/ui/DisplayIdentification.cpp
similarity index 86%
rename from services/surfaceflinger/DisplayHardware/DisplayIdentification.cpp
rename to libs/ui/DisplayIdentification.cpp
index 83c2b2e..16ed82a 100644
--- a/services/surfaceflinger/DisplayHardware/DisplayIdentification.cpp
+++ b/libs/ui/DisplayIdentification.cpp
@@ -24,12 +24,63 @@
 
 #include <log/log.h>
 
-#include "DisplayIdentification.h"
-#include "Hash.h"
+#include <ui/DisplayIdentification.h>
 
 namespace android {
 namespace {
 
+template <class T>
+inline T load(const void* p) {
+    static_assert(std::is_integral<T>::value, "T must be integral");
+
+    T r;
+    std::memcpy(&r, p, sizeof(r));
+    return r;
+}
+
+uint64_t rotateByAtLeast1(uint64_t val, uint8_t shift) {
+    return (val >> shift) | (val << (64 - shift));
+}
+
+uint64_t shiftMix(uint64_t val) {
+    return val ^ (val >> 47);
+}
+
+uint64_t hash64Len16(uint64_t u, uint64_t v) {
+    constexpr uint64_t kMul = 0x9ddfea08eb382d69;
+    uint64_t a = (u ^ v) * kMul;
+    a ^= (a >> 47);
+    uint64_t b = (v ^ a) * kMul;
+    b ^= (b >> 47);
+    b *= kMul;
+    return b;
+}
+
+uint64_t hash64Len0To16(const char* s, uint64_t len) {
+    constexpr uint64_t k2 = 0x9ae16a3b2f90404f;
+    constexpr uint64_t k3 = 0xc949d7c7509e6557;
+
+    if (len > 8) {
+        const uint64_t a = load<uint64_t>(s);
+        const uint64_t b = load<uint64_t>(s + len - 8);
+        return hash64Len16(a, rotateByAtLeast1(b + len, static_cast<uint8_t>(len))) ^ b;
+    }
+    if (len >= 4) {
+        const uint32_t a = load<uint32_t>(s);
+        const uint32_t b = load<uint32_t>(s + len - 4);
+        return hash64Len16(len + (a << 3), b);
+    }
+    if (len > 0) {
+        const unsigned char a = static_cast<unsigned char>(s[0]);
+        const unsigned char b = static_cast<unsigned char>(s[len >> 1]);
+        const unsigned char c = static_cast<unsigned char>(s[len - 1]);
+        const uint32_t y = static_cast<uint32_t>(a) + (static_cast<uint32_t>(b) << 8);
+        const uint32_t z = static_cast<uint32_t>(len) + (static_cast<uint32_t>(c) << 2);
+        return shiftMix(y * k2 ^ z * k3) * k2;
+    }
+    return k2;
+}
+
 using byte_view = std::basic_string_view<uint8_t>;
 
 constexpr size_t kEdidBlockSize = 128;
@@ -339,5 +390,13 @@
     return PhysicalDisplayId::fromEdid(0, kVirtualEdidManufacturerId, id);
 }
 
-} // namespace android
+uint64_t cityHash64Len0To16(std::string_view sv) {
+    auto len = sv.length();
+    if (len > 16) {
+        ALOGE("%s called with length %zu. Only hashing the first 16 chars", __FUNCTION__, len);
+        len = 16;
+    }
+    return hash64Len0To16(sv.data(), len);
+}
 
+} // namespace android
\ No newline at end of file
diff --git a/libs/ui/DynamicDisplayInfo.cpp b/libs/ui/DynamicDisplayInfo.cpp
index d5c4ef0..78ba996 100644
--- a/libs/ui/DynamicDisplayInfo.cpp
+++ b/libs/ui/DynamicDisplayInfo.cpp
@@ -41,7 +41,8 @@
             FlattenableHelpers::getFlattenedSize(activeColorMode) +
             FlattenableHelpers::getFlattenedSize(hdrCapabilities) +
             FlattenableHelpers::getFlattenedSize(autoLowLatencyModeSupported) +
-            FlattenableHelpers::getFlattenedSize(gameContentTypeSupported);
+            FlattenableHelpers::getFlattenedSize(gameContentTypeSupported) +
+            FlattenableHelpers::getFlattenedSize(preferredBootDisplayMode);
 }
 
 status_t DynamicDisplayInfo::flatten(void* buffer, size_t size) const {
@@ -55,6 +56,7 @@
     RETURN_IF_ERROR(FlattenableHelpers::flatten(&buffer, &size, hdrCapabilities));
     RETURN_IF_ERROR(FlattenableHelpers::flatten(&buffer, &size, autoLowLatencyModeSupported));
     RETURN_IF_ERROR(FlattenableHelpers::flatten(&buffer, &size, gameContentTypeSupported));
+    RETURN_IF_ERROR(FlattenableHelpers::flatten(&buffer, &size, preferredBootDisplayMode));
     return OK;
 }
 
@@ -66,6 +68,7 @@
     RETURN_IF_ERROR(FlattenableHelpers::unflatten(&buffer, &size, &hdrCapabilities));
     RETURN_IF_ERROR(FlattenableHelpers::unflatten(&buffer, &size, &autoLowLatencyModeSupported));
     RETURN_IF_ERROR(FlattenableHelpers::unflatten(&buffer, &size, &gameContentTypeSupported));
+    RETURN_IF_ERROR(FlattenableHelpers::unflatten(&buffer, &size, &preferredBootDisplayMode));
     return OK;
 }
 
diff --git a/libs/ui/Fence.cpp b/libs/ui/Fence.cpp
index 33ab7c4..cc96f83 100644
--- a/libs/ui/Fence.cpp
+++ b/libs/ui/Fence.cpp
@@ -132,9 +132,13 @@
         ALOGE("sync_file_info returned NULL for fd %d", mFenceFd.get());
         return SIGNAL_TIME_INVALID;
     }
+
     if (finfo->status != 1) {
+        const auto status = finfo->status;
+        ALOGE_IF(status < 0, "%s: sync_file_info contains an error: <%d> for fd: <%d>", __func__,
+                 status, mFenceFd.get());
         sync_file_info_free(finfo);
-        return SIGNAL_TIME_PENDING;
+        return status < 0 ? SIGNAL_TIME_INVALID : SIGNAL_TIME_PENDING;
     }
 
     uint64_t timestamp = 0;
diff --git a/libs/ui/Gralloc2.cpp b/libs/ui/Gralloc2.cpp
index 040a62b..f23f10a 100644
--- a/libs/ui/Gralloc2.cpp
+++ b/libs/ui/Gralloc2.cpp
@@ -110,6 +110,15 @@
               descriptorInfo->usage & ~validUsageBits);
         return BAD_VALUE;
     }
+
+    // Gralloc2 implementations never understand non-BLOB with GPU_DATA_BUFFER
+    // and do not reliably reject it.
+    if (descriptorInfo->usage & BufferUsage::GPU_DATA_BUFFER &&
+        descriptorInfo->format != hardware::graphics::common::V1_1::PixelFormat::BLOB) {
+        ALOGE("gralloc2 does not support non-BLOB pixel formats with GPU_DATA_BUFFER usage");
+        return BAD_VALUE;
+    }
+
     return NO_ERROR;
 }
 
diff --git a/libs/ui/Gralloc3.cpp b/libs/ui/Gralloc3.cpp
index 882674f..15c60bc 100644
--- a/libs/ui/Gralloc3.cpp
+++ b/libs/ui/Gralloc3.cpp
@@ -101,6 +101,15 @@
               descriptorInfo->usage & ~validUsageBits);
         return BAD_VALUE;
     }
+
+    // Gralloc3 implementations never understand non-BLOB with GPU_DATA_BUFFER
+    // and do not reliably reject it.
+    if (descriptorInfo->usage & BufferUsage::GPU_DATA_BUFFER &&
+        descriptorInfo->format != hardware::graphics::common::V1_2::PixelFormat::BLOB) {
+        ALOGE("gralloc3 does not support non-BLOB pixel formats with GPU_DATA_BUFFER usage");
+        return BAD_VALUE;
+    }
+
     return NO_ERROR;
 }
 
diff --git a/libs/ui/Gralloc4.cpp b/libs/ui/Gralloc4.cpp
index 80f6c82..1a42642 100644
--- a/libs/ui/Gralloc4.cpp
+++ b/libs/ui/Gralloc4.cpp
@@ -16,6 +16,12 @@
 
 #define LOG_TAG "Gralloc4"
 
+#include <aidl/android/hardware/graphics/allocator/AllocationError.h>
+#include <aidl/android/hardware/graphics/allocator/AllocationResult.h>
+#include <aidl/android/hardware/graphics/common/BufferUsage.h>
+#include <aidlcommonsupport/NativeHandle.h>
+#include <android/binder_enums.h>
+#include <android/binder_manager.h>
 #include <hidl/ServiceManagement.h>
 #include <hwbinder/IPCThreadState.h>
 #include <ui/Gralloc4.h>
@@ -27,16 +33,22 @@
 #include <sync/sync.h>
 #pragma clang diagnostic pop
 
+using aidl::android::hardware::graphics::allocator::AllocationError;
+using aidl::android::hardware::graphics::allocator::AllocationResult;
 using aidl::android::hardware::graphics::common::ExtendableType;
 using aidl::android::hardware::graphics::common::PlaneLayoutComponentType;
 using aidl::android::hardware::graphics::common::StandardMetadataType;
 using android::hardware::hidl_vec;
 using android::hardware::graphics::allocator::V4_0::IAllocator;
 using android::hardware::graphics::common::V1_2::BufferUsage;
+using android::hardware::graphics::common::V1_2::PixelFormat;
 using android::hardware::graphics::mapper::V4_0::BufferDescriptor;
 using android::hardware::graphics::mapper::V4_0::Error;
 using android::hardware::graphics::mapper::V4_0::IMapper;
+using AidlIAllocator = ::aidl::android::hardware::graphics::allocator::IAllocator;
+using AidlBufferUsage = ::aidl::android::hardware::graphics::common::BufferUsage;
 using AidlDataspace = ::aidl::android::hardware::graphics::common::Dataspace;
+using AidlNativeHandle = ::aidl::android::hardware::common::NativeHandle;
 using BufferDump = android::hardware::graphics::mapper::V4_0::IMapper::BufferDump;
 using MetadataDump = android::hardware::graphics::mapper::V4_0::IMapper::MetadataDump;
 using MetadataType = android::hardware::graphics::mapper::V4_0::IMapper::MetadataType;
@@ -48,6 +60,10 @@
 namespace {
 
 static constexpr Error kTransactionError = Error::NO_RESOURCES;
+static const auto kAidlAllocatorServiceName = AidlIAllocator::descriptor + std::string("/default");
+
+// TODO(b/72323293, b/72703005): Remove these invalid bits from callers
+static constexpr uint64_t kRemovedUsageBits = static_cast<uint64_t>((1 << 10) | (1 << 13));
 
 uint64_t getValidUsageBits() {
     static const uint64_t validUsageBits = []() -> uint64_t {
@@ -58,6 +74,17 @@
         }
         return bits;
     }();
+    return validUsageBits | kRemovedUsageBits;
+}
+
+uint64_t getValidUsageBits41() {
+    static const uint64_t validUsageBits = []() -> uint64_t {
+        uint64_t bits = 0;
+        for (const auto bit : ndk::enum_range<AidlBufferUsage>{}) {
+            bits |= static_cast<int64_t>(bit);
+        }
+        return bits;
+    }();
     return validUsageBits;
 }
 
@@ -69,9 +96,48 @@
     outRect.height = rect.height();
     return outRect;
 }
-static inline void sBufferDescriptorInfo(std::string name, uint32_t width, uint32_t height,
-                                         PixelFormat format, uint32_t layerCount, uint64_t usage,
-                                         IMapper::BufferDescriptorInfo* outDescriptorInfo) {
+
+// See if gralloc "4.1" is available.
+static bool hasIAllocatorAidl() {
+    // Avoid re-querying repeatedly for this information;
+    static bool sHasIAllocatorAidl = []() -> bool {
+        if (__builtin_available(android 31, *)) {
+            return AServiceManager_isDeclared(kAidlAllocatorServiceName.c_str());
+        }
+        return false;
+    }();
+    return sHasIAllocatorAidl;
+}
+
+// Determines whether the passed info is compatible with the mapper.
+static status_t validateBufferDescriptorInfo(IMapper::BufferDescriptorInfo* descriptorInfo) {
+    uint64_t validUsageBits = getValidUsageBits();
+    if (hasIAllocatorAidl()) {
+        validUsageBits |= getValidUsageBits41();
+    }
+
+    if (descriptorInfo->usage & ~validUsageBits) {
+        ALOGE("buffer descriptor contains invalid usage bits 0x%" PRIx64,
+              descriptorInfo->usage & ~validUsageBits);
+        return BAD_VALUE;
+    }
+
+    // Combinations that are only allowed with gralloc 4.1.
+    // Previous grallocs must be protected from this.
+    if (!hasIAllocatorAidl() &&
+            descriptorInfo->format != hardware::graphics::common::V1_2::PixelFormat::BLOB &&
+            descriptorInfo->usage & BufferUsage::GPU_DATA_BUFFER) {
+        ALOGE("non-BLOB pixel format with GPU_DATA_BUFFER usage is not supported prior to gralloc 4.1");
+        return BAD_VALUE;
+    }
+
+    return NO_ERROR;
+}
+
+static inline status_t sBufferDescriptorInfo(std::string name, uint32_t width, uint32_t height,
+                                             PixelFormat format, uint32_t layerCount,
+                                             uint64_t usage,
+                                             IMapper::BufferDescriptorInfo* outDescriptorInfo) {
     outDescriptorInfo->name = name;
     outDescriptorInfo->width = width;
     outDescriptorInfo->height = height;
@@ -79,6 +145,8 @@
     outDescriptorInfo->format = static_cast<hardware::graphics::common::V1_2::PixelFormat>(format);
     outDescriptorInfo->usage = usage;
     outDescriptorInfo->reservedSize = 0;
+
+    return validateBufferDescriptorInfo(outDescriptorInfo);
 }
 
 } // anonymous namespace
@@ -102,18 +170,6 @@
     return mMapper != nullptr;
 }
 
-status_t Gralloc4Mapper::validateBufferDescriptorInfo(
-        IMapper::BufferDescriptorInfo* descriptorInfo) const {
-    uint64_t validUsageBits = getValidUsageBits();
-
-    if (descriptorInfo->usage & ~validUsageBits) {
-        ALOGE("buffer descriptor contains invalid usage bits 0x%" PRIx64,
-              descriptorInfo->usage & ~validUsageBits);
-        return BAD_VALUE;
-    }
-    return NO_ERROR;
-}
-
 status_t Gralloc4Mapper::createDescriptor(void* bufferDescriptorInfo,
                                           void* outBufferDescriptor) const {
     IMapper::BufferDescriptorInfo* descriptorInfo =
@@ -166,8 +222,10 @@
                                             uint32_t layerCount, uint64_t usage,
                                             uint32_t stride) const {
     IMapper::BufferDescriptorInfo descriptorInfo;
-    sBufferDescriptorInfo("validateBufferSize", width, height, format, layerCount, usage,
-                          &descriptorInfo);
+    if (auto error = sBufferDescriptorInfo("validateBufferSize", width, height, format, layerCount,
+                                           usage, &descriptorInfo) != OK) {
+        return error;
+    }
 
     auto buffer = const_cast<native_handle_t*>(bufferHandle);
     auto ret = mMapper->validateBufferSize(buffer, descriptorInfo, stride);
@@ -386,7 +444,7 @@
             if (fd >= 0) {
                 releaseFence = fd;
             } else {
-                ALOGD("failed to dup unlock release fence");
+                ALOGW("failed to dup unlock release fence");
                 sync_wait(fenceHandle->data[0], -1);
             }
         }
@@ -407,7 +465,12 @@
                                      uint32_t layerCount, uint64_t usage,
                                      bool* outSupported) const {
     IMapper::BufferDescriptorInfo descriptorInfo;
-    sBufferDescriptorInfo("isSupported", width, height, format, layerCount, usage, &descriptorInfo);
+    if (auto error = sBufferDescriptorInfo("isSupported", width, height, format, layerCount, usage,
+                                           &descriptorInfo) != OK) {
+        // Usage isn't known to the HAL or otherwise failed validation.
+        *outSupported = false;
+        return OK;
+    }
 
     Error error;
     auto ret = mMapper->isSupported(descriptorInfo,
@@ -634,6 +697,12 @@
                outSmpte2094_40);
 }
 
+status_t Gralloc4Mapper::getSmpte2094_10(
+        buffer_handle_t bufferHandle, std::optional<std::vector<uint8_t>>* outSmpte2094_10) const {
+    return get(bufferHandle, gralloc4::MetadataType_Smpte2094_10, gralloc4::decodeSmpte2094_10,
+               outSmpte2094_10);
+}
+
 template <class T>
 status_t Gralloc4Mapper::getDefault(uint32_t width, uint32_t height, PixelFormat format,
                                     uint32_t layerCount, uint64_t usage,
@@ -644,7 +713,10 @@
     }
 
     IMapper::BufferDescriptorInfo descriptorInfo;
-    sBufferDescriptorInfo("getDefault", width, height, format, layerCount, usage, &descriptorInfo);
+    if (auto error = sBufferDescriptorInfo("getDefault", width, height, format, layerCount, usage,
+                                           &descriptorInfo) != OK) {
+        return error;
+    }
 
     hidl_vec<uint8_t> vec;
     Error error;
@@ -935,9 +1007,10 @@
     }
     double allocationSizeKiB = static_cast<double>(allocationSize) / 1024;
 
-    *outDump << "+ name:" << name << ", id:" << bufferId << ", size:" << allocationSizeKiB
-             << "KiB, w/h:" << width << "x" << height << ", usage: 0x" << std::hex << usage
-             << std::dec << ", req fmt:" << static_cast<int32_t>(pixelFormatRequested)
+    *outDump << "+ name:" << name << ", id:" << bufferId << ", size:" << std::fixed
+             << allocationSizeKiB << "KiB, w/h:" << width << "x" << height << ", usage: 0x"
+             << std::hex << usage << std::dec
+             << ", req fmt:" << static_cast<int32_t>(pixelFormatRequested)
              << ", fourcc/mod:" << pixelFormatFourCC << "/" << pixelFormatModifier
              << ", dataspace: 0x" << std::hex << static_cast<uint32_t>(dataspace) << std::dec
              << ", compressed: ";
@@ -1063,6 +1136,13 @@
         ALOGW("allocator 4.x is not supported");
         return;
     }
+    if (__builtin_available(android 31, *)) {
+        if (hasIAllocatorAidl()) {
+            mAidlAllocator = AidlIAllocator::fromBinder(ndk::SpAIBinder(
+                    AServiceManager_waitForService(kAidlAllocatorServiceName.c_str())));
+            ALOGE_IF(!mAidlAllocator, "AIDL IAllocator declared but failed to get service");
+        }
+    }
 }
 
 bool Gralloc4Allocator::isLoaded() const {
@@ -1078,7 +1158,10 @@
                                      uint64_t usage, uint32_t bufferCount, uint32_t* outStride,
                                      buffer_handle_t* outBufferHandles, bool importBuffers) const {
     IMapper::BufferDescriptorInfo descriptorInfo;
-    sBufferDescriptorInfo(requestorName, width, height, format, layerCount, usage, &descriptorInfo);
+    if (auto error = sBufferDescriptorInfo(requestorName, width, height, format, layerCount, usage,
+                                           &descriptorInfo) != OK) {
+        return error;
+    }
 
     BufferDescriptor descriptor;
     status_t error = mMapper.createDescriptor(static_cast<void*>(&descriptorInfo),
@@ -1087,6 +1170,52 @@
         return error;
     }
 
+    if (mAidlAllocator) {
+        AllocationResult result;
+        auto status = mAidlAllocator->allocate(descriptor, bufferCount, &result);
+        if (!status.isOk()) {
+            error = status.getExceptionCode();
+            if (error == EX_SERVICE_SPECIFIC) {
+                error = status.getServiceSpecificError();
+            }
+            if (error == OK) {
+                error = UNKNOWN_ERROR;
+            }
+        } else {
+            if (importBuffers) {
+                for (uint32_t i = 0; i < bufferCount; i++) {
+                    error = mMapper.importBuffer(makeFromAidl(result.buffers[i]),
+                                                 &outBufferHandles[i]);
+                    if (error != NO_ERROR) {
+                        for (uint32_t j = 0; j < i; j++) {
+                            mMapper.freeBuffer(outBufferHandles[j]);
+                            outBufferHandles[j] = nullptr;
+                        }
+                        break;
+                    }
+                }
+            } else {
+                for (uint32_t i = 0; i < bufferCount; i++) {
+                    outBufferHandles[i] = dupFromAidl(result.buffers[i]);
+                    if (!outBufferHandles[i]) {
+                        for (uint32_t j = 0; j < i; j++) {
+                            auto buffer = const_cast<native_handle_t*>(outBufferHandles[j]);
+                            native_handle_close(buffer);
+                            native_handle_delete(buffer);
+                            outBufferHandles[j] = nullptr;
+                        }
+                    }
+                }
+            }
+        }
+        *outStride = result.stride;
+        // Release all the resources held by AllocationResult (specifically any remaining FDs)
+        result = {};
+        // make sure the kernel driver sees BC_FREE_BUFFER and closes the fds now
+        hardware::IPCThreadState::self()->flushCommands();
+        return error;
+    }
+
     auto ret = mAllocator->allocate(descriptor, bufferCount,
                                     [&](const auto& tmpError, const auto& tmpStride,
                                         const auto& tmpBuffers) {
diff --git a/libs/ui/GraphicBufferMapper.cpp b/libs/ui/GraphicBufferMapper.cpp
index d20bd7a..82d6cd5 100644
--- a/libs/ui/GraphicBufferMapper.cpp
+++ b/libs/ui/GraphicBufferMapper.cpp
@@ -301,6 +301,11 @@
     return mMapper->getSmpte2094_40(bufferHandle, outSmpte2094_40);
 }
 
+status_t GraphicBufferMapper::getSmpte2094_10(
+        buffer_handle_t bufferHandle, std::optional<std::vector<uint8_t>>* outSmpte2094_10) {
+    return mMapper->getSmpte2094_10(bufferHandle, outSmpte2094_10);
+}
+
 status_t GraphicBufferMapper::getDefaultPixelFormatFourCC(uint32_t width, uint32_t height,
                                                           PixelFormat format, uint32_t layerCount,
                                                           uint64_t usage,
diff --git a/libs/ui/PixelFormat.cpp b/libs/ui/PixelFormat.cpp
index e88fdd5..799fbc9 100644
--- a/libs/ui/PixelFormat.cpp
+++ b/libs/ui/PixelFormat.cpp
@@ -35,25 +35,8 @@
         case PIXEL_FORMAT_RGBA_5551:
         case PIXEL_FORMAT_RGBA_4444:
             return 2;
-    }
-    return 0;
-}
-
-uint32_t bitsPerPixel(PixelFormat format) {
-    switch (format) {
-        case PIXEL_FORMAT_RGBA_FP16:
-            return 64;
-        case PIXEL_FORMAT_RGBA_8888:
-        case PIXEL_FORMAT_RGBX_8888:
-        case PIXEL_FORMAT_BGRA_8888:
-        case PIXEL_FORMAT_RGBA_1010102:
-            return 32;
-        case PIXEL_FORMAT_RGB_888:
-            return 24;
-        case PIXEL_FORMAT_RGB_565:
-        case PIXEL_FORMAT_RGBA_5551:
-        case PIXEL_FORMAT_RGBA_4444:
-            return 16;
+        case PIXEL_FORMAT_R_8:
+            return 1;
     }
     return 0;
 }
diff --git a/libs/ui/StaticAsserts.cpp b/libs/ui/StaticAsserts.cpp
new file mode 100644
index 0000000..85da64f
--- /dev/null
+++ b/libs/ui/StaticAsserts.cpp
@@ -0,0 +1,24 @@
+/*
+ * 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.
+ */
+
+#include <ui/PixelFormat.h>
+#include <aidl/android/hardware/graphics/common/PixelFormat.h>
+
+// Ideally, PIXEL_FORMAT_R_8 would simply be defined to match the aidl PixelFormat, but
+// PixelFormat.h (where PIXEL_FORMAT_R_8 is defined) is pulled in by builds for
+// which there is no aidl build (e.g. Windows).
+static_assert(android::PIXEL_FORMAT_R_8 ==static_cast<int32_t>(
+                                  aidl::android::hardware::graphics::common::PixelFormat::R_8));
diff --git a/libs/ui/StaticDisplayInfo.cpp b/libs/ui/StaticDisplayInfo.cpp
index b66b281..03d15e4 100644
--- a/libs/ui/StaticDisplayInfo.cpp
+++ b/libs/ui/StaticDisplayInfo.cpp
@@ -29,7 +29,8 @@
     return FlattenableHelpers::getFlattenedSize(connectionType) +
             FlattenableHelpers::getFlattenedSize(density) +
             FlattenableHelpers::getFlattenedSize(secure) +
-            FlattenableHelpers::getFlattenedSize(deviceProductInfo);
+            FlattenableHelpers::getFlattenedSize(deviceProductInfo) +
+            FlattenableHelpers::getFlattenedSize(installOrientation);
 }
 
 status_t StaticDisplayInfo::flatten(void* buffer, size_t size) const {
@@ -40,6 +41,7 @@
     RETURN_IF_ERROR(FlattenableHelpers::flatten(&buffer, &size, density));
     RETURN_IF_ERROR(FlattenableHelpers::flatten(&buffer, &size, secure));
     RETURN_IF_ERROR(FlattenableHelpers::flatten(&buffer, &size, deviceProductInfo));
+    RETURN_IF_ERROR(FlattenableHelpers::flatten(&buffer, &size, installOrientation));
     return OK;
 }
 
@@ -48,6 +50,7 @@
     RETURN_IF_ERROR(FlattenableHelpers::unflatten(&buffer, &size, &density));
     RETURN_IF_ERROR(FlattenableHelpers::unflatten(&buffer, &size, &secure));
     RETURN_IF_ERROR(FlattenableHelpers::unflatten(&buffer, &size, &deviceProductInfo));
+    RETURN_IF_ERROR(FlattenableHelpers::unflatten(&buffer, &size, &installOrientation));
     return OK;
 }
 
diff --git a/libs/ui/Transform.cpp b/libs/ui/Transform.cpp
index cd68c1c..42dd85e 100644
--- a/libs/ui/Transform.cpp
+++ b/libs/ui/Transform.cpp
@@ -134,6 +134,10 @@
     return mMatrix[1][1];
 }
 
+float Transform::det() const {
+    return mMatrix[0][0] * mMatrix[1][1] - mMatrix[0][1] * mMatrix[1][0];
+}
+
 float Transform::getScaleX() const {
     return sqrt((dsdx() * dsdx()) + (dtdx() * dtdx()));
 }
@@ -390,12 +394,17 @@
         const float x = M[2][0];
         const float y = M[2][1];
 
-        const float idet = 1.0f / (a*d - b*c);
+        const float idet = 1.0f / det();
         result.mMatrix[0][0] =  d*idet;
         result.mMatrix[0][1] = -c*idet;
         result.mMatrix[1][0] = -b*idet;
         result.mMatrix[1][1] =  a*idet;
         result.mType = mType;
+        if (getOrientation() & ROT_90) {
+            // Recalculate the type if there is a 90-degree rotation component, since the inverse
+            // of ROT_90 is ROT_270 and vice versa.
+            result.mType |= UNKNOWN_TYPE;
+        }
 
         vec2 T(-x, -y);
         T = result.transform(T);
diff --git a/libs/ui/include/ui/DisplayId.h b/libs/ui/include/ui/DisplayId.h
index f196ab9..9120972 100644
--- a/libs/ui/include/ui/DisplayId.h
+++ b/libs/ui/include/ui/DisplayId.h
@@ -38,12 +38,22 @@
 
     uint64_t value;
 
+    // For deserialization.
+    static constexpr std::optional<DisplayId> fromValue(uint64_t);
+
+    // As above, but also upcast to Id.
+    template <typename Id>
+    static constexpr std::optional<Id> fromValue(uint64_t value) {
+        if (const auto id = Id::tryCast(DisplayId(value))) {
+            return id;
+        }
+        return {};
+    }
+
 protected:
     explicit constexpr DisplayId(uint64_t id) : value(id) {}
 };
 
-static_assert(sizeof(DisplayId) == sizeof(uint64_t));
-
 inline bool operator==(DisplayId lhs, DisplayId rhs) {
     return lhs.value == rhs.value;
 }
@@ -80,11 +90,8 @@
 
     // TODO(b/162612135) Remove default constructor
     PhysicalDisplayId() = default;
-    // TODO(b/162612135) Remove constructor
-    explicit constexpr PhysicalDisplayId(uint64_t id) : DisplayId(id) {}
 
     constexpr uint16_t getManufacturerId() const { return static_cast<uint16_t>(value >> 40); }
-
     constexpr uint8_t getPort() const { return static_cast<uint8_t>(value); }
 
 private:
@@ -96,10 +103,9 @@
     explicit constexpr PhysicalDisplayId(DisplayId other) : DisplayId(other) {}
 };
 
-static_assert(sizeof(PhysicalDisplayId) == sizeof(uint64_t));
-
 struct VirtualDisplayId : DisplayId {
     using BaseId = uint32_t;
+
     // Flag indicating that this virtual display is backed by the GPU.
     static constexpr uint64_t FLAG_GPU = 1ULL << 61;
 
@@ -163,10 +169,23 @@
     explicit constexpr HalDisplayId(DisplayId other) : DisplayId(other) {}
 };
 
+constexpr std::optional<DisplayId> DisplayId::fromValue(uint64_t value) {
+    if (const auto id = fromValue<PhysicalDisplayId>(value)) {
+        return id;
+    }
+    if (const auto id = fromValue<VirtualDisplayId>(value)) {
+        return id;
+    }
+    return {};
+}
+
+static_assert(sizeof(DisplayId) == sizeof(uint64_t));
+static_assert(sizeof(HalDisplayId) == sizeof(uint64_t));
 static_assert(sizeof(VirtualDisplayId) == sizeof(uint64_t));
+
+static_assert(sizeof(PhysicalDisplayId) == sizeof(uint64_t));
 static_assert(sizeof(HalVirtualDisplayId) == sizeof(uint64_t));
 static_assert(sizeof(GpuVirtualDisplayId) == sizeof(uint64_t));
-static_assert(sizeof(HalDisplayId) == sizeof(uint64_t));
 
 } // namespace android
 
diff --git a/services/surfaceflinger/DisplayHardware/DisplayIdentification.h b/libs/ui/include/ui/DisplayIdentification.h
similarity index 93%
rename from services/surfaceflinger/DisplayHardware/DisplayIdentification.h
rename to libs/ui/include/ui/DisplayIdentification.h
index fbea4e5..fc9c0f4 100644
--- a/services/surfaceflinger/DisplayHardware/DisplayIdentification.h
+++ b/libs/ui/include/ui/DisplayIdentification.h
@@ -31,7 +31,6 @@
 
 namespace android {
 
-
 using DisplayIdentificationData = std::vector<uint8_t>;
 
 struct DisplayIdentificationInfo {
@@ -81,5 +80,7 @@
 
 PhysicalDisplayId getVirtualDisplayId(uint32_t id);
 
-} // namespace android
+// CityHash64 implementation that only hashes at most the first 16 characters of the given string.
+uint64_t cityHash64Len0To16(std::string_view sv);
 
+} // namespace android
diff --git a/libs/ui/include/ui/DisplayState.h b/libs/ui/include/ui/DisplayState.h
index 70a0d50..98ee356 100644
--- a/libs/ui/include/ui/DisplayState.h
+++ b/libs/ui/include/ui/DisplayState.h
@@ -16,21 +16,18 @@
 
 #pragma once
 
+#include <ui/LayerStack.h>
 #include <ui/Rotation.h>
 #include <ui/Size.h>
 
-#include <cstdint>
 #include <type_traits>
 
 namespace android::ui {
 
-using LayerStack = uint32_t;
-constexpr LayerStack NO_LAYER_STACK = static_cast<LayerStack>(-1);
-
 // Transactional state of physical or virtual display. Note that libgui defines
 // android::DisplayState as a superset of android::ui::DisplayState.
 struct DisplayState {
-    LayerStack layerStack = NO_LAYER_STACK;
+    LayerStack layerStack;
     Rotation orientation = ROTATION_0;
     Size layerStackSpaceRect;
 };
diff --git a/libs/ui/include/ui/DynamicDisplayInfo.h b/libs/ui/include/ui/DynamicDisplayInfo.h
index a4c2f71..ce75a65 100644
--- a/libs/ui/include/ui/DynamicDisplayInfo.h
+++ b/libs/ui/include/ui/DynamicDisplayInfo.h
@@ -35,7 +35,7 @@
     // This struct is going to be serialized over binder, so
     // we can't use size_t because it may have different width
     // in the client process.
-    int32_t activeDisplayModeId;
+    ui::DisplayModeId activeDisplayModeId;
 
     std::vector<ui::ColorMode> supportedColorModes;
     ui::ColorMode activeColorMode;
@@ -49,6 +49,9 @@
     // For more information, see the HDMI 1.4 specification.
     bool gameContentTypeSupported;
 
+    // The boot display mode preferred by the implementation.
+    ui::DisplayModeId preferredBootDisplayMode;
+
     std::optional<ui::DisplayMode> getActiveDisplayMode() const;
 
     bool isFixedSize() const { return false; }
diff --git a/libs/ui/include/ui/Fence.h b/libs/ui/include/ui/Fence.h
index 6efecd3..9aae145 100644
--- a/libs/ui/include/ui/Fence.h
+++ b/libs/ui/include/ui/Fence.h
@@ -26,6 +26,10 @@
 
 namespace android {
 
+namespace mock {
+class MockFence;
+}
+
 class String8;
 
 // ===========================================================================
@@ -109,7 +113,7 @@
     // fence transitioned to the signaled state.  If the fence is not signaled
     // then SIGNAL_TIME_PENDING is returned.  If the fence is invalid or if an
     // error occurs then SIGNAL_TIME_INVALID is returned.
-    nsecs_t getSignalTime() const;
+    virtual nsecs_t getSignalTime() const;
 
     enum class Status {
         Invalid,     // Fence is invalid
@@ -120,7 +124,7 @@
     // getStatus() returns whether the fence has signaled yet. Prefer this to
     // getSignalTime() or wait() if all you care about is whether the fence has
     // signaled.
-    inline Status getStatus() {
+    virtual inline Status getStatus() {
         // The sync_wait call underlying wait() has been measured to be
         // significantly faster than the sync_fence_info call underlying
         // getSignalTime(), which might otherwise appear to be the more obvious
@@ -144,7 +148,10 @@
 private:
     // Only allow instantiation using ref counting.
     friend class LightRefBase<Fence>;
-    ~Fence() = default;
+    virtual ~Fence() = default;
+
+    // Allow mocking for unit testing
+    friend class mock::MockFence;
 
     base::unique_fd mFenceFd;
 };
diff --git a/libs/ui/include/ui/Gralloc.h b/libs/ui/include/ui/Gralloc.h
index e199648..753b0a6 100644
--- a/libs/ui/include/ui/Gralloc.h
+++ b/libs/ui/include/ui/Gralloc.h
@@ -178,6 +178,11 @@
             std::optional<std::vector<uint8_t>>* /*outSmpte2094_40*/) const {
         return INVALID_OPERATION;
     }
+    virtual status_t getSmpte2094_10(
+            buffer_handle_t /*bufferHandle*/,
+            std::optional<std::vector<uint8_t>>* /*outSmpte2094_10*/) const {
+        return INVALID_OPERATION;
+    }
 
     virtual status_t getDefaultPixelFormatFourCC(uint32_t /*width*/, uint32_t /*height*/,
                                                  PixelFormat /*format*/, uint32_t /*layerCount*/,
diff --git a/libs/ui/include/ui/Gralloc4.h b/libs/ui/include/ui/Gralloc4.h
index 4729cba..fe38709 100644
--- a/libs/ui/include/ui/Gralloc4.h
+++ b/libs/ui/include/ui/Gralloc4.h
@@ -17,6 +17,7 @@
 #ifndef ANDROID_UI_GRALLOC4_H
 #define ANDROID_UI_GRALLOC4_H
 
+#include <aidl/android/hardware/graphics/allocator/IAllocator.h>
 #include <android/hardware/graphics/allocator/4.0/IAllocator.h>
 #include <android/hardware/graphics/common/1.1/types.h>
 #include <android/hardware/graphics/mapper/4.0/IMapper.h>
@@ -108,6 +109,8 @@
                          std::optional<ui::Cta861_3>* outCta861_3) const override;
     status_t getSmpte2094_40(buffer_handle_t bufferHandle,
                              std::optional<std::vector<uint8_t>>* outSmpte2094_40) const override;
+    status_t getSmpte2094_10(buffer_handle_t bufferHandle,
+                             std::optional<std::vector<uint8_t>>* outSmpte2094_10) const override;
 
     status_t getDefaultPixelFormatFourCC(uint32_t width, uint32_t height, PixelFormat format,
                                          uint32_t layerCount, uint64_t usage,
@@ -152,10 +155,6 @@
 private:
     friend class GraphicBufferAllocator;
 
-    // Determines whether the passed info is compatible with the mapper.
-    status_t validateBufferDescriptorInfo(
-            hardware::graphics::mapper::V4_0::IMapper::BufferDescriptorInfo* descriptorInfo) const;
-
     template <class T>
     using DecodeFunction = status_t (*)(const hardware::hidl_vec<uint8_t>& input, T* output);
 
@@ -202,6 +201,8 @@
 private:
     const Gralloc4Mapper& mMapper;
     sp<hardware::graphics::allocator::V4_0::IAllocator> mAllocator;
+    // Optional "4.1" allocator
+    std::shared_ptr<aidl::android::hardware::graphics::allocator::IAllocator> mAidlAllocator;
 };
 
 } // namespace android
diff --git a/libs/ui/include/ui/GraphicBufferMapper.h b/libs/ui/include/ui/GraphicBufferMapper.h
index 837e3d8..257c155 100644
--- a/libs/ui/include/ui/GraphicBufferMapper.h
+++ b/libs/ui/include/ui/GraphicBufferMapper.h
@@ -126,6 +126,8 @@
     status_t getCta861_3(buffer_handle_t bufferHandle, std::optional<ui::Cta861_3>* outCta861_3);
     status_t getSmpte2094_40(buffer_handle_t bufferHandle,
                              std::optional<std::vector<uint8_t>>* outSmpte2094_40);
+    status_t getSmpte2094_10(buffer_handle_t bufferHandle,
+                             std::optional<std::vector<uint8_t>>* outSmpte2094_10);
 
     /**
      * Gets the default metadata for a gralloc buffer allocated with the given parameters.
diff --git a/libs/ui/include/ui/LayerStack.h b/libs/ui/include/ui/LayerStack.h
new file mode 100644
index 0000000..d6ffeb7
--- /dev/null
+++ b/libs/ui/include/ui/LayerStack.h
@@ -0,0 +1,78 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <cstdint>
+
+#include <ftl/cast.h>
+#include <ftl/string.h>
+#include <log/log.h>
+
+namespace android::ui {
+
+// A LayerStack identifies a Z-ordered group of layers. A layer can only be associated to a single
+// LayerStack, but a LayerStack can be associated to multiple displays, mirroring the same content.
+struct LayerStack {
+    uint32_t id = UINT32_MAX;
+
+    template <typename T>
+    static constexpr LayerStack fromValue(T v) {
+        if (ftl::cast_safety<uint32_t>(v) == ftl::CastSafety::kSafe) {
+            return {static_cast<uint32_t>(v)};
+        }
+
+        ALOGW("Invalid layer stack %s", ftl::to_string(v).c_str());
+        return {};
+    }
+};
+
+constexpr LayerStack INVALID_LAYER_STACK;
+constexpr LayerStack DEFAULT_LAYER_STACK{0u};
+
+inline bool operator==(LayerStack lhs, LayerStack rhs) {
+    return lhs.id == rhs.id;
+}
+
+inline bool operator!=(LayerStack lhs, LayerStack rhs) {
+    return !(lhs == rhs);
+}
+
+inline bool operator>(LayerStack lhs, LayerStack rhs) {
+    return lhs.id > rhs.id;
+}
+
+// A LayerFilter determines if a layer is included for output to a display.
+struct LayerFilter {
+    LayerStack layerStack;
+
+    // True if the layer is only output to internal displays, i.e. excluded from screenshots, screen
+    // recordings, and mirroring to virtual or external displays. Used for display cutout overlays.
+    bool toInternalDisplay = false;
+
+    // Returns true if the input filter can be output to this filter.
+    bool includes(LayerFilter other) const {
+        // The layer stacks must match.
+        if (other.layerStack == INVALID_LAYER_STACK || other.layerStack != layerStack) {
+            return false;
+        }
+
+        // The output must be to an internal display if the input filter has that constraint.
+        return !other.toInternalDisplay || toInternalDisplay;
+    }
+};
+
+} // namespace android::ui
diff --git a/libs/ui/include/ui/PixelFormat.h b/libs/ui/include/ui/PixelFormat.h
index 02773d9..f422ce4 100644
--- a/libs/ui/include/ui/PixelFormat.h
+++ b/libs/ui/include/ui/PixelFormat.h
@@ -62,12 +62,12 @@
     PIXEL_FORMAT_RGBA_4444    = 7,                             // 16-bit ARGB
     PIXEL_FORMAT_RGBA_FP16    = HAL_PIXEL_FORMAT_RGBA_FP16,    // 64-bit RGBA
     PIXEL_FORMAT_RGBA_1010102 = HAL_PIXEL_FORMAT_RGBA_1010102, // 32-bit RGBA
+    PIXEL_FORMAT_R_8          = 0x38,
 };
 
 typedef int32_t PixelFormat;
 
 uint32_t bytesPerPixel(PixelFormat format);
-uint32_t bitsPerPixel(PixelFormat format);
 
 }; // namespace android
 
diff --git a/libs/ui/include/ui/Size.h b/libs/ui/include/ui/Size.h
index f1e8252..ecc192d 100644
--- a/libs/ui/include/ui/Size.h
+++ b/libs/ui/include/ui/Size.h
@@ -23,103 +23,70 @@
 #include <type_traits>
 #include <utility>
 
-namespace android {
-namespace ui {
+namespace android::ui {
 
-// Forward declare a few things.
-struct Size;
-bool operator==(const Size& lhs, const Size& rhs);
-
-/**
- * A simple value type representing a two-dimensional size
- */
+// A simple value type representing a two-dimensional size.
 struct Size {
-    int32_t width;
-    int32_t height;
+    int32_t width = -1;
+    int32_t height = -1;
 
-    // Special values
-    static const Size INVALID;
-    static const Size EMPTY;
+    constexpr Size() = default;
 
-    // ------------------------------------------------------------------------
-    // Construction
-    // ------------------------------------------------------------------------
-
-    Size() : Size(INVALID) {}
     template <typename T>
-    Size(T&& w, T&& h)
-          : width(Size::clamp<int32_t, T>(std::forward<T>(w))),
-            height(Size::clamp<int32_t, T>(std::forward<T>(h))) {}
-
-    // ------------------------------------------------------------------------
-    // Accessors
-    // ------------------------------------------------------------------------
+    constexpr Size(T w, T h) : width(clamp<int32_t>(w)), height(clamp<int32_t>(h)) {}
 
     int32_t getWidth() const { return width; }
     int32_t getHeight() const { return height; }
 
-    template <typename T>
-    void setWidth(T&& v) {
-        width = Size::clamp<int32_t, T>(std::forward<T>(v));
-    }
-    template <typename T>
-    void setHeight(T&& v) {
-        height = Size::clamp<int32_t, T>(std::forward<T>(v));
-    }
-
-    // ------------------------------------------------------------------------
-    // Assignment
-    // ------------------------------------------------------------------------
-
-    void set(const Size& size) { *this = size; }
-    template <typename T>
-    void set(T&& w, T&& h) {
-        set(Size(std::forward<T>(w), std::forward<T>(h)));
-    }
-
-    // Sets the value to INVALID
-    void makeInvalid() { set(INVALID); }
-
-    // Sets the value to EMPTY
-    void clear() { set(EMPTY); }
-
-    // ------------------------------------------------------------------------
-    // Semantic checks
-    // ------------------------------------------------------------------------
-
     // Valid means non-negative width and height
     bool isValid() const { return width >= 0 && height >= 0; }
 
     // Empty means zero width and height
-    bool isEmpty() const { return *this == EMPTY; }
+    bool isEmpty() const;
 
-    // ------------------------------------------------------------------------
-    // Clamp Helpers
-    // ------------------------------------------------------------------------
-
-    // Note: We use only features available in C++11 here for compatibility with
-    // external targets which include this file directly or indirectly and which
-    // themselves use C++11.
-
-    // C++11 compatible replacement for std::remove_cv_reference_t [C++20]
     template <typename T>
-    using remove_cv_reference_t =
-            typename std::remove_cv<typename std::remove_reference<T>::type>::type;
+    void setWidth(T v) {
+        width = clamp<int32_t>(v);
+    }
+
+    template <typename T>
+    void setHeight(T v) {
+        height = clamp<int32_t>(v);
+    }
+
+    void set(Size size) { *this = size; }
+
+    template <typename T>
+    void set(T w, T h) {
+        set(Size(w, h));
+    }
+
+    // Sets the value to kInvalidSize
+    void makeInvalid();
+
+    // Sets the value to kEmptySize
+    void clear();
+
+    // TODO: Replace with std::remove_cvref_t in C++20.
+    template <typename T>
+    using remove_cvref_t = std::remove_cv_t<std::remove_reference_t<T>>;
 
     // Takes a value of type FromType, and ensures it can be represented as a value of type ToType,
     // clamping the input value to the output range if necessary.
     template <typename ToType, typename FromType>
-    static Size::remove_cv_reference_t<ToType>
-    clamp(typename std::enable_if<
-            std::numeric_limits<Size::remove_cv_reference_t<ToType>>::is_specialized &&
-                    std::numeric_limits<Size::remove_cv_reference_t<FromType>>::is_specialized,
-            FromType>::type v) {
-        using BareToType = remove_cv_reference_t<ToType>;
-        using BareFromType = remove_cv_reference_t<FromType>;
-        static constexpr auto toHighest = std::numeric_limits<BareToType>::max();
-        static constexpr auto toLowest = std::numeric_limits<BareToType>::lowest();
-        static constexpr auto fromHighest = std::numeric_limits<BareFromType>::max();
-        static constexpr auto fromLowest = std::numeric_limits<BareFromType>::lowest();
+    static constexpr remove_cvref_t<ToType> clamp(FromType v) {
+        using BareToType = remove_cvref_t<ToType>;
+        using ToLimits = std::numeric_limits<BareToType>;
+
+        using BareFromType = remove_cvref_t<FromType>;
+        using FromLimits = std::numeric_limits<BareFromType>;
+
+        static_assert(ToLimits::is_specialized && FromLimits::is_specialized);
+
+        constexpr auto toHighest = ToLimits::max();
+        constexpr auto toLowest = ToLimits::lowest();
+        constexpr auto fromHighest = FromLimits::max();
+        constexpr auto fromLowest = FromLimits::lowest();
 
         // Get the closest representation of [toLowest, toHighest] in type
         // FromType to use to clamp the input value before conversion.
@@ -127,37 +94,35 @@
         // std::common_type<...> is used to get a value-preserving type for the
         // top end of the range.
         using CommonHighestType = std::common_type_t<BareToType, BareFromType>;
+        using CommonLimits = std::numeric_limits<CommonHighestType>;
 
         // std::make_signed<std::common_type<...>> is used to get a
         // value-preserving type for the bottom end of the range, except this is
         // a bit trickier for non-integer types like float.
-        using CommonLowestType =
-                std::conditional_t<std::numeric_limits<CommonHighestType>::is_integer,
-                                   std::make_signed_t<std::conditional_t<
-                                           std::numeric_limits<CommonHighestType>::is_integer,
-                                           CommonHighestType, int /* not used */>>,
-                                   CommonHighestType>;
+        using CommonLowestType = std::conditional_t<
+                CommonLimits::is_integer,
+                std::make_signed_t<std::conditional_t<CommonLimits::is_integer, CommonHighestType,
+                                                      int /* not used */>>,
+                CommonHighestType>;
 
         // We can then compute the clamp range in a way that can be later
         // trivially converted to either the 'from' or 'to' types, and be
-        // representabile in either.
-        static constexpr auto commonClampHighest =
-                std::min(static_cast<CommonHighestType>(fromHighest),
-                         static_cast<CommonHighestType>(toHighest));
-        static constexpr auto commonClampLowest =
-                std::max(static_cast<CommonLowestType>(fromLowest),
-                         static_cast<CommonLowestType>(toLowest));
+        // representable in either.
+        constexpr auto commonClampHighest = std::min(static_cast<CommonHighestType>(fromHighest),
+                                                     static_cast<CommonHighestType>(toHighest));
+        constexpr auto commonClampLowest = std::max(static_cast<CommonLowestType>(fromLowest),
+                                                    static_cast<CommonLowestType>(toLowest));
 
-        static constexpr auto fromClampHighest = static_cast<BareFromType>(commonClampHighest);
-        static constexpr auto fromClampLowest = static_cast<BareFromType>(commonClampLowest);
+        constexpr auto fromClampHighest = static_cast<BareFromType>(commonClampHighest);
+        constexpr auto fromClampLowest = static_cast<BareFromType>(commonClampLowest);
 
         // A clamp is needed only if the range we are clamping to is not the
         // same as the range of the input.
-        static constexpr bool isClampNeeded =
+        constexpr bool isClampNeeded =
                 (fromLowest != fromClampLowest) || (fromHighest != fromClampHighest);
 
         // If a clamp is not needed, the conversion is just a trivial cast.
-        if (!isClampNeeded) {
+        if constexpr (!isClampNeeded) {
             return static_cast<BareToType>(v);
         }
 
@@ -170,34 +135,46 @@
 
         // Otherwise clamping is done by using the already computed endpoints
         // for each type.
-        return (v <= fromClampLowest)
-                ? toClampLowest
-                : ((v >= fromClampHighest) ? toClampHighest : static_cast<BareToType>(v));
+        if (v <= fromClampLowest) {
+            return toClampLowest;
+        }
+
+        return v >= fromClampHighest ? toClampHighest : static_cast<BareToType>(v);
     }
 };
 
-// ------------------------------------------------------------------------
-// Comparisons
-// ------------------------------------------------------------------------
+constexpr Size kInvalidSize;
+constexpr Size kEmptySize{0, 0};
 
-inline bool operator==(const Size& lhs, const Size& rhs) {
+inline void Size::makeInvalid() {
+    set(kInvalidSize);
+}
+
+inline void Size::clear() {
+    set(kEmptySize);
+}
+
+inline bool operator==(Size lhs, Size rhs) {
     return lhs.width == rhs.width && lhs.height == rhs.height;
 }
 
-inline bool operator!=(const Size& lhs, const Size& rhs) {
-    return !operator==(lhs, rhs);
+inline bool Size::isEmpty() const {
+    return *this == kEmptySize;
 }
 
-inline bool operator<(const Size& lhs, const Size& rhs) {
+inline bool operator!=(Size lhs, Size rhs) {
+    return !(lhs == rhs);
+}
+
+inline bool operator<(Size lhs, Size rhs) {
     // Orders by increasing width, then height.
     if (lhs.width != rhs.width) return lhs.width < rhs.width;
     return lhs.height < rhs.height;
 }
 
 // Defining PrintTo helps with Google Tests.
-static inline void PrintTo(const Size& size, ::std::ostream* os) {
-    *os << "Size(" << size.width << ", " << size.height << ")";
+inline void PrintTo(Size size, std::ostream* stream) {
+    *stream << "Size(" << size.width << ", " << size.height << ')';
 }
 
-} // namespace ui
-} // namespace android
+} // namespace android::ui
diff --git a/libs/ui/include/ui/StaticDisplayInfo.h b/libs/ui/include/ui/StaticDisplayInfo.h
index e86ca29..cc7c869 100644
--- a/libs/ui/include/ui/StaticDisplayInfo.h
+++ b/libs/ui/include/ui/StaticDisplayInfo.h
@@ -19,6 +19,7 @@
 #include <optional>
 
 #include <ui/DeviceProductInfo.h>
+#include <ui/Rotation.h>
 #include <utils/Flattenable.h>
 
 namespace android::ui {
@@ -31,6 +32,7 @@
     float density = 0.f;
     bool secure = false;
     std::optional<DeviceProductInfo> deviceProductInfo;
+    Rotation installOrientation = ROTATION_0;
 
     bool isFixedSize() const { return false; }
     size_t getFlattenedSize() const;
diff --git a/libs/ui/include/ui/StretchEffect.h b/libs/ui/include/ui/StretchEffect.h
index cf08acb..123b275 100644
--- a/libs/ui/include/ui/StretchEffect.h
+++ b/libs/ui/include/ui/StretchEffect.h
@@ -44,6 +44,8 @@
         mappedChildBounds == other.mappedChildBounds;
   }
 
+  bool operator!=(const StretchEffect& other) const { return !(*this == other); }
+
   static bool isZero(float value) {
     constexpr float NON_ZERO_EPSILON = 0.001f;
     return fabsf(value) <= NON_ZERO_EPSILON;
diff --git a/libs/ui/include/ui/Transform.h b/libs/ui/include/ui/Transform.h
index 33fbe05..f1178ca 100644
--- a/libs/ui/include/ui/Transform.h
+++ b/libs/ui/include/ui/Transform.h
@@ -77,6 +77,7 @@
     float dtdx() const;
     float dtdy() const;
     float dsdy() const;
+    float det() const;
 
     float getScaleX() const;
     float getScaleY() const;
diff --git a/libs/ui/include/ui/fuzzer/FuzzableDataspaces.h b/libs/ui/include/ui/fuzzer/FuzzableDataspaces.h
new file mode 100644
index 0000000..4200d6a
--- /dev/null
+++ b/libs/ui/include/ui/fuzzer/FuzzableDataspaces.h
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <ui/GraphicTypes.h>
+using namespace android;
+
+constexpr ui::Dataspace kDataspaces[] = {
+        ui::Dataspace::UNKNOWN,
+        ui::Dataspace::ARBITRARY,
+        ui::Dataspace::STANDARD_UNSPECIFIED,
+        ui::Dataspace::STANDARD_BT709,
+        ui::Dataspace::STANDARD_BT601_625,
+        ui::Dataspace::STANDARD_BT601_625_UNADJUSTED,
+        ui::Dataspace::STANDARD_BT601_525,
+        ui::Dataspace::STANDARD_BT601_525_UNADJUSTED,
+        ui::Dataspace::STANDARD_BT2020,
+        ui::Dataspace::STANDARD_BT2020_CONSTANT_LUMINANCE,
+        ui::Dataspace::STANDARD_BT470M,
+        ui::Dataspace::STANDARD_FILM,
+        ui::Dataspace::STANDARD_DCI_P3,
+        ui::Dataspace::STANDARD_ADOBE_RGB,
+        ui::Dataspace::TRANSFER_UNSPECIFIED,
+        ui::Dataspace::TRANSFER_LINEAR,
+        ui::Dataspace::TRANSFER_SRGB,
+        ui::Dataspace::TRANSFER_SMPTE_170M,
+        ui::Dataspace::TRANSFER_GAMMA2_2,
+        ui::Dataspace::TRANSFER_GAMMA2_6,
+        ui::Dataspace::TRANSFER_GAMMA2_8,
+        ui::Dataspace::TRANSFER_ST2084,
+        ui::Dataspace::TRANSFER_HLG,
+        ui::Dataspace::RANGE_UNSPECIFIED,
+        ui::Dataspace::RANGE_FULL,
+        ui::Dataspace::RANGE_LIMITED,
+        ui::Dataspace::RANGE_EXTENDED,
+        ui::Dataspace::SRGB_LINEAR,
+        ui::Dataspace::V0_SRGB_LINEAR,
+        ui::Dataspace::V0_SCRGB_LINEAR,
+        ui::Dataspace::SRGB,
+        ui::Dataspace::V0_SRGB,
+        ui::Dataspace::V0_SCRGB,
+        ui::Dataspace::JFIF,
+        ui::Dataspace::V0_JFIF,
+        ui::Dataspace::BT601_625,
+        ui::Dataspace::V0_BT601_625,
+        ui::Dataspace::BT601_525,
+        ui::Dataspace::V0_BT601_525,
+        ui::Dataspace::BT709,
+        ui::Dataspace::V0_BT709,
+        ui::Dataspace::DCI_P3_LINEAR,
+        ui::Dataspace::DCI_P3,
+        ui::Dataspace::DISPLAY_P3_LINEAR,
+        ui::Dataspace::DISPLAY_P3,
+        ui::Dataspace::ADOBE_RGB,
+        ui::Dataspace::BT2020_LINEAR,
+        ui::Dataspace::BT2020,
+        ui::Dataspace::BT2020_PQ,
+        ui::Dataspace::DEPTH,
+        ui::Dataspace::SENSOR,
+        ui::Dataspace::BT2020_ITU,
+        ui::Dataspace::BT2020_ITU_PQ,
+        ui::Dataspace::BT2020_ITU_HLG,
+        ui::Dataspace::BT2020_HLG,
+        ui::Dataspace::DISPLAY_BT2020,
+        ui::Dataspace::DYNAMIC_DEPTH,
+        ui::Dataspace::JPEG_APP_SEGMENTS,
+        ui::Dataspace::HEIF,
+};
diff --git a/libs/ui/include_mock/ui/MockFence.h b/libs/ui/include_mock/ui/MockFence.h
new file mode 100644
index 0000000..71adee4
--- /dev/null
+++ b/libs/ui/include_mock/ui/MockFence.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <gmock/gmock.h>
+#include <ui/Fence.h>
+
+namespace android::mock {
+
+class MockFence : public android::Fence {
+public:
+    MockFence() = default;
+    virtual ~MockFence() = default;
+
+    MOCK_METHOD(nsecs_t, getSignalTime, (), (const, override));
+    MOCK_METHOD(Status, getStatus, (), (override));
+};
+
+}; // namespace android::mock
diff --git a/services/surfaceflinger/DisplayHardware/Hash.h b/libs/ui/include_types/ui/DataspaceUtils.h
similarity index 72%
copy from services/surfaceflinger/DisplayHardware/Hash.h
copy to libs/ui/include_types/ui/DataspaceUtils.h
index a7b6c71..a461cb4 100644
--- a/services/surfaceflinger/DisplayHardware/Hash.h
+++ b/libs/ui/include_types/ui/DataspaceUtils.h
@@ -16,12 +16,14 @@
 
 #pragma once
 
-#include <cstdint>
-#include <string_view>
+#include <ui/GraphicTypes.h>
 
 namespace android {
 
-// CityHash64 implementation that only hashes at most the first 16 characters of the given string.
-uint64_t cityHash64Len0To16(std::string_view sv);
+inline bool isHdrDataspace(ui::Dataspace dataspace) {
+    const auto transfer = dataspace & HAL_DATASPACE_TRANSFER_MASK;
+
+    return transfer == HAL_DATASPACE_TRANSFER_ST2084 || transfer == HAL_DATASPACE_TRANSFER_HLG;
+}
 
 } // namespace android
\ No newline at end of file
diff --git a/libs/ui/include_types/ui/GraphicTypes.h b/libs/ui/include_types/ui/GraphicTypes.h
new file mode 120000
index 0000000..b1859e0
--- /dev/null
+++ b/libs/ui/include_types/ui/GraphicTypes.h
@@ -0,0 +1 @@
+../../include/ui/GraphicTypes.h
\ No newline at end of file
diff --git a/libs/ui/tests/Android.bp b/libs/ui/tests/Android.bp
index 516aad8..22fbf45 100644
--- a/libs/ui/tests/Android.bp
+++ b/libs/ui/tests/Android.bp
@@ -27,28 +27,40 @@
     name: "Region_test",
     shared_libs: ["libui"],
     srcs: ["Region_test.cpp"],
-    cflags: ["-Wall", "-Werror"],
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
 }
 
 cc_test {
     name: "colorspace_test",
     shared_libs: ["libui"],
     srcs: ["colorspace_test.cpp"],
-    cflags: ["-Wall", "-Werror"],
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
 }
 
 cc_test {
     name: "DisplayId_test",
     shared_libs: ["libui"],
     srcs: ["DisplayId_test.cpp"],
-    cflags: ["-Wall", "-Werror"],
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
 }
 
 cc_test {
     name: "FlattenableHelpers_test",
     shared_libs: ["libui"],
     srcs: ["FlattenableHelpers_test.cpp"],
-    cflags: ["-Wall", "-Werror"],
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
 }
 
 cc_test {
@@ -68,7 +80,10 @@
         "GraphicBufferAllocator_test.cpp",
         "mock/MockGrallocAllocator.cpp",
     ],
-    cflags: ["-Wall", "-Werror"],
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
 }
 
 cc_test {
@@ -83,14 +98,20 @@
         "libutils",
     ],
     srcs: ["GraphicBuffer_test.cpp"],
-    cflags: ["-Wall", "-Werror"],
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
 }
 
 // This test has a main method, and requires a separate binary to be built.
 cc_test {
     name: "GraphicBufferOverBinder_test",
     srcs: ["GraphicBufferOverBinder_test.cpp"],
-    cflags: ["-Wall", "-Werror"],
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
     shared_libs: [
         "libbinder",
         "libgui",
@@ -105,7 +126,10 @@
     test_suites: ["device-tests"],
     shared_libs: ["libui"],
     srcs: ["Rect_test.cpp"],
-    cflags: ["-Wall", "-Werror"],
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
 }
 
 cc_test {
@@ -113,5 +137,39 @@
     test_suites: ["device-tests"],
     shared_libs: ["libui"],
     srcs: ["Size_test.cpp"],
-    cflags: ["-Wall", "-Werror"],
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
+}
+
+cc_test {
+    name: "MockFence_test",
+    shared_libs: ["libui"],
+    static_libs: ["libgmock"],
+    srcs: ["MockFence_test.cpp"],
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
+}
+
+cc_test {
+    name: "Transform_test",
+    shared_libs: ["libui"],
+    srcs: ["Transform_test.cpp"],
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
+}
+
+cc_test {
+    name: "DataspaceUtils_test",
+    shared_libs: ["libui"],
+    srcs: ["DataspaceUtils_test.cpp"],
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
 }
diff --git a/libs/ui/tests/DataspaceUtils_test.cpp b/libs/ui/tests/DataspaceUtils_test.cpp
new file mode 100644
index 0000000..3e09671
--- /dev/null
+++ b/libs/ui/tests/DataspaceUtils_test.cpp
@@ -0,0 +1,53 @@
+/*
+ * 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.
+ */
+
+#undef LOG_TAG
+#define LOG_TAG "DataspaceUtilsTest"
+
+#include <gtest/gtest.h>
+#include <ui/DataspaceUtils.h>
+
+namespace android {
+
+class DataspaceUtilsTest : public testing::Test {};
+
+TEST_F(DataspaceUtilsTest, isHdrDataspace) {
+    EXPECT_TRUE(isHdrDataspace(ui::Dataspace::BT2020_ITU_HLG));
+    EXPECT_TRUE(isHdrDataspace(ui::Dataspace::BT2020_ITU_PQ));
+    EXPECT_TRUE(isHdrDataspace(ui::Dataspace::BT2020_PQ));
+    EXPECT_TRUE(isHdrDataspace(ui::Dataspace::BT2020_HLG));
+
+    EXPECT_FALSE(isHdrDataspace(ui::Dataspace::V0_SRGB_LINEAR));
+    // scRGB defines a very wide gamut but not an expanded luminance range
+    EXPECT_FALSE(isHdrDataspace(ui::Dataspace::V0_SCRGB_LINEAR));
+    EXPECT_FALSE(isHdrDataspace(ui::Dataspace::V0_SRGB));
+    EXPECT_FALSE(isHdrDataspace(ui::Dataspace::V0_SCRGB));
+    EXPECT_FALSE(isHdrDataspace(ui::Dataspace::V0_JFIF));
+    EXPECT_FALSE(isHdrDataspace(ui::Dataspace::V0_BT601_625));
+    EXPECT_FALSE(isHdrDataspace(ui::Dataspace::V0_BT601_525));
+    EXPECT_FALSE(isHdrDataspace(ui::Dataspace::V0_BT709));
+    EXPECT_FALSE(isHdrDataspace(ui::Dataspace::DCI_P3_LINEAR));
+    EXPECT_FALSE(isHdrDataspace(ui::Dataspace::DCI_P3));
+    EXPECT_FALSE(isHdrDataspace(ui::Dataspace::DISPLAY_P3_LINEAR));
+    EXPECT_FALSE(isHdrDataspace(ui::Dataspace::DISPLAY_P3));
+    EXPECT_FALSE(isHdrDataspace(ui::Dataspace::ADOBE_RGB));
+    EXPECT_FALSE(isHdrDataspace(ui::Dataspace::BT2020_LINEAR));
+    EXPECT_FALSE(isHdrDataspace(ui::Dataspace::BT2020));
+    EXPECT_FALSE(isHdrDataspace(ui::Dataspace::BT2020_ITU));
+    EXPECT_FALSE(isHdrDataspace(ui::Dataspace::DISPLAY_BT2020));
+}
+
+} // namespace android
diff --git a/libs/ui/tests/DisplayId_test.cpp b/libs/ui/tests/DisplayId_test.cpp
index 1d908b8..8ddee7e 100644
--- a/libs/ui/tests/DisplayId_test.cpp
+++ b/libs/ui/tests/DisplayId_test.cpp
@@ -32,6 +32,9 @@
     EXPECT_FALSE(GpuVirtualDisplayId::tryCast(id));
     EXPECT_TRUE(PhysicalDisplayId::tryCast(id));
     EXPECT_TRUE(HalDisplayId::tryCast(id));
+
+    EXPECT_EQ(id, DisplayId::fromValue(id.value));
+    EXPECT_EQ(id, DisplayId::fromValue<PhysicalDisplayId>(id.value));
 }
 
 TEST(DisplayIdTest, createPhysicalIdFromPort) {
@@ -43,6 +46,9 @@
     EXPECT_FALSE(GpuVirtualDisplayId::tryCast(id));
     EXPECT_TRUE(PhysicalDisplayId::tryCast(id));
     EXPECT_TRUE(HalDisplayId::tryCast(id));
+
+    EXPECT_EQ(id, DisplayId::fromValue(id.value));
+    EXPECT_EQ(id, DisplayId::fromValue<PhysicalDisplayId>(id.value));
 }
 
 TEST(DisplayIdTest, createGpuVirtualId) {
@@ -52,6 +58,9 @@
     EXPECT_FALSE(HalVirtualDisplayId::tryCast(id));
     EXPECT_FALSE(PhysicalDisplayId::tryCast(id));
     EXPECT_FALSE(HalDisplayId::tryCast(id));
+
+    EXPECT_EQ(id, DisplayId::fromValue(id.value));
+    EXPECT_EQ(id, DisplayId::fromValue<GpuVirtualDisplayId>(id.value));
 }
 
 TEST(DisplayIdTest, createHalVirtualId) {
@@ -61,6 +70,9 @@
     EXPECT_FALSE(GpuVirtualDisplayId::tryCast(id));
     EXPECT_FALSE(PhysicalDisplayId::tryCast(id));
     EXPECT_TRUE(HalDisplayId::tryCast(id));
+
+    EXPECT_EQ(id, DisplayId::fromValue(id.value));
+    EXPECT_EQ(id, DisplayId::fromValue<HalVirtualDisplayId>(id.value));
 }
 
 } // namespace android::ui
diff --git a/services/surfaceflinger/tests/unittests/DisplayIdentificationTest.cpp b/libs/ui/tests/DisplayIdentification_test.cpp
similarity index 98%
rename from services/surfaceflinger/tests/unittests/DisplayIdentificationTest.cpp
rename to libs/ui/tests/DisplayIdentification_test.cpp
index cd4a5c9..736979a 100644
--- a/services/surfaceflinger/tests/unittests/DisplayIdentificationTest.cpp
+++ b/libs/ui/tests/DisplayIdentification_test.cpp
@@ -24,12 +24,12 @@
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
 
-#include "DisplayHardware/DisplayIdentification.h"
-#include "DisplayHardware/Hash.h"
+#include <ui/DisplayIdentification.h>
 
 using ::testing::ElementsAre;
 
 namespace android {
+
 namespace {
 
 const unsigned char kInternalEdid[] =
diff --git a/libs/ui/tests/MockFence_test.cpp b/libs/ui/tests/MockFence_test.cpp
new file mode 100644
index 0000000..40dddc3
--- /dev/null
+++ b/libs/ui/tests/MockFence_test.cpp
@@ -0,0 +1,57 @@
+/*
+ * 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.
+ */
+
+#include <ui/MockFence.h>
+
+#include <gtest/gtest.h>
+
+namespace android::ui {
+
+using testing::Return;
+
+class MockFenceTest : public testing::Test {
+public:
+    sp<Fence> getFenceForTesting() const { return mMockFence; }
+
+    const mock::MockFence& getMockFence() const { return *mMockFence; }
+
+private:
+    sp<mock::MockFence> mMockFence = sp<mock::MockFence>::make();
+};
+
+TEST_F(MockFenceTest, getSignalTime) {
+    sp<Fence> fence = getFenceForTesting();
+
+    EXPECT_CALL(getMockFence(), getSignalTime).WillOnce(Return(Fence::SIGNAL_TIME_PENDING));
+    EXPECT_EQ(Fence::SIGNAL_TIME_PENDING, fence->getSignalTime());
+
+    EXPECT_CALL(getMockFence(), getSignalTime).WillOnce(Return(1234));
+    EXPECT_EQ(1234, fence->getSignalTime());
+}
+
+TEST_F(MockFenceTest, getStatus) {
+    sp<Fence> fence = getFenceForTesting();
+
+    EXPECT_CALL(getMockFence(), getStatus).WillOnce(Return(Fence::Status::Unsignaled));
+    EXPECT_EQ(Fence::Status::Unsignaled, fence->getStatus());
+
+    EXPECT_CALL(getMockFence(), getStatus).WillOnce(Return(Fence::Status::Signaled));
+    EXPECT_EQ(Fence::Status::Signaled, fence->getStatus());
+
+    EXPECT_CALL(getMockFence(), getStatus).WillOnce(Return(Fence::Status::Invalid));
+    EXPECT_EQ(Fence::Status::Invalid, fence->getStatus());
+}
+} // namespace android::ui
diff --git a/libs/ui/tests/Size_test.cpp b/libs/ui/tests/Size_test.cpp
index 5f75aea..acef47f 100644
--- a/libs/ui/tests/Size_test.cpp
+++ b/libs/ui/tests/Size_test.cpp
@@ -93,9 +93,8 @@
     }
 
     {
-        const auto& s = Size::INVALID;
-        EXPECT_FALSE(s.isValid());
-        EXPECT_FALSE(s.isEmpty());
+        EXPECT_FALSE(kInvalidSize.isValid());
+        EXPECT_FALSE(kInvalidSize.isEmpty());
     }
 
     {
@@ -112,9 +111,8 @@
     }
 
     {
-        const auto& s = Size::EMPTY;
-        EXPECT_TRUE(s.isValid());
-        EXPECT_TRUE(s.isEmpty());
+        EXPECT_TRUE(kEmptySize.isValid());
+        EXPECT_TRUE(kEmptySize.isEmpty());
     }
 
     {
diff --git a/libs/ui/tests/Transform_test.cpp b/libs/ui/tests/Transform_test.cpp
new file mode 100644
index 0000000..6964284
--- /dev/null
+++ b/libs/ui/tests/Transform_test.cpp
@@ -0,0 +1,49 @@
+/*
+ * 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.
+ */
+
+#include <ui/Transform.h>
+
+#include <gtest/gtest.h>
+
+namespace android::ui {
+
+TEST(TransformTest, inverseRotation_hasCorrectType) {
+    const auto testRotationFlagsForInverse = [](Transform::RotationFlags rotation,
+                                                Transform::RotationFlags expectedInverse,
+                                                bool isRotation) {
+        const Transform t(rotation, 0, 0);
+        EXPECT_EQ(t.getOrientation(), rotation);
+        const Transform inverse = t.inverse();
+        EXPECT_EQ(inverse.getOrientation(), expectedInverse);
+
+        if (isRotation) {
+            EXPECT_TRUE(t.getType() & Transform::ROTATE);
+            EXPECT_TRUE(inverse.getType() & Transform::ROTATE);
+        } else {
+            EXPECT_FALSE(t.getType() & Transform::ROTATE);
+            EXPECT_FALSE(inverse.getType() & Transform::ROTATE);
+        }
+    };
+
+    testRotationFlagsForInverse(Transform::ROT_0, Transform::ROT_0, false);
+    testRotationFlagsForInverse(Transform::ROT_90, Transform::ROT_270, true);
+    testRotationFlagsForInverse(Transform::ROT_180, Transform::ROT_180, true);
+    testRotationFlagsForInverse(Transform::ROT_270, Transform::ROT_90, true);
+    testRotationFlagsForInverse(Transform::FLIP_H, Transform::FLIP_H, false);
+    testRotationFlagsForInverse(Transform::FLIP_V, Transform::FLIP_V, false);
+}
+
+} // namespace android::ui
diff --git a/libs/vr/libvrflinger/Android.bp b/libs/vr/libvrflinger/Android.bp
deleted file mode 100644
index bf848af..0000000
--- a/libs/vr/libvrflinger/Android.bp
+++ /dev/null
@@ -1,107 +0,0 @@
-// Copyright (C) 2008 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 = [
-    "acquired_buffer.cpp",
-    "epoll_event_dispatcher.cpp",
-    "display_manager_service.cpp",
-    "display_service.cpp",
-    "display_surface.cpp",
-    "hardware_composer.cpp",
-    "vr_flinger.cpp",
-]
-
-includeFiles = [ "include" ]
-
-staticLibraries = [
-    "libdisplay",
-    "libdvrcommon",
-    "libperformance",
-    "libvrsensor",
-    "libbroadcastring",
-    "libvr_manager",
-    "libbroadcastring",
-]
-
-sharedLibraries = [
-    "android.frameworks.vr.composer@2.0",
-    "android.hardware.graphics.allocator@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.4",
-    "libbinder",
-    "libbase",
-    "libbufferhubqueue",
-    "libcutils",
-    "liblog",
-    "libhardware",
-    "libnativewindow",
-    "libprocessgroup",
-    "libutils",
-    "libEGL",
-    "libGLESv1_CM",
-    "libGLESv2",
-    "libvulkan",
-    "libui",
-    "libgui",
-    "libsync",
-    "libhidlbase",
-    "libfmq",
-    "libpdx_default_transport",
-]
-
-headerLibraries = [
-    "android.hardware.graphics.composer@2.1-command-buffer",
-    "android.hardware.graphics.composer@2.2-command-buffer",
-    "android.hardware.graphics.composer@2.3-command-buffer",
-    "android.hardware.graphics.composer@2.4-command-buffer",
-    "libdvr_headers",
-    "libsurfaceflinger_headers",
-]
-
-cc_library_static {
-    srcs: sourceFiles,
-    export_include_dirs: includeFiles,
-
-    clang: true,
-    cflags: [
-        "-DLOG_TAG=\"vr_flinger\"",
-        "-DTRACE=0",
-        "-DATRACE_TAG=ATRACE_TAG_GRAPHICS",
-        "-DGL_GLEXT_PROTOTYPES",
-        "-DEGL_EGLEXT_PROTOTYPES",
-        "-Wall",
-        "-Werror",
-        "-Wno-error=sign-compare", // to fix later
-        "-Wno-unused-variable",
-    ],
-    shared_libs: sharedLibraries,
-    whole_static_libs: staticLibraries,
-    header_libs: headerLibraries,
-    name: "libvrflinger",
-}
-
-subdirs = [
-    "tests",
-]
diff --git a/libs/vr/libvrflinger/acquired_buffer.cpp b/libs/vr/libvrflinger/acquired_buffer.cpp
deleted file mode 100644
index c360dee..0000000
--- a/libs/vr/libvrflinger/acquired_buffer.cpp
+++ /dev/null
@@ -1,103 +0,0 @@
-#include "acquired_buffer.h"
-
-#include <log/log.h>
-#include <sync/sync.h>
-
-using android::pdx::LocalHandle;
-
-namespace android {
-namespace dvr {
-
-AcquiredBuffer::AcquiredBuffer(const std::shared_ptr<ConsumerBuffer>& buffer,
-                               LocalHandle acquire_fence, std::size_t slot)
-    : buffer_(buffer), acquire_fence_(std::move(acquire_fence)), slot_(slot) {}
-
-AcquiredBuffer::AcquiredBuffer(const std::shared_ptr<ConsumerBuffer>& buffer,
-                               int* error) {
-  LocalHandle fence;
-  const int ret = buffer->Acquire(&fence);
-
-  if (error)
-    *error = ret;
-
-  if (ret < 0) {
-    ALOGW("AcquiredBuffer::AcquiredBuffer: Failed to acquire buffer: %s",
-          strerror(-ret));
-    buffer_ = nullptr;
-    // Default construct sets acquire_fence_ to empty.
-  } else {
-    buffer_ = buffer;
-    acquire_fence_ = std::move(fence);
-  }
-}
-
-AcquiredBuffer::AcquiredBuffer(AcquiredBuffer&& other) noexcept {
-  *this = std::move(other);
-}
-
-AcquiredBuffer::~AcquiredBuffer() { Release(LocalHandle(kEmptyFence)); }
-
-AcquiredBuffer& AcquiredBuffer::operator=(AcquiredBuffer&& other) noexcept {
-  if (this != &other) {
-    Release();
-
-    using std::swap;
-    swap(buffer_, other.buffer_);
-    swap(acquire_fence_, other.acquire_fence_);
-    swap(slot_, other.slot_);
-  }
-  return *this;
-}
-
-bool AcquiredBuffer::IsAvailable() const {
-  if (IsEmpty())
-    return false;
-
-  // Only check the fence if the acquire fence is not empty.
-  if (acquire_fence_) {
-    const int ret = sync_wait(acquire_fence_.Get(), 0);
-    ALOGD_IF(TRACE || (ret < 0 && errno != ETIME),
-             "AcquiredBuffer::IsAvailable: buffer_id=%d acquire_fence=%d "
-             "sync_wait()=%d errno=%d.",
-             buffer_->id(), acquire_fence_.Get(), ret, ret < 0 ? errno : 0);
-    if (ret == 0) {
-      // The fence is completed, so to avoid further calls to sync_wait we close
-      // it here.
-      acquire_fence_.Close();
-    }
-    return ret == 0;
-  } else {
-    return true;
-  }
-}
-
-LocalHandle AcquiredBuffer::ClaimAcquireFence() {
-  return std::move(acquire_fence_);
-}
-
-std::shared_ptr<ConsumerBuffer> AcquiredBuffer::ClaimBuffer() {
-  return std::move(buffer_);
-}
-
-int AcquiredBuffer::Release(LocalHandle release_fence) {
-  ALOGD_IF(TRACE, "AcquiredBuffer::Release: buffer_id=%d release_fence=%d",
-           buffer_ ? buffer_->id() : -1, release_fence.Get());
-  if (buffer_) {
-    const int ret = buffer_->ReleaseAsync();
-    if (ret < 0) {
-      ALOGE("AcquiredBuffer::Release: Failed to release buffer %d: %s",
-            buffer_->id(), strerror(-ret));
-      if (ret != -ESHUTDOWN)
-        return ret;
-    }
-
-    buffer_ = nullptr;
-  }
-
-  acquire_fence_.Close();
-  slot_ = 0;
-  return 0;
-}
-
-}  // namespace dvr
-}  // namespace android
diff --git a/libs/vr/libvrflinger/acquired_buffer.h b/libs/vr/libvrflinger/acquired_buffer.h
deleted file mode 100644
index 7643e75..0000000
--- a/libs/vr/libvrflinger/acquired_buffer.h
+++ /dev/null
@@ -1,87 +0,0 @@
-#ifndef ANDROID_DVR_SERVICES_DISPLAYD_ACQUIRED_BUFFER_H_
-#define ANDROID_DVR_SERVICES_DISPLAYD_ACQUIRED_BUFFER_H_
-
-#include <pdx/file_handle.h>
-#include <private/dvr/consumer_buffer.h>
-
-#include <memory>
-
-namespace android {
-namespace dvr {
-
-// Manages the ACQUIRE/RELEASE ownership cycle of a ConsumerBuffer.
-class AcquiredBuffer {
- public:
-  static constexpr int kEmptyFence = pdx::LocalHandle::kEmptyFileHandle;
-
-  AcquiredBuffer() : buffer_(nullptr), acquire_fence_(kEmptyFence) {}
-
-  // Constructs an AcquiredBuffer from a ConsumerBuffer pointer and an acquire
-  // fence. The ConsumerBuffer MUST be in the ACQUIRED state prior to calling
-  // this constructor; the constructor does not attempt to ACQUIRE the buffer
-  // itself.
-  AcquiredBuffer(const std::shared_ptr<ConsumerBuffer>& buffer,
-                 pdx::LocalHandle acquire_fence, std::size_t slot = 0);
-
-  // Constructs an AcquiredBuffer from a ConsumerBuffer. The ConsumerBuffer MUST
-  // be in the POSTED state prior to calling this constructor, as this
-  // constructor attempts to ACQUIRE the buffer. If ACQUIRING the buffer fails
-  // this instance is left in the empty state. An optional error code is
-  // returned in |error|, which may be nullptr if not needed.
-  AcquiredBuffer(const std::shared_ptr<ConsumerBuffer>& buffer, int* error);
-
-  // Move constructor. Behaves similarly to the move assignment operator below.
-  AcquiredBuffer(AcquiredBuffer&& other) noexcept;
-
-  ~AcquiredBuffer();
-
-  // Move assignment operator. Moves the ConsumerBuffer and acquire fence from
-  // |other| into this instance after RELEASING the current ConsumerBuffer and
-  // closing the acquire fence. After the move |other| is left in the empty
-  // state.
-  AcquiredBuffer& operator=(AcquiredBuffer&& other) noexcept;
-
-  // Accessors for the underlying ConsumerBuffer, the acquire fence, and the
-  // use-case specific sequence value from the acquisition (see
-  // private/dvr/consumer_buffer.h).
-  std::shared_ptr<ConsumerBuffer> buffer() const { return buffer_; }
-  int acquire_fence() const { return acquire_fence_.Get(); }
-
-  // When non-empty, returns true if the acquired fence was signaled (or if the
-  // fence is empty). Returns false when empty or if the fence is not signaled.
-  bool IsAvailable() const;
-
-  bool IsEmpty() const { return buffer_ == nullptr; }
-
-  // Returns the acquire fence, passing ownership to the caller.
-  pdx::LocalHandle ClaimAcquireFence();
-
-  // Returns the buffer, passing ownership to the caller. Caller is responsible
-  // for calling Release on the returned buffer.
-  std::shared_ptr<ConsumerBuffer> ClaimBuffer();
-
-  // Releases the ConsumerBuffer, passing the release fence in |release_fence|
-  // to the producer. On success, the ConsumerBuffer and acquire fence are set
-  // to empty state; if release fails, the ConsumerBuffer and acquire fence are
-  // left in place and a negative error code is returned.
-  int Release(pdx::LocalHandle release_fence = {});
-
-  // Returns the slot in the queue this buffer belongs to. Buffers that are not
-  // part of a queue return 0.
-  std::size_t slot() const { return slot_; }
-
- private:
-  std::shared_ptr<ConsumerBuffer> buffer_;
-  // Mutable so that the fence can be closed when it is determined to be
-  // signaled during IsAvailable().
-  mutable pdx::LocalHandle acquire_fence_;
-  std::size_t slot_{0};
-
-  AcquiredBuffer(const AcquiredBuffer&) = delete;
-  void operator=(const AcquiredBuffer&) = delete;
-};
-
-}  // namespace dvr
-}  // namespace android
-
-#endif  // ANDROID_DVR_SERVICES_DISPLAYD_ACQUIRED_BUFFER_H_
diff --git a/libs/vr/libvrflinger/display_manager_service.cpp b/libs/vr/libvrflinger/display_manager_service.cpp
deleted file mode 100644
index 34b3b0a..0000000
--- a/libs/vr/libvrflinger/display_manager_service.cpp
+++ /dev/null
@@ -1,142 +0,0 @@
-#include "display_manager_service.h"
-
-#include <pdx/channel_handle.h>
-#include <pdx/default_transport/service_endpoint.h>
-#include <private/android_filesystem_config.h>
-#include <private/dvr/display_protocol.h>
-#include <private/dvr/trusted_uids.h>
-#include <sys/poll.h>
-
-#include <array>
-
-using android::dvr::display::DisplayManagerProtocol;
-using android::pdx::Channel;
-using android::pdx::LocalChannelHandle;
-using android::pdx::Message;
-using android::pdx::default_transport::Endpoint;
-using android::pdx::ErrorStatus;
-using android::pdx::rpc::DispatchRemoteMethod;
-using android::pdx::rpc::IfAnyOf;
-using android::pdx::rpc::RemoteMethodError;
-
-namespace android {
-namespace dvr {
-
-void DisplayManager::SetNotificationsPending(bool pending) {
-  auto status = service_->ModifyChannelEvents(channel_id_, pending ? 0 : POLLIN,
-                                              pending ? POLLIN : 0);
-  ALOGE_IF(!status,
-           "DisplayManager::SetNotificationPending: Failed to modify channel "
-           "events: %s",
-           status.GetErrorMessage().c_str());
-}
-
-DisplayManagerService::DisplayManagerService(
-    const std::shared_ptr<DisplayService>& display_service)
-    : BASE("DisplayManagerService",
-           Endpoint::Create(DisplayManagerProtocol::kClientPath)),
-      display_service_(display_service) {
-  display_service_->SetDisplayConfigurationUpdateNotifier(
-      std::bind(&DisplayManagerService::OnDisplaySurfaceChange, this));
-}
-
-std::shared_ptr<pdx::Channel> DisplayManagerService::OnChannelOpen(
-    pdx::Message& message) {
-  const int user_id = message.GetEffectiveUserId();
-  const bool trusted = user_id == AID_ROOT || IsTrustedUid(user_id);
-
-  // Check if the display_manager_ has a defunct channel.
-  if (display_manager_ && !HasChannelId(display_manager_->channel_id())) {
-    ALOGE("DisplayManagerService::OnChannelOpen: Found defunct channel %d with "
-          "no OnChannelClose, clearing prior display manager.",
-          display_manager_->channel_id());
-    display_manager_ = nullptr;
-  }
-
-  // Prevent more than one display manager from registering at a time or
-  // untrusted UIDs from connecting.
-  if (display_manager_ || !trusted) {
-    RemoteMethodError(message, EPERM);
-    return nullptr;
-  }
-
-  display_manager_ =
-      std::make_shared<DisplayManager>(this, message.GetChannelId());
-  return display_manager_;
-}
-
-void DisplayManagerService::OnChannelClose(
-    pdx::Message& /*message*/, const std::shared_ptr<pdx::Channel>& channel) {
-  // Unregister the display manager when the channel closes.
-  if (display_manager_ == channel)
-    display_manager_ = nullptr;
-}
-
-pdx::Status<void> DisplayManagerService::HandleMessage(pdx::Message& message) {
-  ATRACE_NAME("DisplayManagerService::HandleMessage");
-  auto channel = std::static_pointer_cast<DisplayManager>(message.GetChannel());
-
-  switch (message.GetOp()) {
-    case DisplayManagerProtocol::GetSurfaceState::Opcode:
-      DispatchRemoteMethod<DisplayManagerProtocol::GetSurfaceState>(
-          *this, &DisplayManagerService::OnGetSurfaceState, message);
-      return {};
-
-    case DisplayManagerProtocol::GetSurfaceQueue::Opcode:
-      DispatchRemoteMethod<DisplayManagerProtocol::GetSurfaceQueue>(
-          *this, &DisplayManagerService::OnGetSurfaceQueue, message);
-      return {};
-
-    default:
-      return Service::DefaultHandleMessage(message);
-  }
-}
-
-pdx::Status<std::vector<display::SurfaceState>>
-DisplayManagerService::OnGetSurfaceState(pdx::Message& /*message*/) {
-  std::vector<display::SurfaceState> items;
-
-  display_service_->ForEachDisplaySurface(
-      SurfaceType::Application,
-      [&items](const std::shared_ptr<DisplaySurface>& surface) mutable {
-        items.push_back({surface->surface_id(), surface->process_id(),
-                         surface->user_id(), surface->attributes(),
-                         surface->update_flags(), surface->GetQueueIds()});
-        surface->ClearUpdate();
-      });
-
-  // The fact that we're in the message handler implies that display_manager_ is
-  // not nullptr. No check required, unless this service becomes multi-threaded.
-  display_manager_->SetNotificationsPending(false);
-  return items;
-}
-
-pdx::Status<pdx::LocalChannelHandle> DisplayManagerService::OnGetSurfaceQueue(
-    pdx::Message& /*message*/, int surface_id, int queue_id) {
-  auto surface = display_service_->GetDisplaySurface(surface_id);
-  if (!surface || surface->surface_type() != SurfaceType::Application)
-    return ErrorStatus(EINVAL);
-
-  auto queue =
-      std::static_pointer_cast<ApplicationDisplaySurface>(surface)->GetQueue(
-          queue_id);
-  if (!queue)
-    return ErrorStatus(EINVAL);
-
-  auto status = queue->CreateConsumerQueueHandle();
-  ALOGE_IF(
-      !status,
-      "DisplayManagerService::OnGetSurfaceQueue: Failed to create consumer "
-      "queue for queue_id=%d: %s",
-      queue->id(), status.GetErrorMessage().c_str());
-
-  return status;
-}
-
-void DisplayManagerService::OnDisplaySurfaceChange() {
-  if (display_manager_)
-    display_manager_->SetNotificationsPending(true);
-}
-
-}  // namespace dvr
-}  // namespace android
diff --git a/libs/vr/libvrflinger/display_manager_service.h b/libs/vr/libvrflinger/display_manager_service.h
deleted file mode 100644
index 3133fe1..0000000
--- a/libs/vr/libvrflinger/display_manager_service.h
+++ /dev/null
@@ -1,74 +0,0 @@
-#ifndef ANDROID_DVR_SERVICES_VRFLINGER_DISPLAY_MANAGER_SERVICE_H_
-#define ANDROID_DVR_SERVICES_VRFLINGER_DISPLAY_MANAGER_SERVICE_H_
-
-#include <pdx/service.h>
-#include <pdx/status.h>
-#include <private/dvr/display_protocol.h>
-
-#include "display_service.h"
-
-namespace android {
-namespace dvr {
-
-class DisplayManagerService;
-
-// The display manager is a client of the display manager service. This class
-// represents the connected client that the display manager service sends
-// notifications to.
-class DisplayManager : public pdx::Channel {
- public:
-  DisplayManager(DisplayManagerService* service, int channel_id)
-      : service_(service), channel_id_(channel_id) {}
-
-  int channel_id() const { return channel_id_; }
-
-  // Sets or clears the channel event mask to indicate pending events that the
-  // display manager on the other end of the channel should read and handle.
-  // When |pending| is true the POLLIN bit is set in the event mask; when
-  // |pending| is false the POLLIN bit is cleared in the event mask.
-  void SetNotificationsPending(bool pending);
-
- private:
-  DisplayManager(const DisplayManager&) = delete;
-  void operator=(const DisplayManager&) = delete;
-
-  DisplayManagerService* service_;
-  int channel_id_;
-};
-
-// The display manager service marshalls state and events from the display
-// service to the display manager.
-class DisplayManagerService : public pdx::ServiceBase<DisplayManagerService> {
- public:
-  std::shared_ptr<pdx::Channel> OnChannelOpen(pdx::Message& message) override;
-  void OnChannelClose(pdx::Message& message,
-                      const std::shared_ptr<pdx::Channel>& channel) override;
-  pdx::Status<void> HandleMessage(pdx::Message& message) override;
-
- private:
-  friend BASE;
-
-  explicit DisplayManagerService(
-      const std::shared_ptr<DisplayService>& display_service);
-
-  pdx::Status<std::vector<display::SurfaceState>> OnGetSurfaceState(
-      pdx::Message& message);
-  pdx::Status<pdx::LocalChannelHandle> OnGetSurfaceQueue(pdx::Message& message,
-                                                         int surface_id,
-                                                         int queue_id);
-
-  // Called by the display service to indicate changes to display surfaces that
-  // the display manager should evaluate.
-  void OnDisplaySurfaceChange();
-
-  DisplayManagerService(const DisplayManagerService&) = delete;
-  void operator=(const DisplayManagerService&) = delete;
-
-  std::shared_ptr<DisplayService> display_service_;
-  std::shared_ptr<DisplayManager> display_manager_;
-};
-
-}  // namespace dvr
-}  // namespace android
-
-#endif  // ANDROID_DVR_SERVICES_VRFLINGER_DISPLAY_MANAGER_SERVICE_H_
diff --git a/libs/vr/libvrflinger/display_service.cpp b/libs/vr/libvrflinger/display_service.cpp
deleted file mode 100644
index 582fed3..0000000
--- a/libs/vr/libvrflinger/display_service.cpp
+++ /dev/null
@@ -1,437 +0,0 @@
-#include "display_service.h"
-
-#include <unistd.h>
-
-#include <algorithm>
-#include <sstream>
-#include <string>
-#include <vector>
-
-#include <android-base/file.h>
-#include <android-base/properties.h>
-#include <dvr/dvr_display_types.h>
-#include <pdx/default_transport/service_endpoint.h>
-#include <pdx/rpc/remote_method.h>
-#include <private/android_filesystem_config.h>
-#include <private/dvr/display_protocol.h>
-#include <private/dvr/numeric.h>
-#include <private/dvr/trusted_uids.h>
-#include <private/dvr/types.h>
-
-#include "DisplayHardware/DisplayIdentification.h"
-
-using android::dvr::display::DisplayProtocol;
-using android::pdx::Channel;
-using android::pdx::ErrorStatus;
-using android::pdx::Message;
-using android::pdx::Status;
-using android::pdx::default_transport::Endpoint;
-using android::pdx::rpc::DispatchRemoteMethod;
-
-namespace {
-
-const char kDvrLensMetricsProperty[] = "ro.dvr.lens_metrics";
-const char kDvrDeviceMetricsProperty[] = "ro.dvr.device_metrics";
-const char kDvrDeviceConfigProperty[] = "ro.dvr.device_configuration";
-
-}  // namespace
-
-namespace android {
-namespace dvr {
-
-DisplayService::DisplayService(Hwc2::Composer* hidl,
-                               hwc2_display_t primary_display_id,
-                               RequestDisplayCallback request_display_callback)
-    : BASE("DisplayService",
-           Endpoint::Create(display::DisplayProtocol::kClientPath)) {
-    hardware_composer_.Initialize(
-        hidl, primary_display_id, request_display_callback);
-}
-
-bool DisplayService::IsInitialized() const {
-  return BASE::IsInitialized() && hardware_composer_.IsInitialized();
-}
-
-std::string DisplayService::DumpState(size_t /*max_length*/) {
-  std::ostringstream stream;
-
-  auto surfaces = GetDisplaySurfaces();
-  std::sort(surfaces.begin(), surfaces.end(), [](const auto& a, const auto& b) {
-    return a->surface_id() < b->surface_id();
-  });
-
-  stream << "Application Surfaces:" << std::endl;
-
-  size_t count = 0;
-  for (const auto& surface : surfaces) {
-    if (surface->surface_type() == SurfaceType::Application) {
-      stream << "Surface " << count++ << ":";
-      stream << " surface_id=" << surface->surface_id()
-             << " process_id=" << surface->process_id()
-             << " user_id=" << surface->user_id()
-             << " visible=" << surface->visible()
-             << " z_order=" << surface->z_order();
-
-      stream << " queue_ids=";
-      auto queue_ids = surface->GetQueueIds();
-      std::sort(queue_ids.begin(), queue_ids.end());
-      for (int32_t id : queue_ids) {
-        if (id != queue_ids[0])
-          stream << ",";
-        stream << id;
-      }
-      stream << std::endl;
-    }
-  }
-  stream << std::endl;
-
-  stream << "Direct Surfaces:" << std::endl;
-
-  count = 0;
-  for (const auto& surface : surfaces) {
-    if (surface->surface_type() == SurfaceType::Direct) {
-      stream << "Surface " << count++ << ":";
-      stream << " surface_id=" << surface->surface_id()
-             << " process_id=" << surface->process_id()
-             << " user_id=" << surface->user_id()
-             << " visible=" << surface->visible()
-             << " z_order=" << surface->z_order();
-
-      stream << " queue_ids=";
-      auto queue_ids = surface->GetQueueIds();
-      std::sort(queue_ids.begin(), queue_ids.end());
-      for (int32_t id : queue_ids) {
-        if (id != queue_ids[0])
-          stream << ",";
-        stream << id;
-      }
-      stream << std::endl;
-    }
-  }
-  stream << std::endl;
-
-  stream << hardware_composer_.Dump();
-  return stream.str();
-}
-
-void DisplayService::OnChannelClose(pdx::Message& message,
-                                    const std::shared_ptr<Channel>& channel) {
-  if (auto surface = std::static_pointer_cast<DisplaySurface>(channel)) {
-    surface->OnSetAttributes(message,
-                             {{display::SurfaceAttribute::Visible,
-                               display::SurfaceAttributeValue{false}}});
-  }
-}
-
-// First-level dispatch for display service messages. Directly handles messages
-// that are independent of the display surface (metrics, creation) and routes
-// surface-specific messages to the per-instance handlers.
-Status<void> DisplayService::HandleMessage(pdx::Message& message) {
-  ALOGD_IF(TRACE, "DisplayService::HandleMessage: opcode=%d", message.GetOp());
-  ATRACE_NAME("DisplayService::HandleMessage");
-
-  switch (message.GetOp()) {
-    case DisplayProtocol::GetMetrics::Opcode:
-      DispatchRemoteMethod<DisplayProtocol::GetMetrics>(
-          *this, &DisplayService::OnGetMetrics, message);
-      return {};
-
-    case DisplayProtocol::GetConfigurationData::Opcode:
-      DispatchRemoteMethod<DisplayProtocol::GetConfigurationData>(
-          *this, &DisplayService::OnGetConfigurationData, message);
-      return {};
-
-    case DisplayProtocol::GetDisplayIdentificationPort::Opcode:
-      DispatchRemoteMethod<DisplayProtocol::GetDisplayIdentificationPort>(
-          *this, &DisplayService::OnGetDisplayIdentificationPort, message);
-      return {};
-
-    case DisplayProtocol::CreateSurface::Opcode:
-      DispatchRemoteMethod<DisplayProtocol::CreateSurface>(
-          *this, &DisplayService::OnCreateSurface, message);
-      return {};
-
-    case DisplayProtocol::SetupGlobalBuffer::Opcode:
-      DispatchRemoteMethod<DisplayProtocol::SetupGlobalBuffer>(
-          *this, &DisplayService::OnSetupGlobalBuffer, message);
-      return {};
-
-    case DisplayProtocol::DeleteGlobalBuffer::Opcode:
-      DispatchRemoteMethod<DisplayProtocol::DeleteGlobalBuffer>(
-          *this, &DisplayService::OnDeleteGlobalBuffer, message);
-      return {};
-
-    case DisplayProtocol::GetGlobalBuffer::Opcode:
-      DispatchRemoteMethod<DisplayProtocol::GetGlobalBuffer>(
-          *this, &DisplayService::OnGetGlobalBuffer, message);
-      return {};
-
-    case DisplayProtocol::IsVrAppRunning::Opcode:
-      DispatchRemoteMethod<DisplayProtocol::IsVrAppRunning>(
-          *this, &DisplayService::IsVrAppRunning, message);
-      return {};
-
-    // Direct the surface specific messages to the surface instance.
-    case DisplayProtocol::SetAttributes::Opcode:
-    case DisplayProtocol::CreateQueue::Opcode:
-    case DisplayProtocol::GetSurfaceInfo::Opcode:
-      return HandleSurfaceMessage(message);
-
-    default:
-      return Service::HandleMessage(message);
-  }
-}
-
-Status<display::Metrics> DisplayService::OnGetMetrics(
-    pdx::Message& /*message*/) {
-  const auto& params = hardware_composer_.GetPrimaryDisplayParams();
-  return {{static_cast<uint32_t>(params.width),
-           static_cast<uint32_t>(params.height),
-           static_cast<uint32_t>(params.dpi.x),
-           static_cast<uint32_t>(params.dpi.y),
-           static_cast<uint32_t>(params.vsync_period_ns),
-           0,
-           0,
-           0,
-           0.0,
-           {},
-           {}}};
-}
-
-pdx::Status<std::string> DisplayService::OnGetConfigurationData(
-    pdx::Message& /*message*/, display::ConfigFileType config_type) {
-  std::string property_name;
-  DisplayIdentificationData display_identification_data;
-  switch (config_type) {
-    case display::ConfigFileType::kLensMetrics:
-      property_name = kDvrLensMetricsProperty;
-      break;
-    case display::ConfigFileType::kDeviceMetrics:
-      property_name = kDvrDeviceMetricsProperty;
-      break;
-    case display::ConfigFileType::kDeviceConfiguration:
-      property_name = kDvrDeviceConfigProperty;
-      break;
-    case display::ConfigFileType::kDeviceEdid:
-      display_identification_data =
-          hardware_composer_.GetCurrentDisplayIdentificationData();
-      if (display_identification_data.size() == 0) {
-        return ErrorStatus(ENOENT);
-      }
-      return std::string(display_identification_data.begin(),
-                         display_identification_data.end());
-    default:
-      return ErrorStatus(EINVAL);
-  }
-  std::string file_path = base::GetProperty(property_name, "");
-  if (file_path.empty()) {
-    return ErrorStatus(ENOENT);
-  }
-
-  std::string data;
-  if (!base::ReadFileToString(file_path, &data)) {
-    return ErrorStatus(errno);
-  }
-
-  return std::move(data);
-}
-
-pdx::Status<uint8_t> DisplayService::OnGetDisplayIdentificationPort(
-    pdx::Message& /*message*/) {
-  return hardware_composer_.GetCurrentDisplayPort();
-}
-
-// Creates a new DisplaySurface and associates it with this channel. This may
-// only be done once per channel.
-Status<display::SurfaceInfo> DisplayService::OnCreateSurface(
-    pdx::Message& message, const display::SurfaceAttributes& attributes) {
-  // A surface may only be created once per channel.
-  if (message.GetChannel())
-    return ErrorStatus(EINVAL);
-
-  ALOGI_IF(TRACE, "DisplayService::OnCreateSurface: cid=%d",
-           message.GetChannelId());
-
-  // Use the channel id as the unique surface id.
-  const int surface_id = message.GetChannelId();
-  const int process_id = message.GetProcessId();
-  const int user_id = message.GetEffectiveUserId();
-
-  ALOGI_IF(TRACE,
-           "DisplayService::OnCreateSurface: surface_id=%d process_id=%d",
-           surface_id, process_id);
-
-  auto surface_status =
-      DisplaySurface::Create(this, surface_id, process_id, user_id, attributes);
-  if (!surface_status) {
-    ALOGE("DisplayService::OnCreateSurface: Failed to create surface: %s",
-          surface_status.GetErrorMessage().c_str());
-    return ErrorStatus(surface_status.error());
-  }
-  auto surface = surface_status.take();
-  message.SetChannel(surface);
-
-  // Update the surface with the attributes supplied with the create call. For
-  // application surfaces this has the side effect of notifying the display
-  // manager of the new surface. For direct surfaces, this may trigger a mode
-  // change, depending on the value of the visible attribute.
-  surface->OnSetAttributes(message, attributes);
-
-  return {{surface->surface_id(), surface->visible(), surface->z_order()}};
-}
-
-void DisplayService::SurfaceUpdated(SurfaceType surface_type,
-                                    display::SurfaceUpdateFlags update_flags) {
-  ALOGD_IF(TRACE, "DisplayService::SurfaceUpdated: update_flags=%x",
-           update_flags.value());
-  if (update_flags.value() != 0) {
-    if (surface_type == SurfaceType::Application)
-      NotifyDisplayConfigurationUpdate();
-    else
-      UpdateActiveDisplaySurfaces();
-  }
-}
-
-pdx::Status<BorrowedNativeBufferHandle> DisplayService::OnSetupGlobalBuffer(
-    pdx::Message& message, DvrGlobalBufferKey key, size_t size,
-    uint64_t usage) {
-  const int user_id = message.GetEffectiveUserId();
-  const bool trusted = user_id == AID_ROOT || IsTrustedUid(user_id);
-
-  if (!trusted) {
-    ALOGE(
-        "DisplayService::OnSetupGlobalBuffer: Permission denied for user_id=%d",
-        user_id);
-    return ErrorStatus(EPERM);
-  }
-  return SetupGlobalBuffer(key, size, usage);
-}
-
-pdx::Status<void> DisplayService::OnDeleteGlobalBuffer(pdx::Message& message,
-                                                       DvrGlobalBufferKey key) {
-  const int user_id = message.GetEffectiveUserId();
-  const bool trusted = (user_id == AID_ROOT) || IsTrustedUid(user_id);
-
-  if (!trusted) {
-    ALOGE(
-        "DisplayService::OnDeleteGlobalBuffer: Permission denied for "
-        "user_id=%d",
-        user_id);
-    return ErrorStatus(EPERM);
-  }
-  return DeleteGlobalBuffer(key);
-}
-
-pdx::Status<BorrowedNativeBufferHandle> DisplayService::OnGetGlobalBuffer(
-    pdx::Message& /* message */, DvrGlobalBufferKey key) {
-  ALOGD_IF(TRACE, "DisplayService::OnGetGlobalBuffer: key=%d", key);
-  auto global_buffer = global_buffers_.find(key);
-  if (global_buffer != global_buffers_.end())
-    return {BorrowedNativeBufferHandle(*global_buffer->second, 0)};
-  else
-    return pdx::ErrorStatus(EINVAL);
-}
-
-// Calls the message handler for the DisplaySurface associated with this
-// channel.
-Status<void> DisplayService::HandleSurfaceMessage(pdx::Message& message) {
-  auto surface = std::static_pointer_cast<DisplaySurface>(message.GetChannel());
-  ALOGW_IF(!surface,
-           "DisplayService::HandleSurfaceMessage: surface is nullptr!");
-
-  if (surface)
-    return surface->HandleMessage(message);
-  else
-    return ErrorStatus(EINVAL);
-}
-
-std::shared_ptr<DisplaySurface> DisplayService::GetDisplaySurface(
-    int surface_id) const {
-  return std::static_pointer_cast<DisplaySurface>(GetChannel(surface_id));
-}
-
-std::vector<std::shared_ptr<DisplaySurface>>
-DisplayService::GetDisplaySurfaces() const {
-  return GetChannels<DisplaySurface>();
-}
-
-std::vector<std::shared_ptr<DirectDisplaySurface>>
-DisplayService::GetVisibleDisplaySurfaces() const {
-  std::vector<std::shared_ptr<DirectDisplaySurface>> visible_surfaces;
-
-  ForEachDisplaySurface(
-      SurfaceType::Direct,
-      [&](const std::shared_ptr<DisplaySurface>& surface) mutable {
-        if (surface->visible()) {
-          visible_surfaces.push_back(
-              std::static_pointer_cast<DirectDisplaySurface>(surface));
-          surface->ClearUpdate();
-        }
-      });
-
-  return visible_surfaces;
-}
-
-void DisplayService::UpdateActiveDisplaySurfaces() {
-  auto visible_surfaces = GetVisibleDisplaySurfaces();
-  ALOGD_IF(TRACE,
-           "DisplayService::UpdateActiveDisplaySurfaces: %zd visible surfaces",
-           visible_surfaces.size());
-  hardware_composer_.SetDisplaySurfaces(std::move(visible_surfaces));
-}
-
-pdx::Status<BorrowedNativeBufferHandle> DisplayService::SetupGlobalBuffer(
-    DvrGlobalBufferKey key, size_t size, uint64_t usage) {
-  auto global_buffer = global_buffers_.find(key);
-  if (global_buffer == global_buffers_.end()) {
-    auto ion_buffer = std::make_unique<IonBuffer>(static_cast<int>(size), 1,
-                                                  HAL_PIXEL_FORMAT_BLOB, usage);
-
-    // Some buffers are used internally. If they were configured with an
-    // invalid size or format, this will fail.
-    int result = hardware_composer_.OnNewGlobalBuffer(key, *ion_buffer.get());
-    if (result < 0)
-      return ErrorStatus(result);
-    global_buffer =
-        global_buffers_.insert(std::make_pair(key, std::move(ion_buffer)))
-            .first;
-  }
-
-  return {BorrowedNativeBufferHandle(*global_buffer->second, 0)};
-}
-
-pdx::Status<void> DisplayService::DeleteGlobalBuffer(DvrGlobalBufferKey key) {
-  auto global_buffer = global_buffers_.find(key);
-  if (global_buffer != global_buffers_.end()) {
-    // Some buffers are used internally.
-    hardware_composer_.OnDeletedGlobalBuffer(key);
-    global_buffers_.erase(global_buffer);
-  }
-
-  return {0};
-}
-
-void DisplayService::SetDisplayConfigurationUpdateNotifier(
-    DisplayConfigurationUpdateNotifier update_notifier) {
-  update_notifier_ = update_notifier;
-}
-
-void DisplayService::NotifyDisplayConfigurationUpdate() {
-  if (update_notifier_)
-    update_notifier_();
-}
-
-Status<bool> DisplayService::IsVrAppRunning(pdx::Message& /*message*/) {
-  bool visible = false;
-  ForEachDisplaySurface(
-      SurfaceType::Application,
-      [&visible](const std::shared_ptr<DisplaySurface>& surface) {
-        if (surface->visible())
-          visible = true;
-      });
-
-  return {visible};
-}
-
-}  // namespace dvr
-}  // namespace android
diff --git a/libs/vr/libvrflinger/display_service.h b/libs/vr/libvrflinger/display_service.h
deleted file mode 100644
index 89f1eae..0000000
--- a/libs/vr/libvrflinger/display_service.h
+++ /dev/null
@@ -1,126 +0,0 @@
-#ifndef ANDROID_DVR_SERVICES_DISPLAYD_DISPLAY_SERVICE_H_
-#define ANDROID_DVR_SERVICES_DISPLAYD_DISPLAY_SERVICE_H_
-
-#include <dvr/dvr_api.h>
-#include <pdx/service.h>
-#include <pdx/status.h>
-#include <private/dvr/bufferhub_rpc.h>
-#include <private/dvr/display_protocol.h>
-
-#include <functional>
-#include <iterator>
-#include <memory>
-#include <string>
-#include <vector>
-
-#include "acquired_buffer.h"
-#include "display_surface.h"
-#include "epoll_event_dispatcher.h"
-#include "hardware_composer.h"
-
-namespace android {
-namespace dvr {
-
-// DisplayService implements the display service component of VrFlinger.
-class DisplayService : public pdx::ServiceBase<DisplayService> {
- public:
-  bool IsInitialized() const override;
-  std::string DumpState(size_t max_length) override;
-
-  void OnChannelClose(pdx::Message& message,
-                      const std::shared_ptr<pdx::Channel>& channel) override;
-  pdx::Status<void> HandleMessage(pdx::Message& message) override;
-
-  std::shared_ptr<DisplaySurface> GetDisplaySurface(int surface_id) const;
-  std::vector<std::shared_ptr<DisplaySurface>> GetDisplaySurfaces() const;
-  std::vector<std::shared_ptr<DirectDisplaySurface>> GetVisibleDisplaySurfaces()
-      const;
-
-  // Updates the list of actively displayed surfaces. This must be called after
-  // any change to client/manager attributes that affect visibility or z order.
-  void UpdateActiveDisplaySurfaces();
-
-  pdx::Status<BorrowedNativeBufferHandle> SetupGlobalBuffer(
-      DvrGlobalBufferKey key, size_t size, uint64_t usage);
-
-  pdx::Status<void> DeleteGlobalBuffer(DvrGlobalBufferKey key);
-
-  template <class A>
-  void ForEachDisplaySurface(SurfaceType surface_type, A action) const {
-    ForEachChannel([surface_type,
-                    action](const ChannelIterator::value_type& pair) mutable {
-      auto surface = std::static_pointer_cast<DisplaySurface>(pair.second);
-      if (surface->surface_type() == surface_type)
-        action(surface);
-    });
-  }
-
-  using DisplayConfigurationUpdateNotifier = std::function<void(void)>;
-  void SetDisplayConfigurationUpdateNotifier(
-      DisplayConfigurationUpdateNotifier notifier);
-
-  void GrantDisplayOwnership() { hardware_composer_.Enable(); }
-  void SeizeDisplayOwnership() { hardware_composer_.Disable(); }
-  void OnBootFinished() { hardware_composer_.OnBootFinished(); }
-
- private:
-  friend BASE;
-  friend DisplaySurface;
-
-  friend class VrDisplayStateService;
-
-  using RequestDisplayCallback = std::function<void(bool)>;
-
-  DisplayService(android::Hwc2::Composer* hidl,
-                 hwc2_display_t primary_display_id,
-                 RequestDisplayCallback request_display_callback);
-
-  pdx::Status<BorrowedNativeBufferHandle> OnGetGlobalBuffer(
-      pdx::Message& message, DvrGlobalBufferKey key);
-  pdx::Status<display::Metrics> OnGetMetrics(pdx::Message& message);
-  pdx::Status<std::string> OnGetConfigurationData(
-      pdx::Message& message, display::ConfigFileType config_type);
-  pdx::Status<uint8_t> OnGetDisplayIdentificationPort(pdx::Message& message);
-  pdx::Status<display::SurfaceInfo> OnCreateSurface(
-      pdx::Message& message, const display::SurfaceAttributes& attributes);
-  pdx::Status<BorrowedNativeBufferHandle> OnSetupGlobalBuffer(
-      pdx::Message& message, DvrGlobalBufferKey key, size_t size,
-      uint64_t usage);
-  pdx::Status<void> OnDeleteGlobalBuffer(pdx::Message& message,
-                                         DvrGlobalBufferKey key);
-
-  // Temporary query for current VR status. Will be removed later.
-  pdx::Status<bool> IsVrAppRunning(pdx::Message& message);
-
-  pdx::Status<void> AddEventHandler(int fd, int events,
-                                    EpollEventDispatcher::Handler handler) {
-    return dispatcher_.AddEventHandler(fd, events, handler);
-  }
-  pdx::Status<void> RemoveEventHandler(int fd) {
-    return dispatcher_.RemoveEventHandler(fd);
-  }
-
-  void SurfaceUpdated(SurfaceType surface_type,
-                      display::SurfaceUpdateFlags update_flags);
-
-  // Called by DisplaySurface to signal that a surface property has changed and
-  // the display manager should be notified.
-  void NotifyDisplayConfigurationUpdate();
-
-  pdx::Status<void> HandleSurfaceMessage(pdx::Message& message);
-
-  HardwareComposer hardware_composer_;
-  EpollEventDispatcher dispatcher_;
-  DisplayConfigurationUpdateNotifier update_notifier_;
-
-  std::unordered_map<DvrGlobalBufferKey, std::unique_ptr<IonBuffer>>
-      global_buffers_;
-
-  DisplayService(const DisplayService&) = delete;
-  void operator=(const DisplayService&) = delete;
-};
-
-}  // namespace dvr
-}  // namespace android
-
-#endif  // ANDROID_DVR_SERVICES_DISPLAYD_DISPLAY_SERVICE_H_
diff --git a/libs/vr/libvrflinger/display_surface.cpp b/libs/vr/libvrflinger/display_surface.cpp
deleted file mode 100644
index 87c823e..0000000
--- a/libs/vr/libvrflinger/display_surface.cpp
+++ /dev/null
@@ -1,488 +0,0 @@
-#include "display_surface.h"
-
-#include <private/android_filesystem_config.h>
-#include <utils/Trace.h>
-
-#include <private/dvr/trusted_uids.h>
-
-#include "display_service.h"
-#include "hardware_composer.h"
-
-#define LOCAL_TRACE 1
-
-using android::dvr::display::DisplayProtocol;
-using android::pdx::BorrowedChannelHandle;
-using android::pdx::ErrorStatus;
-using android::pdx::LocalChannelHandle;
-using android::pdx::LocalHandle;
-using android::pdx::Message;
-using android::pdx::RemoteChannelHandle;
-using android::pdx::Status;
-using android::pdx::rpc::DispatchRemoteMethod;
-using android::pdx::rpc::IfAnyOf;
-
-namespace android {
-namespace dvr {
-
-DisplaySurface::DisplaySurface(DisplayService* service,
-                               SurfaceType surface_type, int surface_id,
-                               int process_id, int user_id)
-    : service_(service),
-      surface_type_(surface_type),
-      surface_id_(surface_id),
-      process_id_(process_id),
-      user_id_(user_id),
-      update_flags_(display::SurfaceUpdateFlags::NewSurface) {}
-
-DisplaySurface::~DisplaySurface() {
-  ALOGD_IF(LOCAL_TRACE,
-           "DisplaySurface::~DisplaySurface: surface_id=%d process_id=%d",
-           surface_id(), process_id());
-}
-
-Status<void> DisplaySurface::HandleMessage(pdx::Message& message) {
-  switch (message.GetOp()) {
-    case DisplayProtocol::SetAttributes::Opcode:
-      DispatchRemoteMethod<DisplayProtocol::SetAttributes>(
-          *this, &DisplaySurface::OnSetAttributes, message);
-      break;
-
-    case DisplayProtocol::GetSurfaceInfo::Opcode:
-      DispatchRemoteMethod<DisplayProtocol::GetSurfaceInfo>(
-          *this, &DisplaySurface::OnGetSurfaceInfo, message);
-      break;
-
-    case DisplayProtocol::CreateQueue::Opcode:
-      DispatchRemoteMethod<DisplayProtocol::CreateQueue>(
-          *this, &DisplaySurface::OnCreateQueue, message);
-      break;
-  }
-
-  return {};
-}
-
-Status<void> DisplaySurface::OnSetAttributes(
-    pdx::Message& /*message*/, const display::SurfaceAttributes& attributes) {
-  display::SurfaceUpdateFlags update_flags;
-
-  for (const auto& attribute : attributes) {
-    const auto key = attribute.first;
-    const auto* variant = &attribute.second;
-    bool invalid_value = false;
-    bool visibility_changed = false;
-
-    // Catch attributes that have significance to the display service.
-    switch (key) {
-      case display::SurfaceAttribute::ZOrder:
-        invalid_value = !IfAnyOf<int32_t, int64_t, float>::Call(
-            variant, [&](const auto& value) {
-              if (z_order_ != value) {
-                visibility_changed = true;
-                z_order_ = value;
-              }
-            });
-        break;
-      case display::SurfaceAttribute::Visible:
-        invalid_value = !IfAnyOf<int32_t, int64_t, bool>::Call(
-            variant, [&](const auto& value) {
-              if (visible_ != value) {
-                visibility_changed = true;
-                visible_ = value;
-              }
-            });
-        break;
-    }
-
-    // Only update the attribute map with valid values. This check also has the
-    // effect of preventing special attributes handled above from being deleted
-    // by an empty value.
-    if (invalid_value) {
-      ALOGW(
-          "DisplaySurface::OnClientSetAttributes: Failed to set display "
-          "surface attribute '%d' because of incompatible type: %d",
-          key, variant->index());
-    } else {
-      // An empty value indicates the attribute should be deleted.
-      if (variant->empty()) {
-        auto search = attributes_.find(key);
-        if (search != attributes_.end())
-          attributes_.erase(search);
-      } else {
-        attributes_[key] = *variant;
-      }
-
-      // All attribute changes generate a notification, even if the value
-      // doesn't change. Visibility attributes set a flag only if the value
-      // changes.
-      update_flags.Set(display::SurfaceUpdateFlags::AttributesChanged);
-      if (visibility_changed)
-        update_flags.Set(display::SurfaceUpdateFlags::VisibilityChanged);
-    }
-  }
-
-  SurfaceUpdated(update_flags);
-  return {};
-}
-
-void DisplaySurface::SurfaceUpdated(display::SurfaceUpdateFlags update_flags) {
-  ALOGD_IF(TRACE,
-           "DisplaySurface::SurfaceUpdated: surface_id=%d update_flags=0x%x",
-           surface_id(), update_flags.value());
-
-  update_flags_.Set(update_flags);
-  service()->SurfaceUpdated(surface_type(), update_flags_);
-}
-
-void DisplaySurface::ClearUpdate() {
-  ALOGD_IF(TRACE > 1, "DisplaySurface::ClearUpdate: surface_id=%d",
-           surface_id());
-  update_flags_ = display::SurfaceUpdateFlags::None;
-}
-
-Status<display::SurfaceInfo> DisplaySurface::OnGetSurfaceInfo(
-    Message& /*message*/) {
-  ALOGD_IF(
-      TRACE,
-      "DisplaySurface::OnGetSurfaceInfo: surface_id=%d visible=%d z_order=%d",
-      surface_id(), visible(), z_order());
-  return {{surface_id(), visible(), z_order()}};
-}
-
-Status<void> DisplaySurface::RegisterQueue(
-    const std::shared_ptr<ConsumerQueue>& consumer_queue) {
-  ALOGD_IF(TRACE, "DisplaySurface::RegisterQueue: surface_id=%d queue_id=%d",
-           surface_id(), consumer_queue->id());
-  // Capture references for the lambda to work around apparent clang bug.
-  // TODO(eieio): Figure out if there is a clang bug or C++11 ambiguity when
-  // capturing self and consumer_queue by copy in the following case:
-  //    auto self = Self();
-  //    [self, consumer_queue](int events) {
-  //        self->OnQueueEvent(consuemr_queue, events); }
-  //
-  struct State {
-    std::shared_ptr<DisplaySurface> surface;
-    std::shared_ptr<ConsumerQueue> queue;
-  };
-  State state{Self(), consumer_queue};
-
-  return service()->AddEventHandler(
-      consumer_queue->queue_fd(), EPOLLIN | EPOLLHUP | EPOLLET,
-      [state](int events) {
-        state.surface->OnQueueEvent(state.queue, events);
-      });
-}
-
-Status<void> DisplaySurface::UnregisterQueue(
-    const std::shared_ptr<ConsumerQueue>& consumer_queue) {
-  ALOGD_IF(TRACE, "DisplaySurface::UnregisterQueue: surface_id=%d queue_id=%d",
-           surface_id(), consumer_queue->id());
-  return service()->RemoveEventHandler(consumer_queue->queue_fd());
-}
-
-void DisplaySurface::OnQueueEvent(
-    const std::shared_ptr<ConsumerQueue>& /*consumer_queue*/, int /*events*/) {
-  ALOGE(
-      "DisplaySurface::OnQueueEvent: ERROR base virtual method should not be "
-      "called!!!");
-}
-
-std::shared_ptr<ConsumerQueue> ApplicationDisplaySurface::GetQueue(
-    int32_t queue_id) {
-  ALOGD_IF(TRACE,
-           "ApplicationDisplaySurface::GetQueue: surface_id=%d queue_id=%d",
-           surface_id(), queue_id);
-
-  std::lock_guard<std::mutex> autolock(lock_);
-  auto search = consumer_queues_.find(queue_id);
-  if (search != consumer_queues_.end())
-    return search->second;
-  else
-    return nullptr;
-}
-
-std::vector<int32_t> ApplicationDisplaySurface::GetQueueIds() const {
-  std::lock_guard<std::mutex> autolock(lock_);
-  std::vector<int32_t> queue_ids;
-  for (const auto& entry : consumer_queues_)
-    queue_ids.push_back(entry.first);
-  return queue_ids;
-}
-
-Status<LocalChannelHandle> ApplicationDisplaySurface::OnCreateQueue(
-    Message& /*message*/, const ProducerQueueConfig& config) {
-  ATRACE_NAME("ApplicationDisplaySurface::OnCreateQueue");
-  ALOGD_IF(TRACE,
-           "ApplicationDisplaySurface::OnCreateQueue: surface_id=%d, "
-           "user_metadata_size=%zu",
-           surface_id(), config.user_metadata_size);
-
-  std::lock_guard<std::mutex> autolock(lock_);
-  auto producer = ProducerQueue::Create(config, UsagePolicy{});
-  if (!producer) {
-    ALOGE(
-        "ApplicationDisplaySurface::OnCreateQueue: Failed to create producer "
-        "queue!");
-    return ErrorStatus(ENOMEM);
-  }
-
-  std::shared_ptr<ConsumerQueue> consumer =
-      producer->CreateSilentConsumerQueue();
-  auto status = RegisterQueue(consumer);
-  if (!status) {
-    ALOGE(
-        "ApplicationDisplaySurface::OnCreateQueue: Failed to register consumer "
-        "queue: %s",
-        status.GetErrorMessage().c_str());
-    return status.error_status();
-  }
-
-  consumer_queues_[consumer->id()] = std::move(consumer);
-
-  SurfaceUpdated(display::SurfaceUpdateFlags::BuffersChanged);
-  return std::move(producer->GetChannelHandle());
-}
-
-void ApplicationDisplaySurface::OnQueueEvent(
-    const std::shared_ptr<ConsumerQueue>& consumer_queue, int events) {
-  ALOGD_IF(TRACE,
-           "ApplicationDisplaySurface::OnQueueEvent: queue_id=%d events=%x",
-           consumer_queue->id(), events);
-
-  std::lock_guard<std::mutex> autolock(lock_);
-
-  // Always give the queue a chance to handle its internal bookkeeping.
-  consumer_queue->HandleQueueEvents();
-
-  // Check for hangup and remove a queue that is no longer needed.
-  if (consumer_queue->hung_up()) {
-    ALOGD_IF(TRACE, "ApplicationDisplaySurface::OnQueueEvent: Removing queue.");
-    UnregisterQueue(consumer_queue);
-    auto search = consumer_queues_.find(consumer_queue->id());
-    if (search != consumer_queues_.end()) {
-      consumer_queues_.erase(search);
-    } else {
-      ALOGE(
-          "ApplicationDisplaySurface::OnQueueEvent: Failed to find queue_id=%d",
-          consumer_queue->id());
-    }
-    SurfaceUpdated(display::SurfaceUpdateFlags::BuffersChanged);
-  }
-}
-
-std::vector<int32_t> DirectDisplaySurface::GetQueueIds() const {
-  std::lock_guard<std::mutex> autolock(lock_);
-  std::vector<int32_t> queue_ids;
-  if (direct_queue_)
-    queue_ids.push_back(direct_queue_->id());
-  return queue_ids;
-}
-
-Status<LocalChannelHandle> DirectDisplaySurface::OnCreateQueue(
-    Message& /*message*/, const ProducerQueueConfig& config) {
-  ATRACE_NAME("DirectDisplaySurface::OnCreateQueue");
-  ALOGD_IF(TRACE,
-           "DirectDisplaySurface::OnCreateQueue: surface_id=%d "
-           "user_metadata_size=%zu",
-           surface_id(), config.user_metadata_size);
-
-  std::lock_guard<std::mutex> autolock(lock_);
-  if (!direct_queue_) {
-    // Inject the hw composer usage flag to enable the display to read the
-    // buffers.
-    auto producer = ProducerQueue::Create(
-        config, UsagePolicy{GraphicBuffer::USAGE_HW_COMPOSER, 0, 0, 0});
-    if (!producer) {
-      ALOGE(
-          "DirectDisplaySurface::OnCreateQueue: Failed to create producer "
-          "queue!");
-      return ErrorStatus(ENOMEM);
-    }
-
-    direct_queue_ = producer->CreateConsumerQueue();
-    if (direct_queue_->metadata_size() > 0) {
-      metadata_.reset(new uint8_t[direct_queue_->metadata_size()]);
-    }
-    auto status = RegisterQueue(direct_queue_);
-    if (!status) {
-      ALOGE(
-          "DirectDisplaySurface::OnCreateQueue: Failed to register consumer "
-          "queue: %s",
-          status.GetErrorMessage().c_str());
-      return status.error_status();
-    }
-
-    return std::move(producer->GetChannelHandle());
-  } else {
-    return ErrorStatus(EALREADY);
-  }
-}
-
-void DirectDisplaySurface::OnQueueEvent(
-    const std::shared_ptr<ConsumerQueue>& consumer_queue, int events) {
-  ALOGD_IF(TRACE, "DirectDisplaySurface::OnQueueEvent: queue_id=%d events=%x",
-           consumer_queue->id(), events);
-
-  std::lock_guard<std::mutex> autolock(lock_);
-
-  // Always give the queue a chance to handle its internal bookkeeping.
-  consumer_queue->HandleQueueEvents();
-
-  // Check for hangup and remove a queue that is no longer needed.
-  if (consumer_queue->hung_up()) {
-    ALOGD_IF(TRACE, "DirectDisplaySurface::OnQueueEvent: Removing queue.");
-    UnregisterQueue(consumer_queue);
-    direct_queue_ = nullptr;
-  }
-}
-
-void DirectDisplaySurface::DequeueBuffersLocked() {
-  if (direct_queue_ == nullptr) {
-    ALOGE(
-        "DirectDisplaySurface::DequeueBuffersLocked: Consumer queue is not "
-        "initialized.");
-    return;
-  }
-
-  while (true) {
-    LocalHandle acquire_fence;
-    size_t slot;
-    auto buffer_status = direct_queue_->Dequeue(
-        0, &slot, metadata_.get(),
-        direct_queue_->metadata_size(), &acquire_fence);
-    ALOGD_IF(TRACE,
-             "DirectDisplaySurface::DequeueBuffersLocked: Dequeue with metadata_size: %zu",
-             direct_queue_->metadata_size());
-    if (!buffer_status) {
-      ALOGD_IF(
-          TRACE > 1 && buffer_status.error() == ETIMEDOUT,
-          "DirectDisplaySurface::DequeueBuffersLocked: All buffers dequeued.");
-      ALOGE_IF(buffer_status.error() != ETIMEDOUT,
-               "DirectDisplaySurface::DequeueBuffersLocked: Failed to dequeue "
-               "buffer: %s",
-               buffer_status.GetErrorMessage().c_str());
-      return;
-    }
-    auto buffer_consumer = buffer_status.take();
-
-    if (!visible()) {
-      ATRACE_NAME("DropFrameOnInvisibleSurface");
-      ALOGD_IF(TRACE,
-               "DirectDisplaySurface::DequeueBuffersLocked: Discarding "
-               "buffer_id=%d on invisible surface.",
-               buffer_consumer->id());
-      buffer_consumer->Discard();
-      continue;
-    }
-
-    if (acquired_buffers_.IsFull()) {
-      ALOGE(
-          "DirectDisplaySurface::DequeueBuffersLocked: Posted buffers full, "
-          "overwriting.");
-      acquired_buffers_.PopBack();
-    }
-
-    acquired_buffers_.Append(
-        AcquiredBuffer(buffer_consumer, std::move(acquire_fence), slot));
-  }
-}
-
-AcquiredBuffer DirectDisplaySurface::AcquireCurrentBuffer() {
-  std::lock_guard<std::mutex> autolock(lock_);
-  DequeueBuffersLocked();
-
-  if (acquired_buffers_.IsEmpty()) {
-    ALOGE(
-        "DirectDisplaySurface::AcquireCurrentBuffer: attempt to acquire buffer "
-        "when none are posted.");
-    return AcquiredBuffer();
-  }
-  AcquiredBuffer buffer = std::move(acquired_buffers_.Front());
-  acquired_buffers_.PopFront();
-  ALOGD_IF(TRACE, "DirectDisplaySurface::AcquireCurrentBuffer: buffer_id=%d",
-           buffer.buffer()->id());
-  return buffer;
-}
-
-AcquiredBuffer DirectDisplaySurface::AcquireNewestAvailableBuffer(
-    AcquiredBuffer* skipped_buffer) {
-  std::lock_guard<std::mutex> autolock(lock_);
-  DequeueBuffersLocked();
-
-  AcquiredBuffer buffer;
-  int frames = 0;
-  // Basic latency stopgap for when the application misses a frame:
-  // If the application recovers on the 2nd or 3rd (etc) frame after
-  // missing, this code will skip frames to catch up by checking if
-  // the next frame is also available.
-  while (!acquired_buffers_.IsEmpty() &&
-         acquired_buffers_.Front().IsAvailable()) {
-    // Capture the skipped buffer into the result parameter.
-    // Note that this API only supports skipping one buffer per vsync.
-    if (frames > 0 && skipped_buffer)
-      *skipped_buffer = std::move(buffer);
-    ++frames;
-    buffer = std::move(acquired_buffers_.Front());
-    acquired_buffers_.PopFront();
-    if (frames == 2)
-      break;
-  }
-  ALOGD_IF(TRACE,
-           "DirectDisplaySurface::AcquireNewestAvailableBuffer: buffer_id=%d",
-           buffer.buffer()->id());
-  return buffer;
-}
-
-bool DirectDisplaySurface::IsBufferAvailable() {
-  std::lock_guard<std::mutex> autolock(lock_);
-  DequeueBuffersLocked();
-
-  return !acquired_buffers_.IsEmpty() &&
-         acquired_buffers_.Front().IsAvailable();
-}
-
-bool DirectDisplaySurface::IsBufferPosted() {
-  std::lock_guard<std::mutex> autolock(lock_);
-  DequeueBuffersLocked();
-
-  return !acquired_buffers_.IsEmpty();
-}
-
-Status<std::shared_ptr<DisplaySurface>> DisplaySurface::Create(
-    DisplayService* service, int surface_id, int process_id, int user_id,
-    const display::SurfaceAttributes& attributes) {
-  bool direct = false;
-  auto search = attributes.find(display::SurfaceAttribute::Direct);
-  if (search != attributes.end()) {
-    if (!IfAnyOf<int32_t, int64_t, bool, float>::Get(&search->second,
-                                                     &direct)) {
-      ALOGE(
-          "DisplaySurface::Create: Invalid type for SurfaceAttribute::Direct!");
-      return ErrorStatus(EINVAL);
-    }
-  }
-
-  ALOGD_IF(TRACE,
-           "DisplaySurface::Create: surface_id=%d process_id=%d user_id=%d "
-           "direct=%d",
-           surface_id, process_id, user_id, direct);
-
-  if (direct) {
-    const bool trusted = user_id == AID_ROOT || IsTrustedUid(user_id);
-    if (trusted) {
-      return {std::shared_ptr<DisplaySurface>{
-          new DirectDisplaySurface(service, surface_id, process_id, user_id)}};
-    } else {
-      ALOGE(
-          "DisplaySurface::Create: Direct surfaces may only be created by "
-          "trusted UIDs: user_id=%d",
-          user_id);
-      return ErrorStatus(EPERM);
-    }
-  } else {
-    return {std::shared_ptr<DisplaySurface>{new ApplicationDisplaySurface(
-        service, surface_id, process_id, user_id)}};
-  }
-}
-
-}  // namespace dvr
-}  // namespace android
diff --git a/libs/vr/libvrflinger/display_surface.h b/libs/vr/libvrflinger/display_surface.h
deleted file mode 100644
index c8b1a07..0000000
--- a/libs/vr/libvrflinger/display_surface.h
+++ /dev/null
@@ -1,188 +0,0 @@
-#ifndef ANDROID_DVR_SERVICES_DISPLAYD_DISPLAY_SURFACE_H_
-#define ANDROID_DVR_SERVICES_DISPLAYD_DISPLAY_SURFACE_H_
-
-#include <pdx/file_handle.h>
-#include <pdx/service.h>
-#include <private/dvr/buffer_hub_queue_client.h>
-#include <private/dvr/display_protocol.h>
-#include <private/dvr/ring_buffer.h>
-
-#include <functional>
-#include <iterator>
-#include <memory>
-#include <string>
-#include <vector>
-
-#include "acquired_buffer.h"
-
-namespace android {
-namespace dvr {
-
-class DisplayService;
-
-enum class SurfaceType {
-  Direct,
-  Application,
-};
-
-class DisplaySurface : public pdx::Channel {
- public:
-  static pdx::Status<std::shared_ptr<DisplaySurface>> Create(
-      DisplayService* service, int surface_id, int process_id, int user_id,
-      const display::SurfaceAttributes& attributes);
-
-  ~DisplaySurface() override;
-
-  DisplayService* service() const { return service_; }
-  SurfaceType surface_type() const { return surface_type_; }
-  int surface_id() const { return surface_id_; }
-  int process_id() const { return process_id_; }
-  int user_id() const { return user_id_; }
-
-  bool visible() const { return visible_; }
-  int z_order() const { return z_order_; }
-
-  const display::SurfaceAttributes& attributes() const { return attributes_; }
-  display::SurfaceUpdateFlags update_flags() const { return update_flags_; }
-
-  virtual std::vector<int32_t> GetQueueIds() const { return {}; }
-
-  bool IsUpdatePending() const {
-    return update_flags_.value() != display::SurfaceUpdateFlags::None;
-  }
-
- protected:
-  DisplaySurface(DisplayService* service, SurfaceType surface_type,
-                 int surface_id, int process_id, int user_id);
-
-  // Utility to retrieve a shared pointer to this channel as the desired derived
-  // type.
-  template <
-      typename T = DisplaySurface,
-      typename = std::enable_if_t<std::is_base_of<DisplaySurface, T>::value>>
-  std::shared_ptr<T> Self() {
-    return std::static_pointer_cast<T>(shared_from_this());
-  }
-
-  virtual pdx::Status<pdx::LocalChannelHandle> OnCreateQueue(
-      pdx::Message& message, const ProducerQueueConfig& config) = 0;
-
-  // Registers a consumer queue with the event dispatcher in DisplayService. The
-  // OnQueueEvent callback below is called to handle queue events.
-  pdx::Status<void> RegisterQueue(
-      const std::shared_ptr<ConsumerQueue>& consumer_queue);
-  pdx::Status<void> UnregisterQueue(
-      const std::shared_ptr<ConsumerQueue>& consumer_queue);
-
-  // Called by the event dispatcher in DisplayService when a registered queue
-  // event triggers. Executes on the event dispatcher thread.
-  virtual void OnQueueEvent(
-      const std::shared_ptr<ConsumerQueue>& consumer_queue, int events);
-
-  void SurfaceUpdated(display::SurfaceUpdateFlags update_flags);
-  void ClearUpdate();
-
-  // Synchronizes access to mutable state below between message dispatch thread
-  // and frame post thread.
-  mutable std::mutex lock_;
-
- private:
-  friend class DisplayService;
-  friend class DisplayManagerService;
-
-  // Dispatches display surface messages to the appropriate handlers. This
-  // handler runs on the VrFlinger message dispatch thread.
-  pdx::Status<void> HandleMessage(pdx::Message& message);
-
-  pdx::Status<void> OnSetAttributes(
-      pdx::Message& message, const display::SurfaceAttributes& attributes);
-  pdx::Status<display::SurfaceInfo> OnGetSurfaceInfo(pdx::Message& message);
-
-  DisplayService* service_;
-  SurfaceType surface_type_;
-  int surface_id_;
-  int process_id_;
-  int user_id_;
-
-  display::SurfaceAttributes attributes_;
-  display::SurfaceUpdateFlags update_flags_ = display::SurfaceUpdateFlags::None;
-
-  // Subset of attributes that may be interpreted by the display service.
-  bool visible_ = false;
-  int z_order_ = 0;
-
-  DisplaySurface(const DisplaySurface&) = delete;
-  void operator=(const DisplaySurface&) = delete;
-};
-
-class ApplicationDisplaySurface : public DisplaySurface {
- public:
-  ApplicationDisplaySurface(DisplayService* service, int surface_id,
-                            int process_id, int user_id)
-      : DisplaySurface(service, SurfaceType::Application, surface_id,
-                       process_id, user_id) {}
-
-  std::shared_ptr<ConsumerQueue> GetQueue(int32_t queue_id);
-  std::vector<int32_t> GetQueueIds() const override;
-
- private:
-  pdx::Status<pdx::LocalChannelHandle> OnCreateQueue(
-      pdx::Message& message, const ProducerQueueConfig& config) override;
-  void OnQueueEvent(const std::shared_ptr<ConsumerQueue>& consumer_queue,
-                    int events) override;
-
-  // Accessed by both message dispatch thread and epoll event thread.
-  std::unordered_map<int32_t, std::shared_ptr<ConsumerQueue>> consumer_queues_;
-};
-
-class DirectDisplaySurface : public DisplaySurface {
- public:
-  DirectDisplaySurface(DisplayService* service, int surface_id, int process_id,
-                       int user_id)
-      : DisplaySurface(service, SurfaceType::Direct, surface_id, process_id,
-                       user_id),
-        acquired_buffers_(kMaxPostedBuffers),
-        metadata_(nullptr) {}
-  std::vector<int32_t> GetQueueIds() const override;
-  bool IsBufferAvailable();
-  bool IsBufferPosted();
-  AcquiredBuffer AcquireCurrentBuffer();
-
-  // Get the newest buffer. Up to one buffer will be skipped. If a buffer is
-  // skipped, it will be stored in skipped_buffer if non null.
-  AcquiredBuffer AcquireNewestAvailableBuffer(AcquiredBuffer* skipped_buffer);
-
- private:
-  pdx::Status<pdx::LocalChannelHandle> OnCreateQueue(
-      pdx::Message& message, const ProducerQueueConfig& config) override;
-  void OnQueueEvent(const std::shared_ptr<ConsumerQueue>& consumer_queue,
-                    int events) override;
-
-  // The capacity of the pending buffer queue. Should be enough to hold all the
-  // buffers of this DisplaySurface, although in practice only 1 or 2 frames
-  // will be pending at a time.
-  static constexpr int kSurfaceBufferMaxCount = 4;
-  static constexpr int kSurfaceViewMaxCount = 4;
-  static constexpr int kMaxPostedBuffers =
-      kSurfaceBufferMaxCount * kSurfaceViewMaxCount;
-
-  // Returns whether a frame is available without locking the mutex.
-  bool IsFrameAvailableNoLock() const;
-
-  // Dequeue all available buffers from the consumer queue.
-  void DequeueBuffersLocked();
-
-  // In a triple-buffered surface, up to kMaxPostedBuffers buffers may be
-  // posted and pending.
-  RingBuffer<AcquiredBuffer> acquired_buffers_;
-
-  std::shared_ptr<ConsumerQueue> direct_queue_;
-
-  // Stores metadata when it dequeue buffers from consumer queue.
-  std::unique_ptr<uint8_t[]> metadata_;
-};
-
-}  // namespace dvr
-}  // namespace android
-
-#endif  // ANDROID_DVR_SERVICES_DISPLAYD_DISPLAY_SURFACE_H_
diff --git a/libs/vr/libvrflinger/epoll_event_dispatcher.cpp b/libs/vr/libvrflinger/epoll_event_dispatcher.cpp
deleted file mode 100644
index 0d5eb80..0000000
--- a/libs/vr/libvrflinger/epoll_event_dispatcher.cpp
+++ /dev/null
@@ -1,142 +0,0 @@
-#include "epoll_event_dispatcher.h"
-
-#include <log/log.h>
-#include <sys/epoll.h>
-#include <sys/eventfd.h>
-#include <sys/prctl.h>
-
-#include <dvr/performance_client_api.h>
-
-namespace android {
-namespace dvr {
-
-EpollEventDispatcher::EpollEventDispatcher() {
-  epoll_fd_.Reset(epoll_create1(EPOLL_CLOEXEC));
-  if (!epoll_fd_) {
-    ALOGE("Failed to create epoll fd: %s", strerror(errno));
-    return;
-  }
-
-  event_fd_.Reset(eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK));
-  if (!event_fd_) {
-    ALOGE("Failed to create event for epolling: %s", strerror(errno));
-    return;
-  }
-
-  // Add watch for eventfd. This should only watch for EPOLLIN, which gets set
-  // when eventfd_write occurs. Use "this" as a unique sentinal value to
-  // identify events from the event fd.
-  epoll_event event = {.events = EPOLLIN, .data = {.ptr = this}};
-  if (epoll_ctl(epoll_fd_.Get(), EPOLL_CTL_ADD, event_fd_.Get(), &event) < 0) {
-    ALOGE("Failed to add eventfd to epoll set because: %s", strerror(errno));
-    return;
-  }
-
-  thread_ = std::thread(&EpollEventDispatcher::EventThread, this);
-}
-
-EpollEventDispatcher::~EpollEventDispatcher() { Stop(); }
-
-void EpollEventDispatcher::Stop() {
-  exit_thread_.store(true);
-  eventfd_write(event_fd_.Get(), 1);
-}
-
-pdx::Status<void> EpollEventDispatcher::AddEventHandler(int fd, int event_mask,
-                                                        Handler handler) {
-  std::lock_guard<std::mutex> lock(lock_);
-
-  epoll_event event;
-  event.events = event_mask;
-  event.data.ptr = &(handlers_[fd] = handler);
-
-  ALOGD_IF(
-      TRACE,
-      "EpollEventDispatcher::AddEventHandler: fd=%d event_mask=0x%x handler=%p",
-      fd, event_mask, event.data.ptr);
-
-  if (epoll_ctl(epoll_fd_.Get(), EPOLL_CTL_ADD, fd, &event) < 0) {
-    const int error = errno;
-    ALOGE("Failed to add fd to epoll set because: %s", strerror(error));
-    return pdx::ErrorStatus(error);
-  } else {
-    return {};
-  }
-}
-
-pdx::Status<void> EpollEventDispatcher::RemoveEventHandler(int fd) {
-  ALOGD_IF(TRACE, "EpollEventDispatcher::RemoveEventHandler: fd=%d", fd);
-  std::lock_guard<std::mutex> lock(lock_);
-
-  epoll_event ee;  // See BUGS in man 2 epoll_ctl.
-  if (epoll_ctl(epoll_fd_.Get(), EPOLL_CTL_DEL, fd, &ee) < 0) {
-    const int error = errno;
-    ALOGE("Failed to remove fd from epoll set because: %s", strerror(error));
-    return pdx::ErrorStatus(error);
-  }
-
-  // If the fd was valid above, add it to the list of ids to remove.
-  removed_handlers_.push_back(fd);
-
-  // Wake up the event thread to clean up.
-  eventfd_write(event_fd_.Get(), 1);
-
-  return {};
-}
-
-void EpollEventDispatcher::EventThread() {
-  prctl(PR_SET_NAME, reinterpret_cast<unsigned long>("VrEvent"), 0, 0, 0);
-
-  const int error = dvrSetSchedulerClass(0, "graphics");
-  LOG_ALWAYS_FATAL_IF(
-      error < 0,
-      "EpollEventDispatcher::EventThread: Failed to set scheduler class: %s",
-      strerror(-error));
-
-  const size_t kMaxNumEvents = 128;
-  epoll_event events[kMaxNumEvents];
-
-  while (!exit_thread_.load()) {
-    const int num_events = epoll_wait(epoll_fd_.Get(), events, kMaxNumEvents, -1);
-    if (num_events < 0 && errno != EINTR)
-      break;
-
-    ALOGD_IF(TRACE > 1, "EpollEventDispatcher::EventThread: num_events=%d",
-             num_events);
-
-    for (int i = 0; i < num_events; i++) {
-      ALOGD_IF(
-          TRACE > 1,
-          "EpollEventDispatcher::EventThread: event %d: handler=%p events=0x%x",
-          i, events[i].data.ptr, events[i].events);
-
-      if (events[i].data.ptr == this) {
-        // Clear pending event on event_fd_. Serialize the read with respect to
-        // writes from other threads.
-        std::lock_guard<std::mutex> lock(lock_);
-        eventfd_t value;
-        eventfd_read(event_fd_.Get(), &value);
-      } else {
-        auto handler = reinterpret_cast<Handler*>(events[i].data.ptr);
-        if (handler)
-          (*handler)(events[i].events);
-      }
-    }
-
-    // Remove any handlers that have been posted for removal. This is done here
-    // instead of in RemoveEventHandler() to prevent races between the dispatch
-    // thread and the code requesting the removal. Handlers are guaranteed to
-    // stay alive between exiting epoll_wait() and the dispatch loop above.
-    std::lock_guard<std::mutex> lock(lock_);
-    for (auto handler_fd : removed_handlers_) {
-      ALOGD_IF(TRACE,
-               "EpollEventDispatcher::EventThread: removing handler: fd=%d",
-               handler_fd);
-      handlers_.erase(handler_fd);
-    }
-    removed_handlers_.clear();
-  }
-}
-
-}  // namespace dvr
-}  // namespace android
diff --git a/libs/vr/libvrflinger/epoll_event_dispatcher.h b/libs/vr/libvrflinger/epoll_event_dispatcher.h
deleted file mode 100644
index eb687f4..0000000
--- a/libs/vr/libvrflinger/epoll_event_dispatcher.h
+++ /dev/null
@@ -1,63 +0,0 @@
-#ifndef ANDROID_DVR_SERVICES_DISPLAYD_EPOLL_EVENT_DISPATCHER_H_
-#define ANDROID_DVR_SERVICES_DISPLAYD_EPOLL_EVENT_DISPATCHER_H_
-
-#include <sys/epoll.h>
-
-#include <atomic>
-#include <functional>
-#include <mutex>
-#include <thread>
-#include <unordered_map>
-#include <vector>
-
-#include <pdx/file_handle.h>
-#include <pdx/status.h>
-
-namespace android {
-namespace dvr {
-
-class EpollEventDispatcher {
- public:
-  // Function type for event handlers. The handler receives a bitmask of the
-  // epoll events that occurred on the file descriptor associated with the
-  // handler.
-  using Handler = std::function<void(int)>;
-
-  EpollEventDispatcher();
-  ~EpollEventDispatcher();
-
-  // |handler| is called on the internal dispatch thread when |fd| is signaled
-  // by events in |event_mask|.
-  pdx::Status<void> AddEventHandler(int fd, int event_mask, Handler handler);
-  pdx::Status<void> RemoveEventHandler(int fd);
-
-  void Stop();
-
- private:
-  void EventThread();
-
-  std::thread thread_;
-  std::atomic<bool> exit_thread_{false};
-
-  // Protects handlers_ and removed_handlers_ and serializes operations on
-  // epoll_fd_ and event_fd_.
-  std::mutex lock_;
-
-  // Maintains a map of fds to event handlers. This is primarily to keep any
-  // references alive that may be bound in the std::function instances. It is
-  // not used at dispatch time to avoid performance problems with different
-  // versions of std::unordered_map.
-  std::unordered_map<int, Handler> handlers_;
-
-  // List of fds to be removed from the map. The actual removal is performed
-  // by the event dispatch thread to avoid races.
-  std::vector<int> removed_handlers_;
-
-  pdx::LocalHandle epoll_fd_;
-  pdx::LocalHandle event_fd_;
-};
-
-}  // namespace dvr
-}  // namespace android
-
-#endif  // ANDROID_DVR_SERVICES_DISPLAYD_EPOLL_EVENT_DISPATCHER_H_
diff --git a/libs/vr/libvrflinger/hardware_composer.cpp b/libs/vr/libvrflinger/hardware_composer.cpp
deleted file mode 100644
index 70f303b..0000000
--- a/libs/vr/libvrflinger/hardware_composer.cpp
+++ /dev/null
@@ -1,1541 +0,0 @@
-#include "hardware_composer.h"
-
-#include <binder/IServiceManager.h>
-#include <cutils/properties.h>
-#include <cutils/sched_policy.h>
-#include <fcntl.h>
-#include <log/log.h>
-#include <poll.h>
-#include <stdint.h>
-#include <sync/sync.h>
-#include <sys/eventfd.h>
-#include <sys/prctl.h>
-#include <sys/resource.h>
-#include <sys/system_properties.h>
-#include <sys/timerfd.h>
-#include <sys/types.h>
-#include <time.h>
-#include <unistd.h>
-#include <utils/Trace.h>
-
-#include <algorithm>
-#include <chrono>
-#include <functional>
-#include <map>
-#include <sstream>
-#include <string>
-#include <tuple>
-
-#include <dvr/dvr_display_types.h>
-#include <dvr/performance_client_api.h>
-#include <private/dvr/clock_ns.h>
-#include <private/dvr/ion_buffer.h>
-
-using android::hardware::Return;
-using android::hardware::Void;
-using android::pdx::ErrorStatus;
-using android::pdx::LocalHandle;
-using android::pdx::Status;
-using android::pdx::rpc::EmptyVariant;
-using android::pdx::rpc::IfAnyOf;
-
-using namespace std::chrono_literals;
-
-namespace android {
-namespace dvr {
-
-namespace {
-
-const char kDvrPerformanceProperty[] = "sys.dvr.performance";
-
-const char kRightEyeOffsetProperty[] = "dvr.right_eye_offset_ns";
-
-// Surface flinger uses "VSYNC-sf" and "VSYNC-app" for its version of these
-// events. Name ours similarly.
-const char kVsyncTraceEventName[] = "VSYNC-vrflinger";
-
-// How long to wait after boot finishes before we turn the display off.
-constexpr int kBootFinishedDisplayOffTimeoutSec = 10;
-
-constexpr int kDefaultDisplayWidth = 1920;
-constexpr int kDefaultDisplayHeight = 1080;
-constexpr int64_t kDefaultVsyncPeriodNs = 16666667;
-// Hardware composer reports dpi as dots per thousand inches (dpi * 1000).
-constexpr int kDefaultDpi = 400000;
-
-// Get time offset from a vsync to when the pose for that vsync should be
-// predicted out to. For example, if scanout gets halfway through the frame
-// at the halfway point between vsyncs, then this could be half the period.
-// With global shutter displays, this should be changed to the offset to when
-// illumination begins. Low persistence adds a frame of latency, so we predict
-// to the center of the next frame.
-inline int64_t GetPosePredictionTimeOffset(int64_t vsync_period_ns) {
-  return (vsync_period_ns * 150) / 100;
-}
-
-// Attempts to set the scheduler class and partiton for the current thread.
-// Returns true on success or false on failure.
-bool SetThreadPolicy(const std::string& scheduler_class,
-                     const std::string& partition) {
-  int error = dvrSetSchedulerClass(0, scheduler_class.c_str());
-  if (error < 0) {
-    ALOGE(
-        "SetThreadPolicy: Failed to set scheduler class \"%s\" for "
-        "thread_id=%d: %s",
-        scheduler_class.c_str(), gettid(), strerror(-error));
-    return false;
-  }
-  error = dvrSetCpuPartition(0, partition.c_str());
-  if (error < 0) {
-    ALOGE(
-        "SetThreadPolicy: Failed to set cpu partiton \"%s\" for thread_id=%d: "
-        "%s",
-        partition.c_str(), gettid(), strerror(-error));
-    return false;
-  }
-  return true;
-}
-
-// Utility to generate scoped tracers with arguments.
-// TODO(eieio): Move/merge this into utils/Trace.h?
-class TraceArgs {
- public:
-  template <typename... Args>
-  explicit TraceArgs(const char* format, Args&&... args) {
-    std::array<char, 1024> buffer;
-    snprintf(buffer.data(), buffer.size(), format, std::forward<Args>(args)...);
-    atrace_begin(ATRACE_TAG, buffer.data());
-  }
-
-  ~TraceArgs() { atrace_end(ATRACE_TAG); }
-
- private:
-  TraceArgs(const TraceArgs&) = delete;
-  void operator=(const TraceArgs&) = delete;
-};
-
-// Macro to define a scoped tracer with arguments. Uses PASTE(x, y) macro
-// defined in utils/Trace.h.
-#define TRACE_FORMAT(format, ...) \
-  TraceArgs PASTE(__tracer, __LINE__) { format, ##__VA_ARGS__ }
-
-// Returns "primary" or "external". Useful for writing more readable logs.
-const char* GetDisplayName(bool is_primary) {
-  return is_primary ? "primary" : "external";
-}
-
-}  // anonymous namespace
-
-HardwareComposer::HardwareComposer()
-    : initialized_(false), request_display_callback_(nullptr) {}
-
-HardwareComposer::~HardwareComposer(void) {
-  UpdatePostThreadState(PostThreadState::Quit, true);
-  if (post_thread_.joinable())
-    post_thread_.join();
-  composer_callback_->SetVsyncService(nullptr);
-}
-
-void HardwareComposer::UpdateEdidData(Hwc2::Composer* composer,
-                                      hwc2_display_t hw_id) {
-  const auto error = composer->getDisplayIdentificationData(
-      hw_id, &display_port_, &display_identification_data_);
-  if (error != android::hardware::graphics::composer::V2_1::Error::NONE) {
-    if (error !=
-        android::hardware::graphics::composer::V2_1::Error::UNSUPPORTED) {
-      ALOGI("hardware_composer: identification data error\n");
-    } else {
-      ALOGI("hardware_composer: identification data unsupported\n");
-    }
-  }
-}
-
-bool HardwareComposer::Initialize(
-    Hwc2::Composer* composer, hwc2_display_t primary_display_id,
-    RequestDisplayCallback request_display_callback) {
-  if (initialized_) {
-    ALOGE("HardwareComposer::Initialize: already initialized.");
-    return false;
-  }
-
-  request_display_callback_ = request_display_callback;
-
-  primary_display_ = GetDisplayParams(composer, primary_display_id, true);
-
-  vsync_service_ = new VsyncService;
-  sp<IServiceManager> sm(defaultServiceManager());
-  auto result = sm->addService(String16(VsyncService::GetServiceName()),
-      vsync_service_, false);
-  LOG_ALWAYS_FATAL_IF(result != android::OK,
-      "addService(%s) failed", VsyncService::GetServiceName());
-
-  post_thread_event_fd_.Reset(eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK));
-  LOG_ALWAYS_FATAL_IF(
-      !post_thread_event_fd_,
-      "HardwareComposer: Failed to create interrupt event fd : %s",
-      strerror(errno));
-
-  UpdateEdidData(composer, primary_display_id);
-
-  post_thread_ = std::thread(&HardwareComposer::PostThread, this);
-
-  initialized_ = true;
-
-  return initialized_;
-}
-
-void HardwareComposer::Enable() {
-  UpdatePostThreadState(PostThreadState::Suspended, false);
-}
-
-void HardwareComposer::Disable() {
-  UpdatePostThreadState(PostThreadState::Suspended, true);
-
-  std::unique_lock<std::mutex> lock(post_thread_mutex_);
-  post_thread_ready_.wait(lock, [this] {
-    return !post_thread_resumed_;
-  });
-}
-
-void HardwareComposer::OnBootFinished() {
-  std::lock_guard<std::mutex> lock(post_thread_mutex_);
-  if (boot_finished_)
-    return;
-  boot_finished_ = true;
-  post_thread_wait_.notify_one();
-}
-
-// Update the post thread quiescent state based on idle and suspended inputs.
-void HardwareComposer::UpdatePostThreadState(PostThreadStateType state,
-                                             bool suspend) {
-  std::unique_lock<std::mutex> lock(post_thread_mutex_);
-
-  // Update the votes in the state variable before evaluating the effective
-  // quiescent state. Any bits set in post_thread_state_ indicate that the post
-  // thread should be suspended.
-  if (suspend) {
-    post_thread_state_ |= state;
-  } else {
-    post_thread_state_ &= ~state;
-  }
-
-  const bool quit = post_thread_state_ & PostThreadState::Quit;
-  const bool effective_suspend = post_thread_state_ != PostThreadState::Active;
-  if (quit) {
-    post_thread_quiescent_ = true;
-    eventfd_write(post_thread_event_fd_.Get(), 1);
-    post_thread_wait_.notify_one();
-  } else if (effective_suspend && !post_thread_quiescent_) {
-    post_thread_quiescent_ = true;
-    eventfd_write(post_thread_event_fd_.Get(), 1);
-  } else if (!effective_suspend && post_thread_quiescent_) {
-    post_thread_quiescent_ = false;
-    eventfd_t value;
-    eventfd_read(post_thread_event_fd_.Get(), &value);
-    post_thread_wait_.notify_one();
-  }
-}
-
-void HardwareComposer::CreateComposer() {
-  if (composer_)
-    return;
-  composer_.reset(new Hwc2::impl::Composer("default"));
-  composer_callback_ = new ComposerCallback;
-  composer_->registerCallback(composer_callback_);
-  LOG_ALWAYS_FATAL_IF(!composer_callback_->GotFirstHotplug(),
-      "Registered composer callback but didn't get hotplug for primary"
-      " display");
-  composer_callback_->SetVsyncService(vsync_service_);
-}
-
-void HardwareComposer::OnPostThreadResumed() {
-  ALOGI("OnPostThreadResumed");
-  EnableDisplay(*target_display_, true);
-
-  // Trigger target-specific performance mode change.
-  property_set(kDvrPerformanceProperty, "performance");
-}
-
-void HardwareComposer::OnPostThreadPaused() {
-  ALOGI("OnPostThreadPaused");
-  retire_fence_fds_.clear();
-  layers_.clear();
-
-  // Phones create a new composer client on resume and destroy it on pause.
-  if (composer_callback_ != nullptr) {
-    composer_callback_->SetVsyncService(nullptr);
-    composer_callback_ = nullptr;
-  }
-  composer_.reset(nullptr);
-
-  // Trigger target-specific performance mode change.
-  property_set(kDvrPerformanceProperty, "idle");
-}
-
-bool HardwareComposer::PostThreadCondWait(std::unique_lock<std::mutex>& lock,
-                                          int timeout_sec,
-                                          const std::function<bool()>& pred) {
-  auto pred_with_quit = [&] {
-    return pred() || (post_thread_state_ & PostThreadState::Quit);
-  };
-  if (timeout_sec >= 0) {
-    post_thread_wait_.wait_for(lock, std::chrono::seconds(timeout_sec),
-                               pred_with_quit);
-  } else {
-    post_thread_wait_.wait(lock, pred_with_quit);
-  }
-  if (post_thread_state_ & PostThreadState::Quit) {
-    ALOGI("HardwareComposer::PostThread: Quitting.");
-    return true;
-  }
-  return false;
-}
-
-HWC::Error HardwareComposer::Validate(hwc2_display_t display) {
-  uint32_t num_types;
-  uint32_t num_requests;
-  HWC::Error error =
-      composer_->validateDisplay(display, &num_types, &num_requests);
-
-  if (error == HWC2_ERROR_HAS_CHANGES) {
-    ALOGE("Hardware composer has requested composition changes, "
-          "which we don't support.");
-    // Accept the changes anyway and see if we can get something on the screen.
-    error = composer_->acceptDisplayChanges(display);
-  }
-
-  return error;
-}
-
-bool HardwareComposer::EnableVsync(const DisplayParams& display, bool enabled) {
-  HWC::Error error = composer_->setVsyncEnabled(display.id,
-      (Hwc2::IComposerClient::Vsync)(enabled ? HWC2_VSYNC_ENABLE
-                                             : HWC2_VSYNC_DISABLE));
-  if (error != HWC::Error::None) {
-    ALOGE("Error attempting to %s vsync on %s display: %s",
-        enabled ? "enable" : "disable", GetDisplayName(display.is_primary),
-        error.to_string().c_str());
-  }
-  return error == HWC::Error::None;
-}
-
-bool HardwareComposer::SetPowerMode(const DisplayParams& display, bool active) {
-  ALOGI("Turning %s display %s", GetDisplayName(display.is_primary),
-      active ? "on" : "off");
-  HWC::PowerMode power_mode = active ? HWC::PowerMode::On : HWC::PowerMode::Off;
-  HWC::Error error = composer_->setPowerMode(display.id,
-      power_mode.cast<Hwc2::IComposerClient::PowerMode>());
-  if (error != HWC::Error::None) {
-    ALOGE("Error attempting to turn %s display %s: %s",
-          GetDisplayName(display.is_primary), active ? "on" : "off",
-        error.to_string().c_str());
-  }
-  return error == HWC::Error::None;
-}
-
-bool HardwareComposer::EnableDisplay(const DisplayParams& display,
-                                     bool enabled) {
-  bool power_result;
-  bool vsync_result;
-  // When turning a display on, we set the power state then set vsync. When
-  // turning a display off we do it in the opposite order.
-  if (enabled) {
-    power_result = SetPowerMode(display, enabled);
-    vsync_result = EnableVsync(display, enabled);
-  } else {
-    vsync_result = EnableVsync(display, enabled);
-    power_result = SetPowerMode(display, enabled);
-  }
-  return power_result && vsync_result;
-}
-
-HWC::Error HardwareComposer::Present(hwc2_display_t display) {
-  int32_t present_fence;
-  HWC::Error error = composer_->presentDisplay(display, &present_fence);
-
-  // According to the documentation, this fence is signaled at the time of
-  // vsync/DMA for physical displays.
-  if (error == HWC::Error::None) {
-    retire_fence_fds_.emplace_back(present_fence);
-  } else {
-    ATRACE_INT("HardwareComposer: PresentResult", error);
-  }
-
-  return error;
-}
-
-DisplayParams HardwareComposer::GetDisplayParams(
-    Hwc2::Composer* composer, hwc2_display_t display, bool is_primary) {
-  DisplayParams params;
-  params.id = display;
-  params.is_primary = is_primary;
-
-  Hwc2::Config config;
-  HWC::Error error = composer->getActiveConfig(display, &config);
-
-  if (error == HWC::Error::None) {
-    auto get_attr = [&](hwc2_attribute_t attr, const char* attr_name)
-        -> std::optional<int32_t> {
-      int32_t val;
-      HWC::Error error = composer->getDisplayAttribute(
-          display, config, (Hwc2::IComposerClient::Attribute)attr, &val);
-      if (error != HWC::Error::None) {
-        ALOGE("Failed to get %s display attr %s: %s",
-            GetDisplayName(is_primary), attr_name,
-            error.to_string().c_str());
-        return std::nullopt;
-      }
-      return val;
-    };
-
-    auto width = get_attr(HWC2_ATTRIBUTE_WIDTH, "width");
-    auto height = get_attr(HWC2_ATTRIBUTE_HEIGHT, "height");
-
-    if (width && height) {
-      params.width = *width;
-      params.height = *height;
-    } else {
-      ALOGI("Failed to get width and/or height for %s display. Using default"
-          " size %dx%d.", GetDisplayName(is_primary), kDefaultDisplayWidth,
-          kDefaultDisplayHeight);
-      params.width = kDefaultDisplayWidth;
-      params.height = kDefaultDisplayHeight;
-    }
-
-    auto vsync_period = get_attr(HWC2_ATTRIBUTE_VSYNC_PERIOD, "vsync period");
-    if (vsync_period) {
-      params.vsync_period_ns = *vsync_period;
-    } else {
-      ALOGI("Failed to get vsync period for %s display. Using default vsync"
-          " period %.2fms", GetDisplayName(is_primary),
-          static_cast<float>(kDefaultVsyncPeriodNs) / 1000000);
-      params.vsync_period_ns = kDefaultVsyncPeriodNs;
-    }
-
-    auto dpi_x = get_attr(HWC2_ATTRIBUTE_DPI_X, "DPI X");
-    auto dpi_y = get_attr(HWC2_ATTRIBUTE_DPI_Y, "DPI Y");
-    if (dpi_x && dpi_y) {
-      params.dpi.x = *dpi_x;
-      params.dpi.y = *dpi_y;
-    } else {
-      ALOGI("Failed to get dpi_x and/or dpi_y for %s display. Using default"
-          " dpi %d.", GetDisplayName(is_primary), kDefaultDpi);
-      params.dpi.x = kDefaultDpi;
-      params.dpi.y = kDefaultDpi;
-    }
-  } else {
-    ALOGE("HardwareComposer: Failed to get current %s display config: %d."
-        " Using default display values.",
-        GetDisplayName(is_primary), error.value);
-    params.width = kDefaultDisplayWidth;
-    params.height = kDefaultDisplayHeight;
-    params.dpi.x = kDefaultDpi;
-    params.dpi.y = kDefaultDpi;
-    params.vsync_period_ns = kDefaultVsyncPeriodNs;
-  }
-
-  ALOGI(
-      "HardwareComposer: %s display attributes: width=%d height=%d "
-      "vsync_period_ns=%d DPI=%dx%d",
-      GetDisplayName(is_primary),
-      params.width,
-      params.height,
-      params.vsync_period_ns,
-      params.dpi.x,
-      params.dpi.y);
-
-  return params;
-}
-
-std::string HardwareComposer::Dump() {
-  std::unique_lock<std::mutex> lock(post_thread_mutex_);
-  std::ostringstream stream;
-
-  auto print_display_metrics = [&](const DisplayParams& params) {
-    stream << GetDisplayName(params.is_primary)
-           << " display metrics:     " << params.width << "x"
-           << params.height << " " << (params.dpi.x / 1000.0)
-           << "x" << (params.dpi.y / 1000.0) << " dpi @ "
-           << (1000000000.0 / params.vsync_period_ns) << " Hz"
-           << std::endl;
-  };
-
-  print_display_metrics(primary_display_);
-  if (external_display_)
-    print_display_metrics(*external_display_);
-
-  stream << "Post thread resumed: " << post_thread_resumed_ << std::endl;
-  stream << "Active layers:       " << layers_.size() << std::endl;
-  stream << std::endl;
-
-  for (size_t i = 0; i < layers_.size(); i++) {
-    stream << "Layer " << i << ":";
-    stream << " type=" << layers_[i].GetCompositionType().to_string();
-    stream << " surface_id=" << layers_[i].GetSurfaceId();
-    stream << " buffer_id=" << layers_[i].GetBufferId();
-    stream << std::endl;
-  }
-  stream << std::endl;
-
-  if (post_thread_resumed_) {
-    stream << "Hardware Composer Debug Info:" << std::endl;
-    stream << composer_->dumpDebugInfo();
-  }
-
-  return stream.str();
-}
-
-void HardwareComposer::PostLayers(hwc2_display_t display) {
-  ATRACE_NAME("HardwareComposer::PostLayers");
-
-  // Setup the hardware composer layers with current buffers.
-  for (auto& layer : layers_) {
-    layer.Prepare();
-  }
-
-  // Now that we have taken in a frame from the application, we have a chance
-  // to drop the frame before passing the frame along to HWC.
-  // If the display driver has become backed up, we detect it here and then
-  // react by skipping this frame to catch up latency.
-  while (!retire_fence_fds_.empty() &&
-         (!retire_fence_fds_.front() ||
-          sync_wait(retire_fence_fds_.front().Get(), 0) == 0)) {
-    // There are only 2 fences in here, no performance problem to shift the
-    // array of ints.
-    retire_fence_fds_.erase(retire_fence_fds_.begin());
-  }
-
-  const bool is_fence_pending = static_cast<int32_t>(retire_fence_fds_.size()) >
-                                post_thread_config_.allowed_pending_fence_count;
-
-  if (is_fence_pending) {
-    ATRACE_INT("frame_skip_count", ++frame_skip_count_);
-
-    ALOGW_IF(is_fence_pending,
-             "Warning: dropping a frame to catch up with HWC (pending = %zd)",
-             retire_fence_fds_.size());
-
-    for (auto& layer : layers_) {
-      layer.Drop();
-    }
-    return;
-  } else {
-    // Make the transition more obvious in systrace when the frame skip happens
-    // above.
-    ATRACE_INT("frame_skip_count", 0);
-  }
-
-#if TRACE > 1
-  for (size_t i = 0; i < layers_.size(); i++) {
-    ALOGI("HardwareComposer::PostLayers: layer=%zu buffer_id=%d composition=%s",
-          i, layers_[i].GetBufferId(),
-          layers_[i].GetCompositionType().to_string().c_str());
-  }
-#endif
-
-  HWC::Error error = Validate(display);
-  if (error != HWC::Error::None) {
-    ALOGE("HardwareComposer::PostLayers: Validate failed: %s display=%" PRIu64,
-          error.to_string().c_str(), display);
-    return;
-  }
-
-  error = Present(display);
-  if (error != HWC::Error::None) {
-    ALOGE("HardwareComposer::PostLayers: Present failed: %s",
-          error.to_string().c_str());
-    return;
-  }
-
-  std::vector<Hwc2::Layer> out_layers;
-  std::vector<int> out_fences;
-  error = composer_->getReleaseFences(display,
-                                      &out_layers, &out_fences);
-  ALOGE_IF(error != HWC::Error::None,
-           "HardwareComposer::PostLayers: Failed to get release fences: %s",
-           error.to_string().c_str());
-
-  // Perform post-frame bookkeeping.
-  uint32_t num_elements = out_layers.size();
-  for (size_t i = 0; i < num_elements; ++i) {
-    for (auto& layer : layers_) {
-      if (layer.GetLayerHandle() == out_layers[i]) {
-        layer.Finish(out_fences[i]);
-      }
-    }
-  }
-}
-
-void HardwareComposer::SetDisplaySurfaces(
-    std::vector<std::shared_ptr<DirectDisplaySurface>> surfaces) {
-  ALOGI("HardwareComposer::SetDisplaySurfaces: surface count=%zd",
-        surfaces.size());
-  const bool display_idle = surfaces.size() == 0;
-  {
-    std::unique_lock<std::mutex> lock(post_thread_mutex_);
-    surfaces_ = std::move(surfaces);
-    surfaces_changed_ = true;
-  }
-
-  if (request_display_callback_)
-    request_display_callback_(!display_idle);
-
-  // Set idle state based on whether there are any surfaces to handle.
-  UpdatePostThreadState(PostThreadState::Idle, display_idle);
-}
-
-int HardwareComposer::OnNewGlobalBuffer(DvrGlobalBufferKey key,
-                                        IonBuffer& ion_buffer) {
-  if (key == DvrGlobalBuffers::kVsyncBuffer) {
-    vsync_ring_ = std::make_unique<CPUMappedBroadcastRing<DvrVsyncRing>>(
-        &ion_buffer, CPUUsageMode::WRITE_OFTEN);
-
-    if (vsync_ring_->IsMapped() == false) {
-      return -EPERM;
-    }
-  }
-
-  if (key == DvrGlobalBuffers::kVrFlingerConfigBufferKey) {
-    return MapConfigBuffer(ion_buffer);
-  }
-
-  return 0;
-}
-
-void HardwareComposer::OnDeletedGlobalBuffer(DvrGlobalBufferKey key) {
-  if (key == DvrGlobalBuffers::kVrFlingerConfigBufferKey) {
-    ConfigBufferDeleted();
-  }
-}
-
-int HardwareComposer::MapConfigBuffer(IonBuffer& ion_buffer) {
-  std::lock_guard<std::mutex> lock(shared_config_mutex_);
-  shared_config_ring_ = DvrConfigRing();
-
-  if (ion_buffer.width() < DvrConfigRing::MemorySize()) {
-    ALOGE("HardwareComposer::MapConfigBuffer: invalid buffer size.");
-    return -EINVAL;
-  }
-
-  void* buffer_base = 0;
-  int result = ion_buffer.Lock(ion_buffer.usage(), 0, 0, ion_buffer.width(),
-                               ion_buffer.height(), &buffer_base);
-  if (result != 0) {
-    ALOGE(
-        "HardwareComposer::MapConfigBuffer: Failed to map vrflinger config "
-        "buffer.");
-    return -EPERM;
-  }
-
-  shared_config_ring_ = DvrConfigRing::Create(buffer_base, ion_buffer.width());
-  ion_buffer.Unlock();
-
-  return 0;
-}
-
-void HardwareComposer::ConfigBufferDeleted() {
-  std::lock_guard<std::mutex> lock(shared_config_mutex_);
-  shared_config_ring_ = DvrConfigRing();
-}
-
-void HardwareComposer::UpdateConfigBuffer() {
-  std::lock_guard<std::mutex> lock(shared_config_mutex_);
-  if (!shared_config_ring_.is_valid())
-    return;
-  // Copy from latest record in shared_config_ring_ to local copy.
-  DvrConfig record;
-  if (shared_config_ring_.GetNewest(&shared_config_ring_sequence_, &record)) {
-    ALOGI("DvrConfig updated: sequence %u, post offset %d",
-          shared_config_ring_sequence_, record.frame_post_offset_ns);
-    ++shared_config_ring_sequence_;
-    post_thread_config_ = record;
-  }
-}
-
-int HardwareComposer::PostThreadPollInterruptible(
-    const pdx::LocalHandle& event_fd, int requested_events, int timeout_ms) {
-  pollfd pfd[2] = {
-      {
-          .fd = event_fd.Get(),
-          .events = static_cast<short>(requested_events),
-          .revents = 0,
-      },
-      {
-          .fd = post_thread_event_fd_.Get(),
-          .events = POLLPRI | POLLIN,
-          .revents = 0,
-      },
-  };
-  int ret, error;
-  do {
-    ret = poll(pfd, 2, timeout_ms);
-    error = errno;
-    ALOGW_IF(ret < 0,
-             "HardwareComposer::PostThreadPollInterruptible: Error during "
-             "poll(): %s (%d)",
-             strerror(error), error);
-  } while (ret < 0 && error == EINTR);
-
-  if (ret < 0) {
-    return -error;
-  } else if (ret == 0) {
-    return -ETIMEDOUT;
-  } else if (pfd[0].revents != 0) {
-    return 0;
-  } else if (pfd[1].revents != 0) {
-    ALOGI("VrHwcPost thread interrupted: revents=%x", pfd[1].revents);
-    return kPostThreadInterrupted;
-  } else {
-    return 0;
-  }
-}
-
-// Sleep until the next predicted vsync, returning the predicted vsync
-// timestamp.
-Status<int64_t> HardwareComposer::WaitForPredictedVSync() {
-  const int64_t predicted_vsync_time = last_vsync_timestamp_ +
-      (target_display_->vsync_period_ns * vsync_prediction_interval_);
-  const int error = SleepUntil(predicted_vsync_time);
-  if (error < 0) {
-    ALOGE("HardwareComposer::WaifForVSync:: Failed to sleep: %s",
-          strerror(-error));
-    return error;
-  }
-  return {predicted_vsync_time};
-}
-
-int HardwareComposer::SleepUntil(int64_t wakeup_timestamp) {
-  const int timer_fd = vsync_sleep_timer_fd_.Get();
-  const itimerspec wakeup_itimerspec = {
-      .it_interval = {.tv_sec = 0, .tv_nsec = 0},
-      .it_value = NsToTimespec(wakeup_timestamp),
-  };
-  int ret =
-      timerfd_settime(timer_fd, TFD_TIMER_ABSTIME, &wakeup_itimerspec, nullptr);
-  int error = errno;
-  if (ret < 0) {
-    ALOGE("HardwareComposer::SleepUntil: Failed to set timerfd: %s",
-          strerror(error));
-    return -error;
-  }
-
-  return PostThreadPollInterruptible(vsync_sleep_timer_fd_, POLLIN,
-                                     /*timeout_ms*/ -1);
-}
-
-void HardwareComposer::PostThread() {
-  // NOLINTNEXTLINE(runtime/int)
-  prctl(PR_SET_NAME, reinterpret_cast<unsigned long>("VrHwcPost"), 0, 0, 0);
-
-  // Set the scheduler to SCHED_FIFO with high priority. If this fails here
-  // there may have been a startup timing issue between this thread and
-  // performanced. Try again later when this thread becomes active.
-  bool thread_policy_setup =
-      SetThreadPolicy("graphics:high", "/system/performance");
-
-  // Create a timerfd based on CLOCK_MONOTINIC.
-  vsync_sleep_timer_fd_.Reset(timerfd_create(CLOCK_MONOTONIC, 0));
-  LOG_ALWAYS_FATAL_IF(
-      !vsync_sleep_timer_fd_,
-      "HardwareComposer: Failed to create vsync sleep timerfd: %s",
-      strerror(errno));
-
-  struct VsyncEyeOffsets { int64_t left_ns, right_ns; };
-  bool was_running = false;
-
-  auto get_vsync_eye_offsets = [this]() -> VsyncEyeOffsets {
-    VsyncEyeOffsets offsets;
-    offsets.left_ns =
-        GetPosePredictionTimeOffset(target_display_->vsync_period_ns);
-
-    // TODO(jbates) Query vblank time from device, when such an API is
-    // available. This value (6.3%) was measured on A00 in low persistence mode.
-    int64_t vblank_ns = target_display_->vsync_period_ns * 63 / 1000;
-    offsets.right_ns = (target_display_->vsync_period_ns - vblank_ns) / 2;
-
-    // Check property for overriding right eye offset value.
-    offsets.right_ns =
-        property_get_int64(kRightEyeOffsetProperty, offsets.right_ns);
-
-    return offsets;
-  };
-
-  VsyncEyeOffsets vsync_eye_offsets = get_vsync_eye_offsets();
-
-  while (1) {
-    ATRACE_NAME("HardwareComposer::PostThread");
-
-    // Check for updated config once per vsync.
-    UpdateConfigBuffer();
-
-    while (post_thread_quiescent_) {
-      std::unique_lock<std::mutex> lock(post_thread_mutex_);
-      ALOGI("HardwareComposer::PostThread: Entering quiescent state.");
-
-      if (was_running) {
-        vsync_trace_parity_ = false;
-        ATRACE_INT(kVsyncTraceEventName, 0);
-      }
-
-      // Tear down resources.
-      OnPostThreadPaused();
-      was_running = false;
-      post_thread_resumed_ = false;
-      post_thread_ready_.notify_all();
-
-      if (PostThreadCondWait(lock, -1,
-                             [this] { return !post_thread_quiescent_; })) {
-        // A true return value means we've been asked to quit.
-        return;
-      }
-
-      post_thread_resumed_ = true;
-      post_thread_ready_.notify_all();
-
-      ALOGI("HardwareComposer::PostThread: Exiting quiescent state.");
-    }
-
-    if (!composer_)
-      CreateComposer();
-
-    bool target_display_changed = UpdateTargetDisplay();
-    bool just_resumed_running = !was_running;
-    was_running = true;
-
-    if (target_display_changed)
-      vsync_eye_offsets = get_vsync_eye_offsets();
-
-    if (just_resumed_running) {
-      OnPostThreadResumed();
-
-      // Try to setup the scheduler policy if it failed during startup. Only
-      // attempt to do this on transitions from inactive to active to avoid
-      // spamming the system with RPCs and log messages.
-      if (!thread_policy_setup) {
-        thread_policy_setup =
-            SetThreadPolicy("graphics:high", "/system/performance");
-      }
-    }
-
-    if (target_display_changed || just_resumed_running) {
-      // Initialize the last vsync timestamp with the current time. The
-      // predictor below uses this time + the vsync interval in absolute time
-      // units for the initial delay. Once the driver starts reporting vsync the
-      // predictor will sync up with the real vsync.
-      last_vsync_timestamp_ = GetSystemClockNs();
-      vsync_prediction_interval_ = 1;
-      retire_fence_fds_.clear();
-    }
-
-    int64_t vsync_timestamp = 0;
-    {
-      TRACE_FORMAT("wait_vsync|vsync=%u;last_timestamp=%" PRId64
-                   ";prediction_interval=%d|",
-                   vsync_count_ + 1, last_vsync_timestamp_,
-                   vsync_prediction_interval_);
-
-      auto status = WaitForPredictedVSync();
-      ALOGE_IF(
-          !status,
-          "HardwareComposer::PostThread: Failed to wait for vsync event: %s",
-          status.GetErrorMessage().c_str());
-
-      // If there was an error either sleeping was interrupted due to pausing or
-      // there was an error getting the latest timestamp.
-      if (!status)
-        continue;
-
-      // Predicted vsync timestamp for this interval. This is stable because we
-      // use absolute time for the wakeup timer.
-      vsync_timestamp = status.get();
-    }
-
-    vsync_trace_parity_ = !vsync_trace_parity_;
-    ATRACE_INT(kVsyncTraceEventName, vsync_trace_parity_ ? 1 : 0);
-
-    // Advance the vsync counter only if the system is keeping up with hardware
-    // vsync to give clients an indication of the delays.
-    if (vsync_prediction_interval_ == 1)
-      ++vsync_count_;
-
-    UpdateLayerConfig();
-
-    // Publish the vsync event.
-    if (vsync_ring_) {
-      DvrVsync vsync;
-      vsync.vsync_count = vsync_count_;
-      vsync.vsync_timestamp_ns = vsync_timestamp;
-      vsync.vsync_left_eye_offset_ns = vsync_eye_offsets.left_ns;
-      vsync.vsync_right_eye_offset_ns = vsync_eye_offsets.right_ns;
-      vsync.vsync_period_ns = target_display_->vsync_period_ns;
-
-      vsync_ring_->Publish(vsync);
-    }
-
-    {
-      // Sleep until shortly before vsync.
-      ATRACE_NAME("sleep");
-
-      const int64_t display_time_est_ns =
-          vsync_timestamp + target_display_->vsync_period_ns;
-      const int64_t now_ns = GetSystemClockNs();
-      const int64_t sleep_time_ns = display_time_est_ns - now_ns -
-                                    post_thread_config_.frame_post_offset_ns;
-      const int64_t wakeup_time_ns =
-          display_time_est_ns - post_thread_config_.frame_post_offset_ns;
-
-      ATRACE_INT64("sleep_time_ns", sleep_time_ns);
-      if (sleep_time_ns > 0) {
-        int error = SleepUntil(wakeup_time_ns);
-        ALOGE_IF(error < 0 && error != kPostThreadInterrupted,
-                 "HardwareComposer::PostThread: Failed to sleep: %s",
-                 strerror(-error));
-        // If the sleep was interrupted (error == kPostThreadInterrupted),
-        // we still go through and present this frame because we may have set
-        // layers earlier and we want to flush the Composer's internal command
-        // buffer by continuing through to validate and present.
-      }
-    }
-
-    {
-      auto status = composer_callback_->GetVsyncTime(target_display_->id);
-
-      // If we failed to read vsync there might be a problem with the driver.
-      // Since there's nothing we can do just behave as though we didn't get an
-      // updated vsync time and let the prediction continue.
-      const int64_t current_vsync_timestamp =
-          status ? status.get() : last_vsync_timestamp_;
-
-      const bool vsync_delayed =
-          last_vsync_timestamp_ == current_vsync_timestamp;
-      ATRACE_INT("vsync_delayed", vsync_delayed);
-
-      // If vsync was delayed advance the prediction interval and allow the
-      // fence logic in PostLayers() to skip the frame.
-      if (vsync_delayed) {
-        ALOGW(
-            "HardwareComposer::PostThread: VSYNC timestamp did not advance "
-            "since last frame: timestamp=%" PRId64 " prediction_interval=%d",
-            current_vsync_timestamp, vsync_prediction_interval_);
-        vsync_prediction_interval_++;
-      } else {
-        // We have an updated vsync timestamp, reset the prediction interval.
-        last_vsync_timestamp_ = current_vsync_timestamp;
-        vsync_prediction_interval_ = 1;
-      }
-    }
-
-    PostLayers(target_display_->id);
-  }
-}
-
-bool HardwareComposer::UpdateTargetDisplay() {
-  bool target_display_changed = false;
-  auto displays = composer_callback_->GetDisplays();
-  if (displays.external_display_was_hotplugged) {
-    bool was_using_external_display = !target_display_->is_primary;
-    if (was_using_external_display) {
-      // The external display was hotplugged, so make sure to ignore any bad
-      // display errors as we destroy the layers.
-      for (auto& layer: layers_)
-        layer.IgnoreBadDisplayErrorsOnDestroy(true);
-    }
-
-    if (displays.external_display) {
-      // External display was connected
-      external_display_ = GetDisplayParams(composer_.get(),
-          *displays.external_display, /*is_primary*/ false);
-
-      ALOGI("External display connected. Switching to external display.");
-      target_display_ = &(*external_display_);
-      target_display_changed = true;
-    } else {
-      // External display was disconnected
-      external_display_ = std::nullopt;
-      if (was_using_external_display) {
-        ALOGI("External display disconnected. Switching to primary display.");
-        target_display_ = &primary_display_;
-        target_display_changed = true;
-      }
-    }
-  }
-
-  if (target_display_changed) {
-    // If we're switching to the external display, turn the primary display off.
-    if (!target_display_->is_primary) {
-      EnableDisplay(primary_display_, false);
-    }
-    // If we're switching to the primary display, and the external display is
-    // still connected, turn the external display off.
-    else if (target_display_->is_primary && external_display_) {
-      EnableDisplay(*external_display_, false);
-    }
-
-    // Update the cached edid data for the current display.
-    UpdateEdidData(composer_.get(), target_display_->id);
-
-    // Turn the new target display on.
-    EnableDisplay(*target_display_, true);
-
-    // When we switch displays we need to recreate all the layers, so clear the
-    // current list, which will trigger layer recreation.
-    layers_.clear();
-  }
-
-  return target_display_changed;
-}
-
-// Checks for changes in the surface stack and updates the layer config to
-// accomodate the new stack.
-void HardwareComposer::UpdateLayerConfig() {
-  std::vector<std::shared_ptr<DirectDisplaySurface>> surfaces;
-  {
-    std::unique_lock<std::mutex> lock(post_thread_mutex_);
-
-    if (!surfaces_changed_ && (!layers_.empty() || surfaces_.empty()))
-      return;
-
-    surfaces = surfaces_;
-    surfaces_changed_ = false;
-  }
-
-  ATRACE_NAME("UpdateLayerConfig_HwLayers");
-
-  // Sort the new direct surface list by z-order to determine the relative order
-  // of the surfaces. This relative order is used for the HWC z-order value to
-  // insulate VrFlinger and HWC z-order semantics from each other.
-  std::sort(surfaces.begin(), surfaces.end(), [](const auto& a, const auto& b) {
-    return a->z_order() < b->z_order();
-  });
-
-  // Prepare a new layer stack, pulling in layers from the previous
-  // layer stack that are still active and updating their attributes.
-  std::vector<Layer> layers;
-  size_t layer_index = 0;
-  for (const auto& surface : surfaces) {
-    // The bottom layer is opaque, other layers blend.
-    HWC::BlendMode blending =
-        layer_index == 0 ? HWC::BlendMode::None : HWC::BlendMode::Coverage;
-
-    // Try to find a layer for this surface in the set of active layers.
-    auto search =
-        std::lower_bound(layers_.begin(), layers_.end(), surface->surface_id());
-    const bool found = search != layers_.end() &&
-                       search->GetSurfaceId() == surface->surface_id();
-    if (found) {
-      // Update the attributes of the layer that may have changed.
-      search->SetBlending(blending);
-      search->SetZOrder(layer_index);  // Relative z-order.
-
-      // Move the existing layer to the new layer set and remove the empty layer
-      // object from the current set.
-      layers.push_back(std::move(*search));
-      layers_.erase(search);
-    } else {
-      // Insert a layer for the new surface.
-      layers.emplace_back(composer_.get(), *target_display_, surface, blending,
-          HWC::Composition::Device, layer_index);
-    }
-
-    ALOGI_IF(
-        TRACE,
-        "HardwareComposer::UpdateLayerConfig: layer_index=%zu surface_id=%d",
-        layer_index, layers[layer_index].GetSurfaceId());
-
-    layer_index++;
-  }
-
-  // Sort the new layer stack by ascending surface id.
-  std::sort(layers.begin(), layers.end());
-
-  // Replace the previous layer set with the new layer set. The destructor of
-  // the previous set will clean up the remaining Layers that are not moved to
-  // the new layer set.
-  layers_ = std::move(layers);
-
-  ALOGD_IF(TRACE, "HardwareComposer::UpdateLayerConfig: %zd active layers",
-           layers_.size());
-}
-
-std::vector<sp<IVsyncCallback>>::const_iterator
-HardwareComposer::VsyncService::FindCallback(
-    const sp<IVsyncCallback>& callback) const {
-  sp<IBinder> binder = IInterface::asBinder(callback);
-  return std::find_if(callbacks_.cbegin(), callbacks_.cend(),
-                      [&](const sp<IVsyncCallback>& callback) {
-                        return IInterface::asBinder(callback) == binder;
-                      });
-}
-
-status_t HardwareComposer::VsyncService::registerCallback(
-    const sp<IVsyncCallback> callback) {
-  std::lock_guard<std::mutex> autolock(mutex_);
-  if (FindCallback(callback) == callbacks_.cend()) {
-    callbacks_.push_back(callback);
-  }
-  return OK;
-}
-
-status_t HardwareComposer::VsyncService::unregisterCallback(
-    const sp<IVsyncCallback> callback) {
-  std::lock_guard<std::mutex> autolock(mutex_);
-  auto iter = FindCallback(callback);
-  if (iter != callbacks_.cend()) {
-    callbacks_.erase(iter);
-  }
-  return OK;
-}
-
-void HardwareComposer::VsyncService::OnVsync(int64_t vsync_timestamp) {
-  ATRACE_NAME("VsyncService::OnVsync");
-  std::lock_guard<std::mutex> autolock(mutex_);
-  for (auto iter = callbacks_.begin(); iter != callbacks_.end();) {
-    if ((*iter)->onVsync(vsync_timestamp) == android::DEAD_OBJECT) {
-      iter = callbacks_.erase(iter);
-    } else {
-      ++iter;
-    }
-  }
-}
-
-Return<void> HardwareComposer::ComposerCallback::onHotplug(
-    Hwc2::Display display, IComposerCallback::Connection conn) {
-  std::lock_guard<std::mutex> lock(mutex_);
-  ALOGI("onHotplug display=%" PRIu64 " conn=%d", display, conn);
-
-  bool is_primary = !got_first_hotplug_ || display == primary_display_.id;
-
-  // Our first onHotplug callback is always for the primary display.
-  if (!got_first_hotplug_) {
-    LOG_ALWAYS_FATAL_IF(conn != IComposerCallback::Connection::CONNECTED,
-        "Initial onHotplug callback should be primary display connected");
-    got_first_hotplug_ = true;
-  } else if (is_primary) {
-    ALOGE("Ignoring unexpected onHotplug() call for primary display");
-    return Void();
-  }
-
-  if (conn == IComposerCallback::Connection::CONNECTED) {
-    if (!is_primary)
-      external_display_ = DisplayInfo();
-    DisplayInfo& display_info = is_primary ?
-        primary_display_ : *external_display_;
-    display_info.id = display;
-
-    std::array<char, 1024> buffer;
-    snprintf(buffer.data(), buffer.size(),
-             "/sys/class/graphics/fb%" PRIu64 "/vsync_event", display);
-    if (LocalHandle handle{buffer.data(), O_RDONLY}) {
-      ALOGI(
-          "HardwareComposer::ComposerCallback::onHotplug: Driver supports "
-          "vsync_event node for display %" PRIu64,
-          display);
-      display_info.driver_vsync_event_fd = std::move(handle);
-    } else {
-      ALOGI(
-          "HardwareComposer::ComposerCallback::onHotplug: Driver does not "
-          "support vsync_event node for display %" PRIu64,
-          display);
-    }
-  } else if (conn == IComposerCallback::Connection::DISCONNECTED) {
-    external_display_ = std::nullopt;
-  }
-
-  if (!is_primary)
-    external_display_was_hotplugged_ = true;
-
-  return Void();
-}
-
-Return<void> HardwareComposer::ComposerCallback::onRefresh(
-    Hwc2::Display /*display*/) {
-  return hardware::Void();
-}
-
-Return<void> HardwareComposer::ComposerCallback::onVsync(Hwc2::Display display,
-                                                         int64_t timestamp) {
-  TRACE_FORMAT("vsync_callback|display=%" PRIu64 ";timestamp=%" PRId64 "|",
-               display, timestamp);
-  std::lock_guard<std::mutex> lock(mutex_);
-  DisplayInfo* display_info = GetDisplayInfo(display);
-  if (display_info) {
-    display_info->callback_vsync_timestamp = timestamp;
-  }
-  if (primary_display_.id == display && vsync_service_ != nullptr) {
-    vsync_service_->OnVsync(timestamp);
-  }
-
-  return Void();
-}
-
-Return<void> HardwareComposer::ComposerCallback::onVsync_2_4(
-    Hwc2::Display /*display*/, int64_t /*timestamp*/,
-    Hwc2::VsyncPeriodNanos /*vsyncPeriodNanos*/) {
-  LOG_ALWAYS_FATAL("Unexpected onVsync_2_4 callback");
-  return Void();
-}
-
-Return<void> HardwareComposer::ComposerCallback::onVsyncPeriodTimingChanged(
-    Hwc2::Display /*display*/,
-    const Hwc2::VsyncPeriodChangeTimeline& /*updatedTimeline*/) {
-  LOG_ALWAYS_FATAL("Unexpected onVsyncPeriodTimingChanged callback");
-  return Void();
-}
-
-Return<void> HardwareComposer::ComposerCallback::onSeamlessPossible(
-    Hwc2::Display /*display*/) {
-  LOG_ALWAYS_FATAL("Unexpected onSeamlessPossible callback");
-  return Void();
-}
-
-void HardwareComposer::ComposerCallback::SetVsyncService(
-    const sp<VsyncService>& vsync_service) {
-  std::lock_guard<std::mutex> lock(mutex_);
-  vsync_service_ = vsync_service;
-}
-
-HardwareComposer::ComposerCallback::Displays
-HardwareComposer::ComposerCallback::GetDisplays() {
-  std::lock_guard<std::mutex> lock(mutex_);
-  Displays displays;
-  displays.primary_display = primary_display_.id;
-  if (external_display_)
-    displays.external_display = external_display_->id;
-  if (external_display_was_hotplugged_) {
-    external_display_was_hotplugged_ = false;
-    displays.external_display_was_hotplugged = true;
-  }
-  return displays;
-}
-
-Status<int64_t> HardwareComposer::ComposerCallback::GetVsyncTime(
-    hwc2_display_t display) {
-  std::lock_guard<std::mutex> autolock(mutex_);
-  DisplayInfo* display_info = GetDisplayInfo(display);
-  if (!display_info) {
-    ALOGW("Attempt to get vsync time for unknown display %" PRIu64, display);
-    return ErrorStatus(EINVAL);
-  }
-
-  // See if the driver supports direct vsync events.
-  LocalHandle& event_fd = display_info->driver_vsync_event_fd;
-  if (!event_fd) {
-    // Fall back to returning the last timestamp returned by the vsync
-    // callback.
-    return display_info->callback_vsync_timestamp;
-  }
-
-  // When the driver supports the vsync_event sysfs node we can use it to
-  // determine the latest vsync timestamp, even if the HWC callback has been
-  // delayed.
-
-  // The driver returns data in the form "VSYNC=<timestamp ns>".
-  std::array<char, 32> data;
-  data.fill('\0');
-
-  // Seek back to the beginning of the event file.
-  int ret = lseek(event_fd.Get(), 0, SEEK_SET);
-  if (ret < 0) {
-    const int error = errno;
-    ALOGE(
-        "HardwareComposer::ComposerCallback::GetVsyncTime: Failed to seek "
-        "vsync event fd: %s",
-        strerror(error));
-    return ErrorStatus(error);
-  }
-
-  // Read the vsync event timestamp.
-  ret = read(event_fd.Get(), data.data(), data.size());
-  if (ret < 0) {
-    const int error = errno;
-    ALOGE_IF(error != EAGAIN,
-             "HardwareComposer::ComposerCallback::GetVsyncTime: Error "
-             "while reading timestamp: %s",
-             strerror(error));
-    return ErrorStatus(error);
-  }
-
-  int64_t timestamp;
-  ret = sscanf(data.data(), "VSYNC=%" PRIu64,
-               reinterpret_cast<uint64_t*>(&timestamp));
-  if (ret < 0) {
-    const int error = errno;
-    ALOGE(
-        "HardwareComposer::ComposerCallback::GetVsyncTime: Error while "
-        "parsing timestamp: %s",
-        strerror(error));
-    return ErrorStatus(error);
-  }
-
-  return {timestamp};
-}
-
-HardwareComposer::ComposerCallback::DisplayInfo*
-HardwareComposer::ComposerCallback::GetDisplayInfo(hwc2_display_t display) {
-  if (display == primary_display_.id) {
-    return &primary_display_;
-  } else if (external_display_ && display == external_display_->id) {
-    return &(*external_display_);
-  }
-  return nullptr;
-}
-
-void Layer::Reset() {
-  if (hardware_composer_layer_) {
-    HWC::Error error =
-        composer_->destroyLayer(display_params_.id, hardware_composer_layer_);
-    if (error != HWC::Error::None &&
-        (!ignore_bad_display_errors_on_destroy_ ||
-         error != HWC::Error::BadDisplay)) {
-      ALOGE("destroyLayer() failed for display %" PRIu64 ", layer %" PRIu64
-          ". error: %s", display_params_.id, hardware_composer_layer_,
-          error.to_string().c_str());
-    }
-    hardware_composer_layer_ = 0;
-  }
-
-  z_order_ = 0;
-  blending_ = HWC::BlendMode::None;
-  composition_type_ = HWC::Composition::Invalid;
-  target_composition_type_ = composition_type_;
-  source_ = EmptyVariant{};
-  acquire_fence_.Close();
-  surface_rect_functions_applied_ = false;
-  pending_visibility_settings_ = true;
-  cached_buffer_map_.clear();
-  ignore_bad_display_errors_on_destroy_ = false;
-}
-
-Layer::Layer(Hwc2::Composer* composer, const DisplayParams& display_params,
-             const std::shared_ptr<DirectDisplaySurface>& surface,
-             HWC::BlendMode blending, HWC::Composition composition_type,
-             size_t z_order)
-    : composer_(composer),
-      display_params_(display_params),
-      z_order_{z_order},
-      blending_{blending},
-      target_composition_type_{composition_type},
-      source_{SourceSurface{surface}} {
-  CommonLayerSetup();
-}
-
-Layer::Layer(Hwc2::Composer* composer, const DisplayParams& display_params,
-             const std::shared_ptr<IonBuffer>& buffer, HWC::BlendMode blending,
-             HWC::Composition composition_type, size_t z_order)
-    : composer_(composer),
-      display_params_(display_params),
-      z_order_{z_order},
-      blending_{blending},
-      target_composition_type_{composition_type},
-      source_{SourceBuffer{buffer}} {
-  CommonLayerSetup();
-}
-
-Layer::~Layer() { Reset(); }
-
-Layer::Layer(Layer&& other) noexcept { *this = std::move(other); }
-
-Layer& Layer::operator=(Layer&& other) noexcept {
-  if (this != &other) {
-    Reset();
-    using std::swap;
-    swap(composer_, other.composer_);
-    swap(display_params_, other.display_params_);
-    swap(hardware_composer_layer_, other.hardware_composer_layer_);
-    swap(z_order_, other.z_order_);
-    swap(blending_, other.blending_);
-    swap(composition_type_, other.composition_type_);
-    swap(target_composition_type_, other.target_composition_type_);
-    swap(source_, other.source_);
-    swap(acquire_fence_, other.acquire_fence_);
-    swap(surface_rect_functions_applied_,
-         other.surface_rect_functions_applied_);
-    swap(pending_visibility_settings_, other.pending_visibility_settings_);
-    swap(cached_buffer_map_, other.cached_buffer_map_);
-    swap(ignore_bad_display_errors_on_destroy_,
-         other.ignore_bad_display_errors_on_destroy_);
-  }
-  return *this;
-}
-
-void Layer::UpdateBuffer(const std::shared_ptr<IonBuffer>& buffer) {
-  if (source_.is<SourceBuffer>())
-    std::get<SourceBuffer>(source_) = {buffer};
-}
-
-void Layer::SetBlending(HWC::BlendMode blending) {
-  if (blending_ != blending) {
-    blending_ = blending;
-    pending_visibility_settings_ = true;
-  }
-}
-
-void Layer::SetZOrder(size_t z_order) {
-  if (z_order_ != z_order) {
-    z_order_ = z_order;
-    pending_visibility_settings_ = true;
-  }
-}
-
-IonBuffer* Layer::GetBuffer() {
-  struct Visitor {
-    IonBuffer* operator()(SourceSurface& source) { return source.GetBuffer(); }
-    IonBuffer* operator()(SourceBuffer& source) { return source.GetBuffer(); }
-    IonBuffer* operator()(EmptyVariant) { return nullptr; }
-  };
-  return source_.Visit(Visitor{});
-}
-
-void Layer::UpdateVisibilitySettings() {
-  if (pending_visibility_settings_) {
-    pending_visibility_settings_ = false;
-
-    HWC::Error error;
-
-    error = composer_->setLayerBlendMode(
-        display_params_.id, hardware_composer_layer_,
-        blending_.cast<Hwc2::IComposerClient::BlendMode>());
-    ALOGE_IF(error != HWC::Error::None,
-             "Layer::UpdateLayerSettings: Error setting layer blend mode: %s",
-             error.to_string().c_str());
-
-    error = composer_->setLayerZOrder(display_params_.id,
-        hardware_composer_layer_, z_order_);
-    ALOGE_IF(error != HWC::Error::None,
-             "Layer::UpdateLayerSettings: Error setting z_ order: %s",
-             error.to_string().c_str());
-  }
-}
-
-void Layer::UpdateLayerSettings() {
-  HWC::Error error;
-
-  UpdateVisibilitySettings();
-
-  // TODO(eieio): Use surface attributes or some other mechanism to control
-  // the layer display frame.
-  error = composer_->setLayerDisplayFrame(
-      display_params_.id, hardware_composer_layer_,
-      {0, 0, display_params_.width, display_params_.height});
-  ALOGE_IF(error != HWC::Error::None,
-           "Layer::UpdateLayerSettings: Error setting layer display frame: %s",
-           error.to_string().c_str());
-
-  error = composer_->setLayerVisibleRegion(
-      display_params_.id, hardware_composer_layer_,
-      {{0, 0, display_params_.width, display_params_.height}});
-  ALOGE_IF(error != HWC::Error::None,
-           "Layer::UpdateLayerSettings: Error setting layer visible region: %s",
-           error.to_string().c_str());
-
-  error = composer_->setLayerPlaneAlpha(display_params_.id,
-      hardware_composer_layer_, 1.0f);
-  ALOGE_IF(error != HWC::Error::None,
-           "Layer::UpdateLayerSettings: Error setting layer plane alpha: %s",
-           error.to_string().c_str());
-}
-
-void Layer::CommonLayerSetup() {
-  HWC::Error error = composer_->createLayer(display_params_.id,
-                                            &hardware_composer_layer_);
-  ALOGE_IF(error != HWC::Error::None,
-           "Layer::CommonLayerSetup: Failed to create layer on primary "
-           "display: %s",
-           error.to_string().c_str());
-  UpdateLayerSettings();
-}
-
-bool Layer::CheckAndUpdateCachedBuffer(std::size_t slot, int buffer_id) {
-  auto search = cached_buffer_map_.find(slot);
-  if (search != cached_buffer_map_.end() && search->second == buffer_id)
-    return true;
-
-  // Assign or update the buffer slot.
-  if (buffer_id >= 0)
-    cached_buffer_map_[slot] = buffer_id;
-  return false;
-}
-
-void Layer::Prepare() {
-  int right, bottom, id;
-  sp<GraphicBuffer> handle;
-  std::size_t slot;
-
-  // Acquire the next buffer according to the type of source.
-  IfAnyOf<SourceSurface, SourceBuffer>::Call(&source_, [&](auto& source) {
-    std::tie(right, bottom, id, handle, acquire_fence_, slot) =
-        source.Acquire();
-  });
-
-  TRACE_FORMAT("Layer::Prepare|buffer_id=%d;slot=%zu|", id, slot);
-
-  // Update any visibility (blending, z-order) changes that occurred since
-  // last prepare.
-  UpdateVisibilitySettings();
-
-  // When a layer is first setup there may be some time before the first
-  // buffer arrives. Setup the HWC layer as a solid color to stall for time
-  // until the first buffer arrives. Once the first buffer arrives there will
-  // always be a buffer for the frame even if it is old.
-  if (!handle.get()) {
-    if (composition_type_ == HWC::Composition::Invalid) {
-      composition_type_ = HWC::Composition::SolidColor;
-      composer_->setLayerCompositionType(
-          display_params_.id, hardware_composer_layer_,
-          composition_type_.cast<Hwc2::IComposerClient::Composition>());
-      Hwc2::IComposerClient::Color layer_color = {0, 0, 0, 0};
-      composer_->setLayerColor(display_params_.id, hardware_composer_layer_,
-                               layer_color);
-    } else {
-      // The composition type is already set. Nothing else to do until a
-      // buffer arrives.
-    }
-  } else {
-    if (composition_type_ != target_composition_type_) {
-      composition_type_ = target_composition_type_;
-      composer_->setLayerCompositionType(
-          display_params_.id, hardware_composer_layer_,
-          composition_type_.cast<Hwc2::IComposerClient::Composition>());
-    }
-
-    // See if the HWC cache already has this buffer.
-    const bool cached = CheckAndUpdateCachedBuffer(slot, id);
-    if (cached)
-      handle = nullptr;
-
-    HWC::Error error{HWC::Error::None};
-    error =
-        composer_->setLayerBuffer(display_params_.id, hardware_composer_layer_,
-                                  slot, handle, acquire_fence_.Get());
-
-    ALOGE_IF(error != HWC::Error::None,
-             "Layer::Prepare: Error setting layer buffer: %s",
-             error.to_string().c_str());
-
-    if (!surface_rect_functions_applied_) {
-      const float float_right = right;
-      const float float_bottom = bottom;
-      error = composer_->setLayerSourceCrop(display_params_.id,
-                                            hardware_composer_layer_,
-                                            {0, 0, float_right, float_bottom});
-
-      ALOGE_IF(error != HWC::Error::None,
-               "Layer::Prepare: Error setting layer source crop: %s",
-               error.to_string().c_str());
-
-      surface_rect_functions_applied_ = true;
-    }
-  }
-}
-
-void Layer::Finish(int release_fence_fd) {
-  IfAnyOf<SourceSurface, SourceBuffer>::Call(
-      &source_, [release_fence_fd](auto& source) {
-        source.Finish(LocalHandle(release_fence_fd));
-      });
-}
-
-void Layer::Drop() { acquire_fence_.Close(); }
-
-}  // namespace dvr
-}  // namespace android
diff --git a/libs/vr/libvrflinger/hardware_composer.h b/libs/vr/libvrflinger/hardware_composer.h
deleted file mode 100644
index bfce10b..0000000
--- a/libs/vr/libvrflinger/hardware_composer.h
+++ /dev/null
@@ -1,577 +0,0 @@
-#ifndef ANDROID_DVR_SERVICES_DISPLAYD_HARDWARE_COMPOSER_H_
-#define ANDROID_DVR_SERVICES_DISPLAYD_HARDWARE_COMPOSER_H_
-
-#include <ui/GraphicBuffer.h>
-#include "DisplayHardware/ComposerHal.h"
-#include "hwc_types.h"
-
-#include <dvr/dvr_shared_buffers.h>
-#include <hardware/gralloc.h>
-#include <log/log.h>
-
-#include <array>
-#include <condition_variable>
-#include <memory>
-#include <mutex>
-#include <optional>
-#include <thread>
-#include <tuple>
-#include <vector>
-
-#include <dvr/dvr_config.h>
-#include <dvr/dvr_vsync.h>
-#include <pdx/file_handle.h>
-#include <pdx/rpc/variant.h>
-#include <private/dvr/shared_buffer_helpers.h>
-#include <private/dvr/vsync_service.h>
-
-#include "DisplayHardware/DisplayIdentification.h"
-#include "acquired_buffer.h"
-#include "display_surface.h"
-
-// Hardware composer HAL doesn't define HWC_TRANSFORM_NONE as of this writing.
-#ifndef HWC_TRANSFORM_NONE
-#define HWC_TRANSFORM_NONE static_cast<hwc_transform_t>(0)
-#endif
-
-namespace android {
-namespace dvr {
-
-// Basic display metrics for physical displays.
-struct DisplayParams {
-  hwc2_display_t id;
-  bool is_primary;
-
-  int width;
-  int height;
-
-  struct {
-    int x;
-    int y;
-  } dpi;
-
-  int vsync_period_ns;
-};
-
-// Layer represents the connection between a hardware composer layer and the
-// source supplying buffers for the layer's contents.
-class Layer {
- public:
-  Layer() = default;
-
-  // Sets up the layer to use a display surface as its content source. The Layer
-  // automatically handles ACQUIRE/RELEASE phases for the surface's buffer train
-  // every frame.
-  //
-  // |composer| The composer instance.
-  // |display_params| Info about the display to use.
-  // |blending| receives HWC_BLENDING_* values.
-  // |composition_type| receives either HWC_FRAMEBUFFER for most layers or
-  // HWC_FRAMEBUFFER_TARGET (unless you know what you are doing).
-  // |index| is the index of this surface in the DirectDisplaySurface array.
-  Layer(Hwc2::Composer* composer, const DisplayParams& display_params,
-        const std::shared_ptr<DirectDisplaySurface>& surface,
-        HWC::BlendMode blending, HWC::Composition composition_type,
-        size_t z_order);
-
-  // Sets up the layer to use a direct buffer as its content source. No special
-  // handling of the buffer is performed; responsibility for updating or
-  // changing the buffer each frame is on the caller.
-  //
-  // |composer| The composer instance.
-  // |display_params| Info about the display to use.
-  // |blending| receives HWC_BLENDING_* values.
-  // |composition_type| receives either HWC_FRAMEBUFFER for most layers or
-  // HWC_FRAMEBUFFER_TARGET (unless you know what you are doing).
-  Layer(Hwc2::Composer* composer, const DisplayParams& display_params,
-        const std::shared_ptr<IonBuffer>& buffer, HWC::BlendMode blending,
-        HWC::Composition composition_type, size_t z_order);
-
-  Layer(Layer&&) noexcept;
-  Layer& operator=(Layer&&) noexcept;
-
-  ~Layer();
-
-  // Releases any shared pointers and fence handles held by this instance.
-  void Reset();
-
-  // Layers that use a direct IonBuffer should call this each frame to update
-  // which buffer will be used for the next PostLayers.
-  void UpdateBuffer(const std::shared_ptr<IonBuffer>& buffer);
-
-  // Sets up the hardware composer layer for the next frame. When the layer is
-  // associated with a display surface, this method automatically ACQUIRES a new
-  // buffer if one is available.
-  void Prepare();
-
-  // After calling prepare, if this frame is to be dropped instead of passing
-  // along to the HWC, call Drop to close the contained fence(s).
-  void Drop();
-
-  // Performs fence bookkeeping after the frame has been posted to hardware
-  // composer.
-  void Finish(int release_fence_fd);
-
-  // Sets the blending for the layer. |blending| receives HWC_BLENDING_* values.
-  void SetBlending(HWC::BlendMode blending);
-
-  // Sets the z-order of this layer
-  void SetZOrder(size_t z_order);
-
-  // Gets the current IonBuffer associated with this layer. Ownership of the
-  // buffer DOES NOT pass to the caller and the pointer is not guaranteed to
-  // remain valid across calls to Layer::Setup(), Layer::Prepare(), or
-  // Layer::Reset(). YOU HAVE BEEN WARNED.
-  IonBuffer* GetBuffer();
-
-  HWC::Composition GetCompositionType() const { return composition_type_; }
-  HWC::Layer GetLayerHandle() const { return hardware_composer_layer_; }
-  bool IsLayerSetup() const { return !source_.empty(); }
-
-  int GetSurfaceId() const {
-    int surface_id = -1;
-    pdx::rpc::IfAnyOf<SourceSurface>::Call(
-        &source_, [&surface_id](const SourceSurface& surface_source) {
-          surface_id = surface_source.GetSurfaceId();
-        });
-    return surface_id;
-  }
-
-  int GetBufferId() const {
-    int buffer_id = -1;
-    pdx::rpc::IfAnyOf<SourceSurface>::Call(
-        &source_, [&buffer_id](const SourceSurface& surface_source) {
-          buffer_id = surface_source.GetBufferId();
-        });
-    return buffer_id;
-  }
-
-  // Compares Layers by surface id.
-  bool operator<(const Layer& other) const {
-    return GetSurfaceId() < other.GetSurfaceId();
-  }
-  bool operator<(int surface_id) const { return GetSurfaceId() < surface_id; }
-
-  void IgnoreBadDisplayErrorsOnDestroy(bool ignore) {
-    ignore_bad_display_errors_on_destroy_ = ignore;
-  }
-
- private:
-  void CommonLayerSetup();
-
-  // Applies all of the settings to this layer using the hwc functions
-  void UpdateLayerSettings();
-
-  // Applies visibility settings that may have changed.
-  void UpdateVisibilitySettings();
-
-  // Checks whether the buffer, given by id, is associated with the given slot
-  // in the HWC buffer cache. If the slot is not associated with the given
-  // buffer the cache is updated to establish the association and the buffer
-  // should be sent to HWC using setLayerBuffer. Returns true if the association
-  // was already established, false if not. A buffer_id of -1 is never
-  // associated and always returns false.
-  bool CheckAndUpdateCachedBuffer(std::size_t slot, int buffer_id);
-
-  // Composer instance.
-  Hwc2::Composer* composer_ = nullptr;
-
-  // Parameters of the display to use for this layer.
-  DisplayParams display_params_;
-
-  // The hardware composer layer and metrics to use during the prepare cycle.
-  hwc2_layer_t hardware_composer_layer_ = 0;
-
-  // Layer properties used to setup the hardware composer layer during the
-  // Prepare phase.
-  size_t z_order_ = 0;
-  HWC::BlendMode blending_ = HWC::BlendMode::None;
-  HWC::Composition composition_type_ = HWC::Composition::Invalid;
-  HWC::Composition target_composition_type_ = HWC::Composition::Device;
-
-  // State when the layer is connected to a surface. Provides the same interface
-  // as SourceBuffer to simplify internal use by Layer.
-  struct SourceSurface {
-    std::shared_ptr<DirectDisplaySurface> surface;
-    AcquiredBuffer acquired_buffer;
-    pdx::LocalHandle release_fence;
-
-    explicit SourceSurface(const std::shared_ptr<DirectDisplaySurface>& surface)
-        : surface(surface) {}
-
-    // Attempts to acquire a new buffer from the surface and return a tuple with
-    // width, height, buffer handle, and fence. If a new buffer is not available
-    // the previous buffer is returned or an empty value if no buffer has ever
-    // been posted. When a new buffer is acquired the previous buffer's release
-    // fence is passed out automatically.
-    std::tuple<int, int, int, sp<GraphicBuffer>, pdx::LocalHandle, std::size_t>
-    Acquire() {
-      if (surface->IsBufferAvailable()) {
-        acquired_buffer.Release(std::move(release_fence));
-        acquired_buffer = surface->AcquireCurrentBuffer();
-        ATRACE_ASYNC_END("BufferPost", acquired_buffer.buffer()->id());
-      }
-      if (!acquired_buffer.IsEmpty()) {
-        return std::make_tuple(
-            acquired_buffer.buffer()->width(),
-            acquired_buffer.buffer()->height(), acquired_buffer.buffer()->id(),
-            acquired_buffer.buffer()->buffer()->buffer(),
-            acquired_buffer.ClaimAcquireFence(), acquired_buffer.slot());
-      } else {
-        return std::make_tuple(0, 0, -1, nullptr, pdx::LocalHandle{}, 0);
-      }
-    }
-
-    void Finish(pdx::LocalHandle fence) { release_fence = std::move(fence); }
-
-    // Gets a pointer to the current acquired buffer or returns nullptr if there
-    // isn't one.
-    IonBuffer* GetBuffer() {
-      if (acquired_buffer.IsAvailable())
-        return acquired_buffer.buffer()->buffer();
-      else
-        return nullptr;
-    }
-
-    // Returns the surface id of the surface.
-    int GetSurfaceId() const { return surface->surface_id(); }
-
-    // Returns the buffer id for the current buffer.
-    int GetBufferId() const {
-      if (acquired_buffer.IsAvailable())
-        return acquired_buffer.buffer()->id();
-      else
-        return -1;
-    }
-  };
-
-  // State when the layer is connected to a buffer. Provides the same interface
-  // as SourceSurface to simplify internal use by Layer.
-  struct SourceBuffer {
-    std::shared_ptr<IonBuffer> buffer;
-
-    std::tuple<int, int, int, sp<GraphicBuffer>, pdx::LocalHandle, std::size_t>
-    Acquire() {
-      if (buffer)
-        return std::make_tuple(buffer->width(), buffer->height(), -1,
-                               buffer->buffer(), pdx::LocalHandle{}, 0);
-      else
-        return std::make_tuple(0, 0, -1, nullptr, pdx::LocalHandle{}, 0);
-    }
-
-    void Finish(pdx::LocalHandle /*fence*/) {}
-
-    IonBuffer* GetBuffer() { return buffer.get(); }
-
-    int GetSurfaceId() const { return -1; }
-    int GetBufferId() const { return -1; }
-  };
-
-  // The underlying hardware composer layer is supplied buffers either from a
-  // surface buffer train or from a buffer directly.
-  pdx::rpc::Variant<SourceSurface, SourceBuffer> source_;
-
-  pdx::LocalHandle acquire_fence_;
-  bool surface_rect_functions_applied_ = false;
-  bool pending_visibility_settings_ = true;
-
-  // Map of buffer slot assignments that have already been established with HWC:
-  // slot -> buffer_id. When this map contains a matching slot and buffer_id the
-  // buffer argument to setLayerBuffer may be nullptr to avoid the cost of
-  // importing a buffer HWC already knows about.
-  std::map<std::size_t, int> cached_buffer_map_;
-
-  // When calling destroyLayer() on an external display that's been removed we
-  // typically get HWC2_ERROR_BAD_DISPLAY errors. If
-  // ignore_bad_display_errors_on_destroy_ is true, don't log the bad display
-  // errors, since they're expected.
-  bool ignore_bad_display_errors_on_destroy_ = false;
-
-  Layer(const Layer&) = delete;
-  void operator=(const Layer&) = delete;
-};
-
-// HardwareComposer encapsulates the hardware composer HAL, exposing a
-// simplified API to post buffers to the display.
-//
-// HardwareComposer is accessed by both the vr flinger dispatcher thread and the
-// surface flinger main thread, in addition to internally running a separate
-// thread for compositing/EDS and posting layers to the HAL. When changing how
-// variables are used or adding new state think carefully about which threads
-// will access the state and whether it needs to be synchronized.
-class HardwareComposer {
- public:
-  using RequestDisplayCallback = std::function<void(bool)>;
-
-  HardwareComposer();
-  ~HardwareComposer();
-
-  bool Initialize(Hwc2::Composer* composer,
-                  hwc2_display_t primary_display_id,
-                  RequestDisplayCallback request_display_callback);
-
-  bool IsInitialized() const { return initialized_; }
-
-  // Start the post thread if there's work to do (i.e. visible layers). This
-  // should only be called from surface flinger's main thread.
-  void Enable();
-  // Pause the post thread, blocking until the post thread has signaled that
-  // it's paused. This should only be called from surface flinger's main thread.
-  void Disable();
-
-  // Called on a binder thread.
-  void OnBootFinished();
-
-  std::string Dump();
-
-  const DisplayParams& GetPrimaryDisplayParams() const {
-    return primary_display_;
-  }
-
-  // Sets the display surfaces to compose the hardware layer stack.
-  void SetDisplaySurfaces(
-      std::vector<std::shared_ptr<DirectDisplaySurface>> surfaces);
-
-  int OnNewGlobalBuffer(DvrGlobalBufferKey key, IonBuffer& ion_buffer);
-  void OnDeletedGlobalBuffer(DvrGlobalBufferKey key);
-
-  // Gets the edid data for the current active display (internal or external)
-  DisplayIdentificationData GetCurrentDisplayIdentificationData() {
-    return display_identification_data_;
-  }
-
-  // Gets the edid port for the current active display (internal or external)
-  uint8_t GetCurrentDisplayPort() { return display_port_; }
-
- private:
-  DisplayParams GetDisplayParams(Hwc2::Composer* composer,
-      hwc2_display_t display, bool is_primary);
-
-  // Turn display vsync on/off. Returns true on success, false on failure.
-  bool EnableVsync(const DisplayParams& display, bool enabled);
-  // Turn display power on/off. Returns true on success, false on failure.
-  bool SetPowerMode(const DisplayParams& display, bool active);
-  // Convenience function to turn a display on/off. Turns both power and vsync
-  // on/off. Returns true on success, false on failure.
-  bool EnableDisplay(const DisplayParams& display, bool enabled);
-
-  class VsyncService : public BnVsyncService {
-   public:
-    status_t registerCallback(const sp<IVsyncCallback> callback) override;
-    status_t unregisterCallback(const sp<IVsyncCallback> callback) override;
-    void OnVsync(int64_t vsync_timestamp);
-   private:
-    std::vector<sp<IVsyncCallback>>::const_iterator FindCallback(
-        const sp<IVsyncCallback>& callback) const;
-    std::mutex mutex_;
-    std::vector<sp<IVsyncCallback>> callbacks_;
-  };
-
-  class ComposerCallback : public Hwc2::IComposerCallback {
-   public:
-    ComposerCallback() = default;
-    hardware::Return<void> onHotplug(Hwc2::Display display,
-                                     Connection conn) override;
-    hardware::Return<void> onRefresh(Hwc2::Display display) override;
-    hardware::Return<void> onVsync(Hwc2::Display display,
-                                   int64_t timestamp) override;
-    hardware::Return<void> onVsync_2_4(
-        Hwc2::Display display, int64_t timestamp,
-        Hwc2::VsyncPeriodNanos vsyncPeriodNanos) override;
-    hardware::Return<void> onVsyncPeriodTimingChanged(
-        Hwc2::Display display,
-        const Hwc2::VsyncPeriodChangeTimeline& updatedTimeline) override;
-    hardware::Return<void> onSeamlessPossible(Hwc2::Display display) override;
-
-    bool GotFirstHotplug() { return got_first_hotplug_; }
-    void SetVsyncService(const sp<VsyncService>& vsync_service);
-
-    struct Displays {
-      hwc2_display_t primary_display = 0;
-      std::optional<hwc2_display_t> external_display;
-      bool external_display_was_hotplugged = false;
-    };
-
-    Displays GetDisplays();
-    pdx::Status<int64_t> GetVsyncTime(hwc2_display_t display);
-
-   private:
-    struct DisplayInfo {
-      hwc2_display_t id = 0;
-      pdx::LocalHandle driver_vsync_event_fd;
-      int64_t callback_vsync_timestamp{0};
-    };
-
-    DisplayInfo* GetDisplayInfo(hwc2_display_t display);
-
-    std::mutex mutex_;
-
-    bool got_first_hotplug_ = false;
-    DisplayInfo primary_display_;
-    std::optional<DisplayInfo> external_display_;
-    bool external_display_was_hotplugged_ = false;
-    sp<VsyncService> vsync_service_;
-  };
-
-  HWC::Error Validate(hwc2_display_t display);
-  HWC::Error Present(hwc2_display_t display);
-
-  void PostLayers(hwc2_display_t display);
-  void PostThread();
-
-  // The post thread has two controlling states:
-  // 1. Idle: no work to do (no visible surfaces).
-  // 2. Suspended: explicitly halted (system is not in VR mode).
-  // When either #1 or #2 is true then the post thread is quiescent, otherwise
-  // it is active.
-  using PostThreadStateType = uint32_t;
-  struct PostThreadState {
-    enum : PostThreadStateType {
-      Active = 0,
-      Idle = (1 << 0),
-      Suspended = (1 << 1),
-      Quit = (1 << 2),
-    };
-  };
-
-  void UpdatePostThreadState(uint32_t state, bool suspend);
-
-  // Blocks until either event_fd becomes readable, or we're interrupted by a
-  // control thread, or timeout_ms is reached before any events occur. Any
-  // errors are returned as negative errno values, with -ETIMEDOUT returned in
-  // the case of a timeout. If we're interrupted, kPostThreadInterrupted will be
-  // returned.
-  int PostThreadPollInterruptible(const pdx::LocalHandle& event_fd,
-                                  int requested_events, int timeout_ms);
-
-  // WaitForPredictedVSync and SleepUntil are blocking calls made on the post
-  // thread that can be interrupted by a control thread. If interrupted, these
-  // calls return kPostThreadInterrupted.
-  int ReadWaitPPState();
-  pdx::Status<int64_t> WaitForPredictedVSync();
-  int SleepUntil(int64_t wakeup_timestamp);
-
-  // Initialize any newly connected displays, and set target_display_ to the
-  // display we should render to. Returns true if target_display_
-  // changed. Called only from the post thread.
-  bool UpdateTargetDisplay();
-
-  // Reconfigures the layer stack if the display surfaces changed since the last
-  // frame. Called only from the post thread.
-  void UpdateLayerConfig();
-
-  // Called on the post thread to create the Composer instance.
-  void CreateComposer();
-
-  // Called on the post thread when the post thread is resumed.
-  void OnPostThreadResumed();
-  // Called on the post thread when the post thread is paused or quits.
-  void OnPostThreadPaused();
-
-  // Use post_thread_wait_ to wait for a specific condition, specified by pred.
-  // timeout_sec < 0 means wait indefinitely, otherwise it specifies the timeout
-  // in seconds.
-  // The lock must be held when this function is called.
-  // Returns true if the wait was interrupted because the post thread was asked
-  // to quit.
-  bool PostThreadCondWait(std::unique_lock<std::mutex>& lock,
-                          int timeout_sec,
-                          const std::function<bool()>& pred);
-
-  // Map the given shared memory buffer to our broadcast ring to track updates
-  // to the config parameters.
-  int MapConfigBuffer(IonBuffer& ion_buffer);
-  void ConfigBufferDeleted();
-  // Poll for config udpates.
-  void UpdateConfigBuffer();
-
-  bool initialized_;
-  bool is_standalone_device_;
-
-  std::unique_ptr<Hwc2::Composer> composer_;
-  sp<ComposerCallback> composer_callback_;
-  RequestDisplayCallback request_display_callback_;
-
-  DisplayParams primary_display_;
-  std::optional<DisplayParams> external_display_;
-  DisplayParams* target_display_ = &primary_display_;
-
-  // The list of surfaces we should draw. Set by the display service when
-  // DirectSurfaces are added, removed, or change visibility. Written by the
-  // message dispatch thread and read by the post thread.
-  std::vector<std::shared_ptr<DirectDisplaySurface>> surfaces_;
-  // Set to true by the dispatch thread whenever surfaces_ changes. Set to false
-  // by the post thread when the new list of surfaces is processed.
-  bool surfaces_changed_ = false;
-
-  std::vector<std::shared_ptr<DirectDisplaySurface>> current_surfaces_;
-
-  // Layer set for handling buffer flow into hardware composer layers. This
-  // vector must be sorted by surface_id in ascending order.
-  std::vector<Layer> layers_;
-
-  // The layer posting thread. This thread wakes up a short time before vsync to
-  // hand buffers to hardware composer.
-  std::thread post_thread_;
-
-  // Post thread state machine and synchronization primitives.
-  PostThreadStateType post_thread_state_{PostThreadState::Idle |
-                                         PostThreadState::Suspended};
-  std::atomic<bool> post_thread_quiescent_{true};
-  bool post_thread_resumed_{false};
-  pdx::LocalHandle post_thread_event_fd_;
-  std::mutex post_thread_mutex_;
-  std::condition_variable post_thread_wait_;
-  std::condition_variable post_thread_ready_;
-
-  // When boot is finished this will be set to true and the post thread will be
-  // notified via post_thread_wait_.
-  bool boot_finished_ = false;
-
-  // VSync sleep timerfd.
-  pdx::LocalHandle vsync_sleep_timer_fd_;
-
-  // The timestamp of the last vsync.
-  int64_t last_vsync_timestamp_ = 0;
-
-  // The number of vsync intervals to predict since the last vsync.
-  int vsync_prediction_interval_ = 1;
-
-  // Vsync count since display on.
-  uint32_t vsync_count_ = 0;
-
-  // Counter tracking the number of skipped frames.
-  int frame_skip_count_ = 0;
-
-  // Fd array for tracking retire fences that are returned by hwc. This allows
-  // us to detect when the display driver begins queuing frames.
-  std::vector<pdx::LocalHandle> retire_fence_fds_;
-
-  // If we are publishing vsync data, we will put it here.
-  std::unique_ptr<CPUMappedBroadcastRing<DvrVsyncRing>> vsync_ring_;
-
-  // Broadcast ring for receiving config data from the DisplayManager.
-  DvrConfigRing shared_config_ring_;
-  uint32_t shared_config_ring_sequence_{0};
-  // Config buffer for reading from the post thread.
-  DvrConfig post_thread_config_;
-  std::mutex shared_config_mutex_;
-
-  bool vsync_trace_parity_ = false;
-  sp<VsyncService> vsync_service_;
-
-  // Edid section.
-  void UpdateEdidData(Hwc2::Composer* composer, hwc2_display_t hw_id);
-  DisplayIdentificationData display_identification_data_;
-  uint8_t display_port_;
-
-  static constexpr int kPostThreadInterrupted = 1;
-
-  HardwareComposer(const HardwareComposer&) = delete;
-  void operator=(const HardwareComposer&) = delete;
-};
-
-}  // namespace dvr
-}  // namespace android
-
-#endif  // ANDROID_DVR_SERVICES_DISPLAYD_HARDWARE_COMPOSER_H_
diff --git a/libs/vr/libvrflinger/hwc_types.h b/libs/vr/libvrflinger/hwc_types.h
deleted file mode 100644
index 8b5c3b3..0000000
--- a/libs/vr/libvrflinger/hwc_types.h
+++ /dev/null
@@ -1,307 +0,0 @@
-#ifndef ANDROID_LIBVRFLINGER_HWCTYPES_H
-#define ANDROID_LIBVRFLINGER_HWCTYPES_H
-
-// General HWC type support. Hardware composer type support is a bit of a mess
-// between HWC1, HWC2 C/C++11, and HIDL types. Particularly bothersome is the
-// use of enum classes, which make analogous types between versions much
-// harder to deal with in a uniform way.
-//
-// These utilities help address some of these pains by providing a type-safe,
-// flexible interface to translate between different type spaces.
-
-#define HWC2_INCLUDE_STRINGIFICATION
-#define HWC2_USE_CPP11
-#include <hardware/hwcomposer2.h>
-#undef HWC2_INCLUDE_STRINGIFICATION
-#undef HWC2_USE_CPP11
-
-#include <string>
-#include <type_traits>
-
-namespace HWC {
-
-// Value types derived from HWC HAL types. Some of these are stand-alone,
-// while others are also wrapped in translator classes below.
-using ColorMode = int32_t;  // android_color_mode_t;
-using Config = hwc2_config_t;
-using ColorTransform =
-    std::underlying_type<android_color_transform_t>::type;          // int32_t;
-using Dataspace = std::underlying_type<android_dataspace_t>::type;  // int32_t;
-using DisplayId = hwc2_display_t;
-using DisplayRequest = std::underlying_type<HWC2::DisplayRequest>::type;
-using Hdr = std::underlying_type<android_hdr_t>::type;  // int32_t;
-using Layer = hwc2_layer_t;
-using PixelFormat =
-    std::underlying_type<android_pixel_format_t>::type;  // int32_t;
-
-// Type traits and casting utilities.
-
-// SFINAE utility to evaluate type expressions.
-template <typename...>
-using TestTypeExpression = void;
-
-// Traits type to determine the underlying type of an enum, integer,
-// or wrapper class.
-template <typename T, typename = typename std::is_enum<T>::type,
-          typename = typename std::is_integral<T>::type, typename = void>
-struct UnderlyingType {
-  using Type = T;
-};
-// Partial specialization that matches enum types. Captures the underlying type
-// of the enum in member type Type.
-template <typename T>
-struct UnderlyingType<T, std::true_type, std::false_type> {
-  using Type = typename std::underlying_type<T>::type;
-};
-// Partial specialization that matches integral types. Captures the type of the
-// integer in member type Type.
-template <typename T>
-struct UnderlyingType<T, std::false_type, std::true_type> {
-  using Type = T;
-};
-// Partial specialization that matches the wrapper types below. Captures
-// wrapper member type ValueType in member type Type.
-template <typename T>
-struct UnderlyingType<T, std::false_type, std::false_type,
-                      TestTypeExpression<typename T::ValueType>> {
-  using Type = typename T::ValueType;
-};
-
-// Enable if T is an enum with underlying type U.
-template <typename T, typename U, typename ReturnType = void>
-using EnableIfMatchingEnum = typename std::enable_if<
-    std::is_enum<T>::value &&
-        std::is_same<U, typename UnderlyingType<T>::Type>::value,
-    ReturnType>::type;
-
-// Enable if T and U are the same size/alignment and have the same underlying
-// type. Handles enum, integral, and wrapper classes below.
-template <typename T, typename U, typename Return = void>
-using EnableIfSafeCast = typename std::enable_if<
-    sizeof(T) == sizeof(U) && alignof(T) == alignof(U) &&
-        std::is_same<typename UnderlyingType<T>::Type,
-                     typename UnderlyingType<U>::Type>::value,
-    Return>::type;
-
-// Safely cast between std::vectors of matching enum/integer/wraper types.
-// Normally this is not possible with pendantic compiler type checks. However,
-// given the same size, alignment, and underlying type this is safe due to
-// allocator requirements and array-like element access guarantees.
-template <typename T, typename U>
-EnableIfSafeCast<T, U, std::vector<T>*> VectorCast(std::vector<U>* in) {
-  return reinterpret_cast<std::vector<T>*>(in);
-}
-
-// Translator classes that wrap specific HWC types to make translating
-// between different types (especially enum class) in code cleaner.
-
-// Base type for the enum wrappers below. This type provides type definitions
-// and implicit conversion logic common to each wrapper type.
-template <typename EnumType>
-struct Wrapper {
-  // Alias type of this instantiantion of Wrapper. Useful for inheriting
-  // constructors in subclasses via "using Base::Base;" statements.
-  using Base = Wrapper<EnumType>;
-
-  // The enum type wrapped by this instantiation of Wrapper.
-  using BaseType = EnumType;
-
-  // The underlying type of the base enum type.
-  using ValueType = typename UnderlyingType<BaseType>::Type;
-
-  // A default constructor is not defined here. Subclasses should define one
-  // as appropriate to define the correct inital value for the enum type.
-
-  // Default copy constructor.
-  Wrapper(const Wrapper&) = default;
-
-  // Implicit conversion from ValueType.
-  // NOLINTNEXTLINE(google-explicit-constructor)
-  Wrapper(ValueType value) : value(value) {}
-
-  // Implicit conversion from BaseType.
-  // NOLINTNEXTLINE(google-explicit-constructor)
-  Wrapper(BaseType value) : value(static_cast<ValueType>(value)) {}
-
-  // Implicit conversion from an enum type of the same underlying type.
-  template <typename T, typename = EnableIfMatchingEnum<T, ValueType>>
-  // NOLINTNEXTLINE(google-explicit-constructor)
-  Wrapper(const T& value) : value(static_cast<ValueType>(value)) {}
-
-  // Implicit conversion to BaseType.
-  // NOLINTNEXTLINE(google-explicit-constructor)
-  operator BaseType() const { return static_cast<BaseType>(value); }
-
-  // Implicit conversion to ValueType.
-  // NOLINTNEXTLINE(google-explicit-constructor)
-  operator ValueType() const { return value; }
-
-  template <typename T, typename = EnableIfMatchingEnum<T, ValueType>>
-  T cast() const {
-    return static_cast<T>(value);
-  }
-
-  // Converts to string using HWC2 stringification of BaseType.
-  std::string to_string() const {
-    return HWC2::to_string(static_cast<BaseType>(value));
-  }
-
-  bool operator!=(const Wrapper& other) const { return value != other.value; }
-  bool operator!=(ValueType other_value) const { return value != other_value; }
-  bool operator!=(BaseType other_value) const {
-    return static_cast<BaseType>(value) != other_value;
-  }
-  bool operator==(const Wrapper& other) const { return value == other.value; }
-  bool operator==(ValueType other_value) const { return value == other_value; }
-  bool operator==(BaseType other_value) const {
-    return static_cast<BaseType>(value) == other_value;
-  }
-
-  ValueType value;
-};
-
-struct Attribute final : public Wrapper<HWC2::Attribute> {
-  enum : ValueType {
-    Invalid = HWC2_ATTRIBUTE_INVALID,
-    Width = HWC2_ATTRIBUTE_WIDTH,
-    Height = HWC2_ATTRIBUTE_HEIGHT,
-    VsyncPeriod = HWC2_ATTRIBUTE_VSYNC_PERIOD,
-    DpiX = HWC2_ATTRIBUTE_DPI_X,
-    DpiY = HWC2_ATTRIBUTE_DPI_Y,
-  };
-
-  Attribute() : Base(Invalid) {}
-  using Base::Base;
-};
-
-struct BlendMode final : public Wrapper<HWC2::BlendMode> {
-  enum : ValueType {
-    Invalid = HWC2_BLEND_MODE_INVALID,
-    None = HWC2_BLEND_MODE_NONE,
-    Premultiplied = HWC2_BLEND_MODE_PREMULTIPLIED,
-    Coverage = HWC2_BLEND_MODE_COVERAGE,
-  };
-
-  BlendMode() : Base(Invalid) {}
-  using Base::Base;
-};
-
-struct Composition final : public Wrapper<HWC2::Composition> {
-  enum : ValueType {
-    Invalid = HWC2_COMPOSITION_INVALID,
-    Client = HWC2_COMPOSITION_CLIENT,
-    Device = HWC2_COMPOSITION_DEVICE,
-    SolidColor = HWC2_COMPOSITION_SOLID_COLOR,
-    Cursor = HWC2_COMPOSITION_CURSOR,
-    Sideband = HWC2_COMPOSITION_SIDEBAND,
-  };
-
-  Composition() : Base(Invalid) {}
-  using Base::Base;
-};
-
-struct DisplayType final : public Wrapper<HWC2::DisplayType> {
-  enum : ValueType {
-    Invalid = HWC2_DISPLAY_TYPE_INVALID,
-    Physical = HWC2_DISPLAY_TYPE_PHYSICAL,
-    Virtual = HWC2_DISPLAY_TYPE_VIRTUAL,
-  };
-
-  DisplayType() : Base(Invalid) {}
-  using Base::Base;
-};
-
-struct Error final : public Wrapper<HWC2::Error> {
-  enum : ValueType {
-    None = HWC2_ERROR_NONE,
-    BadConfig = HWC2_ERROR_BAD_CONFIG,
-    BadDisplay = HWC2_ERROR_BAD_DISPLAY,
-    BadLayer = HWC2_ERROR_BAD_LAYER,
-    BadParameter = HWC2_ERROR_BAD_PARAMETER,
-    HasChanges = HWC2_ERROR_HAS_CHANGES,
-    NoResources = HWC2_ERROR_NO_RESOURCES,
-    NotValidated = HWC2_ERROR_NOT_VALIDATED,
-    Unsupported = HWC2_ERROR_UNSUPPORTED,
-  };
-
-  Error() : Base(None) {}
-  using Base::Base;
-};
-
-struct LayerRequest final : public Wrapper<HWC2::LayerRequest> {
-  enum : ValueType {
-    ClearClientTarget = HWC2_LAYER_REQUEST_CLEAR_CLIENT_TARGET,
-  };
-
-  LayerRequest() : Base(0) {}
-  using Base::Base;
-};
-
-struct PowerMode final : public Wrapper<HWC2::PowerMode> {
-  enum : ValueType {
-    Off = HWC2_POWER_MODE_OFF,
-    DozeSuspend = HWC2_POWER_MODE_DOZE_SUSPEND,
-    Doze = HWC2_POWER_MODE_DOZE,
-    On = HWC2_POWER_MODE_ON,
-  };
-
-  PowerMode() : Base(Off) {}
-  using Base::Base;
-};
-
-struct Transform final : public Wrapper<HWC2::Transform> {
-  enum : ValueType {
-    None = 0,
-    FlipH = HWC_TRANSFORM_FLIP_H,
-    FlipV = HWC_TRANSFORM_FLIP_V,
-    Rotate90 = HWC_TRANSFORM_ROT_90,
-    Rotate180 = HWC_TRANSFORM_ROT_180,
-    Rotate270 = HWC_TRANSFORM_ROT_270,
-    FlipHRotate90 = HWC_TRANSFORM_FLIP_H_ROT_90,
-    FlipVRotate90 = HWC_TRANSFORM_FLIP_V_ROT_90,
-  };
-
-  Transform() : Base(None) {}
-  using Base::Base;
-};
-
-struct Vsync final : public Wrapper<HWC2::Vsync> {
-  enum : ValueType {
-    Invalid = HWC2_VSYNC_INVALID,
-    Enable = HWC2_VSYNC_ENABLE,
-    Disable = HWC2_VSYNC_DISABLE,
-  };
-
-  Vsync() : Base(Invalid) {}
-  using Base::Base;
-};
-
-// Utility color type.
-struct Color final {
-  Color(const Color&) = default;
-  Color(uint8_t r, uint8_t g, uint8_t b, uint8_t a) : r(r), g(g), b(b), a(a) {}
-  // NOLINTNEXTLINE(google-explicit-constructor)
-  Color(hwc_color_t color) : r(color.r), g(color.g), b(color.b), a(color.a) {}
-
-  // NOLINTNEXTLINE(google-explicit-constructor)
-  operator hwc_color_t() const { return {r, g, b, a}; }
-
-  uint8_t r __attribute__((aligned(1)));
-  uint8_t g __attribute__((aligned(1)));
-  uint8_t b __attribute__((aligned(1)));
-  uint8_t a __attribute__((aligned(1)));
-};
-
-// Utility rectangle type.
-struct Rect final {
-  // TODO(eieio): Implicit conversion to/from Android rect types.
-
-  int32_t left __attribute__((aligned(4)));
-  int32_t top __attribute__((aligned(4)));
-  int32_t right __attribute__((aligned(4)));
-  int32_t bottom __attribute__((aligned(4)));
-};
-
-}  // namespace HWC
-
-#endif  // ANDROID_LIBVRFLINGER_HWCTYPES_H
diff --git a/libs/vr/libvrflinger/include/dvr/vr_flinger.h b/libs/vr/libvrflinger/include/dvr/vr_flinger.h
deleted file mode 100644
index ae52076..0000000
--- a/libs/vr/libvrflinger/include/dvr/vr_flinger.h
+++ /dev/null
@@ -1,70 +0,0 @@
-#ifndef ANDROID_DVR_VR_FLINGER_H_
-#define ANDROID_DVR_VR_FLINGER_H_
-
-#include <thread>
-#include <memory>
-
-#define HWC2_INCLUDE_STRINGIFICATION
-#define HWC2_USE_CPP11
-#include <hardware/hwcomposer2.h>
-#undef HWC2_INCLUDE_STRINGIFICATION
-#undef HWC2_USE_CPP11
-
-#include <pdx/service_dispatcher.h>
-#include <vr/vr_manager/vr_manager.h>
-
-namespace android {
-
-namespace Hwc2 {
-class Composer;
-}  // namespace Hwc2
-
-namespace dvr {
-
-class DisplayService;
-
-class VrFlinger {
- public:
-  using RequestDisplayCallback = std::function<void(bool)>;
-  static std::unique_ptr<VrFlinger> Create(
-      Hwc2::Composer* hidl,
-      hwc2_display_t primary_display_id,
-      RequestDisplayCallback request_display_callback);
-  ~VrFlinger();
-
-  // These functions are all called on surface flinger's main thread.
-  void OnBootFinished();
-  void GrantDisplayOwnership();
-  void SeizeDisplayOwnership();
-
-  // dump all vr flinger state.
-  std::string Dump();
-
- private:
-  VrFlinger();
-  bool Init(Hwc2::Composer* hidl,
-            hwc2_display_t primary_display_id,
-            RequestDisplayCallback request_display_callback);
-
-  // Needs to be a separate class for binder's ref counting
-  class PersistentVrStateCallback : public BnPersistentVrStateCallbacks {
-   public:
-    explicit PersistentVrStateCallback(
-        RequestDisplayCallback request_display_callback)
-        : request_display_callback_(request_display_callback) {}
-    void onPersistentVrStateChanged(bool enabled) override;
-   private:
-    RequestDisplayCallback request_display_callback_;
-  };
-
-  std::thread dispatcher_thread_;
-  std::unique_ptr<android::pdx::ServiceDispatcher> dispatcher_;
-  std::shared_ptr<android::dvr::DisplayService> display_service_;
-  sp<PersistentVrStateCallback> persistent_vr_state_callback_;
-  RequestDisplayCallback request_display_callback_;
-};
-
-} // namespace dvr
-} // namespace android
-
-#endif // ANDROID_DVR_VR_FLINGER_H_
diff --git a/libs/vr/libvrflinger/tests/Android.bp b/libs/vr/libvrflinger/tests/Android.bp
deleted file mode 100644
index 095f556..0000000
--- a/libs/vr/libvrflinger/tests/Android.bp
+++ /dev/null
@@ -1,47 +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"],
-}
-
-shared_libs = [
-    "android.hardware.configstore-utils",
-    "android.hardware.configstore@1.0",
-    "libbinder",
-    "libbufferhubqueue",
-    "libcutils",
-    "libgui",
-    "libhidlbase",
-    "liblog",
-    "libui",
-    "libutils",
-    "libnativewindow",
-    "libpdx_default_transport",
-    "libSurfaceFlingerProp",
-]
-
-static_libs = [
-    "libdisplay",
-]
-
-cc_test {
-    srcs: ["vrflinger_test.cpp"],
-    // See go/apct-presubmit for documentation on how this .filter file is used
-    // by Android's automated testing infrastructure for test filtering.
-    data: ["vrflinger_test.filter"],
-    static_libs: static_libs,
-    shared_libs: shared_libs,
-    cflags: [
-        "-DLOG_TAG=\"VrFlingerTest\"",
-        "-DTRACE=0",
-        "-O0",
-        "-g",
-        "-Wall",
-        "-Werror",
-    ],
-    header_libs: ["libsurfaceflinger_headers"],
-    name: "vrflinger_test",
-}
diff --git a/libs/vr/libvrflinger/tests/vrflinger_test.cpp b/libs/vr/libvrflinger/tests/vrflinger_test.cpp
deleted file mode 100644
index ac44f74..0000000
--- a/libs/vr/libvrflinger/tests/vrflinger_test.cpp
+++ /dev/null
@@ -1,226 +0,0 @@
-#include <SurfaceFlingerProperties.h>
-#include <android/hardware/configstore/1.0/ISurfaceFlingerConfigs.h>
-#include <android/hardware/configstore/1.1/types.h>
-#include <android/hardware_buffer.h>
-#include <binder/IServiceManager.h>
-#include <binder/Parcel.h>
-#include <binder/ProcessState.h>
-#include <configstore/Utils.h>
-#include <cutils/properties.h>
-#include <gtest/gtest.h>
-#include <gui/ISurfaceComposer.h>
-#include <log/log.h>
-#include <utils/StrongPointer.h>
-
-#include <chrono>
-#include <memory>
-#include <mutex>
-#include <optional>
-#include <thread>
-
-#include <private/dvr/display_client.h>
-
-using namespace android::hardware::configstore;
-using namespace android::hardware::configstore::V1_0;
-using android::dvr::display::DisplayClient;
-using android::dvr::display::Surface;
-using android::dvr::display::SurfaceAttribute;
-using android::dvr::display::SurfaceAttributeValue;
-
-namespace android {
-namespace dvr {
-
-// The transaction code for asking surface flinger if vr flinger is active. This
-// is done as a hidden api since it's only used for tests. See the "case 1028"
-// block in SurfaceFlinger::onTransact() in SurfaceFlinger.cpp.
-constexpr uint32_t kIsVrFlingerActiveTransactionCode = 1028;
-
-// The maximum amount of time to give vr flinger to activate/deactivate. If the
-// switch hasn't completed in this amount of time, the test will fail.
-constexpr auto kVrFlingerSwitchMaxTime = std::chrono::seconds(1);
-
-// How long to wait between each check to see if the vr flinger switch
-// completed.
-constexpr auto kVrFlingerSwitchPollInterval = std::chrono::milliseconds(50);
-
-// A Binder connection to surface flinger.
-class SurfaceFlingerConnection {
- public:
-  static std::unique_ptr<SurfaceFlingerConnection> Create() {
-    sp<ISurfaceComposer> surface_flinger = interface_cast<ISurfaceComposer>(
-        defaultServiceManager()->getService(String16("SurfaceFlinger")));
-    if (surface_flinger == nullptr) {
-      return nullptr;
-    }
-
-    return std::unique_ptr<SurfaceFlingerConnection>(
-        new SurfaceFlingerConnection(surface_flinger));
-  }
-
-  // Returns true if the surface flinger process is still running. We use this
-  // to detect if surface flinger has crashed.
-  bool IsAlive() {
-    IInterface::asBinder(surface_flinger_)->pingBinder();
-    return IInterface::asBinder(surface_flinger_)->isBinderAlive();
-  }
-
-  // Return true if vr flinger is currently active, false otherwise. If there's
-  // an error communicating with surface flinger, std::nullopt is returned.
-  std::optional<bool> IsVrFlingerActive() {
-    Parcel data, reply;
-    status_t result =
-        data.writeInterfaceToken(surface_flinger_->getInterfaceDescriptor());
-    if (result != OK) {
-      return std::nullopt;
-    }
-    result = IInterface::asBinder(surface_flinger_)
-                 ->transact(kIsVrFlingerActiveTransactionCode, data, &reply);
-    if (result != OK) {
-      return std::nullopt;
-    }
-    bool vr_flinger_active;
-    result = reply.readBool(&vr_flinger_active);
-    if (result != OK) {
-      return std::nullopt;
-    }
-    return vr_flinger_active;
-  }
-
-  enum class VrFlingerSwitchResult : int8_t {
-    kSuccess,
-    kTimedOut,
-    kCommunicationError,
-    kSurfaceFlingerDied
-  };
-
-  // Wait for vr flinger to become active or inactive.
-  VrFlingerSwitchResult WaitForVrFlinger(bool wait_active) {
-    return WaitForVrFlingerTimed(wait_active, kVrFlingerSwitchPollInterval,
-        kVrFlingerSwitchMaxTime);
-  }
-
-  // Wait for vr flinger to become active or inactive, specifying custom timeouts.
-  VrFlingerSwitchResult WaitForVrFlingerTimed(bool wait_active,
-      std::chrono::milliseconds pollInterval, std::chrono::seconds timeout) {
-    auto start_time = std::chrono::steady_clock::now();
-    while (1) {
-      std::this_thread::sleep_for(pollInterval);
-      if (!IsAlive()) {
-        return VrFlingerSwitchResult::kSurfaceFlingerDied;
-      }
-      std::optional<bool> vr_flinger_active = IsVrFlingerActive();
-      if (!vr_flinger_active.has_value()) {
-        return VrFlingerSwitchResult::kCommunicationError;
-      }
-      if (vr_flinger_active.value() == wait_active) {
-        return VrFlingerSwitchResult::kSuccess;
-      } else if (std::chrono::steady_clock::now() - start_time > timeout) {
-        return VrFlingerSwitchResult::kTimedOut;
-      }
-    }
-  }
-
- private:
-  SurfaceFlingerConnection(sp<ISurfaceComposer> surface_flinger)
-      : surface_flinger_(surface_flinger) {}
-
-  sp<ISurfaceComposer> surface_flinger_ = nullptr;
-};
-
-// This test activates vr flinger by creating a vr flinger surface, then
-// deactivates vr flinger by destroying the surface. We verify that vr flinger
-// is activated and deactivated as expected, and that surface flinger doesn't
-// crash.
-//
-// If the device doesn't support vr flinger (as repoted by ConfigStore), the
-// test does nothing.
-//
-// If the device is a standalone vr device, the test also does nothing, since
-// this test verifies the behavior of display handoff from surface flinger to vr
-// flinger and back, and standalone devices never hand control of the display
-// back to surface flinger.
-TEST(VrFlingerTest, ActivateDeactivate) {
-  android::ProcessState::self()->startThreadPool();
-
-  // Exit immediately if the device doesn't support vr flinger. This ConfigStore
-  // check is the same mechanism used by surface flinger to decide if it should
-  // initialize vr flinger.
-  bool vr_flinger_enabled = android::sysprop::use_vr_flinger(false);
-  if (!vr_flinger_enabled) {
-    return;
-  }
-
-  auto surface_flinger_connection = SurfaceFlingerConnection::Create();
-  ASSERT_NE(surface_flinger_connection, nullptr);
-
-  // Verify we start off with vr flinger disabled.
-  ASSERT_TRUE(surface_flinger_connection->IsAlive());
-  auto vr_flinger_active = surface_flinger_connection->IsVrFlingerActive();
-  ASSERT_TRUE(vr_flinger_active.has_value());
-  ASSERT_FALSE(vr_flinger_active.value());
-
-  // Create a vr flinger surface, and verify vr flinger becomes active.
-  // Introduce a scope so that, at the end of the scope, the vr flinger surface
-  // is destroyed, and vr flinger deactivates.
-  {
-    auto display_client = DisplayClient::Create();
-    ASSERT_NE(display_client, nullptr);
-    auto metrics = display_client->GetDisplayMetrics();
-    ASSERT_TRUE(metrics.ok());
-
-    auto surface = Surface::CreateSurface({
-        {SurfaceAttribute::Direct, SurfaceAttributeValue(true)},
-        {SurfaceAttribute::Visible, SurfaceAttributeValue(true)},
-    });
-    ASSERT_TRUE(surface.ok());
-    ASSERT_TRUE(surface.get() != nullptr);
-
-    auto queue = surface.get()->CreateQueue(
-        metrics.get().display_width, metrics.get().display_height,
-        /*layer_count=*/1, AHARDWAREBUFFER_FORMAT_R8G8B8X8_UNORM,
-        AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE |
-            AHARDWAREBUFFER_USAGE_GPU_COLOR_OUTPUT |
-            AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN,
-        /*capacity=*/1,
-        /*metadata_size=*/0);
-    ASSERT_TRUE(queue.ok());
-    ASSERT_TRUE(queue.get() != nullptr);
-
-    size_t slot;
-    pdx::LocalHandle release_fence;
-    auto buffer = queue.get()->Dequeue(/*timeout=*/0, &slot, &release_fence);
-    ASSERT_TRUE(buffer.ok());
-    ASSERT_TRUE(buffer.get() != nullptr);
-
-    ASSERT_EQ(buffer.get()->width(), metrics.get().display_width);
-    ASSERT_EQ(buffer.get()->height(), metrics.get().display_height);
-
-    void* raw_buf = nullptr;
-    ASSERT_GE(buffer.get()->buffer()->Lock(
-                  AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN, /*x=*/0, /*y=*/0,
-                  buffer.get()->width(), buffer.get()->height(), &raw_buf),
-              0);
-    ASSERT_NE(raw_buf, nullptr);
-    uint32_t* pixels = static_cast<uint32_t*>(raw_buf);
-
-    for (int i = 0; i < buffer.get()->stride() * buffer.get()->height(); ++i) {
-      pixels[i] = 0x0000ff00;
-    }
-
-    ASSERT_GE(buffer.get()->buffer()->Unlock(), 0);
-
-    ASSERT_GE(buffer.get()->Post(/*ready_fence=*/pdx::LocalHandle()), 0);
-
-    ASSERT_EQ(
-        surface_flinger_connection->WaitForVrFlinger(/*wait_active=*/true),
-        SurfaceFlingerConnection::VrFlingerSwitchResult::kSuccess);
-  }
-
-  // Now that the vr flinger surface is destroyed, vr flinger should deactivate.
-  ASSERT_EQ(
-      surface_flinger_connection->WaitForVrFlinger(/*wait_active=*/false),
-      SurfaceFlingerConnection::VrFlingerSwitchResult::kSuccess);
-}
-
-}  // namespace dvr
-}  // namespace android
diff --git a/libs/vr/libvrflinger/tests/vrflinger_test.filter b/libs/vr/libvrflinger/tests/vrflinger_test.filter
deleted file mode 100644
index 030bb7b..0000000
--- a/libs/vr/libvrflinger/tests/vrflinger_test.filter
+++ /dev/null
@@ -1,5 +0,0 @@
-{
-        "presubmit": {
-            "filter": "BootVrFlingerTest.*"
-        }
-}
diff --git a/libs/vr/libvrflinger/vr_flinger.cpp b/libs/vr/libvrflinger/vr_flinger.cpp
deleted file mode 100644
index de7d8f8..0000000
--- a/libs/vr/libvrflinger/vr_flinger.cpp
+++ /dev/null
@@ -1,142 +0,0 @@
-#include <dvr/vr_flinger.h>
-
-#include <errno.h>
-#include <fcntl.h>
-#include <poll.h>
-#include <signal.h>
-#include <string.h>
-#include <time.h>
-#include <unistd.h>
-#include <memory>
-
-#include <binder/IServiceManager.h>
-#include <binder/ProcessState.h>
-#include <cutils/properties.h>
-#include <log/log.h>
-#include <private/dvr/display_client.h>
-#include <processgroup/sched_policy.h>
-#include <sys/prctl.h>
-#include <sys/resource.h>
-#include <utils/ThreadDefs.h>
-
-#include <functional>
-
-#include "DisplayHardware/ComposerHal.h"
-#include "display_manager_service.h"
-#include "display_service.h"
-
-namespace android {
-namespace dvr {
-
-std::unique_ptr<VrFlinger> VrFlinger::Create(
-    Hwc2::Composer* hidl, hwc2_display_t primary_display_id,
-    RequestDisplayCallback request_display_callback) {
-  std::unique_ptr<VrFlinger> vr_flinger(new VrFlinger);
-  if (vr_flinger->Init(hidl, primary_display_id, request_display_callback))
-    return vr_flinger;
-  else
-    return nullptr;
-}
-
-VrFlinger::VrFlinger() {}
-
-VrFlinger::~VrFlinger() {
-  if (persistent_vr_state_callback_.get()) {
-    sp<IVrManager> vr_manager = interface_cast<IVrManager>(
-        defaultServiceManager()->checkService(String16("vrmanager")));
-    if (vr_manager.get()) {
-      vr_manager->unregisterPersistentVrStateListener(
-          persistent_vr_state_callback_);
-    }
-  }
-
-  if (dispatcher_)
-    dispatcher_->SetCanceled(true);
-  if (dispatcher_thread_.joinable())
-    dispatcher_thread_.join();
-}
-
-bool VrFlinger::Init(Hwc2::Composer* hidl,
-                     hwc2_display_t primary_display_id,
-                     RequestDisplayCallback request_display_callback) {
-  if (!hidl || !request_display_callback)
-    return false;
-
-  std::shared_ptr<android::pdx::Service> service;
-
-  ALOGI("Starting up VrFlinger...");
-
-  // We need to be able to create endpoints with full perms.
-  umask(0000);
-
-  android::ProcessState::self()->startThreadPool();
-
-  request_display_callback_ = request_display_callback;
-
-  dispatcher_ = android::pdx::ServiceDispatcher::Create();
-  CHECK_ERROR(!dispatcher_, error, "Failed to create service dispatcher.");
-
-  display_service_ = android::dvr::DisplayService::Create(
-      hidl, primary_display_id, request_display_callback);
-  CHECK_ERROR(!display_service_, error, "Failed to create display service.");
-  dispatcher_->AddService(display_service_);
-
-  service = android::dvr::DisplayManagerService::Create(display_service_);
-  CHECK_ERROR(!service, error, "Failed to create display manager service.");
-  dispatcher_->AddService(service);
-
-  dispatcher_thread_ = std::thread([this]() {
-    prctl(PR_SET_NAME, reinterpret_cast<unsigned long>("VrDispatch"), 0, 0, 0);
-    ALOGI("Entering message loop.");
-
-    setpriority(PRIO_PROCESS, 0, android::PRIORITY_URGENT_DISPLAY);
-    set_sched_policy(0, SP_FOREGROUND);
-
-    int ret = dispatcher_->EnterDispatchLoop();
-    if (ret < 0) {
-      ALOGE("Dispatch loop exited because: %s\n", strerror(-ret));
-    }
-  });
-
-  return true;
-
-error:
-  return false;
-}
-
-void VrFlinger::OnBootFinished() {
-  display_service_->OnBootFinished();
-  sp<IVrManager> vr_manager = interface_cast<IVrManager>(
-      defaultServiceManager()->checkService(String16("vrmanager")));
-  if (vr_manager.get()) {
-    persistent_vr_state_callback_ =
-        new PersistentVrStateCallback(request_display_callback_);
-    vr_manager->registerPersistentVrStateListener(
-        persistent_vr_state_callback_);
-  } else {
-    ALOGE("Unable to register vr flinger for persistent vr mode changes");
-  }
-}
-
-void VrFlinger::GrantDisplayOwnership() {
-  display_service_->GrantDisplayOwnership();
-}
-
-void VrFlinger::SeizeDisplayOwnership() {
-  display_service_->SeizeDisplayOwnership();
-}
-
-std::string VrFlinger::Dump() {
-  // TODO(karthikrs): Add more state information here.
-  return display_service_->DumpState(0/*unused*/);
-}
-
-void VrFlinger::PersistentVrStateCallback::onPersistentVrStateChanged(
-    bool enabled) {
-  ALOGV("Notified persistent vr mode is %s", enabled ? "on" : "off");
-  // TODO(eieio): Determine the correct signal to request display control.
-  // Persistent VR mode is not enough.
-  // request_display_callback_(enabled);
-}
-}  // namespace dvr
-}  // namespace android
diff --git a/opengl/libs/EGL/egl_platform_entries.cpp b/opengl/libs/EGL/egl_platform_entries.cpp
index de36a7a..f4dbe49 100644
--- a/opengl/libs/EGL/egl_platform_entries.cpp
+++ b/opengl/libs/EGL/egl_platform_entries.cpp
@@ -29,6 +29,7 @@
 #include <private/android/AHardwareBufferHelpers.h>
 #include <stdlib.h>
 #include <string.h>
+#include <aidl/android/hardware/graphics/common/PixelFormat.h>
 
 #include <condition_variable>
 #include <deque>
@@ -564,9 +565,11 @@
     newList.push_back(EGL_NONE);
 }
 
+using PixelFormat = aidl::android::hardware::graphics::common::PixelFormat;
+
 // Gets the native pixel format corrsponding to the passed EGLConfig.
 void getNativePixelFormat(EGLDisplay dpy, egl_connection_t* cnx, EGLConfig config,
-                          android_pixel_format* format) {
+                          PixelFormat* format) {
     // Set the native window's buffers format to match what this config requests.
     // Whether to use sRGB gamma is not part of the EGLconfig, but is part
     // of our native format. So if sRGB gamma is requested, we have to
@@ -599,28 +602,30 @@
     //    strip colorspace from attribs.
     // endif
     if (a == 0) {
-        if (colorDepth <= 16) {
-            *format = HAL_PIXEL_FORMAT_RGB_565;
+        if (8 == r && 0 == g && 0 == b) {
+            *format = PixelFormat::R_8;
+        } else if (colorDepth <= 16) {
+            *format = PixelFormat::RGB_565;
         } else {
             if (componentType == EGL_COLOR_COMPONENT_TYPE_FIXED_EXT) {
                 if (colorDepth > 24) {
-                    *format = HAL_PIXEL_FORMAT_RGBA_1010102;
+                    *format = PixelFormat::RGBA_1010102;
                 } else {
-                    *format = HAL_PIXEL_FORMAT_RGBX_8888;
+                    *format = PixelFormat::RGBX_8888;
                 }
             } else {
-                *format = HAL_PIXEL_FORMAT_RGBA_FP16;
+                *format = PixelFormat::RGBA_FP16;
             }
         }
     } else {
         if (componentType == EGL_COLOR_COMPONENT_TYPE_FIXED_EXT) {
             if (colorDepth > 24) {
-                *format = HAL_PIXEL_FORMAT_RGBA_1010102;
+                *format = PixelFormat::RGBA_1010102;
             } else {
-                *format = HAL_PIXEL_FORMAT_RGBA_8888;
+                *format = PixelFormat::RGBA_8888;
             }
         } else {
-            *format = HAL_PIXEL_FORMAT_RGBA_FP16;
+            *format = PixelFormat::RGBA_FP16;
         }
     }
 }
@@ -678,7 +683,7 @@
     }
 
     EGLDisplay iDpy = dp->disp.dpy;
-    android_pixel_format format;
+    PixelFormat format;
     getNativePixelFormat(iDpy, cnx, config, &format);
 
     // now select correct colorspace and dataspace based on user's attribute list
@@ -694,7 +699,7 @@
     attrib_list = strippedAttribList.data();
 
     if (!cnx->useAngle) {
-        int err = native_window_set_buffers_format(window, format);
+        int err = native_window_set_buffers_format(window, static_cast<int>(format));
         if (err != 0) {
             ALOGE("error setting native window pixel format: %s (%d)", strerror(-err), err);
             native_window_api_disconnect(window, NATIVE_WINDOW_API_EGL);
@@ -818,7 +823,7 @@
     if (!dp) return EGL_NO_SURFACE;
 
     EGLDisplay iDpy = dp->disp.dpy;
-    android_pixel_format format;
+    PixelFormat format;
     getNativePixelFormat(iDpy, cnx, config, &format);
 
     // Select correct colorspace based on user's attribute list
diff --git a/services/automotive/display/AutomotiveDisplayProxyService.cpp b/services/automotive/display/AutomotiveDisplayProxyService.cpp
index d6fc695..d205231 100644
--- a/services/automotive/display/AutomotiveDisplayProxyService.cpp
+++ b/services/automotive/display/AutomotiveDisplayProxyService.cpp
@@ -34,7 +34,10 @@
     sp<IBinder> displayToken = nullptr;
     sp<SurfaceControl> surfaceControl = nullptr;
     if (it == mDisplays.end()) {
-        displayToken = SurfaceComposerClient::getPhysicalDisplayToken(PhysicalDisplayId(id));
+        if (const auto displayId = DisplayId::fromValue<PhysicalDisplayId>(id)) {
+            displayToken = SurfaceComposerClient::getPhysicalDisplayToken(*displayId);
+        }
+
         if (displayToken == nullptr) {
             ALOGE("Given display id, 0x%lX, is invalid.", (unsigned long)id);
             return nullptr;
@@ -157,7 +160,11 @@
     HwDisplayConfig activeConfig;
     HwDisplayState  activeState;
 
-    auto displayToken = SurfaceComposerClient::getPhysicalDisplayToken(PhysicalDisplayId(id));
+    sp<IBinder> displayToken;
+    if (const auto displayId = DisplayId::fromValue<PhysicalDisplayId>(id)) {
+        displayToken = SurfaceComposerClient::getPhysicalDisplayToken(*displayId);
+    }
+
     if (displayToken == nullptr) {
         ALOGE("Given display id, 0x%lX, is invalid.", (unsigned long)id);
     } else {
diff --git a/services/gpuservice/Android.bp b/services/gpuservice/Android.bp
index b9b6a19..5b4ee21 100644
--- a/services/gpuservice/Android.bp
+++ b/services/gpuservice/Android.bp
@@ -31,6 +31,7 @@
         "libcutils",
         "libgfxstats",
         "libgpumem",
+        "libgpuwork",
         "libgpumemtracer",
         "libgraphicsenv",
         "liblog",
diff --git a/services/gpuservice/GpuService.cpp b/services/gpuservice/GpuService.cpp
index 52d5d4f..7b9782f 100644
--- a/services/gpuservice/GpuService.cpp
+++ b/services/gpuservice/GpuService.cpp
@@ -25,6 +25,7 @@
 #include <binder/PermissionCache.h>
 #include <cutils/properties.h>
 #include <gpumem/GpuMem.h>
+#include <gpuwork/GpuWork.h>
 #include <gpustats/GpuStats.h>
 #include <private/android_filesystem_config.h>
 #include <tracing/GpuMemTracer.h>
@@ -50,13 +51,20 @@
 
 GpuService::GpuService()
       : mGpuMem(std::make_shared<GpuMem>()),
+        mGpuWork(std::make_shared<gpuwork::GpuWork>()),
         mGpuStats(std::make_unique<GpuStats>()),
         mGpuMemTracer(std::make_unique<GpuMemTracer>()) {
-    std::thread asyncInitThread([this]() {
+
+    std::thread gpuMemAsyncInitThread([this]() {
         mGpuMem->initialize();
         mGpuMemTracer->initialize(mGpuMem);
     });
-    asyncInitThread.detach();
+    gpuMemAsyncInitThread.detach();
+
+    std::thread gpuWorkAsyncInitThread([this]() {
+        mGpuWork->initialize();
+    });
+    gpuWorkAsyncInitThread.detach();
 };
 
 void GpuService::setGpuStats(const std::string& driverPackageName,
@@ -124,6 +132,7 @@
         bool dumpDriverInfo = false;
         bool dumpMem = false;
         bool dumpStats = false;
+        bool dumpWork = false;
         size_t numArgs = args.size();
 
         if (numArgs) {
@@ -134,9 +143,11 @@
                     dumpDriverInfo = true;
                 } else if (args[index] == String16("--gpumem")) {
                     dumpMem = true;
+                } else if (args[index] == String16("--gpuwork")) {
+                    dumpWork = true;
                 }
             }
-            dumpAll = !(dumpDriverInfo || dumpMem || dumpStats);
+            dumpAll = !(dumpDriverInfo || dumpMem || dumpStats || dumpWork);
         }
 
         if (dumpAll || dumpDriverInfo) {
@@ -151,6 +162,10 @@
             mGpuStats->dump(args, &result);
             result.append("\n");
         }
+         if (dumpAll || dumpWork) {
+            mGpuWork->dump(args, &result);
+            result.append("\n");
+        }
     }
 
     write(fd, result.c_str(), result.size());
diff --git a/services/gpuservice/GpuService.h b/services/gpuservice/GpuService.h
index 409084b..d7313d1 100644
--- a/services/gpuservice/GpuService.h
+++ b/services/gpuservice/GpuService.h
@@ -28,6 +28,10 @@
 
 namespace android {
 
+namespace gpuwork {
+class GpuWork;
+}
+
 class GpuMem;
 class GpuStats;
 class GpuMemTracer;
@@ -77,6 +81,7 @@
      * Attributes
      */
     std::shared_ptr<GpuMem> mGpuMem;
+    std::shared_ptr<gpuwork::GpuWork> mGpuWork;
     std::unique_ptr<GpuStats> mGpuStats;
     std::unique_ptr<GpuMemTracer> mGpuMemTracer;
     std::mutex mLock;
diff --git a/services/gpuservice/gpuwork/Android.bp b/services/gpuservice/gpuwork/Android.bp
new file mode 100644
index 0000000..a9a59bc
--- /dev/null
+++ b/services/gpuservice/gpuwork/Android.bp
@@ -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.
+
+package {
+    default_applicable_licenses: ["frameworks_native_license"],
+}
+
+cc_library_shared {
+    name: "libgpuwork",
+    srcs: [
+        "GpuWork.cpp",
+    ],
+    header_libs: [
+        "bpf_headers",
+        "gpu_work_structs",
+    ],
+    shared_libs: [
+        "libbase",
+        "libbinder",
+        "libbpf_bcc",
+        "libcutils",
+        "liblog",
+        "libstatslog",
+        "libstatspull",
+        "libutils",
+    ],
+    export_include_dirs: [
+        "include",
+    ],
+    export_header_lib_headers: [
+        "gpu_work_structs",
+    ],
+    export_shared_lib_headers: [
+        "libbase",
+        "libstatspull",
+    ],
+    cppflags: [
+        "-Wall",
+        "-Werror",
+        "-Wformat",
+        "-Wthread-safety",
+        "-Wunused",
+        "-Wunreachable-code",
+    ],
+    required: [
+        "bpfloader",
+        "gpu_work.o",
+    ],
+}
diff --git a/services/gpuservice/gpuwork/GpuWork.cpp b/services/gpuservice/gpuwork/GpuWork.cpp
new file mode 100644
index 0000000..974ae38
--- /dev/null
+++ b/services/gpuservice/gpuwork/GpuWork.cpp
@@ -0,0 +1,541 @@
+/*
+ * 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.
+ */
+
+#undef LOG_TAG
+#define LOG_TAG "GpuWork"
+#define ATRACE_TAG ATRACE_TAG_GRAPHICS
+
+#include "gpuwork/GpuWork.h"
+
+#include <android-base/stringprintf.h>
+#include <binder/PermissionCache.h>
+#include <bpf/WaitForProgsLoaded.h>
+#include <libbpf.h>
+#include <log/log.h>
+#include <random>
+#include <stats_event.h>
+#include <statslog.h>
+#include <unistd.h>
+#include <utils/Timers.h>
+#include <utils/Trace.h>
+
+#include <bit>
+#include <chrono>
+#include <cstdint>
+#include <limits>
+#include <map>
+#include <mutex>
+#include <unordered_map>
+#include <unordered_set>
+#include <vector>
+
+#include "gpuwork/gpu_work.h"
+
+#define ONE_MS_IN_NS (10000000)
+
+namespace android {
+namespace gpuwork {
+
+namespace {
+
+bool lessThanGpuIdUid(const android::gpuwork::GpuIdUid& l, const android::gpuwork::GpuIdUid& r) {
+    return std::tie(l.gpu_id, l.uid) < std::tie(r.gpu_id, r.uid);
+}
+
+size_t hashGpuIdUid(const android::gpuwork::GpuIdUid& gpuIdUid) {
+    return static_cast<size_t>((gpuIdUid.gpu_id << 5U) + gpuIdUid.uid);
+}
+
+bool equalGpuIdUid(const android::gpuwork::GpuIdUid& l, const android::gpuwork::GpuIdUid& r) {
+    return std::tie(l.gpu_id, l.uid) == std::tie(r.gpu_id, r.uid);
+}
+
+// Gets a BPF map from |mapPath|.
+template <class Key, class Value>
+bool getBpfMap(const char* mapPath, bpf::BpfMap<Key, Value>* out) {
+    errno = 0;
+    auto map = bpf::BpfMap<Key, Value>(mapPath);
+    if (!map.isValid()) {
+        ALOGW("Failed to create bpf map from %s [%d(%s)]", mapPath, errno, strerror(errno));
+        return false;
+    }
+    *out = std::move(map);
+    return true;
+}
+
+template <typename SourceType>
+inline int32_t cast_int32(SourceType) = delete;
+
+template <typename SourceType>
+inline int32_t bitcast_int32(SourceType) = delete;
+
+template <>
+inline int32_t bitcast_int32<uint32_t>(uint32_t source) {
+    int32_t result;
+    memcpy(&result, &source, sizeof(result));
+    return result;
+}
+
+} // namespace
+
+using base::StringAppendF;
+
+GpuWork::~GpuWork() {
+    // If we created our clearer thread, then we must stop it and join it.
+    if (mMapClearerThread.joinable()) {
+        // Tell the thread to terminate.
+        {
+            std::scoped_lock<std::mutex> lock(mMutex);
+            mIsTerminating = true;
+            mIsTerminatingConditionVariable.notify_all();
+        }
+
+        // Now, we can join it.
+        mMapClearerThread.join();
+    }
+
+    {
+        std::scoped_lock<std::mutex> lock(mMutex);
+        if (mStatsdRegistered) {
+            AStatsManager_clearPullAtomCallback(android::util::GPU_WORK_PER_UID);
+        }
+    }
+
+    bpf_detach_tracepoint("power", "gpu_work_period");
+}
+
+void GpuWork::initialize() {
+    // Make sure BPF programs are loaded.
+    bpf::waitForProgsLoaded();
+
+    waitForPermissions();
+
+    // Get the BPF maps before trying to attach the BPF program; if we can't get
+    // the maps then there is no point in attaching the BPF program.
+    {
+        std::lock_guard<std::mutex> lock(mMutex);
+
+        if (!getBpfMap("/sys/fs/bpf/map_gpu_work_gpu_work_map", &mGpuWorkMap)) {
+            return;
+        }
+
+        if (!getBpfMap("/sys/fs/bpf/map_gpu_work_gpu_work_global_data", &mGpuWorkGlobalDataMap)) {
+            return;
+        }
+
+        mPreviousMapClearTimePoint = std::chrono::steady_clock::now();
+    }
+
+    // Attach the tracepoint.
+    if (!attachTracepoint("/sys/fs/bpf/prog_gpu_work_tracepoint_power_gpu_work_period", "power",
+                          "gpu_work_period")) {
+        return;
+    }
+
+    // Create the map clearer thread, and store it to |mMapClearerThread|.
+    std::thread thread([this]() { periodicallyClearMap(); });
+
+    mMapClearerThread.swap(thread);
+
+    {
+        std::lock_guard<std::mutex> lock(mMutex);
+        AStatsManager_setPullAtomCallback(int32_t{android::util::GPU_WORK_PER_UID}, nullptr,
+                                          GpuWork::pullAtomCallback, this);
+        mStatsdRegistered = true;
+    }
+
+    ALOGI("Initialized!");
+
+    mInitialized.store(true);
+}
+
+void GpuWork::dump(const Vector<String16>& /* args */, std::string* result) {
+    if (!mInitialized.load()) {
+        result->append("GPU work information is not available.\n");
+        return;
+    }
+
+    // Ordered map ensures output data is sorted.
+    std::map<GpuIdUid, UidTrackingInfo, decltype(lessThanGpuIdUid)*> dumpMap(&lessThanGpuIdUid);
+
+    {
+        std::lock_guard<std::mutex> lock(mMutex);
+
+        if (!mGpuWorkMap.isValid()) {
+            result->append("GPU work map is not available.\n");
+            return;
+        }
+
+        // Iteration of BPF hash maps can be unreliable (no data races, but elements
+        // may be repeated), as the map is typically being modified by other
+        // threads. The buckets are all preallocated. Our eBPF program only updates
+        // entries (in-place) or adds entries. |GpuWork| only iterates or clears the
+        // map while holding |mMutex|. Given this, we should be able to iterate over
+        // all elements reliably. Nevertheless, we copy into a map to avoid
+        // duplicates.
+
+        // Note that userspace reads of BPF maps make a copy of the value, and
+        // thus the returned value is not being concurrently accessed by the BPF
+        // program (no atomic reads needed below).
+
+        mGpuWorkMap.iterateWithValue(
+                [&dumpMap](const GpuIdUid& key, const UidTrackingInfo& value,
+                           const android::bpf::BpfMap<GpuIdUid, UidTrackingInfo>&)
+                        -> base::Result<void> {
+                    dumpMap[key] = value;
+                    return {};
+                });
+    }
+
+    // Dump work information.
+    // E.g.
+    // GPU work information.
+    // gpu_id uid total_active_duration_ns total_inactive_duration_ns
+    // 0 1000 0 0
+    // 0 1003 1234 123
+    // [errors:3]0 1006 4567 456
+
+    // Header.
+    result->append("GPU work information.\ngpu_id uid total_active_duration_ns "
+                   "total_inactive_duration_ns\n");
+
+    for (const auto& idToUidInfo : dumpMap) {
+        if (idToUidInfo.second.error_count) {
+            StringAppendF(result, "[errors:%" PRIu32 "]", idToUidInfo.second.error_count);
+        }
+        StringAppendF(result, "%" PRIu32 " %" PRIu32 " %" PRIu64 " %" PRIu64 "\n",
+                      idToUidInfo.first.gpu_id, idToUidInfo.first.uid,
+                      idToUidInfo.second.total_active_duration_ns,
+                      idToUidInfo.second.total_inactive_duration_ns);
+    }
+}
+
+bool GpuWork::attachTracepoint(const char* programPath, const char* tracepointGroup,
+                               const char* tracepointName) {
+    errno = 0;
+    base::unique_fd fd(bpf::retrieveProgram(programPath));
+    if (fd < 0) {
+        ALOGW("Failed to retrieve pinned program from %s [%d(%s)]", programPath, errno,
+              strerror(errno));
+        return false;
+    }
+
+    // Attach the program to the tracepoint. The tracepoint is automatically enabled.
+    errno = 0;
+    int count = 0;
+    while (bpf_attach_tracepoint(fd.get(), tracepointGroup, tracepointName) < 0) {
+        if (++count > kGpuWaitTimeoutSeconds) {
+            ALOGW("Failed to attach bpf program to %s/%s tracepoint [%d(%s)]", tracepointGroup,
+                  tracepointName, errno, strerror(errno));
+            return false;
+        }
+        // Retry until GPU driver loaded or timeout.
+        sleep(1);
+        errno = 0;
+    }
+
+    return true;
+}
+
+AStatsManager_PullAtomCallbackReturn GpuWork::pullAtomCallback(int32_t atomTag,
+                                                               AStatsEventList* data,
+                                                               void* cookie) {
+    ATRACE_CALL();
+
+    GpuWork* gpuWork = reinterpret_cast<GpuWork*>(cookie);
+    if (atomTag == android::util::GPU_WORK_PER_UID) {
+        return gpuWork->pullWorkAtoms(data);
+    }
+
+    return AStatsManager_PULL_SKIP;
+}
+
+AStatsManager_PullAtomCallbackReturn GpuWork::pullWorkAtoms(AStatsEventList* data) {
+    ATRACE_CALL();
+
+    if (!data || !mInitialized.load()) {
+        return AStatsManager_PULL_SKIP;
+    }
+
+    std::lock_guard<std::mutex> lock(mMutex);
+
+    if (!mGpuWorkMap.isValid()) {
+        return AStatsManager_PULL_SKIP;
+    }
+
+    std::unordered_map<GpuIdUid, UidTrackingInfo, decltype(hashGpuIdUid)*, decltype(equalGpuIdUid)*>
+            workMap(32, &hashGpuIdUid, &equalGpuIdUid);
+
+    // Iteration of BPF hash maps can be unreliable (no data races, but elements
+    // may be repeated), as the map is typically being modified by other
+    // threads. The buckets are all preallocated. Our eBPF program only updates
+    // entries (in-place) or adds entries. |GpuWork| only iterates or clears the
+    // map while holding |mMutex|. Given this, we should be able to iterate over
+    // all elements reliably. Nevertheless, we copy into a map to avoid
+    // duplicates.
+
+    // Note that userspace reads of BPF maps make a copy of the value, and thus
+    // the returned value is not being concurrently accessed by the BPF program
+    // (no atomic reads needed below).
+
+    mGpuWorkMap.iterateWithValue([&workMap](const GpuIdUid& key, const UidTrackingInfo& value,
+                                            const android::bpf::BpfMap<GpuIdUid, UidTrackingInfo>&)
+                                         -> base::Result<void> {
+        workMap[key] = value;
+        return {};
+    });
+
+    // Get a list of just the UIDs; the order does not matter.
+    std::vector<Uid> uids;
+    // Get a list of the GPU IDs, in order.
+    std::set<uint32_t> gpuIds;
+    {
+        // To avoid adding duplicate UIDs.
+        std::unordered_set<Uid> addedUids;
+
+        for (const auto& workInfo : workMap) {
+            if (addedUids.insert(workInfo.first.uid).second) {
+                // Insertion was successful.
+                uids.push_back(workInfo.first.uid);
+            }
+            gpuIds.insert(workInfo.first.gpu_id);
+        }
+    }
+
+    ALOGI("pullWorkAtoms: uids.size() == %zu", uids.size());
+    ALOGI("pullWorkAtoms: gpuIds.size() == %zu", gpuIds.size());
+
+    if (gpuIds.size() > kNumGpusHardLimit) {
+        // If we observe a very high number of GPUs then something has probably
+        // gone wrong, so don't log any atoms.
+        return AStatsManager_PULL_SKIP;
+    }
+
+    size_t numSampledUids = kNumSampledUids;
+
+    if (gpuIds.size() > kNumGpusSoftLimit) {
+        // If we observe a high number of GPUs then we just sample 1 UID.
+        numSampledUids = 1;
+    }
+
+    // Remove all UIDs that do not have at least |kMinGpuTimeNanoseconds| on at
+    // least one GPU.
+    {
+        auto uidIt = uids.begin();
+        while (uidIt != uids.end()) {
+            bool hasEnoughGpuTime = false;
+            for (uint32_t gpuId : gpuIds) {
+                auto infoIt = workMap.find(GpuIdUid{gpuId, *uidIt});
+                if (infoIt == workMap.end()) {
+                    continue;
+                }
+                if (infoIt->second.total_active_duration_ns +
+                            infoIt->second.total_inactive_duration_ns >=
+                    kMinGpuTimeNanoseconds) {
+                    hasEnoughGpuTime = true;
+                    break;
+                }
+            }
+            if (hasEnoughGpuTime) {
+                ++uidIt;
+            } else {
+                uidIt = uids.erase(uidIt);
+            }
+        }
+    }
+
+    ALOGI("pullWorkAtoms: after removing uids with very low GPU time: uids.size() == %zu",
+          uids.size());
+
+    std::random_device device;
+    std::default_random_engine random_engine(device());
+
+    // If we have more than |numSampledUids| UIDs, choose |numSampledUids|
+    // random UIDs. We swap them to the front of the list. Given the list
+    // indices 0..i..n-1, we have the following inclusive-inclusive ranges:
+    // - [0, i-1] == the randomly chosen elements.
+    // - [i, n-1] == the remaining unchosen elements.
+    if (uids.size() > numSampledUids) {
+        for (size_t i = 0; i < numSampledUids; ++i) {
+            std::uniform_int_distribution<size_t> uniform_dist(i, uids.size() - 1);
+            size_t random_index = uniform_dist(random_engine);
+            std::swap(uids[i], uids[random_index]);
+        }
+        // Only keep the front |numSampledUids| elements.
+        uids.resize(numSampledUids);
+    }
+
+    ALOGI("pullWorkAtoms: after random selection: uids.size() == %zu", uids.size());
+
+    auto now = std::chrono::steady_clock::now();
+    long long duration =
+            std::chrono::duration_cast<std::chrono::seconds>(now - mPreviousMapClearTimePoint)
+                    .count();
+    if (duration > std::numeric_limits<int32_t>::max() || duration < 0) {
+        // This is essentially impossible. If it does somehow happen, give up,
+        // but still clear the map.
+        clearMap();
+        return AStatsManager_PULL_SKIP;
+    }
+
+    // Log an atom for each (gpu id, uid) pair for which we have data.
+    for (uint32_t gpuId : gpuIds) {
+        for (Uid uid : uids) {
+            auto it = workMap.find(GpuIdUid{gpuId, uid});
+            if (it == workMap.end()) {
+                continue;
+            }
+            const UidTrackingInfo& info = it->second;
+
+            uint64_t total_active_duration_ms = info.total_active_duration_ns / ONE_MS_IN_NS;
+            uint64_t total_inactive_duration_ms = info.total_inactive_duration_ns / ONE_MS_IN_NS;
+
+            // Skip this atom if any numbers are out of range. |duration| is
+            // already checked above.
+            if (total_active_duration_ms > std::numeric_limits<int32_t>::max() ||
+                total_inactive_duration_ms > std::numeric_limits<int32_t>::max()) {
+                continue;
+            }
+
+            ALOGI("pullWorkAtoms: adding stats for GPU ID %" PRIu32 "; UID %" PRIu32, gpuId, uid);
+            android::util::addAStatsEvent(data, int32_t{android::util::GPU_WORK_PER_UID},
+                                          // uid
+                                          bitcast_int32(uid),
+                                          // gpu_id
+                                          bitcast_int32(gpuId),
+                                          // time_duration_seconds
+                                          static_cast<int32_t>(duration),
+                                          // total_active_duration_millis
+                                          static_cast<int32_t>(total_active_duration_ms),
+                                          // total_inactive_duration_millis
+                                          static_cast<int32_t>(total_inactive_duration_ms));
+        }
+    }
+    clearMap();
+    return AStatsManager_PULL_SUCCESS;
+}
+
+void GpuWork::periodicallyClearMap() {
+    std::unique_lock<std::mutex> lock(mMutex);
+
+    auto previousTime = std::chrono::steady_clock::now();
+
+    while (true) {
+        if (mIsTerminating) {
+            break;
+        }
+        auto nextTime = std::chrono::steady_clock::now();
+        auto differenceSeconds =
+                std::chrono::duration_cast<std::chrono::seconds>(nextTime - previousTime);
+        if (differenceSeconds.count() > kMapClearerWaitDurationSeconds) {
+            // It has been >1 hour, so clear the map, if needed.
+            clearMapIfNeeded();
+            // We only update |previousTime| if we actually checked the map.
+            previousTime = nextTime;
+        }
+        // Sleep for ~1 hour. It does not matter if we don't check the map for 2
+        // hours.
+        mIsTerminatingConditionVariable.wait_for(lock,
+                                                 std::chrono::seconds{
+                                                         kMapClearerWaitDurationSeconds});
+    }
+}
+
+void GpuWork::clearMapIfNeeded() {
+    if (!mInitialized.load() || !mGpuWorkMap.isValid() || !mGpuWorkGlobalDataMap.isValid()) {
+        ALOGW("Map clearing could not occur because we are not initialized properly");
+        return;
+    }
+
+    base::Result<GlobalData> globalData = mGpuWorkGlobalDataMap.readValue(0);
+    if (!globalData.ok()) {
+        ALOGW("Could not read BPF global data map entry");
+        return;
+    }
+
+    // Note that userspace reads of BPF maps make a copy of the value, and thus
+    // the return value is not being concurrently accessed by the BPF program
+    // (no atomic reads needed below).
+
+    uint64_t numEntries = globalData.value().num_map_entries;
+
+    // If the map is <=75% full, we do nothing.
+    if (numEntries <= (kMaxTrackedGpuIdUids / 4) * 3) {
+        return;
+    }
+
+    clearMap();
+}
+
+void GpuWork::clearMap() {
+    if (!mInitialized.load() || !mGpuWorkMap.isValid() || !mGpuWorkGlobalDataMap.isValid()) {
+        ALOGW("Map clearing could not occur because we are not initialized properly");
+        return;
+    }
+
+    base::Result<GlobalData> globalData = mGpuWorkGlobalDataMap.readValue(0);
+    if (!globalData.ok()) {
+        ALOGW("Could not read BPF global data map entry");
+        return;
+    }
+
+    // Iterating BPF maps to delete keys is tricky. If we just repeatedly call
+    // |getFirstKey()| and delete that, we may loop forever (or for a long time)
+    // because our BPF program might be repeatedly re-adding keys. Also, even if
+    // we limit the number of elements we try to delete, we might only delete
+    // new entries, leaving old entries in the map. If we delete a key A and
+    // then call |getNextKey(A)|, the first key in the map is returned, so we
+    // have the same issue.
+    //
+    // Thus, we instead get the next key and then delete the previous key. We
+    // also limit the number of deletions we try, just in case.
+
+    base::Result<GpuIdUid> key = mGpuWorkMap.getFirstKey();
+
+    for (size_t i = 0; i < kMaxTrackedGpuIdUids; ++i) {
+        if (!key.ok()) {
+            break;
+        }
+        base::Result<GpuIdUid> previousKey = key;
+        key = mGpuWorkMap.getNextKey(previousKey.value());
+        mGpuWorkMap.deleteValue(previousKey.value());
+    }
+
+    // Reset our counter; |globalData| is a copy of the data, so we have to use
+    // |writeValue|.
+    globalData.value().num_map_entries = 0;
+    mGpuWorkGlobalDataMap.writeValue(0, globalData.value(), BPF_ANY);
+
+    // Update |mPreviousMapClearTimePoint| so we know when we started collecting
+    // the stats.
+    mPreviousMapClearTimePoint = std::chrono::steady_clock::now();
+}
+
+void GpuWork::waitForPermissions() {
+    const String16 permissionRegisterStatsPullAtom(kPermissionRegisterStatsPullAtom);
+    int count = 0;
+    while (!PermissionCache::checkPermission(permissionRegisterStatsPullAtom, getpid(), getuid())) {
+        if (++count > kPermissionsWaitTimeoutSeconds) {
+            ALOGW("Timed out waiting for android.permission.REGISTER_STATS_PULL_ATOM");
+            return;
+        }
+        // Retry.
+        sleep(1);
+    }
+}
+
+} // namespace gpuwork
+} // namespace android
diff --git a/services/gpuservice/gpuwork/bpfprogs/Android.bp b/services/gpuservice/gpuwork/bpfprogs/Android.bp
new file mode 100644
index 0000000..b3c4eff
--- /dev/null
+++ b/services/gpuservice/gpuwork/bpfprogs/Android.bp
@@ -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.
+
+package {
+    default_applicable_licenses: ["frameworks_native_license"],
+}
+
+bpf {
+    name: "gpu_work.o",
+    srcs: ["gpu_work.c"],
+    cflags: [
+        "-Wall",
+        "-Werror",
+        "-Wformat",
+        "-Wthread-safety",
+        "-Wunused",
+        "-Wunreachable-code",
+    ],
+}
+
+cc_library_headers {
+    name: "gpu_work_structs",
+    export_include_dirs: ["include"],
+}
diff --git a/services/gpuservice/gpuwork/bpfprogs/gpu_work.c b/services/gpuservice/gpuwork/bpfprogs/gpu_work.c
new file mode 100644
index 0000000..c8e79a2
--- /dev/null
+++ b/services/gpuservice/gpuwork/bpfprogs/gpu_work.c
@@ -0,0 +1,281 @@
+/*
+ * 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 "include/gpuwork/gpu_work.h"
+
+#include <linux/bpf.h>
+#include <stddef.h>
+#include <stdint.h>
+
+#ifdef MOCK_BPF
+#include <test/mock_bpf_helpers.h>
+#else
+#include <bpf_helpers.h>
+#endif
+
+#define S_IN_NS (1000000000)
+#define SMALL_TIME_GAP_LIMIT_NS (S_IN_NS)
+
+// A map from GpuIdUid (GPU ID and application UID) to |UidTrackingInfo|.
+DEFINE_BPF_MAP_GRW(gpu_work_map, HASH, GpuIdUid, UidTrackingInfo, kMaxTrackedGpuIdUids,
+                   AID_GRAPHICS);
+
+// A map containing a single entry of |GlobalData|.
+DEFINE_BPF_MAP_GRW(gpu_work_global_data, ARRAY, uint32_t, GlobalData, 1, AID_GRAPHICS);
+
+// Defines the structure of the kernel tracepoint:
+//
+//  /sys/kernel/tracing/events/power/gpu_work_period/
+//
+// Drivers must define an appropriate gpu_work_period kernel tracepoint (for
+// example, using the DECLARE_EVENT_CLASS and DEFINE_EVENT macros) such that the
+// arguments/fields match the fields of |GpuWorkPeriodEvent|, excluding the
+// initial "common" field. Drivers must invoke the tracepoint (also referred to
+// as emitting the event) as described below. Note that the description below
+// assumes a single physical GPU and its driver; for devices with multiple GPUs,
+// each GPU and its driver should emit events independently, using a different
+// value for |gpu_id| per GPU.
+//
+// |GpuWorkPeriodEvent| defines a non-overlapping, non-zero period of time from
+// |start_time_ns| (inclusive) until |end_time_ns| (exclusive) for a given
+// |uid|, and includes details of how much work the GPU was performing for |uid|
+// during the period. When GPU work for a given |uid| runs on the GPU, the
+// driver must track one or more periods that cover the time where the work was
+// running, and emit events soon after. The driver should try to emit the event
+// for a period at most 1 second after |end_time_ns|, and must emit the event at
+// most 2 seconds after |end_time_ns|. A period's duration (|end_time_ns| -
+// |start_time_ns|) must be at most 1 second. Periods for different |uids| can
+// overlap, but periods for the same |uid| must not overlap. The driver must
+// emit events for the same |uid| in strictly increasing order of
+// |start_time_ns|, such that it is guaranteed that the tracepoint call for a
+// period for |uid| has returned before the tracepoint call for the next period
+// for |uid| is made. Note that synchronization may be necessary if the driver
+// emits events for the same |uid| from different threads/contexts. Note that
+// |end_time_ns| for a period for a |uid| may equal the |start_time_ns| of the
+// next period for |uid|. The driver should try to avoid emitting a large number
+// of events in a short time period (e.g. 1000 events per second) for a given
+// |uid|.
+//
+// The |total_active_duration_ns| must be set to the approximate total amount of
+// time the GPU spent running work for |uid| within the period, without
+// "double-counting" parallel GPU work on the same GPU for the same |uid|. "GPU
+// work" should correspond to the "GPU slices" shown in the AGI (Android GPU
+// Inspector) tool, and so should include work such as fragment and non-fragment
+// work/shaders running on the shader cores of the GPU. For example, given the
+// following:
+//  - A period has:
+//    - |start_time_ns|: 100,000,000 ns
+//    - |end_time_ns|:   800,000,000 ns
+//  - Some GPU vertex work (A):
+//    - started at:      200,000,000 ns
+//    - ended at:        400,000,000 ns
+//  - Some GPU fragment work (B):
+//    - started at:      300,000,000 ns
+//    - ended at:        500,000,000 ns
+//  - Some GPU fragment work (C):
+//    - started at:      300,000,000 ns
+//    - ended at:        400,000,000 ns
+//  - Some GPU fragment work (D):
+//    - started at:      600,000,000 ns
+//    - ended at:        700,000,000 ns
+//
+// The |total_active_duration_ns| would be 400,000,000 ns, because GPU work for
+// |uid| was executing:
+//  - from 200,000,000 ns to 500,000,000 ns, giving a duration of 300,000,000 ns
+//    (encompassing GPU work A, B, and C)
+//  - from 600,000,000 ns to 700,000,000 ns, giving a duration of 100,000,000 ns
+//    (GPU work D)
+//
+// Thus, the |total_active_duration_ns| is the sum of the (non-overlapping)
+// durations. Drivers may not have efficient access to the exact start and end
+// times of all GPU work, as shown above, but drivers should try to
+// approximate/aggregate the value of |total_active_duration_ns| as accurately
+// as possible within the limitations of the hardware, without double-counting
+// parallel GPU work for the same |uid|. The |total_active_duration_ns| value
+// must be less than or equal to the period duration (|end_time_ns| -
+// |start_time_ns|); if the aggregation approach might violate this requirement
+// then the driver must clamp |total_active_duration_ns| to be at most the
+// period duration.
+//
+// Note that the above description allows for a certain amount of flexibility in
+// how the driver tracks periods and emits the events. We list a few examples of
+// how drivers might implement the above:
+//
+// - 1: The driver could track periods for all |uid| values at fixed intervals
+//   of 1 second. Thus, every period duration would be exactly 1 second, and
+//   periods from different |uid|s that overlap would have the same
+//   |start_time_ns| and |end_time_ns| values.
+//
+// - 2: The driver could track periods with many different durations (up to 1
+//   second), as needed in order to cover the GPU work for each |uid|.
+//   Overlapping periods for different |uid|s may have very different durations,
+//   as well as different |start_time_ns| and |end_time_ns| values.
+//
+// - 3: The driver could track fine-grained periods with different durations
+//   that precisely cover the time where GPU work is running for each |uid|.
+//   Thus, |total_active_duration_ns| would always equal the period duration.
+//   For example, if a game was running at 60 frames per second, the driver
+//   would most likely emit _at least_ 60 events per second (probably more, as
+//   there would likely be multiple "chunks" of GPU work per frame, with gaps
+//   between each chunk). However, the driver may sometimes need to resort to
+//   more coarse-grained periods to avoid emitting thousands of events per
+//   second for a |uid|, where |total_active_duration_ns| would then be less
+//   than the period duration.
+typedef struct {
+    // Actual fields start at offset 8.
+    uint64_t common;
+
+    // A value that uniquely identifies the GPU within the system.
+    uint32_t gpu_id;
+
+    // The UID of the application (i.e. persistent, unique ID of the Android
+    // app) that submitted work to the GPU.
+    uint32_t uid;
+
+    // The start time of the period in nanoseconds. The clock must be
+    // CLOCK_MONOTONIC, as returned by the ktime_get_ns(void) function.
+    uint64_t start_time_ns;
+
+    // The end time of the period in nanoseconds. The clock must be
+    // CLOCK_MONOTONIC, as returned by the ktime_get_ns(void) function.
+    uint64_t end_time_ns;
+
+    // The amount of time the GPU was running GPU work for |uid| during the
+    // period, in nanoseconds, without double-counting parallel GPU work for the
+    // same |uid|. For example, this might include the amount of time the GPU
+    // spent performing shader work (vertex work, fragment work, etc.) for
+    // |uid|.
+    uint64_t total_active_duration_ns;
+
+} GpuWorkPeriodEvent;
+
+_Static_assert(offsetof(GpuWorkPeriodEvent, gpu_id) == 8 &&
+                       offsetof(GpuWorkPeriodEvent, uid) == 12 &&
+                       offsetof(GpuWorkPeriodEvent, start_time_ns) == 16 &&
+                       offsetof(GpuWorkPeriodEvent, end_time_ns) == 24 &&
+                       offsetof(GpuWorkPeriodEvent, total_active_duration_ns) == 32,
+               "Field offsets of struct GpuWorkPeriodEvent must not be changed because they "
+               "must match the tracepoint field offsets found via adb shell cat "
+               "/sys/kernel/tracing/events/power/gpu_work_period/format");
+
+DEFINE_BPF_PROG("tracepoint/power/gpu_work_period", AID_ROOT, AID_GRAPHICS, tp_gpu_work_period)
+(GpuWorkPeriodEvent* const period) {
+    // Note: In eBPF programs, |__sync_fetch_and_add| is translated to an atomic
+    // add.
+
+    // Return 1 to avoid blocking simpleperf from receiving events.
+    const int ALLOW = 1;
+
+    GpuIdUid gpu_id_and_uid;
+    __builtin_memset(&gpu_id_and_uid, 0, sizeof(gpu_id_and_uid));
+    gpu_id_and_uid.gpu_id = period->gpu_id;
+    gpu_id_and_uid.uid = period->uid;
+
+    // Get |UidTrackingInfo|.
+    UidTrackingInfo* uid_tracking_info = bpf_gpu_work_map_lookup_elem(&gpu_id_and_uid);
+    if (!uid_tracking_info) {
+        // There was no existing entry, so we add a new one.
+        UidTrackingInfo initial_info;
+        __builtin_memset(&initial_info, 0, sizeof(initial_info));
+        if (0 == bpf_gpu_work_map_update_elem(&gpu_id_and_uid, &initial_info, BPF_NOEXIST)) {
+            // We added an entry to the map, so we increment our entry counter in
+            // |GlobalData|.
+            const uint32_t zero = 0;
+            // Get the |GlobalData|.
+            GlobalData* global_data = bpf_gpu_work_global_data_lookup_elem(&zero);
+            // Getting the global data never fails because it is an |ARRAY| map,
+            // but we need to keep the verifier happy.
+            if (global_data) {
+                __sync_fetch_and_add(&global_data->num_map_entries, 1);
+            }
+        }
+        uid_tracking_info = bpf_gpu_work_map_lookup_elem(&gpu_id_and_uid);
+        if (!uid_tracking_info) {
+            // This should never happen, unless entries are getting deleted at
+            // this moment. If so, we just give up.
+            return ALLOW;
+        }
+    }
+
+    if (
+            // The period duration must be non-zero.
+            period->start_time_ns >= period->end_time_ns ||
+            // The period duration must be at most 1 second.
+            (period->end_time_ns - period->start_time_ns) > S_IN_NS) {
+        __sync_fetch_and_add(&uid_tracking_info->error_count, 1);
+        return ALLOW;
+    }
+
+    // If |total_active_duration_ns| is 0 then no GPU work occurred and there is
+    // nothing to do.
+    if (period->total_active_duration_ns == 0) {
+        return ALLOW;
+    }
+
+    // Update |uid_tracking_info->total_active_duration_ns|.
+    __sync_fetch_and_add(&uid_tracking_info->total_active_duration_ns,
+                         period->total_active_duration_ns);
+
+    // |small_gap_time_ns| is the time gap between the current and previous
+    // active period, which could be 0. If the gap is more than
+    // |SMALL_TIME_GAP_LIMIT_NS| then |small_gap_time_ns| will be set to 0
+    // because we want to estimate the small gaps between "continuous" GPU work.
+    uint64_t small_gap_time_ns = 0;
+    if (uid_tracking_info->previous_active_end_time_ns > period->start_time_ns) {
+        // The current period appears to have occurred before the previous
+        // active period, which must not happen because per-UID periods must not
+        // overlap and must be emitted in strictly increasing order of
+        // |start_time_ns|.
+        __sync_fetch_and_add(&uid_tracking_info->error_count, 1);
+    } else {
+        // The current period appears to have been emitted after the previous
+        // active period, as expected, so we can calculate the gap between the
+        // current and previous active period.
+        small_gap_time_ns = period->start_time_ns - uid_tracking_info->previous_active_end_time_ns;
+
+        // Update |previous_active_end_time_ns|.
+        uid_tracking_info->previous_active_end_time_ns = period->end_time_ns;
+
+        // We want to estimate the small gaps between "continuous" GPU work; if
+        // the gap is more than |SMALL_TIME_GAP_LIMIT_NS| then we don't consider
+        // this "continuous" GPU work.
+        if (small_gap_time_ns > SMALL_TIME_GAP_LIMIT_NS) {
+            small_gap_time_ns = 0;
+        }
+    }
+
+    uint64_t period_total_inactive_time_ns = 0;
+    const uint64_t period_duration_ns = period->end_time_ns - period->start_time_ns;
+    // |period->total_active_duration_ns| is the active time within the period duration, so
+    // it must not be larger than |period_duration_ns|.
+    if (period->total_active_duration_ns > period_duration_ns) {
+        __sync_fetch_and_add(&uid_tracking_info->error_count, 1);
+    } else {
+        period_total_inactive_time_ns = period_duration_ns - period->total_active_duration_ns;
+    }
+
+    // Update |uid_tracking_info->total_inactive_duration_ns| by adding the
+    // inactive time from this period, plus the small gap between the current
+    // and previous active period. Either or both of these values could be 0.
+    if (small_gap_time_ns > 0 || period_total_inactive_time_ns > 0) {
+        __sync_fetch_and_add(&uid_tracking_info->total_inactive_duration_ns,
+                             small_gap_time_ns + period_total_inactive_time_ns);
+    }
+
+    return ALLOW;
+}
+
+LICENSE("Apache 2.0");
diff --git a/services/gpuservice/gpuwork/bpfprogs/include/gpuwork/gpu_work.h b/services/gpuservice/gpuwork/bpfprogs/include/gpuwork/gpu_work.h
new file mode 100644
index 0000000..57338f4
--- /dev/null
+++ b/services/gpuservice/gpuwork/bpfprogs/include/gpuwork/gpu_work.h
@@ -0,0 +1,67 @@
+/*
+ * 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 <stdint.h>
+
+#ifdef __cplusplus
+#include <type_traits>
+
+namespace android {
+namespace gpuwork {
+#endif
+
+typedef struct  {
+    uint32_t gpu_id;
+    uint32_t uid;
+} GpuIdUid;
+
+typedef struct {
+    // The end time of the previous period where the GPU was active for the UID,
+    // in nanoseconds.
+    uint64_t previous_active_end_time_ns;
+
+    // The total amount of time the GPU has spent running work for the UID, in
+    // nanoseconds.
+    uint64_t total_active_duration_ns;
+
+    // The total amount of time of the "gaps" between "continuous" GPU work for
+    // the UID, in nanoseconds. This is estimated by ignoring large gaps between
+    // GPU work for this UID.
+    uint64_t total_inactive_duration_ns;
+
+    // The number of errors detected due to |GpuWorkPeriodEvent| events for the
+    // UID violating the specification in some way. E.g. periods with a zero or
+    // negative duration.
+    uint32_t error_count;
+
+} UidTrackingInfo;
+
+typedef struct {
+    // We cannot query the number of entries in BPF map |gpu_work_map|. We track
+    // the number of entries (approximately) using a counter so we can check if
+    // the map is nearly full.
+    uint64_t num_map_entries;
+} GlobalData;
+
+// The maximum number of tracked GPU ID and UID pairs (|GpuIdUid|).
+static const uint32_t kMaxTrackedGpuIdUids = 512;
+
+#ifdef __cplusplus
+} // namespace gpuwork
+} // namespace android
+#endif
diff --git a/services/gpuservice/gpuwork/include/gpuwork/GpuWork.h b/services/gpuservice/gpuwork/include/gpuwork/GpuWork.h
new file mode 100644
index 0000000..acecd56
--- /dev/null
+++ b/services/gpuservice/gpuwork/include/gpuwork/GpuWork.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 <bpf/BpfMap.h>
+#include <stats_pull_atom_callback.h>
+#include <utils/Mutex.h>
+#include <utils/String16.h>
+#include <utils/Vector.h>
+
+#include <condition_variable>
+#include <cstdint>
+#include <functional>
+#include <thread>
+
+#include "gpuwork/gpu_work.h"
+
+namespace android {
+namespace gpuwork {
+
+class GpuWork {
+public:
+    using Uid = uint32_t;
+
+    GpuWork() = default;
+    ~GpuWork();
+
+    void initialize();
+
+    // Dumps the GPU work information.
+    void dump(const Vector<String16>& args, std::string* result);
+
+private:
+    // Attaches tracepoint |tracepoint_group|/|tracepoint_name| to BPF program at path
+    // |program_path|. The tracepoint is also enabled.
+    static bool attachTracepoint(const char* program_path, const char* tracepoint_group,
+                                 const char* tracepoint_name);
+
+    // Native atom puller callback registered in statsd.
+    static AStatsManager_PullAtomCallbackReturn pullAtomCallback(int32_t atomTag,
+                                                                 AStatsEventList* data,
+                                                                 void* cookie);
+
+    AStatsManager_PullAtomCallbackReturn pullWorkAtoms(AStatsEventList* data);
+
+    // Periodically calls |clearMapIfNeeded| to clear the |mGpuWorkMap| map, if
+    // needed.
+    //
+    // Thread safety analysis is skipped because we need to use
+    // |std::unique_lock|, which is not currently supported by thread safety
+    // analysis.
+    void periodicallyClearMap() NO_THREAD_SAFETY_ANALYSIS;
+
+    // Checks whether the |mGpuWorkMap| map is nearly full and, if so, clears
+    // it.
+    void clearMapIfNeeded() REQUIRES(mMutex);
+
+    // Clears the |mGpuWorkMap| map.
+    void clearMap() REQUIRES(mMutex);
+
+    // Waits for required permissions to become set. This seems to be needed
+    // because platform service permissions might not be set when a service
+    // first starts. See b/214085769.
+    void waitForPermissions();
+
+    // Indicates whether our eBPF components have been initialized.
+    std::atomic<bool> mInitialized = false;
+
+    // A thread that periodically checks whether |mGpuWorkMap| is nearly full
+    // and, if so, clears it.
+    std::thread mMapClearerThread;
+
+    // Mutex for |mGpuWorkMap| and a few other fields.
+    std::mutex mMutex;
+
+    // BPF map for per-UID GPU work.
+    bpf::BpfMap<GpuIdUid, UidTrackingInfo> mGpuWorkMap GUARDED_BY(mMutex);
+
+    // BPF map containing a single element for global data.
+    bpf::BpfMap<uint32_t, GlobalData> mGpuWorkGlobalDataMap GUARDED_BY(mMutex);
+
+    // When true, we are being destructed, so |mMapClearerThread| should stop.
+    bool mIsTerminating GUARDED_BY(mMutex);
+
+    // A condition variable for |mIsTerminating|.
+    std::condition_variable mIsTerminatingConditionVariable GUARDED_BY(mMutex);
+
+    // 30 second timeout for trying to attach a BPF program to a tracepoint.
+    static constexpr int kGpuWaitTimeoutSeconds = 30;
+
+    // The wait duration for the map clearer thread; the thread checks the map
+    // every ~1 hour.
+    static constexpr uint32_t kMapClearerWaitDurationSeconds = 60 * 60;
+
+    // Whether our |pullAtomCallback| function is registered.
+    bool mStatsdRegistered GUARDED_BY(mMutex) = false;
+
+    // The number of randomly chosen (i.e. sampled) UIDs to log stats for.
+    static constexpr size_t kNumSampledUids = 10;
+
+    // A "large" number of GPUs. If we observe more GPUs than this limit then
+    // we reduce the amount of stats we log.
+    static constexpr size_t kNumGpusSoftLimit = 4;
+
+    // A "very large" number of GPUs. If we observe more GPUs than this limit
+    // then we don't log any stats.
+    static constexpr size_t kNumGpusHardLimit = 32;
+
+    // The minimum GPU time needed to actually log stats for a UID.
+    static constexpr uint64_t kMinGpuTimeNanoseconds = 30U * 1000000000U; // 30 seconds.
+
+    // The previous time point at which |mGpuWorkMap| was cleared.
+    std::chrono::steady_clock::time_point mPreviousMapClearTimePoint GUARDED_BY(mMutex);
+
+    // Permission to register a statsd puller.
+    static constexpr char16_t kPermissionRegisterStatsPullAtom[] =
+            u"android.permission.REGISTER_STATS_PULL_ATOM";
+
+    // Time limit for waiting for permissions.
+    static constexpr int kPermissionsWaitTimeoutSeconds = 30;
+};
+
+} // namespace gpuwork
+} // namespace android
diff --git a/services/gpuservice/vts/Android.bp b/services/gpuservice/vts/Android.bp
new file mode 100644
index 0000000..83a40e7
--- /dev/null
+++ b/services/gpuservice/vts/Android.bp
@@ -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.
+
+package {
+    default_applicable_licenses: ["frameworks_native_license"],
+}
+
+java_test_host {
+    name: "GpuServiceVendorTests",
+    srcs: ["src/**/*.java"],
+    libs: [
+        "tradefed",
+        "vts-core-tradefed-harness",
+    ],
+    test_suites: [
+        "general-tests",
+        "vts",
+    ],
+}
diff --git a/services/gpuservice/vts/AndroidTest.xml b/services/gpuservice/vts/AndroidTest.xml
new file mode 100644
index 0000000..34dc2ea
--- /dev/null
+++ b/services/gpuservice/vts/AndroidTest.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<configuration description="Runs GpuServiceVendorTests">
+    <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer">
+        <option name="force-root" value="false" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.HostTest" >
+        <option name="jar" value="GpuServiceVendorTests.jar" />
+    </test>
+</configuration>
diff --git a/services/gpuservice/vts/TEST_MAPPING b/services/gpuservice/vts/TEST_MAPPING
new file mode 100644
index 0000000..b33e962
--- /dev/null
+++ b/services/gpuservice/vts/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "GpuServiceVendorTests"
+    }
+  ]
+}
diff --git a/services/gpuservice/vts/src/com/android/tests/gpuservice/GpuWorkTracepointTest.java b/services/gpuservice/vts/src/com/android/tests/gpuservice/GpuWorkTracepointTest.java
new file mode 100644
index 0000000..9fa9016
--- /dev/null
+++ b/services/gpuservice/vts/src/com/android/tests/gpuservice/GpuWorkTracepointTest.java
@@ -0,0 +1,97 @@
+/*
+ * 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 com.android.tests.gpuservice;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+import com.android.tradefed.util.CommandResult;
+import com.android.tradefed.util.CommandStatus;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Arrays;
+import java.util.stream.Collectors;
+
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class GpuWorkTracepointTest extends BaseHostJUnit4Test {
+
+    private static final String CPU_FREQUENCY_TRACEPOINT_FORMAT_PATH =
+            "/sys/kernel/tracing/events/power/cpu_frequency/format";
+    private static final String GPU_WORK_PERIOD_TRACEPOINT_FORMAT_PATH =
+            "/sys/kernel/tracing/events/power/gpu_work_period/format";
+
+    @Test
+    public void testReadTracingEvents() throws Exception {
+        // Test |testGpuWorkPeriodTracepointFormat| is dependent on whether certain tracepoint
+        // paths exist. This means the test will vacuously pass if the tracepoint file system is
+        // inaccessible. Thus, as a basic check, we make sure the CPU frequency tracepoint format
+        // is accessible. If not, something is probably fundamentally broken about the tracing
+        // file system.
+        CommandResult commandResult = getDevice().executeShellV2Command(
+                String.format("cat %s", CPU_FREQUENCY_TRACEPOINT_FORMAT_PATH));
+
+        assertEquals(String.format(
+                        "Failed to read \"%s\". This probably means that the tracing file system "
+                                + "is fundamentally broken in some way, possibly due to bad "
+                                + "permissions.",
+                        CPU_FREQUENCY_TRACEPOINT_FORMAT_PATH),
+                commandResult.getStatus(), CommandStatus.SUCCESS);
+    }
+
+    @Test
+    public void testGpuWorkPeriodTracepointFormat() throws Exception {
+        CommandResult commandResult = getDevice().executeShellV2Command(
+                String.format("cat %s", GPU_WORK_PERIOD_TRACEPOINT_FORMAT_PATH));
+
+        // If we failed to cat the tracepoint format then the test ends here.
+        assumeTrue(String.format("Failed to cat the gpu_work_period tracepoint format at %s",
+                        GPU_WORK_PERIOD_TRACEPOINT_FORMAT_PATH),
+                commandResult.getStatus().equals(CommandStatus.SUCCESS));
+
+        // Otherwise, we check that the fields match the expected fields.
+        String actualFields = Arrays.stream(
+                commandResult.getStdout().trim().split("\n")).filter(
+                s -> s.startsWith("\tfield:")).collect(
+                Collectors.joining("\n"));
+
+        String expectedFields = String.join("\n",
+                "\tfield:unsigned short common_type;\toffset:0;\tsize:2;\tsigned:0;",
+                "\tfield:unsigned char common_flags;\toffset:2;\tsize:1;\tsigned:0;",
+                "\tfield:unsigned char common_preempt_count;\toffset:3;\tsize:1;\tsigned:0;",
+                "\tfield:int common_pid;\toffset:4;\tsize:4;\tsigned:1;",
+                "\tfield:u32 gpu_id;\toffset:8;\tsize:4;\tsigned:0;",
+                "\tfield:u32 uid;\toffset:12;\tsize:4;\tsigned:0;",
+                "\tfield:u64 start_time_ns;\toffset:16;\tsize:8;\tsigned:0;",
+                "\tfield:u64 end_time_ns;\toffset:24;\tsize:8;\tsigned:0;",
+                "\tfield:u64 total_active_duration_ns;\toffset:32;\tsize:8;\tsigned:0;"
+        );
+
+        // We use |fail| rather than |assertEquals| because it allows us to give a clearer message.
+        if (!expectedFields.equals(actualFields)) {
+            String message = String.format(
+                    "Tracepoint format given by \"%s\" does not match the expected format.\n"
+                            + "Expected fields:\n%s\n\nActual fields:\n%s\n\n",
+                    GPU_WORK_PERIOD_TRACEPOINT_FORMAT_PATH, expectedFields, actualFields);
+            fail(message);
+        }
+    }
+}
diff --git a/services/inputflinger/Android.bp b/services/inputflinger/Android.bp
index 73e5749..ed9bfd2 100644
--- a/services/inputflinger/Android.bp
+++ b/services/inputflinger/Android.bp
@@ -22,8 +22,13 @@
     default_applicable_licenses: ["frameworks_native_license"],
 }
 
+inputflinger_tidy_checks = [
+    "android-*",
+]
+
 cc_defaults {
     name: "inputflinger_defaults",
+    cpp_std: "c++20",
     cflags: [
         "-Wall",
         "-Wextra",
@@ -37,6 +42,11 @@
     sanitize: {
         misc_undefined: ["bounds"],
     },
+    tidy: true,
+    tidy_checks: [
+        "-*", // Disable all checks not explicitly enabled for now
+    ] + inputflinger_tidy_checks,
+    tidy_checks_as_errors: inputflinger_tidy_checks,
 }
 
 /////////////////////////////////////////////////
@@ -47,7 +57,8 @@
     name: "libinputflinger_sources",
     srcs: [
         "InputClassifier.cpp",
-        "InputClassifierConverter.cpp",
+        "InputCommonConverter.cpp",
+        "UnwantedInteractionBlocker.cpp",
         "InputManager.cpp",
     ],
 }
@@ -56,9 +67,11 @@
     name: "libinputflinger_defaults",
     srcs: [":libinputflinger_sources"],
     shared_libs: [
-        "android.hardware.input.classifier@1.0",
+        "android.hardware.input.processor-V1-ndk",
         "libbase",
         "libbinder",
+        "libbinder_ndk",
+        "libchrome",
         "libcrypto",
         "libcutils",
         "libhidlbase",
@@ -71,12 +84,11 @@
         "libstatssocket",
         "libutils",
         "libui",
-        "lib-platform-compat-native-api",
         "server_configurable_flags",
-        "InputFlingerProperties",
     ],
     static_libs: [
         "libattestation",
+        "libpalmrejection",
     ],
 }
 
@@ -125,7 +137,7 @@
         "InputListener.cpp",
         "InputReaderBase.cpp",
         "InputThread.cpp",
-        "VibrationElement.cpp"
+        "VibrationElement.cpp",
     ],
 }
 
diff --git a/services/inputflinger/BlockingQueue.h b/services/inputflinger/BlockingQueue.h
index b612ca7..8300e8a 100644
--- a/services/inputflinger/BlockingQueue.h
+++ b/services/inputflinger/BlockingQueue.h
@@ -71,8 +71,7 @@
 
     void erase(const std::function<bool(const T&)>& lambda) {
         std::scoped_lock lock(mLock);
-        mQueue.erase(std::remove_if(mQueue.begin(), mQueue.end(),
-                [&lambda](const T& t) { return lambda(t); }), mQueue.end());
+        std::erase_if(mQueue, [&lambda](const auto& t) { return lambda(t); });
     }
 
     /**
diff --git a/services/inputflinger/InputClassifier.cpp b/services/inputflinger/InputClassifier.cpp
index a9cbd5a..3ea0986 100644
--- a/services/inputflinger/InputClassifier.cpp
+++ b/services/inputflinger/InputClassifier.cpp
@@ -17,20 +17,20 @@
 #define LOG_TAG "InputClassifier"
 
 #include "InputClassifier.h"
-#include "InputClassifierConverter.h"
+#include "InputCommonConverter.h"
 
-#include <algorithm>
 #include <android-base/stringprintf.h>
-#include <cmath>
+#include <android/binder_manager.h>
+#include <android/binder_process.h>
 #include <inttypes.h>
 #include <log/log.h>
+#include <algorithm>
+#include <cmath>
 #if defined(__linux__)
     #include <pthread.h>
 #endif
 #include <unordered_set>
 
-#include <android/hardware/input/classifier/1.0/IInputClassifier.h>
-
 #define INDENT1 "  "
 #define INDENT2 "    "
 #define INDENT3 "      "
@@ -38,10 +38,9 @@
 #define INDENT5 "          "
 
 using android::base::StringPrintf;
-using android::hardware::hidl_bitfield;
-using android::hardware::hidl_vec;
-using android::hardware::Return;
-using namespace android::hardware::input;
+using namespace std::chrono_literals;
+using namespace ::aidl::android::hardware::input;
+using aidl::android::hardware::input::processor::IInputProcessor;
 
 namespace android {
 
@@ -57,18 +56,69 @@
     return it->second;
 }
 
-static MotionClassification getMotionClassification(common::V1_0::Classification classification) {
+static MotionClassification getMotionClassification(common::Classification classification) {
     static_assert(MotionClassification::NONE ==
-            static_cast<MotionClassification>(common::V1_0::Classification::NONE));
+                  static_cast<MotionClassification>(common::Classification::NONE));
     static_assert(MotionClassification::AMBIGUOUS_GESTURE ==
-            static_cast<MotionClassification>(common::V1_0::Classification::AMBIGUOUS_GESTURE));
+                  static_cast<MotionClassification>(common::Classification::AMBIGUOUS_GESTURE));
     static_assert(MotionClassification::DEEP_PRESS ==
-            static_cast<MotionClassification>(common::V1_0::Classification::DEEP_PRESS));
+                  static_cast<MotionClassification>(common::Classification::DEEP_PRESS));
     return static_cast<MotionClassification>(classification);
 }
 
 static bool isTouchEvent(const NotifyMotionArgs& args) {
-    return args.source == AINPUT_SOURCE_TOUCHPAD || args.source == AINPUT_SOURCE_TOUCHSCREEN;
+    return isFromSource(args.source, AINPUT_SOURCE_TOUCHPAD) ||
+            isFromSource(args.source, AINPUT_SOURCE_TOUCHSCREEN);
+}
+
+static void setCurrentThreadName(const char* name) {
+#if defined(__linux__)
+    // Set the thread name for debugging
+    pthread_setname_np(pthread_self(), name);
+#else
+    (void*)(name); // prevent unused variable warning
+#endif
+}
+
+static std::shared_ptr<IInputProcessor> getService() {
+    const std::string aidl_instance_name = std::string(IInputProcessor::descriptor) + "/default";
+
+    if (!AServiceManager_isDeclared(aidl_instance_name.c_str())) {
+        ALOGI("HAL %s is not declared", aidl_instance_name.c_str());
+        return nullptr;
+    }
+
+    ndk::SpAIBinder binder(AServiceManager_waitForService(aidl_instance_name.c_str()));
+    return IInputProcessor::fromBinder(binder);
+}
+
+// Temporarily releases a held mutex for the lifetime of the instance.
+// Named to match std::scoped_lock
+class scoped_unlock {
+public:
+    explicit scoped_unlock(std::mutex& mutex) : mMutex(mutex) { mMutex.unlock(); }
+    ~scoped_unlock() { mMutex.lock(); }
+
+private:
+    std::mutex& mMutex;
+};
+
+// --- ScopedDeathRecipient ---
+ScopedDeathRecipient::ScopedDeathRecipient(AIBinder_DeathRecipient_onBinderDied onBinderDied,
+                                           void* cookie)
+      : mCookie(cookie) {
+    mRecipient = AIBinder_DeathRecipient_new(onBinderDied);
+}
+
+void ScopedDeathRecipient::linkToDeath(AIBinder* binder) {
+    binder_status_t linked = AIBinder_linkToDeath(binder, mRecipient, mCookie);
+    if (linked != STATUS_OK) {
+        ALOGE("Could not link death recipient to the HAL death");
+    }
+}
+
+ScopedDeathRecipient::~ScopedDeathRecipient() {
+    AIBinder_DeathRecipient_delete(mRecipient);
 }
 
 // --- ClassifierEvent ---
@@ -119,9 +169,8 @@
 
 // --- MotionClassifier ---
 
-MotionClassifier::MotionClassifier(
-        sp<android::hardware::input::classifier::V1_0::IInputClassifier> service)
-      : mEvents(MAX_EVENTS), mService(service) {
+MotionClassifier::MotionClassifier(std::shared_ptr<IInputProcessor> service)
+      : mEvents(MAX_EVENTS), mService(std::move(service)) {
     // Under normal operation, we do not need to reset the HAL here. But in the case where system
     // crashed, but HAL didn't, we may be connecting to an existing HAL process that might already
     // have received events in the past. That means, that HAL could be in an inconsistent state
@@ -136,23 +185,10 @@
 }
 
 std::unique_ptr<MotionClassifierInterface> MotionClassifier::create(
-        sp<android::hardware::hidl_death_recipient> deathRecipient) {
-    sp<android::hardware::input::classifier::V1_0::IInputClassifier> service =
-            classifier::V1_0::IInputClassifier::getService();
-    if (!service) {
-        // Not really an error, maybe the device does not have this HAL,
-        // but somehow the feature flag is flipped
-        ALOGI("Could not obtain InputClassifier HAL");
-        return nullptr;
-    }
-
-    const bool linked = service->linkToDeath(deathRecipient, 0 /* cookie */).withDefault(false);
-    if (!linked) {
-        ALOGE("Could not link death recipient to the HAL death");
-        return nullptr;
-    }
+        std::shared_ptr<IInputProcessor> service) {
+    LOG_ALWAYS_FATAL_IF(service == nullptr);
     // Using 'new' to access a non-public constructor
-    return std::unique_ptr<MotionClassifier>(new MotionClassifier(service));
+    return std::unique_ptr<MotionClassifier>(new MotionClassifier(std::move(service)));
 }
 
 MotionClassifier::~MotionClassifier() {
@@ -177,14 +213,12 @@
         switch (event.type) {
             case ClassifierEventType::MOTION: {
                 NotifyMotionArgs* motionArgs = static_cast<NotifyMotionArgs*>(event.args.get());
-                common::V1_0::MotionEvent motionEvent =
-                        notifyMotionArgsToHalMotionEvent(*motionArgs);
-                Return<common::V1_0::Classification> response = mService->classify(motionEvent);
-                halResponseOk = response.isOk();
-                if (halResponseOk) {
-                    common::V1_0::Classification halClassification = response;
+                common::MotionEvent motionEvent = notifyMotionArgsToHalMotionEvent(*motionArgs);
+                common::Classification classification;
+                ndk::ScopedAStatus response = mService->classify(motionEvent, &classification);
+                if (response.isOk()) {
                     updateClassification(motionArgs->deviceId, motionArgs->eventTime,
-                            getMotionClassification(halClassification));
+                                         getMotionClassification(classification));
                 }
                 break;
             }
@@ -301,7 +335,8 @@
     if (!mService) {
         return "null";
     }
-    if (mService->ping().isOk()) {
+
+    if (AIBinder_ping(mService->asBinder().get()) == STATUS_OK) {
         return "running";
     }
     return "not responding";
@@ -330,51 +365,64 @@
     }
 }
 
-// --- HalDeathRecipient
-
-InputClassifier::HalDeathRecipient::HalDeathRecipient(InputClassifier& parent) : mParent(parent) {}
-
-void InputClassifier::HalDeathRecipient::serviceDied(
-        uint64_t cookie, const wp<android::hidl::base::V1_0::IBase>& who) {
-    sp<android::hidl::base::V1_0::IBase> service = who.promote();
-    if (service) {
-        service->unlinkToDeath(this);
-    }
-    mParent.setMotionClassifier(nullptr);
-}
-
 // --- InputClassifier ---
 
-InputClassifier::InputClassifier(const sp<InputListenerInterface>& listener)
-      : mListener(listener), mHalDeathRecipient(new HalDeathRecipient(*this)) {}
+InputClassifier::InputClassifier(InputListenerInterface& listener) : mListener(listener) {}
+
+void InputClassifier::onBinderDied(void* cookie) {
+    InputClassifier* classifier = static_cast<InputClassifier*>(cookie);
+    if (classifier == nullptr) {
+        LOG_ALWAYS_FATAL("Cookie is not valid");
+        return;
+    }
+    classifier->setMotionClassifierEnabled(false);
+}
 
 void InputClassifier::setMotionClassifierEnabled(bool enabled) {
+    std::scoped_lock lock(mLock);
     if (enabled) {
         ALOGI("Enabling motion classifier");
-        if (mInitializeMotionClassifierThread.joinable()) {
-            mInitializeMotionClassifierThread.join();
+        if (mInitializeMotionClassifier.valid()) {
+            scoped_unlock unlock(mLock);
+            std::future_status status = mInitializeMotionClassifier.wait_for(5s);
+            if (status != std::future_status::ready) {
+                /**
+                 * We don't have a better option here than to crash. We can't stop the thread,
+                 * and we can't continue because 'mInitializeMotionClassifier' will block in its
+                 * destructor.
+                 */
+                LOG_ALWAYS_FATAL("The thread to load IInputClassifier is stuck!");
+            }
         }
-        mInitializeMotionClassifierThread = std::thread(
-                [this] { setMotionClassifier(MotionClassifier::create(mHalDeathRecipient)); });
-#if defined(__linux__)
-        // Set the thread name for debugging
-        pthread_setname_np(mInitializeMotionClassifierThread.native_handle(),
-                           "Create MotionClassifier");
-#endif
+        mInitializeMotionClassifier = std::async(std::launch::async, [this] {
+            setCurrentThreadName("Create MotionClassifier");
+            std::shared_ptr<IInputProcessor> service = getService();
+            if (service == nullptr) {
+                // Keep the MotionClassifier null, no service was found
+                return;
+            }
+            { // acquire lock
+                std::scoped_lock threadLock(mLock);
+                mHalDeathRecipient =
+                        std::make_unique<ScopedDeathRecipient>(onBinderDied, this /*cookie*/);
+                mHalDeathRecipient->linkToDeath(service->asBinder().get());
+                setMotionClassifierLocked(MotionClassifier::create(std::move(service)));
+            } // release lock
+        });
     } else {
         ALOGI("Disabling motion classifier");
-        setMotionClassifier(nullptr);
+        setMotionClassifierLocked(nullptr);
     }
 }
 
 void InputClassifier::notifyConfigurationChanged(const NotifyConfigurationChangedArgs* args) {
     // pass through
-    mListener->notifyConfigurationChanged(args);
+    mListener.notifyConfigurationChanged(args);
 }
 
 void InputClassifier::notifyKey(const NotifyKeyArgs* args) {
     // pass through
-    mListener->notifyKey(args);
+    mListener.notifyKey(args);
 }
 
 void InputClassifier::notifyMotion(const NotifyMotionArgs* args) {
@@ -382,28 +430,28 @@
     // MotionClassifier is only used for touch events, for now
     const bool sendToMotionClassifier = mMotionClassifier && isTouchEvent(*args);
     if (!sendToMotionClassifier) {
-        mListener->notifyMotion(args);
+        mListener.notifyMotion(args);
         return;
     }
 
     NotifyMotionArgs newArgs(*args);
     newArgs.classification = mMotionClassifier->classify(newArgs);
-    mListener->notifyMotion(&newArgs);
+    mListener.notifyMotion(&newArgs);
 }
 
 void InputClassifier::notifySensor(const NotifySensorArgs* args) {
     // pass through
-    mListener->notifySensor(args);
+    mListener.notifySensor(args);
 }
 
 void InputClassifier::notifyVibratorState(const NotifyVibratorStateArgs* args) {
     // pass through
-    mListener->notifyVibratorState(args);
+    mListener.notifyVibratorState(args);
 }
 
 void InputClassifier::notifySwitch(const NotifySwitchArgs* args) {
     // pass through
-    mListener->notifySwitch(args);
+    mListener.notifySwitch(args);
 }
 
 void InputClassifier::notifyDeviceReset(const NotifyDeviceResetArgs* args) {
@@ -412,17 +460,21 @@
         mMotionClassifier->reset(*args);
     }
     // continue to next stage
-    mListener->notifyDeviceReset(args);
+    mListener.notifyDeviceReset(args);
 }
 
 void InputClassifier::notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs* args) {
     // pass through
-    mListener->notifyPointerCaptureChanged(args);
+    mListener.notifyPointerCaptureChanged(args);
 }
 
-void InputClassifier::setMotionClassifier(
-        std::unique_ptr<MotionClassifierInterface> motionClassifier) {
-    std::scoped_lock lock(mLock);
+void InputClassifier::setMotionClassifierLocked(
+        std::unique_ptr<MotionClassifierInterface> motionClassifier) REQUIRES(mLock) {
+    if (motionClassifier == nullptr) {
+        // Destroy the ScopedDeathRecipient object, which will cause it to unlinkToDeath.
+        // We can't call 'unlink' here because we don't have the binder handle.
+        mHalDeathRecipient = nullptr;
+    }
     mMotionClassifier = std::move(motionClassifier);
 }
 
@@ -439,9 +491,6 @@
 }
 
 InputClassifier::~InputClassifier() {
-    if (mInitializeMotionClassifierThread.joinable()) {
-        mInitializeMotionClassifierThread.join();
-    }
 }
 
 } // namespace android
diff --git a/services/inputflinger/InputClassifier.h b/services/inputflinger/InputClassifier.h
index 1eef020..e2a0bc2 100644
--- a/services/inputflinger/InputClassifier.h
+++ b/services/inputflinger/InputClassifier.h
@@ -18,14 +18,13 @@
 #define _UI_INPUT_CLASSIFIER_H
 
 #include <android-base/thread_annotations.h>
-#include <utils/RefBase.h>
+#include <future>
 #include <thread>
 #include <unordered_map>
 
+#include <aidl/android/hardware/input/processor/IInputProcessor.h>
 #include "BlockingQueue.h"
 #include "InputListener.h"
-#include <android/hardware/input/classifier/1.0/IInputClassifier.h>
-
 namespace android {
 
 enum class ClassifierEventType : uint8_t {
@@ -88,7 +87,7 @@
  * Base interface for an InputListener stage.
  * Provides classification to events.
  */
-class InputClassifierInterface : public virtual RefBase, public InputListenerInterface {
+class InputClassifierInterface : public InputListenerInterface {
 public:
     virtual void setMotionClassifierEnabled(bool enabled) = 0;
     /**
@@ -96,13 +95,26 @@
      * This method may be called on any thread (usually by the input manager).
      */
     virtual void dump(std::string& dump) = 0;
-protected:
+
     InputClassifierInterface() { }
     virtual ~InputClassifierInterface() { }
 };
 
 // --- Implementations ---
 
+class ScopedDeathRecipient {
+public:
+    explicit ScopedDeathRecipient(AIBinder_DeathRecipient_onBinderDied onBinderDied, void* cookie);
+    ScopedDeathRecipient(const ScopedDeathRecipient&) = delete;
+    ScopedDeathRecipient& operator=(ScopedDeathRecipient const&) = delete;
+    void linkToDeath(AIBinder* binder);
+    ~ScopedDeathRecipient();
+
+private:
+    AIBinder_DeathRecipient* mRecipient;
+    void* mCookie;
+};
+
 /**
  * Implementation of MotionClassifierInterface that calls the InputClassifier HAL
  * in order to determine the classification for the current gesture.
@@ -122,7 +134,7 @@
      * This function should be called asynchronously, because getService takes a long time.
      */
     static std::unique_ptr<MotionClassifierInterface> create(
-            sp<android::hardware::hidl_death_recipient> deathRecipient);
+            std::shared_ptr<aidl::android::hardware::input::processor::IInputProcessor> service);
 
     ~MotionClassifier();
 
@@ -144,7 +156,7 @@
 private:
     friend class MotionClassifierTest; // to create MotionClassifier with a test HAL implementation
     explicit MotionClassifier(
-            sp<android::hardware::input::classifier::V1_0::IInputClassifier> service);
+            std::shared_ptr<aidl::android::hardware::input::processor::IInputProcessor> service);
 
     // The events that need to be sent to the HAL.
     BlockingQueue<ClassifierEvent> mEvents;
@@ -163,14 +175,14 @@
      */
     void processEvents();
     /**
-     * Access to the InputClassifier HAL. May be null if init() hasn't completed yet.
+     * Access to the InputProcessor HAL. May be null if init() hasn't completed yet.
      * When init() successfully completes, mService is guaranteed to remain non-null and to not
      * change its value until MotionClassifier is destroyed.
      * This variable is *not* guarded by mLock in the InputClassifier thread, because
      * that thread knows exactly when this variable is initialized.
      * When accessed in any other thread, mService is checked for nullness with a lock.
      */
-    sp<android::hardware::input::classifier::V1_0::IInputClassifier> mService;
+    std::shared_ptr<aidl::android::hardware::input::processor::IInputProcessor> mService;
     std::mutex mLock;
     /**
      * Per-device input classifications. Should only be accessed using the
@@ -223,32 +235,33 @@
  */
 class InputClassifier : public InputClassifierInterface {
 public:
-    explicit InputClassifier(const sp<InputListenerInterface>& listener);
+    explicit InputClassifier(InputListenerInterface& listener);
 
-    virtual void notifyConfigurationChanged(const NotifyConfigurationChangedArgs* args) override;
-    virtual void notifyKey(const NotifyKeyArgs* args) override;
-    virtual void notifyMotion(const NotifyMotionArgs* args) override;
-    virtual void notifySwitch(const NotifySwitchArgs* args) override;
-    virtual void notifySensor(const NotifySensorArgs* args) override;
-    virtual void notifyVibratorState(const NotifyVibratorStateArgs* args) override;
-    virtual void notifyDeviceReset(const NotifyDeviceResetArgs* args) override;
+    void notifyConfigurationChanged(const NotifyConfigurationChangedArgs* args) override;
+    void notifyKey(const NotifyKeyArgs* args) override;
+    void notifyMotion(const NotifyMotionArgs* args) override;
+    void notifySwitch(const NotifySwitchArgs* args) override;
+    void notifySensor(const NotifySensorArgs* args) override;
+    void notifyVibratorState(const NotifyVibratorStateArgs* args) override;
+    void notifyDeviceReset(const NotifyDeviceResetArgs* args) override;
     void notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs* args) override;
 
-    virtual void dump(std::string& dump) override;
+    void dump(std::string& dump) override;
 
     ~InputClassifier();
 
     // Called from InputManager
-    virtual void setMotionClassifierEnabled(bool enabled) override;
+    void setMotionClassifierEnabled(bool enabled) override;
 
 private:
     // Protect access to mMotionClassifier, since it may become null via a hidl callback
     std::mutex mLock;
     // The next stage to pass input events to
-    sp<InputListenerInterface> mListener;
+    InputListenerInterface& mListener;
 
     std::unique_ptr<MotionClassifierInterface> mMotionClassifier GUARDED_BY(mLock);
-    std::thread mInitializeMotionClassifierThread;
+    std::future<void> mInitializeMotionClassifier GUARDED_BY(mLock);
+
     /**
      * Set the value of mMotionClassifier.
      * This is called from 2 different threads:
@@ -256,25 +269,12 @@
      * 2) A binder thread of the HalDeathRecipient, which is created when HAL dies. This would cause
      *    mMotionClassifier to become nullptr.
      */
-    void setMotionClassifier(std::unique_ptr<MotionClassifierInterface> motionClassifier);
+    void setMotionClassifierLocked(std::unique_ptr<MotionClassifierInterface> motionClassifier)
+            REQUIRES(mLock);
 
-    /**
-     * The deathRecipient will call setMotionClassifier(null) when the HAL dies.
-     */
-    class HalDeathRecipient : public android::hardware::hidl_death_recipient {
-    public:
-        explicit HalDeathRecipient(InputClassifier& parent);
-        virtual void serviceDied(uint64_t cookie,
-                                 const wp<android::hidl::base::V1_0::IBase>& who) override;
+    static void onBinderDied(void* cookie);
 
-    private:
-        InputClassifier& mParent;
-    };
-    // We retain a reference to death recipient, because the death recipient will be calling
-    // ~MotionClassifier if the HAL dies.
-    // If we don't retain a reference, and MotionClassifier is the only owner of the death
-    // recipient, the serviceDied call will cause death recipient to call its own destructor.
-    sp<HalDeathRecipient> mHalDeathRecipient;
+    std::unique_ptr<ScopedDeathRecipient> mHalDeathRecipient GUARDED_BY(mLock);
 };
 
 } // namespace android
diff --git a/services/inputflinger/InputClassifierConverter.cpp b/services/inputflinger/InputClassifierConverter.cpp
deleted file mode 100644
index fc8c7c3..0000000
--- a/services/inputflinger/InputClassifierConverter.cpp
+++ /dev/null
@@ -1,384 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "InputClassifierConverter.h"
-
-using android::hardware::hidl_bitfield;
-using namespace android::hardware::input;
-
-namespace android {
-
-static common::V1_0::Source getSource(uint32_t source) {
-    static_assert(static_cast<common::V1_0::Source>(AINPUT_SOURCE_UNKNOWN) ==
-            common::V1_0::Source::UNKNOWN, "SOURCE_UNKNOWN mismatch");
-    static_assert(static_cast<common::V1_0::Source>(AINPUT_SOURCE_KEYBOARD) ==
-            common::V1_0::Source::KEYBOARD, "SOURCE_KEYBOARD mismatch");
-    static_assert(static_cast<common::V1_0::Source>(AINPUT_SOURCE_DPAD) ==
-            common::V1_0::Source::DPAD, "SOURCE_DPAD mismatch");
-    static_assert(static_cast<common::V1_0::Source>(AINPUT_SOURCE_GAMEPAD) ==
-            common::V1_0::Source::GAMEPAD, "SOURCE_GAMEPAD mismatch");
-    static_assert(static_cast<common::V1_0::Source>(AINPUT_SOURCE_TOUCHSCREEN) ==
-            common::V1_0::Source::TOUCHSCREEN, "SOURCE_TOUCHSCREEN mismatch");
-    static_assert(static_cast<common::V1_0::Source>(AINPUT_SOURCE_MOUSE) ==
-            common::V1_0::Source::MOUSE, "SOURCE_MOUSE mismatch");
-    static_assert(static_cast<common::V1_0::Source>(AINPUT_SOURCE_STYLUS) ==
-            common::V1_0::Source::STYLUS, "SOURCE_STYLUS mismatch");
-    static_assert(static_cast<common::V1_0::Source>(AINPUT_SOURCE_BLUETOOTH_STYLUS) ==
-            common::V1_0::Source::BLUETOOTH_STYLUS, "SOURCE_BLUETOOTH_STYLUS mismatch");
-    static_assert(static_cast<common::V1_0::Source>(AINPUT_SOURCE_TRACKBALL) ==
-            common::V1_0::Source::TRACKBALL, "SOURCE_TRACKBALL mismatch");
-    static_assert(static_cast<common::V1_0::Source>(AINPUT_SOURCE_MOUSE_RELATIVE) ==
-            common::V1_0::Source::MOUSE_RELATIVE, "SOURCE_MOUSE_RELATIVE mismatch");
-    static_assert(static_cast<common::V1_0::Source>(AINPUT_SOURCE_TOUCHPAD) ==
-            common::V1_0::Source::TOUCHPAD, "SOURCE_TOUCHPAD mismatch");
-    static_assert(static_cast<common::V1_0::Source>(AINPUT_SOURCE_TOUCH_NAVIGATION) ==
-            common::V1_0::Source::TOUCH_NAVIGATION, "SOURCE_TOUCH_NAVIGATION mismatch");
-    static_assert(static_cast<common::V1_0::Source>(AINPUT_SOURCE_JOYSTICK) ==
-            common::V1_0::Source::JOYSTICK, "SOURCE_JOYSTICK mismatch");
-    static_assert(static_cast<common::V1_0::Source>(AINPUT_SOURCE_ROTARY_ENCODER) ==
-            common::V1_0::Source::ROTARY_ENCODER, "SOURCE_ROTARY_ENCODER mismatch");
-    static_assert(static_cast<common::V1_0::Source>(AINPUT_SOURCE_ANY) ==
-            common::V1_0::Source::ANY, "SOURCE_ANY mismatch");
-    return static_cast<common::V1_0::Source>(source);
-}
-
-static common::V1_0::Action getAction(int32_t actionMasked) {
-    static_assert(static_cast<common::V1_0::Action>(AMOTION_EVENT_ACTION_DOWN) ==
-            common::V1_0::Action::DOWN, "ACTION_DOWN mismatch");
-    static_assert(static_cast<common::V1_0::Action>(AMOTION_EVENT_ACTION_UP) ==
-            common::V1_0::Action::UP, "ACTION_UP mismatch");
-    static_assert(static_cast<common::V1_0::Action>(AMOTION_EVENT_ACTION_MOVE) ==
-            common::V1_0::Action::MOVE, "ACTION_MOVE mismatch");
-    static_assert(static_cast<common::V1_0::Action>(AMOTION_EVENT_ACTION_CANCEL) ==
-            common::V1_0::Action::CANCEL, "ACTION_CANCEL mismatch");
-    static_assert(static_cast<common::V1_0::Action>(AMOTION_EVENT_ACTION_OUTSIDE) ==
-            common::V1_0::Action::OUTSIDE, "ACTION_OUTSIDE mismatch");
-    static_assert(static_cast<common::V1_0::Action>(AMOTION_EVENT_ACTION_POINTER_DOWN) ==
-            common::V1_0::Action::POINTER_DOWN, "ACTION_POINTER_DOWN mismatch");
-    static_assert(static_cast<common::V1_0::Action>(AMOTION_EVENT_ACTION_POINTER_UP) ==
-            common::V1_0::Action::POINTER_UP, "ACTION_POINTER_UP mismatch");
-    static_assert(static_cast<common::V1_0::Action>( AMOTION_EVENT_ACTION_HOVER_MOVE) ==
-            common::V1_0::Action::HOVER_MOVE, "ACTION_HOVER_MOVE mismatch");
-    static_assert(static_cast<common::V1_0::Action>(AMOTION_EVENT_ACTION_SCROLL) ==
-            common::V1_0::Action::SCROLL, "ACTION_SCROLL mismatch");
-    static_assert(static_cast<common::V1_0::Action>(AMOTION_EVENT_ACTION_HOVER_ENTER) ==
-            common::V1_0::Action::HOVER_ENTER, "ACTION_HOVER_ENTER mismatch");
-    static_assert(static_cast<common::V1_0::Action>(AMOTION_EVENT_ACTION_HOVER_EXIT) ==
-            common::V1_0::Action::HOVER_EXIT, "ACTION_HOVER_EXIT mismatch");
-    static_assert(static_cast<common::V1_0::Action>(AMOTION_EVENT_ACTION_BUTTON_PRESS) ==
-            common::V1_0::Action::BUTTON_PRESS, "ACTION_BUTTON_PRESS mismatch");
-    static_assert(static_cast<common::V1_0::Action>(AMOTION_EVENT_ACTION_BUTTON_RELEASE) ==
-            common::V1_0::Action::BUTTON_RELEASE, "ACTION_BUTTON_RELEASE mismatch");
-    return static_cast<common::V1_0::Action>(actionMasked);
-}
-
-static common::V1_0::Button getActionButton(int32_t actionButton) {
-    static_assert(static_cast<common::V1_0::Button>(0) ==
-            common::V1_0::Button::NONE, "BUTTON_NONE mismatch");
-    static_assert(static_cast<common::V1_0::Button>(AMOTION_EVENT_BUTTON_PRIMARY) ==
-            common::V1_0::Button::PRIMARY, "BUTTON_PRIMARY mismatch");
-    static_assert(static_cast<common::V1_0::Button>(AMOTION_EVENT_BUTTON_SECONDARY) ==
-            common::V1_0::Button::SECONDARY, "BUTTON_SECONDARY mismatch");
-    static_assert(static_cast<common::V1_0::Button>(AMOTION_EVENT_BUTTON_TERTIARY) ==
-            common::V1_0::Button::TERTIARY, "BUTTON_TERTIARY mismatch");
-    static_assert(static_cast<common::V1_0::Button>(AMOTION_EVENT_BUTTON_BACK) ==
-            common::V1_0::Button::BACK, "BUTTON_BACK mismatch");
-    static_assert(static_cast<common::V1_0::Button>(AMOTION_EVENT_BUTTON_FORWARD) ==
-            common::V1_0::Button::FORWARD, "BUTTON_FORWARD mismatch");
-    static_assert(static_cast<common::V1_0::Button>(AMOTION_EVENT_BUTTON_STYLUS_PRIMARY) ==
-            common::V1_0::Button::STYLUS_PRIMARY, "BUTTON_STYLUS_PRIMARY mismatch");
-    static_assert(static_cast<common::V1_0::Button>(AMOTION_EVENT_BUTTON_STYLUS_SECONDARY) ==
-            common::V1_0::Button::STYLUS_SECONDARY, "BUTTON_STYLUS_SECONDARY mismatch");
-    return static_cast<common::V1_0::Button>(actionButton);
-}
-
-static hidl_bitfield<common::V1_0::Flag> getFlags(int32_t flags) {
-    static_assert(static_cast<common::V1_0::Flag>(AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED) ==
-            common::V1_0::Flag::WINDOW_IS_OBSCURED);
-    static_assert(static_cast<common::V1_0::Flag>(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE) ==
-            common::V1_0::Flag::IS_GENERATED_GESTURE);
-    static_assert(static_cast<common::V1_0::Flag>(AMOTION_EVENT_FLAG_TAINTED) ==
-            common::V1_0::Flag::TAINTED);
-    return static_cast<hidl_bitfield<common::V1_0::Flag>>(flags);
-}
-
-static hidl_bitfield<common::V1_0::PolicyFlag> getPolicyFlags(int32_t flags) {
-    static_assert(static_cast<common::V1_0::PolicyFlag>(POLICY_FLAG_WAKE) ==
-            common::V1_0::PolicyFlag::WAKE);
-    static_assert(static_cast<common::V1_0::PolicyFlag>(POLICY_FLAG_VIRTUAL) ==
-            common::V1_0::PolicyFlag::VIRTUAL);
-    static_assert(static_cast<common::V1_0::PolicyFlag>(POLICY_FLAG_FUNCTION) ==
-            common::V1_0::PolicyFlag::FUNCTION);
-    static_assert(static_cast<common::V1_0::PolicyFlag>(POLICY_FLAG_GESTURE) ==
-            common::V1_0::PolicyFlag::GESTURE);
-    static_assert(static_cast<common::V1_0::PolicyFlag>(POLICY_FLAG_INJECTED) ==
-            common::V1_0::PolicyFlag::INJECTED);
-    static_assert(static_cast<common::V1_0::PolicyFlag>(POLICY_FLAG_TRUSTED) ==
-            common::V1_0::PolicyFlag::TRUSTED);
-    static_assert(static_cast<common::V1_0::PolicyFlag>(POLICY_FLAG_FILTERED) ==
-            common::V1_0::PolicyFlag::FILTERED);
-    static_assert(static_cast<common::V1_0::PolicyFlag>(POLICY_FLAG_DISABLE_KEY_REPEAT) ==
-            common::V1_0::PolicyFlag::DISABLE_KEY_REPEAT);
-    static_assert(static_cast<common::V1_0::PolicyFlag>(POLICY_FLAG_INTERACTIVE) ==
-            common::V1_0::PolicyFlag::INTERACTIVE);
-    static_assert(static_cast<common::V1_0::PolicyFlag>(POLICY_FLAG_PASS_TO_USER) ==
-            common::V1_0::PolicyFlag::PASS_TO_USER);
-    return static_cast<hidl_bitfield<common::V1_0::PolicyFlag>>(flags);
-}
-
-static hidl_bitfield<common::V1_0::EdgeFlag> getEdgeFlags(int32_t flags) {
-    static_assert(static_cast<common::V1_0::EdgeFlag>(AMOTION_EVENT_EDGE_FLAG_NONE) ==
-            common::V1_0::EdgeFlag::NONE);
-    static_assert(static_cast<common::V1_0::EdgeFlag>(AMOTION_EVENT_EDGE_FLAG_TOP) ==
-            common::V1_0::EdgeFlag::TOP);
-    static_assert(static_cast<common::V1_0::EdgeFlag>(AMOTION_EVENT_EDGE_FLAG_BOTTOM) ==
-            common::V1_0::EdgeFlag::BOTTOM);
-    static_assert(static_cast<common::V1_0::EdgeFlag>(AMOTION_EVENT_EDGE_FLAG_LEFT) ==
-            common::V1_0::EdgeFlag::LEFT);
-    static_assert(static_cast<common::V1_0::EdgeFlag>(AMOTION_EVENT_EDGE_FLAG_RIGHT) ==
-            common::V1_0::EdgeFlag::RIGHT);
-    return static_cast<hidl_bitfield<common::V1_0::EdgeFlag>>(flags);
-}
-
-static hidl_bitfield<common::V1_0::Meta> getMetastate(int32_t state) {
-    static_assert(static_cast<common::V1_0::Meta>(AMETA_NONE) ==
-            common::V1_0::Meta::NONE);
-    static_assert(static_cast<common::V1_0::Meta>(AMETA_ALT_ON) ==
-            common::V1_0::Meta::ALT_ON);
-    static_assert(static_cast<common::V1_0::Meta>(AMETA_ALT_LEFT_ON) ==
-            common::V1_0::Meta::ALT_LEFT_ON);
-    static_assert(static_cast<common::V1_0::Meta>(AMETA_ALT_RIGHT_ON) ==
-            common::V1_0::Meta::ALT_RIGHT_ON);
-    static_assert(static_cast<common::V1_0::Meta>(AMETA_SHIFT_ON) ==
-            common::V1_0::Meta::SHIFT_ON);
-    static_assert(static_cast<common::V1_0::Meta>(AMETA_SHIFT_LEFT_ON) ==
-            common::V1_0::Meta::SHIFT_LEFT_ON);
-    static_assert(static_cast<common::V1_0::Meta>(AMETA_SHIFT_RIGHT_ON) ==
-            common::V1_0::Meta::SHIFT_RIGHT_ON);
-    static_assert(static_cast<common::V1_0::Meta>(AMETA_SYM_ON) ==
-            common::V1_0::Meta::SYM_ON);
-    static_assert(static_cast<common::V1_0::Meta>(AMETA_FUNCTION_ON) ==
-            common::V1_0::Meta::FUNCTION_ON);
-    static_assert(static_cast<common::V1_0::Meta>(AMETA_CTRL_ON) ==
-            common::V1_0::Meta::CTRL_ON);
-    static_assert(static_cast<common::V1_0::Meta>(AMETA_CTRL_LEFT_ON) ==
-            common::V1_0::Meta::CTRL_LEFT_ON);
-    static_assert(static_cast<common::V1_0::Meta>(AMETA_CTRL_RIGHT_ON) ==
-            common::V1_0::Meta::CTRL_RIGHT_ON);
-    static_assert(static_cast<common::V1_0::Meta>(AMETA_META_ON) ==
-            common::V1_0::Meta::META_ON);
-    static_assert(static_cast<common::V1_0::Meta>(AMETA_META_LEFT_ON) ==
-            common::V1_0::Meta::META_LEFT_ON);
-    static_assert(static_cast<common::V1_0::Meta>(AMETA_META_RIGHT_ON) ==
-            common::V1_0::Meta::META_RIGHT_ON);
-    static_assert(static_cast<common::V1_0::Meta>(AMETA_CAPS_LOCK_ON) ==
-            common::V1_0::Meta::CAPS_LOCK_ON);
-    static_assert(static_cast<common::V1_0::Meta>(AMETA_NUM_LOCK_ON) ==
-            common::V1_0::Meta::NUM_LOCK_ON);
-    static_assert(static_cast<common::V1_0::Meta>(AMETA_SCROLL_LOCK_ON) ==
-            common::V1_0::Meta::SCROLL_LOCK_ON);
-    return static_cast<hidl_bitfield<common::V1_0::Meta>>(state);
-}
-
-static hidl_bitfield<common::V1_0::Button> getButtonState(int32_t buttonState) {
-    // No need for static_assert here.
-    // The button values have already been asserted in getActionButton(..) above
-    return static_cast<hidl_bitfield<common::V1_0::Button>>(buttonState);
-}
-
-static common::V1_0::ToolType getToolType(int32_t toolType) {
-    static_assert(static_cast<common::V1_0::ToolType>(AMOTION_EVENT_TOOL_TYPE_UNKNOWN) ==
-            common::V1_0::ToolType::UNKNOWN);
-    static_assert(static_cast<common::V1_0::ToolType>(AMOTION_EVENT_TOOL_TYPE_FINGER) ==
-            common::V1_0::ToolType::FINGER);
-    static_assert(static_cast<common::V1_0::ToolType>(AMOTION_EVENT_TOOL_TYPE_STYLUS) ==
-            common::V1_0::ToolType::STYLUS);
-    static_assert(static_cast<common::V1_0::ToolType>(AMOTION_EVENT_TOOL_TYPE_MOUSE) ==
-            common::V1_0::ToolType::MOUSE);
-    static_assert(static_cast<common::V1_0::ToolType>(AMOTION_EVENT_TOOL_TYPE_ERASER) ==
-            common::V1_0::ToolType::ERASER);
-    return static_cast<common::V1_0::ToolType>(toolType);
-}
-
-// MotionEvent axes asserts
-static_assert(static_cast<common::V1_0::Axis>(AMOTION_EVENT_AXIS_X) ==
-        common::V1_0::Axis::X);
-static_assert(static_cast<common::V1_0::Axis>(AMOTION_EVENT_AXIS_Y) ==
-        common::V1_0::Axis::Y);
-static_assert(static_cast<common::V1_0::Axis>(AMOTION_EVENT_AXIS_PRESSURE) ==
-        common::V1_0::Axis::PRESSURE);
-static_assert(static_cast<common::V1_0::Axis>(AMOTION_EVENT_AXIS_SIZE) ==
-        common::V1_0::Axis::SIZE);
-static_assert(static_cast<common::V1_0::Axis>(AMOTION_EVENT_AXIS_TOUCH_MAJOR) ==
-        common::V1_0::Axis::TOUCH_MAJOR);
-static_assert(static_cast<common::V1_0::Axis>(AMOTION_EVENT_AXIS_TOUCH_MINOR) ==
-        common::V1_0::Axis::TOUCH_MINOR);
-static_assert(static_cast<common::V1_0::Axis>(AMOTION_EVENT_AXIS_TOOL_MAJOR) ==
-        common::V1_0::Axis::TOOL_MAJOR);
-static_assert(static_cast<common::V1_0::Axis>(AMOTION_EVENT_AXIS_TOOL_MINOR) ==
-        common::V1_0::Axis::TOOL_MINOR);
-static_assert(static_cast<common::V1_0::Axis>(AMOTION_EVENT_AXIS_ORIENTATION) ==
-        common::V1_0::Axis::ORIENTATION);
-static_assert(static_cast<common::V1_0::Axis>(AMOTION_EVENT_AXIS_VSCROLL) ==
-        common::V1_0::Axis::VSCROLL);
-static_assert(static_cast<common::V1_0::Axis>(AMOTION_EVENT_AXIS_HSCROLL) ==
-        common::V1_0::Axis::HSCROLL);
-static_assert(static_cast<common::V1_0::Axis>(AMOTION_EVENT_AXIS_Z) ==
-        common::V1_0::Axis::Z);
-static_assert(static_cast<common::V1_0::Axis>(AMOTION_EVENT_AXIS_RX) ==
-        common::V1_0::Axis::RX);
-static_assert(static_cast<common::V1_0::Axis>(AMOTION_EVENT_AXIS_RY) ==
-        common::V1_0::Axis::RY);
-static_assert(static_cast<common::V1_0::Axis>(AMOTION_EVENT_AXIS_RZ) ==
-        common::V1_0::Axis::RZ);
-static_assert(static_cast<common::V1_0::Axis>(AMOTION_EVENT_AXIS_HAT_X) ==
-        common::V1_0::Axis::HAT_X);
-static_assert(static_cast<common::V1_0::Axis>(AMOTION_EVENT_AXIS_HAT_Y) ==
-        common::V1_0::Axis::HAT_Y);
-static_assert(static_cast<common::V1_0::Axis>(AMOTION_EVENT_AXIS_LTRIGGER) ==
-        common::V1_0::Axis::LTRIGGER);
-static_assert(static_cast<common::V1_0::Axis>(AMOTION_EVENT_AXIS_RTRIGGER) ==
-        common::V1_0::Axis::RTRIGGER);
-static_assert(static_cast<common::V1_0::Axis>(AMOTION_EVENT_AXIS_THROTTLE) ==
-        common::V1_0::Axis::THROTTLE);
-static_assert(static_cast<common::V1_0::Axis>(AMOTION_EVENT_AXIS_RUDDER) ==
-        common::V1_0::Axis::RUDDER);
-static_assert(static_cast<common::V1_0::Axis>(AMOTION_EVENT_AXIS_WHEEL) ==
-        common::V1_0::Axis::WHEEL);
-static_assert(static_cast<common::V1_0::Axis>(AMOTION_EVENT_AXIS_GAS) ==
-        common::V1_0::Axis::GAS);
-static_assert(static_cast<common::V1_0::Axis>(AMOTION_EVENT_AXIS_BRAKE) ==
-        common::V1_0::Axis::BRAKE);
-static_assert(static_cast<common::V1_0::Axis>(AMOTION_EVENT_AXIS_DISTANCE) ==
-        common::V1_0::Axis::DISTANCE);
-static_assert(static_cast<common::V1_0::Axis>(AMOTION_EVENT_AXIS_TILT) ==
-        common::V1_0::Axis::TILT);
-static_assert(static_cast<common::V1_0::Axis>(AMOTION_EVENT_AXIS_SCROLL) ==
-        common::V1_0::Axis::SCROLL);
-static_assert(static_cast<common::V1_0::Axis>(AMOTION_EVENT_AXIS_RELATIVE_X) ==
-        common::V1_0::Axis::RELATIVE_X);
-static_assert(static_cast<common::V1_0::Axis>(AMOTION_EVENT_AXIS_RELATIVE_Y) ==
-        common::V1_0::Axis::RELATIVE_Y);
-static_assert(static_cast<common::V1_0::Axis>(AMOTION_EVENT_AXIS_GENERIC_1) ==
-        common::V1_0::Axis::GENERIC_1);
-static_assert(static_cast<common::V1_0::Axis>(AMOTION_EVENT_AXIS_GENERIC_2) ==
-        common::V1_0::Axis::GENERIC_2);
-static_assert(static_cast<common::V1_0::Axis>(AMOTION_EVENT_AXIS_GENERIC_3) ==
-        common::V1_0::Axis::GENERIC_3);
-static_assert(static_cast<common::V1_0::Axis>(AMOTION_EVENT_AXIS_GENERIC_4) ==
-        common::V1_0::Axis::GENERIC_4);
-static_assert(static_cast<common::V1_0::Axis>(AMOTION_EVENT_AXIS_GENERIC_5) ==
-        common::V1_0::Axis::GENERIC_5);
-static_assert(static_cast<common::V1_0::Axis>(AMOTION_EVENT_AXIS_GENERIC_6) ==
-        common::V1_0::Axis::GENERIC_6);
-static_assert(static_cast<common::V1_0::Axis>(AMOTION_EVENT_AXIS_GENERIC_7) ==
-        common::V1_0::Axis::GENERIC_7);
-static_assert(static_cast<common::V1_0::Axis>(AMOTION_EVENT_AXIS_GENERIC_8) ==
-        common::V1_0::Axis::GENERIC_8);
-static_assert(static_cast<common::V1_0::Axis>(AMOTION_EVENT_AXIS_GENERIC_9) ==
-        common::V1_0::Axis::GENERIC_9);
-static_assert(static_cast<common::V1_0::Axis>(AMOTION_EVENT_AXIS_GENERIC_10) ==
-        common::V1_0::Axis::GENERIC_10);
-static_assert(static_cast<common::V1_0::Axis>(AMOTION_EVENT_AXIS_GENERIC_11) ==
-        common::V1_0::Axis::GENERIC_11);
-static_assert(static_cast<common::V1_0::Axis>(AMOTION_EVENT_AXIS_GENERIC_12) ==
-        common::V1_0::Axis::GENERIC_12);
-static_assert(static_cast<common::V1_0::Axis>(AMOTION_EVENT_AXIS_GENERIC_13) ==
-        common::V1_0::Axis::GENERIC_13);
-static_assert(static_cast<common::V1_0::Axis>(AMOTION_EVENT_AXIS_GENERIC_14) ==
-        common::V1_0::Axis::GENERIC_14);
-static_assert(static_cast<common::V1_0::Axis>(AMOTION_EVENT_AXIS_GENERIC_15) ==
-        common::V1_0::Axis::GENERIC_15);
-static_assert(static_cast<common::V1_0::Axis>(AMOTION_EVENT_AXIS_GENERIC_16) ==
-        common::V1_0::Axis::GENERIC_16);
-
-static common::V1_0::VideoFrame getHalVideoFrame(const TouchVideoFrame& frame) {
-    common::V1_0::VideoFrame out;
-    out.width = frame.getWidth();
-    out.height = frame.getHeight();
-    out.data = frame.getData();
-    struct timeval timestamp = frame.getTimestamp();
-    out.timestamp = seconds_to_nanoseconds(timestamp.tv_sec) +
-             microseconds_to_nanoseconds(timestamp.tv_usec);
-    return out;
-}
-
-static std::vector<common::V1_0::VideoFrame> convertVideoFrames(
-        const std::vector<TouchVideoFrame>& frames) {
-    std::vector<common::V1_0::VideoFrame> out;
-    for (const TouchVideoFrame& frame : frames) {
-        out.push_back(getHalVideoFrame(frame));
-    }
-    return out;
-}
-
-static uint8_t getActionIndex(int32_t action) {
-    return (action & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK) >>
-            AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
-}
-
-static void getHidlPropertiesAndCoords(const NotifyMotionArgs& args,
-        std::vector<common::V1_0::PointerProperties>* outPointerProperties,
-        std::vector<common::V1_0::PointerCoords>* outPointerCoords) {
-    outPointerProperties->reserve(args.pointerCount);
-    outPointerCoords->reserve(args.pointerCount);
-    for (size_t i = 0; i < args.pointerCount; i++) {
-        common::V1_0::PointerProperties properties;
-        properties.id = args.pointerProperties[i].id;
-        properties.toolType = getToolType(args.pointerProperties[i].toolType);
-        outPointerProperties->push_back(properties);
-
-        common::V1_0::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 + BitSet64::count(args.pointerCoords[i].bits));
-        outPointerCoords->push_back(coords);
-    }
-}
-
-common::V1_0::MotionEvent notifyMotionArgsToHalMotionEvent(const NotifyMotionArgs& args) {
-    common::V1_0::MotionEvent event;
-    event.deviceId = args.deviceId;
-    event.source = getSource(args.source);
-    event.displayId = args.displayId;
-    event.downTime = args.downTime;
-    event.eventTime = args.eventTime;
-    event.deviceTimestamp = 0;
-    event.action = getAction(args.action & AMOTION_EVENT_ACTION_MASK);
-    event.actionIndex = getActionIndex(args.action);
-    event.actionButton = getActionButton(args.actionButton);
-    event.flags = getFlags(args.flags);
-    event.policyFlags = getPolicyFlags(args.policyFlags);
-    event.edgeFlags = getEdgeFlags(args.edgeFlags);
-    event.metaState = getMetastate(args.metaState);
-    event.buttonState = getButtonState(args.buttonState);
-    event.xPrecision = args.xPrecision;
-    event.yPrecision = args.yPrecision;
-
-    std::vector<common::V1_0::PointerProperties> pointerProperties;
-    std::vector<common::V1_0::PointerCoords> pointerCoords;
-    getHidlPropertiesAndCoords(args, /*out*/&pointerProperties, /*out*/&pointerCoords);
-    event.pointerProperties = pointerProperties;
-    event.pointerCoords = pointerCoords;
-
-    event.frames = convertVideoFrames(args.videoFrames);
-
-    return event;
-}
-
-} // namespace android
diff --git a/services/inputflinger/InputCommonConverter.cpp b/services/inputflinger/InputCommonConverter.cpp
new file mode 100644
index 0000000..8aee39f
--- /dev/null
+++ b/services/inputflinger/InputCommonConverter.cpp
@@ -0,0 +1,339 @@
+/*
+ * 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 "InputCommonConverter.h"
+
+using namespace ::aidl::android::hardware::input;
+
+namespace android {
+
+static common::Source getSource(uint32_t source) {
+    static_assert(static_cast<common::Source>(AINPUT_SOURCE_UNKNOWN) == common::Source::UNKNOWN,
+                  "SOURCE_UNKNOWN mismatch");
+    static_assert(static_cast<common::Source>(AINPUT_SOURCE_KEYBOARD) == common::Source::KEYBOARD,
+                  "SOURCE_KEYBOARD mismatch");
+    static_assert(static_cast<common::Source>(AINPUT_SOURCE_DPAD) == common::Source::DPAD,
+                  "SOURCE_DPAD mismatch");
+    static_assert(static_cast<common::Source>(AINPUT_SOURCE_GAMEPAD) == common::Source::GAMEPAD,
+                  "SOURCE_GAMEPAD mismatch");
+    static_assert(static_cast<common::Source>(AINPUT_SOURCE_TOUCHSCREEN) ==
+                          common::Source::TOUCHSCREEN,
+                  "SOURCE_TOUCHSCREEN mismatch");
+    static_assert(static_cast<common::Source>(AINPUT_SOURCE_MOUSE) == common::Source::MOUSE,
+                  "SOURCE_MOUSE mismatch");
+    static_assert(static_cast<common::Source>(AINPUT_SOURCE_STYLUS) == common::Source::STYLUS,
+                  "SOURCE_STYLUS mismatch");
+    static_assert(static_cast<common::Source>(AINPUT_SOURCE_BLUETOOTH_STYLUS) ==
+                          common::Source::BLUETOOTH_STYLUS,
+                  "SOURCE_BLUETOOTH_STYLUS mismatch");
+    static_assert(static_cast<common::Source>(AINPUT_SOURCE_TRACKBALL) == common::Source::TRACKBALL,
+                  "SOURCE_TRACKBALL mismatch");
+    static_assert(static_cast<common::Source>(AINPUT_SOURCE_MOUSE_RELATIVE) ==
+                          common::Source::MOUSE_RELATIVE,
+                  "SOURCE_MOUSE_RELATIVE mismatch");
+    static_assert(static_cast<common::Source>(AINPUT_SOURCE_TOUCHPAD) == common::Source::TOUCHPAD,
+                  "SOURCE_TOUCHPAD mismatch");
+    static_assert(static_cast<common::Source>(AINPUT_SOURCE_TOUCH_NAVIGATION) ==
+                          common::Source::TOUCH_NAVIGATION,
+                  "SOURCE_TOUCH_NAVIGATION mismatch");
+    static_assert(static_cast<common::Source>(AINPUT_SOURCE_JOYSTICK) == common::Source::JOYSTICK,
+                  "SOURCE_JOYSTICK mismatch");
+    static_assert(static_cast<common::Source>(AINPUT_SOURCE_ROTARY_ENCODER) ==
+                          common::Source::ROTARY_ENCODER,
+                  "SOURCE_ROTARY_ENCODER mismatch");
+    static_assert(static_cast<common::Source>(AINPUT_SOURCE_HDMI) == common::Source::HDMI);
+    static_assert(static_cast<common::Source>(AINPUT_SOURCE_SENSOR) == common::Source::SENSOR);
+    static_assert(static_cast<common::Source>(AINPUT_SOURCE_ANY) == common::Source::ANY,
+                  "SOURCE_ANY mismatch");
+    return static_cast<common::Source>(source);
+}
+
+static common::Action getAction(int32_t actionMasked) {
+    static_assert(static_cast<common::Action>(AMOTION_EVENT_ACTION_DOWN) == common::Action::DOWN,
+                  "ACTION_DOWN mismatch");
+    static_assert(static_cast<common::Action>(AMOTION_EVENT_ACTION_UP) == common::Action::UP,
+                  "ACTION_UP mismatch");
+    static_assert(static_cast<common::Action>(AMOTION_EVENT_ACTION_MOVE) == common::Action::MOVE,
+                  "ACTION_MOVE mismatch");
+    static_assert(static_cast<common::Action>(AMOTION_EVENT_ACTION_CANCEL) ==
+                          common::Action::CANCEL,
+                  "ACTION_CANCEL mismatch");
+    static_assert(static_cast<common::Action>(AMOTION_EVENT_ACTION_OUTSIDE) ==
+                          common::Action::OUTSIDE,
+                  "ACTION_OUTSIDE mismatch");
+    static_assert(static_cast<common::Action>(AMOTION_EVENT_ACTION_POINTER_DOWN) ==
+                          common::Action::POINTER_DOWN,
+                  "ACTION_POINTER_DOWN mismatch");
+    static_assert(static_cast<common::Action>(AMOTION_EVENT_ACTION_POINTER_UP) ==
+                          common::Action::POINTER_UP,
+                  "ACTION_POINTER_UP mismatch");
+    static_assert(static_cast<common::Action>(AMOTION_EVENT_ACTION_HOVER_MOVE) ==
+                          common::Action::HOVER_MOVE,
+                  "ACTION_HOVER_MOVE mismatch");
+    static_assert(static_cast<common::Action>(AMOTION_EVENT_ACTION_SCROLL) ==
+                          common::Action::SCROLL,
+                  "ACTION_SCROLL mismatch");
+    static_assert(static_cast<common::Action>(AMOTION_EVENT_ACTION_HOVER_ENTER) ==
+                          common::Action::HOVER_ENTER,
+                  "ACTION_HOVER_ENTER mismatch");
+    static_assert(static_cast<common::Action>(AMOTION_EVENT_ACTION_HOVER_EXIT) ==
+                          common::Action::HOVER_EXIT,
+                  "ACTION_HOVER_EXIT mismatch");
+    static_assert(static_cast<common::Action>(AMOTION_EVENT_ACTION_BUTTON_PRESS) ==
+                          common::Action::BUTTON_PRESS,
+                  "ACTION_BUTTON_PRESS mismatch");
+    static_assert(static_cast<common::Action>(AMOTION_EVENT_ACTION_BUTTON_RELEASE) ==
+                          common::Action::BUTTON_RELEASE,
+                  "ACTION_BUTTON_RELEASE mismatch");
+    return static_cast<common::Action>(actionMasked);
+}
+
+static common::Button getActionButton(int32_t actionButton) {
+    static_assert(static_cast<common::Button>(0) == common::Button::NONE, "BUTTON_NONE mismatch");
+    static_assert(static_cast<common::Button>(AMOTION_EVENT_BUTTON_PRIMARY) ==
+                          common::Button::PRIMARY,
+                  "BUTTON_PRIMARY mismatch");
+    static_assert(static_cast<common::Button>(AMOTION_EVENT_BUTTON_SECONDARY) ==
+                          common::Button::SECONDARY,
+                  "BUTTON_SECONDARY mismatch");
+    static_assert(static_cast<common::Button>(AMOTION_EVENT_BUTTON_TERTIARY) ==
+                          common::Button::TERTIARY,
+                  "BUTTON_TERTIARY mismatch");
+    static_assert(static_cast<common::Button>(AMOTION_EVENT_BUTTON_BACK) == common::Button::BACK,
+                  "BUTTON_BACK mismatch");
+    static_assert(static_cast<common::Button>(AMOTION_EVENT_BUTTON_FORWARD) ==
+                          common::Button::FORWARD,
+                  "BUTTON_FORWARD mismatch");
+    static_assert(static_cast<common::Button>(AMOTION_EVENT_BUTTON_STYLUS_PRIMARY) ==
+                          common::Button::STYLUS_PRIMARY,
+                  "BUTTON_STYLUS_PRIMARY mismatch");
+    static_assert(static_cast<common::Button>(AMOTION_EVENT_BUTTON_STYLUS_SECONDARY) ==
+                          common::Button::STYLUS_SECONDARY,
+                  "BUTTON_STYLUS_SECONDARY mismatch");
+    return static_cast<common::Button>(actionButton);
+}
+
+static common::Flag getFlags(int32_t flags) {
+    static_assert(static_cast<common::Flag>(AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED) ==
+                  common::Flag::WINDOW_IS_OBSCURED);
+    static_assert(static_cast<common::Flag>(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE) ==
+                  common::Flag::IS_GENERATED_GESTURE);
+    static_assert(static_cast<common::Flag>(AMOTION_EVENT_FLAG_TAINTED) == common::Flag::TAINTED);
+    return static_cast<common::Flag>(flags);
+}
+
+static common::PolicyFlag getPolicyFlags(int32_t flags) {
+    static_assert(static_cast<common::PolicyFlag>(POLICY_FLAG_WAKE) == common::PolicyFlag::WAKE);
+    static_assert(static_cast<common::PolicyFlag>(POLICY_FLAG_VIRTUAL) ==
+                  common::PolicyFlag::VIRTUAL);
+    static_assert(static_cast<common::PolicyFlag>(POLICY_FLAG_FUNCTION) ==
+                  common::PolicyFlag::FUNCTION);
+    static_assert(static_cast<common::PolicyFlag>(POLICY_FLAG_GESTURE) ==
+                  common::PolicyFlag::GESTURE);
+    static_assert(static_cast<common::PolicyFlag>(POLICY_FLAG_INJECTED) ==
+                  common::PolicyFlag::INJECTED);
+    static_assert(static_cast<common::PolicyFlag>(POLICY_FLAG_TRUSTED) ==
+                  common::PolicyFlag::TRUSTED);
+    static_assert(static_cast<common::PolicyFlag>(POLICY_FLAG_FILTERED) ==
+                  common::PolicyFlag::FILTERED);
+    static_assert(static_cast<common::PolicyFlag>(POLICY_FLAG_DISABLE_KEY_REPEAT) ==
+                  common::PolicyFlag::DISABLE_KEY_REPEAT);
+    static_assert(static_cast<common::PolicyFlag>(POLICY_FLAG_INTERACTIVE) ==
+                  common::PolicyFlag::INTERACTIVE);
+    static_assert(static_cast<common::PolicyFlag>(POLICY_FLAG_PASS_TO_USER) ==
+                  common::PolicyFlag::PASS_TO_USER);
+    return static_cast<common::PolicyFlag>(flags);
+}
+
+static common::EdgeFlag getEdgeFlags(int32_t flags) {
+    static_assert(static_cast<common::EdgeFlag>(AMOTION_EVENT_EDGE_FLAG_NONE) ==
+                  common::EdgeFlag::NONE);
+    static_assert(static_cast<common::EdgeFlag>(AMOTION_EVENT_EDGE_FLAG_TOP) ==
+                  common::EdgeFlag::TOP);
+    static_assert(static_cast<common::EdgeFlag>(AMOTION_EVENT_EDGE_FLAG_BOTTOM) ==
+                  common::EdgeFlag::BOTTOM);
+    static_assert(static_cast<common::EdgeFlag>(AMOTION_EVENT_EDGE_FLAG_LEFT) ==
+                  common::EdgeFlag::LEFT);
+    static_assert(static_cast<common::EdgeFlag>(AMOTION_EVENT_EDGE_FLAG_RIGHT) ==
+                  common::EdgeFlag::RIGHT);
+    return static_cast<common::EdgeFlag>(flags);
+}
+
+static common::Meta getMetastate(int32_t state) {
+    static_assert(static_cast<common::Meta>(AMETA_NONE) == common::Meta::NONE);
+    static_assert(static_cast<common::Meta>(AMETA_ALT_ON) == common::Meta::ALT_ON);
+    static_assert(static_cast<common::Meta>(AMETA_ALT_LEFT_ON) == common::Meta::ALT_LEFT_ON);
+    static_assert(static_cast<common::Meta>(AMETA_ALT_RIGHT_ON) == common::Meta::ALT_RIGHT_ON);
+    static_assert(static_cast<common::Meta>(AMETA_SHIFT_ON) == common::Meta::SHIFT_ON);
+    static_assert(static_cast<common::Meta>(AMETA_SHIFT_LEFT_ON) == common::Meta::SHIFT_LEFT_ON);
+    static_assert(static_cast<common::Meta>(AMETA_SHIFT_RIGHT_ON) == common::Meta::SHIFT_RIGHT_ON);
+    static_assert(static_cast<common::Meta>(AMETA_SYM_ON) == common::Meta::SYM_ON);
+    static_assert(static_cast<common::Meta>(AMETA_FUNCTION_ON) == common::Meta::FUNCTION_ON);
+    static_assert(static_cast<common::Meta>(AMETA_CTRL_ON) == common::Meta::CTRL_ON);
+    static_assert(static_cast<common::Meta>(AMETA_CTRL_LEFT_ON) == common::Meta::CTRL_LEFT_ON);
+    static_assert(static_cast<common::Meta>(AMETA_CTRL_RIGHT_ON) == common::Meta::CTRL_RIGHT_ON);
+    static_assert(static_cast<common::Meta>(AMETA_META_ON) == common::Meta::META_ON);
+    static_assert(static_cast<common::Meta>(AMETA_META_LEFT_ON) == common::Meta::META_LEFT_ON);
+    static_assert(static_cast<common::Meta>(AMETA_META_RIGHT_ON) == common::Meta::META_RIGHT_ON);
+    static_assert(static_cast<common::Meta>(AMETA_CAPS_LOCK_ON) == common::Meta::CAPS_LOCK_ON);
+    static_assert(static_cast<common::Meta>(AMETA_NUM_LOCK_ON) == common::Meta::NUM_LOCK_ON);
+    static_assert(static_cast<common::Meta>(AMETA_SCROLL_LOCK_ON) == common::Meta::SCROLL_LOCK_ON);
+    return static_cast<common::Meta>(state);
+}
+
+static common::Button getButtonState(int32_t buttonState) {
+    // No need for static_assert here.
+    // The button values have already been asserted in getActionButton(..) above
+    return static_cast<common::Button>(buttonState);
+}
+
+static common::ToolType getToolType(int32_t toolType) {
+    static_assert(static_cast<common::ToolType>(AMOTION_EVENT_TOOL_TYPE_UNKNOWN) ==
+                  common::ToolType::UNKNOWN);
+    static_assert(static_cast<common::ToolType>(AMOTION_EVENT_TOOL_TYPE_FINGER) ==
+                  common::ToolType::FINGER);
+    static_assert(static_cast<common::ToolType>(AMOTION_EVENT_TOOL_TYPE_STYLUS) ==
+                  common::ToolType::STYLUS);
+    static_assert(static_cast<common::ToolType>(AMOTION_EVENT_TOOL_TYPE_MOUSE) ==
+                  common::ToolType::MOUSE);
+    static_assert(static_cast<common::ToolType>(AMOTION_EVENT_TOOL_TYPE_ERASER) ==
+                  common::ToolType::ERASER);
+    return static_cast<common::ToolType>(toolType);
+}
+
+// MotionEvent axes asserts
+static_assert(static_cast<common::Axis>(AMOTION_EVENT_AXIS_X) == common::Axis::X);
+static_assert(static_cast<common::Axis>(AMOTION_EVENT_AXIS_Y) == common::Axis::Y);
+static_assert(static_cast<common::Axis>(AMOTION_EVENT_AXIS_PRESSURE) == common::Axis::PRESSURE);
+static_assert(static_cast<common::Axis>(AMOTION_EVENT_AXIS_SIZE) == common::Axis::SIZE);
+static_assert(static_cast<common::Axis>(AMOTION_EVENT_AXIS_TOUCH_MAJOR) ==
+              common::Axis::TOUCH_MAJOR);
+static_assert(static_cast<common::Axis>(AMOTION_EVENT_AXIS_TOUCH_MINOR) ==
+              common::Axis::TOUCH_MINOR);
+static_assert(static_cast<common::Axis>(AMOTION_EVENT_AXIS_TOOL_MAJOR) == common::Axis::TOOL_MAJOR);
+static_assert(static_cast<common::Axis>(AMOTION_EVENT_AXIS_TOOL_MINOR) == common::Axis::TOOL_MINOR);
+static_assert(static_cast<common::Axis>(AMOTION_EVENT_AXIS_ORIENTATION) ==
+              common::Axis::ORIENTATION);
+static_assert(static_cast<common::Axis>(AMOTION_EVENT_AXIS_VSCROLL) == common::Axis::VSCROLL);
+static_assert(static_cast<common::Axis>(AMOTION_EVENT_AXIS_HSCROLL) == common::Axis::HSCROLL);
+static_assert(static_cast<common::Axis>(AMOTION_EVENT_AXIS_Z) == common::Axis::Z);
+static_assert(static_cast<common::Axis>(AMOTION_EVENT_AXIS_RX) == common::Axis::RX);
+static_assert(static_cast<common::Axis>(AMOTION_EVENT_AXIS_RY) == common::Axis::RY);
+static_assert(static_cast<common::Axis>(AMOTION_EVENT_AXIS_RZ) == common::Axis::RZ);
+static_assert(static_cast<common::Axis>(AMOTION_EVENT_AXIS_HAT_X) == common::Axis::HAT_X);
+static_assert(static_cast<common::Axis>(AMOTION_EVENT_AXIS_HAT_Y) == common::Axis::HAT_Y);
+static_assert(static_cast<common::Axis>(AMOTION_EVENT_AXIS_LTRIGGER) == common::Axis::LTRIGGER);
+static_assert(static_cast<common::Axis>(AMOTION_EVENT_AXIS_RTRIGGER) == common::Axis::RTRIGGER);
+static_assert(static_cast<common::Axis>(AMOTION_EVENT_AXIS_THROTTLE) == common::Axis::THROTTLE);
+static_assert(static_cast<common::Axis>(AMOTION_EVENT_AXIS_RUDDER) == common::Axis::RUDDER);
+static_assert(static_cast<common::Axis>(AMOTION_EVENT_AXIS_WHEEL) == common::Axis::WHEEL);
+static_assert(static_cast<common::Axis>(AMOTION_EVENT_AXIS_GAS) == common::Axis::GAS);
+static_assert(static_cast<common::Axis>(AMOTION_EVENT_AXIS_BRAKE) == common::Axis::BRAKE);
+static_assert(static_cast<common::Axis>(AMOTION_EVENT_AXIS_DISTANCE) == common::Axis::DISTANCE);
+static_assert(static_cast<common::Axis>(AMOTION_EVENT_AXIS_TILT) == common::Axis::TILT);
+static_assert(static_cast<common::Axis>(AMOTION_EVENT_AXIS_SCROLL) == common::Axis::SCROLL);
+static_assert(static_cast<common::Axis>(AMOTION_EVENT_AXIS_RELATIVE_X) == common::Axis::RELATIVE_X);
+static_assert(static_cast<common::Axis>(AMOTION_EVENT_AXIS_RELATIVE_Y) == common::Axis::RELATIVE_Y);
+static_assert(static_cast<common::Axis>(AMOTION_EVENT_AXIS_GENERIC_1) == common::Axis::GENERIC_1);
+static_assert(static_cast<common::Axis>(AMOTION_EVENT_AXIS_GENERIC_2) == common::Axis::GENERIC_2);
+static_assert(static_cast<common::Axis>(AMOTION_EVENT_AXIS_GENERIC_3) == common::Axis::GENERIC_3);
+static_assert(static_cast<common::Axis>(AMOTION_EVENT_AXIS_GENERIC_4) == common::Axis::GENERIC_4);
+static_assert(static_cast<common::Axis>(AMOTION_EVENT_AXIS_GENERIC_5) == common::Axis::GENERIC_5);
+static_assert(static_cast<common::Axis>(AMOTION_EVENT_AXIS_GENERIC_6) == common::Axis::GENERIC_6);
+static_assert(static_cast<common::Axis>(AMOTION_EVENT_AXIS_GENERIC_7) == common::Axis::GENERIC_7);
+static_assert(static_cast<common::Axis>(AMOTION_EVENT_AXIS_GENERIC_8) == common::Axis::GENERIC_8);
+static_assert(static_cast<common::Axis>(AMOTION_EVENT_AXIS_GENERIC_9) == common::Axis::GENERIC_9);
+static_assert(static_cast<common::Axis>(AMOTION_EVENT_AXIS_GENERIC_10) == common::Axis::GENERIC_10);
+static_assert(static_cast<common::Axis>(AMOTION_EVENT_AXIS_GENERIC_11) == common::Axis::GENERIC_11);
+static_assert(static_cast<common::Axis>(AMOTION_EVENT_AXIS_GENERIC_12) == common::Axis::GENERIC_12);
+static_assert(static_cast<common::Axis>(AMOTION_EVENT_AXIS_GENERIC_13) == common::Axis::GENERIC_13);
+static_assert(static_cast<common::Axis>(AMOTION_EVENT_AXIS_GENERIC_14) == common::Axis::GENERIC_14);
+static_assert(static_cast<common::Axis>(AMOTION_EVENT_AXIS_GENERIC_15) == common::Axis::GENERIC_15);
+static_assert(static_cast<common::Axis>(AMOTION_EVENT_AXIS_GENERIC_16) == common::Axis::GENERIC_16);
+
+static common::VideoFrame getHalVideoFrame(const TouchVideoFrame& frame) {
+    common::VideoFrame out;
+    out.width = frame.getWidth();
+    out.height = frame.getHeight();
+    std::vector<char16_t> unsignedData(frame.getData().begin(), frame.getData().end());
+    out.data = unsignedData;
+    struct timeval timestamp = frame.getTimestamp();
+    out.timestamp = seconds_to_nanoseconds(timestamp.tv_sec) +
+            microseconds_to_nanoseconds(timestamp.tv_usec);
+    return out;
+}
+
+static std::vector<common::VideoFrame> convertVideoFrames(
+        const std::vector<TouchVideoFrame>& frames) {
+    std::vector<common::VideoFrame> out;
+    for (const TouchVideoFrame& frame : frames) {
+        out.push_back(getHalVideoFrame(frame));
+    }
+    return out;
+}
+
+static void getHalPropertiesAndCoords(const NotifyMotionArgs& args,
+                                      std::vector<common::PointerProperties>& outPointerProperties,
+                                      std::vector<common::PointerCoords>& outPointerCoords) {
+    outPointerProperties.reserve(args.pointerCount);
+    outPointerCoords.reserve(args.pointerCount);
+    for (size_t i = 0; i < args.pointerCount; i++) {
+        common::PointerProperties properties;
+        properties.id = args.pointerProperties[i].id;
+        properties.toolType = getToolType(args.pointerProperties[i].toolType);
+        outPointerProperties.push_back(properties);
+
+        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 +
+                                                   BitSet64::count(args.pointerCoords[i].bits));
+        outPointerCoords.push_back(coords);
+    }
+}
+
+common::MotionEvent notifyMotionArgsToHalMotionEvent(const NotifyMotionArgs& args) {
+    common::MotionEvent event;
+    event.deviceId = args.deviceId;
+    event.source = getSource(args.source);
+    event.displayId = args.displayId;
+    event.downTime = args.downTime;
+    event.eventTime = args.eventTime;
+    event.deviceTimestamp = 0;
+    event.action = getAction(args.action & AMOTION_EVENT_ACTION_MASK);
+    event.actionIndex = MotionEvent::getActionIndex(args.action);
+    event.actionButton = getActionButton(args.actionButton);
+    event.flags = getFlags(args.flags);
+    event.policyFlags = getPolicyFlags(args.policyFlags);
+    event.edgeFlags = getEdgeFlags(args.edgeFlags);
+    event.metaState = getMetastate(args.metaState);
+    event.buttonState = getButtonState(args.buttonState);
+    event.xPrecision = args.xPrecision;
+    event.yPrecision = args.yPrecision;
+
+    std::vector<common::PointerProperties> pointerProperties;
+    std::vector<common::PointerCoords> pointerCoords;
+    getHalPropertiesAndCoords(args, /*out*/ pointerProperties, /*out*/ pointerCoords);
+    event.pointerProperties = pointerProperties;
+    event.pointerCoords = pointerCoords;
+
+    event.frames = convertVideoFrames(args.videoFrames);
+
+    return event;
+}
+
+} // namespace android
diff --git a/services/inputflinger/InputClassifierConverter.h b/services/inputflinger/InputCommonConverter.h
similarity index 65%
rename from services/inputflinger/InputClassifierConverter.h
rename to services/inputflinger/InputCommonConverter.h
index 5154b0b..4d3b768 100644
--- a/services/inputflinger/InputClassifierConverter.h
+++ b/services/inputflinger/InputCommonConverter.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2019 The Android Open Source Project
+ * 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.
@@ -14,21 +14,18 @@
  * limitations under the License.
  */
 
-#ifndef _UI_INPUT_CLASSIFIER_CONVERTER_H
-#define _UI_INPUT_CLASSIFIER_CONVERTER_H
+#pragma once
 
+#include <aidl/android/hardware/input/common/Axis.h>
+#include <aidl/android/hardware/input/common/MotionEvent.h>
 #include "InputListener.h"
-#include <android/hardware/input/common/1.0/types.h>
-
 
 namespace android {
 
 /**
- * Convert from framework's NotifyMotionArgs to hidl's common::V1_0::MotionEvent
+ * Convert from framework's NotifyMotionArgs to hidl's common::MotionEvent
  */
-::android::hardware::input::common::V1_0::MotionEvent notifyMotionArgsToHalMotionEvent(
+::aidl::android::hardware::input::common::MotionEvent notifyMotionArgsToHalMotionEvent(
         const NotifyMotionArgs& args);
 
 } // namespace android
-
-#endif // _UI_INPUT_CLASSIFIER_CONVERTER_H
diff --git a/services/inputflinger/InputListener.cpp b/services/inputflinger/InputListener.cpp
index 71b0f5f..3a4b6c5 100644
--- a/services/inputflinger/InputListener.cpp
+++ b/services/inputflinger/InputListener.cpp
@@ -44,8 +44,8 @@
     return id == rhs.id && eventTime == rhs.eventTime;
 }
 
-void NotifyConfigurationChangedArgs::notify(const sp<InputListenerInterface>& listener) const {
-    listener->notifyConfigurationChanged(this);
+void NotifyConfigurationChangedArgs::notify(InputListenerInterface& listener) const {
+    listener.notifyConfigurationChanged(this);
 }
 
 // --- NotifyKeyArgs ---
@@ -89,8 +89,8 @@
             downTime == rhs.downTime;
 }
 
-void NotifyKeyArgs::notify(const sp<InputListenerInterface>& listener) const {
-    listener->notifyKey(this);
+void NotifyKeyArgs::notify(InputListenerInterface& listener) const {
+    listener.notifyKey(this);
 }
 
 // --- NotifyMotionArgs ---
@@ -188,8 +188,27 @@
     return true;
 }
 
-void NotifyMotionArgs::notify(const sp<InputListenerInterface>& listener) const {
-    listener->notifyMotion(this);
+std::string NotifyMotionArgs::dump() const {
+    std::string coords;
+    for (uint32_t i = 0; i < pointerCount; i++) {
+        if (!coords.empty()) {
+            coords += ", ";
+        }
+        coords += StringPrintf("{%" PRIu32 ": ", i);
+        coords +=
+                StringPrintf("id=%" PRIu32 " x=%.1f y=%.1f, pressure=%.1f", pointerProperties[i].id,
+                             pointerCoords[i].getX(), pointerCoords[i].getY(),
+                             pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_PRESSURE));
+        coords += "}";
+    }
+    return StringPrintf("NotifyMotionArgs(id=%" PRId32 ", eventTime=%" PRId64 ", deviceId=%" PRId32
+                        ", source=%s, action=%s, pointerCount=%" PRIu32 " pointers=%s)",
+                        id, eventTime, deviceId, inputEventSourceToString(source).c_str(),
+                        MotionEvent::actionToString(action).c_str(), pointerCount, coords.c_str());
+}
+
+void NotifyMotionArgs::notify(InputListenerInterface& listener) const {
+    listener.notifyMotion(this);
 }
 
 // --- NotifySwitchArgs ---
@@ -212,8 +231,8 @@
             switchValues == rhs.switchValues && switchMask == rhs.switchMask;
 }
 
-void NotifySwitchArgs::notify(const sp<InputListenerInterface>& listener) const {
-    listener->notifySwitch(this);
+void NotifySwitchArgs::notify(InputListenerInterface& listener) const {
+    listener.notifySwitch(this);
 }
 
 // --- NotifySensorArgs ---
@@ -247,8 +266,8 @@
             hwTimestamp == rhs.hwTimestamp && values == rhs.values;
 }
 
-void NotifySensorArgs::notify(const sp<InputListenerInterface>& listener) const {
-    listener->notifySensor(this);
+void NotifySensorArgs::notify(InputListenerInterface& listener) const {
+    listener.notifySensor(this);
 }
 
 // --- NotifyVibratorStateArgs ---
@@ -265,8 +284,8 @@
             isOn == rhs.isOn;
 }
 
-void NotifyVibratorStateArgs::notify(const sp<InputListenerInterface>& listener) const {
-    listener->notifyVibratorState(this);
+void NotifyVibratorStateArgs::notify(InputListenerInterface& listener) const {
+    listener.notifyVibratorState(this);
 }
 
 // --- NotifyDeviceResetArgs ---
@@ -281,8 +300,8 @@
     return id == rhs.id && eventTime == rhs.eventTime && deviceId == rhs.deviceId;
 }
 
-void NotifyDeviceResetArgs::notify(const sp<InputListenerInterface>& listener) const {
-    listener->notifyDeviceReset(this);
+void NotifyDeviceResetArgs::notify(InputListenerInterface& listener) const {
+    listener.notifyDeviceReset(this);
 }
 
 // --- NotifyPointerCaptureChangedArgs ---
@@ -299,8 +318,8 @@
     return id == rhs.id && eventTime == rhs.eventTime && request == rhs.request;
 }
 
-void NotifyPointerCaptureChangedArgs::notify(const sp<InputListenerInterface>& listener) const {
-    listener->notifyPointerCaptureChanged(this);
+void NotifyPointerCaptureChangedArgs::notify(InputListenerInterface& listener) const {
+    listener.notifyPointerCaptureChanged(this);
 }
 
 // --- QueuedInputListener ---
@@ -312,64 +331,53 @@
     }
 }
 
-QueuedInputListener::QueuedInputListener(const sp<InputListenerInterface>& innerListener) :
-        mInnerListener(innerListener) {
-}
-
-QueuedInputListener::~QueuedInputListener() {
-    size_t count = mArgsQueue.size();
-    for (size_t i = 0; i < count; i++) {
-        delete mArgsQueue[i];
-    }
-}
+QueuedInputListener::QueuedInputListener(InputListenerInterface& innerListener)
+      : mInnerListener(innerListener) {}
 
 void QueuedInputListener::notifyConfigurationChanged(
         const NotifyConfigurationChangedArgs* args) {
     traceEvent(__func__, args->id);
-    mArgsQueue.push_back(new NotifyConfigurationChangedArgs(*args));
+    mArgsQueue.emplace_back(std::make_unique<NotifyConfigurationChangedArgs>(*args));
 }
 
 void QueuedInputListener::notifyKey(const NotifyKeyArgs* args) {
     traceEvent(__func__, args->id);
-    mArgsQueue.push_back(new NotifyKeyArgs(*args));
+    mArgsQueue.emplace_back(std::make_unique<NotifyKeyArgs>(*args));
 }
 
 void QueuedInputListener::notifyMotion(const NotifyMotionArgs* args) {
     traceEvent(__func__, args->id);
-    mArgsQueue.push_back(new NotifyMotionArgs(*args));
+    mArgsQueue.emplace_back(std::make_unique<NotifyMotionArgs>(*args));
 }
 
 void QueuedInputListener::notifySwitch(const NotifySwitchArgs* args) {
     traceEvent(__func__, args->id);
-    mArgsQueue.push_back(new NotifySwitchArgs(*args));
+    mArgsQueue.emplace_back(std::make_unique<NotifySwitchArgs>(*args));
 }
 
 void QueuedInputListener::notifySensor(const NotifySensorArgs* args) {
     traceEvent(__func__, args->id);
-    mArgsQueue.push_back(new NotifySensorArgs(*args));
+    mArgsQueue.emplace_back(std::make_unique<NotifySensorArgs>(*args));
 }
 
 void QueuedInputListener::notifyVibratorState(const NotifyVibratorStateArgs* args) {
     traceEvent(__func__, args->id);
-    mArgsQueue.push_back(new NotifyVibratorStateArgs(*args));
+    mArgsQueue.emplace_back(std::make_unique<NotifyVibratorStateArgs>(*args));
 }
 
 void QueuedInputListener::notifyDeviceReset(const NotifyDeviceResetArgs* args) {
     traceEvent(__func__, args->id);
-    mArgsQueue.push_back(new NotifyDeviceResetArgs(*args));
+    mArgsQueue.emplace_back(std::make_unique<NotifyDeviceResetArgs>(*args));
 }
 
 void QueuedInputListener::notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs* args) {
     traceEvent(__func__, args->id);
-    mArgsQueue.push_back(new NotifyPointerCaptureChangedArgs(*args));
+    mArgsQueue.emplace_back(std::make_unique<NotifyPointerCaptureChangedArgs>(*args));
 }
 
 void QueuedInputListener::flush() {
-    size_t count = mArgsQueue.size();
-    for (size_t i = 0; i < count; i++) {
-        NotifyArgs* args = mArgsQueue[i];
+    for (const std::unique_ptr<NotifyArgs>& args : mArgsQueue) {
         args->notify(mInnerListener);
-        delete args;
     }
     mArgsQueue.clear();
 }
diff --git a/services/inputflinger/InputManager.cpp b/services/inputflinger/InputManager.cpp
index 7b3658d..7b03631 100644
--- a/services/inputflinger/InputManager.cpp
+++ b/services/inputflinger/InputManager.cpp
@@ -21,6 +21,7 @@
 #include "InputManager.h"
 #include "InputDispatcherFactory.h"
 #include "InputReaderFactory.h"
+#include "UnwantedInteractionBlocker.h"
 
 #include <binder/IPCThreadState.h>
 
@@ -32,8 +33,6 @@
 namespace android {
 
 using gui::FocusRequest;
-using gui::WindowInfo;
-using gui::WindowInfoHandle;
 
 static int32_t exceptionCodeFromStatusT(status_t status) {
     switch (status) {
@@ -54,12 +53,17 @@
     }
 }
 
+/**
+ * The event flow is via the "InputListener" interface, as follows:
+ * InputReader -> UnwantedInteractionBlocker -> InputClassifier -> InputDispatcher
+ */
 InputManager::InputManager(
         const sp<InputReaderPolicyInterface>& readerPolicy,
         const sp<InputDispatcherPolicyInterface>& dispatcherPolicy) {
     mDispatcher = createInputDispatcher(dispatcherPolicy);
-    mClassifier = new InputClassifier(mDispatcher);
-    mReader = createInputReader(readerPolicy, mClassifier);
+    mClassifier = std::make_unique<InputClassifier>(*mDispatcher);
+    mUnwantedInteractionBlocker = std::make_unique<UnwantedInteractionBlocker>(*mClassifier);
+    mReader = createInputReader(readerPolicy, *mUnwantedInteractionBlocker);
 }
 
 InputManager::~InputManager() {
@@ -102,16 +106,20 @@
     return status;
 }
 
-sp<InputReaderInterface> InputManager::getReader() {
-    return mReader;
+InputReaderInterface& InputManager::getReader() {
+    return *mReader;
 }
 
-sp<InputClassifierInterface> InputManager::getClassifier() {
-    return mClassifier;
+UnwantedInteractionBlockerInterface& InputManager::getUnwantedInteractionBlocker() {
+    return *mUnwantedInteractionBlocker;
 }
 
-sp<InputDispatcherInterface> InputManager::getDispatcher() {
-    return mDispatcher;
+InputClassifierInterface& InputManager::getClassifier() {
+    return *mClassifier;
+}
+
+InputDispatcherInterface& InputManager::getDispatcher() {
+    return *mDispatcher;
 }
 
 // Used by tests only.
diff --git a/services/inputflinger/InputManager.h b/services/inputflinger/InputManager.h
index 4c07c22..35d2b0f 100644
--- a/services/inputflinger/InputManager.h
+++ b/services/inputflinger/InputManager.h
@@ -23,6 +23,7 @@
 
 #include "InputClassifier.h"
 #include "InputReaderBase.h"
+#include "include/UnwantedInteractionBlockerInterface.h"
 
 #include <InputDispatcherInterface.h>
 #include <InputDispatcherPolicyInterface.h>
@@ -30,11 +31,9 @@
 #include <input/InputTransport.h>
 
 #include <android/os/BnInputFlinger.h>
-#include <android/os/IInputFlinger.h>
 #include <utils/Errors.h>
 #include <utils/RefBase.h>
 #include <utils/Timers.h>
-#include <utils/Vector.h>
 
 using android::os::BnInputFlinger;
 
@@ -48,11 +47,16 @@
  * The input manager has three components.
  *
  * 1. The InputReader class starts a thread that reads and preprocesses raw input events, applies
- *    policy, and posts messages to a queue managed by the InputClassifier.
- * 2. The InputClassifier class starts a thread to communicate with the device-specific
- *    classifiers. It then waits on the queue of events from InputReader, applies a classification
- *    to them, and queues them for the InputDispatcher.
- * 3. The InputDispatcher class starts a thread that waits for new events on the
+ *    policy, and posts messages to a queue managed by the UnwantedInteractionBlocker.
+ * 2. The UnwantedInteractionBlocker is responsible for removing unwanted interactions. For example,
+ *    this could be a palm on the screen. This stage would alter the event stream to remove either
+ *    partially (some of the pointers) or fully (all touches) the unwanted interaction. The events
+ *    are processed on the InputReader thread, without any additional queue. The events are then
+ *    posted to the queue managed by the InputClassifier.
+ * 3. The InputClassifier class starts a thread to communicate with the device-specific
+ *    classifiers. It then waits on the queue of events from UnwantedInteractionBlocker, applies
+ *    a classification to them, and queues them for the InputDispatcher.
+ * 4. The InputDispatcher class starts a thread that waits for new events on the
  *    previous queue and asynchronously dispatches them to applications.
  *
  * By design, none of these classes share any internal state.  Moreover, all communication is
@@ -76,13 +80,16 @@
     virtual status_t stop() = 0;
 
     /* Gets the input reader. */
-    virtual sp<InputReaderInterface> getReader() = 0;
+    virtual InputReaderInterface& getReader() = 0;
+
+    /* Gets the unwanted interaction blocker. */
+    virtual UnwantedInteractionBlockerInterface& getUnwantedInteractionBlocker() = 0;
 
     /* Gets the input classifier */
-    virtual sp<InputClassifierInterface> getClassifier() = 0;
+    virtual InputClassifierInterface& getClassifier() = 0;
 
     /* Gets the input dispatcher. */
-    virtual sp<InputDispatcherInterface> getDispatcher() = 0;
+    virtual InputDispatcherInterface& getDispatcher() = 0;
 };
 
 class InputManager : public InputManagerInterface, public BnInputFlinger {
@@ -97,9 +104,10 @@
     status_t start() override;
     status_t stop() override;
 
-    sp<InputReaderInterface> getReader() override;
-    sp<InputClassifierInterface> getClassifier() override;
-    sp<InputDispatcherInterface> getDispatcher() override;
+    InputReaderInterface& getReader() override;
+    UnwantedInteractionBlockerInterface& getUnwantedInteractionBlocker() override;
+    InputClassifierInterface& getClassifier() override;
+    InputDispatcherInterface& getDispatcher() override;
 
     status_t dump(int fd, const Vector<String16>& args) override;
     binder::Status createInputChannel(const std::string& name, InputChannel* outChannel) override;
@@ -107,11 +115,13 @@
     binder::Status setFocusedWindow(const gui::FocusRequest&) override;
 
 private:
-    sp<InputReaderInterface> mReader;
+    std::unique_ptr<InputReaderInterface> mReader;
 
-    sp<InputClassifierInterface> mClassifier;
+    std::unique_ptr<UnwantedInteractionBlockerInterface> mUnwantedInteractionBlocker;
 
-    sp<InputDispatcherInterface> mDispatcher;
+    std::unique_ptr<InputClassifierInterface> mClassifier;
+
+    std::unique_ptr<InputDispatcherInterface> mDispatcher;
 };
 
 } // namespace android
diff --git a/services/inputflinger/InputReaderBase.cpp b/services/inputflinger/InputReaderBase.cpp
index 05ef489..a864cf8 100644
--- a/services/inputflinger/InputReaderBase.cpp
+++ b/services/inputflinger/InputReaderBase.cpp
@@ -19,12 +19,12 @@
 //#define LOG_NDEBUG 0
 
 #include "InputReaderBase.h"
-#include <ftl/NamedEnum.h>
 #include "input/DisplayViewport.h"
 #include "input/Input.h"
 
-#include <android/log.h>
 #include <android-base/stringprintf.h>
+#include <android/log.h>
+#include <ftl/enum.h>
 
 #define INDENT "  "
 #define INDENT2 "    "
@@ -117,7 +117,7 @@
     }
     if (count > 1) {
         ALOGW("Found %zu viewports with type %s, but expected 1 at most", count,
-              NamedEnum::string(type).c_str());
+              ftl::enum_string(type).c_str());
     }
     return result;
 }
diff --git a/services/inputflinger/TEST_MAPPING b/services/inputflinger/TEST_MAPPING
index 3d85bef..9b72ff4 100644
--- a/services/inputflinger/TEST_MAPPING
+++ b/services/inputflinger/TEST_MAPPING
@@ -15,6 +15,9 @@
       "name": "inputflinger_tests"
     },
     {
+      "name": "libpalmrejection_test"
+    },
+    {
       "name": "InputTests"
     },
     {
diff --git a/services/inputflinger/UnwantedInteractionBlocker.cpp b/services/inputflinger/UnwantedInteractionBlocker.cpp
new file mode 100644
index 0000000..64dbb8c
--- /dev/null
+++ b/services/inputflinger/UnwantedInteractionBlocker.cpp
@@ -0,0 +1,687 @@
+/*
+ * 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 "UnwantedInteractionBlocker"
+#include "UnwantedInteractionBlocker.h"
+
+#include <android-base/stringprintf.h>
+#include <inttypes.h>
+#include <linux/input-event-codes.h>
+#include <linux/input.h>
+#include <server_configurable_flags/get_flags.h>
+
+#include "ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter.h"
+#include "ui/events/ozone/evdev/touch_filter/palm_model/onedevice_train_palm_detection_filter_model.h"
+
+using android::base::StringPrintf;
+
+namespace android {
+
+// Category (=namespace) name for the input settings that are applied at boot time
+static const char* INPUT_NATIVE_BOOT = "input_native_boot";
+/**
+ * Feature flag name. This flag determines whether palm rejection is enabled. To enable, specify
+ * 'true' (not case sensitive) or '1'. To disable, specify any other value.
+ */
+static const char* PALM_REJECTION_ENABLED = "palm_rejection_enabled";
+
+static std::string toLower(std::string s) {
+    std::transform(s.begin(), s.end(), s.begin(), [](unsigned char c) { return std::tolower(c); });
+    return s;
+}
+
+static bool isFromTouchscreen(int32_t source) {
+    return isFromSource(source, AINPUT_SOURCE_TOUCHSCREEN);
+}
+
+static ::base::TimeTicks toChromeTimestamp(nsecs_t eventTime) {
+    return ::base::TimeTicks::UnixEpoch() +
+            ::base::Milliseconds(static_cast<float>(ns2ms(eventTime)));
+}
+
+/**
+ * Return true if palm rejection is enabled via the server configurable flags. Return false
+ * otherwise.
+ */
+static bool isPalmRejectionEnabled() {
+    std::string value = toLower(
+            server_configurable_flags::GetServerConfigurableFlag(INPUT_NATIVE_BOOT,
+                                                                 PALM_REJECTION_ENABLED, "false"));
+    if (value == "true" || value == "1") {
+        return true;
+    }
+    return false;
+}
+
+static int getLinuxToolType(int32_t toolType) {
+    switch (toolType) {
+        case AMOTION_EVENT_TOOL_TYPE_FINGER:
+            return MT_TOOL_FINGER;
+        case AMOTION_EVENT_TOOL_TYPE_STYLUS:
+            return MT_TOOL_PEN;
+        case AMOTION_EVENT_TOOL_TYPE_PALM:
+            return MT_TOOL_PALM;
+    }
+    ALOGW("Got tool type %" PRId32 ", converting to MT_TOOL_FINGER", toolType);
+    return MT_TOOL_FINGER;
+}
+
+static std::string addPrefix(std::string str, const std::string& prefix) {
+    std::stringstream ss;
+    bool newLineStarted = true;
+    for (const auto& ch : str) {
+        if (newLineStarted) {
+            ss << prefix;
+            newLineStarted = false;
+        }
+        if (ch == '\n') {
+            newLineStarted = true;
+        }
+        ss << ch;
+    }
+    return ss.str();
+}
+
+template <typename T>
+static std::string dumpSet(const std::set<T>& v) {
+    static_assert(std::is_integral<T>::value, "Only integral types can be printed.");
+    std::string out;
+    for (const T& entry : v) {
+        out += out.empty() ? "{" : ", ";
+        out += android::base::StringPrintf("%i", entry);
+    }
+    return out.empty() ? "{}" : (out + "}");
+}
+
+template <typename K, typename V>
+static std::string dumpMap(const std::map<K, V>& map) {
+    static_assert(std::is_integral<K>::value, "Keys should have integral type to be printed.");
+    static_assert(std::is_integral<V>::value, "Values should have integral type to be printed.");
+    std::string out;
+    for (const auto& [k, v] : map) {
+        if (!out.empty()) {
+            out += "\n";
+        }
+        out += android::base::StringPrintf("%i : %i", static_cast<int>(k), static_cast<int>(v));
+    }
+    return out;
+}
+
+static std::string dumpDeviceInfo(const AndroidPalmFilterDeviceInfo& info) {
+    std::string out;
+    out += StringPrintf("max_x = %.2f\n", info.max_x);
+    out += StringPrintf("max_y = %.2f\n", info.max_y);
+    out += StringPrintf("x_res = %.2f\n", info.x_res);
+    out += StringPrintf("y_res = %.2f\n", info.y_res);
+    out += StringPrintf("major_radius_res = %.2f\n", info.major_radius_res);
+    out += StringPrintf("minor_radius_res = %.2f\n", info.minor_radius_res);
+    out += StringPrintf("minor_radius_supported = %s\n",
+                        info.minor_radius_supported ? "true" : "false");
+    out += StringPrintf("touch_major_res = %" PRId32 "\n", info.touch_major_res);
+    out += StringPrintf("touch_minor_res = %" PRId32 "\n", info.touch_minor_res);
+    return out;
+}
+
+static int32_t getActionUpForPointerId(const NotifyMotionArgs& args, int32_t pointerId) {
+    for (size_t i = 0; i < args.pointerCount; i++) {
+        if (pointerId == args.pointerProperties[i].id) {
+            return AMOTION_EVENT_ACTION_POINTER_UP |
+                    (i << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
+        }
+    }
+    LOG_ALWAYS_FATAL("Can't find pointerId %" PRId32 " in %s", pointerId, args.dump().c_str());
+}
+
+/**
+ * Find the action for individual pointer at the given pointer index.
+ * This is always equal to MotionEvent::getActionMasked, except for
+ * POINTER_UP or POINTER_DOWN events. For example, in a POINTER_UP event, the action for
+ * the active pointer is ACTION_POINTER_UP, while the action for the other pointers is ACTION_MOVE.
+ */
+static int32_t resolveActionForPointer(uint8_t pointerIndex, int32_t action) {
+    const int32_t actionMasked = MotionEvent::getActionMasked(action);
+    if (actionMasked != AMOTION_EVENT_ACTION_POINTER_DOWN &&
+        actionMasked != AMOTION_EVENT_ACTION_POINTER_UP) {
+        return actionMasked;
+    }
+    // This is a POINTER_DOWN or POINTER_UP event
+    const uint8_t actionIndex = MotionEvent::getActionIndex(action);
+    if (pointerIndex == actionIndex) {
+        return actionMasked;
+    }
+    // When POINTER_DOWN or POINTER_UP happens, it's actually a MOVE for all of the other
+    // pointers
+    return AMOTION_EVENT_ACTION_MOVE;
+}
+
+static const char* toString(bool value) {
+    return value ? "true" : "false";
+}
+
+std::string toString(const ::ui::InProgressTouchEvdev& touch) {
+    return StringPrintf("x=%.1f, y=%.1f, tracking_id=%i, slot=%zu,"
+                        " pressure=%.1f, major=%i, minor=%i, "
+                        "tool_type=%i, altered=%s, was_touching=%s, touching=%s",
+                        touch.x, touch.y, touch.tracking_id, touch.slot, touch.pressure,
+                        touch.major, touch.minor, touch.tool_type, toString(touch.altered),
+                        toString(touch.was_touching), toString(touch.touching));
+}
+
+/**
+ * Remove the data for the provided pointers from the args. The pointers are identified by their
+ * pointerId, not by the index inside the array.
+ * Return the new NotifyMotionArgs struct that has the remaining pointers.
+ * The only fields that may be different in the returned args from the provided args are:
+ *     - action
+ *     - pointerCount
+ *     - pointerProperties
+ *     - pointerCoords
+ * Action might change because it contains a pointer index. If another pointer is removed, the
+ * active pointer index would be shifted.
+ * Do not call this function for events with POINTER_UP or POINTER_DOWN events when removed pointer
+ * id is the acting pointer id.
+ *
+ * @param args the args from which the pointers should be removed
+ * @param pointerIds the pointer ids of the pointers that should be removed
+ */
+NotifyMotionArgs removePointerIds(const NotifyMotionArgs& args,
+                                  const std::set<int32_t>& pointerIds) {
+    const uint8_t actionIndex = MotionEvent::getActionIndex(args.action);
+    const int32_t actionMasked = MotionEvent::getActionMasked(args.action);
+    const bool isPointerUpOrDownAction = actionMasked == AMOTION_EVENT_ACTION_POINTER_DOWN ||
+            actionMasked == AMOTION_EVENT_ACTION_POINTER_UP;
+
+    NotifyMotionArgs newArgs{args};
+    newArgs.pointerCount = 0;
+    int32_t newActionIndex = 0;
+    for (uint32_t i = 0; i < args.pointerCount; i++) {
+        const int32_t pointerId = args.pointerProperties[i].id;
+        if (pointerIds.find(pointerId) != pointerIds.end()) {
+            // skip this pointer
+            if (isPointerUpOrDownAction && i == actionIndex) {
+                // The active pointer is being removed, so the action is no longer valid.
+                // Set the action to 'UNKNOWN' here. The caller is responsible for updating this
+                // action later to a proper value.
+                newArgs.action = ACTION_UNKNOWN;
+            }
+            continue;
+        }
+        newArgs.pointerProperties[newArgs.pointerCount].copyFrom(args.pointerProperties[i]);
+        newArgs.pointerCoords[newArgs.pointerCount].copyFrom(args.pointerCoords[i]);
+        if (i == actionIndex) {
+            newActionIndex = newArgs.pointerCount;
+        }
+        newArgs.pointerCount++;
+    }
+    // Update POINTER_DOWN or POINTER_UP actions
+    if (isPointerUpOrDownAction && newArgs.action != ACTION_UNKNOWN) {
+        newArgs.action =
+                actionMasked | (newActionIndex << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
+        // Convert POINTER_DOWN and POINTER_UP to DOWN and UP if there's only 1 pointer remaining
+        if (newArgs.pointerCount == 1) {
+            if (actionMasked == AMOTION_EVENT_ACTION_POINTER_DOWN) {
+                newArgs.action = AMOTION_EVENT_ACTION_DOWN;
+            } else if (actionMasked == AMOTION_EVENT_ACTION_POINTER_UP) {
+                newArgs.action = AMOTION_EVENT_ACTION_UP;
+            }
+        }
+    }
+    return newArgs;
+}
+
+std::optional<AndroidPalmFilterDeviceInfo> createPalmFilterDeviceInfo(
+        const InputDeviceInfo& deviceInfo) {
+    if (!isFromTouchscreen(deviceInfo.getSources())) {
+        return std::nullopt;
+    }
+    AndroidPalmFilterDeviceInfo out;
+    const InputDeviceInfo::MotionRange* axisX =
+            deviceInfo.getMotionRange(AMOTION_EVENT_AXIS_X, AINPUT_SOURCE_TOUCHSCREEN);
+    if (axisX != nullptr) {
+        out.max_x = axisX->max;
+        out.x_res = axisX->resolution;
+    } else {
+        ALOGW("Palm rejection is disabled for %s because AXIS_X is not supported",
+              deviceInfo.getDisplayName().c_str());
+        return std::nullopt;
+    }
+    const InputDeviceInfo::MotionRange* axisY =
+            deviceInfo.getMotionRange(AMOTION_EVENT_AXIS_Y, AINPUT_SOURCE_TOUCHSCREEN);
+    if (axisY != nullptr) {
+        out.max_y = axisY->max;
+        out.y_res = axisY->resolution;
+    } else {
+        ALOGW("Palm rejection is disabled for %s because AXIS_Y is not supported",
+              deviceInfo.getDisplayName().c_str());
+        return std::nullopt;
+    }
+    const InputDeviceInfo::MotionRange* axisMajor =
+            deviceInfo.getMotionRange(AMOTION_EVENT_AXIS_TOUCH_MAJOR, AINPUT_SOURCE_TOUCHSCREEN);
+    if (axisMajor != nullptr) {
+        out.major_radius_res = axisMajor->resolution;
+        out.touch_major_res = axisMajor->resolution;
+    } else {
+        return std::nullopt;
+    }
+    const InputDeviceInfo::MotionRange* axisMinor =
+            deviceInfo.getMotionRange(AMOTION_EVENT_AXIS_TOUCH_MINOR, AINPUT_SOURCE_TOUCHSCREEN);
+    if (axisMinor != nullptr) {
+        out.minor_radius_res = axisMinor->resolution;
+        out.touch_minor_res = axisMinor->resolution;
+        out.minor_radius_supported = true;
+    } else {
+        out.minor_radius_supported = false;
+    }
+
+    return out;
+}
+
+/**
+ * Synthesize CANCEL events for any new pointers that should be canceled, while removing pointers
+ * that have already been canceled.
+ * The flow of the function is as follows:
+ * 1. Remove all already canceled pointers
+ * 2. Cancel all newly suppressed pointers
+ * 3. Decide what to do with the current event : keep it, or drop it
+ * The pointers can never be "unsuppressed": once a pointer is canceled, it will never become valid.
+ */
+std::vector<NotifyMotionArgs> cancelSuppressedPointers(
+        const NotifyMotionArgs& args, const std::set<int32_t>& oldSuppressedPointerIds,
+        const std::set<int32_t>& newSuppressedPointerIds) {
+    LOG_ALWAYS_FATAL_IF(args.pointerCount == 0, "0 pointers in %s", args.dump().c_str());
+
+    // First, let's remove the old suppressed pointers. They've already been canceled previously.
+    NotifyMotionArgs oldArgs = removePointerIds(args, oldSuppressedPointerIds);
+
+    // Cancel any newly suppressed pointers.
+    std::vector<NotifyMotionArgs> out;
+    const int32_t activePointerId =
+            args.pointerProperties[MotionEvent::getActionIndex(args.action)].id;
+    const int32_t actionMasked = MotionEvent::getActionMasked(args.action);
+    // We will iteratively remove pointers from 'removedArgs'.
+    NotifyMotionArgs removedArgs{oldArgs};
+    for (uint32_t i = 0; i < oldArgs.pointerCount; i++) {
+        const int32_t pointerId = oldArgs.pointerProperties[i].id;
+        if (newSuppressedPointerIds.find(pointerId) == newSuppressedPointerIds.end()) {
+            // This is a pointer that should not be canceled. Move on.
+            continue;
+        }
+        if (pointerId == activePointerId && actionMasked == AMOTION_EVENT_ACTION_POINTER_DOWN) {
+            // Remove this pointer, but don't cancel it. We'll just not send the POINTER_DOWN event
+            removedArgs = removePointerIds(removedArgs, {pointerId});
+            continue;
+        }
+
+        if (removedArgs.pointerCount == 1) {
+            // We are about to remove the last pointer, which means there will be no more gesture
+            // remaining. This is identical to canceling all pointers, so just send a single CANCEL
+            // event, without any of the preceding POINTER_UP with FLAG_CANCELED events.
+            oldArgs.flags |= AMOTION_EVENT_FLAG_CANCELED;
+            oldArgs.action = AMOTION_EVENT_ACTION_CANCEL;
+            return {oldArgs};
+        }
+        // Cancel the current pointer
+        out.push_back(removedArgs);
+        out.back().flags |= AMOTION_EVENT_FLAG_CANCELED;
+        out.back().action = getActionUpForPointerId(out.back(), pointerId);
+
+        // Remove the newly canceled pointer from the args
+        removedArgs = removePointerIds(removedArgs, {pointerId});
+    }
+
+    // Now 'removedArgs' contains only pointers that are valid.
+    if (removedArgs.pointerCount <= 0 || removedArgs.action == ACTION_UNKNOWN) {
+        return out;
+    }
+    out.push_back(removedArgs);
+    return out;
+}
+
+UnwantedInteractionBlocker::UnwantedInteractionBlocker(InputListenerInterface& listener)
+      : UnwantedInteractionBlocker(listener, isPalmRejectionEnabled()){};
+
+UnwantedInteractionBlocker::UnwantedInteractionBlocker(InputListenerInterface& listener,
+                                                       bool enablePalmRejection)
+      : mListener(listener), mEnablePalmRejection(enablePalmRejection) {}
+
+void UnwantedInteractionBlocker::notifyConfigurationChanged(
+        const NotifyConfigurationChangedArgs* args) {
+    mListener.notifyConfigurationChanged(args);
+}
+
+void UnwantedInteractionBlocker::notifyKey(const NotifyKeyArgs* args) {
+    mListener.notifyKey(args);
+}
+
+void UnwantedInteractionBlocker::notifyMotion(const NotifyMotionArgs* args) {
+    auto it = mPalmRejectors.find(args->deviceId);
+    const bool sendToPalmRejector = it != mPalmRejectors.end() && isFromTouchscreen(args->source);
+    if (!sendToPalmRejector) {
+        mListener.notifyMotion(args);
+        return;
+    }
+
+    const std::vector<NotifyMotionArgs> newMotions = it->second.processMotion(*args);
+    for (const NotifyMotionArgs& newArgs : newMotions) {
+        mListener.notifyMotion(&newArgs);
+    }
+}
+
+void UnwantedInteractionBlocker::notifySwitch(const NotifySwitchArgs* args) {
+    mListener.notifySwitch(args);
+}
+
+void UnwantedInteractionBlocker::notifySensor(const NotifySensorArgs* args) {
+    mListener.notifySensor(args);
+}
+
+void UnwantedInteractionBlocker::notifyVibratorState(const NotifyVibratorStateArgs* args) {
+    mListener.notifyVibratorState(args);
+}
+void UnwantedInteractionBlocker::notifyDeviceReset(const NotifyDeviceResetArgs* args) {
+    auto it = mPalmRejectors.find(args->deviceId);
+    if (it != mPalmRejectors.end()) {
+        AndroidPalmFilterDeviceInfo info = it->second.getPalmFilterDeviceInfo();
+        // Re-create the object instead of resetting it
+        mPalmRejectors.erase(it);
+        mPalmRejectors.emplace(args->deviceId, info);
+    }
+    mListener.notifyDeviceReset(args);
+}
+
+void UnwantedInteractionBlocker::notifyPointerCaptureChanged(
+        const NotifyPointerCaptureChangedArgs* args) {
+    mListener.notifyPointerCaptureChanged(args);
+}
+
+void UnwantedInteractionBlocker::notifyInputDevicesChanged(
+        const std::vector<InputDeviceInfo>& inputDevices) {
+    if (!mEnablePalmRejection) {
+        // Palm rejection is disabled. Don't create any palm rejector objects.
+        return;
+    }
+
+    // Let's see which of the existing devices didn't change, so that we can keep them
+    // and prevent event stream disruption
+    std::set<int32_t /*deviceId*/> devicesToKeep;
+    for (const InputDeviceInfo& device : inputDevices) {
+        std::optional<AndroidPalmFilterDeviceInfo> info = createPalmFilterDeviceInfo(device);
+        if (!info) {
+            continue;
+        }
+
+        auto [it, emplaced] = mPalmRejectors.try_emplace(device.getId(), *info);
+        if (!emplaced && *info != it->second.getPalmFilterDeviceInfo()) {
+            // Re-create the PalmRejector because the device info has changed.
+            mPalmRejectors.erase(it);
+            mPalmRejectors.emplace(device.getId(), *info);
+        }
+        devicesToKeep.insert(device.getId());
+    }
+    // Delete all devices that we don't need to keep
+    std::erase_if(mPalmRejectors, [&devicesToKeep](const auto& item) {
+        auto const& [deviceId, _] = item;
+        return devicesToKeep.find(deviceId) == devicesToKeep.end();
+    });
+}
+
+void UnwantedInteractionBlocker::dump(std::string& dump) {
+    dump += "UnwantedInteractionBlocker:\n";
+    dump += StringPrintf("  mEnablePalmRejection: %s\n", toString(mEnablePalmRejection));
+    dump += StringPrintf("  isPalmRejectionEnabled (flag value): %s\n",
+                         toString(isPalmRejectionEnabled()));
+    dump += mPalmRejectors.empty() ? "  mPalmRejectors: None\n" : "  mPalmRejectors:\n";
+    for (const auto& [deviceId, palmRejector] : mPalmRejectors) {
+        dump += StringPrintf("    deviceId = %" PRId32 ":\n", deviceId);
+        dump += addPrefix(palmRejector.dump(), "      ");
+    }
+}
+
+void UnwantedInteractionBlocker::monitor() {}
+
+UnwantedInteractionBlocker::~UnwantedInteractionBlocker() {}
+
+void SlotState::update(const NotifyMotionArgs& args) {
+    for (size_t i = 0; i < args.pointerCount; i++) {
+        const int32_t pointerId = args.pointerProperties[i].id;
+        const int32_t resolvedAction = resolveActionForPointer(i, args.action);
+        processPointerId(pointerId, resolvedAction);
+    }
+}
+
+size_t SlotState::findUnusedSlot() const {
+    size_t unusedSlot = 0;
+    // Since the collection is ordered, we can rely on the in-order traversal
+    for (const auto& [slot, trackingId] : mPointerIdsBySlot) {
+        if (unusedSlot != slot) {
+            break;
+        }
+        unusedSlot++;
+    }
+    return unusedSlot;
+}
+
+void SlotState::processPointerId(int pointerId, int32_t actionMasked) {
+    switch (MotionEvent::getActionMasked(actionMasked)) {
+        case AMOTION_EVENT_ACTION_DOWN:
+        case AMOTION_EVENT_ACTION_POINTER_DOWN:
+        case AMOTION_EVENT_ACTION_HOVER_ENTER: {
+            // New pointer going down
+            size_t newSlot = findUnusedSlot();
+            mPointerIdsBySlot[newSlot] = pointerId;
+            mSlotsByPointerId[pointerId] = newSlot;
+            return;
+        }
+        case AMOTION_EVENT_ACTION_MOVE:
+        case AMOTION_EVENT_ACTION_HOVER_MOVE: {
+            return;
+        }
+        case AMOTION_EVENT_ACTION_CANCEL:
+        case AMOTION_EVENT_ACTION_POINTER_UP:
+        case AMOTION_EVENT_ACTION_UP:
+        case AMOTION_EVENT_ACTION_HOVER_EXIT: {
+            auto it = mSlotsByPointerId.find(pointerId);
+            LOG_ALWAYS_FATAL_IF(it == mSlotsByPointerId.end());
+            size_t slot = it->second;
+            // Erase this pointer from both collections
+            mPointerIdsBySlot.erase(slot);
+            mSlotsByPointerId.erase(pointerId);
+            return;
+        }
+    }
+    LOG_ALWAYS_FATAL("Unhandled action : %s", MotionEvent::actionToString(actionMasked).c_str());
+    return;
+}
+
+std::optional<size_t> SlotState::getSlotForPointerId(int32_t pointerId) const {
+    auto it = mSlotsByPointerId.find(pointerId);
+    if (it == mSlotsByPointerId.end()) {
+        return std::nullopt;
+    }
+    return it->second;
+}
+
+std::string SlotState::dump() const {
+    std::string out = "mSlotsByPointerId:\n";
+    out += addPrefix(dumpMap(mSlotsByPointerId), "  ") + "\n";
+    out += "mPointerIdsBySlot:\n";
+    out += addPrefix(dumpMap(mPointerIdsBySlot), "  ") + "\n";
+    return out;
+}
+
+PalmRejector::PalmRejector(const AndroidPalmFilterDeviceInfo& info,
+                           std::unique_ptr<::ui::PalmDetectionFilter> filter)
+      : mSharedPalmState(std::make_unique<::ui::SharedPalmDetectionFilterState>()),
+        mDeviceInfo(info),
+        mPalmDetectionFilter(std::move(filter)) {
+    if (mPalmDetectionFilter != nullptr) {
+        // This path is used for testing. Non-testing invocations should let this constructor
+        // create a real PalmDetectionFilter
+        return;
+    }
+    std::unique_ptr<::ui::NeuralStylusPalmDetectionFilterModel> model =
+            std::make_unique<::ui::OneDeviceTrainNeuralStylusPalmDetectionFilterModel>(
+                    std::vector<float>());
+    mPalmDetectionFilter =
+            std::make_unique<::ui::NeuralStylusPalmDetectionFilter>(mDeviceInfo, std::move(model),
+                                                                    mSharedPalmState.get());
+}
+
+std::vector<::ui::InProgressTouchEvdev> getTouches(const NotifyMotionArgs& args,
+                                                   const AndroidPalmFilterDeviceInfo& deviceInfo,
+                                                   const SlotState& oldSlotState,
+                                                   const SlotState& newSlotState) {
+    std::vector<::ui::InProgressTouchEvdev> touches;
+
+    for (size_t i = 0; i < args.pointerCount; i++) {
+        const int32_t pointerId = args.pointerProperties[i].id;
+        touches.emplace_back(::ui::InProgressTouchEvdev());
+        touches.back().major = args.pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR);
+        touches.back().minor = args.pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR);
+        touches.back().tool_type = getLinuxToolType(args.pointerProperties[i].toolType);
+
+        // Whether there is new information for the touch.
+        touches.back().altered = true;
+
+        // Whether the touch was cancelled. Touch events should be ignored till a
+        // new touch is initiated.
+        touches.back().was_cancelled = false;
+
+        // Whether the touch is going to be canceled.
+        touches.back().cancelled = false;
+
+        // Whether the touch is delayed at first appearance. Will not be reported yet.
+        touches.back().delayed = false;
+
+        // Whether the touch was delayed before.
+        touches.back().was_delayed = false;
+
+        // Whether the touch is held until end or no longer held.
+        touches.back().held = false;
+
+        // Whether this touch was held before being sent.
+        touches.back().was_held = false;
+
+        const int32_t resolvedAction = resolveActionForPointer(i, args.action);
+        const bool isDown = resolvedAction == AMOTION_EVENT_ACTION_POINTER_DOWN ||
+                resolvedAction == AMOTION_EVENT_ACTION_DOWN;
+        touches.back().was_touching = !isDown;
+
+        const bool isUpOrCancel = resolvedAction == AMOTION_EVENT_ACTION_CANCEL ||
+                resolvedAction == AMOTION_EVENT_ACTION_UP ||
+                resolvedAction == AMOTION_EVENT_ACTION_POINTER_UP;
+
+        touches.back().x = args.pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_X);
+        touches.back().y = args.pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_Y);
+
+        std::optional<size_t> slot = newSlotState.getSlotForPointerId(pointerId);
+        if (!slot) {
+            slot = oldSlotState.getSlotForPointerId(pointerId);
+        }
+        LOG_ALWAYS_FATAL_IF(!slot, "Could not find slot for pointer %d", pointerId);
+        touches.back().slot = *slot;
+        touches.back().tracking_id = (!isUpOrCancel) ? pointerId : -1;
+        touches.back().touching = !isUpOrCancel;
+
+        // The fields 'radius_x' and 'radius_x' are not used for palm rejection
+        touches.back().pressure = args.pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_PRESSURE);
+        touches.back().tool_code = BTN_TOOL_FINGER;
+        // The field 'orientation' is not used for palm rejection
+        // The fields 'tilt_x' and 'tilt_y' are not used for palm rejection
+        touches.back().reported_tool_type = ::ui::EventPointerType::kTouch;
+        touches.back().stylus_button = false;
+    }
+    return touches;
+}
+
+std::vector<NotifyMotionArgs> PalmRejector::processMotion(const NotifyMotionArgs& args) {
+    if (mPalmDetectionFilter == nullptr) {
+        return {args};
+    }
+    const bool skipThisEvent = args.action == AMOTION_EVENT_ACTION_HOVER_ENTER ||
+            args.action == AMOTION_EVENT_ACTION_HOVER_MOVE ||
+            args.action == AMOTION_EVENT_ACTION_HOVER_EXIT ||
+            args.action == AMOTION_EVENT_ACTION_BUTTON_PRESS ||
+            args.action == AMOTION_EVENT_ACTION_BUTTON_RELEASE ||
+            args.action == AMOTION_EVENT_ACTION_SCROLL;
+    if (skipThisEvent) {
+        // Lets not process hover events, button events, or scroll for now.
+        return {args};
+    }
+    if (args.action == AMOTION_EVENT_ACTION_DOWN) {
+        mSuppressedPointerIds.clear();
+    }
+    std::bitset<::ui::kNumTouchEvdevSlots> slotsToHold;
+    std::bitset<::ui::kNumTouchEvdevSlots> slotsToSuppress;
+
+    // Store the slot state before we call getTouches and update it. This way, we can find
+    // the slots that have been removed due to the incoming event.
+    SlotState oldSlotState = mSlotState;
+    mSlotState.update(args);
+    std::vector<::ui::InProgressTouchEvdev> touches =
+            getTouches(args, mDeviceInfo, oldSlotState, mSlotState);
+    ::base::TimeTicks chromeTimestamp = toChromeTimestamp(args.eventTime);
+
+    mPalmDetectionFilter->Filter(touches, chromeTimestamp, &slotsToHold, &slotsToSuppress);
+
+    // Now that we know which slots should be suppressed, let's convert those to pointer id's.
+    std::set<int32_t> oldSuppressedIds;
+    std::swap(oldSuppressedIds, mSuppressedPointerIds);
+    for (size_t i = 0; i < args.pointerCount; i++) {
+        const int32_t pointerId = args.pointerProperties[i].id;
+        std::optional<size_t> slot = oldSlotState.getSlotForPointerId(pointerId);
+        if (!slot) {
+            slot = mSlotState.getSlotForPointerId(pointerId);
+            LOG_ALWAYS_FATAL_IF(!slot, "Could not find slot for pointer id %" PRId32, pointerId);
+        }
+        if (slotsToSuppress.test(*slot)) {
+            mSuppressedPointerIds.insert(pointerId);
+        }
+    }
+
+    std::vector<NotifyMotionArgs> argsWithoutUnwantedPointers =
+            cancelSuppressedPointers(args, oldSuppressedIds, mSuppressedPointerIds);
+    for (const NotifyMotionArgs& checkArgs : argsWithoutUnwantedPointers) {
+        LOG_ALWAYS_FATAL_IF(checkArgs.action == ACTION_UNKNOWN, "%s", checkArgs.dump().c_str());
+    }
+
+    if (mSuppressedPointerIds != oldSuppressedIds) {
+        if (argsWithoutUnwantedPointers.size() != 1 ||
+            argsWithoutUnwantedPointers[0].pointerCount != args.pointerCount) {
+            ALOGI("Palm detected, removing pointer ids %s from %s",
+                  dumpSet(mSuppressedPointerIds).c_str(), args.dump().c_str());
+        }
+    }
+
+    return argsWithoutUnwantedPointers;
+}
+
+const AndroidPalmFilterDeviceInfo& PalmRejector::getPalmFilterDeviceInfo() {
+    return mDeviceInfo;
+}
+
+std::string PalmRejector::dump() const {
+    std::string out;
+    out += "mDeviceInfo:\n";
+    out += addPrefix(dumpDeviceInfo(mDeviceInfo), "  ");
+    out += "mSlotState:\n";
+    out += addPrefix(mSlotState.dump(), "  ");
+    out += "mSuppressedPointerIds: ";
+    out += dumpSet(mSuppressedPointerIds) + "\n";
+    return out;
+}
+
+} // namespace android
diff --git a/services/inputflinger/UnwantedInteractionBlocker.h b/services/inputflinger/UnwantedInteractionBlocker.h
new file mode 100644
index 0000000..14068fd
--- /dev/null
+++ b/services/inputflinger/UnwantedInteractionBlocker.h
@@ -0,0 +1,153 @@
+/*
+ * 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 <map>
+#include <set>
+
+#include "include/UnwantedInteractionBlockerInterface.h"
+#include "ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter_util.h"
+#include "ui/events/ozone/evdev/touch_filter/palm_detection_filter.h"
+
+namespace android {
+
+// --- Functions for manipulation of event streams
+
+struct AndroidPalmFilterDeviceInfo : ::ui::PalmFilterDeviceInfo {
+    // Additional fields from 'TouchEventConverterEvdev', added here for convenience
+    int32_t touch_major_res = 1; // info.GetAbsInfoByCode(ABS_MT_TOUCH_MAJOR).resolution;
+    int32_t touch_minor_res = 1; // info.GetAbsInfoByCode(ABS_MT_TOUCH_MINOR).resolution;
+
+    auto operator<=>(const AndroidPalmFilterDeviceInfo&) const = default;
+};
+
+std::optional<AndroidPalmFilterDeviceInfo> createPalmFilterDeviceInfo(
+        const InputDeviceInfo& deviceInfo);
+
+static constexpr int32_t ACTION_UNKNOWN = -1;
+
+NotifyMotionArgs removePointerIds(const NotifyMotionArgs& args,
+                                  const std::set<int32_t>& pointerIds);
+
+std::vector<NotifyMotionArgs> cancelSuppressedPointers(
+        const NotifyMotionArgs& args, const std::set<int32_t>& oldSuppressedPointerIds,
+        const std::set<int32_t>& newSuppressedPointerIds);
+
+std::string toString(const ::ui::InProgressTouchEvdev& touch);
+
+// --- Main classes and interfaces ---
+
+class PalmRejector;
+
+// --- Implementations ---
+
+/**
+ * Implementation of the UnwantedInteractionBlockerInterface.
+ * Represents a separate stage of input processing. All of the input events go through this stage.
+ * Acts as a passthrough for all input events except for motion events.
+ *
+ * The events of motion type are sent to PalmRejectors. PalmRejectors detect unwanted touches,
+ * and emit input streams with the bad pointers removed.
+ */
+class UnwantedInteractionBlocker : public UnwantedInteractionBlockerInterface {
+public:
+    explicit UnwantedInteractionBlocker(InputListenerInterface& listener);
+    explicit UnwantedInteractionBlocker(InputListenerInterface& listener, bool enablePalmRejection);
+
+    void notifyConfigurationChanged(const NotifyConfigurationChangedArgs* args) override;
+    void notifyKey(const NotifyKeyArgs* args) override;
+    void notifyMotion(const NotifyMotionArgs* args) override;
+    void notifySwitch(const NotifySwitchArgs* args) override;
+    void notifySensor(const NotifySensorArgs* args) override;
+    void notifyVibratorState(const NotifyVibratorStateArgs* args) override;
+    void notifyDeviceReset(const NotifyDeviceResetArgs* args) override;
+    void notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs* args) override;
+
+    void notifyInputDevicesChanged(const std::vector<InputDeviceInfo>& inputDevices) override;
+    void dump(std::string& dump) override;
+    void monitor() override;
+
+    ~UnwantedInteractionBlocker();
+
+private:
+    // The next stage to pass input events to
+    InputListenerInterface& mListener;
+    const bool mEnablePalmRejection;
+
+    // Detect and reject unwanted palms on screen
+    // Use a separate palm rejector for every touch device.
+    std::map<int32_t /*deviceId*/, PalmRejector> mPalmRejectors;
+};
+
+class SlotState {
+public:
+    /**
+     * Update the state using the new information provided in the NotifyMotionArgs
+     */
+    void update(const NotifyMotionArgs& args);
+    std::optional<size_t> getSlotForPointerId(int32_t pointerId) const;
+    std::string dump() const;
+
+private:
+    // Process a pointer with the provided action, and return the slot associated with it
+    void processPointerId(int32_t pointerId, int32_t action);
+    // The map from tracking id to slot state. Since the PalmRejectionFilter works close to the
+    // evdev level, the only way to tell it about UP or CANCEL events is by sending tracking id = -1
+    // to the appropriate touch slot. So we need to reconstruct the original slot.
+    // The two collections below must always be in-sync.
+    // Use std::map instead of std::unordered_map because we rely on these collections being
+    // ordered. It also has better space efficiency than unordered_map because we only have a few
+    // pointers most of the time.
+    std::map<int32_t /*pointerId*/, size_t /*slot*/> mSlotsByPointerId;
+    std::map<size_t /*slot*/, int32_t /*pointerId */> mPointerIdsBySlot;
+
+    size_t findUnusedSlot() const;
+};
+
+/**
+ * Convert an Android event to a linux-like 'InProgressTouchEvdev'. The provided SlotState's
+ * are used to figure out which slot does each pointer belong to.
+ */
+std::vector<::ui::InProgressTouchEvdev> getTouches(const NotifyMotionArgs& args,
+                                                   const AndroidPalmFilterDeviceInfo& deviceInfo,
+                                                   const SlotState& oldSlotState,
+                                                   const SlotState& newSlotState);
+
+class PalmRejector {
+public:
+    explicit PalmRejector(const AndroidPalmFilterDeviceInfo& info,
+                          std::unique_ptr<::ui::PalmDetectionFilter> filter = nullptr);
+    std::vector<NotifyMotionArgs> processMotion(const NotifyMotionArgs& args);
+
+    // Get the device info of this device, for comparison purposes
+    const AndroidPalmFilterDeviceInfo& getPalmFilterDeviceInfo();
+    std::string dump() const;
+
+private:
+    PalmRejector(const PalmRejector&) = delete;
+    PalmRejector& operator=(const PalmRejector&) = delete;
+
+    std::unique_ptr<::ui::SharedPalmDetectionFilterState> mSharedPalmState;
+    AndroidPalmFilterDeviceInfo mDeviceInfo;
+    std::unique_ptr<::ui::PalmDetectionFilter> mPalmDetectionFilter;
+    std::set<int32_t> mSuppressedPointerIds;
+
+    // Used to help convert an Android touch stream to Linux input stream.
+    SlotState mSlotState;
+};
+
+} // namespace android
diff --git a/services/inputflinger/benchmarks/Android.bp b/services/inputflinger/benchmarks/Android.bp
index 902bd0d..75071d5 100644
--- a/services/inputflinger/benchmarks/Android.bp
+++ b/services/inputflinger/benchmarks/Android.bp
@@ -28,7 +28,6 @@
         "libstatslog",
         "libui",
         "libutils",
-        "lib-platform-compat-native-api",
     ],
     static_libs: [
         "libattestation",
diff --git a/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp b/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp
index 6ce0313..9691ad8 100644
--- a/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp
+++ b/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp
@@ -21,6 +21,7 @@
 #include <gui/constants.h>
 #include "../dispatcher/InputDispatcher.h"
 
+using android::base::Result;
 using android::gui::WindowInfo;
 using android::gui::WindowInfoHandle;
 using android::os::IInputConstants;
@@ -60,18 +61,13 @@
         ALOGE("There is no focused window for %s", applicationHandle->getName().c_str());
     }
 
-    void notifyWindowUnresponsive(const sp<IBinder>& connectionToken,
+    void notifyWindowUnresponsive(const sp<IBinder>& connectionToken, std::optional<int32_t> pid,
                                   const std::string& reason) override {
         ALOGE("Window is not responding: %s", reason.c_str());
     }
 
-    void notifyWindowResponsive(const sp<IBinder>& connectionToken) override {}
-
-    void notifyMonitorUnresponsive(int32_t pid, const std::string& reason) override {
-        ALOGE("Monitor is not responding: %s", reason.c_str());
-    }
-
-    void notifyMonitorResponsive(int32_t pid) override {}
+    void notifyWindowResponsive(const sp<IBinder>& connectionToken,
+                                std::optional<int32_t> pid) override {}
 
     void notifyInputChannelBroken(const sp<IBinder>&) override {}
 
@@ -162,15 +158,15 @@
     }
 
 protected:
-    explicit FakeInputReceiver(const sp<InputDispatcher>& dispatcher, const std::string name)
-          : mDispatcher(dispatcher) {
-        mClientChannel = *mDispatcher->createInputChannel(name);
+    explicit FakeInputReceiver(InputDispatcher& dispatcher, const std::string name) {
+        Result<std::unique_ptr<InputChannel>> channelResult = dispatcher.createInputChannel(name);
+        LOG_ALWAYS_FATAL_IF(!channelResult.ok());
+        mClientChannel = std::move(*channelResult);
         mConsumer = std::make_unique<InputConsumer>(mClientChannel);
     }
 
     virtual ~FakeInputReceiver() {}
 
-    sp<InputDispatcher> mDispatcher;
     std::shared_ptr<InputChannel> mClientChannel;
     std::unique_ptr<InputConsumer> mConsumer;
     PreallocatedInputEventFactory mEventFactory;
@@ -182,7 +178,7 @@
     static const int32_t HEIGHT = 200;
 
     FakeWindowHandle(const std::shared_ptr<InputApplicationHandle>& inputApplicationHandle,
-                     const sp<InputDispatcher>& dispatcher, const std::string name)
+                     InputDispatcher& dispatcher, const std::string name)
           : FakeInputReceiver(dispatcher, name), mFrame(Rect(0, 0, WIDTH, HEIGHT)) {
         inputApplicationHandle->updateInfo();
         updateInfo();
@@ -192,7 +188,6 @@
     void updateInfo() {
         mInfo.token = mClientChannel->getConnectionToken();
         mInfo.name = "FakeWindowHandle";
-        mInfo.type = WindowInfo::Type::APPLICATION;
         mInfo.dispatchingTimeout = DISPATCHING_TIMEOUT;
         mInfo.frameLeft = mFrame.left;
         mInfo.frameTop = mFrame.top;
@@ -201,10 +196,6 @@
         mInfo.globalScaleFactor = 1.0;
         mInfo.touchableRegion.clear();
         mInfo.addTouchableRegion(mFrame);
-        mInfo.visible = true;
-        mInfo.focusable = true;
-        mInfo.hasWallpaper = false;
-        mInfo.paused = false;
         mInfo.ownerPid = INJECTOR_PID;
         mInfo.ownerUid = INJECTOR_UID;
         mInfo.displayId = ADISPLAY_ID_DEFAULT;
@@ -236,8 +227,8 @@
                      /* edgeFlags */ 0, AMETA_NONE, /* buttonState */ 0, MotionClassification::NONE,
                      identityTransform, /* xPrecision */ 0,
                      /* yPrecision */ 0, AMOTION_EVENT_INVALID_CURSOR_POSITION,
-                     AMOTION_EVENT_INVALID_CURSOR_POSITION, ui::Transform::ROT_0,
-                     INVALID_DISPLAY_SIZE, INVALID_DISPLAY_SIZE, currentTime, currentTime,
+                     AMOTION_EVENT_INVALID_CURSOR_POSITION, identityTransform, currentTime,
+                     currentTime,
                      /*pointerCount*/ 1, pointerProperties, pointerCoords);
     return event;
 }
@@ -272,13 +263,13 @@
 static void benchmarkNotifyMotion(benchmark::State& state) {
     // Create dispatcher
     sp<FakeInputDispatcherPolicy> fakePolicy = new FakeInputDispatcherPolicy();
-    sp<InputDispatcher> dispatcher = new InputDispatcher(fakePolicy);
+    std::unique_ptr<InputDispatcher> dispatcher = std::make_unique<InputDispatcher>(fakePolicy);
     dispatcher->setInputDispatchMode(/*enabled*/ true, /*frozen*/ false);
     dispatcher->start();
 
     // Create a window that will receive motion events
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window = new FakeWindowHandle(application, dispatcher, "Fake Window");
+    sp<FakeWindowHandle> window = new FakeWindowHandle(application, *dispatcher, "Fake Window");
 
     dispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
 
@@ -306,13 +297,13 @@
 static void benchmarkInjectMotion(benchmark::State& state) {
     // Create dispatcher
     sp<FakeInputDispatcherPolicy> fakePolicy = new FakeInputDispatcherPolicy();
-    sp<InputDispatcher> dispatcher = new InputDispatcher(fakePolicy);
+    std::unique_ptr<InputDispatcher> dispatcher = std::make_unique<InputDispatcher>(fakePolicy);
     dispatcher->setInputDispatchMode(/*enabled*/ true, /*frozen*/ false);
     dispatcher->start();
 
     // Create a window that will receive motion events
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window = new FakeWindowHandle(application, dispatcher, "Fake Window");
+    sp<FakeWindowHandle> window = new FakeWindowHandle(application, *dispatcher, "Fake Window");
 
     dispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
 
diff --git a/services/inputflinger/dispatcher/Android.bp b/services/inputflinger/dispatcher/Android.bp
index 171f2b5..cdad9c9 100644
--- a/services/inputflinger/dispatcher/Android.bp
+++ b/services/inputflinger/dispatcher/Android.bp
@@ -66,9 +66,7 @@
         "libui",
         "libgui",
         "libutils",
-        "lib-platform-compat-native-api",
         "server_configurable_flags",
-        "InputFlingerProperties",
     ],
     static_libs: [
         "libattestation",
diff --git a/services/inputflinger/dispatcher/Connection.cpp b/services/inputflinger/dispatcher/Connection.cpp
index cee9c39..b4497fd 100644
--- a/services/inputflinger/dispatcher/Connection.cpp
+++ b/services/inputflinger/dispatcher/Connection.cpp
@@ -22,7 +22,7 @@
 
 Connection::Connection(const std::shared_ptr<InputChannel>& inputChannel, bool monitor,
                        const IdGenerator& idGenerator)
-      : status(STATUS_NORMAL),
+      : status(Status::NORMAL),
         inputChannel(inputChannel),
         monitor(monitor),
         inputPublisher(inputChannel),
@@ -40,19 +40,6 @@
     return "?";
 }
 
-const char* Connection::getStatusLabel() const {
-    switch (status) {
-        case STATUS_NORMAL:
-            return "NORMAL";
-        case STATUS_BROKEN:
-            return "BROKEN";
-        case STATUS_ZOMBIE:
-            return "ZOMBIE";
-        default:
-            return "UNKNOWN";
-    }
-}
-
 std::deque<DispatchEntry*>::iterator Connection::findWaitQueueEntry(uint32_t seq) {
     for (std::deque<DispatchEntry*>::iterator it = waitQueue.begin(); it != waitQueue.end(); it++) {
         if ((*it)->seq == seq) {
diff --git a/services/inputflinger/dispatcher/Connection.h b/services/inputflinger/dispatcher/Connection.h
index c4262ad..dc6a081 100644
--- a/services/inputflinger/dispatcher/Connection.h
+++ b/services/inputflinger/dispatcher/Connection.h
@@ -20,6 +20,7 @@
 #include "InputState.h"
 
 #include <input/InputTransport.h>
+#include <utils/RefBase.h>
 #include <deque>
 
 namespace android::inputdispatcher {
@@ -32,13 +33,16 @@
     virtual ~Connection();
 
 public:
-    enum Status {
+    enum class Status {
         // Everything is peachy.
-        STATUS_NORMAL,
+        NORMAL,
         // An unrecoverable communication error has occurred.
-        STATUS_BROKEN,
+        BROKEN,
         // The input channel has been unregistered.
-        STATUS_ZOMBIE
+        ZOMBIE,
+
+        ftl_first = NORMAL,
+        ftl_last = ZOMBIE,
     };
 
     Status status;
@@ -65,7 +69,6 @@
     inline const std::string getInputChannelName() const { return inputChannel->getName(); }
 
     const std::string getWindowName() const;
-    const char* getStatusLabel() const;
 
     std::deque<DispatchEntry*>::iterator findWaitQueueEntry(uint32_t seq);
 };
diff --git a/services/inputflinger/dispatcher/DragState.h b/services/inputflinger/dispatcher/DragState.h
index b3c5709..4636820 100644
--- a/services/inputflinger/dispatcher/DragState.h
+++ b/services/inputflinger/dispatcher/DragState.h
@@ -18,7 +18,7 @@
 #define _UI_INPUT_INPUTDISPATCHER_DRAGSTATE_H
 
 #include <gui/WindowInfo.h>
-#include <utils/RefBase.h>
+#include <utils/StrongPointer.h>
 #include <string>
 
 namespace android {
diff --git a/services/inputflinger/dispatcher/Entry.cpp b/services/inputflinger/dispatcher/Entry.cpp
index 82b4fe4..8046bbe 100644
--- a/services/inputflinger/dispatcher/Entry.cpp
+++ b/services/inputflinger/dispatcher/Entry.cpp
@@ -32,25 +32,26 @@
     return {{VerifiedInputEvent::Type::KEY, entry.deviceId, entry.eventTime, entry.source,
              entry.displayId},
             entry.action,
-            entry.downTime,
             entry.flags & VERIFIED_KEY_EVENT_FLAGS,
+            entry.downTime,
             entry.keyCode,
             entry.scanCode,
             entry.metaState,
             entry.repeatCount};
 }
 
-VerifiedMotionEvent verifiedMotionEventFromMotionEntry(const MotionEntry& entry) {
-    const float rawX = entry.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_X);
-    const float rawY = entry.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_Y);
+VerifiedMotionEvent verifiedMotionEventFromMotionEntry(const MotionEntry& entry,
+                                                       const ui::Transform& rawTransform) {
+    const vec2 rawXY = MotionEvent::calculateTransformedXY(entry.source, rawTransform,
+                                                           entry.pointerCoords[0].getXYValue());
     const int actionMasked = entry.action & AMOTION_EVENT_ACTION_MASK;
     return {{VerifiedInputEvent::Type::MOTION, entry.deviceId, entry.eventTime, entry.source,
              entry.displayId},
-            rawX,
-            rawY,
+            rawXY.x,
+            rawXY.y,
             actionMasked,
-            entry.downTime,
             entry.flags & VERIFIED_MOTION_EVENT_FLAGS,
+            entry.downTime,
             entry.metaState,
             entry.buttonState};
 }
@@ -174,12 +175,13 @@
     if (!GetBoolProperty("ro.debuggable", false)) {
         return "KeyEvent";
     }
-    return StringPrintf("KeyEvent(deviceId=%d, eventTime=%" PRIu64
-                        ", source=0x%08x, displayId=%" PRId32 ", action=%s, "
-                        "flags=0x%08x, keyCode=%d, scanCode=%d, metaState=0x%08x, "
+    return StringPrintf("KeyEvent(deviceId=%d, eventTime=%" PRIu64 ", source=%s, displayId=%" PRId32
+                        ", action=%s, "
+                        "flags=0x%08x, keyCode=%s(%d), scanCode=%d, metaState=0x%08x, "
                         "repeatCount=%d), policyFlags=0x%08x",
-                        deviceId, eventTime, source, displayId, KeyEvent::actionToString(action),
-                        flags, keyCode, scanCode, metaState, repeatCount, policyFlags);
+                        deviceId, eventTime, inputEventSourceToString(source).c_str(), displayId,
+                        KeyEvent::actionToString(action), flags, KeyEvent::getLabel(keyCode),
+                        keyCode, scanCode, metaState, repeatCount, policyFlags);
 }
 
 void KeyEntry::recycle() {
@@ -191,6 +193,18 @@
     interceptKeyWakeupTime = 0;
 }
 
+// --- TouchModeEntry ---
+
+TouchModeEntry::TouchModeEntry(int32_t id, nsecs_t eventTime, bool inTouchMode)
+      : EventEntry(id, Type::TOUCH_MODE_CHANGED, eventTime, POLICY_FLAG_PASS_TO_USER),
+        inTouchMode(inTouchMode) {}
+
+TouchModeEntry::~TouchModeEntry() {}
+
+std::string TouchModeEntry::getDescription() const {
+    return StringPrintf("TouchModeEvent(inTouchMode=%s)", inTouchMode ? "true" : "false");
+}
+
 // --- MotionEntry ---
 
 MotionEntry::MotionEntry(int32_t id, nsecs_t eventTime, int32_t deviceId, uint32_t source,
@@ -200,7 +214,7 @@
                          int32_t edgeFlags, float xPrecision, float yPrecision,
                          float xCursorPosition, float yCursorPosition, nsecs_t downTime,
                          uint32_t pointerCount, const PointerProperties* pointerProperties,
-                         const PointerCoords* pointerCoords, float xOffset, float yOffset)
+                         const PointerCoords* pointerCoords)
       : EventEntry(id, Type::MOTION, eventTime, policyFlags),
         deviceId(deviceId),
         source(source),
@@ -221,9 +235,6 @@
     for (uint32_t i = 0; i < pointerCount; i++) {
         this->pointerProperties[i].copyFrom(pointerProperties[i]);
         this->pointerCoords[i].copyFrom(pointerCoords[i]);
-        if (xOffset || yOffset) {
-            this->pointerCoords[i].applyOffset(xOffset, yOffset);
-        }
     }
 }
 
@@ -235,12 +246,12 @@
     }
     std::string msg;
     msg += StringPrintf("MotionEvent(deviceId=%d, eventTime=%" PRIu64
-                        ", source=0x%08x, displayId=%" PRId32
+                        ", source=%s, displayId=%" PRId32
                         ", action=%s, actionButton=0x%08x, flags=0x%08x, metaState=0x%08x, "
                         "buttonState=0x%08x, "
                         "classification=%s, edgeFlags=0x%08x, xPrecision=%.1f, yPrecision=%.1f, "
                         "xCursorPosition=%0.1f, yCursorPosition=%0.1f, pointers=[",
-                        deviceId, eventTime, source, displayId,
+                        deviceId, eventTime, inputEventSourceToString(source).c_str(), displayId,
                         MotionEvent::actionToString(action).c_str(), actionButton, flags, metaState,
                         buttonState, motionClassificationToString(classification), edgeFlags,
                         xPrecision, yPrecision, xCursorPosition, yCursorPosition);
@@ -275,9 +286,10 @@
 
 std::string SensorEntry::getDescription() const {
     std::string msg;
-    msg += StringPrintf("SensorEntry(deviceId=%d, source=0x%08x, sensorType=0x%08x, "
+    msg += StringPrintf("SensorEntry(deviceId=%d, source=%s, sensorType=%s, "
                         "accuracy=0x%08x, hwTimestamp=%" PRId64,
-                        deviceId, source, sensorType, accuracy, hwTimestamp);
+                        deviceId, inputEventSourceToString(source).c_str(),
+                        ftl::enum_string(sensorType).c_str(), accuracy, hwTimestamp);
 
     if (!GetBoolProperty("ro.debuggable", false)) {
         for (size_t i = 0; i < values.size(); i++) {
@@ -296,15 +308,14 @@
 volatile int32_t DispatchEntry::sNextSeqAtomic;
 
 DispatchEntry::DispatchEntry(std::shared_ptr<EventEntry> eventEntry, int32_t targetFlags,
-                             ui::Transform transform, float globalScaleFactor,
-                             uint32_t displayOrientation, int2 displaySize)
+                             const ui::Transform& transform, const ui::Transform& rawTransform,
+                             float globalScaleFactor)
       : seq(nextSeq()),
         eventEntry(std::move(eventEntry)),
         targetFlags(targetFlags),
         transform(transform),
+        rawTransform(rawTransform),
         globalScaleFactor(globalScaleFactor),
-        displayOrientation(displayOrientation),
-        displaySize(displaySize),
         deliveryTime(0),
         resolvedAction(0),
         resolvedFlags(0) {}
@@ -318,16 +329,4 @@
     return seq;
 }
 
-// --- CommandEntry ---
-
-CommandEntry::CommandEntry(Command command)
-      : command(command),
-        eventTime(0),
-        keyEntry(nullptr),
-        userActivityEventType(0),
-        seq(0),
-        handled(false) {}
-
-CommandEntry::~CommandEntry() {}
-
 } // namespace android::inputdispatcher
diff --git a/services/inputflinger/dispatcher/Entry.h b/services/inputflinger/dispatcher/Entry.h
index ffe3bb6..0f79296 100644
--- a/services/inputflinger/dispatcher/Entry.h
+++ b/services/inputflinger/dispatcher/Entry.h
@@ -39,6 +39,9 @@
         SENSOR,
         POINTER_CAPTURE_CHANGED,
         DRAG,
+        TOUCH_MODE_CHANGED,
+
+        ftl_last = TOUCH_MODE_CHANGED
     };
 
     int32_t id;
@@ -181,11 +184,10 @@
                 int32_t metaState, int32_t buttonState, MotionClassification classification,
                 int32_t edgeFlags, float xPrecision, float yPrecision, float xCursorPosition,
                 float yCursorPosition, nsecs_t downTime, uint32_t pointerCount,
-                const PointerProperties* pointerProperties, const PointerCoords* pointerCoords,
-                float xOffset, float yOffset);
+                const PointerProperties* pointerProperties, const PointerCoords* pointerCoords);
     std::string getDescription() const override;
 
-    virtual ~MotionEntry();
+    ~MotionEntry() override;
 };
 
 struct SensorEntry : EventEntry {
@@ -207,6 +209,15 @@
     ~SensorEntry() override;
 };
 
+struct TouchModeEntry : EventEntry {
+    bool inTouchMode;
+
+    TouchModeEntry(int32_t id, nsecs_t eventTime, bool inTouchMode);
+    std::string getDescription() const override;
+
+    ~TouchModeEntry() override;
+};
+
 // Tracks the progress of dispatching a particular event to a particular connection.
 struct DispatchEntry {
     const uint32_t seq; // unique sequence number, never 0
@@ -214,9 +225,8 @@
     std::shared_ptr<EventEntry> eventEntry; // the event to dispatch
     int32_t targetFlags;
     ui::Transform transform;
+    ui::Transform rawTransform;
     float globalScaleFactor;
-    uint32_t displayOrientation;
-    int2 displaySize;
     // Both deliveryTime and timeoutTime are only populated when the entry is sent to the app,
     // and will be undefined before that.
     nsecs_t deliveryTime; // time when the event was actually delivered
@@ -229,8 +239,8 @@
     int32_t resolvedFlags;
 
     DispatchEntry(std::shared_ptr<EventEntry> eventEntry, int32_t targetFlags,
-                  ui::Transform transform, float globalScaleFactor, uint32_t displayOrientation,
-                  int2 displaySize);
+                  const ui::Transform& transform, const ui::Transform& rawTransform,
+                  float globalScaleFactor);
 
     inline bool hasForegroundTarget() const { return targetFlags & InputTarget::FLAG_FOREGROUND; }
 
@@ -243,56 +253,8 @@
 };
 
 VerifiedKeyEvent verifiedKeyEventFromKeyEntry(const KeyEntry& entry);
-VerifiedMotionEvent verifiedMotionEventFromMotionEntry(const MotionEntry& entry);
-
-class InputDispatcher;
-// A command entry captures state and behavior for an action to be performed in the
-// dispatch loop after the initial processing has taken place.  It is essentially
-// a kind of continuation used to postpone sensitive policy interactions to a point
-// in the dispatch loop where it is safe to release the lock (generally after finishing
-// the critical parts of the dispatch cycle).
-//
-// The special thing about commands is that they can voluntarily release and reacquire
-// the dispatcher lock at will.  Initially when the command starts running, the
-// dispatcher lock is held.  However, if the command needs to call into the policy to
-// do some work, it can release the lock, do the work, then reacquire the lock again
-// before returning.
-//
-// This mechanism is a bit clunky but it helps to preserve the invariant that the dispatch
-// never calls into the policy while holding its lock.
-//
-// Commands are implicitly 'LockedInterruptible'.
-struct CommandEntry;
-typedef std::function<void(InputDispatcher&, CommandEntry*)> Command;
-
-class Connection;
-struct CommandEntry {
-    explicit CommandEntry(Command command);
-    ~CommandEntry();
-
-    Command command;
-
-    // parameters for the command (usage varies by command)
-    sp<Connection> connection;
-    nsecs_t eventTime;
-    std::shared_ptr<KeyEntry> keyEntry;
-    std::shared_ptr<SensorEntry> sensorEntry;
-    std::shared_ptr<InputApplicationHandle> inputApplicationHandle;
-    std::string reason;
-    int32_t userActivityEventType;
-    uint32_t seq;
-    bool handled;
-    sp<IBinder> connectionToken;
-    sp<IBinder> oldToken;
-    sp<IBinder> newToken;
-    std::string obscuringPackage;
-    PointerCaptureRequest pointerCaptureRequest;
-    int32_t pid;
-    nsecs_t consumeTime; // time when the event was consumed by InputConsumer
-    int32_t displayId;
-    float x;
-    float y;
-};
+VerifiedMotionEvent verifiedMotionEventFromMotionEntry(const MotionEntry& entry,
+                                                       const ui::Transform& rawTransform);
 
 } // namespace android::inputdispatcher
 
diff --git a/services/inputflinger/dispatcher/EventLogTags.logtags b/services/inputflinger/dispatcher/EventLogTags.logtags
index 2836467..2c5fe21 100644
--- a/services/inputflinger/dispatcher/EventLogTags.logtags
+++ b/services/inputflinger/dispatcher/EventLogTags.logtags
@@ -37,6 +37,7 @@
 
 62000 input_interaction (windows|4)
 62001 input_focus (window|3),(reason|3)
+62003 input_cancel (window|3),(reason|3)
 
 # NOTE - the range 1000000-2000000 is reserved for partners and others who
 # want to define their own log tags without conflicting with the core platform.
\ No newline at end of file
diff --git a/services/inputflinger/dispatcher/FocusResolver.cpp b/services/inputflinger/dispatcher/FocusResolver.cpp
index 4a75773..a02b3e8 100644
--- a/services/inputflinger/dispatcher/FocusResolver.cpp
+++ b/services/inputflinger/dispatcher/FocusResolver.cpp
@@ -27,7 +27,7 @@
 
 #include <android-base/stringprintf.h>
 #include <binder/Binder.h>
-#include <ftl/NamedEnum.h>
+#include <ftl/enum.h>
 #include <gui/WindowInfo.h>
 #include <log/log.h>
 
@@ -65,7 +65,7 @@
         if (result == Focusability::OK) {
             return std::nullopt;
         }
-        removeFocusReason = NamedEnum::string(result);
+        removeFocusReason = ftl::enum_string(result);
     }
 
     // We don't have a focused window or the currently focused window is no longer focusable. Check
@@ -79,7 +79,7 @@
         if (result == Focusability::OK) {
             return updateFocusedWindow(displayId,
                                        "Window became focusable. Previous reason: " +
-                                               NamedEnum::string(previousResult),
+                                               ftl::enum_string(previousResult),
                                        requestedFocus, request->windowName);
         }
     }
@@ -116,7 +116,7 @@
                                        request.token, request.windowName);
         }
         ALOGW("setFocusedWindow %s on display %" PRId32 " ignored, reason: %s",
-              request.windowName.c_str(), displayId, NamedEnum::string(result).c_str());
+              request.windowName.c_str(), displayId, ftl::enum_string(result).c_str());
         return std::nullopt;
     }
 
@@ -134,7 +134,7 @@
     // The requested window is not currently focusable. Wait for the window to become focusable
     // but remove focus from the current window so that input events can go into a pending queue
     // and be sent to the window when it becomes focused.
-    return updateFocusedWindow(displayId, "Waiting for window because " + NamedEnum::string(result),
+    return updateFocusedWindow(displayId, "Waiting for window because " + ftl::enum_string(result),
                                nullptr);
 }
 
@@ -148,11 +148,11 @@
             continue;
         }
         windowFound = true;
-        if (window->getInfo()->visible) {
+        if (!window->getInfo()->inputConfig.test(gui::WindowInfo::InputConfig::NOT_VISIBLE)) {
             // Check if at least a single window is visible.
             visibleWindowFound = true;
         }
-        if (!window->getInfo()->focusable) {
+        if (window->getInfo()->inputConfig.test(gui::WindowInfo::InputConfig::NOT_FOCUSABLE)) {
             // Check if all windows with the window token are focusable.
             allWindowsAreFocusable = false;
             break;
@@ -212,7 +212,7 @@
     for (const auto& [displayId, request] : mFocusRequestByDisplay) {
         auto it = mLastFocusResultByDisplay.find(displayId);
         std::string result =
-                it != mLastFocusResultByDisplay.end() ? NamedEnum::string(it->second) : "";
+                it != mLastFocusResultByDisplay.end() ? ftl::enum_string(it->second) : "";
         dump += base::StringPrintf(INDENT2 "displayId=%" PRId32 ", name='%s' result='%s'\n",
                                    displayId, request.windowName.c_str(), result.c_str());
     }
diff --git a/services/inputflinger/dispatcher/FocusResolver.h b/services/inputflinger/dispatcher/FocusResolver.h
index 1d6cd9a..6d11a77 100644
--- a/services/inputflinger/dispatcher/FocusResolver.h
+++ b/services/inputflinger/dispatcher/FocusResolver.h
@@ -77,6 +77,8 @@
         NO_WINDOW,
         NOT_FOCUSABLE,
         NOT_VISIBLE,
+
+        ftl_last = NOT_VISIBLE
     };
 
     // Checks if the window token can be focused on a display. The token can be focused if there is
@@ -113,4 +115,4 @@
     std::optional<android::gui::FocusRequest> getFocusRequest(int32_t displayId);
 };
 
-} // namespace android::inputdispatcher
\ No newline at end of file
+} // namespace android::inputdispatcher
diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp
index d53495f..c8a3ccf 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.cpp
+++ b/services/inputflinger/dispatcher/InputDispatcher.cpp
@@ -19,42 +19,12 @@
 
 #define LOG_NDEBUG 1
 
-// Log detailed debug messages about each inbound event notification to the dispatcher.
-#define DEBUG_INBOUND_EVENT_DETAILS 0
-
-// Log detailed debug messages about each outbound event processed by the dispatcher.
-#define DEBUG_OUTBOUND_EVENT_DETAILS 0
-
-// Log debug messages about the dispatch cycle.
-#define DEBUG_DISPATCH_CYCLE 0
-
-// Log debug messages about channel creation
-#define DEBUG_CHANNEL_CREATION 0
-
-// Log debug messages about input event injection.
-#define DEBUG_INJECTION 0
-
-// Log debug messages about input focus tracking.
-static constexpr bool DEBUG_FOCUS = false;
-
-// Log debug messages about touch occlusion
-// STOPSHIP(b/169067926): Set to false
-static constexpr bool DEBUG_TOUCH_OCCLUSION = true;
-
-// Log debug messages about the app switch latency optimization.
-#define DEBUG_APP_SWITCH 0
-
-// Log debug messages about hover events.
-#define DEBUG_HOVER 0
-
-#include <InputFlingerProperties.sysprop.h>
 #include <android-base/chrono_utils.h>
 #include <android-base/properties.h>
 #include <android-base/stringprintf.h>
 #include <android/os/IInputConstants.h>
 #include <binder/Binder.h>
-#include <binder/IServiceManager.h>
-#include <com/android/internal/compat/IPlatformCompatNative.h>
+#include <ftl/enum.h>
 #include <gui/SurfaceComposerClient.h>
 #include <input/InputDevice.h>
 #include <log/log.h>
@@ -82,6 +52,7 @@
 using android::base::HwTimeoutMultiplier;
 using android::base::Result;
 using android::base::StringPrintf;
+using android::gui::DisplayInfo;
 using android::gui::FocusRequest;
 using android::gui::TouchOcclusionMode;
 using android::gui::WindowInfo;
@@ -90,18 +61,93 @@
 using android::os::IInputConstants;
 using android::os::InputEventInjectionResult;
 using android::os::InputEventInjectionSync;
-using com::android::internal::compat::IPlatformCompatNative;
 
 namespace android::inputdispatcher {
 
-// When per-window-input-rotation is enabled, InputFlinger works in the un-rotated display
-// coordinates and SurfaceFlinger includes the display rotation in the input window transforms.
-static bool isPerWindowInputRotationEnabled() {
-    static const bool PER_WINDOW_INPUT_ROTATION =
-            sysprop::InputFlingerProperties::per_window_input_rotation().value_or(false);
+namespace {
 
-    return PER_WINDOW_INPUT_ROTATION;
-}
+/**
+ * Log detailed debug messages about each inbound event notification to the dispatcher.
+ * Enable this via "adb shell setprop log.tag.InputDispatcherInboundEvent DEBUG" (requires restart)
+ */
+const bool DEBUG_INBOUND_EVENT_DETAILS =
+        __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "InboundEvent", ANDROID_LOG_INFO);
+
+/**
+ * Log detailed debug messages about each outbound event processed by the dispatcher.
+ * Enable this via "adb shell setprop log.tag.InputDispatcherOutboundEvent DEBUG" (requires restart)
+ */
+const bool DEBUG_OUTBOUND_EVENT_DETAILS =
+        __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "OutboundEvent", ANDROID_LOG_INFO);
+
+/**
+ * Log debug messages about the dispatch cycle.
+ * Enable this via "adb shell setprop log.tag.InputDispatcherDispatchCycle DEBUG" (requires restart)
+ */
+const bool DEBUG_DISPATCH_CYCLE =
+        __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "DispatchCycle", ANDROID_LOG_INFO);
+
+/**
+ * Log debug messages about channel creation
+ * Enable this via "adb shell setprop log.tag.InputDispatcherChannelCreation DEBUG" (requires
+ * restart)
+ */
+const bool DEBUG_CHANNEL_CREATION =
+        __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "ChannelCreation", ANDROID_LOG_INFO);
+
+/**
+ * Log debug messages about input event injection.
+ * Enable this via "adb shell setprop log.tag.InputDispatcherInjection DEBUG" (requires restart)
+ */
+const bool DEBUG_INJECTION =
+        __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Injection", ANDROID_LOG_INFO);
+
+/**
+ * Log debug messages about input focus tracking.
+ * Enable this via "adb shell setprop log.tag.InputDispatcherFocus DEBUG" (requires restart)
+ */
+const bool DEBUG_FOCUS =
+        __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Focus", ANDROID_LOG_INFO);
+
+/**
+ * Log debug messages about touch mode event
+ * Enable this via "adb shell setprop log.tag.InputDispatcherTouchMode DEBUG" (requires restart)
+ */
+const bool DEBUG_TOUCH_MODE =
+        __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "TouchMode", ANDROID_LOG_INFO);
+
+/**
+ * Log debug messages about touch occlusion
+ * Enable this via "adb shell setprop log.tag.InputDispatcherTouchOcclusion DEBUG" (requires
+ * restart)
+ */
+const bool DEBUG_TOUCH_OCCLUSION =
+        __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "TouchOcclusion", ANDROID_LOG_INFO);
+
+/**
+ * Log debug messages about the app switch latency optimization.
+ * Enable this via "adb shell setprop log.tag.InputDispatcherAppSwitch DEBUG" (requires restart)
+ */
+const bool DEBUG_APP_SWITCH =
+        __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "AppSwitch", ANDROID_LOG_INFO);
+
+/**
+ * Log debug messages about hover events.
+ * Enable this via "adb shell setprop log.tag.InputDispatcherHover DEBUG" (requires restart)
+ */
+const bool DEBUG_HOVER =
+        __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Hover", ANDROID_LOG_INFO);
+
+// Temporarily releases a held mutex for the lifetime of the instance.
+// Named to match std::scoped_lock
+class scoped_unlock {
+public:
+    explicit scoped_unlock(std::mutex& mutex) : mMutex(mutex) { mMutex.unlock(); }
+    ~scoped_unlock() { mMutex.lock(); }
+
+private:
+    std::mutex& mMutex;
+};
 
 // Default input dispatching timeout if there is no focused application or paused window
 // from which to determine an appropriate dispatching timeout.
@@ -114,9 +160,7 @@
 // when an application takes too long to respond and the user has pressed an app switch key.
 constexpr nsecs_t APP_SWITCH_TIMEOUT = 500 * 1000000LL; // 0.5sec
 
-// Amount of time to allow for an event to be dispatched (measured since its eventTime)
-// before considering it stale and dropping it.
-constexpr nsecs_t STALE_EVENT_TIMEOUT = 10000 * 1000000LL; // 10sec
+const std::chrono::duration STALE_EVENT_TIMEOUT = std::chrono::seconds(10) * HwTimeoutMultiplier();
 
 // Log a warning when an event takes longer than this to process, even if an ANR does not occur.
 constexpr nsecs_t SLOW_EVENT_PROCESSING_WARNING_TIMEOUT = 2000 * 1000000LL; // 2sec
@@ -132,31 +176,32 @@
 // Number of recent events to keep for debugging purposes.
 constexpr size_t RECENT_QUEUE_MAX_SIZE = 10;
 
-// Event log tags. See EventLogTags.logtags for reference
+// Event log tags. See EventLogTags.logtags for reference.
 constexpr int LOGTAG_INPUT_INTERACTION = 62000;
 constexpr int LOGTAG_INPUT_FOCUS = 62001;
+constexpr int LOGTAG_INPUT_CANCEL = 62003;
 
-static inline nsecs_t now() {
+inline nsecs_t now() {
     return systemTime(SYSTEM_TIME_MONOTONIC);
 }
 
-static inline const char* toString(bool value) {
+inline const char* toString(bool value) {
     return value ? "true" : "false";
 }
 
-static inline const std::string toString(sp<IBinder> binder) {
+inline const std::string toString(const sp<IBinder>& binder) {
     if (binder == nullptr) {
         return "<null>";
     }
     return StringPrintf("%p", binder.get());
 }
 
-static inline int32_t getMotionEventActionPointerIndex(int32_t action) {
+inline int32_t getMotionEventActionPointerIndex(int32_t action) {
     return (action & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK) >>
             AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
 }
 
-static bool isValidKeyAction(int32_t action) {
+bool isValidKeyAction(int32_t action) {
     switch (action) {
         case AKEY_EVENT_ACTION_DOWN:
         case AKEY_EVENT_ACTION_UP:
@@ -166,7 +211,7 @@
     }
 }
 
-static bool validateKeyEvent(int32_t action) {
+bool validateKeyEvent(int32_t action) {
     if (!isValidKeyAction(action)) {
         ALOGE("Key event has invalid action code 0x%x", action);
         return false;
@@ -174,7 +219,7 @@
     return true;
 }
 
-static bool isValidMotionAction(int32_t action, int32_t actionButton, int32_t pointerCount) {
+bool isValidMotionAction(int32_t action, int32_t actionButton, int32_t pointerCount) {
     switch (action & AMOTION_EVENT_ACTION_MASK) {
         case AMOTION_EVENT_ACTION_DOWN:
         case AMOTION_EVENT_ACTION_UP:
@@ -199,18 +244,18 @@
     }
 }
 
-static int64_t millis(std::chrono::nanoseconds t) {
+int64_t millis(std::chrono::nanoseconds t) {
     return std::chrono::duration_cast<std::chrono::milliseconds>(t).count();
 }
 
-static bool validateMotionEvent(int32_t action, int32_t actionButton, size_t pointerCount,
-                                const PointerProperties* pointerProperties) {
+bool validateMotionEvent(int32_t action, int32_t actionButton, size_t pointerCount,
+                         const PointerProperties* pointerProperties) {
     if (!isValidMotionAction(action, actionButton, pointerCount)) {
         ALOGE("Motion event has invalid action code 0x%x", action);
         return false;
     }
     if (pointerCount < 1 || pointerCount > MAX_POINTERS) {
-        ALOGE("Motion event has invalid pointer count %zu; value must be between 1 and %d.",
+        ALOGE("Motion event has invalid pointer count %zu; value must be between 1 and %zu.",
               pointerCount, MAX_POINTERS);
         return false;
     }
@@ -231,7 +276,7 @@
     return true;
 }
 
-static std::string dumpRegion(const Region& region) {
+std::string dumpRegion(const Region& region) {
     if (region.isEmpty()) {
         return "<empty>";
     }
@@ -252,7 +297,7 @@
     return dump;
 }
 
-static std::string dumpQueue(const std::deque<DispatchEntry*>& queue, nsecs_t currentTime) {
+std::string dumpQueue(const std::deque<DispatchEntry*>& queue, nsecs_t currentTime) {
     constexpr size_t maxEntries = 50; // max events to print
     constexpr size_t skipBegin = maxEntries / 2;
     const size_t skipEnd = queue.size() - maxEntries / 2;
@@ -291,12 +336,12 @@
  * Also useful when the entries are sp<>. If an entry is not found, nullptr is returned.
  */
 template <typename K, typename V>
-static V getValueByKey(const std::unordered_map<K, V>& map, K key) {
+V getValueByKey(const std::unordered_map<K, V>& map, K key) {
     auto it = map.find(key);
     return it != map.end() ? it->second : V{};
 }
 
-static bool haveSameToken(const sp<WindowInfoHandle>& first, const sp<WindowInfoHandle>& second) {
+bool haveSameToken(const sp<WindowInfoHandle>& first, const sp<WindowInfoHandle>& second) {
     if (first == second) {
         return true;
     }
@@ -308,7 +353,7 @@
     return first->getToken() == second->getToken();
 }
 
-static bool haveSameApplicationToken(const WindowInfo* first, const WindowInfo* second) {
+bool haveSameApplicationToken(const WindowInfo* first, const WindowInfo* second) {
     if (first == nullptr || second == nullptr) {
         return false;
     }
@@ -316,33 +361,14 @@
             first->applicationInfo.token == second->applicationInfo.token;
 }
 
-static bool isStaleEvent(nsecs_t currentTime, const EventEntry& entry) {
-    return currentTime - entry.eventTime >= STALE_EVENT_TIMEOUT;
-}
-
-static std::unique_ptr<DispatchEntry> createDispatchEntry(const InputTarget& inputTarget,
-                                                          std::shared_ptr<EventEntry> eventEntry,
-                                                          int32_t inputTargetFlags) {
-    if (eventEntry->type == EventEntry::Type::MOTION) {
-        const MotionEntry& motionEntry = static_cast<const MotionEntry&>(*eventEntry);
-        if ((motionEntry.source & AINPUT_SOURCE_CLASS_JOYSTICK) ||
-            (motionEntry.source & AINPUT_SOURCE_CLASS_POSITION)) {
-            const ui::Transform identityTransform;
-            // Use identity transform for joystick and position-based (touchpad) events because they
-            // don't depend on the window transform.
-            return std::make_unique<DispatchEntry>(eventEntry, inputTargetFlags, identityTransform,
-                                                   1.0f /*globalScaleFactor*/,
-                                                   inputTarget.displayOrientation,
-                                                   inputTarget.displaySize);
-        }
-    }
-
+std::unique_ptr<DispatchEntry> createDispatchEntry(const InputTarget& inputTarget,
+                                                   std::shared_ptr<EventEntry> eventEntry,
+                                                   int32_t inputTargetFlags) {
     if (inputTarget.useDefaultPointerTransform()) {
         const ui::Transform& transform = inputTarget.getDefaultPointerTransform();
         return std::make_unique<DispatchEntry>(eventEntry, inputTargetFlags, transform,
-                                               inputTarget.globalScaleFactor,
-                                               inputTarget.displayOrientation,
-                                               inputTarget.displaySize);
+                                               inputTarget.displayTransform,
+                                               inputTarget.globalScaleFactor);
     }
 
     ALOG_ASSERT(eventEntry->type == EventEntry::Type::MOTION);
@@ -384,7 +410,7 @@
                                           motionEntry.yPrecision, motionEntry.xCursorPosition,
                                           motionEntry.yCursorPosition, motionEntry.downTime,
                                           motionEntry.pointerCount, motionEntry.pointerProperties,
-                                          pointerCoords.data(), 0 /* xOffset */, 0 /* yOffset */);
+                                          pointerCoords.data());
 
     if (motionEntry.injectionState) {
         combinedMotionEntry->injectionState = motionEntry.injectionState;
@@ -393,27 +419,13 @@
 
     std::unique_ptr<DispatchEntry> dispatchEntry =
             std::make_unique<DispatchEntry>(std::move(combinedMotionEntry), inputTargetFlags,
-                                            firstPointerTransform, inputTarget.globalScaleFactor,
-                                            inputTarget.displayOrientation,
-                                            inputTarget.displaySize);
+                                            firstPointerTransform, inputTarget.displayTransform,
+                                            inputTarget.globalScaleFactor);
     return dispatchEntry;
 }
 
-static void addGestureMonitors(const std::vector<Monitor>& monitors,
-                               std::vector<TouchedMonitor>& outTouchedMonitors, float xOffset = 0,
-                               float yOffset = 0) {
-    if (monitors.empty()) {
-        return;
-    }
-    outTouchedMonitors.reserve(monitors.size() + outTouchedMonitors.size());
-    for (const Monitor& monitor : monitors) {
-        outTouchedMonitors.emplace_back(monitor, xOffset, yOffset);
-    }
-}
-
-static status_t openInputChannelPair(const std::string& name,
-                                     std::shared_ptr<InputChannel>& serverChannel,
-                                     std::unique_ptr<InputChannel>& clientChannel) {
+status_t openInputChannelPair(const std::string& name, std::shared_ptr<InputChannel>& serverChannel,
+                              std::unique_ptr<InputChannel>& clientChannel) {
     std::unique_ptr<InputChannel> uniqueServerChannel;
     status_t result = InputChannel::openInputChannelPair(name, uniqueServerChannel, clientChannel);
 
@@ -422,7 +434,7 @@
 }
 
 template <typename T>
-static bool sharedPointersEqual(const std::shared_ptr<T>& lhs, const std::shared_ptr<T>& rhs) {
+bool sharedPointersEqual(const std::shared_ptr<T>& lhs, const std::shared_ptr<T>& rhs) {
     if (lhs == nullptr && rhs == nullptr) {
         return true;
     }
@@ -432,16 +444,7 @@
     return *lhs == *rhs;
 }
 
-static sp<IPlatformCompatNative> getCompatService() {
-    sp<IBinder> service(defaultServiceManager()->getService(String16("platform_compat_native")));
-    if (service == nullptr) {
-        ALOGE("Failed to link to compat service");
-        return nullptr;
-    }
-    return interface_cast<IPlatformCompatNative>(service);
-}
-
-static KeyEvent createKeyEvent(const KeyEntry& entry) {
+KeyEvent createKeyEvent(const KeyEntry& entry) {
     KeyEvent event;
     event.initialize(entry.id, entry.deviceId, entry.source, entry.displayId, INVALID_HMAC,
                      entry.action, entry.flags, entry.keyCode, entry.scanCode, entry.metaState,
@@ -449,21 +452,7 @@
     return event;
 }
 
-static std::optional<int32_t> findMonitorPidByToken(
-        const std::unordered_map<int32_t, std::vector<Monitor>>& monitorsByDisplay,
-        const sp<IBinder>& token) {
-    for (const auto& it : monitorsByDisplay) {
-        const std::vector<Monitor>& monitors = it.second;
-        for (const Monitor& monitor : monitors) {
-            if (monitor.inputChannel->getConnectionToken() == token) {
-                return monitor.pid;
-            }
-        }
-    }
-    return std::nullopt;
-}
-
-static bool shouldReportMetricsForConnection(const Connection& connection) {
+bool shouldReportMetricsForConnection(const Connection& connection) {
     // Do not keep track of gesture monitors. They receive every event and would disproportionately
     // affect the statistics.
     if (connection.monitor) {
@@ -476,8 +465,7 @@
     return true;
 }
 
-static bool shouldReportFinishedEvent(const DispatchEntry& dispatchEntry,
-                                      const Connection& connection) {
+bool shouldReportFinishedEvent(const DispatchEntry& dispatchEntry, const Connection& connection) {
     const EventEntry& eventEntry = *dispatchEntry.eventEntry;
     const int32_t& inputEventId = eventEntry.id;
     if (inputEventId != dispatchEntry.resolvedEventId) {
@@ -513,9 +501,81 @@
     return true;
 }
 
+/**
+ * Connection is responsive if it has no events in the waitQueue that are older than the
+ * current time.
+ */
+bool isConnectionResponsive(const Connection& connection) {
+    const nsecs_t currentTime = now();
+    for (const DispatchEntry* entry : connection.waitQueue) {
+        if (entry->timeoutTime < currentTime) {
+            return false;
+        }
+    }
+    return true;
+}
+
+// Returns true if the event type passed as argument represents a user activity.
+bool isUserActivityEvent(const EventEntry& eventEntry) {
+    switch (eventEntry.type) {
+        case EventEntry::Type::FOCUS:
+        case EventEntry::Type::POINTER_CAPTURE_CHANGED:
+        case EventEntry::Type::DRAG:
+        case EventEntry::Type::TOUCH_MODE_CHANGED:
+        case EventEntry::Type::SENSOR:
+        case EventEntry::Type::CONFIGURATION_CHANGED:
+            return false;
+        case EventEntry::Type::DEVICE_RESET:
+        case EventEntry::Type::KEY:
+        case EventEntry::Type::MOTION:
+            return true;
+    }
+}
+
+// Returns true if the given window can accept pointer events at the given display location.
+bool windowAcceptsTouchAt(const WindowInfo& windowInfo, int32_t displayId, int32_t x, int32_t y,
+                          bool isStylus) {
+    const auto inputConfig = windowInfo.inputConfig;
+    if (windowInfo.displayId != displayId ||
+        inputConfig.test(WindowInfo::InputConfig::NOT_VISIBLE)) {
+        return false;
+    }
+    const bool windowCanInterceptTouch = isStylus && windowInfo.interceptsStylus();
+    if (inputConfig.test(WindowInfo::InputConfig::NOT_TOUCHABLE) && !windowCanInterceptTouch) {
+        return false;
+    }
+    if (!windowInfo.touchableRegionContainsPoint(x, y)) {
+        return false;
+    }
+    return true;
+}
+
+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);
+}
+
+// Determines if the given window can be targeted as InputTarget::FLAG_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.
+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.
+    return !info.inputConfig.test(gui::WindowInfo::InputConfig::NOT_TOUCHABLE) && !info.isSpy();
+}
+
+} // namespace
+
 // --- InputDispatcher ---
 
 InputDispatcher::InputDispatcher(const sp<InputDispatcherPolicyInterface>& policy)
+      : InputDispatcher(policy, STALE_EVENT_TIMEOUT) {}
+
+InputDispatcher::InputDispatcher(const sp<InputDispatcherPolicyInterface>& policy,
+                                 std::chrono::nanoseconds staleEventTimeout)
       : mPolicy(policy),
         mPendingEvent(nullptr),
         mLastDropReason(DropReason::NOT_DROPPED),
@@ -523,46 +583,46 @@
         mAppSwitchSawKeyDown(false),
         mAppSwitchDueTime(LONG_LONG_MAX),
         mNextUnblockedEvent(nullptr),
+        mMonitorDispatchingTimeout(DEFAULT_INPUT_DISPATCHING_TIMEOUT),
         mDispatchEnabled(false),
         mDispatchFrozen(false),
         mInputFilterEnabled(false),
         // mInTouchMode will be initialized by the WindowManager to the default device config.
         // To avoid leaking stack in case that call never comes, and for tests,
         // initialize it here anyways.
-        mInTouchMode(true),
+        mInTouchMode(kDefaultInTouchMode),
         mMaximumObscuringOpacityForTouch(1.0f),
         mFocusedDisplayId(ADISPLAY_ID_DEFAULT),
         mWindowTokenWithPointerCapture(nullptr),
+        mStaleEventTimeout(staleEventTimeout),
         mLatencyAggregator(),
-        mLatencyTracker(&mLatencyAggregator),
-        mCompatService(getCompatService()) {
+        mLatencyTracker(&mLatencyAggregator) {
     mLooper = new Looper(false);
     mReporter = createInputReporter();
 
+    mWindowInfoListener = new DispatcherWindowListener(*this);
+    SurfaceComposerClient::getDefault()->addWindowInfosListener(mWindowInfoListener);
+
     mKeyRepeatState.lastKeyEntry = nullptr;
 
     policy->getDispatcherConfiguration(&mConfig);
 }
 
 InputDispatcher::~InputDispatcher() {
-    { // acquire lock
-        std::scoped_lock _l(mLock);
+    std::scoped_lock _l(mLock);
 
-        resetKeyRepeatLocked();
-        releasePendingEventLocked();
-        drainInboundQueueLocked();
-    }
+    resetKeyRepeatLocked();
+    releasePendingEventLocked();
+    drainInboundQueueLocked();
+    mCommandQueue.clear();
 
     while (!mConnectionsByToken.empty()) {
         sp<Connection> connection = mConnectionsByToken.begin()->second;
-        removeInputChannel(connection->inputChannel->getConnectionToken());
+        removeInputChannelLocked(connection->inputChannel->getConnectionToken(),
+                                 false /* notify */);
     }
 }
 
-void InputDispatcher::onFirstRef() {
-    SurfaceComposerClient::getDefault()->addWindowInfosListener(this);
-}
-
 status_t InputDispatcher::start() {
     if (mThread) {
         return ALREADY_EXISTS;
@@ -595,7 +655,7 @@
 
         // Run all pending commands if there are any.
         // If any commands were run then force the next poll to wake up immediately.
-        if (runCommandsLockedInterruptible()) {
+        if (runCommandsLockedInterruptable()) {
             nextWakeupTime = LONG_LONG_MIN;
         }
 
@@ -684,8 +744,13 @@
     return LONG_LONG_MIN;
 }
 
-std::chrono::nanoseconds InputDispatcher::getDispatchingTimeoutLocked(const sp<IBinder>& token) {
-    sp<WindowInfoHandle> window = getWindowHandleLocked(token);
+std::chrono::nanoseconds InputDispatcher::getDispatchingTimeoutLocked(
+        const sp<Connection>& connection) {
+    if (connection->monitor) {
+        return mMonitorDispatchingTimeout;
+    }
+    const sp<WindowInfoHandle> window =
+            getWindowHandleLocked(connection->inputChannel->getConnectionToken());
     if (window != nullptr) {
         return window->getDispatchingTimeout(DEFAULT_INPUT_DISPATCHING_TIMEOUT);
     }
@@ -798,6 +863,14 @@
             break;
         }
 
+        case EventEntry::Type::TOUCH_MODE_CHANGED: {
+            const auto typedEntry = std::static_pointer_cast<TouchModeEntry>(mPendingEvent);
+            dispatchTouchModeChangeLocked(currentTime, typedEntry);
+            done = true;
+            dropReason = DropReason::NOT_DROPPED; // touch mode events are never dropped
+            break;
+        }
+
         case EventEntry::Type::POINTER_CAPTURE_CHANGED: {
             const auto typedEntry =
                     std::static_pointer_cast<PointerCaptureChangedEntry>(mPendingEvent);
@@ -879,13 +952,17 @@
     }
 }
 
+bool InputDispatcher::isStaleEvent(nsecs_t currentTime, const EventEntry& entry) {
+    return std::chrono::nanoseconds(currentTime - entry.eventTime) >= mStaleEventTimeout;
+}
+
 /**
  * Return true if the events preceding this incoming motion event should be dropped
  * Return false otherwise (the default behaviour)
  */
 bool InputDispatcher::shouldPruneInboundQueueLocked(const MotionEntry& motionEntry) {
     const bool isPointerDownEvent = motionEntry.action == AMOTION_EVENT_ACTION_DOWN &&
-            (motionEntry.source & AINPUT_SOURCE_CLASS_POINTER);
+            isFromSource(motionEntry.source, AINPUT_SOURCE_CLASS_POINTER);
 
     // Optimize case where the current application is unresponsive and the user
     // decides to touch a window in a different application.
@@ -897,8 +974,10 @@
                 motionEntry.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_X));
         int32_t y = static_cast<int32_t>(
                 motionEntry.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_Y));
+
+        const bool isStylus = isPointerFromStylus(motionEntry, 0 /*pointerIndex*/);
         sp<WindowInfoHandle> touchedWindowHandle =
-                findTouchedWindowAtLocked(displayId, x, y, nullptr);
+                findTouchedWindowAtLocked(displayId, x, y, nullptr, isStylus);
         if (touchedWindowHandle != nullptr &&
             touchedWindowHandle->getApplicationToken() !=
                     mAwaitedFocusedApplication->getApplicationToken()) {
@@ -909,17 +988,16 @@
             return true;
         }
 
-        // Alternatively, maybe there's a gesture monitor that could handle this event
-        std::vector<TouchedMonitor> gestureMonitors =
-                findTouchedGestureMonitorsLocked(displayId, {});
-        for (TouchedMonitor& gestureMonitor : gestureMonitors) {
-            sp<Connection> connection =
-                    getConnectionLocked(gestureMonitor.monitor.inputChannel->getConnectionToken());
+        // Alternatively, maybe there's a spy window that could handle this event.
+        const std::vector<sp<WindowInfoHandle>> touchedSpies =
+                findTouchedSpyWindowsAtLocked(displayId, x, y, isStylus);
+        for (const auto& windowHandle : touchedSpies) {
+            const sp<Connection> connection = getConnectionLocked(windowHandle->getToken());
             if (connection != nullptr && connection->responsive) {
-                // This monitor could take more input. Drop all events preceding this
-                // event, so that gesture monitor could get a chance to receive the stream
+                // This spy window could take more input. Drop all events preceding this
+                // event, so that the spy window can get a chance to receive the stream.
                 ALOGW("Pruning the input queue because %s is unresponsive, but we have a "
-                      "responsive gesture monitor that may handle the event",
+                      "responsive spy window that may handle the event.",
                       mAwaitedFocusedApplication->getName().c_str());
                 return true;
             }
@@ -956,15 +1034,30 @@
                     mAppSwitchSawKeyDown = true;
                 } else if (keyEntry.action == AKEY_EVENT_ACTION_UP) {
                     if (mAppSwitchSawKeyDown) {
-#if DEBUG_APP_SWITCH
-                        ALOGD("App switch is pending!");
-#endif
+                        if (DEBUG_APP_SWITCH) {
+                            ALOGD("App switch is pending!");
+                        }
                         mAppSwitchDueTime = keyEntry.eventTime + APP_SWITCH_TIMEOUT;
                         mAppSwitchSawKeyDown = false;
                         needWake = true;
                     }
                 }
             }
+
+            // If a new up event comes in, and the pending event with same key code has been asked
+            // to try again later because of the policy. We have to reset the intercept key wake up
+            // time for it may have been handled in the policy and could be dropped.
+            if (keyEntry.action == AKEY_EVENT_ACTION_UP && mPendingEvent &&
+                mPendingEvent->type == EventEntry::Type::KEY) {
+                KeyEntry& pendingKey = static_cast<KeyEntry&>(*mPendingEvent);
+                if (pendingKey.keyCode == keyEntry.keyCode &&
+                    pendingKey.interceptKeyResult ==
+                            KeyEntry::INTERCEPT_KEY_RESULT_TRY_AGAIN_LATER) {
+                    pendingKey.interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_UNKNOWN;
+                    pendingKey.interceptKeyWakeupTime = 0;
+                    needWake = true;
+                }
+            }
             break;
         }
 
@@ -979,6 +1072,7 @@
             LOG_ALWAYS_FATAL("Focus events should be inserted using enqueueFocusEventLocked");
             break;
         }
+        case EventEntry::Type::TOUCH_MODE_CHANGED:
         case EventEntry::Type::CONFIGURATION_CHANGED:
         case EventEntry::Type::DEVICE_RESET:
         case EventEntry::Type::SENSOR:
@@ -1004,76 +1098,60 @@
 
 sp<WindowInfoHandle> InputDispatcher::findTouchedWindowAtLocked(int32_t displayId, int32_t x,
                                                                 int32_t y, TouchState* touchState,
+                                                                bool isStylus,
                                                                 bool addOutsideTargets,
-                                                                bool addPortalWindows,
                                                                 bool ignoreDragWindow) {
-    if ((addPortalWindows || addOutsideTargets) && touchState == nullptr) {
-        LOG_ALWAYS_FATAL(
-                "Must provide a valid touch state if adding portal windows or outside targets");
+    if (addOutsideTargets && touchState == nullptr) {
+        LOG_ALWAYS_FATAL("Must provide a valid touch state if adding outside targets");
     }
     // Traverse windows from front to back to find touched window.
-    const std::vector<sp<WindowInfoHandle>>& windowHandles = getWindowHandlesLocked(displayId);
+    const auto& windowHandles = getWindowHandlesLocked(displayId);
     for (const sp<WindowInfoHandle>& windowHandle : windowHandles) {
         if (ignoreDragWindow && haveSameToken(windowHandle, mDragState->dragWindow)) {
             continue;
         }
-        const WindowInfo* windowInfo = windowHandle->getInfo();
-        if (windowInfo->displayId == displayId) {
-            auto flags = windowInfo->flags;
 
-            if (windowInfo->visible) {
-                if (!flags.test(WindowInfo::Flag::NOT_TOUCHABLE)) {
-                    bool isTouchModal = !flags.test(WindowInfo::Flag::NOT_FOCUSABLE) &&
-                            !flags.test(WindowInfo::Flag::NOT_TOUCH_MODAL);
-                    if (isTouchModal || windowInfo->touchableRegionContainsPoint(x, y)) {
-                        int32_t portalToDisplayId = windowInfo->portalToDisplayId;
-                        if (portalToDisplayId != ADISPLAY_ID_NONE &&
-                            portalToDisplayId != displayId) {
-                            if (addPortalWindows) {
-                                // For the monitoring channels of the display.
-                                touchState->addPortalWindow(windowHandle);
-                            }
-                            return findTouchedWindowAtLocked(portalToDisplayId, x, y, touchState,
-                                                             addOutsideTargets, addPortalWindows);
-                        }
-                        // Found window.
-                        return windowHandle;
-                    }
-                }
+        const WindowInfo& info = *windowHandle->getInfo();
+        if (!info.isSpy() && windowAcceptsTouchAt(info, displayId, x, y, isStylus)) {
+            return windowHandle;
+        }
 
-                if (addOutsideTargets && flags.test(WindowInfo::Flag::WATCH_OUTSIDE_TOUCH)) {
-                    touchState->addOrUpdateWindow(windowHandle,
-                                                  InputTarget::FLAG_DISPATCH_AS_OUTSIDE,
-                                                  BitSet32(0));
-                }
-            }
+        if (addOutsideTargets &&
+            info.inputConfig.test(WindowInfo::InputConfig::WATCH_OUTSIDE_TOUCH)) {
+            touchState->addOrUpdateWindow(windowHandle, InputTarget::FLAG_DISPATCH_AS_OUTSIDE,
+                                          BitSet32(0));
         }
     }
     return nullptr;
 }
 
-std::vector<TouchedMonitor> InputDispatcher::findTouchedGestureMonitorsLocked(
-        int32_t displayId, const std::vector<sp<WindowInfoHandle>>& portalWindows) const {
-    std::vector<TouchedMonitor> touchedMonitors;
+std::vector<sp<WindowInfoHandle>> InputDispatcher::findTouchedSpyWindowsAtLocked(
+        int32_t displayId, int32_t x, int32_t y, bool isStylus) const {
+    // Traverse windows from front to back and gather the touched spy windows.
+    std::vector<sp<WindowInfoHandle>> spyWindows;
+    const auto& windowHandles = getWindowHandlesLocked(displayId);
+    for (const sp<WindowInfoHandle>& windowHandle : windowHandles) {
+        const WindowInfo& info = *windowHandle->getInfo();
 
-    std::vector<Monitor> monitors = getValueByKey(mGestureMonitorsByDisplay, displayId);
-    addGestureMonitors(monitors, touchedMonitors);
-    for (const sp<WindowInfoHandle>& portalWindow : portalWindows) {
-        const WindowInfo* windowInfo = portalWindow->getInfo();
-        monitors = getValueByKey(mGestureMonitorsByDisplay, windowInfo->portalToDisplayId);
-        addGestureMonitors(monitors, touchedMonitors, -windowInfo->frameLeft,
-                           -windowInfo->frameTop);
+        if (!windowAcceptsTouchAt(info, displayId, x, y, isStylus)) {
+            continue;
+        }
+        if (!info.isSpy()) {
+            // The first touched non-spy window was found, so return the spy windows touched so far.
+            return spyWindows;
+        }
+        spyWindows.push_back(windowHandle);
     }
-    return touchedMonitors;
+    return spyWindows;
 }
 
 void InputDispatcher::dropInboundEventLocked(const EventEntry& entry, DropReason dropReason) {
     const char* reason;
     switch (dropReason) {
         case DropReason::POLICY:
-#if DEBUG_INBOUND_EVENT_DETAILS
-            ALOGD("Dropped event because policy consumed it.");
-#endif
+            if (DEBUG_INBOUND_EVENT_DETAILS) {
+                ALOGD("Dropped event because policy consumed it.");
+            }
             reason = "inbound event was dropped because the policy consumed it";
             break;
         case DropReason::DISABLED:
@@ -1131,9 +1209,10 @@
             break;
         }
         case EventEntry::Type::FOCUS:
+        case EventEntry::Type::TOUCH_MODE_CHANGED:
         case EventEntry::Type::CONFIGURATION_CHANGED:
         case EventEntry::Type::DEVICE_RESET: {
-            LOG_ALWAYS_FATAL("Should not drop %s events", NamedEnum::string(entry.type).c_str());
+            LOG_ALWAYS_FATAL("Should not drop %s events", ftl::enum_string(entry.type).c_str());
             break;
         }
     }
@@ -1157,37 +1236,35 @@
 void InputDispatcher::resetPendingAppSwitchLocked(bool handled) {
     mAppSwitchDueTime = LONG_LONG_MAX;
 
-#if DEBUG_APP_SWITCH
-    if (handled) {
-        ALOGD("App switch has arrived.");
-    } else {
-        ALOGD("App switch was abandoned.");
+    if (DEBUG_APP_SWITCH) {
+        if (handled) {
+            ALOGD("App switch has arrived.");
+        } else {
+            ALOGD("App switch was abandoned.");
+        }
     }
-#endif
 }
 
 bool InputDispatcher::haveCommandsLocked() const {
     return !mCommandQueue.empty();
 }
 
-bool InputDispatcher::runCommandsLockedInterruptible() {
+bool InputDispatcher::runCommandsLockedInterruptable() {
     if (mCommandQueue.empty()) {
         return false;
     }
 
     do {
-        std::unique_ptr<CommandEntry> commandEntry = std::move(mCommandQueue.front());
+        auto command = std::move(mCommandQueue.front());
         mCommandQueue.pop_front();
-        Command command = commandEntry->command;
-        command(*this, commandEntry.get()); // commands are implicitly 'LockedInterruptible'
-
-        commandEntry->connection.clear();
+        // Commands are run with the lock held, but may release and re-acquire the lock from within.
+        command();
     } while (!mCommandQueue.empty());
     return true;
 }
 
-void InputDispatcher::postCommandLocked(std::unique_ptr<CommandEntry> commandEntry) {
-    mCommandQueue.push_back(std::move(commandEntry));
+void InputDispatcher::postCommandLocked(Command&& command) {
+    mCommandQueue.push_back(command);
 }
 
 void InputDispatcher::drainInboundQueueLocked() {
@@ -1209,9 +1286,9 @@
 void InputDispatcher::releaseInboundEventLocked(std::shared_ptr<EventEntry> entry) {
     InjectionState* injectionState = entry->injectionState;
     if (injectionState && injectionState->injectionResult == InputEventInjectionResult::PENDING) {
-#if DEBUG_DISPATCH_CYCLE
-        ALOGD("Injected inbound event was dropped.");
-#endif
+        if (DEBUG_DISPATCH_CYCLE) {
+            ALOGD("Injected inbound event was dropped.");
+        }
         setInjectionResult(*entry, InputEventInjectionResult::FAILED);
     }
     if (entry == mNextUnblockedEvent) {
@@ -1246,27 +1323,28 @@
 
 bool InputDispatcher::dispatchConfigurationChangedLocked(nsecs_t currentTime,
                                                          const ConfigurationChangedEntry& entry) {
-#if DEBUG_OUTBOUND_EVENT_DETAILS
-    ALOGD("dispatchConfigurationChanged - eventTime=%" PRId64, entry.eventTime);
-#endif
+    if (DEBUG_OUTBOUND_EVENT_DETAILS) {
+        ALOGD("dispatchConfigurationChanged - eventTime=%" PRId64, entry.eventTime);
+    }
 
     // Reset key repeating in case a keyboard device was added or removed or something.
     resetKeyRepeatLocked();
 
     // Enqueue a command to run outside the lock to tell the policy that the configuration changed.
-    std::unique_ptr<CommandEntry> commandEntry = std::make_unique<CommandEntry>(
-            &InputDispatcher::doNotifyConfigurationChangedLockedInterruptible);
-    commandEntry->eventTime = entry.eventTime;
-    postCommandLocked(std::move(commandEntry));
+    auto command = [this, eventTime = entry.eventTime]() REQUIRES(mLock) {
+        scoped_unlock unlock(mLock);
+        mPolicy->notifyConfigurationChanged(eventTime);
+    };
+    postCommandLocked(std::move(command));
     return true;
 }
 
 bool InputDispatcher::dispatchDeviceResetLocked(nsecs_t currentTime,
                                                 const DeviceResetEntry& entry) {
-#if DEBUG_OUTBOUND_EVENT_DETAILS
-    ALOGD("dispatchDeviceReset - eventTime=%" PRId64 ", deviceId=%d", entry.eventTime,
-          entry.deviceId);
-#endif
+    if (DEBUG_OUTBOUND_EVENT_DETAILS) {
+        ALOGD("dispatchDeviceReset - eventTime=%" PRId64 ", deviceId=%d", entry.eventTime,
+              entry.deviceId);
+    }
 
     // Reset key repeating in case a keyboard device was disabled or enabled.
     if (mKeyRepeatState.lastKeyEntry && mKeyRepeatState.lastKeyEntry->deviceId == entry.deviceId) {
@@ -1390,6 +1468,43 @@
     dropReason = DropReason::NOT_DROPPED;
 }
 
+void InputDispatcher::dispatchTouchModeChangeLocked(nsecs_t currentTime,
+                                                    const std::shared_ptr<TouchModeEntry>& entry) {
+    const std::vector<sp<WindowInfoHandle>>& windowHandles =
+            getWindowHandlesLocked(mFocusedDisplayId);
+    if (windowHandles.empty()) {
+        return;
+    }
+    const std::vector<InputTarget> inputTargets =
+            getInputTargetsFromWindowHandlesLocked(windowHandles);
+    if (inputTargets.empty()) {
+        return;
+    }
+    entry->dispatchInProgress = true;
+    dispatchEventLocked(currentTime, entry, inputTargets);
+}
+
+std::vector<InputTarget> InputDispatcher::getInputTargetsFromWindowHandlesLocked(
+        const std::vector<sp<WindowInfoHandle>>& windowHandles) const {
+    std::vector<InputTarget> inputTargets;
+    for (const sp<WindowInfoHandle>& handle : windowHandles) {
+        // TODO(b/193718270): Due to performance concerns, consider notifying visible windows only.
+        const sp<IBinder>& token = handle->getToken();
+        if (token == nullptr) {
+            continue;
+        }
+        std::shared_ptr<InputChannel> channel = getInputChannelLocked(token);
+        if (channel == nullptr) {
+            continue; // Window has gone away
+        }
+        InputTarget target;
+        target.inputChannel = channel;
+        target.flags = InputTarget::FLAG_DISPATCH_AS_IS;
+        inputTargets.push_back(target);
+    }
+    return inputTargets;
+}
+
 bool InputDispatcher::dispatchKeyLocked(nsecs_t currentTime, std::shared_ptr<KeyEntry> entry,
                                         DropReason* dropReason, nsecs_t* nextWakeupTime) {
     // Preprocessing.
@@ -1421,9 +1536,9 @@
         } else if (entry->action == AKEY_EVENT_ACTION_UP && mKeyRepeatState.lastKeyEntry &&
                    mKeyRepeatState.lastKeyEntry->deviceId != entry->deviceId) {
             // The key on device 'deviceId' is still down, do not stop key repeat
-#if DEBUG_INBOUND_EVENT_DETAILS
-            ALOGD("deviceId=%d got KEY_UP as stale", entry->deviceId);
-#endif
+            if (DEBUG_INBOUND_EVENT_DETAILS) {
+                ALOGD("deviceId=%d got KEY_UP as stale", entry->deviceId);
+            }
         } else if (!entry->syntheticRepeat) {
             resetKeyRepeatLocked();
         }
@@ -1454,13 +1569,13 @@
     // Give the policy a chance to intercept the key.
     if (entry->interceptKeyResult == KeyEntry::INTERCEPT_KEY_RESULT_UNKNOWN) {
         if (entry->policyFlags & POLICY_FLAG_PASS_TO_USER) {
-            std::unique_ptr<CommandEntry> commandEntry = std::make_unique<CommandEntry>(
-                    &InputDispatcher::doInterceptKeyBeforeDispatchingLockedInterruptible);
             sp<IBinder> focusedWindowToken =
                     mFocusResolver.getFocusedWindowToken(getTargetDisplayId(*entry));
-            commandEntry->connectionToken = focusedWindowToken;
-            commandEntry->keyEntry = entry;
-            postCommandLocked(std::move(commandEntry));
+
+            auto command = [this, focusedWindowToken, entry]() REQUIRES(mLock) {
+                doInterceptKeyBeforeDispatchingCommand(focusedWindowToken, *entry);
+            };
+            postCommandLocked(std::move(command));
             return false; // wait for the command to run
         } else {
             entry->interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_CONTINUE;
@@ -1502,47 +1617,42 @@
 }
 
 void InputDispatcher::logOutboundKeyDetails(const char* prefix, const KeyEntry& entry) {
-#if DEBUG_OUTBOUND_EVENT_DETAILS
-    ALOGD("%seventTime=%" PRId64 ", deviceId=%d, source=0x%x, displayId=%" PRId32 ", "
-          "policyFlags=0x%x, action=0x%x, flags=0x%x, keyCode=0x%x, scanCode=0x%x, "
-          "metaState=0x%x, repeatCount=%d, downTime=%" PRId64,
-          prefix, entry.eventTime, entry.deviceId, entry.source, entry.displayId, entry.policyFlags,
-          entry.action, entry.flags, entry.keyCode, entry.scanCode, entry.metaState,
-          entry.repeatCount, entry.downTime);
-#endif
-}
-
-void InputDispatcher::doNotifySensorLockedInterruptible(CommandEntry* commandEntry) {
-    mLock.unlock();
-
-    const std::shared_ptr<SensorEntry>& entry = commandEntry->sensorEntry;
-    if (entry->accuracyChanged) {
-        mPolicy->notifySensorAccuracy(entry->deviceId, entry->sensorType, entry->accuracy);
+    if (DEBUG_OUTBOUND_EVENT_DETAILS) {
+        ALOGD("%seventTime=%" PRId64 ", deviceId=%d, source=0x%x, displayId=%" PRId32 ", "
+              "policyFlags=0x%x, action=0x%x, flags=0x%x, keyCode=0x%x, scanCode=0x%x, "
+              "metaState=0x%x, repeatCount=%d, downTime=%" PRId64,
+              prefix, entry.eventTime, entry.deviceId, entry.source, entry.displayId,
+              entry.policyFlags, entry.action, entry.flags, entry.keyCode, entry.scanCode,
+              entry.metaState, entry.repeatCount, entry.downTime);
     }
-    mPolicy->notifySensorEvent(entry->deviceId, entry->sensorType, entry->accuracy,
-                               entry->hwTimestamp, entry->values);
-    mLock.lock();
 }
 
-void InputDispatcher::dispatchSensorLocked(nsecs_t currentTime, std::shared_ptr<SensorEntry> entry,
+void InputDispatcher::dispatchSensorLocked(nsecs_t currentTime,
+                                           const std::shared_ptr<SensorEntry>& entry,
                                            DropReason* dropReason, nsecs_t* nextWakeupTime) {
-#if DEBUG_OUTBOUND_EVENT_DETAILS
-    ALOGD("notifySensorEvent eventTime=%" PRId64 ", hwTimestamp=%" PRId64 ", deviceId=%d, "
-          "source=0x%x, sensorType=%s",
-          entry->eventTime, entry->hwTimestamp, entry->deviceId, entry->source,
-          NamedEnum::string(entry->sensorType).c_str());
-#endif
-    std::unique_ptr<CommandEntry> commandEntry =
-            std::make_unique<CommandEntry>(&InputDispatcher::doNotifySensorLockedInterruptible);
-    commandEntry->sensorEntry = entry;
-    postCommandLocked(std::move(commandEntry));
+    if (DEBUG_OUTBOUND_EVENT_DETAILS) {
+        ALOGD("notifySensorEvent eventTime=%" PRId64 ", hwTimestamp=%" PRId64 ", deviceId=%d, "
+              "source=0x%x, sensorType=%s",
+              entry->eventTime, entry->hwTimestamp, entry->deviceId, entry->source,
+              ftl::enum_string(entry->sensorType).c_str());
+    }
+    auto command = [this, entry]() REQUIRES(mLock) {
+        scoped_unlock unlock(mLock);
+
+        if (entry->accuracyChanged) {
+            mPolicy->notifySensorAccuracy(entry->deviceId, entry->sensorType, entry->accuracy);
+        }
+        mPolicy->notifySensorEvent(entry->deviceId, entry->sensorType, entry->accuracy,
+                                   entry->hwTimestamp, entry->values);
+    };
+    postCommandLocked(std::move(command));
 }
 
 bool InputDispatcher::flushSensor(int deviceId, InputDeviceSensorType sensorType) {
-#if DEBUG_OUTBOUND_EVENT_DETAILS
-    ALOGD("flushSensor deviceId=%d, sensorType=%s", deviceId,
-          NamedEnum::string(sensorType).c_str());
-#endif
+    if (DEBUG_OUTBOUND_EVENT_DETAILS) {
+        ALOGD("flushSensor deviceId=%d, sensorType=%s", deviceId,
+              ftl::enum_string(sensorType).c_str());
+    }
     { // acquire lock
         std::scoped_lock _l(mLock);
 
@@ -1575,7 +1685,7 @@
         return true;
     }
 
-    bool isPointerEvent = entry->source & AINPUT_SOURCE_CLASS_POINTER;
+    const bool isPointerEvent = isFromSource(entry->source, AINPUT_SOURCE_CLASS_POINTER);
 
     // Identify targets.
     std::vector<InputTarget> inputTargets;
@@ -1613,23 +1723,6 @@
     // Add monitor channels from event's or focused display.
     addGlobalMonitoringTargetsLocked(inputTargets, getTargetDisplayId(*entry));
 
-    if (isPointerEvent) {
-        std::unordered_map<int32_t, TouchState>::iterator it =
-                mTouchStatesByDisplay.find(entry->displayId);
-        if (it != mTouchStatesByDisplay.end()) {
-            const TouchState& state = it->second;
-            if (!state.portalWindows.empty()) {
-                // The event has gone through these portal windows, so we add monitoring targets of
-                // the corresponding displays as well.
-                for (size_t i = 0; i < state.portalWindows.size(); i++) {
-                    const WindowInfo* windowInfo = state.portalWindows[i]->getInfo();
-                    addGlobalMonitoringTargetsLocked(inputTargets, windowInfo->portalToDisplayId,
-                                                     -windowInfo->frameLeft, -windowInfo->frameTop);
-                }
-            }
-        }
-    }
-
     // Dispatch the motion.
     if (conflictingPointerActions) {
         CancelationOptions options(CancelationOptions::CANCEL_POINTER_EVENTS,
@@ -1670,42 +1763,43 @@
 }
 
 void InputDispatcher::logOutboundMotionDetails(const char* prefix, const MotionEntry& entry) {
-#if DEBUG_OUTBOUND_EVENT_DETAILS
-    ALOGD("%seventTime=%" 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,"
-          "edgeFlags=0x%x, xPrecision=%f, yPrecision=%f, downTime=%" PRId64,
-          prefix, entry.eventTime, entry.deviceId, entry.source, entry.displayId, entry.policyFlags,
-          entry.action, entry.actionButton, entry.flags, entry.metaState, entry.buttonState,
-          entry.edgeFlags, entry.xPrecision, entry.yPrecision, entry.downTime);
+    if (DEBUG_OUTBOUND_EVENT_DETAILS) {
+        ALOGD("%seventTime=%" PRId64 ", deviceId=%d, source=0x%x, 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);
 
-    for (uint32_t i = 0; i < entry.pointerCount; i++) {
-        ALOGD("  Pointer %d: id=%d, toolType=%d, "
-              "x=%f, y=%f, pressure=%f, size=%f, "
-              "touchMajor=%f, touchMinor=%f, toolMajor=%f, toolMinor=%f, "
-              "orientation=%f",
-              i, entry.pointerProperties[i].id, entry.pointerProperties[i].toolType,
-              entry.pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_X),
-              entry.pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_Y),
-              entry.pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_PRESSURE),
-              entry.pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_SIZE),
-              entry.pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR),
-              entry.pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR),
-              entry.pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR),
-              entry.pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR),
-              entry.pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_ORIENTATION));
+        for (uint32_t i = 0; i < entry.pointerCount; i++) {
+            ALOGD("  Pointer %d: id=%d, toolType=%d, "
+                  "x=%f, y=%f, pressure=%f, size=%f, "
+                  "touchMajor=%f, touchMinor=%f, toolMajor=%f, toolMinor=%f, "
+                  "orientation=%f",
+                  i, entry.pointerProperties[i].id, entry.pointerProperties[i].toolType,
+                  entry.pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_X),
+                  entry.pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_Y),
+                  entry.pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_PRESSURE),
+                  entry.pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_SIZE),
+                  entry.pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR),
+                  entry.pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR),
+                  entry.pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR),
+                  entry.pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR),
+                  entry.pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_ORIENTATION));
+        }
     }
-#endif
 }
 
 void InputDispatcher::dispatchEventLocked(nsecs_t currentTime,
                                           std::shared_ptr<EventEntry> eventEntry,
                                           const std::vector<InputTarget>& inputTargets) {
     ATRACE_CALL();
-#if DEBUG_DISPATCH_CYCLE
-    ALOGD("dispatchEventToCurrentInputTargets");
-#endif
+    if (DEBUG_DISPATCH_CYCLE) {
+        ALOGD("dispatchEventToCurrentInputTargets");
+    }
 
     updateInteractionTokensLocked(*eventEntry, inputTargets);
 
@@ -1736,7 +1830,7 @@
     // pile up.
     ALOGW("Canceling events for %s because it is unresponsive",
           connection->inputChannel->getName().c_str());
-    if (connection->status == Connection::STATUS_NORMAL) {
+    if (connection->status == Connection::Status::NORMAL) {
         CancelationOptions options(CancelationOptions::CANCEL_ALL_EVENTS,
                                    "application not responding");
         synthesizeCancelationEventsForConnectionLocked(connection, options);
@@ -1771,13 +1865,14 @@
             displayId = motionEntry.displayId;
             break;
         }
+        case EventEntry::Type::TOUCH_MODE_CHANGED:
         case EventEntry::Type::POINTER_CAPTURE_CHANGED:
         case EventEntry::Type::FOCUS:
         case EventEntry::Type::CONFIGURATION_CHANGED:
         case EventEntry::Type::DEVICE_RESET:
         case EventEntry::Type::SENSOR:
         case EventEntry::Type::DRAG: {
-            ALOGE("%s events do not have a target display", NamedEnum::string(entry.type).c_str());
+            ALOGE("%s events do not have a target display", ftl::enum_string(entry.type).c_str());
             return ADISPLAY_ID_NONE;
         }
     }
@@ -1829,7 +1924,7 @@
     if (focusedWindowHandle == nullptr && focusedApplicationHandle == nullptr) {
         ALOGI("Dropping %s event because there is no focused window or focused application in "
               "display %" PRId32 ".",
-              NamedEnum::string(entry.type).c_str(), displayId);
+              ftl::enum_string(entry.type).c_str(), displayId);
         return InputEventInjectionResult::FAILED;
     }
 
@@ -1859,7 +1954,7 @@
         } else if (currentTime > *mNoFocusedWindowTimeoutTime) {
             // Already raised ANR. Drop the event
             ALOGE("Dropping %s event because there is no focused window",
-                  NamedEnum::string(entry.type).c_str());
+                  ftl::enum_string(entry.type).c_str());
             return InputEventInjectionResult::FAILED;
         } else {
             // Still waiting for the focused window
@@ -1875,7 +1970,8 @@
         return InputEventInjectionResult::PERMISSION_DENIED;
     }
 
-    if (focusedWindowHandle->getInfo()->paused) {
+    if (focusedWindowHandle->getInfo()->inputConfig.test(
+                WindowInfo::InputConfig::PAUSE_DISPATCHING)) {
         ALOGI("Waiting because %s is paused", focusedWindowHandle->getName().c_str());
         return InputEventInjectionResult::PENDING;
     }
@@ -1911,16 +2007,16 @@
  * Given a list of monitors, remove the ones we cannot find a connection for, and the ones
  * that are currently unresponsive.
  */
-std::vector<TouchedMonitor> InputDispatcher::selectResponsiveMonitorsLocked(
-        const std::vector<TouchedMonitor>& monitors) const {
-    std::vector<TouchedMonitor> responsiveMonitors;
+std::vector<Monitor> InputDispatcher::selectResponsiveMonitorsLocked(
+        const std::vector<Monitor>& monitors) const {
+    std::vector<Monitor> responsiveMonitors;
     std::copy_if(monitors.begin(), monitors.end(), std::back_inserter(responsiveMonitors),
-                 [this](const TouchedMonitor& monitor) REQUIRES(mLock) {
-                     sp<Connection> connection = getConnectionLocked(
-                             monitor.monitor.inputChannel->getConnectionToken());
+                 [this](const Monitor& monitor) REQUIRES(mLock) {
+                     sp<Connection> connection =
+                             getConnectionLocked(monitor.inputChannel->getConnectionToken());
                      if (connection == nullptr) {
                          ALOGE("Could not find connection for monitor %s",
-                               monitor.monitor.inputChannel->getName().c_str());
+                               monitor.inputChannel->getName().c_str());
                          return false;
                      }
                      if (!connection->responsive) {
@@ -1945,9 +2041,9 @@
 
     // For security reasons, we defer updating the touch state until we are sure that
     // event injection will be allowed.
-    int32_t displayId = entry.displayId;
-    int32_t action = entry.action;
-    int32_t maskedAction = action & AMOTION_EVENT_ACTION_MASK;
+    const int32_t displayId = entry.displayId;
+    const int32_t action = entry.action;
+    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;
@@ -1960,23 +2056,22 @@
     // If no state for the specified display exists, then our initial state will be empty.
     const TouchState* oldState = nullptr;
     TouchState tempTouchState;
-    std::unordered_map<int32_t, TouchState>::iterator oldStateIt =
-            mTouchStatesByDisplay.find(displayId);
-    if (oldStateIt != mTouchStatesByDisplay.end()) {
-        oldState = &(oldStateIt->second);
-        tempTouchState.copyFrom(*oldState);
+    if (const auto it = mTouchStatesByDisplay.find(displayId); it != mTouchStatesByDisplay.end()) {
+        oldState = &(it->second);
+        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 isHoverAction = (maskedAction == AMOTION_EVENT_ACTION_HOVER_MOVE ||
-                          maskedAction == AMOTION_EVENT_ACTION_HOVER_ENTER ||
-                          maskedAction == AMOTION_EVENT_ACTION_HOVER_EXIT);
-    bool newGesture = (maskedAction == AMOTION_EVENT_ACTION_DOWN ||
-                       maskedAction == AMOTION_EVENT_ACTION_SCROLL || isHoverAction);
-    const bool isFromMouse = entry.source == AINPUT_SOURCE_MOUSE;
+
+    const bool isHoverAction = (maskedAction == AMOTION_EVENT_ACTION_HOVER_MOVE ||
+                                maskedAction == AMOTION_EVENT_ACTION_HOVER_ENTER ||
+                                maskedAction == AMOTION_EVENT_ACTION_HOVER_EXIT);
+    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;
@@ -2012,7 +2107,7 @@
 
         int32_t x;
         int32_t y;
-        int32_t pointerIndex = getMotionEventActionPointerIndex(action);
+        const int32_t pointerIndex = getMotionEventActionPointerIndex(action);
         // Always dispatch mouse events to cursor position.
         if (isFromMouse) {
             x = int32_t(entry.xCursorPosition);
@@ -2021,116 +2116,126 @@
             x = int32_t(entry.pointerCoords[pointerIndex].getAxisValue(AMOTION_EVENT_AXIS_X));
             y = int32_t(entry.pointerCoords[pointerIndex].getAxisValue(AMOTION_EVENT_AXIS_Y));
         }
-        bool isDown = maskedAction == AMOTION_EVENT_ACTION_DOWN;
-        newTouchedWindowHandle =
-                findTouchedWindowAtLocked(displayId, x, y, &tempTouchState,
-                                          isDown /*addOutsideTargets*/, true /*addPortalWindows*/);
-
-        // Figure out whether splitting will be allowed for this window.
-        if (newTouchedWindowHandle != nullptr &&
-            newTouchedWindowHandle->getInfo()->supportsSplitTouch()) {
-            // New window supports splitting, but we should never split mouse events.
-            isSplit = !isFromMouse;
-        } else if (isSplit) {
-            // New window does not support splitting but we have already split events.
-            // Ignore the new window.
-            newTouchedWindowHandle = nullptr;
-        }
+        const bool isDown = maskedAction == AMOTION_EVENT_ACTION_DOWN;
+        const bool isStylus = isPointerFromStylus(entry, pointerIndex);
+        newTouchedWindowHandle = findTouchedWindowAtLocked(displayId, x, y, &tempTouchState,
+                                                           isStylus, isDown /*addOutsideTargets*/);
 
         // Handle the case where we did not find a window.
         if (newTouchedWindowHandle == nullptr) {
+            ALOGD("No new touched window at (%" PRId32 ", %" PRId32 ") in display %" PRId32, x, y,
+                  displayId);
             // Try to assign the pointer to the first foreground window we find, if there is one.
             newTouchedWindowHandle = tempTouchState.getFirstForegroundWindowHandle();
         }
 
-        if (newTouchedWindowHandle != nullptr && newTouchedWindowHandle->getInfo()->paused) {
-            ALOGI("Not sending touch event to %s because it is paused",
-                  newTouchedWindowHandle->getName().c_str());
-            newTouchedWindowHandle = nullptr;
-        }
-
-        // Ensure the window has a connection and the connection is responsive
+        // Figure out whether splitting will be allowed for this window.
         if (newTouchedWindowHandle != nullptr) {
-            const bool isResponsive = hasResponsiveConnectionLocked(*newTouchedWindowHandle);
-            if (!isResponsive) {
-                ALOGW("%s will not receive the new gesture at %" PRIu64,
-                      newTouchedWindowHandle->getName().c_str(), entry.eventTime);
+            if (newTouchedWindowHandle->getInfo()->supportsSplitTouch()) {
+                // New window supports splitting, but we should never split mouse events.
+                isSplit = !isFromMouse;
+            } else if (isSplit) {
+                // New window does not support splitting but we have already split events.
+                // Ignore the new window.
                 newTouchedWindowHandle = nullptr;
             }
+        } else {
+            // 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;
         }
 
-        // Drop events that can't be trusted due to occlusion
-        if (newTouchedWindowHandle != nullptr &&
-            mBlockUntrustedTouchesMode != BlockUntrustedTouchesMode::DISABLED) {
-            TouchOcclusionInfo occlusionInfo =
-                    computeTouchOcclusionInfoLocked(newTouchedWindowHandle, x, y);
-            if (!isTouchTrustedLocked(occlusionInfo)) {
-                if (DEBUG_TOUCH_OCCLUSION) {
-                    ALOGD("Stack of obscuring windows during untrusted touch (%d, %d):", x, y);
-                    for (const auto& log : occlusionInfo.debugInfo) {
-                        ALOGD("%s", log.c_str());
-                    }
-                }
-                onUntrustedTouchLocked(occlusionInfo.obscuringPackage);
-                if (mBlockUntrustedTouchesMode == BlockUntrustedTouchesMode::BLOCK) {
-                    ALOGW("Dropping untrusted touch event due to %s/%d",
-                          occlusionInfo.obscuringPackage.c_str(), occlusionInfo.obscuringUid);
-                    newTouchedWindowHandle = nullptr;
-                }
+        // Update hover state.
+        if (newTouchedWindowHandle != nullptr) {
+            if (maskedAction == AMOTION_EVENT_ACTION_HOVER_EXIT) {
+                newHoverWindowHandle = nullptr;
+            } else if (isHoverAction) {
+                newHoverWindowHandle = newTouchedWindowHandle;
             }
         }
 
-        // Drop touch events if requested by input feature
-        if (newTouchedWindowHandle != nullptr && shouldDropInput(entry, newTouchedWindowHandle)) {
-            newTouchedWindowHandle = nullptr;
+        std::vector<sp<WindowInfoHandle>> newTouchedWindows =
+                findTouchedSpyWindowsAtLocked(displayId, x, y, isStylus);
+        if (newTouchedWindowHandle != nullptr) {
+            // Process the foreground window first so that it is the first to receive the event.
+            newTouchedWindows.insert(newTouchedWindows.begin(), newTouchedWindowHandle);
         }
 
-        const std::vector<TouchedMonitor> newGestureMonitors = isDown
-                ? selectResponsiveMonitorsLocked(
-                          findTouchedGestureMonitorsLocked(displayId, tempTouchState.portalWindows))
-                : tempTouchState.gestureMonitors;
-
-        if (newTouchedWindowHandle == nullptr && newGestureMonitors.empty()) {
-            ALOGI("Dropping event because there is no touchable window or gesture monitor at "
-                  "(%d, %d) in display %" PRId32 ".",
+        if (newTouchedWindows.empty()) {
+            ALOGI("Dropping event because there is no touchable window at (%d, %d) on display %d.",
                   x, y, displayId);
             injectionResult = InputEventInjectionResult::FAILED;
             goto Failed;
         }
 
-        if (newTouchedWindowHandle != nullptr) {
+        for (const sp<WindowInfoHandle>& windowHandle : newTouchedWindows) {
+            const WindowInfo& info = *windowHandle->getInfo();
+
+            if (info.inputConfig.test(WindowInfo::InputConfig::PAUSE_DISPATCHING)) {
+                ALOGI("Not sending touch event to %s because it is paused",
+                      windowHandle->getName().c_str());
+                continue;
+            }
+
+            // Ensure the window has a connection and the connection is responsive
+            const bool isResponsive = hasResponsiveConnectionLocked(*windowHandle);
+            if (!isResponsive) {
+                ALOGW("Not sending touch gesture to %s because it is not responsive",
+                      windowHandle->getName().c_str());
+                continue;
+            }
+
+            // Drop events that can't be trusted due to occlusion
+            if (mBlockUntrustedTouchesMode != BlockUntrustedTouchesMode::DISABLED) {
+                TouchOcclusionInfo occlusionInfo =
+                        computeTouchOcclusionInfoLocked(windowHandle, x, y);
+                if (!isTouchTrustedLocked(occlusionInfo)) {
+                    if (DEBUG_TOUCH_OCCLUSION) {
+                        ALOGD("Stack of obscuring windows during untrusted touch (%d, %d):", x, y);
+                        for (const auto& log : occlusionInfo.debugInfo) {
+                            ALOGD("%s", log.c_str());
+                        }
+                    }
+                    sendUntrustedTouchCommandLocked(occlusionInfo.obscuringPackage);
+                    if (mBlockUntrustedTouchesMode == BlockUntrustedTouchesMode::BLOCK) {
+                        ALOGW("Dropping untrusted touch event due to %s/%d",
+                              occlusionInfo.obscuringPackage.c_str(), occlusionInfo.obscuringUid);
+                        continue;
+                    }
+                }
+            }
+
+            // Drop touch events if requested by input feature
+            if (shouldDropInput(entry, windowHandle)) {
+                continue;
+            }
+
             // Set target flags.
-            int32_t targetFlags = InputTarget::FLAG_FOREGROUND | InputTarget::FLAG_DISPATCH_AS_IS;
+            int32_t targetFlags = InputTarget::FLAG_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;
+            }
+
             if (isSplit) {
                 targetFlags |= InputTarget::FLAG_SPLIT;
             }
-            if (isWindowObscuredAtPointLocked(newTouchedWindowHandle, x, y)) {
+            if (isWindowObscuredAtPointLocked(windowHandle, x, y)) {
                 targetFlags |= InputTarget::FLAG_WINDOW_IS_OBSCURED;
-            } else if (isWindowObscuredLocked(newTouchedWindowHandle)) {
+            } else if (isWindowObscuredLocked(windowHandle)) {
                 targetFlags |= InputTarget::FLAG_WINDOW_IS_PARTIALLY_OBSCURED;
             }
 
-            // Update hover state.
-            if (maskedAction == AMOTION_EVENT_ACTION_HOVER_EXIT) {
-                newHoverWindowHandle = nullptr;
-            } else if (isHoverAction) {
-                newHoverWindowHandle = newTouchedWindowHandle;
-            }
-
             // Update the temporary touch state.
             BitSet32 pointerIds;
             if (isSplit) {
                 uint32_t pointerId = entry.pointerProperties[pointerIndex].id;
                 pointerIds.markBit(pointerId);
             }
-            tempTouchState.addOrUpdateWindow(newTouchedWindowHandle, targetFlags, pointerIds);
-        } else if (tempTouchState.windows.empty()) {
-            // If no window is touched, set split to true. This will allow the next pointer down to
-            // be delivered to a new window which supports split touch.
-            tempTouchState.split = true;
-        }
-        if (isDown) {
-            tempTouchState.addGestureMonitors(newGestureMonitors);
+
+            tempTouchState.addOrUpdateWindow(windowHandle, targetFlags, pointerIds);
         }
     } else {
         /* Case 2: Pointer move, up, cancel or non-splittable pointer down. */
@@ -2151,12 +2256,14 @@
         // Check whether touches should slip outside of the current foreground window.
         if (maskedAction == AMOTION_EVENT_ACTION_MOVE && entry.pointerCount == 1 &&
             tempTouchState.isSlippery()) {
-            int32_t x = int32_t(entry.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_X));
-            int32_t y = int32_t(entry.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_Y));
+            const int32_t x = int32_t(entry.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_X));
+            const int32_t y = int32_t(entry.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_Y));
 
+            const bool isStylus = isPointerFromStylus(entry, 0 /*pointerIndex*/);
             sp<WindowInfoHandle> oldTouchedWindowHandle =
                     tempTouchState.getFirstForegroundWindowHandle();
-            newTouchedWindowHandle = findTouchedWindowAtLocked(displayId, x, y, &tempTouchState);
+            newTouchedWindowHandle =
+                    findTouchedWindowAtLocked(displayId, x, y, &tempTouchState, isStylus);
 
             // Drop touch events if requested by input feature
             if (newTouchedWindowHandle != nullptr &&
@@ -2178,11 +2285,13 @@
 
                 // Make a slippery entrance into the new window.
                 if (newTouchedWindowHandle->getInfo()->supportsSplitTouch()) {
-                    isSplit = true;
+                    isSplit = !isFromMouse;
                 }
 
-                int32_t targetFlags =
-                        InputTarget::FLAG_FOREGROUND | InputTarget::FLAG_DISPATCH_AS_SLIPPERY_ENTER;
+                int32_t targetFlags = InputTarget::FLAG_DISPATCH_AS_SLIPPERY_ENTER;
+                if (canReceiveForegroundTouches(*newTouchedWindowHandle->getInfo())) {
+                    targetFlags |= InputTarget::FLAG_FOREGROUND;
+                }
                 if (isSplit) {
                     targetFlags |= InputTarget::FLAG_SPLIT;
                 }
@@ -2201,16 +2310,17 @@
         }
     }
 
+    // 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.
+        // 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());
-#endif
+            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));
         }
@@ -2220,43 +2330,46 @@
         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());
-#endif
+            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));
         }
     }
 
-    // Check permission to inject into all touched foreground windows and ensure there
-    // is at least one touched foreground window.
-    {
-        bool haveForegroundWindow = false;
-        for (const TouchedWindow& touchedWindow : tempTouchState.windows) {
-            if (touchedWindow.targetFlags & InputTarget::FLAG_FOREGROUND) {
-                haveForegroundWindow = true;
-                if (!checkInjectionPermission(touchedWindow.windowHandle, entry.injectionState)) {
-                    injectionResult = InputEventInjectionResult::PERMISSION_DENIED;
-                    injectionPermission = INJECTION_PERMISSION_DENIED;
-                    goto Failed;
-                }
-            }
-        }
-        bool hasGestureMonitor = !tempTouchState.gestureMonitors.empty();
-        if (!haveForegroundWindow && !hasGestureMonitor) {
-            ALOGI("Dropping event because there is no touched foreground window in display "
-                  "%" PRId32 " or gesture monitor to receive it.",
-                  displayId);
-            injectionResult = InputEventInjectionResult::FAILED;
-            goto Failed;
-        }
-
-        // Permission granted to injection into all touched foreground windows.
-        injectionPermission = INJECTION_PERMISSION_GRANTED;
+    // 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
+    // that is actually receiving the entire gesture.
+    if (std::none_of(tempTouchState.windows.begin(), tempTouchState.windows.end(),
+                     [](const TouchedWindow& touchedWindow) {
+                         return !canReceiveForegroundTouches(
+                                        *touchedWindow.windowHandle->getInfo()) ||
+                                 (touchedWindow.targetFlags & InputTarget::FLAG_FOREGROUND) != 0;
+                     })) {
+        ALOGI("Dropping event because there is no touched window on display %d to receive it.",
+              displayId);
+        injectionResult = InputEventInjectionResult::FAILED;
+        goto Failed;
     }
 
+    // Check permission to inject into all touched foreground windows.
+    if (std::any_of(tempTouchState.windows.begin(), tempTouchState.windows.end(),
+                    [this, &entry](const TouchedWindow& touchedWindow) {
+                        return (touchedWindow.targetFlags & InputTarget::FLAG_FOREGROUND) != 0 &&
+                                !checkInjectionPermission(touchedWindow.windowHandle,
+                                                          entry.injectionState);
+                    })) {
+        injectionResult = InputEventInjectionResult::PERMISSION_DENIED;
+        injectionPermission = INJECTION_PERMISSION_DENIED;
+        goto Failed;
+    }
+    // Permission granted to inject into all touched foreground windows.
+    injectionPermission = INJECTION_PERMISSION_GRANTED;
+
     // Check whether windows listening for outside touches are owned by the same UID. If it is
     // set the policy flag that we will not reveal coordinate information to this window.
     if (maskedAction == AMOTION_EVENT_ACTION_DOWN) {
@@ -2286,13 +2399,16 @@
     if (maskedAction == AMOTION_EVENT_ACTION_DOWN) {
         sp<WindowInfoHandle> foregroundWindowHandle =
                 tempTouchState.getFirstForegroundWindowHandle();
-        if (foregroundWindowHandle && foregroundWindowHandle->getInfo()->hasWallpaper) {
+        if (foregroundWindowHandle &&
+            foregroundWindowHandle->getInfo()->inputConfig.test(
+                    WindowInfo::InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER)) {
             const std::vector<sp<WindowInfoHandle>>& windowHandles =
                     getWindowHandlesLocked(displayId);
             for (const sp<WindowInfoHandle>& windowHandle : windowHandles) {
                 const WindowInfo* info = windowHandle->getInfo();
                 if (info->displayId == displayId &&
-                    windowHandle->getInfo()->type == WindowInfo::Type::WALLPAPER) {
+                    windowHandle->getInfo()->inputConfig.test(
+                            WindowInfo::InputConfig::IS_WALLPAPER)) {
                     tempTouchState
                             .addOrUpdateWindow(windowHandle,
                                                InputTarget::FLAG_WINDOW_IS_OBSCURED |
@@ -2313,11 +2429,6 @@
                               touchedWindow.pointerIds, inputTargets);
     }
 
-    for (const TouchedMonitor& touchedMonitor : tempTouchState.gestureMonitors) {
-        addMonitoringTargetLocked(touchedMonitor.monitor, touchedMonitor.xOffset,
-                                  touchedMonitor.yOffset, inputTargets);
-    }
-
     // Drop the outside or hover touch windows since we will not care about them
     // in the next iteration.
     tempTouchState.filterNonAsIsTouchWindows();
@@ -2411,15 +2522,18 @@
 }
 
 void InputDispatcher::finishDragAndDrop(int32_t displayId, float x, float y) {
+    // Prevent stylus interceptor windows from affecting drag and drop behavior for now, until we
+    // have an explicit reason to support it.
+    constexpr bool isStylus = false;
+
     const sp<WindowInfoHandle> dropWindow =
-            findTouchedWindowAtLocked(displayId, x, y, nullptr /*touchState*/,
-                                      false /*addOutsideTargets*/, false /*addPortalWindows*/,
-                                      true /*ignoreDragWindow*/);
+            findTouchedWindowAtLocked(displayId, x, y, nullptr /*touchState*/, isStylus,
+                                      false /*addOutsideTargets*/, true /*ignoreDragWindow*/);
     if (dropWindow) {
         vec2 local = dropWindow->getInfo()->transform.transform(x, y);
-        notifyDropWindowLocked(dropWindow->getToken(), local.x, local.y);
+        sendDropWindowCommandLocked(dropWindow->getToken(), local.x, local.y);
     } else {
-        notifyDropWindowLocked(nullptr, 0, 0);
+        sendDropWindowCommandLocked(nullptr, 0, 0);
     }
     mDragState.reset();
 }
@@ -2446,10 +2560,13 @@
             return;
         }
 
+        // Prevent stylus interceptor windows from affecting drag and drop behavior for now, until
+        // we have an explicit reason to support it.
+        constexpr bool isStylus = false;
+
         const sp<WindowInfoHandle> hoverWindowHandle =
-                findTouchedWindowAtLocked(entry.displayId, x, y, nullptr /*touchState*/,
-                                          false /*addOutsideTargets*/, false /*addPortalWindows*/,
-                                          true /*ignoreDragWindow*/);
+                findTouchedWindowAtLocked(entry.displayId, x, y, nullptr /*touchState*/, isStylus,
+                                          false /*addOutsideTargets*/, true /*ignoreDragWindow*/);
         // enqueue drag exit if needed.
         if (hoverWindowHandle != mDragState->dragHoverWindowHandle &&
             !haveSameToken(hoverWindowHandle, mDragState->dragHoverWindowHandle)) {
@@ -2466,7 +2583,7 @@
     } else if (maskedAction == AMOTION_EVENT_ACTION_UP) {
         finishDragAndDrop(entry.displayId, x, y);
     } else if (maskedAction == AMOTION_EVENT_ACTION_CANCEL) {
-        notifyDropWindowLocked(nullptr, 0, 0);
+        sendDropWindowCommandLocked(nullptr, 0, 0);
         mDragState.reset();
     }
 }
@@ -2494,9 +2611,12 @@
         inputTarget.inputChannel = inputChannel;
         inputTarget.flags = targetFlags;
         inputTarget.globalScaleFactor = windowInfo->globalScaleFactor;
-        inputTarget.displayOrientation = windowInfo->displayOrientation;
-        inputTarget.displaySize =
-                int2(windowHandle->getInfo()->displayWidth, windowHandle->getInfo()->displayHeight);
+        const auto& displayInfoIt = mDisplayInfos.find(windowInfo->displayId);
+        if (displayInfoIt != mDisplayInfos.end()) {
+            inputTarget.displayTransform = displayInfoIt->second.transform;
+        } else {
+            ALOGE("DisplayInfo not found for window on display: %d", windowInfo->displayId);
+        }
         inputTargets.push_back(inputTarget);
         it = inputTargets.end() - 1;
     }
@@ -2508,31 +2628,22 @@
 }
 
 void InputDispatcher::addGlobalMonitoringTargetsLocked(std::vector<InputTarget>& inputTargets,
-                                                       int32_t displayId, float xOffset,
-                                                       float yOffset) {
-    std::unordered_map<int32_t, std::vector<Monitor>>::const_iterator it =
-            mGlobalMonitorsByDisplay.find(displayId);
+                                                       int32_t displayId) {
+    auto monitorsIt = mGlobalMonitorsByDisplay.find(displayId);
+    if (monitorsIt == mGlobalMonitorsByDisplay.end()) return;
 
-    if (it != mGlobalMonitorsByDisplay.end()) {
-        const std::vector<Monitor>& monitors = it->second;
-        for (const Monitor& monitor : monitors) {
-            addMonitoringTargetLocked(monitor, xOffset, yOffset, inputTargets);
+    for (const Monitor& monitor : selectResponsiveMonitorsLocked(monitorsIt->second)) {
+        InputTarget target;
+        target.inputChannel = monitor.inputChannel;
+        target.flags = InputTarget::FLAG_DISPATCH_AS_IS;
+        if (const auto& it = mDisplayInfos.find(displayId); it != mDisplayInfos.end()) {
+            target.displayTransform = it->second.transform;
         }
+        target.setDefaultPointerTransform(target.displayTransform);
+        inputTargets.push_back(target);
     }
 }
 
-void InputDispatcher::addMonitoringTargetLocked(const Monitor& monitor, float xOffset,
-                                                float yOffset,
-                                                std::vector<InputTarget>& inputTargets) {
-    InputTarget target;
-    target.inputChannel = monitor.inputChannel;
-    target.flags = InputTarget::FLAG_DISPATCH_AS_IS;
-    ui::Transform t;
-    t.set(xOffset, yOffset);
-    target.setDefaultPointerTransform(t);
-    inputTargets.push_back(target);
-}
-
 bool InputDispatcher::checkInjectionPermission(const sp<WindowInfoHandle>& windowHandle,
                                                const InjectionState* injectionState) {
     if (injectionState &&
@@ -2566,9 +2677,10 @@
     }
     auto info = windowHandle->getInfo();
     auto otherInfo = otherHandle->getInfo();
-    if (!otherInfo->visible) {
+    if (otherInfo->inputConfig.test(WindowInfo::InputConfig::NOT_VISIBLE)) {
         return false;
-    } else if (otherInfo->alpha == 0 && otherInfo->flags.test(WindowInfo::Flag::NOT_TOUCHABLE)) {
+    } else if (otherInfo->alpha == 0 &&
+               otherInfo->inputConfig.test(WindowInfo::InputConfig::NOT_TOUCHABLE)) {
         // Those act as if they were invisible, so we don't need to flag them.
         // 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
@@ -2580,7 +2692,7 @@
         // If ownerUid is the same we don't generate occlusion events as there
         // is no security boundary within an uid.
         return false;
-    } else if (otherInfo->trustedOverlay) {
+    } else if (otherInfo->inputConfig.test(gui::WindowInfo::InputConfig::TRUSTED_OVERLAY)) {
         return false;
     } else if (otherInfo->displayId != info->displayId) {
         return false;
@@ -2660,18 +2772,15 @@
 
 std::string InputDispatcher::dumpWindowForTouchOcclusion(const WindowInfo* info,
                                                          bool isTouchedWindow) const {
-    return StringPrintf(INDENT2
-                        "* %stype=%s, package=%s/%" PRId32 ", id=%" PRId32 ", mode=%s, alpha=%.2f, "
-                        "frame=[%" PRId32 ",%" PRId32 "][%" PRId32 ",%" PRId32
-                        "], touchableRegion=%s, window={%s}, flags={%s}, inputFeatures={%s}, "
-                        "hasToken=%s, applicationInfo.name=%s, applicationInfo.token=%s\n",
-                        (isTouchedWindow) ? "[TOUCHED] " : "",
-                        NamedEnum::string(info->type, "%" PRId32).c_str(),
-                        info->packageName.c_str(), info->ownerUid, info->id,
-                        toString(info->touchOcclusionMode).c_str(), info->alpha, info->frameLeft,
-                        info->frameTop, info->frameRight, info->frameBottom,
-                        dumpRegion(info->touchableRegion).c_str(), info->name.c_str(),
-                        info->flags.string().c_str(), info->inputFeatures.string().c_str(),
+    return StringPrintf(INDENT2 "* %spackage=%s/%" PRId32 ", id=%" PRId32 ", mode=%s, alpha=%.2f, "
+                                "frame=[%" PRId32 ",%" PRId32 "][%" PRId32 ",%" PRId32
+                                "], touchableRegion=%s, window={%s}, inputConfig={%s}, "
+                                "hasToken=%s, applicationInfo.name=%s, applicationInfo.token=%s\n",
+                        isTouchedWindow ? "[TOUCHED] " : "", info->packageName.c_str(),
+                        info->ownerUid, info->id, toString(info->touchOcclusionMode).c_str(),
+                        info->alpha, info->frameLeft, info->frameTop, info->frameRight,
+                        info->frameBottom, dumpRegion(info->touchableRegion).c_str(),
+                        info->name.c_str(), info->inputConfig.string().c_str(),
                         toString(info->token != nullptr), info->applicationInfo.name.c_str(),
                         toString(info->applicationInfo.token).c_str());
 }
@@ -2742,21 +2851,18 @@
 }
 
 void InputDispatcher::pokeUserActivityLocked(const EventEntry& eventEntry) {
-    if (eventEntry.type == EventEntry::Type::FOCUS ||
-        eventEntry.type == EventEntry::Type::POINTER_CAPTURE_CHANGED ||
-        eventEntry.type == EventEntry::Type::DRAG) {
-        // Focus or pointer capture changed events are passed to apps, but do not represent user
-        // activity.
+    if (!isUserActivityEvent(eventEntry)) {
+        // Not poking user activity if the event type does not represent a user activity
         return;
     }
     int32_t displayId = getTargetDisplayId(eventEntry);
     sp<WindowInfoHandle> focusedWindowHandle = getFocusedWindowHandleLocked(displayId);
     if (focusedWindowHandle != nullptr) {
         const WindowInfo* info = focusedWindowHandle->getInfo();
-        if (info->inputFeatures.test(WindowInfo::Feature::DISABLE_USER_ACTIVITY)) {
-#if DEBUG_DISPATCH_CYCLE
-            ALOGD("Not poking user activity: disabled by window '%s'.", info->name.c_str());
-#endif
+        if (info->inputConfig.test(WindowInfo::InputConfig::DISABLE_USER_ACTIVITY)) {
+            if (DEBUG_DISPATCH_CYCLE) {
+                ALOGD("Not poking user activity: disabled by window '%s'.", info->name.c_str());
+            }
             return;
         }
     }
@@ -2782,24 +2888,19 @@
             eventType = USER_ACTIVITY_EVENT_BUTTON;
             break;
         }
-        case EventEntry::Type::FOCUS:
-        case EventEntry::Type::CONFIGURATION_CHANGED:
-        case EventEntry::Type::DEVICE_RESET:
-        case EventEntry::Type::SENSOR:
-        case EventEntry::Type::POINTER_CAPTURE_CHANGED:
-        case EventEntry::Type::DRAG: {
+        default: {
             LOG_ALWAYS_FATAL("%s events are not user activity",
-                             NamedEnum::string(eventEntry.type).c_str());
+                             ftl::enum_string(eventEntry.type).c_str());
             break;
         }
     }
 
-    std::unique_ptr<CommandEntry> commandEntry =
-            std::make_unique<CommandEntry>(&InputDispatcher::doPokeUserActivityLockedInterruptible);
-    commandEntry->eventTime = eventEntry.eventTime;
-    commandEntry->userActivityEventType = eventType;
-    commandEntry->displayId = displayId;
-    postCommandLocked(std::move(commandEntry));
+    auto command = [this, eventTime = eventEntry.eventTime, eventType, displayId]()
+                           REQUIRES(mLock) {
+                               scoped_unlock unlock(mLock);
+                               mPolicy->pokeUserActivity(eventTime, eventType, displayId);
+                           };
+    postCommandLocked(std::move(command));
 }
 
 void InputDispatcher::prepareDispatchCycleLocked(nsecs_t currentTime,
@@ -2812,21 +2913,22 @@
                              connection->getInputChannelName().c_str(), eventEntry->id);
         ATRACE_NAME(message.c_str());
     }
-#if DEBUG_DISPATCH_CYCLE
-    ALOGD("channel '%s' ~ prepareDispatchCycle - flags=0x%08x, "
-          "globalScaleFactor=%f, pointerIds=0x%x %s",
-          connection->getInputChannelName().c_str(), inputTarget.flags,
-          inputTarget.globalScaleFactor, inputTarget.pointerIds.value,
-          inputTarget.getPointerInfoString().c_str());
-#endif
+    if (DEBUG_DISPATCH_CYCLE) {
+        ALOGD("channel '%s' ~ prepareDispatchCycle - flags=0x%08x, "
+              "globalScaleFactor=%f, pointerIds=0x%x %s",
+              connection->getInputChannelName().c_str(), inputTarget.flags,
+              inputTarget.globalScaleFactor, inputTarget.pointerIds.value,
+              inputTarget.getPointerInfoString().c_str());
+    }
 
     // Skip this event if the connection status is not normal.
     // We don't want to enqueue additional outbound events if the connection is broken.
-    if (connection->status != Connection::STATUS_NORMAL) {
-#if DEBUG_DISPATCH_CYCLE
-        ALOGD("channel '%s' ~ Dropping event because the channel status is %s",
-              connection->getInputChannelName().c_str(), connection->getStatusLabel());
-#endif
+    if (connection->status != Connection::Status::NORMAL) {
+        if (DEBUG_DISPATCH_CYCLE) {
+            ALOGD("channel '%s' ~ Dropping event because the channel status is %s",
+                  connection->getInputChannelName().c_str(),
+                  ftl::enum_string(connection->status).c_str());
+        }
         return;
     }
 
@@ -2834,7 +2936,7 @@
     if (inputTarget.flags & InputTarget::FLAG_SPLIT) {
         LOG_ALWAYS_FATAL_IF(eventEntry->type != EventEntry::Type::MOTION,
                             "Entry type %s should not have FLAG_SPLIT",
-                            NamedEnum::string(eventEntry->type).c_str());
+                            ftl::enum_string(eventEntry->type).c_str());
 
         const MotionEntry& originalMotionEntry = static_cast<const MotionEntry&>(*eventEntry);
         if (inputTarget.pointerIds.count() != originalMotionEntry.pointerCount) {
@@ -2843,6 +2945,11 @@
             if (!splitMotionEntry) {
                 return; // split event was dropped
             }
+            if (splitMotionEntry->action == AMOTION_EVENT_ACTION_CANCEL) {
+                std::string reason = std::string("reason=pointer cancel on split window");
+                android_log_event_list(LOGTAG_INPUT_CANCEL)
+                        << connection->getInputChannelName().c_str() << reason << LOG_ID_EVENTS;
+            }
             if (DEBUG_FOCUS) {
                 ALOGD("channel '%s' ~ Split motion event.",
                       connection->getInputChannelName().c_str());
@@ -2925,10 +3032,11 @@
 
             if (!connection->inputState.trackKey(keyEntry, dispatchEntry->resolvedAction,
                                                  dispatchEntry->resolvedFlags)) {
-#if DEBUG_DISPATCH_CYCLE
-                ALOGD("channel '%s' ~ enqueueDispatchEntryLocked: skipping inconsistent key event",
-                      connection->getInputChannelName().c_str());
-#endif
+                if (DEBUG_DISPATCH_CYCLE) {
+                    ALOGD("channel '%s' ~ enqueueDispatchEntryLocked: skipping inconsistent key "
+                          "event",
+                          connection->getInputChannelName().c_str());
+                }
                 return; // skip the inconsistent event
             }
             break;
@@ -2958,11 +3066,11 @@
             if (dispatchEntry->resolvedAction == AMOTION_EVENT_ACTION_HOVER_MOVE &&
                 !connection->inputState.isHovering(motionEntry.deviceId, motionEntry.source,
                                                    motionEntry.displayId)) {
-#if DEBUG_DISPATCH_CYCLE
-                ALOGD("channel '%s' ~ enqueueDispatchEntryLocked: filling in missing hover enter "
-                      "event",
-                      connection->getInputChannelName().c_str());
-#endif
+                if (DEBUG_DISPATCH_CYCLE) {
+                    ALOGD("channel '%s' ~ enqueueDispatchEntryLocked: filling in missing hover "
+                          "enter event",
+                          connection->getInputChannelName().c_str());
+                }
                 // We keep the 'resolvedEventId' here equal to the original 'motionEntry.id' because
                 // this is a one-to-one event conversion.
                 dispatchEntry->resolvedAction = AMOTION_EVENT_ACTION_HOVER_ENTER;
@@ -2978,11 +3086,11 @@
 
             if (!connection->inputState.trackMotion(motionEntry, dispatchEntry->resolvedAction,
                                                     dispatchEntry->resolvedFlags)) {
-#if DEBUG_DISPATCH_CYCLE
-                ALOGD("channel '%s' ~ enqueueDispatchEntryLocked: skipping inconsistent motion "
-                      "event",
-                      connection->getInputChannelName().c_str());
-#endif
+                if (DEBUG_DISPATCH_CYCLE) {
+                    ALOGD("channel '%s' ~ enqueueDispatchEntryLocked: skipping inconsistent motion "
+                          "event",
+                          connection->getInputChannelName().c_str());
+                }
                 return; // skip the inconsistent event
             }
 
@@ -3009,6 +3117,7 @@
             break;
         }
         case EventEntry::Type::FOCUS:
+        case EventEntry::Type::TOUCH_MODE_CHANGED:
         case EventEntry::Type::POINTER_CAPTURE_CHANGED:
         case EventEntry::Type::DRAG: {
             break;
@@ -3020,7 +3129,7 @@
         case EventEntry::Type::CONFIGURATION_CHANGED:
         case EventEntry::Type::DEVICE_RESET: {
             LOG_ALWAYS_FATAL("%s events should not go to apps",
-                             NamedEnum::string(newEntry.type).c_str());
+                             ftl::enum_string(newEntry.type).c_str());
             break;
         }
     }
@@ -3114,10 +3223,11 @@
         return;
     }
 
-    std::unique_ptr<CommandEntry> commandEntry = std::make_unique<CommandEntry>(
-            &InputDispatcher::doOnPointerDownOutsideFocusLockedInterruptible);
-    commandEntry->newToken = token;
-    postCommandLocked(std::move(commandEntry));
+    auto command = [this, token]() REQUIRES(mLock) {
+        scoped_unlock unlock(mLock);
+        mPolicy->onPointerDownOutsideFocus(token);
+    };
+    postCommandLocked(std::move(command));
 }
 
 void InputDispatcher::startDispatchCycleLocked(nsecs_t currentTime,
@@ -3127,15 +3237,14 @@
                                            connection->getInputChannelName().c_str());
         ATRACE_NAME(message.c_str());
     }
-#if DEBUG_DISPATCH_CYCLE
-    ALOGD("channel '%s' ~ startDispatchCycle", connection->getInputChannelName().c_str());
-#endif
+    if (DEBUG_DISPATCH_CYCLE) {
+        ALOGD("channel '%s' ~ startDispatchCycle", connection->getInputChannelName().c_str());
+    }
 
-    while (connection->status == Connection::STATUS_NORMAL && !connection->outboundQueue.empty()) {
+    while (connection->status == Connection::Status::NORMAL && !connection->outboundQueue.empty()) {
         DispatchEntry* dispatchEntry = connection->outboundQueue.front();
         dispatchEntry->deliveryTime = currentTime;
-        const std::chrono::nanoseconds timeout =
-                getDispatchingTimeoutLocked(connection->inputChannel->getConnectionToken());
+        const std::chrono::nanoseconds timeout = getDispatchingTimeoutLocked(connection);
         dispatchEntry->timeoutTime = currentTime + timeout.count();
 
         // Publish the event.
@@ -3208,9 +3317,7 @@
                                                      motionEntry.xPrecision, motionEntry.yPrecision,
                                                      motionEntry.xCursorPosition,
                                                      motionEntry.yCursorPosition,
-                                                     dispatchEntry->displayOrientation,
-                                                     dispatchEntry->displaySize.x,
-                                                     dispatchEntry->displaySize.y,
+                                                     dispatchEntry->rawTransform,
                                                      motionEntry.downTime, motionEntry.eventTime,
                                                      motionEntry.pointerCount,
                                                      motionEntry.pointerProperties, usingCoords);
@@ -3221,8 +3328,17 @@
                 const FocusEntry& focusEntry = static_cast<const FocusEntry&>(eventEntry);
                 status = connection->inputPublisher.publishFocusEvent(dispatchEntry->seq,
                                                                       focusEntry.id,
-                                                                      focusEntry.hasFocus,
-                                                                      mInTouchMode);
+                                                                      focusEntry.hasFocus);
+                break;
+            }
+
+            case EventEntry::Type::TOUCH_MODE_CHANGED: {
+                const TouchModeEntry& touchModeEntry =
+                        static_cast<const TouchModeEntry&>(eventEntry);
+                status = connection->inputPublisher
+                                 .publishTouchModeEvent(dispatchEntry->seq, touchModeEntry.id,
+                                                        touchModeEntry.inTouchMode);
+
                 break;
             }
 
@@ -3248,7 +3364,7 @@
             case EventEntry::Type::DEVICE_RESET:
             case EventEntry::Type::SENSOR: {
                 LOG_ALWAYS_FATAL("Should never start dispatch cycles for %s events",
-                                 NamedEnum::string(eventEntry.type).c_str());
+                                 ftl::enum_string(eventEntry.type).c_str());
                 return;
             }
         }
@@ -3267,11 +3383,11 @@
                 } else {
                     // Pipe is full and we are waiting for the app to finish process some events
                     // before sending more events to it.
-#if DEBUG_DISPATCH_CYCLE
-                    ALOGD("channel '%s' ~ Could not publish event because the pipe is full, "
-                          "waiting for the application to catch up",
-                          connection->getInputChannelName().c_str());
-#endif
+                    if (DEBUG_DISPATCH_CYCLE) {
+                        ALOGD("channel '%s' ~ Could not publish event because the pipe is full, "
+                              "waiting for the application to catch up",
+                              connection->getInputChannelName().c_str());
+                    }
                 }
             } else {
                 ALOGE("channel '%s' ~ Could not publish event due to an unexpected error, "
@@ -3315,16 +3431,18 @@
 
 const std::array<uint8_t, 32> InputDispatcher::getSignature(
         const MotionEntry& motionEntry, const DispatchEntry& dispatchEntry) const {
-    int32_t actionMasked = dispatchEntry.resolvedAction & AMOTION_EVENT_ACTION_MASK;
-    if ((actionMasked == AMOTION_EVENT_ACTION_UP) || (actionMasked == AMOTION_EVENT_ACTION_DOWN)) {
+    const int32_t actionMasked = dispatchEntry.resolvedAction & AMOTION_EVENT_ACTION_MASK;
+    if (actionMasked != AMOTION_EVENT_ACTION_UP && actionMasked != AMOTION_EVENT_ACTION_DOWN) {
         // Only sign events up and down events as the purely move events
         // are tied to their up/down counterparts so signing would be redundant.
-        VerifiedMotionEvent verifiedEvent = verifiedMotionEventFromMotionEntry(motionEntry);
-        verifiedEvent.actionMasked = actionMasked;
-        verifiedEvent.flags = dispatchEntry.resolvedFlags & VERIFIED_MOTION_EVENT_FLAGS;
-        return sign(verifiedEvent);
+        return INVALID_HMAC;
     }
-    return INVALID_HMAC;
+
+    VerifiedMotionEvent verifiedEvent =
+            verifiedMotionEventFromMotionEntry(motionEntry, dispatchEntry.rawTransform);
+    verifiedEvent.actionMasked = actionMasked;
+    verifiedEvent.flags = dispatchEntry.resolvedFlags & VERIFIED_MOTION_EVENT_FLAGS;
+    return sign(verifiedEvent);
 }
 
 const std::array<uint8_t, 32> InputDispatcher::getSignature(
@@ -3338,27 +3456,30 @@
 void InputDispatcher::finishDispatchCycleLocked(nsecs_t currentTime,
                                                 const sp<Connection>& connection, uint32_t seq,
                                                 bool handled, nsecs_t consumeTime) {
-#if DEBUG_DISPATCH_CYCLE
-    ALOGD("channel '%s' ~ finishDispatchCycle - seq=%u, handled=%s",
-          connection->getInputChannelName().c_str(), seq, toString(handled));
-#endif
+    if (DEBUG_DISPATCH_CYCLE) {
+        ALOGD("channel '%s' ~ finishDispatchCycle - seq=%u, handled=%s",
+              connection->getInputChannelName().c_str(), seq, toString(handled));
+    }
 
-    if (connection->status == Connection::STATUS_BROKEN ||
-        connection->status == Connection::STATUS_ZOMBIE) {
+    if (connection->status == Connection::Status::BROKEN ||
+        connection->status == Connection::Status::ZOMBIE) {
         return;
     }
 
     // Notify other system components and prepare to start the next dispatch cycle.
-    onDispatchCycleFinishedLocked(currentTime, connection, seq, handled, consumeTime);
+    auto command = [this, currentTime, connection, seq, handled, consumeTime]() REQUIRES(mLock) {
+        doDispatchCycleFinishedCommand(currentTime, connection, seq, handled, consumeTime);
+    };
+    postCommandLocked(std::move(command));
 }
 
 void InputDispatcher::abortBrokenDispatchCycleLocked(nsecs_t currentTime,
                                                      const sp<Connection>& connection,
                                                      bool notify) {
-#if DEBUG_DISPATCH_CYCLE
-    ALOGD("channel '%s' ~ abortBrokenDispatchCycle - notify=%s",
-          connection->getInputChannelName().c_str(), toString(notify));
-#endif
+    if (DEBUG_DISPATCH_CYCLE) {
+        ALOGD("channel '%s' ~ abortBrokenDispatchCycle - notify=%s",
+              connection->getInputChannelName().c_str(), toString(notify));
+    }
 
     // Clear the dispatch queues.
     drainDispatchQueue(connection->outboundQueue);
@@ -3368,12 +3489,19 @@
 
     // The connection appears to be unrecoverably broken.
     // Ignore already broken or zombie connections.
-    if (connection->status == Connection::STATUS_NORMAL) {
-        connection->status = Connection::STATUS_BROKEN;
+    if (connection->status == Connection::Status::NORMAL) {
+        connection->status = Connection::Status::BROKEN;
 
         if (notify) {
             // Notify other system components.
-            onDispatchCycleBrokenLocked(currentTime, connection);
+            ALOGE("channel '%s' ~ Channel is unrecoverably broken and will be disposed!",
+                  connection->getInputChannelName().c_str());
+
+            auto command = [this, connection]() REQUIRES(mLock) {
+                scoped_unlock unlock(mLock);
+                mPolicy->notifyInputChannelBroken(connection->inputChannel->getConnectionToken());
+            };
+            postCommandLocked(std::move(command));
         }
     }
 }
@@ -3440,7 +3568,7 @@
             gotOne = true;
         }
         if (gotOne) {
-            runCommandsLockedInterruptible();
+            runCommandsLockedInterruptable();
             if (status == WOULD_BLOCK) {
                 return 1;
             }
@@ -3478,15 +3606,7 @@
 
 void InputDispatcher::synthesizeCancelationEventsForMonitorsLocked(
         const CancelationOptions& options) {
-    synthesizeCancelationEventsForMonitorsLocked(options, mGlobalMonitorsByDisplay);
-    synthesizeCancelationEventsForMonitorsLocked(options, mGestureMonitorsByDisplay);
-}
-
-void InputDispatcher::synthesizeCancelationEventsForMonitorsLocked(
-        const CancelationOptions& options,
-        std::unordered_map<int32_t, std::vector<Monitor>>& monitorsByDisplay) {
-    for (const auto& it : monitorsByDisplay) {
-        const std::vector<Monitor>& monitors = it.second;
+    for (const auto& [_, monitors] : mGlobalMonitorsByDisplay) {
         for (const Monitor& monitor : monitors) {
             synthesizeCancelationEventsForInputChannelLocked(monitor.inputChannel, options);
         }
@@ -3505,7 +3625,7 @@
 
 void InputDispatcher::synthesizeCancelationEventsForConnectionLocked(
         const sp<Connection>& connection, const CancelationOptions& options) {
-    if (connection->status == Connection::STATUS_BROKEN) {
+    if (connection->status == Connection::Status::BROKEN) {
         return;
     }
 
@@ -3517,12 +3637,16 @@
     if (cancelationEvents.empty()) {
         return;
     }
-#if DEBUG_OUTBOUND_EVENT_DETAILS
-    ALOGD("channel '%s' ~ Synthesized %zu cancelation events to bring channel back in sync "
-          "with reality: %s, mode=%d.",
-          connection->getInputChannelName().c_str(), cancelationEvents.size(), options.reason,
-          options.mode);
-#endif
+    if (DEBUG_OUTBOUND_EVENT_DETAILS) {
+        ALOGD("channel '%s' ~ Synthesized %zu cancelation events to bring channel back in sync "
+              "with reality: %s, mode=%d.",
+              connection->getInputChannelName().c_str(), cancelationEvents.size(), options.reason,
+              options.mode);
+    }
+
+    std::string reason = std::string("reason=").append(options.reason);
+    android_log_event_list(LOGTAG_INPUT_CANCEL)
+            << connection->getInputChannelName().c_str() << reason << LOG_ID_EVENTS;
 
     InputTarget target;
     sp<WindowInfoHandle> windowHandle =
@@ -3549,17 +3673,18 @@
                 break;
             }
             case EventEntry::Type::FOCUS:
+            case EventEntry::Type::TOUCH_MODE_CHANGED:
             case EventEntry::Type::POINTER_CAPTURE_CHANGED:
             case EventEntry::Type::DRAG: {
                 LOG_ALWAYS_FATAL("Canceling %s events is not supported",
-                                 NamedEnum::string(cancelationEventEntry->type).c_str());
+                                 ftl::enum_string(cancelationEventEntry->type).c_str());
                 break;
             }
             case EventEntry::Type::CONFIGURATION_CHANGED:
             case EventEntry::Type::DEVICE_RESET:
             case EventEntry::Type::SENSOR: {
                 LOG_ALWAYS_FATAL("%s event should not be found inside Connections's queue",
-                                 NamedEnum::string(cancelationEventEntry->type).c_str());
+                                 ftl::enum_string(cancelationEventEntry->type).c_str());
                 break;
             }
         }
@@ -3573,7 +3698,7 @@
 
 void InputDispatcher::synthesizePointerDownEventsForConnectionLocked(
         const sp<Connection>& connection) {
-    if (connection->status == Connection::STATUS_BROKEN) {
+    if (connection->status == Connection::Status::BROKEN) {
         return;
     }
 
@@ -3586,10 +3711,10 @@
         return;
     }
 
-#if DEBUG_OUTBOUND_EVENT_DETAILS
+    if (DEBUG_OUTBOUND_EVENT_DETAILS) {
         ALOGD("channel '%s' ~ Synthesized %zu down events to ensure consistent event stream.",
               connection->getInputChannelName().c_str(), downEvents.size());
-#endif
+    }
 
     InputTarget target;
     sp<WindowInfoHandle> windowHandle =
@@ -3612,13 +3737,14 @@
 
             case EventEntry::Type::KEY:
             case EventEntry::Type::FOCUS:
+            case EventEntry::Type::TOUCH_MODE_CHANGED:
             case EventEntry::Type::CONFIGURATION_CHANGED:
             case EventEntry::Type::DEVICE_RESET:
             case EventEntry::Type::POINTER_CAPTURE_CHANGED:
             case EventEntry::Type::SENSOR:
             case EventEntry::Type::DRAG: {
                 LOG_ALWAYS_FATAL("%s event should not be found inside Connections's queue",
-                                 NamedEnum::string(downEventEntry->type).c_str());
+                                 ftl::enum_string(downEventEntry->type).c_str());
                 break;
             }
         }
@@ -3721,7 +3847,7 @@
                                           originalMotionEntry.xCursorPosition,
                                           originalMotionEntry.yCursorPosition,
                                           originalMotionEntry.downTime, splitPointerCount,
-                                          splitPointerProperties, splitPointerCoords, 0, 0);
+                                          splitPointerProperties, splitPointerCoords);
 
     if (originalMotionEntry.injectionState) {
         splitMotionEntry->injectionState = originalMotionEntry.injectionState;
@@ -3732,11 +3858,11 @@
 }
 
 void InputDispatcher::notifyConfigurationChanged(const NotifyConfigurationChangedArgs* args) {
-#if DEBUG_INBOUND_EVENT_DETAILS
-    ALOGD("notifyConfigurationChanged - eventTime=%" PRId64, args->eventTime);
-#endif
+    if (DEBUG_INBOUND_EVENT_DETAILS) {
+        ALOGD("notifyConfigurationChanged - eventTime=%" PRId64, args->eventTime);
+    }
 
-    bool needWake;
+    bool needWake = false;
     { // acquire lock
         std::scoped_lock _l(mLock);
 
@@ -3788,14 +3914,14 @@
 }
 
 void InputDispatcher::notifyKey(const NotifyKeyArgs* args) {
-#if DEBUG_INBOUND_EVENT_DETAILS
-    ALOGD("notifyKey - eventTime=%" PRId64 ", deviceId=%d, source=0x%x, displayId=%" PRId32
-          "policyFlags=0x%x, action=0x%x, "
-          "flags=0x%x, keyCode=0x%x, scanCode=0x%x, metaState=0x%x, downTime=%" PRId64,
-          args->eventTime, args->deviceId, args->source, args->displayId, args->policyFlags,
-          args->action, args->flags, args->keyCode, args->scanCode, args->metaState,
-          args->downTime);
-#endif
+    if (DEBUG_INBOUND_EVENT_DETAILS) {
+        ALOGD("notifyKey - eventTime=%" PRId64 ", deviceId=%d, source=0x%x, displayId=%" PRId32
+              "policyFlags=0x%x, action=0x%x, "
+              "flags=0x%x, keyCode=0x%x, scanCode=0x%x, metaState=0x%x, downTime=%" PRId64,
+              args->eventTime, args->deviceId, args->source, args->displayId, args->policyFlags,
+              args->action, args->flags, args->keyCode, args->scanCode, args->metaState,
+              args->downTime);
+    }
     if (!validateKeyEvent(args->action)) {
         return;
     }
@@ -3831,7 +3957,7 @@
               std::to_string(t.duration().count()).c_str());
     }
 
-    bool needWake;
+    bool needWake = false;
     { // acquire lock
         mLock.lock();
 
@@ -3866,33 +3992,33 @@
 }
 
 void InputDispatcher::notifyMotion(const NotifyMotionArgs* args) {
-#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, "
-          "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);
-    for (uint32_t i = 0; i < args->pointerCount; i++) {
-        ALOGD("  Pointer %d: id=%d, toolType=%d, "
-              "x=%f, y=%f, pressure=%f, size=%f, "
-              "touchMajor=%f, touchMinor=%f, toolMajor=%f, toolMinor=%f, "
-              "orientation=%f",
-              i, args->pointerProperties[i].id, args->pointerProperties[i].toolType,
-              args->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_X),
-              args->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_Y),
-              args->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_PRESSURE),
-              args->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_SIZE),
-              args->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR),
-              args->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR),
-              args->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR),
-              args->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR),
-              args->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_ORIENTATION));
+    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, "
+              "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);
+        for (uint32_t i = 0; i < args->pointerCount; i++) {
+            ALOGD("  Pointer %d: id=%d, toolType=%d, "
+                  "x=%f, y=%f, pressure=%f, size=%f, "
+                  "touchMajor=%f, touchMinor=%f, toolMajor=%f, toolMinor=%f, "
+                  "orientation=%f",
+                  i, args->pointerProperties[i].id, args->pointerProperties[i].toolType,
+                  args->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_X),
+                  args->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_Y),
+                  args->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_PRESSURE),
+                  args->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_SIZE),
+                  args->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR),
+                  args->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR),
+                  args->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR),
+                  args->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR),
+                  args->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_ORIENTATION));
+        }
     }
-#endif
     if (!validateMotionEvent(args->action, args->actionButton, args->pointerCount,
                              args->pointerProperties)) {
         return;
@@ -3908,22 +4034,26 @@
               std::to_string(t.duration().count()).c_str());
     }
 
-    bool needWake;
+    bool needWake = false;
     { // acquire lock
         mLock.lock();
 
         if (shouldSendMotionToInputFilterLocked(args)) {
+            ui::Transform displayTransform;
+            if (const auto it = mDisplayInfos.find(args->displayId); it != mDisplayInfos.end()) {
+                displayTransform = it->second.transform;
+            }
+
             mLock.unlock();
 
             MotionEvent event;
-            ui::Transform transform;
             event.initialize(args->id, args->deviceId, args->source, args->displayId, INVALID_HMAC,
                              args->action, args->actionButton, args->flags, args->edgeFlags,
-                             args->metaState, args->buttonState, args->classification, transform,
-                             args->xPrecision, args->yPrecision, args->xCursorPosition,
-                             args->yCursorPosition, ui::Transform::ROT_0, INVALID_DISPLAY_SIZE,
-                             INVALID_DISPLAY_SIZE, args->downTime, args->eventTime,
-                             args->pointerCount, args->pointerProperties, args->pointerCoords);
+                             args->metaState, args->buttonState, args->classification,
+                             displayTransform, args->xPrecision, args->yPrecision,
+                             args->xCursorPosition, args->yCursorPosition, displayTransform,
+                             args->downTime, args->eventTime, args->pointerCount,
+                             args->pointerProperties, args->pointerCoords);
 
             policyFlags |= POLICY_FLAG_FILTERED;
             if (!mPolicy->filterInputEvent(&event, policyFlags)) {
@@ -3943,7 +4073,7 @@
                                               args->xPrecision, args->yPrecision,
                                               args->xCursorPosition, args->yCursorPosition,
                                               args->downTime, args->pointerCount,
-                                              args->pointerProperties, args->pointerCoords, 0, 0);
+                                              args->pointerProperties, args->pointerCoords);
 
         if (args->id != android::os::IInputConstants::INVALID_INPUT_EVENT_ID &&
             IdGenerator::getSource(args->id) == IdGenerator::Source::INPUT_READER &&
@@ -3962,14 +4092,14 @@
 }
 
 void InputDispatcher::notifySensor(const NotifySensorArgs* args) {
-#if DEBUG_INBOUND_EVENT_DETAILS
-    ALOGD("notifySensor - id=%" PRIx32 " eventTime=%" PRId64 ", deviceId=%d, source=0x%x, "
-          " sensorType=%s",
-          args->id, args->eventTime, args->deviceId, args->source,
-          NamedEnum::string(args->sensorType).c_str());
-#endif
+    if (DEBUG_INBOUND_EVENT_DETAILS) {
+        ALOGD("notifySensor - id=%" PRIx32 " eventTime=%" PRId64 ", deviceId=%d, source=0x%x, "
+              " sensorType=%s",
+              args->id, args->eventTime, args->deviceId, args->source,
+              ftl::enum_string(args->sensorType).c_str());
+    }
 
-    bool needWake;
+    bool needWake = false;
     { // acquire lock
         mLock.lock();
 
@@ -3990,10 +4120,10 @@
 }
 
 void InputDispatcher::notifyVibratorState(const NotifyVibratorStateArgs* args) {
-#if DEBUG_INBOUND_EVENT_DETAILS
-    ALOGD("notifyVibratorState - eventTime=%" PRId64 ", device=%d,  isOn=%d", args->eventTime,
-          args->deviceId, args->isOn);
-#endif
+    if (DEBUG_INBOUND_EVENT_DETAILS) {
+        ALOGD("notifyVibratorState - eventTime=%" PRId64 ", device=%d,  isOn=%d", args->eventTime,
+              args->deviceId, args->isOn);
+    }
     mPolicy->notifyVibratorState(args->deviceId, args->isOn);
 }
 
@@ -4002,11 +4132,11 @@
 }
 
 void InputDispatcher::notifySwitch(const NotifySwitchArgs* args) {
-#if DEBUG_INBOUND_EVENT_DETAILS
-    ALOGD("notifySwitch - eventTime=%" PRId64 ", policyFlags=0x%x, switchValues=0x%08x, "
-          "switchMask=0x%08x",
-          args->eventTime, args->policyFlags, args->switchValues, args->switchMask);
-#endif
+    if (DEBUG_INBOUND_EVENT_DETAILS) {
+        ALOGD("notifySwitch - eventTime=%" PRId64 ", policyFlags=0x%x, switchValues=0x%08x, "
+              "switchMask=0x%08x",
+              args->eventTime, args->policyFlags, args->switchValues, args->switchMask);
+    }
 
     uint32_t policyFlags = args->policyFlags;
     policyFlags |= POLICY_FLAG_TRUSTED;
@@ -4014,12 +4144,12 @@
 }
 
 void InputDispatcher::notifyDeviceReset(const NotifyDeviceResetArgs* args) {
-#if DEBUG_INBOUND_EVENT_DETAILS
-    ALOGD("notifyDeviceReset - eventTime=%" PRId64 ", deviceId=%d", args->eventTime,
-          args->deviceId);
-#endif
+    if (DEBUG_INBOUND_EVENT_DETAILS) {
+        ALOGD("notifyDeviceReset - eventTime=%" PRId64 ", deviceId=%d", args->eventTime,
+              args->deviceId);
+    }
 
-    bool needWake;
+    bool needWake = false;
     { // acquire lock
         std::scoped_lock _l(mLock);
 
@@ -4034,12 +4164,12 @@
 }
 
 void InputDispatcher::notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs* args) {
-#if DEBUG_INBOUND_EVENT_DETAILS
-    ALOGD("notifyPointerCaptureChanged - eventTime=%" PRId64 ", enabled=%s", args->eventTime,
-          args->request.enable ? "true" : "false");
-#endif
+    if (DEBUG_INBOUND_EVENT_DETAILS) {
+        ALOGD("notifyPointerCaptureChanged - eventTime=%" PRId64 ", enabled=%s", args->eventTime,
+              args->request.enable ? "true" : "false");
+    }
 
-    bool needWake;
+    bool needWake = false;
     { // acquire lock
         std::scoped_lock _l(mLock);
         auto entry = std::make_unique<PointerCaptureChangedEntry>(args->id, args->eventTime,
@@ -4055,11 +4185,11 @@
 InputEventInjectionResult InputDispatcher::injectInputEvent(
         const InputEvent* event, int32_t injectorPid, int32_t injectorUid,
         InputEventInjectionSync syncMode, std::chrono::milliseconds timeout, uint32_t policyFlags) {
-#if DEBUG_INBOUND_EVENT_DETAILS
-    ALOGD("injectInputEvent - eventType=%d, injectorPid=%d, injectorUid=%d, "
-          "syncMode=%d, timeout=%lld, policyFlags=0x%08x",
-          event->getType(), injectorPid, injectorUid, syncMode, timeout.count(), policyFlags);
-#endif
+    if (DEBUG_INBOUND_EVENT_DETAILS) {
+        ALOGD("injectInputEvent - eventType=%d, injectorPid=%d, injectorUid=%d, "
+              "syncMode=%d, timeout=%lld, policyFlags=0x%08x",
+              event->getType(), injectorPid, injectorUid, syncMode, timeout.count(), policyFlags);
+    }
     nsecs_t endTime = now() + std::chrono::duration_cast<std::chrono::nanoseconds>(timeout).count();
 
     policyFlags |= POLICY_FLAG_INJECTED;
@@ -4128,12 +4258,17 @@
 
         case AINPUT_EVENT_TYPE_MOTION: {
             const MotionEvent& motionEvent = static_cast<const MotionEvent&>(*event);
-            int32_t action = motionEvent.getAction();
-            size_t pointerCount = motionEvent.getPointerCount();
+            const int32_t action = motionEvent.getAction();
+            const bool isPointerEvent =
+                    isFromSource(event->getSource(), AINPUT_SOURCE_CLASS_POINTER);
+            // If a pointer event has no displayId specified, inject it to the default display.
+            const uint32_t displayId = isPointerEvent && (event->getDisplayId() == ADISPLAY_ID_NONE)
+                    ? ADISPLAY_ID_DEFAULT
+                    : event->getDisplayId();
+            const size_t pointerCount = motionEvent.getPointerCount();
             const PointerProperties* pointerProperties = motionEvent.getPointerProperties();
-            int32_t actionButton = motionEvent.getActionButton();
+            const int32_t actionButton = motionEvent.getActionButton();
             int32_t flags = motionEvent.getFlags();
-            int32_t displayId = motionEvent.getDisplayId();
             if (!validateMotionEvent(action, actionButton, pointerCount, pointerProperties)) {
                 return InputEventInjectionResult::FAILED;
             }
@@ -4158,8 +4293,8 @@
             std::unique_ptr<MotionEntry> injectedEntry =
                     std::make_unique<MotionEntry>(motionEvent.getId(), *sampleEventTimes,
                                                   resolvedDeviceId, motionEvent.getSource(),
-                                                  motionEvent.getDisplayId(), policyFlags, action,
-                                                  actionButton, flags, motionEvent.getMetaState(),
+                                                  displayId, policyFlags, action, actionButton,
+                                                  flags, motionEvent.getMetaState(),
                                                   motionEvent.getButtonState(),
                                                   motionEvent.getClassification(),
                                                   motionEvent.getEdgeFlags(),
@@ -4168,9 +4303,8 @@
                                                   motionEvent.getRawXCursorPosition(),
                                                   motionEvent.getRawYCursorPosition(),
                                                   motionEvent.getDownTime(), uint32_t(pointerCount),
-                                                  pointerProperties, samplePointerCoords,
-                                                  motionEvent.getXOffset(),
-                                                  motionEvent.getYOffset());
+                                                  pointerProperties, samplePointerCoords);
+            transformMotionEntryForInjectionLocked(*injectedEntry, motionEvent.getTransform());
             injectedEntries.push(std::move(injectedEntry));
             for (size_t i = motionEvent.getHistorySize(); i > 0; i--) {
                 sampleEventTimes += 1;
@@ -4178,9 +4312,8 @@
                 std::unique_ptr<MotionEntry> nextInjectedEntry =
                         std::make_unique<MotionEntry>(motionEvent.getId(), *sampleEventTimes,
                                                       resolvedDeviceId, motionEvent.getSource(),
-                                                      motionEvent.getDisplayId(), policyFlags,
-                                                      action, actionButton, flags,
-                                                      motionEvent.getMetaState(),
+                                                      displayId, policyFlags, action, actionButton,
+                                                      flags, motionEvent.getMetaState(),
                                                       motionEvent.getButtonState(),
                                                       motionEvent.getClassification(),
                                                       motionEvent.getEdgeFlags(),
@@ -4190,8 +4323,9 @@
                                                       motionEvent.getRawYCursorPosition(),
                                                       motionEvent.getDownTime(),
                                                       uint32_t(pointerCount), pointerProperties,
-                                                      samplePointerCoords, motionEvent.getXOffset(),
-                                                      motionEvent.getYOffset());
+                                                      samplePointerCoords);
+                transformMotionEntryForInjectionLocked(*nextInjectedEntry,
+                                                       motionEvent.getTransform());
                 injectedEntries.push(std::move(nextInjectedEntry));
             }
             break;
@@ -4237,10 +4371,10 @@
 
                 nsecs_t remainingTimeout = endTime - now();
                 if (remainingTimeout <= 0) {
-#if DEBUG_INJECTION
-                    ALOGD("injectInputEvent - Timed out waiting for injection result "
-                          "to become available.");
-#endif
+                    if (DEBUG_INJECTION) {
+                        ALOGD("injectInputEvent - Timed out waiting for injection result "
+                              "to become available.");
+                    }
                     injectionResult = InputEventInjectionResult::TIMED_OUT;
                     break;
                 }
@@ -4251,16 +4385,16 @@
             if (injectionResult == InputEventInjectionResult::SUCCEEDED &&
                 syncMode == InputEventInjectionSync::WAIT_FOR_FINISHED) {
                 while (injectionState->pendingForegroundDispatches != 0) {
-#if DEBUG_INJECTION
-                    ALOGD("injectInputEvent - Waiting for %d pending foreground dispatches.",
-                          injectionState->pendingForegroundDispatches);
-#endif
+                    if (DEBUG_INJECTION) {
+                        ALOGD("injectInputEvent - Waiting for %d pending foreground dispatches.",
+                              injectionState->pendingForegroundDispatches);
+                    }
                     nsecs_t remainingTimeout = endTime - now();
                     if (remainingTimeout <= 0) {
-#if DEBUG_INJECTION
-                        ALOGD("injectInputEvent - Timed out waiting for pending foreground "
-                              "dispatches to finish.");
-#endif
+                        if (DEBUG_INJECTION) {
+                            ALOGD("injectInputEvent - Timed out waiting for pending foreground "
+                                  "dispatches to finish.");
+                        }
                         injectionResult = InputEventInjectionResult::TIMED_OUT;
                         break;
                     }
@@ -4273,10 +4407,10 @@
         injectionState->release();
     } // release lock
 
-#if DEBUG_INJECTION
-    ALOGD("injectInputEvent - Finished with result %d. injectorPid=%d, injectorUid=%d",
-          injectionResult, injectorPid, injectorUid);
-#endif
+    if (DEBUG_INJECTION) {
+        ALOGD("injectInputEvent - Finished with result %d. injectorPid=%d, injectorUid=%d",
+              injectionResult, injectorPid, injectorUid);
+    }
 
     return injectionResult;
 }
@@ -4323,11 +4457,11 @@
                                          InputEventInjectionResult injectionResult) {
     InjectionState* injectionState = entry.injectionState;
     if (injectionState) {
-#if DEBUG_INJECTION
-        ALOGD("Setting input event injection result to %d.  "
-              "injectorPid=%d, injectorUid=%d",
-              injectionResult, injectionState->injectorPid, injectionState->injectorUid);
-#endif
+        if (DEBUG_INJECTION) {
+            ALOGD("Setting input event injection result to %d.  "
+                  "injectorPid=%d, injectorUid=%d",
+                  injectionResult, injectionState->injectorPid, injectionState->injectorUid);
+        }
 
         if (injectionState->injectionIsAsync && !(entry.policyFlags & POLICY_FLAG_FILTERED)) {
             // Log the outcome since the injector did not wait for the injection result.
@@ -4355,6 +4489,21 @@
     }
 }
 
+void InputDispatcher::transformMotionEntryForInjectionLocked(
+        MotionEntry& entry, const ui::Transform& injectedTransform) const {
+    // Input injection works in the logical display coordinate space, but the input pipeline works
+    // display space, so we need to transform the injected events accordingly.
+    const auto it = mDisplayInfos.find(entry.displayId);
+    if (it == mDisplayInfos.end()) return;
+    const auto& transformToDisplay = it->second.transform.inverse() * injectedTransform;
+
+    for (uint32_t i = 0; i < entry.pointerCount; i++) {
+        entry.pointerCoords[i] =
+                MotionEvent::calculateTransformedCoords(entry.source, transformToDisplay,
+                                                        entry.pointerCoords[i]);
+    }
+}
+
 void InputDispatcher::incrementPendingForegroundDispatches(EventEntry& entry) {
     InjectionState* injectionState = entry.injectionState;
     if (injectionState) {
@@ -4439,7 +4588,7 @@
 bool InputDispatcher::hasResponsiveConnectionLocked(WindowInfoHandle& windowHandle) const {
     sp<Connection> connection = getConnectionLocked(windowHandle.getToken());
     const bool noInputChannel =
-            windowHandle.getInfo()->inputFeatures.test(WindowInfo::Feature::NO_INPUT_CHANNEL);
+            windowHandle.getInfo()->inputConfig.test(WindowInfo::InputConfig::NO_INPUT_CHANNEL);
     if (connection != nullptr && noInputChannel) {
         ALOGW("%s has feature NO_INPUT_CHANNEL, but it matched to connection %s",
               windowHandle.getName().c_str(), connection->inputChannel->getName().c_str());
@@ -4487,12 +4636,12 @@
     std::vector<sp<WindowInfoHandle>> newHandles;
     for (const sp<WindowInfoHandle>& handle : windowInfoHandles) {
         const WindowInfo* info = handle->getInfo();
-        if ((getInputChannelLocked(handle->getToken()) == nullptr &&
-             info->portalToDisplayId == ADISPLAY_ID_NONE)) {
+        if (getInputChannelLocked(handle->getToken()) == nullptr) {
             const bool noInputChannel =
-                    info->inputFeatures.test(WindowInfo::Feature::NO_INPUT_CHANNEL);
-            const bool canReceiveInput = !info->flags.test(WindowInfo::Flag::NOT_TOUCHABLE) ||
-                    !info->flags.test(WindowInfo::Flag::NOT_FOCUSABLE);
+                    info->inputConfig.test(WindowInfo::InputConfig::NO_INPUT_CHANNEL);
+            const bool canReceiveInput =
+                    !info->inputConfig.test(WindowInfo::InputConfig::NOT_TOUCHABLE) ||
+                    !info->inputConfig.test(WindowInfo::InputConfig::NOT_FOCUSABLE);
             if (canReceiveInput && !noInputChannel) {
                 ALOGV("Window handle %s has no registered input channel",
                       handle->getName().c_str());
@@ -4522,6 +4671,7 @@
 
 void InputDispatcher::setInputWindows(
         const std::unordered_map<int32_t, std::vector<sp<WindowInfoHandle>>>& handlesPerDisplay) {
+    // TODO(b/198444055): Remove setInputWindows from InputDispatcher.
     { // acquire lock
         std::scoped_lock _l(mLock);
         for (const auto& [displayId, handles] : handlesPerDisplay) {
@@ -4549,15 +4699,31 @@
         ALOGD("setInputWindows displayId=%" PRId32 " %s", displayId, windowList.c_str());
     }
 
-    // Ensure all tokens are null if the window has feature NO_INPUT_CHANNEL
+    // Check preconditions for new input windows
     for (const sp<WindowInfoHandle>& window : windowInfoHandles) {
-        const bool noInputWindow =
-                window->getInfo()->inputFeatures.test(WindowInfo::Feature::NO_INPUT_CHANNEL);
+        const WindowInfo& info = *window->getInfo();
+
+        // Ensure all tokens are null if the window has feature NO_INPUT_CHANNEL
+        const bool noInputWindow = info.inputConfig.test(WindowInfo::InputConfig::NO_INPUT_CHANNEL);
         if (noInputWindow && window->getToken() != nullptr) {
             ALOGE("%s has feature NO_INPUT_WINDOW, but a non-null token. Clearing",
                   window->getName().c_str());
             window->releaseChannel();
         }
+
+        // Ensure all spy windows are trusted overlays
+        LOG_ALWAYS_FATAL_IF(info.isSpy() &&
+                                    !info.inputConfig.test(
+                                            WindowInfo::InputConfig::TRUSTED_OVERLAY),
+                            "%s has feature SPY, but is not a trusted overlay.",
+                            window->getName().c_str());
+
+        // Ensure all stylus interceptors are trusted overlays
+        LOG_ALWAYS_FATAL_IF(info.interceptsStylus() &&
+                                    !info.inputConfig.test(
+                                            WindowInfo::InputConfig::TRUSTED_OVERLAY),
+                            "%s has feature INTERCEPTS_STYLUS, but is not a trusted overlay.",
+                            window->getName().c_str());
     }
 
     // Copy old handles for release if they are no longer present.
@@ -4602,6 +4768,21 @@
                     CancelationOptions options(CancelationOptions::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 &&
+                        touchedWindow.windowHandle->getInfo()->inputConfig.test(
+                                gui::WindowInfo::InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER)) {
+                        sp<WindowInfoHandle> wallpaper = state.getWallpaperWindow();
+                        if (wallpaper != nullptr) {
+                            sp<Connection> wallpaperConnection =
+                                    getConnectionLocked(wallpaper->getToken());
+                            if (wallpaperConnection != nullptr) {
+                                synthesizeCancelationEventsForConnectionLocked(wallpaperConnection,
+                                                                               options);
+                            }
+                        }
+                    }
                 }
                 state.windows.erase(state.windows.begin() + i);
             } else {
@@ -4618,21 +4799,19 @@
         }
     }
 
-    if (isPerWindowInputRotationEnabled()) {
-        // Determine if the orientation of any of the input windows have changed, and cancel all
-        // pointer events if necessary.
-        for (const sp<WindowInfoHandle>& oldWindowHandle : oldWindowHandles) {
-            const sp<WindowInfoHandle> newWindowHandle = getWindowHandleLocked(oldWindowHandle);
-            if (newWindowHandle != nullptr &&
-                newWindowHandle->getInfo()->transform.getOrientation() !=
-                        oldWindowOrientations[oldWindowHandle->getId()]) {
-                std::shared_ptr<InputChannel> inputChannel =
-                        getInputChannelLocked(newWindowHandle->getToken());
-                if (inputChannel != nullptr) {
-                    CancelationOptions options(CancelationOptions::CANCEL_POINTER_EVENTS,
-                                               "touched window's orientation changed");
-                    synthesizeCancelationEventsForInputChannelLocked(inputChannel, options);
-                }
+    // Determine if the orientation of any of the input windows have changed, and cancel all
+    // pointer events if necessary.
+    for (const sp<WindowInfoHandle>& oldWindowHandle : oldWindowHandles) {
+        const sp<WindowInfoHandle> newWindowHandle = getWindowHandleLocked(oldWindowHandle);
+        if (newWindowHandle != nullptr &&
+            newWindowHandle->getInfo()->transform.getOrientation() !=
+                    oldWindowOrientations[oldWindowHandle->getId()]) {
+            std::shared_ptr<InputChannel> inputChannel =
+                    getInputChannelLocked(newWindowHandle->getToken());
+            if (inputChannel != nullptr) {
+                CancelationOptions options(CancelationOptions::CANCEL_POINTER_EVENTS,
+                                           "touched window's orientation changed");
+                synthesizeCancelationEventsForInputChannelLocked(inputChannel, options);
             }
         }
     }
@@ -4647,18 +4826,6 @@
                 ALOGD("Window went away: %s", oldWindowHandle->getName().c_str());
             }
             oldWindowHandle->releaseChannel();
-            // To avoid making too many calls into the compat framework, only
-            // check for window flags when windows are going away.
-            // TODO(b/157929241) : delete this. This is only needed temporarily
-            // in order to gather some data about the flag usage
-            if (oldWindowHandle->getInfo()->flags.test(WindowInfo::Flag::SLIPPERY)) {
-                ALOGW("%s has FLAG_SLIPPERY. Please report this in b/157929241",
-                      oldWindowHandle->getName().c_str());
-                if (mCompatService != nullptr) {
-                    mCompatService->reportChangeByUid(IInputConstants::BLOCK_FLAG_SLIPPERY,
-                                                      oldWindowHandle->getInfo()->ownerUid);
-                }
-            }
         }
     }
 }
@@ -4733,7 +4900,7 @@
 
             // Find new focused window and validate
             sp<IBinder> newFocusedWindowToken = mFocusResolver.getFocusedWindowToken(displayId);
-            notifyFocusChangedLocked(oldFocusedWindowToken, newFocusedWindowToken);
+            sendFocusChangedCommandLocked(oldFocusedWindowToken, newFocusedWindowToken);
 
             if (newFocusedWindowToken == nullptr) {
                 ALOGW("Focused display #%" PRId32 " does not have a focused window.", displayId);
@@ -4809,9 +4976,50 @@
     mLooper->wake();
 }
 
-void InputDispatcher::setInTouchMode(bool inTouchMode) {
-    std::scoped_lock lock(mLock);
-    mInTouchMode = inTouchMode;
+bool InputDispatcher::setInTouchMode(bool inTouchMode, int32_t pid, int32_t uid,
+                                     bool hasPermission) {
+    bool needWake = false;
+    {
+        std::scoped_lock lock(mLock);
+        if (mInTouchMode == inTouchMode) {
+            return false;
+        }
+        if (DEBUG_TOUCH_MODE) {
+            ALOGD("Request to change touch mode from %s to %s (calling pid=%d, uid=%d, "
+                  "hasPermission=%s)",
+                  toString(mInTouchMode), toString(inTouchMode), pid, uid, toString(hasPermission));
+        }
+        if (!hasPermission) {
+            const sp<IBinder> focusedToken =
+                    mFocusResolver.getFocusedWindowToken(mFocusedDisplayId);
+
+            //  TODO(b/218541064): if no window is currently focused, then we need to check the last
+            //      interacted window (within 1 second timeout). We should allow touch mode change
+            //      if the last interacted window owner's pid/uid match the calling ones.
+            if (focusedToken == nullptr) {
+                return false;
+            }
+            const sp<WindowInfoHandle> windowHandle = getWindowHandleLocked(focusedToken);
+            if (windowHandle == nullptr) {
+                return false;
+            }
+            const WindowInfo* windowInfo = windowHandle->getInfo();
+            if (pid != windowInfo->ownerPid || uid != windowInfo->ownerUid) {
+                return false;
+            }
+        }
+
+        // TODO(b/198499018): Store touch mode per display.
+        mInTouchMode = inTouchMode;
+
+        auto entry = std::make_unique<TouchModeEntry>(mIdGenerator.nextId(), now(), inTouchMode);
+        needWake = enqueueInboundEventLocked(std::move(entry));
+    } // release lock
+
+    if (needWake) {
+        mLooper->wake();
+    }
+    return true;
 }
 
 void InputDispatcher::setMaximumObscuringOpacityForTouch(float opacity) {
@@ -4879,9 +5087,11 @@
         state->removeWindowByToken(fromToken);
 
         // Add new window.
-        int32_t newTargetFlags = oldTargetFlags &
-                (InputTarget::FLAG_FOREGROUND | InputTarget::FLAG_SPLIT |
-                 InputTarget::FLAG_DISPATCH_AS_IS);
+        int32_t newTargetFlags =
+                oldTargetFlags & (InputTarget::FLAG_SPLIT | InputTarget::FLAG_DISPATCH_AS_IS);
+        if (canReceiveForegroundTouches(*toWindowHandle->getInfo())) {
+            newTargetFlags |= InputTarget::FLAG_FOREGROUND;
+        }
         state->addOrUpdateWindow(toWindowHandle, newTargetFlags, pointerIds);
 
         // Store the dragging window.
@@ -5030,14 +5240,6 @@
             } else {
                 dump += INDENT3 "Windows: <none>\n";
             }
-            if (!state.portalWindows.empty()) {
-                dump += INDENT3 "Portal windows:\n";
-                for (size_t i = 0; i < state.portalWindows.size(); i++) {
-                    const sp<WindowInfoHandle> portalWindowHandle = state.portalWindows[i];
-                    dump += StringPrintf(INDENT4 "%zu: name='%s'\n", i,
-                                         portalWindowHandle->getName().c_str());
-                }
-            }
         }
     } else {
         dump += INDENT "TouchStates: <no displays touched>\n";
@@ -5049,9 +5251,17 @@
     }
 
     if (!mWindowHandlesByDisplay.empty()) {
-        for (auto& it : mWindowHandlesByDisplay) {
-            const std::vector<sp<WindowInfoHandle>> windowHandles = it.second;
-            dump += StringPrintf(INDENT "Display: %" PRId32 "\n", it.first);
+        for (const auto& [displayId, windowHandles] : mWindowHandlesByDisplay) {
+            dump += StringPrintf(INDENT "Display: %" PRId32 "\n", displayId);
+            if (const auto& it = mDisplayInfos.find(displayId); it != mDisplayInfos.end()) {
+                const auto& displayInfo = it->second;
+                dump += StringPrintf(INDENT2 "logicalSize=%dx%d\n", displayInfo.logicalWidth,
+                                     displayInfo.logicalHeight);
+                displayInfo.transform.dump(dump, "transform", INDENT4);
+            } else {
+                dump += INDENT2 "No DisplayInfo found!\n";
+            }
+
             if (!windowHandles.empty()) {
                 dump += INDENT2 "Windows:\n";
                 for (size_t i = 0; i < windowHandles.size(); i++) {
@@ -5059,38 +5269,27 @@
                     const WindowInfo* windowInfo = windowHandle->getInfo();
 
                     dump += StringPrintf(INDENT3 "%zu: name='%s', id=%" PRId32 ", displayId=%d, "
-                                                 "portalToDisplayId=%d, paused=%s, focusable=%s, "
-                                                 "hasWallpaper=%s, visible=%s, alpha=%.2f, "
-                                                 "flags=%s, type=%s, "
+                                                 "inputConfig=%s, alpha=%.2f, "
                                                  "frame=[%d,%d][%d,%d], globalScale=%f, "
                                                  "applicationInfo.name=%s, "
                                                  "applicationInfo.token=%s, "
                                                  "touchableRegion=",
                                          i, windowInfo->name.c_str(), windowInfo->id,
-                                         windowInfo->displayId, windowInfo->portalToDisplayId,
-                                         toString(windowInfo->paused),
-                                         toString(windowInfo->focusable),
-                                         toString(windowInfo->hasWallpaper),
-                                         toString(windowInfo->visible), windowInfo->alpha,
-                                         windowInfo->flags.string().c_str(),
-                                         NamedEnum::string(windowInfo->type).c_str(),
-                                         windowInfo->frameLeft, windowInfo->frameTop,
-                                         windowInfo->frameRight, windowInfo->frameBottom,
-                                         windowInfo->globalScaleFactor,
+                                         windowInfo->displayId,
+                                         windowInfo->inputConfig.string().c_str(),
+                                         windowInfo->alpha, windowInfo->frameLeft,
+                                         windowInfo->frameTop, windowInfo->frameRight,
+                                         windowInfo->frameBottom, windowInfo->globalScaleFactor,
                                          windowInfo->applicationInfo.name.c_str(),
                                          toString(windowInfo->applicationInfo.token).c_str());
                     dump += dumpRegion(windowInfo->touchableRegion);
-                    dump += StringPrintf(", inputFeatures=%s",
-                                         windowInfo->inputFeatures.string().c_str());
                     dump += StringPrintf(", ownerPid=%d, ownerUid=%d, dispatchingTimeout=%" PRId64
-                                         "ms, trustedOverlay=%s, hasToken=%s, "
-                                         "touchOcclusionMode=%s, displayOrientation=%d\n",
+                                         "ms, hasToken=%s, "
+                                         "touchOcclusionMode=%s\n",
                                          windowInfo->ownerPid, windowInfo->ownerUid,
                                          millis(windowInfo->dispatchingTimeout),
-                                         toString(windowInfo->trustedOverlay),
                                          toString(windowInfo->token != nullptr),
-                                         toString(windowInfo->touchOcclusionMode).c_str(),
-                                         windowInfo->displayOrientation);
+                                         toString(windowInfo->touchOcclusionMode).c_str());
                     windowInfo->transform.dump(dump, "transform", INDENT4);
                 }
             } else {
@@ -5101,22 +5300,16 @@
         dump += INDENT "Displays: <none>\n";
     }
 
-    if (!mGlobalMonitorsByDisplay.empty() || !mGestureMonitorsByDisplay.empty()) {
-        for (auto& it : mGlobalMonitorsByDisplay) {
-            const std::vector<Monitor>& monitors = it.second;
-            dump += StringPrintf(INDENT "Global monitors in display %" PRId32 ":\n", it.first);
-            dumpMonitors(dump, monitors);
-        }
-        for (auto& it : mGestureMonitorsByDisplay) {
-            const std::vector<Monitor>& monitors = it.second;
-            dump += StringPrintf(INDENT "Gesture monitors in display %" PRId32 ":\n", it.first);
+    if (!mGlobalMonitorsByDisplay.empty()) {
+        for (const auto& [displayId, monitors] : mGlobalMonitorsByDisplay) {
+            dump += StringPrintf(INDENT "Global monitors on display %d:\n", displayId);
             dumpMonitors(dump, monitors);
         }
     } else {
-        dump += INDENT "Monitors: <none>\n";
+        dump += INDENT "Global Monitors: <none>\n";
     }
 
-    nsecs_t currentTime = now();
+    const nsecs_t currentTime = now();
 
     // Dump recently dispatched or dropped events from oldest to newest.
     if (!mRecentQueue.empty()) {
@@ -5165,6 +5358,12 @@
         dump += INDENT "ReplacedKeys: <empty>\n";
     }
 
+    if (!mCommandQueue.empty()) {
+        dump += StringPrintf(INDENT "CommandQueue: size=%zu\n", mCommandQueue.size());
+    } else {
+        dump += INDENT "CommandQueue: <empty>\n";
+    }
+
     if (!mConnectionsByToken.empty()) {
         dump += INDENT "Connections:\n";
         for (const auto& [token, connection] : mConnectionsByToken) {
@@ -5172,7 +5371,8 @@
                                          "status=%s, monitor=%s, responsive=%s\n",
                                  connection->inputChannel->getFd().get(),
                                  connection->getInputChannelName().c_str(),
-                                 connection->getWindowName().c_str(), connection->getStatusLabel(),
+                                 connection->getWindowName().c_str(),
+                                 ftl::enum_string(connection->status).c_str(),
                                  toString(connection->monitor), toString(connection->responsive));
 
             if (!connection->outboundQueue.empty()) {
@@ -5231,9 +5431,9 @@
 };
 
 Result<std::unique_ptr<InputChannel>> InputDispatcher::createInputChannel(const std::string& name) {
-#if DEBUG_CHANNEL_CREATION
-    ALOGD("channel '%s' ~ createInputChannel", name.c_str());
-#endif
+    if (DEBUG_CHANNEL_CREATION) {
+        ALOGD("channel '%s' ~ createInputChannel", name.c_str());
+    }
 
     std::unique_ptr<InputChannel> serverChannel;
     std::unique_ptr<InputChannel> clientChannel;
@@ -5267,7 +5467,6 @@
 }
 
 Result<std::unique_ptr<InputChannel>> InputDispatcher::createInputMonitor(int32_t displayId,
-                                                                          bool isGestureMonitor,
                                                                           const std::string& name,
                                                                           int32_t pid) {
     std::shared_ptr<InputChannel> serverChannel;
@@ -5296,13 +5495,9 @@
         std::function<int(int events)> callback = std::bind(&InputDispatcher::handleReceiveCallback,
                                                             this, std::placeholders::_1, token);
 
-        auto& monitorsByDisplay =
-                isGestureMonitor ? mGestureMonitorsByDisplay : mGlobalMonitorsByDisplay;
-        monitorsByDisplay[displayId].emplace_back(serverChannel, pid);
+        mGlobalMonitorsByDisplay[displayId].emplace_back(serverChannel, pid);
 
         mLooper->addFd(fd, 0, ALOOPER_EVENT_INPUT, new LooperEventCallback(callback), nullptr);
-        ALOGI("Created monitor %s for display %" PRId32 ", gesture=%s, pid=%" PRId32, name.c_str(),
-              displayId, toString(isGestureMonitor), pid);
     }
 
     // Wake the looper because some connections have changed.
@@ -5345,31 +5540,19 @@
     nsecs_t currentTime = now();
     abortBrokenDispatchCycleLocked(currentTime, connection, notify);
 
-    connection->status = Connection::STATUS_ZOMBIE;
+    connection->status = Connection::Status::ZOMBIE;
     return OK;
 }
 
 void InputDispatcher::removeMonitorChannelLocked(const sp<IBinder>& connectionToken) {
-    removeMonitorChannelLocked(connectionToken, mGlobalMonitorsByDisplay);
-    removeMonitorChannelLocked(connectionToken, mGestureMonitorsByDisplay);
-}
+    for (auto it = mGlobalMonitorsByDisplay.begin(); it != mGlobalMonitorsByDisplay.end();) {
+        auto& [displayId, monitors] = *it;
+        std::erase_if(monitors, [connectionToken](const Monitor& monitor) {
+            return monitor.inputChannel->getConnectionToken() == connectionToken;
+        });
 
-void InputDispatcher::removeMonitorChannelLocked(
-        const sp<IBinder>& connectionToken,
-        std::unordered_map<int32_t, std::vector<Monitor>>& monitorsByDisplay) {
-    for (auto it = monitorsByDisplay.begin(); it != monitorsByDisplay.end();) {
-        std::vector<Monitor>& monitors = it->second;
-        const size_t numMonitors = monitors.size();
-        for (size_t i = 0; i < numMonitors; i++) {
-            if (monitors[i].inputChannel->getConnectionToken() == connectionToken) {
-                ALOGI("Erasing monitor %s on display %" PRId32 ", pid=%" PRId32,
-                      monitors[i].inputChannel->getName().c_str(), it->first, monitors[i].pid);
-                monitors.erase(monitors.begin() + i);
-                break;
-            }
-        }
         if (monitors.empty()) {
-            it = monitorsByDisplay.erase(it);
+            it = mGlobalMonitorsByDisplay.erase(it);
         } else {
             ++it;
         }
@@ -5377,61 +5560,45 @@
 }
 
 status_t InputDispatcher::pilferPointers(const sp<IBinder>& token) {
-    { // acquire lock
-        std::scoped_lock _l(mLock);
-        std::optional<int32_t> foundDisplayId = findGestureMonitorDisplayByTokenLocked(token);
+    std::scoped_lock _l(mLock);
 
-        if (!foundDisplayId) {
-            ALOGW("Attempted to pilfer pointers from an un-registered monitor or invalid token");
-            return BAD_VALUE;
-        }
-        int32_t displayId = foundDisplayId.value();
-
-        std::unordered_map<int32_t, TouchState>::iterator stateIt =
-                mTouchStatesByDisplay.find(displayId);
-        if (stateIt == mTouchStatesByDisplay.end()) {
-            ALOGW("Failed to pilfer pointers: no pointers on display %" PRId32 ".", displayId);
-            return BAD_VALUE;
-        }
-
-        TouchState& state = stateIt->second;
-        std::shared_ptr<InputChannel> requestingChannel;
-        std::optional<int32_t> foundDeviceId;
-        for (const TouchedMonitor& touchedMonitor : state.gestureMonitors) {
-            if (touchedMonitor.monitor.inputChannel->getConnectionToken() == token) {
-                requestingChannel = touchedMonitor.monitor.inputChannel;
-                foundDeviceId = state.deviceId;
-            }
-        }
-        if (!foundDeviceId || !state.down) {
-            ALOGW("Attempted to pilfer points from a monitor without any on-going pointer streams."
-                  " Ignoring.");
-            return BAD_VALUE;
-        }
-        int32_t deviceId = foundDeviceId.value();
-
-        // Send cancel events to all the input channels we're stealing from.
-        CancelationOptions options(CancelationOptions::CANCEL_POINTER_EVENTS,
-                                   "gesture monitor stole pointer stream");
-        options.deviceId = deviceId;
-        options.displayId = displayId;
-        std::string canceledWindows = "[";
-        for (const TouchedWindow& window : state.windows) {
-            std::shared_ptr<InputChannel> channel =
-                    getInputChannelLocked(window.windowHandle->getToken());
-            if (channel != nullptr) {
-                synthesizeCancelationEventsForInputChannelLocked(channel, options);
-                canceledWindows += channel->getName() + ", ";
-            }
-        }
-        canceledWindows += "]";
-        ALOGI("Monitor %s is stealing touch from %s", requestingChannel->getName().c_str(),
-              canceledWindows.c_str());
-
-        // Then clear the current touch state so we stop dispatching to them as well.
-        state.split = false;
-        state.filterNonMonitors();
+    const std::shared_ptr<InputChannel> requestingChannel = getInputChannelLocked(token);
+    if (!requestingChannel) {
+        ALOGW("Attempted to pilfer pointers from an un-registered channel or invalid token");
+        return BAD_VALUE;
     }
+
+    auto [statePtr, windowPtr] = findTouchStateAndWindowLocked(token);
+    if (statePtr == nullptr || windowPtr == nullptr || !statePtr->down) {
+        ALOGW("Attempted to pilfer points from a channel without any on-going pointer streams."
+              " Ignoring.");
+        return BAD_VALUE;
+    }
+
+    TouchState& state = *statePtr;
+
+    // Send cancel events to all the input channels we're stealing from.
+    CancelationOptions options(CancelationOptions::CANCEL_POINTER_EVENTS,
+                               "input channel stole pointer stream");
+    options.deviceId = state.deviceId;
+    options.displayId = state.displayId;
+    std::string canceledWindows;
+    for (const TouchedWindow& window : state.windows) {
+        const std::shared_ptr<InputChannel> channel =
+                getInputChannelLocked(window.windowHandle->getToken());
+        if (channel != nullptr && channel->getConnectionToken() != token) {
+            synthesizeCancelationEventsForInputChannelLocked(channel, options);
+            canceledWindows += canceledWindows.empty() ? "[" : ", ";
+            canceledWindows += channel->getName();
+        }
+    }
+    canceledWindows += canceledWindows.empty() ? "[]" : "]";
+    ALOGI("Channel %s is stealing touch from %s", requestingChannel->getName().c_str(),
+          canceledWindows.c_str());
+
+    // Prevent the gesture from being sent to any other windows.
+    state.filterWindowsExcept(token);
+    state.preventNewTargets = true;
     return OK;
 }
 
@@ -5459,6 +5626,15 @@
             return;
         }
 
+        if (enabled) {
+            if (std::find(mIneligibleDisplaysForPointerCapture.begin(),
+                          mIneligibleDisplaysForPointerCapture.end(),
+                          mFocusedDisplayId) != mIneligibleDisplaysForPointerCapture.end()) {
+                ALOGW("Ignoring request to enable Pointer Capture: display is not eligible");
+                return;
+            }
+        }
+
         setPointerCaptureLocked(enabled);
     } // release lock
 
@@ -5466,27 +5642,27 @@
     mLooper->wake();
 }
 
-std::optional<int32_t> InputDispatcher::findGestureMonitorDisplayByTokenLocked(
-        const sp<IBinder>& token) {
-    for (const auto& it : mGestureMonitorsByDisplay) {
-        const std::vector<Monitor>& monitors = it.second;
+void InputDispatcher::setDisplayEligibilityForPointerCapture(int32_t displayId, bool isEligible) {
+    { // acquire lock
+        std::scoped_lock _l(mLock);
+        std::erase(mIneligibleDisplaysForPointerCapture, displayId);
+        if (!isEligible) {
+            mIneligibleDisplaysForPointerCapture.push_back(displayId);
+        }
+    } // release lock
+}
+
+std::optional<int32_t> InputDispatcher::findMonitorPidByTokenLocked(const sp<IBinder>& token) {
+    for (const auto& [_, monitors] : mGlobalMonitorsByDisplay) {
         for (const Monitor& monitor : monitors) {
             if (monitor.inputChannel->getConnectionToken() == token) {
-                return it.first;
+                return monitor.pid;
             }
         }
     }
     return std::nullopt;
 }
 
-std::optional<int32_t> InputDispatcher::findMonitorPidByTokenLocked(const sp<IBinder>& token) {
-    std::optional<int32_t> gesturePid = findMonitorPidByToken(mGestureMonitorsByDisplay, token);
-    if (gesturePid.has_value()) {
-        return gesturePid;
-    }
-    return findMonitorPidByToken(mGlobalMonitorsByDisplay, token);
-}
-
 sp<Connection> InputDispatcher::getConnectionLocked(const sp<IBinder>& inputConnectionToken) const {
     if (inputConnectionToken == nullptr) {
         return nullptr;
@@ -5514,46 +5690,92 @@
     mConnectionsByToken.erase(connection->inputChannel->getConnectionToken());
 }
 
-void InputDispatcher::onDispatchCycleFinishedLocked(nsecs_t currentTime,
-                                                    const sp<Connection>& connection, uint32_t seq,
-                                                    bool handled, nsecs_t consumeTime) {
-    std::unique_ptr<CommandEntry> commandEntry = std::make_unique<CommandEntry>(
-            &InputDispatcher::doDispatchCycleFinishedLockedInterruptible);
-    commandEntry->connection = connection;
-    commandEntry->eventTime = currentTime;
-    commandEntry->seq = seq;
-    commandEntry->handled = handled;
-    commandEntry->consumeTime = consumeTime;
-    postCommandLocked(std::move(commandEntry));
+void InputDispatcher::doDispatchCycleFinishedCommand(nsecs_t finishTime,
+                                                     const sp<Connection>& connection, uint32_t seq,
+                                                     bool handled, nsecs_t consumeTime) {
+    // Handle post-event policy actions.
+    std::deque<DispatchEntry*>::iterator dispatchEntryIt = connection->findWaitQueueEntry(seq);
+    if (dispatchEntryIt == connection->waitQueue.end()) {
+        return;
+    }
+    DispatchEntry* dispatchEntry = *dispatchEntryIt;
+    const nsecs_t eventDuration = finishTime - dispatchEntry->deliveryTime;
+    if (eventDuration > SLOW_EVENT_PROCESSING_WARNING_TIMEOUT) {
+        ALOGI("%s spent %" PRId64 "ms processing %s", connection->getWindowName().c_str(),
+              ns2ms(eventDuration), dispatchEntry->eventEntry->getDescription().c_str());
+    }
+    if (shouldReportFinishedEvent(*dispatchEntry, *connection)) {
+        mLatencyTracker.trackFinishedEvent(dispatchEntry->eventEntry->id,
+                                           connection->inputChannel->getConnectionToken(),
+                                           dispatchEntry->deliveryTime, consumeTime, finishTime);
+    }
+
+    bool restartEvent;
+    if (dispatchEntry->eventEntry->type == EventEntry::Type::KEY) {
+        KeyEntry& keyEntry = static_cast<KeyEntry&>(*(dispatchEntry->eventEntry));
+        restartEvent =
+                afterKeyEventLockedInterruptable(connection, dispatchEntry, keyEntry, handled);
+    } else if (dispatchEntry->eventEntry->type == EventEntry::Type::MOTION) {
+        MotionEntry& motionEntry = static_cast<MotionEntry&>(*(dispatchEntry->eventEntry));
+        restartEvent = afterMotionEventLockedInterruptable(connection, dispatchEntry, motionEntry,
+                                                           handled);
+    } else {
+        restartEvent = false;
+    }
+
+    // Dequeue the event and start the next cycle.
+    // Because the lock might have been released, it is possible that the
+    // contents of the wait queue to have been drained, so we need to double-check
+    // a few things.
+    dispatchEntryIt = connection->findWaitQueueEntry(seq);
+    if (dispatchEntryIt != connection->waitQueue.end()) {
+        dispatchEntry = *dispatchEntryIt;
+        connection->waitQueue.erase(dispatchEntryIt);
+        const sp<IBinder>& connectionToken = connection->inputChannel->getConnectionToken();
+        mAnrTracker.erase(dispatchEntry->timeoutTime, connectionToken);
+        if (!connection->responsive) {
+            connection->responsive = isConnectionResponsive(*connection);
+            if (connection->responsive) {
+                // The connection was unresponsive, and now it's responsive.
+                processConnectionResponsiveLocked(*connection);
+            }
+        }
+        traceWaitQueueLength(*connection);
+        if (restartEvent && connection->status == Connection::Status::NORMAL) {
+            connection->outboundQueue.push_front(dispatchEntry);
+            traceOutboundQueueLength(*connection);
+        } else {
+            releaseDispatchEntry(dispatchEntry);
+        }
+    }
+
+    // Start the next dispatch cycle for this connection.
+    startDispatchCycleLocked(now(), connection);
 }
 
-void InputDispatcher::onDispatchCycleBrokenLocked(nsecs_t currentTime,
-                                                  const sp<Connection>& connection) {
-    ALOGE("channel '%s' ~ Channel is unrecoverably broken and will be disposed!",
-          connection->getInputChannelName().c_str());
-
-    std::unique_ptr<CommandEntry> commandEntry = std::make_unique<CommandEntry>(
-            &InputDispatcher::doNotifyInputChannelBrokenLockedInterruptible);
-    commandEntry->connection = connection;
-    postCommandLocked(std::move(commandEntry));
+void InputDispatcher::sendFocusChangedCommandLocked(const sp<IBinder>& oldToken,
+                                                    const sp<IBinder>& newToken) {
+    auto command = [this, oldToken, newToken]() REQUIRES(mLock) {
+        scoped_unlock unlock(mLock);
+        mPolicy->notifyFocusChanged(oldToken, newToken);
+    };
+    postCommandLocked(std::move(command));
 }
 
-void InputDispatcher::notifyFocusChangedLocked(const sp<IBinder>& oldToken,
-                                               const sp<IBinder>& newToken) {
-    std::unique_ptr<CommandEntry> commandEntry = std::make_unique<CommandEntry>(
-            &InputDispatcher::doNotifyFocusChangedLockedInterruptible);
-    commandEntry->oldToken = oldToken;
-    commandEntry->newToken = newToken;
-    postCommandLocked(std::move(commandEntry));
+void InputDispatcher::sendDropWindowCommandLocked(const sp<IBinder>& token, float x, float y) {
+    auto command = [this, token, x, y]() REQUIRES(mLock) {
+        scoped_unlock unlock(mLock);
+        mPolicy->notifyDropWindow(token, x, y);
+    };
+    postCommandLocked(std::move(command));
 }
 
-void InputDispatcher::notifyDropWindowLocked(const sp<IBinder>& token, float x, float y) {
-    std::unique_ptr<CommandEntry> commandEntry =
-            std::make_unique<CommandEntry>(&InputDispatcher::doNotifyDropWindowLockedInterruptible);
-    commandEntry->newToken = token;
-    commandEntry->x = x;
-    commandEntry->y = y;
-    postCommandLocked(std::move(commandEntry));
+void InputDispatcher::sendUntrustedTouchCommandLocked(const std::string& obscuringPackage) {
+    auto command = [this, obscuringPackage]() REQUIRES(mLock) {
+        scoped_unlock unlock(mLock);
+        mPolicy->notifyUntrustedTouch(obscuringPackage);
+    };
+    postCommandLocked(std::move(command));
 }
 
 void InputDispatcher::onAnrLocked(const sp<Connection>& connection) {
@@ -5596,17 +5818,11 @@
             StringPrintf("%s does not have a focused window", application->getName().c_str());
     updateLastAnrStateLocked(*application, reason);
 
-    std::unique_ptr<CommandEntry> commandEntry = std::make_unique<CommandEntry>(
-            &InputDispatcher::doNotifyNoFocusedWindowAnrLockedInterruptible);
-    commandEntry->inputApplicationHandle = std::move(application);
-    postCommandLocked(std::move(commandEntry));
-}
-
-void InputDispatcher::onUntrustedTouchLocked(const std::string& obscuringPackage) {
-    std::unique_ptr<CommandEntry> commandEntry = std::make_unique<CommandEntry>(
-            &InputDispatcher::doNotifyUntrustedTouchLockedInterruptible);
-    commandEntry->obscuringPackage = obscuringPackage;
-    postCommandLocked(std::move(commandEntry));
+    auto command = [this, application = std::move(application)]() REQUIRES(mLock) {
+        scoped_unlock unlock(mLock);
+        mPolicy->notifyNoFocusedWindowAnr(application);
+    };
+    postCommandLocked(std::move(command));
 }
 
 void InputDispatcher::updateLastAnrStateLocked(const sp<WindowInfoHandle>& window,
@@ -5637,109 +5853,24 @@
     dumpDispatchStateLocked(mLastAnrState);
 }
 
-void InputDispatcher::doNotifyConfigurationChangedLockedInterruptible(CommandEntry* commandEntry) {
-    mLock.unlock();
-
-    mPolicy->notifyConfigurationChanged(commandEntry->eventTime);
-
-    mLock.lock();
-}
-
-void InputDispatcher::doNotifyInputChannelBrokenLockedInterruptible(CommandEntry* commandEntry) {
-    sp<Connection> connection = commandEntry->connection;
-
-    if (connection->status != Connection::STATUS_ZOMBIE) {
-        mLock.unlock();
-
-        mPolicy->notifyInputChannelBroken(connection->inputChannel->getConnectionToken());
-
-        mLock.lock();
-    }
-}
-
-void InputDispatcher::doNotifyFocusChangedLockedInterruptible(CommandEntry* commandEntry) {
-    sp<IBinder> oldToken = commandEntry->oldToken;
-    sp<IBinder> newToken = commandEntry->newToken;
-    mLock.unlock();
-    mPolicy->notifyFocusChanged(oldToken, newToken);
-    mLock.lock();
-}
-
-void InputDispatcher::doNotifyDropWindowLockedInterruptible(CommandEntry* commandEntry) {
-    sp<IBinder> newToken = commandEntry->newToken;
-    mLock.unlock();
-    mPolicy->notifyDropWindow(newToken, commandEntry->x, commandEntry->y);
-    mLock.lock();
-}
-
-void InputDispatcher::doNotifyNoFocusedWindowAnrLockedInterruptible(CommandEntry* commandEntry) {
-    mLock.unlock();
-
-    mPolicy->notifyNoFocusedWindowAnr(commandEntry->inputApplicationHandle);
-
-    mLock.lock();
-}
-
-void InputDispatcher::doNotifyWindowUnresponsiveLockedInterruptible(CommandEntry* commandEntry) {
-    mLock.unlock();
-
-    mPolicy->notifyWindowUnresponsive(commandEntry->connectionToken, commandEntry->reason);
-
-    mLock.lock();
-}
-
-void InputDispatcher::doNotifyMonitorUnresponsiveLockedInterruptible(CommandEntry* commandEntry) {
-    mLock.unlock();
-
-    mPolicy->notifyMonitorUnresponsive(commandEntry->pid, commandEntry->reason);
-
-    mLock.lock();
-}
-
-void InputDispatcher::doNotifyWindowResponsiveLockedInterruptible(CommandEntry* commandEntry) {
-    mLock.unlock();
-
-    mPolicy->notifyWindowResponsive(commandEntry->connectionToken);
-
-    mLock.lock();
-}
-
-void InputDispatcher::doNotifyMonitorResponsiveLockedInterruptible(CommandEntry* commandEntry) {
-    mLock.unlock();
-
-    mPolicy->notifyMonitorResponsive(commandEntry->pid);
-
-    mLock.lock();
-}
-
-void InputDispatcher::doNotifyUntrustedTouchLockedInterruptible(CommandEntry* commandEntry) {
-    mLock.unlock();
-
-    mPolicy->notifyUntrustedTouch(commandEntry->obscuringPackage);
-
-    mLock.lock();
-}
-
-void InputDispatcher::doInterceptKeyBeforeDispatchingLockedInterruptible(
-        CommandEntry* commandEntry) {
-    KeyEntry& entry = *(commandEntry->keyEntry);
-    KeyEvent event = createKeyEvent(entry);
-
-    mLock.unlock();
-
-    android::base::Timer t;
-    const sp<IBinder>& token = commandEntry->connectionToken;
-    nsecs_t delay = mPolicy->interceptKeyBeforeDispatching(token, &event, entry.policyFlags);
-    if (t.duration() > SLOW_INTERCEPTION_THRESHOLD) {
-        ALOGW("Excessive delay in interceptKeyBeforeDispatching; took %s ms",
-              std::to_string(t.duration().count()).c_str());
-    }
-
-    mLock.lock();
+void InputDispatcher::doInterceptKeyBeforeDispatchingCommand(const sp<IBinder>& focusedWindowToken,
+                                                             KeyEntry& entry) {
+    const KeyEvent event = createKeyEvent(entry);
+    nsecs_t delay = 0;
+    { // release lock
+        scoped_unlock unlock(mLock);
+        android::base::Timer t;
+        delay = mPolicy->interceptKeyBeforeDispatching(focusedWindowToken, &event,
+                                                       entry.policyFlags);
+        if (t.duration() > SLOW_INTERCEPTION_THRESHOLD) {
+            ALOGW("Excessive delay in interceptKeyBeforeDispatching; took %s ms",
+                  std::to_string(t.duration().count()).c_str());
+        }
+    } // acquire lock
 
     if (delay < 0) {
         entry.interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_SKIP;
-    } else if (!delay) {
+    } else if (delay == 0) {
         entry.interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_CONTINUE;
     } else {
         entry.interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_TRY_AGAIN_LATER;
@@ -5747,122 +5878,23 @@
     }
 }
 
-void InputDispatcher::doOnPointerDownOutsideFocusLockedInterruptible(CommandEntry* commandEntry) {
-    mLock.unlock();
-    mPolicy->onPointerDownOutsideFocus(commandEntry->newToken);
-    mLock.lock();
-}
-
-/**
- * Connection is responsive if it has no events in the waitQueue that are older than the
- * current time.
- */
-static bool isConnectionResponsive(const Connection& connection) {
-    const nsecs_t currentTime = now();
-    for (const DispatchEntry* entry : connection.waitQueue) {
-        if (entry->timeoutTime < currentTime) {
-            return false;
-        }
-    }
-    return true;
-}
-
-void InputDispatcher::doDispatchCycleFinishedLockedInterruptible(CommandEntry* commandEntry) {
-    sp<Connection> connection = commandEntry->connection;
-    const nsecs_t finishTime = commandEntry->eventTime;
-    uint32_t seq = commandEntry->seq;
-    const bool handled = commandEntry->handled;
-
-    // Handle post-event policy actions.
-    std::deque<DispatchEntry*>::iterator dispatchEntryIt = connection->findWaitQueueEntry(seq);
-    if (dispatchEntryIt == connection->waitQueue.end()) {
-        return;
-    }
-    DispatchEntry* dispatchEntry = *dispatchEntryIt;
-    const nsecs_t eventDuration = finishTime - dispatchEntry->deliveryTime;
-    if (eventDuration > SLOW_EVENT_PROCESSING_WARNING_TIMEOUT) {
-        ALOGI("%s spent %" PRId64 "ms processing %s", connection->getWindowName().c_str(),
-              ns2ms(eventDuration), dispatchEntry->eventEntry->getDescription().c_str());
-    }
-    if (shouldReportFinishedEvent(*dispatchEntry, *connection)) {
-        mLatencyTracker.trackFinishedEvent(dispatchEntry->eventEntry->id,
-                                           connection->inputChannel->getConnectionToken(),
-                                           dispatchEntry->deliveryTime, commandEntry->consumeTime,
-                                           finishTime);
-    }
-
-    bool restartEvent;
-    if (dispatchEntry->eventEntry->type == EventEntry::Type::KEY) {
-        KeyEntry& keyEntry = static_cast<KeyEntry&>(*(dispatchEntry->eventEntry));
-        restartEvent =
-                afterKeyEventLockedInterruptible(connection, dispatchEntry, keyEntry, handled);
-    } else if (dispatchEntry->eventEntry->type == EventEntry::Type::MOTION) {
-        MotionEntry& motionEntry = static_cast<MotionEntry&>(*(dispatchEntry->eventEntry));
-        restartEvent = afterMotionEventLockedInterruptible(connection, dispatchEntry, motionEntry,
-                                                           handled);
-    } else {
-        restartEvent = false;
-    }
-
-    // Dequeue the event and start the next cycle.
-    // Because the lock might have been released, it is possible that the
-    // contents of the wait queue to have been drained, so we need to double-check
-    // a few things.
-    dispatchEntryIt = connection->findWaitQueueEntry(seq);
-    if (dispatchEntryIt != connection->waitQueue.end()) {
-        dispatchEntry = *dispatchEntryIt;
-        connection->waitQueue.erase(dispatchEntryIt);
-        const sp<IBinder>& connectionToken = connection->inputChannel->getConnectionToken();
-        mAnrTracker.erase(dispatchEntry->timeoutTime, connectionToken);
-        if (!connection->responsive) {
-            connection->responsive = isConnectionResponsive(*connection);
-            if (connection->responsive) {
-                // The connection was unresponsive, and now it's responsive.
-                processConnectionResponsiveLocked(*connection);
-            }
-        }
-        traceWaitQueueLength(*connection);
-        if (restartEvent && connection->status == Connection::STATUS_NORMAL) {
-            connection->outboundQueue.push_front(dispatchEntry);
-            traceOutboundQueueLength(*connection);
-        } else {
-            releaseDispatchEntry(dispatchEntry);
-        }
-    }
-
-    // Start the next dispatch cycle for this connection.
-    startDispatchCycleLocked(now(), connection);
-}
-
-void InputDispatcher::sendMonitorUnresponsiveCommandLocked(int32_t pid, std::string reason) {
-    std::unique_ptr<CommandEntry> monitorUnresponsiveCommand = std::make_unique<CommandEntry>(
-            &InputDispatcher::doNotifyMonitorUnresponsiveLockedInterruptible);
-    monitorUnresponsiveCommand->pid = pid;
-    monitorUnresponsiveCommand->reason = std::move(reason);
-    postCommandLocked(std::move(monitorUnresponsiveCommand));
-}
-
-void InputDispatcher::sendWindowUnresponsiveCommandLocked(sp<IBinder> connectionToken,
+void InputDispatcher::sendWindowUnresponsiveCommandLocked(const sp<IBinder>& token,
+                                                          std::optional<int32_t> pid,
                                                           std::string reason) {
-    std::unique_ptr<CommandEntry> windowUnresponsiveCommand = std::make_unique<CommandEntry>(
-            &InputDispatcher::doNotifyWindowUnresponsiveLockedInterruptible);
-    windowUnresponsiveCommand->connectionToken = std::move(connectionToken);
-    windowUnresponsiveCommand->reason = std::move(reason);
-    postCommandLocked(std::move(windowUnresponsiveCommand));
+    auto command = [this, token, pid, reason = std::move(reason)]() REQUIRES(mLock) {
+        scoped_unlock unlock(mLock);
+        mPolicy->notifyWindowUnresponsive(token, pid, reason);
+    };
+    postCommandLocked(std::move(command));
 }
 
-void InputDispatcher::sendMonitorResponsiveCommandLocked(int32_t pid) {
-    std::unique_ptr<CommandEntry> monitorResponsiveCommand = std::make_unique<CommandEntry>(
-            &InputDispatcher::doNotifyMonitorResponsiveLockedInterruptible);
-    monitorResponsiveCommand->pid = pid;
-    postCommandLocked(std::move(monitorResponsiveCommand));
-}
-
-void InputDispatcher::sendWindowResponsiveCommandLocked(sp<IBinder> connectionToken) {
-    std::unique_ptr<CommandEntry> windowResponsiveCommand = std::make_unique<CommandEntry>(
-            &InputDispatcher::doNotifyWindowResponsiveLockedInterruptible);
-    windowResponsiveCommand->connectionToken = std::move(connectionToken);
-    postCommandLocked(std::move(windowResponsiveCommand));
+void InputDispatcher::sendWindowResponsiveCommandLocked(const sp<IBinder>& token,
+                                                        std::optional<int32_t> pid) {
+    auto command = [this, token, pid]() REQUIRES(mLock) {
+        scoped_unlock unlock(mLock);
+        mPolicy->notifyWindowResponsive(token, pid);
+    };
+    postCommandLocked(std::move(command));
 }
 
 /**
@@ -5873,22 +5905,21 @@
 void InputDispatcher::processConnectionUnresponsiveLocked(const Connection& connection,
                                                           std::string reason) {
     const sp<IBinder>& connectionToken = connection.inputChannel->getConnectionToken();
+    std::optional<int32_t> pid;
     if (connection.monitor) {
         ALOGW("Monitor %s is unresponsive: %s", connection.inputChannel->getName().c_str(),
               reason.c_str());
-        std::optional<int32_t> pid = findMonitorPidByTokenLocked(connectionToken);
-        if (!pid.has_value()) {
-            ALOGE("Could not find unresponsive monitor for connection %s",
-                  connection.inputChannel->getName().c_str());
-            return;
+        pid = findMonitorPidByTokenLocked(connectionToken);
+    } else {
+        // The connection is a window
+        ALOGW("Window %s is unresponsive: %s", connection.inputChannel->getName().c_str(),
+              reason.c_str());
+        const sp<WindowInfoHandle> handle = getWindowHandleLocked(connectionToken);
+        if (handle != nullptr) {
+            pid = handle->getInfo()->ownerPid;
         }
-        sendMonitorUnresponsiveCommandLocked(pid.value(), std::move(reason));
-        return;
     }
-    // If not a monitor, must be a window
-    ALOGW("Window %s is unresponsive: %s", connection.inputChannel->getName().c_str(),
-          reason.c_str());
-    sendWindowUnresponsiveCommandLocked(connectionToken, std::move(reason));
+    sendWindowUnresponsiveCommandLocked(connectionToken, pid, std::move(reason));
 }
 
 /**
@@ -5896,21 +5927,20 @@
  */
 void InputDispatcher::processConnectionResponsiveLocked(const Connection& connection) {
     const sp<IBinder>& connectionToken = connection.inputChannel->getConnectionToken();
+    std::optional<int32_t> pid;
     if (connection.monitor) {
-        std::optional<int32_t> pid = findMonitorPidByTokenLocked(connectionToken);
-        if (!pid.has_value()) {
-            ALOGE("Could not find responsive monitor for connection %s",
-                  connection.inputChannel->getName().c_str());
-            return;
+        pid = findMonitorPidByTokenLocked(connectionToken);
+    } else {
+        // The connection is a window
+        const sp<WindowInfoHandle> handle = getWindowHandleLocked(connectionToken);
+        if (handle != nullptr) {
+            pid = handle->getInfo()->ownerPid;
         }
-        sendMonitorResponsiveCommandLocked(pid.value());
-        return;
     }
-    // If not a monitor, must be a window
-    sendWindowResponsiveCommandLocked(connectionToken);
+    sendWindowResponsiveCommandLocked(connectionToken, pid);
 }
 
-bool InputDispatcher::afterKeyEventLockedInterruptible(const sp<Connection>& connection,
+bool InputDispatcher::afterKeyEventLockedInterruptable(const sp<Connection>& connection,
                                                        DispatchEntry* dispatchEntry,
                                                        KeyEntry& keyEntry, bool handled) {
     if (keyEntry.flags & AKEY_EVENT_FLAG_FALLBACK) {
@@ -5935,11 +5965,12 @@
         // then cancel the associated fallback key, if any.
         if (fallbackKeyCode != -1) {
             // Dispatch the unhandled key to the policy with the cancel flag.
-#if DEBUG_OUTBOUND_EVENT_DETAILS
-            ALOGD("Unhandled key event: Asking policy to cancel fallback action.  "
-                  "keyCode=%d, action=%d, repeatCount=%d, policyFlags=0x%08x",
-                  keyEntry.keyCode, keyEntry.action, keyEntry.repeatCount, keyEntry.policyFlags);
-#endif
+            if (DEBUG_OUTBOUND_EVENT_DETAILS) {
+                ALOGD("Unhandled key event: Asking policy to cancel fallback action.  "
+                      "keyCode=%d, action=%d, repeatCount=%d, policyFlags=0x%08x",
+                      keyEntry.keyCode, keyEntry.action, keyEntry.repeatCount,
+                      keyEntry.policyFlags);
+            }
             KeyEvent event = createKeyEvent(keyEntry);
             event.setFlags(event.getFlags() | AKEY_EVENT_FLAG_CANCELED);
 
@@ -5967,21 +5998,21 @@
         // Then ask the policy what to do with it.
         bool initialDown = keyEntry.action == AKEY_EVENT_ACTION_DOWN && keyEntry.repeatCount == 0;
         if (fallbackKeyCode == -1 && !initialDown) {
-#if DEBUG_OUTBOUND_EVENT_DETAILS
-            ALOGD("Unhandled key event: Skipping unhandled key event processing "
-                  "since this is not an initial down.  "
-                  "keyCode=%d, action=%d, repeatCount=%d, policyFlags=0x%08x",
-                  originalKeyCode, keyEntry.action, keyEntry.repeatCount, keyEntry.policyFlags);
-#endif
+            if (DEBUG_OUTBOUND_EVENT_DETAILS) {
+                ALOGD("Unhandled key event: Skipping unhandled key event processing "
+                      "since this is not an initial down.  "
+                      "keyCode=%d, action=%d, repeatCount=%d, policyFlags=0x%08x",
+                      originalKeyCode, keyEntry.action, keyEntry.repeatCount, keyEntry.policyFlags);
+            }
             return false;
         }
 
         // Dispatch the unhandled key to the policy.
-#if DEBUG_OUTBOUND_EVENT_DETAILS
-        ALOGD("Unhandled key event: Asking policy to perform fallback action.  "
-              "keyCode=%d, action=%d, repeatCount=%d, policyFlags=0x%08x",
-              keyEntry.keyCode, keyEntry.action, keyEntry.repeatCount, keyEntry.policyFlags);
-#endif
+        if (DEBUG_OUTBOUND_EVENT_DETAILS) {
+            ALOGD("Unhandled key event: Asking policy to perform fallback action.  "
+                  "keyCode=%d, action=%d, repeatCount=%d, policyFlags=0x%08x",
+                  keyEntry.keyCode, keyEntry.action, keyEntry.repeatCount, keyEntry.policyFlags);
+        }
         KeyEvent event = createKeyEvent(keyEntry);
 
         mLock.unlock();
@@ -5992,7 +6023,7 @@
 
         mLock.lock();
 
-        if (connection->status != Connection::STATUS_NORMAL) {
+        if (connection->status != Connection::Status::NORMAL) {
             connection->inputState.removeFallbackKey(originalKeyCode);
             return false;
         }
@@ -6015,19 +6046,19 @@
         // longer dispatch a fallback key to the application.
         if (fallbackKeyCode != AKEYCODE_UNKNOWN &&
             (!fallback || fallbackKeyCode != event.getKeyCode())) {
-#if DEBUG_OUTBOUND_EVENT_DETAILS
-            if (fallback) {
-                ALOGD("Unhandled key event: Policy requested to send key %d"
-                      "as a fallback for %d, but on the DOWN it had requested "
-                      "to send %d instead.  Fallback canceled.",
-                      event.getKeyCode(), originalKeyCode, fallbackKeyCode);
-            } else {
-                ALOGD("Unhandled key event: Policy did not request fallback for %d, "
-                      "but on the DOWN it had requested to send %d.  "
-                      "Fallback canceled.",
-                      originalKeyCode, fallbackKeyCode);
+            if (DEBUG_OUTBOUND_EVENT_DETAILS) {
+                if (fallback) {
+                    ALOGD("Unhandled key event: Policy requested to send key %d"
+                          "as a fallback for %d, but on the DOWN it had requested "
+                          "to send %d instead.  Fallback canceled.",
+                          event.getKeyCode(), originalKeyCode, fallbackKeyCode);
+                } else {
+                    ALOGD("Unhandled key event: Policy did not request fallback for %d, "
+                          "but on the DOWN it had requested to send %d.  "
+                          "Fallback canceled.",
+                          originalKeyCode, fallbackKeyCode);
+                }
             }
-#endif
 
             CancelationOptions options(CancelationOptions::CANCEL_FALLBACK_EVENTS,
                                        "canceling fallback, policy no longer desires it");
@@ -6041,18 +6072,18 @@
             }
         }
 
-#if DEBUG_OUTBOUND_EVENT_DETAILS
-        {
-            std::string msg;
-            const KeyedVector<int32_t, int32_t>& fallbackKeys =
-                    connection->inputState.getFallbackKeys();
-            for (size_t i = 0; i < fallbackKeys.size(); i++) {
-                msg += StringPrintf(", %d->%d", fallbackKeys.keyAt(i), fallbackKeys.valueAt(i));
+        if (DEBUG_OUTBOUND_EVENT_DETAILS) {
+            {
+                std::string msg;
+                const KeyedVector<int32_t, int32_t>& fallbackKeys =
+                        connection->inputState.getFallbackKeys();
+                for (size_t i = 0; i < fallbackKeys.size(); i++) {
+                    msg += StringPrintf(", %d->%d", fallbackKeys.keyAt(i), fallbackKeys.valueAt(i));
+                }
+                ALOGD("Unhandled key event: %zu currently tracked fallback keys%s.",
+                      fallbackKeys.size(), msg.c_str());
             }
-            ALOGD("Unhandled key event: %zu currently tracked fallback keys%s.",
-                  fallbackKeys.size(), msg.c_str());
         }
-#endif
 
         if (fallback) {
             // Restart the dispatch cycle using the fallback key.
@@ -6068,16 +6099,16 @@
             keyEntry.downTime = event.getDownTime();
             keyEntry.syntheticRepeat = false;
 
-#if DEBUG_OUTBOUND_EVENT_DETAILS
-            ALOGD("Unhandled key event: Dispatching fallback key.  "
-                  "originalKeyCode=%d, fallbackKeyCode=%d, fallbackMetaState=%08x",
-                  originalKeyCode, fallbackKeyCode, keyEntry.metaState);
-#endif
+            if (DEBUG_OUTBOUND_EVENT_DETAILS) {
+                ALOGD("Unhandled key event: Dispatching fallback key.  "
+                      "originalKeyCode=%d, fallbackKeyCode=%d, fallbackMetaState=%08x",
+                      originalKeyCode, fallbackKeyCode, keyEntry.metaState);
+            }
             return true; // restart the event
         } else {
-#if DEBUG_OUTBOUND_EVENT_DETAILS
-            ALOGD("Unhandled key event: No fallback key.");
-#endif
+            if (DEBUG_OUTBOUND_EVENT_DETAILS) {
+                ALOGD("Unhandled key event: No fallback key.");
+            }
 
             // Report the key as unhandled, since there is no fallback key.
             mReporter->reportUnhandledKey(keyEntry.id);
@@ -6086,21 +6117,12 @@
     return false;
 }
 
-bool InputDispatcher::afterMotionEventLockedInterruptible(const sp<Connection>& connection,
+bool InputDispatcher::afterMotionEventLockedInterruptable(const sp<Connection>& connection,
                                                           DispatchEntry* dispatchEntry,
                                                           MotionEntry& motionEntry, bool handled) {
     return false;
 }
 
-void InputDispatcher::doPokeUserActivityLockedInterruptible(CommandEntry* commandEntry) {
-    mLock.unlock();
-
-    mPolicy->pokeUserActivity(commandEntry->eventTime, commandEntry->userActivityEventType,
-                              commandEntry->displayId);
-
-    mLock.lock();
-}
-
 void InputDispatcher::traceInboundQueueLengthLocked() {
     if (ATRACE_ENABLED()) {
         ATRACE_INT("iq", mInboundQueue.size());
@@ -6212,7 +6234,7 @@
     disablePointerCaptureForcedLocked();
 
     if (mFocusedDisplayId == changes.displayId) {
-        notifyFocusChangedLocked(changes.oldFocus, changes.newFocus);
+        sendFocusChangedCommandLocked(changes.oldFocus, changes.newFocus);
     }
 }
 
@@ -6244,22 +6266,14 @@
     mInboundQueue.push_front(std::move(entry));
 }
 
-void InputDispatcher::setPointerCaptureLocked(bool enabled) {
-    mCurrentPointerCaptureRequest.enable = enabled;
+void InputDispatcher::setPointerCaptureLocked(bool enable) {
+    mCurrentPointerCaptureRequest.enable = enable;
     mCurrentPointerCaptureRequest.seq++;
-    std::unique_ptr<CommandEntry> commandEntry = std::make_unique<CommandEntry>(
-            &InputDispatcher::doSetPointerCaptureLockedInterruptible);
-    commandEntry->pointerCaptureRequest = mCurrentPointerCaptureRequest;
-    postCommandLocked(std::move(commandEntry));
-}
-
-void InputDispatcher::doSetPointerCaptureLockedInterruptible(
-        android::inputdispatcher::CommandEntry* commandEntry) {
-    mLock.unlock();
-
-    mPolicy->setPointerCapture(commandEntry->pointerCaptureRequest);
-
-    mLock.lock();
+    auto command = [this, request = mCurrentPointerCaptureRequest]() REQUIRES(mLock) {
+        scoped_unlock unlock(mLock);
+        mPolicy->setPointerCapture(request);
+    };
+    postCommandLocked(std::move(command));
 }
 
 void InputDispatcher::displayRemoved(int32_t displayId) {
@@ -6271,37 +6285,79 @@
         // Call focus resolver to clean up stale requests. This must be called after input windows
         // have been removed for the removed display.
         mFocusResolver.displayRemoved(displayId);
+        // Reset pointer capture eligibility, regardless of previous state.
+        std::erase(mIneligibleDisplaysForPointerCapture, displayId);
     } // release lock
 
     // Wake up poll loop since it may need to make new input dispatching choices.
     mLooper->wake();
 }
 
-void InputDispatcher::onWindowInfosChanged(const std::vector<gui::WindowInfo>& windowInfos) {
+void InputDispatcher::onWindowInfosChanged(const std::vector<WindowInfo>& windowInfos,
+                                           const std::vector<DisplayInfo>& displayInfos) {
     // The listener sends the windows as a flattened array. Separate the windows by display for
     // more convenient parsing.
     std::unordered_map<int32_t, std::vector<sp<WindowInfoHandle>>> handlesPerDisplay;
-
     for (const auto& info : windowInfos) {
         handlesPerDisplay.emplace(info.displayId, std::vector<sp<WindowInfoHandle>>());
         handlesPerDisplay[info.displayId].push_back(new WindowInfoHandle(info));
     }
-    setInputWindows(handlesPerDisplay);
+
+    { // acquire lock
+        std::scoped_lock _l(mLock);
+        mDisplayInfos.clear();
+        for (const auto& displayInfo : displayInfos) {
+            mDisplayInfos.emplace(displayInfo.displayId, displayInfo);
+        }
+
+        for (const auto& [displayId, handles] : handlesPerDisplay) {
+            setInputWindowsLocked(handles, displayId);
+        }
+    }
+    // Wake up poll loop since it may need to make new input dispatching choices.
+    mLooper->wake();
 }
 
 bool InputDispatcher::shouldDropInput(
         const EventEntry& entry, const sp<android::gui::WindowInfoHandle>& windowHandle) const {
-    if (windowHandle->getInfo()->inputFeatures.test(WindowInfo::Feature::DROP_INPUT) ||
-        (windowHandle->getInfo()->inputFeatures.test(WindowInfo::Feature::DROP_INPUT_IF_OBSCURED) &&
+    if (windowHandle->getInfo()->inputConfig.test(WindowInfo::InputConfig::DROP_INPUT) ||
+        (windowHandle->getInfo()->inputConfig.test(
+                 WindowInfo::InputConfig::DROP_INPUT_IF_OBSCURED) &&
          isWindowObscuredLocked(windowHandle))) {
-        ALOGW("Dropping %s event targeting %s as requested by input feature %s on display "
-              "%" PRId32 ".",
-              NamedEnum::string(entry.type).c_str(), windowHandle->getName().c_str(),
-              windowHandle->getInfo()->inputFeatures.string().c_str(),
+        ALOGW("Dropping %s event targeting %s as requested by the input configuration {%s} on "
+              "display %" PRId32 ".",
+              ftl::enum_string(entry.type).c_str(), windowHandle->getName().c_str(),
+              windowHandle->getInfo()->inputConfig.string().c_str(),
               windowHandle->getInfo()->displayId);
         return true;
     }
     return false;
 }
 
+void InputDispatcher::DispatcherWindowListener::onWindowInfosChanged(
+        const std::vector<gui::WindowInfo>& windowInfos,
+        const std::vector<DisplayInfo>& displayInfos) {
+    mDispatcher.onWindowInfosChanged(windowInfos, displayInfos);
+}
+
+void InputDispatcher::cancelCurrentTouch() {
+    {
+        std::scoped_lock _l(mLock);
+        ALOGD("Canceling all ongoing pointer gestures on all displays.");
+        CancelationOptions options(CancelationOptions::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();
+}
+
+void InputDispatcher::setMonitorDispatchingTimeoutForTest(std::chrono::nanoseconds timeout) {
+    std::scoped_lock _l(mLock);
+    mMonitorDispatchingTimeout = timeout;
+}
+
 } // namespace android::inputdispatcher
diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h
index 04913d4..3c79c98 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.h
+++ b/services/inputflinger/dispatcher/InputDispatcher.h
@@ -36,7 +36,6 @@
 #include "TouchedWindow.h"
 
 #include <attestation/HmacKeyManager.h>
-#include <com/android/internal/compat/IPlatformCompatNative.h>
 #include <gui/InputApplication.h>
 #include <gui/WindowInfo.h>
 #include <input/Input.h>
@@ -47,7 +46,6 @@
 #include <unistd.h>
 #include <utils/BitSet.h>
 #include <utils/Looper.h>
-#include <utils/RefBase.h>
 #include <utils/Timers.h>
 #include <utils/threads.h>
 #include <condition_variable>
@@ -81,12 +79,14 @@
  *
  *     A 'LockedInterruptible' method may called a 'Locked' method, but NOT vice-versa.
  */
-class InputDispatcher : public android::InputDispatcherInterface, public gui::WindowInfosListener {
-protected:
-    ~InputDispatcher() override;
-
+class InputDispatcher : public android::InputDispatcherInterface {
 public:
+    static constexpr bool kDefaultInTouchMode = true;
+
     explicit InputDispatcher(const sp<InputDispatcherPolicyInterface>& policy);
+    explicit InputDispatcher(const sp<InputDispatcherPolicyInterface>& policy,
+                             std::chrono::nanoseconds staleEventTimeout);
+    ~InputDispatcher() override;
 
     void dump(std::string& dump) override;
     void monitor() override;
@@ -119,7 +119,7 @@
     void setFocusedDisplay(int32_t displayId) override;
     void setInputDispatchMode(bool enabled, bool frozen) override;
     void setInputFilterEnabled(bool enabled) override;
-    void setInTouchMode(bool inTouchMode) override;
+    bool setInTouchMode(bool inTouchMode, int32_t pid, int32_t uid, bool hasPermission) override;
     void setMaximumObscuringOpacityForTouch(float opacity) override;
     void setBlockUntrustedTouchesMode(android::os::BlockUntrustedTouchesMode mode) override;
 
@@ -131,19 +131,26 @@
             const std::string& name) override;
     void setFocusedWindow(const android::gui::FocusRequest&) override;
     base::Result<std::unique_ptr<InputChannel>> createInputMonitor(int32_t displayId,
-                                                                   bool isGestureMonitor,
                                                                    const std::string& name,
                                                                    int32_t pid) override;
     status_t removeInputChannel(const sp<IBinder>& connectionToken) override;
     status_t pilferPointers(const sp<IBinder>& token) override;
     void requestPointerCapture(const sp<IBinder>& windowToken, bool enabled) override;
     bool flushSensor(int deviceId, InputDeviceSensorType sensorType) override;
+    void setDisplayEligibilityForPointerCapture(int displayId, bool isEligible) override;
 
     std::array<uint8_t, 32> sign(const VerifiedInputEvent& event) const;
 
     void displayRemoved(int32_t displayId) override;
 
-    void onWindowInfosChanged(const std::vector<gui::WindowInfo>& windowInfos) override;
+    // Public because it's also used by tests to simulate the WindowInfosListener callback
+    void onWindowInfosChanged(const std::vector<android::gui::WindowInfo>& windowInfos,
+                              const std::vector<android::gui::DisplayInfo>& displayInfos);
+
+    void cancelCurrentTouch() override;
+
+    // Public to allow tests to verify that a Monitor can get ANR.
+    void setMonitorDispatchingTimeoutForTest(std::chrono::nanoseconds timeout);
 
 private:
     enum class DropReason {
@@ -171,7 +178,26 @@
     std::shared_ptr<EventEntry> mPendingEvent GUARDED_BY(mLock);
     std::deque<std::shared_ptr<EventEntry>> mInboundQueue GUARDED_BY(mLock);
     std::deque<std::shared_ptr<EventEntry>> mRecentQueue GUARDED_BY(mLock);
-    std::deque<std::unique_ptr<CommandEntry>> mCommandQueue GUARDED_BY(mLock);
+
+    // A command entry captures state and behavior for an action to be performed in the
+    // dispatch loop after the initial processing has taken place.  It is essentially
+    // a kind of continuation used to postpone sensitive policy interactions to a point
+    // in the dispatch loop where it is safe to release the lock (generally after finishing
+    // the critical parts of the dispatch cycle).
+    //
+    // The special thing about commands is that they can voluntarily release and reacquire
+    // the dispatcher lock at will.  Initially when the command starts running, the
+    // dispatcher lock is held.  However, if the command needs to call into the policy to
+    // do some work, it can release the lock, do the work, then reacquire the lock again
+    // before returning.
+    //
+    // This mechanism is a bit clunky but it helps to preserve the invariant that the dispatch
+    // never calls into the policy while holding its lock.
+    //
+    // Commands are called with the lock held, but they can release and re-acquire the lock from
+    // within.
+    using Command = std::function<void()>;
+    std::deque<Command> mCommandQueue GUARDED_BY(mLock);
 
     DropReason mLastDropReason GUARDED_BY(mLock);
 
@@ -212,12 +238,12 @@
     // to transfer focus to a new application.
     std::shared_ptr<EventEntry> mNextUnblockedEvent GUARDED_BY(mLock);
 
-    sp<android::gui::WindowInfoHandle> findTouchedWindowAtLocked(int32_t displayId, int32_t x,
-                                                                 int32_t y, TouchState* touchState,
-                                                                 bool addOutsideTargets = false,
-                                                                 bool addPortalWindows = false,
-                                                                 bool ignoreDragWindow = false)
-            REQUIRES(mLock);
+    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);
+
+    std::vector<sp<android::gui::WindowInfoHandle>> findTouchedSpyWindowsAtLocked(
+            int32_t displayId, int32_t x, int32_t y, bool isStylus) const REQUIRES(mLock);
 
     sp<Connection> getConnectionLocked(const sp<IBinder>& inputConnectionToken) const
             REQUIRES(mLock);
@@ -235,21 +261,12 @@
     std::unordered_map<sp<IBinder>, sp<Connection>, StrongPointerHash<IBinder>> mConnectionsByToken
             GUARDED_BY(mLock);
 
-    // Finds the display ID of the gesture monitor identified by the provided token.
-    std::optional<int32_t> findGestureMonitorDisplayByTokenLocked(const sp<IBinder>& token)
-            REQUIRES(mLock);
     // Find a monitor pid by the provided token.
     std::optional<int32_t> findMonitorPidByTokenLocked(const sp<IBinder>& token) REQUIRES(mLock);
 
     // Input channels that will receive a copy of all input events sent to the provided display.
     std::unordered_map<int32_t, std::vector<Monitor>> mGlobalMonitorsByDisplay GUARDED_BY(mLock);
 
-    // Input channels that will receive pointer events that start within the corresponding display.
-    // These are a bit special when compared to global monitors since they'll cause gesture streams
-    // to continue even when there isn't a touched window,and have the ability to steal the rest of
-    // the pointer stream in order to claim it for a system gesture.
-    std::unordered_map<int32_t, std::vector<Monitor>> mGestureMonitorsByDisplay GUARDED_BY(mLock);
-
     const HmacKeyManager mHmacKeyManager;
     const std::array<uint8_t, 32> getSignature(const MotionEntry& motionEntry,
                                                const DispatchEntry& dispatchEntry) const;
@@ -261,6 +278,9 @@
     bool hasInjectionPermission(int32_t injectorPid, int32_t injectorUid);
     void setInjectionResult(EventEntry& entry,
                             android::os::InputEventInjectionResult injectionResult);
+    void transformMotionEntryForInjectionLocked(MotionEntry&,
+                                                const ui::Transform& injectedTransform) const
+            REQUIRES(mLock);
 
     std::condition_variable mInjectionSyncFinished;
     void incrementPendingForegroundDispatches(EventEntry& entry);
@@ -296,11 +316,15 @@
 
     // Deferred command processing.
     bool haveCommandsLocked() const REQUIRES(mLock);
-    bool runCommandsLockedInterruptible() REQUIRES(mLock);
-    void postCommandLocked(std::unique_ptr<CommandEntry> commandEntry) REQUIRES(mLock);
+    bool runCommandsLockedInterruptable() REQUIRES(mLock);
+    void postCommandLocked(Command&& command) REQUIRES(mLock);
+
+    // The dispatching timeout to use for Monitors.
+    std::chrono::nanoseconds mMonitorDispatchingTimeout GUARDED_BY(mLock);
 
     nsecs_t processAnrsLocked() REQUIRES(mLock);
-    std::chrono::nanoseconds getDispatchingTimeoutLocked(const sp<IBinder>& token) REQUIRES(mLock);
+    std::chrono::nanoseconds getDispatchingTimeoutLocked(const sp<Connection>& connection)
+            REQUIRES(mLock);
 
     // Input filter processing.
     bool shouldSendKeyToInputFilterLocked(const NotifyKeyArgs* args) REQUIRES(mLock);
@@ -319,8 +343,22 @@
     float mMaximumObscuringOpacityForTouch GUARDED_BY(mLock);
     android::os::BlockUntrustedTouchesMode mBlockUntrustedTouchesMode GUARDED_BY(mLock);
 
-    std::unordered_map<int32_t, std::vector<sp<android::gui::WindowInfoHandle>>>
+    class DispatcherWindowListener : public gui::WindowInfosListener {
+    public:
+        explicit DispatcherWindowListener(InputDispatcher& dispatcher) : mDispatcher(dispatcher){};
+        void onWindowInfosChanged(
+                const std::vector<android::gui::WindowInfo>& windowInfos,
+                const std::vector<android::gui::DisplayInfo>& displayInfos) override;
+
+    private:
+        InputDispatcher& mDispatcher;
+    };
+    sp<gui::WindowInfosListener> mWindowInfoListener;
+
+    std::unordered_map<int32_t /*displayId*/, std::vector<sp<android::gui::WindowInfoHandle>>>
             mWindowHandlesByDisplay GUARDED_BY(mLock);
+    std::unordered_map<int32_t /*displayId*/, android::gui::DisplayInfo> mDisplayInfos
+            GUARDED_BY(mLock);
     void setInputWindowsLocked(
             const std::vector<sp<android::gui::WindowInfoHandle>>& inputWindowHandles,
             int32_t displayId) REQUIRES(mLock);
@@ -343,6 +381,12 @@
     bool hasResponsiveConnectionLocked(android::gui::WindowInfoHandle& windowHandle) const
             REQUIRES(mLock);
 
+    // Gets all the input targets (with their respective input channels) from the window handles
+    // passed as argument.
+    std::vector<InputTarget> getInputTargetsFromWindowHandlesLocked(
+            const std::vector<sp<android::gui::WindowInfoHandle>>& windowHandles) const
+            REQUIRES(mLock);
+
     /*
      * Validate and update InputWindowHandles for a given display.
      */
@@ -376,6 +420,10 @@
     // This should be in sync with PointerCaptureChangedEvents dispatched to the input channel.
     sp<IBinder> mWindowTokenWithPointerCapture GUARDED_BY(mLock);
 
+    // Displays that are ineligible for pointer capture.
+    // TODO(b/214621487): Remove or move to a display flag.
+    std::vector<int32_t> mIneligibleDisplaysForPointerCapture GUARDED_BY(mLock);
+
     // Disable Pointer Capture as a result of loss of window focus.
     void disablePointerCaptureForcedLocked() REQUIRES(mLock);
 
@@ -405,9 +453,12 @@
     void dispatchPointerCaptureChangedLocked(
             nsecs_t currentTime, const std::shared_ptr<PointerCaptureChangedEntry>& entry,
             DropReason& dropReason) REQUIRES(mLock);
+    void dispatchTouchModeChangeLocked(nsecs_t currentTime,
+                                       const std::shared_ptr<TouchModeEntry>& entry)
+            REQUIRES(mLock);
     void dispatchEventLocked(nsecs_t currentTime, std::shared_ptr<EventEntry> entry,
                              const std::vector<InputTarget>& inputTargets) REQUIRES(mLock);
-    void dispatchSensorLocked(nsecs_t currentTime, std::shared_ptr<SensorEntry> entry,
+    void dispatchSensorLocked(nsecs_t currentTime, const std::shared_ptr<SensorEntry>& entry,
                               DropReason* dropReason, nsecs_t* nextWakeupTime) REQUIRES(mLock);
     void dispatchDragLocked(nsecs_t currentTime, std::shared_ptr<DragEntry> entry) REQUIRES(mLock);
     void logOutboundKeyDetails(const char* prefix, const KeyEntry& entry);
@@ -422,6 +473,11 @@
      */
     std::optional<nsecs_t> mNoFocusedWindowTimeoutTime GUARDED_BY(mLock);
 
+    // Amount of time to allow for an event to be dispatched (measured since its eventTime)
+    // before considering it stale and dropping it.
+    const std::chrono::nanoseconds mStaleEventTimeout;
+    bool isStaleEvent(nsecs_t currentTime, const EventEntry& entry);
+
     bool shouldPruneInboundQueueLocked(const MotionEntry& motionEntry) REQUIRES(mLock);
 
     /**
@@ -454,24 +510,11 @@
      */
     void processConnectionResponsiveLocked(const Connection& connection) REQUIRES(mLock);
 
-    /**
-     * Post `doNotifyMonitorUnresponsiveLockedInterruptible` command.
-     */
-    void sendMonitorUnresponsiveCommandLocked(int32_t pid, std::string reason) REQUIRES(mLock);
-    /**
-     * Post `doNotifyWindowUnresponsiveLockedInterruptible` command.
-     */
-    void sendWindowUnresponsiveCommandLocked(sp<IBinder> connectionToken, std::string reason)
+    void sendWindowUnresponsiveCommandLocked(const sp<IBinder>& connectionToken,
+                                             std::optional<int32_t> pid, std::string reason)
             REQUIRES(mLock);
-    /**
-     * Post `doNotifyMonitorResponsiveLockedInterruptible` command.
-     */
-    void sendMonitorResponsiveCommandLocked(int32_t pid) REQUIRES(mLock);
-    /**
-     * Post `doNotifyWindowResponsiveLockedInterruptible` command.
-     */
-    void sendWindowResponsiveCommandLocked(sp<IBinder> connectionToken) REQUIRES(mLock);
-
+    void sendWindowResponsiveCommandLocked(const sp<IBinder>& connectionToken,
+                                           std::optional<int32_t> pid) REQUIRES(mLock);
 
     // Optimization: AnrTracker is used to quickly find which connection is due for a timeout next.
     // AnrTracker must be kept in-sync with all responsive connection.waitQueues.
@@ -484,7 +527,6 @@
     sp<android::gui::WindowInfoHandle> mLastHoverWindowHandle GUARDED_BY(mLock);
 
     void cancelEventsForAnrLocked(const sp<Connection>& connection) REQUIRES(mLock);
-    nsecs_t getTimeSpentWaitingForApplicationLocked(nsecs_t currentTime) 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.
     // This also means that we will miss cases like pulling down notification shade when the
@@ -499,20 +541,14 @@
     android::os::InputEventInjectionResult findTouchedWindowTargetsLocked(
             nsecs_t currentTime, const MotionEntry& entry, std::vector<InputTarget>& inputTargets,
             nsecs_t* nextWakeupTime, bool* outConflictingPointerActions) REQUIRES(mLock);
-    std::vector<TouchedMonitor> findTouchedGestureMonitorsLocked(
-            int32_t displayId,
-            const std::vector<sp<android::gui::WindowInfoHandle>>& portalWindows) const
-            REQUIRES(mLock);
-    std::vector<TouchedMonitor> selectResponsiveMonitorsLocked(
-            const std::vector<TouchedMonitor>& gestureMonitors) const 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,
                                std::vector<InputTarget>& inputTargets) REQUIRES(mLock);
-    void addMonitoringTargetLocked(const Monitor& monitor, float xOffset, float yOffset,
-                                   std::vector<InputTarget>& inputTargets) REQUIRES(mLock);
-    void addGlobalMonitoringTargetsLocked(std::vector<InputTarget>& inputTargets, int32_t displayId,
-                                          float xOffset = 0, float yOffset = 0) REQUIRES(mLock);
+    void addGlobalMonitoringTargetsLocked(std::vector<InputTarget>& inputTargets, int32_t displayId)
+            REQUIRES(mLock);
     void pokeUserActivityLocked(const EventEntry& eventEntry) REQUIRES(mLock);
     bool checkInjectionPermission(const sp<android::gui::WindowInfoHandle>& windowHandle,
                                   const InjectionState* injectionState);
@@ -576,9 +612,6 @@
             REQUIRES(mLock);
     void synthesizeCancelationEventsForMonitorsLocked(const CancelationOptions& options)
             REQUIRES(mLock);
-    void synthesizeCancelationEventsForMonitorsLocked(
-            const CancelationOptions& options,
-            std::unordered_map<int32_t, std::vector<Monitor>>& monitorsByDisplay) REQUIRES(mLock);
     void synthesizeCancelationEventsForInputChannelLocked(
             const std::shared_ptr<InputChannel>& channel, const CancelationOptions& options)
             REQUIRES(mLock);
@@ -604,60 +637,34 @@
 
     // Registration.
     void removeMonitorChannelLocked(const sp<IBinder>& connectionToken) REQUIRES(mLock);
-    void removeMonitorChannelLocked(
-            const sp<IBinder>& connectionToken,
-            std::unordered_map<int32_t, std::vector<Monitor>>& monitorsByDisplay) REQUIRES(mLock);
     status_t removeInputChannelLocked(const sp<IBinder>& connectionToken, bool notify)
             REQUIRES(mLock);
 
     // Interesting events that we might like to log or tell the framework about.
-    void onDispatchCycleFinishedLocked(nsecs_t currentTime, const sp<Connection>& connection,
-                                       uint32_t seq, bool handled, nsecs_t consumeTime)
+    void doDispatchCycleFinishedCommand(nsecs_t finishTime, const sp<Connection>& connection,
+                                        uint32_t seq, bool handled, nsecs_t consumeTime)
             REQUIRES(mLock);
-    void onDispatchCycleBrokenLocked(nsecs_t currentTime, const sp<Connection>& connection)
-            REQUIRES(mLock);
+    void doInterceptKeyBeforeDispatchingCommand(const sp<IBinder>& focusedWindowToken,
+                                                KeyEntry& entry) REQUIRES(mLock);
     void onFocusChangedLocked(const FocusResolver::FocusChanges& changes) REQUIRES(mLock);
-    void notifyFocusChangedLocked(const sp<IBinder>& oldFocus, const sp<IBinder>& newFocus)
+    void sendFocusChangedCommandLocked(const sp<IBinder>& oldToken, const sp<IBinder>& newToken)
             REQUIRES(mLock);
-    void notifyDropWindowLocked(const sp<IBinder>& token, float x, float y) REQUIRES(mLock);
+    void sendDropWindowCommandLocked(const sp<IBinder>& token, float x, float y) REQUIRES(mLock);
+    void sendUntrustedTouchCommandLocked(const std::string& obscuringPackage) REQUIRES(mLock);
     void onAnrLocked(const sp<Connection>& connection) REQUIRES(mLock);
     void onAnrLocked(std::shared_ptr<InputApplicationHandle> application) REQUIRES(mLock);
-    void onUntrustedTouchLocked(const std::string& obscuringPackage) REQUIRES(mLock);
     void updateLastAnrStateLocked(const sp<android::gui::WindowInfoHandle>& window,
                                   const std::string& reason) REQUIRES(mLock);
     void updateLastAnrStateLocked(const InputApplicationHandle& application,
                                   const std::string& reason) REQUIRES(mLock);
     void updateLastAnrStateLocked(const std::string& windowLabel, const std::string& reason)
             REQUIRES(mLock);
-
-    // Outbound policy interactions.
-    void doNotifyConfigurationChangedLockedInterruptible(CommandEntry* commandEntry)
-            REQUIRES(mLock);
-    void doNotifyInputChannelBrokenLockedInterruptible(CommandEntry* commandEntry) REQUIRES(mLock);
-    void doNotifyFocusChangedLockedInterruptible(CommandEntry* commandEntry) REQUIRES(mLock);
-    void doNotifyDropWindowLockedInterruptible(CommandEntry* commandEntry) REQUIRES(mLock);
-
-    // ANR-related callbacks - start
-    void doNotifyNoFocusedWindowAnrLockedInterruptible(CommandEntry* commandEntry) REQUIRES(mLock);
-    void doNotifyWindowUnresponsiveLockedInterruptible(CommandEntry* commandEntry) REQUIRES(mLock);
-    void doNotifyMonitorUnresponsiveLockedInterruptible(CommandEntry* commandEntry) REQUIRES(mLock);
-    void doNotifyWindowResponsiveLockedInterruptible(CommandEntry* commandEntry) REQUIRES(mLock);
-    void doNotifyMonitorResponsiveLockedInterruptible(CommandEntry* commandEntry) REQUIRES(mLock);
-    // ANR-related callbacks - end
-    void doNotifySensorLockedInterruptible(CommandEntry* commandEntry) REQUIRES(mLock);
-    void doNotifyUntrustedTouchLockedInterruptible(CommandEntry* commandEntry) REQUIRES(mLock);
-    void doInterceptKeyBeforeDispatchingLockedInterruptible(CommandEntry* commandEntry)
-            REQUIRES(mLock);
-    void doDispatchCycleFinishedLockedInterruptible(CommandEntry* commandEntry) REQUIRES(mLock);
-    void doSetPointerCaptureLockedInterruptible(CommandEntry* commandEntry) REQUIRES(mLock);
-    bool afterKeyEventLockedInterruptible(const sp<Connection>& connection,
+    bool afterKeyEventLockedInterruptable(const sp<Connection>& connection,
                                           DispatchEntry* dispatchEntry, KeyEntry& keyEntry,
                                           bool handled) REQUIRES(mLock);
-    bool afterMotionEventLockedInterruptible(const sp<Connection>& connection,
+    bool afterMotionEventLockedInterruptable(const sp<Connection>& connection,
                                              DispatchEntry* dispatchEntry, MotionEntry& motionEntry,
                                              bool handled) REQUIRES(mLock);
-    void doPokeUserActivityLockedInterruptible(CommandEntry* commandEntry) REQUIRES(mLock);
-    void doOnPointerDownOutsideFocusLockedInterruptible(CommandEntry* commandEntry) REQUIRES(mLock);
 
     // Find touched state and touched window by token.
     std::pair<TouchState*, TouchedWindow*> findTouchStateAndWindowLocked(const sp<IBinder>& token)
@@ -671,9 +678,6 @@
     void traceWaitQueueLength(const Connection& connection);
 
     sp<InputReporterInterface> mReporter;
-    sp<com::android::internal::compat::IPlatformCompatNative> mCompatService;
-
-    void onFirstRef() override;
 };
 
 } // namespace android::inputdispatcher
diff --git a/services/inputflinger/dispatcher/InputDispatcherFactory.cpp b/services/inputflinger/dispatcher/InputDispatcherFactory.cpp
index 8d7fa75..bca1600 100644
--- a/services/inputflinger/dispatcher/InputDispatcherFactory.cpp
+++ b/services/inputflinger/dispatcher/InputDispatcherFactory.cpp
@@ -19,9 +19,9 @@
 
 namespace android {
 
-sp<InputDispatcherInterface> createInputDispatcher(
+std::unique_ptr<InputDispatcherInterface> createInputDispatcher(
         const sp<InputDispatcherPolicyInterface>& policy) {
-    return new android::inputdispatcher::InputDispatcher(policy);
+    return std::make_unique<android::inputdispatcher::InputDispatcher>(policy);
 }
 
 } // namespace android
diff --git a/services/inputflinger/dispatcher/InputState.cpp b/services/inputflinger/dispatcher/InputState.cpp
index 3bb0bc9..ad3c615 100644
--- a/services/inputflinger/dispatcher/InputState.cpp
+++ b/services/inputflinger/dispatcher/InputState.cpp
@@ -296,8 +296,7 @@
                                                   memento.yPrecision, memento.xCursorPosition,
                                                   memento.yCursorPosition, memento.downTime,
                                                   memento.pointerCount, memento.pointerProperties,
-                                                  memento.pointerCoords, 0 /*xOffset*/,
-                                                  0 /*yOffset*/));
+                                                  memento.pointerCoords));
         }
     }
     return events;
@@ -349,8 +348,7 @@
                                                   AMOTION_EVENT_EDGE_FLAG_NONE, memento.xPrecision,
                                                   memento.yPrecision, memento.xCursorPosition,
                                                   memento.yCursorPosition, memento.downTime,
-                                                  pointerCount, pointerProperties, pointerCoords,
-                                                  0 /*xOffset*/, 0 /*yOffset*/));
+                                                  pointerCount, pointerProperties, pointerCoords));
         }
 
         memento.firstNewPointerIdx = INVALID_POINTER_INDEX;
diff --git a/services/inputflinger/dispatcher/InputTarget.h b/services/inputflinger/dispatcher/InputTarget.h
index 7c463c8..0725389 100644
--- a/services/inputflinger/dispatcher/InputTarget.h
+++ b/services/inputflinger/dispatcher/InputTarget.h
@@ -21,7 +21,6 @@
 #include <input/InputTransport.h>
 #include <ui/Transform.h>
 #include <utils/BitSet.h>
-#include <utils/RefBase.h>
 
 namespace android::inputdispatcher {
 
@@ -101,11 +100,8 @@
     // (ignored for KeyEvents)
     float globalScaleFactor = 1.0f;
 
-    // Current display orientation
-    uint32_t displayOrientation = ui::Transform::ROT_0;
-
-    // Display-size in its natural rotation. Used for compatibility transform of raw coordinates.
-    int2 displaySize = {INVALID_DISPLAY_SIZE, INVALID_DISPLAY_SIZE};
+    // Current display transform. Used for compatibility for raw coordinates.
+    ui::Transform displayTransform;
 
     // The subset of pointer ids to include in motion events dispatched to this input target
     // if FLAG_SPLIT is set.
diff --git a/services/inputflinger/dispatcher/Monitor.cpp b/services/inputflinger/dispatcher/Monitor.cpp
index bbce759..43a82d5 100644
--- a/services/inputflinger/dispatcher/Monitor.cpp
+++ b/services/inputflinger/dispatcher/Monitor.cpp
@@ -22,8 +22,4 @@
 Monitor::Monitor(const std::shared_ptr<InputChannel>& inputChannel, int32_t pid)
       : inputChannel(inputChannel), pid(pid) {}
 
-// --- TouchedMonitor ---
-TouchedMonitor::TouchedMonitor(const Monitor& monitor, float xOffset, float yOffset)
-      : monitor(monitor), xOffset(xOffset), yOffset(yOffset) {}
-
 } // namespace android::inputdispatcher
diff --git a/services/inputflinger/dispatcher/Monitor.h b/services/inputflinger/dispatcher/Monitor.h
index 7be0760..365d5be 100644
--- a/services/inputflinger/dispatcher/Monitor.h
+++ b/services/inputflinger/dispatcher/Monitor.h
@@ -29,15 +29,6 @@
     explicit Monitor(const std::shared_ptr<InputChannel>& inputChannel, int32_t pid);
 };
 
-// For tracking the offsets we need to apply when adding gesture monitor targets.
-struct TouchedMonitor {
-    Monitor monitor;
-    float xOffset = 0.f;
-    float yOffset = 0.f;
-
-    explicit TouchedMonitor(const Monitor& monitor, float xOffset, float yOffset);
-};
-
 } // namespace android::inputdispatcher
 
 #endif // _UI_INPUT_INPUTDISPATCHER_MONITOR_H
diff --git a/services/inputflinger/dispatcher/TouchState.cpp b/services/inputflinger/dispatcher/TouchState.cpp
index 20b6ead..61e78cc 100644
--- a/services/inputflinger/dispatcher/TouchState.cpp
+++ b/services/inputflinger/dispatcher/TouchState.cpp
@@ -25,31 +25,8 @@
 
 namespace android::inputdispatcher {
 
-TouchState::TouchState()
-      : down(false), split(false), deviceId(-1), source(0), displayId(ADISPLAY_ID_NONE) {}
-
-TouchState::~TouchState() {}
-
 void TouchState::reset() {
-    down = false;
-    split = false;
-    deviceId = -1;
-    source = 0;
-    displayId = ADISPLAY_ID_NONE;
-    windows.clear();
-    portalWindows.clear();
-    gestureMonitors.clear();
-}
-
-void TouchState::copyFrom(const TouchState& other) {
-    down = other.down;
-    split = other.split;
-    deviceId = other.deviceId;
-    source = other.source;
-    displayId = other.displayId;
-    windows = other.windows;
-    portalWindows = other.portalWindows;
-    gestureMonitors = other.gestureMonitors;
+    *this = TouchState();
 }
 
 void TouchState::addOrUpdateWindow(const sp<WindowInfoHandle>& windowHandle, int32_t targetFlags,
@@ -70,6 +47,8 @@
         }
     }
 
+    if (preventNewTargets) return; // Don't add new TouchedWindows.
+
     TouchedWindow touchedWindow;
     touchedWindow.windowHandle = windowHandle;
     touchedWindow.targetFlags = targetFlags;
@@ -77,23 +56,6 @@
     windows.push_back(touchedWindow);
 }
 
-void TouchState::addPortalWindow(const sp<android::gui::WindowInfoHandle>& windowHandle) {
-    size_t numWindows = portalWindows.size();
-    for (size_t i = 0; i < numWindows; i++) {
-        if (portalWindows[i] == windowHandle) {
-            return;
-        }
-    }
-    portalWindows.push_back(windowHandle);
-}
-
-void TouchState::addGestureMonitors(const std::vector<TouchedMonitor>& newMonitors) {
-    const size_t newSize = gestureMonitors.size() + newMonitors.size();
-    gestureMonitors.reserve(newSize);
-    gestureMonitors.insert(std::end(gestureMonitors), std::begin(newMonitors),
-                           std::end(newMonitors));
-}
-
 void TouchState::removeWindowByToken(const sp<IBinder>& token) {
     for (size_t i = 0; i < windows.size(); i++) {
         if (windows[i].windowHandle->getToken() == token) {
@@ -117,9 +79,9 @@
     }
 }
 
-void TouchState::filterNonMonitors() {
-    windows.clear();
-    portalWindows.clear();
+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 {
@@ -138,7 +100,8 @@
     for (const TouchedWindow& window : windows) {
         if (window.targetFlags & InputTarget::FLAG_FOREGROUND) {
             if (haveSlipperyForegroundWindow ||
-                !window.windowHandle->getInfo()->flags.test(WindowInfo::Flag::SLIPPERY)) {
+                !window.windowHandle->getInfo()->inputConfig.test(
+                        WindowInfo::InputConfig::SLIPPERY)) {
                 return false;
             }
             haveSlipperyForegroundWindow = true;
@@ -147,4 +110,25 @@
     return haveSlipperyForegroundWindow;
 }
 
+sp<WindowInfoHandle> TouchState::getWallpaperWindow() const {
+    for (size_t i = 0; i < windows.size(); i++) {
+        const TouchedWindow& window = windows[i];
+        if (window.windowHandle->getInfo()->inputConfig.test(
+                    gui::WindowInfo::InputConfig::IS_WALLPAPER)) {
+            return window.windowHandle;
+        }
+    }
+    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;
+        }
+    }
+    return nullptr;
+}
+
 } // namespace android::inputdispatcher
diff --git a/services/inputflinger/dispatcher/TouchState.h b/services/inputflinger/dispatcher/TouchState.h
index a4e52b0..9efb280 100644
--- a/services/inputflinger/dispatcher/TouchState.h
+++ b/services/inputflinger/dispatcher/TouchState.h
@@ -29,33 +29,33 @@
 namespace inputdispatcher {
 
 struct TouchState {
-    bool down;
-    bool split;
-    int32_t deviceId;  // id of the device that is currently down, others are rejected
-    uint32_t source;   // source of the device that is current down, others are rejected
-    int32_t displayId; // id to the display that currently has a touch, others are rejected
+    bool down = false;
+    bool split = false;
+    bool preventNewTargets = 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;
 
-    // This collects the portal windows that the touch has gone through. Each portal window
-    // targets a display (embedded display for most cases). With this info, we can add the
-    // monitoring channels of the displays touched.
-    std::vector<sp<android::gui::WindowInfoHandle>> portalWindows;
+    TouchState() = default;
+    ~TouchState() = default;
+    TouchState& operator=(const TouchState&) = default;
 
-    std::vector<TouchedMonitor> gestureMonitors;
-
-    TouchState();
-    ~TouchState();
     void reset();
-    void copyFrom(const TouchState& other);
     void addOrUpdateWindow(const sp<android::gui::WindowInfoHandle>& windowHandle,
                            int32_t targetFlags, BitSet32 pointerIds);
-    void addPortalWindow(const sp<android::gui::WindowInfoHandle>& windowHandle);
-    void addGestureMonitors(const std::vector<TouchedMonitor>& monitors);
     void removeWindowByToken(const sp<IBinder>& token);
     void filterNonAsIsTouchWindows();
-    void filterNonMonitors();
+    void filterWindowsExcept(const sp<IBinder>& token);
     sp<android::gui::WindowInfoHandle> getFirstForegroundWindowHandle() const;
     bool isSlippery() const;
+    sp<android::gui::WindowInfoHandle> getWallpaperWindow() const;
+    sp<android::gui::WindowInfoHandle> getWindow(const sp<IBinder>&) const;
 };
 
 } // namespace inputdispatcher
diff --git a/services/inputflinger/dispatcher/include/InputDispatcherFactory.h b/services/inputflinger/dispatcher/include/InputDispatcherFactory.h
index a359557..38d0c32 100644
--- a/services/inputflinger/dispatcher/include/InputDispatcherFactory.h
+++ b/services/inputflinger/dispatcher/include/InputDispatcherFactory.h
@@ -17,7 +17,7 @@
 #ifndef _UI_INPUT_INPUTDISPATCHER_INPUTDISPATCHERFACTORY_H
 #define _UI_INPUT_INPUTDISPATCHER_INPUTDISPATCHERFACTORY_H
 
-#include <utils/RefBase.h>
+#include <utils/StrongPointer.h>
 
 #include "InputDispatcherInterface.h"
 #include "InputDispatcherPolicyInterface.h"
@@ -25,7 +25,7 @@
 namespace android {
 
 // This factory method is used to encapsulate implementation details in internal header files.
-sp<InputDispatcherInterface> createInputDispatcher(
+std::unique_ptr<InputDispatcherInterface> createInputDispatcher(
         const sp<InputDispatcherPolicyInterface>& policy);
 
 } // namespace android
diff --git a/services/inputflinger/dispatcher/include/InputDispatcherInterface.h b/services/inputflinger/dispatcher/include/InputDispatcherInterface.h
index a7dccf0..67e1b6f 100644
--- a/services/inputflinger/dispatcher/include/InputDispatcherInterface.h
+++ b/services/inputflinger/dispatcher/include/InputDispatcherInterface.h
@@ -33,12 +33,10 @@
 
 /* Notifies the system about input events generated by the input reader.
  * The dispatcher is expected to be mostly asynchronous. */
-class InputDispatcherInterface : public virtual RefBase, public InputListenerInterface {
-protected:
+class InputDispatcherInterface : public InputListenerInterface {
+public:
     InputDispatcherInterface() {}
     virtual ~InputDispatcherInterface() {}
-
-public:
     /* Dumps the state of the input dispatcher.
      *
      * This method may be called on any thread (usually by the input manager). */
@@ -125,8 +123,10 @@
      * 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.
+     *
+     * Returns true when changing touch mode state.
      */
-    virtual void setInTouchMode(bool inTouchMode) = 0;
+    virtual bool setInTouchMode(bool inTouchMode, int32_t pid, int32_t uid, bool hasPermission) = 0;
 
     /**
      * Sets the maximum allowed obscuring opacity by UID to propagate touches.
@@ -171,16 +171,14 @@
             const std::string& name) = 0;
 
     /**
-     * Creates an input channel to be used to monitor input events.
+     * Creates an input channel to be used to monitor all input events on a display.
      *
      * Each monitor must target a specific display and will only receive input events sent to that
-     * display. If the monitor is a gesture monitor, it will only receive pointer events on the
-     * targeted display.
+     * display.
      *
      * This method may be called on any thread (usually by the input manager).
      */
     virtual base::Result<std::unique_ptr<InputChannel>> createInputMonitor(int32_t displayId,
-                                                                           bool gestureMonitor,
                                                                            const std::string& name,
                                                                            int32_t pid) = 0;
 
@@ -202,6 +200,14 @@
      * InputDispatcher is the source of truth of Pointer Capture.
      */
     virtual void requestPointerCapture(const sp<IBinder>& windowToken, bool enabled) = 0;
+
+    /**
+     * Sets the eligibility of a given display to enable pointer capture. If a display is marked
+     * ineligible, all attempts to request pointer capture for windows on that display will fail.
+     *  TODO(b/214621487): Remove or move to a display flag.
+     */
+    virtual void setDisplayEligibilityForPointerCapture(int displayId, bool isEligible) = 0;
+
     /* Flush input device motion sensor.
      *
      * Returns true on success.
@@ -212,6 +218,11 @@
      * Called when a display has been removed from the system.
      */
     virtual void displayRemoved(int32_t displayId) = 0;
+
+    /*
+     * Abort the current touch stream.
+     */
+    virtual void cancelCurrentTouch() = 0;
 };
 
 } // namespace android
diff --git a/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h b/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h
index 3c1e637..de0b6da 100644
--- a/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h
+++ b/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h
@@ -51,30 +51,19 @@
             const std::shared_ptr<InputApplicationHandle>& inputApplicationHandle) = 0;
 
     /* Notifies the system that a window just became unresponsive. This indicates that ANR
-     * should be raised for this window. The window is identified via token.
-     * The string reason contains information about the input event that we haven't received
-     * a response for.
+     * should be raised for this window. The window can be identified via its input token and the
+     * pid of the owner. The string reason contains information about the input event that we
+     * haven't received a response for.
      */
-    virtual void notifyWindowUnresponsive(const sp<IBinder>& token, const std::string& reason) = 0;
-    /* Notifies the system that a monitor just became unresponsive. This indicates that ANR
-     * should be raised for this monitor. The monitor is identified via its pid.
-     * The string reason contains information about the input event that we haven't received
-     * a response for.
-     */
-    virtual void notifyMonitorUnresponsive(int32_t pid, const std::string& reason) = 0;
+    virtual void notifyWindowUnresponsive(const sp<IBinder>& token, std::optional<int32_t> pid,
+                                          const std::string& reason) = 0;
 
     /* Notifies the system that a window just became responsive. This is only called after the
      * window was first marked "unresponsive". This indicates that ANR dialog (if any) should
      * no longer should be shown to the user. The window is eligible to cause a new ANR in the
      * future.
      */
-    virtual void notifyWindowResponsive(const sp<IBinder>& token) = 0;
-    /* Notifies the system that a monitor just became responsive. This is only called after the
-     * monitor was first marked "unresponsive". This indicates that ANR dialog (if any) should
-     * no longer should be shown to the user. The monitor is eligible to cause a new ANR in the
-     * future.
-     */
-    virtual void notifyMonitorResponsive(int32_t pid) = 0;
+    virtual void notifyWindowResponsive(const sp<IBinder>& token, std::optional<int32_t> pid) = 0;
 
     /* Notifies the system that an input channel is unrecoverably broken. */
     virtual void notifyInputChannelBroken(const sp<IBinder>& token) = 0;
diff --git a/services/inputflinger/include/InputListener.h b/services/inputflinger/include/InputListener.h
index fe74214..d9822ce 100644
--- a/services/inputflinger/include/InputListener.h
+++ b/services/inputflinger/include/InputListener.h
@@ -22,7 +22,6 @@
 #include <input/Input.h>
 #include <input/InputDevice.h>
 #include <input/TouchVideoFrame.h>
-#include <utils/RefBase.h>
 
 namespace android {
 
@@ -40,7 +39,7 @@
 
     virtual ~NotifyArgs() { }
 
-    virtual void notify(const sp<InputListenerInterface>& listener) const = 0;
+    virtual void notify(InputListenerInterface& listener) const = 0;
 };
 
 
@@ -57,7 +56,7 @@
 
     virtual ~NotifyConfigurationChangedArgs() { }
 
-    virtual void notify(const sp<InputListenerInterface>& listener) const;
+    void notify(InputListenerInterface& listener) const override;
 };
 
 
@@ -88,7 +87,7 @@
 
     virtual ~NotifyKeyArgs() { }
 
-    virtual void notify(const sp<InputListenerInterface>& listener) const;
+    void notify(InputListenerInterface& listener) const override;
 };
 
 
@@ -142,7 +141,9 @@
 
     bool operator==(const NotifyMotionArgs& rhs) const;
 
-    virtual void notify(const sp<InputListenerInterface>& listener) const;
+    void notify(InputListenerInterface& listener) const override;
+
+    std::string dump() const;
 };
 
 /* Describes a sensor event. */
@@ -167,7 +168,7 @@
 
     ~NotifySensorArgs() override {}
 
-    void notify(const sp<InputListenerInterface>& listener) const override;
+    void notify(InputListenerInterface& listener) const override;
 };
 
 /* Describes a switch event. */
@@ -187,7 +188,7 @@
 
     virtual ~NotifySwitchArgs() { }
 
-    virtual void notify(const sp<InputListenerInterface>& listener) const;
+    void notify(InputListenerInterface& listener) const override;
 };
 
 
@@ -206,7 +207,7 @@
 
     virtual ~NotifyDeviceResetArgs() { }
 
-    virtual void notify(const sp<InputListenerInterface>& listener) const;
+    void notify(InputListenerInterface& listener) const override;
 };
 
 /* Describes a change in the state of Pointer Capture. */
@@ -224,7 +225,7 @@
 
     virtual ~NotifyPointerCaptureChangedArgs() {}
 
-    virtual void notify(const sp<InputListenerInterface>& listener) const;
+    void notify(InputListenerInterface& listener) const override;
 };
 
 /* Describes a vibrator state event. */
@@ -242,18 +243,19 @@
 
     virtual ~NotifyVibratorStateArgs() {}
 
-    virtual void notify(const sp<InputListenerInterface>& listener) const;
+    void notify(InputListenerInterface& listener) const override;
 };
 
 /*
  * The interface used by the InputReader to notify the InputListener about input events.
  */
-class InputListenerInterface : public virtual RefBase {
-protected:
+class InputListenerInterface {
+public:
     InputListenerInterface() { }
+    InputListenerInterface(const InputListenerInterface&) = delete;
+    InputListenerInterface& operator=(const InputListenerInterface&) = delete;
     virtual ~InputListenerInterface() { }
 
-public:
     virtual void notifyConfigurationChanged(const NotifyConfigurationChangedArgs* args) = 0;
     virtual void notifyKey(const NotifyKeyArgs* args) = 0;
     virtual void notifyMotion(const NotifyMotionArgs* args) = 0;
@@ -264,17 +266,14 @@
     virtual void notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs* args) = 0;
 };
 
-
 /*
  * An implementation of the listener interface that queues up and defers dispatch
  * of decoded events until flushed.
  */
 class QueuedInputListener : public InputListenerInterface {
-protected:
-    virtual ~QueuedInputListener();
 
 public:
-    explicit QueuedInputListener(const sp<InputListenerInterface>& innerListener);
+    explicit QueuedInputListener(InputListenerInterface& innerListener);
 
     virtual void notifyConfigurationChanged(const NotifyConfigurationChangedArgs* args) override;
     virtual void notifyKey(const NotifyKeyArgs* args) override;
@@ -288,8 +287,8 @@
     void flush();
 
 private:
-    sp<InputListenerInterface> mInnerListener;
-    std::vector<NotifyArgs*> mArgsQueue;
+    InputListenerInterface& mInnerListener;
+    std::vector<std::unique_ptr<NotifyArgs>> mArgsQueue;
 };
 
 } // namespace android
diff --git a/services/inputflinger/include/InputReaderBase.h b/services/inputflinger/include/InputReaderBase.h
index 3c8ac1c..41ecef3 100644
--- a/services/inputflinger/include/InputReaderBase.h
+++ b/services/inputflinger/include/InputReaderBase.h
@@ -17,6 +17,7 @@
 #ifndef _UI_INPUT_READER_BASE_H
 #define _UI_INPUT_READER_BASE_H
 
+#include <android/os/IInputConstants.h>
 #include <input/DisplayViewport.h>
 #include <input/Input.h>
 #include <input/InputDevice.h>
@@ -51,12 +52,11 @@
  * The implementation must guarantee thread safety for this interface. However, since the input
  * listener is NOT thread safe, all calls to the listener must happen from the same thread.
  */
-class InputReaderInterface : public virtual RefBase {
-protected:
+class InputReaderInterface {
+public:
     InputReaderInterface() { }
     virtual ~InputReaderInterface() { }
 
-public:
     /* Dumps the state of the input reader.
      *
      * This method may be called on any thread (usually by the input manager). */
@@ -88,6 +88,8 @@
     virtual int32_t getSwitchState(int32_t deviceId, uint32_t sourceMask,
             int32_t sw) = 0;
 
+    virtual int32_t getKeyCodeForKeyLocation(int32_t deviceId, int32_t locationKeyCode) const = 0;
+
     /* Toggle Caps Lock */
     virtual void toggleCapsLockState(int32_t deviceId) = 0;
 
@@ -287,7 +289,10 @@
 
     InputReaderConfiguration()
           : virtualKeyQuietTime(0),
-            pointerVelocityControlParameters(1.0f, 500.0f, 3000.0f, 3.0f),
+            pointerVelocityControlParameters(1.0f, 500.0f, 3000.0f,
+                                             static_cast<float>(
+                                                     android::os::IInputConstants::
+                                                             DEFAULT_POINTER_ACCELERATION)),
             wheelVelocityControlParameters(1.0f, 15.0f, 50.0f, 4.0f),
             pointerGesturesEnabled(true),
             pointerGestureQuietInterval(100 * 1000000LL),            // 100 ms
diff --git a/services/inputflinger/include/InputReaderFactory.h b/services/inputflinger/include/InputReaderFactory.h
index 9db6233..dad14d6 100644
--- a/services/inputflinger/include/InputReaderFactory.h
+++ b/services/inputflinger/include/InputReaderFactory.h
@@ -22,8 +22,7 @@
 class InputReaderPolicyInterface;
 class InputListenerInterface;
 
-sp<InputReaderInterface> createInputReader(
-        const sp<InputReaderPolicyInterface>& policy,
-        const sp<InputListenerInterface>& listener);
+std::unique_ptr<InputReaderInterface> createInputReader(
+        const sp<InputReaderPolicyInterface>& policy, InputListenerInterface& listener);
 
 } // namespace android
diff --git a/services/inputflinger/include/PointerControllerInterface.h b/services/inputflinger/include/PointerControllerInterface.h
index 85d7247..db4228d 100644
--- a/services/inputflinger/include/PointerControllerInterface.h
+++ b/services/inputflinger/include/PointerControllerInterface.h
@@ -20,7 +20,6 @@
 #include <input/DisplayViewport.h>
 #include <input/Input.h>
 #include <utils/BitSet.h>
-#include <utils/RefBase.h>
 
 namespace android {
 
@@ -31,7 +30,8 @@
  * fingers
  *
  * The pointer controller is responsible for providing synchronization and for tracking
- * display orientation changes if needed.
+ * display orientation changes if needed. It works in the display panel's coordinate space, which
+ * is the same coordinate space used by InputReader.
  */
 class PointerControllerInterface {
 protected:
diff --git a/services/inputflinger/include/UnwantedInteractionBlockerInterface.h b/services/inputflinger/include/UnwantedInteractionBlockerInterface.h
new file mode 100644
index 0000000..2327266
--- /dev/null
+++ b/services/inputflinger/include/UnwantedInteractionBlockerInterface.h
@@ -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.
+ */
+
+#pragma once
+
+#include "InputListener.h"
+
+namespace android {
+
+/**
+ * Base interface for an InputListener stage.
+ * Blocks unintentional input events. Not thread safe. Must be called from the same
+ * thread. All work is performed on the calling threads.
+ */
+class UnwantedInteractionBlockerInterface : public InputListenerInterface {
+public:
+    /* Notifies the input reader policy that some input devices have changed
+     * and provides information about all current input devices.
+     * Important! This call should happen on the same thread as the calls to the
+     * InputListenerInterface methods.
+     * That is, same thread should call 'notifyMotion' and 'notifyInputDevicesChanged' and
+     * 'notifyDeviceReset'. If this architecture changes, we will need to make the implementation
+     * of this interface thread-safe.
+     */
+    virtual void notifyInputDevicesChanged(const std::vector<InputDeviceInfo>& inputDevices) = 0;
+
+    /**
+     * Dump the state of the interaction blocker.
+     * This method may be called on any thread (usually by the input manager).
+     */
+    virtual void dump(std::string& dump) = 0;
+
+    /* Called by the heatbeat to ensures that the dispatcher has not deadlocked. */
+    virtual void monitor() = 0;
+
+    UnwantedInteractionBlockerInterface() {}
+    ~UnwantedInteractionBlockerInterface() override {}
+};
+
+} // namespace android
diff --git a/services/inputflinger/reader/Android.bp b/services/inputflinger/reader/Android.bp
index ee7b392..3bd3275 100644
--- a/services/inputflinger/reader/Android.bp
+++ b/services/inputflinger/reader/Android.bp
@@ -71,7 +71,7 @@
         "libstatslog",
         "libui",
         "libutils",
-        "InputFlingerProperties",
+        "PlatformProperties",
     ],
     static_libs: [
         "libc++fs",
@@ -86,7 +86,7 @@
     name: "libinputreader",
     defaults: [
         "inputflinger_defaults",
-        "libinputreader_defaults"
+        "libinputreader_defaults",
     ],
     srcs: [
         "InputReaderFactory.cpp",
@@ -100,6 +100,6 @@
         "libinputreader_headers",
     ],
     static_libs: [
-        "libc++fs"
+        "libc++fs",
     ],
 }
diff --git a/services/inputflinger/reader/EventHub.cpp b/services/inputflinger/reader/EventHub.cpp
index 166f358..8bd3899 100644
--- a/services/inputflinger/reader/EventHub.cpp
+++ b/services/inputflinger/reader/EventHub.cpp
@@ -40,6 +40,7 @@
 #include <android-base/stringprintf.h>
 #include <android-base/strings.h>
 #include <cutils/properties.h>
+#include <ftl/enum.h>
 #include <input/KeyCharacterMap.h>
 #include <input/KeyLayoutMap.h>
 #include <input/VirtualKeyMap.h>
@@ -263,7 +264,7 @@
  */
 static std::vector<std::filesystem::path> findSysfsNodes(const std::filesystem::path& sysfsRoot,
                                                          SysfsClass clazz) {
-    std::string nodeStr = NamedEnum::string(clazz);
+    std::string nodeStr = ftl::enum_string(clazz);
     std::for_each(nodeStr.begin(), nodeStr.end(),
                   [](char& c) { c = std::tolower(static_cast<unsigned char>(c)); });
     std::vector<std::filesystem::path> nodes;
@@ -684,7 +685,7 @@
     mEpollFd = epoll_create1(EPOLL_CLOEXEC);
     LOG_ALWAYS_FATAL_IF(mEpollFd < 0, "Could not create epoll instance: %s", strerror(errno));
 
-    mINotifyFd = inotify_init();
+    mINotifyFd = inotify_init1(IN_CLOEXEC);
 
     std::error_code errorCode;
     bool isDeviceInotifyAdded = false;
@@ -712,7 +713,7 @@
     LOG_ALWAYS_FATAL_IF(result != 0, "Could not add INotify to epoll instance.  errno=%d", errno);
 
     int wakeFds[2];
-    result = pipe(wakeFds);
+    result = pipe2(wakeFds, O_CLOEXEC);
     LOG_ALWAYS_FATAL_IF(result != 0, "Could not create wake pipe.  errno=%d", errno);
 
     mWakeReadPipeFd = wakeFds[0];
@@ -879,6 +880,42 @@
     return AKEY_STATE_UNKNOWN;
 }
 
+int32_t EventHub::getKeyCodeForKeyLocation(int32_t deviceId, int32_t locationKeyCode) const {
+    std::scoped_lock _l(mLock);
+
+    Device* device = getDeviceLocked(deviceId);
+    if (device == nullptr || !device->hasValidFd() || device->keyMap.keyCharacterMap == nullptr ||
+        device->keyMap.keyLayoutMap == nullptr) {
+        return AKEYCODE_UNKNOWN;
+    }
+    std::vector<int32_t> scanCodes;
+    device->keyMap.keyLayoutMap->findScanCodesForKey(locationKeyCode, &scanCodes);
+    if (scanCodes.empty()) {
+        ALOGW("Failed to get key code for key location: no scan code maps to key code %d for input"
+              "device %d",
+              locationKeyCode, deviceId);
+        return AKEYCODE_UNKNOWN;
+    }
+    if (scanCodes.size() > 1) {
+        ALOGW("Multiple scan codes map to the same key code %d, returning only the first match",
+              locationKeyCode);
+    }
+    int32_t outKeyCode;
+    status_t mapKeyRes =
+            device->getKeyCharacterMap()->mapKey(scanCodes[0], 0 /*usageCode*/, &outKeyCode);
+    switch (mapKeyRes) {
+        case OK:
+            return outKeyCode;
+        case NAME_NOT_FOUND:
+            // key character map doesn't re-map this scanCode, hence the keyCode remains the same
+            return locationKeyCode;
+        default:
+            ALOGW("Failed to get key code for key location: Key character map returned error %s",
+                  statusToString(mapKeyRes).c_str());
+            return AKEYCODE_UNKNOWN;
+    }
+}
+
 int32_t EventHub::getSwitchState(int32_t deviceId, int32_t sw) const {
     if (sw >= 0 && sw <= SW_MAX) {
         std::scoped_lock _l(mLock);
@@ -1209,6 +1246,15 @@
     return false;
 }
 
+bool EventHub::hasKeyCode(int32_t deviceId, int32_t keyCode) const {
+    std::scoped_lock _l(mLock);
+    Device* device = getDeviceLocked(deviceId);
+    if (device != nullptr) {
+        return device->hasKeycodeLocked(keyCode);
+    }
+    return false;
+}
+
 bool EventHub::hasLed(int32_t deviceId, int32_t led) const {
     std::scoped_lock _l(mLock);
     Device* device = getDeviceLocked(deviceId);
@@ -2333,13 +2379,10 @@
             return;
         }
     }
-    mUnattachedVideoDevices
-            .erase(std::remove_if(mUnattachedVideoDevices.begin(), mUnattachedVideoDevices.end(),
-                                  [&devicePath](
-                                          const std::unique_ptr<TouchVideoDevice>& videoDevice) {
-                                      return videoDevice->getPath() == devicePath;
-                                  }),
-                   mUnattachedVideoDevices.end());
+    std::erase_if(mUnattachedVideoDevices,
+                  [&devicePath](const std::unique_ptr<TouchVideoDevice>& videoDevice) {
+                      return videoDevice->getPath() == devicePath;
+                  });
 }
 
 void EventHub::closeAllDevicesLocked() {
diff --git a/services/inputflinger/reader/InputDevice.cpp b/services/inputflinger/reader/InputDevice.cpp
index 1e9ec54..a050963 100644
--- a/services/inputflinger/reader/InputDevice.cpp
+++ b/services/inputflinger/reader/InputDevice.cpp
@@ -309,15 +309,15 @@
                 const auto& displayPort = ports.find(inputPort);
                 if (displayPort != ports.end()) {
                     mAssociatedDisplayPort = std::make_optional(displayPort->second);
+                } else {
+                    const std::unordered_map<std::string, std::string>& displayUniqueIds =
+                            config->uniqueIdAssociations;
+                    const auto& displayUniqueId = displayUniqueIds.find(inputPort);
+                    if (displayUniqueId != displayUniqueIds.end()) {
+                        mAssociatedDisplayUniqueId = displayUniqueId->second;
+                    }
                 }
             }
-            const std::string& inputDeviceName = mIdentifier.name;
-            const std::unordered_map<std::string, std::string>& names =
-                    config->uniqueIdAssociations;
-            const auto& displayUniqueId = names.find(inputDeviceName);
-            if (displayUniqueId != names.end()) {
-                mAssociatedDisplayUniqueId = displayUniqueId->second;
-            }
 
             // If the device was explicitly disabled by the user, it would be present in the
             // "disabledDevices" list. If it is associated with a specific display, and it was not
@@ -338,7 +338,7 @@
                 if (!mAssociatedViewport) {
                     ALOGW("Input device %s should be associated with display %s but the "
                           "corresponding viewport cannot be found",
-                          inputDeviceName.c_str(), mAssociatedDisplayUniqueId->c_str());
+                          getName().c_str(), mAssociatedDisplayUniqueId->c_str());
                     enabled = false;
                 }
             }
@@ -379,21 +379,22 @@
     // gamepad button presses are handled by different mappers but they should be dispatched
     // in the order received.
     for (const RawEvent* rawEvent = rawEvents; count != 0; rawEvent++) {
-#if DEBUG_RAW_EVENTS
-        ALOGD("Input event: device=%d type=0x%04x code=0x%04x value=0x%08x when=%" PRId64,
-              rawEvent->deviceId, rawEvent->type, rawEvent->code, rawEvent->value, rawEvent->when);
-#endif
+        if (DEBUG_RAW_EVENTS) {
+            ALOGD("Input event: device=%d type=0x%04x code=0x%04x value=0x%08x when=%" PRId64,
+                  rawEvent->deviceId, rawEvent->type, rawEvent->code, rawEvent->value,
+                  rawEvent->when);
+        }
 
         if (mDropUntilNextSync) {
             if (rawEvent->type == EV_SYN && rawEvent->code == SYN_REPORT) {
                 mDropUntilNextSync = false;
-#if DEBUG_RAW_EVENTS
-                ALOGD("Recovered from input event buffer overrun.");
-#endif
+                if (DEBUG_RAW_EVENTS) {
+                    ALOGD("Recovered from input event buffer overrun.");
+                }
             } else {
-#if DEBUG_RAW_EVENTS
-                ALOGD("Dropped input event while waiting for next input sync.");
-#endif
+                if (DEBUG_RAW_EVENTS) {
+                    ALOGD("Dropped input event while waiting for next input sync.");
+                }
             }
         } else if (rawEvent->type == EV_SYN && rawEvent->code == SYN_DROPPED) {
             ALOGI("Detected input event buffer overrun for device %s.", getName().c_str());
@@ -474,6 +475,23 @@
     return result;
 }
 
+int32_t InputDevice::getKeyCodeForKeyLocation(int32_t locationKeyCode) const {
+    std::optional<int32_t> result = first_in_mappers<int32_t>(
+            [locationKeyCode](const InputMapper& mapper) -> std::optional<int32_t> const {
+                if (sourcesMatchMask(mapper.getSources(), AINPUT_SOURCE_KEYBOARD)) {
+                    return std::make_optional(mapper.getKeyCodeForKeyLocation(locationKeyCode));
+                }
+                return std::nullopt;
+            });
+    if (!result) {
+        ALOGE("Failed to get key code for key location: No matching InputMapper with source mask "
+              "KEYBOARD found. The provided input device with id %d has sources %s.",
+              getId(), inputEventSourceToString(getSources()).c_str());
+        return AKEYCODE_UNKNOWN;
+    }
+    return *result;
+}
+
 void InputDevice::vibrate(const VibrationSequence& sequence, ssize_t repeat, int32_t token) {
     for_each_mapper([sequence, repeat, token](InputMapper& mapper) {
         mapper.vibrate(sequence, repeat, token);
@@ -559,7 +577,13 @@
 }
 
 void InputDevice::updateMetaState(int32_t keyCode) {
-    for_each_mapper([keyCode](InputMapper& mapper) { mapper.updateMetaState(keyCode); });
+    first_in_mappers<bool>([keyCode](InputMapper& mapper) {
+        if (sourcesMatchMask(mapper.getSources(), AINPUT_SOURCE_KEYBOARD) &&
+            mapper.updateMetaState(keyCode)) {
+            return std::make_optional(true);
+        }
+        return std::optional<bool>();
+    });
 }
 
 void InputDevice::bumpGeneration() {
@@ -568,7 +592,7 @@
 
 void InputDevice::notifyReset(nsecs_t when) {
     NotifyDeviceResetArgs args(mContext->getNextId(), when, mId);
-    mContext->getListener()->notifyDeviceReset(&args);
+    mContext->getListener().notifyDeviceReset(&args);
 }
 
 std::optional<int32_t> InputDevice::getAssociatedDisplayId() {
diff --git a/services/inputflinger/reader/InputReader.cpp b/services/inputflinger/reader/InputReader.cpp
index 5120860..31d331b 100644
--- a/services/inputflinger/reader/InputReader.cpp
+++ b/services/inputflinger/reader/InputReader.cpp
@@ -42,10 +42,11 @@
 
 InputReader::InputReader(std::shared_ptr<EventHubInterface> eventHub,
                          const sp<InputReaderPolicyInterface>& policy,
-                         const sp<InputListenerInterface>& listener)
+                         InputListenerInterface& listener)
       : mContext(this),
         mEventHub(eventHub),
         mPolicy(policy),
+        mQueuedListener(listener),
         mGlobalMetaState(0),
         mLedMetaState(AMETA_NUM_LOCK_ON),
         mGeneration(1),
@@ -53,14 +54,8 @@
         mDisableVirtualKeysTimeout(LLONG_MIN),
         mNextTimeout(LLONG_MAX),
         mConfigurationChangesToRefresh(0) {
-    mQueuedListener = new QueuedInputListener(listener);
-
-    { // acquire lock
-        std::scoped_lock _l(mLock);
-
-        refreshConfigurationLocked(0);
-        updateGlobalMetaStateLocked();
-    } // release lock
+    refreshConfigurationLocked(0);
+    updateGlobalMetaStateLocked();
 }
 
 InputReader::~InputReader() {}
@@ -118,9 +113,9 @@
         if (mNextTimeout != LLONG_MAX) {
             nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
             if (now >= mNextTimeout) {
-#if DEBUG_RAW_EVENTS
-                ALOGD("Timeout expired, latency=%0.3fms", (now - mNextTimeout) * 0.000001f);
-#endif
+                if (DEBUG_RAW_EVENTS) {
+                    ALOGD("Timeout expired, latency=%0.3fms", (now - mNextTimeout) * 0.000001f);
+                }
                 mNextTimeout = LLONG_MAX;
                 timeoutExpiredLocked(now);
             }
@@ -144,7 +139,7 @@
     // resulting in a deadlock.  This situation is actually quite plausible because the
     // listener is actually the input dispatcher, which calls into the window manager,
     // which occasionally calls into the input reader.
-    mQueuedListener->flush();
+    mQueuedListener.flush();
 }
 
 void InputReader::processEventsLocked(const RawEvent* rawEvents, size_t count) {
@@ -160,9 +155,9 @@
                 }
                 batchSize += 1;
             }
-#if DEBUG_RAW_EVENTS
-            ALOGD("BatchSize: %zu Count: %zu", batchSize, count);
-#endif
+            if (DEBUG_RAW_EVENTS) {
+                ALOGD("BatchSize: %zu Count: %zu", batchSize, count);
+            }
             processEventsForDeviceLocked(deviceId, rawEvent, batchSize);
         } else {
             switch (rawEvent->type) {
@@ -242,9 +237,7 @@
     auto mapIt = mDeviceToEventHubIdsMap.find(device);
     if (mapIt != mDeviceToEventHubIdsMap.end()) {
         std::vector<int32_t>& eventHubIds = mapIt->second;
-        eventHubIds.erase(std::remove_if(eventHubIds.begin(), eventHubIds.end(),
-                                         [eventHubId](int32_t eId) { return eId == eventHubId; }),
-                          eventHubIds.end());
+        std::erase_if(eventHubIds, [eventHubId](int32_t eId) { return eId == eventHubId; });
         if (eventHubIds.size() == 0) {
             mDeviceToEventHubIdsMap.erase(mapIt);
         }
@@ -310,7 +303,7 @@
     device->process(rawEvents, count);
 }
 
-InputDevice* InputReader::findInputDeviceLocked(int32_t deviceId) {
+InputDevice* InputReader::findInputDeviceLocked(int32_t deviceId) const {
     auto deviceIt =
             std::find_if(mDevices.begin(), mDevices.end(), [deviceId](const auto& devicePair) {
                 return devicePair.second->getId() == deviceId;
@@ -340,7 +333,7 @@
 
     // Enqueue configuration changed.
     NotifyConfigurationChangedArgs args(mContext.getNextId(), when);
-    mQueuedListener->notifyConfigurationChanged(&args);
+    mQueuedListener.notifyConfigurationChanged(&args);
 }
 
 void InputReader::refreshConfigurationLocked(uint32_t changes) {
@@ -374,7 +367,7 @@
             mCurrentPointerCaptureRequest = mConfig.pointerCaptureRequest;
             const NotifyPointerCaptureChangedArgs args(mContext.getNextId(), now,
                                                        mCurrentPointerCaptureRequest);
-            mQueuedListener->notifyPointerCaptureChanged(&args);
+            mQueuedListener.notifyPointerCaptureChanged(&args);
         }
     }
 }
@@ -561,6 +554,7 @@
     }
 
     if (device->isIgnored()) {
+        ALOGW("Ignoring toggleCapsLock for ignored deviceId %" PRId32 ".", deviceId);
         return;
     }
 
@@ -595,6 +589,18 @@
     return result;
 }
 
+int32_t InputReader::getKeyCodeForKeyLocation(int32_t deviceId, int32_t locationKeyCode) const {
+    std::scoped_lock _l(mLock);
+
+    InputDevice* device = findInputDeviceLocked(deviceId);
+    if (device == nullptr) {
+        ALOGW("Failed to get key code for key location: Input device with id %d not found",
+              deviceId);
+        return AKEYCODE_UNKNOWN;
+    }
+    return device->getKeyCodeForKeyLocation(locationKeyCode);
+}
+
 void InputReader::requestRefreshConfiguration(uint32_t changes) {
     std::scoped_lock _l(mLock);
 
@@ -786,12 +792,8 @@
 
     std::optional<int32_t> associatedDisplayId = device->getAssociatedDisplayId();
     // No associated display. By default, can dispatch to all displays.
-    if (!associatedDisplayId) {
-        return true;
-    }
-
-    if (*associatedDisplayId == ADISPLAY_ID_NONE) {
-        ALOGW("Device %s is associated with display ADISPLAY_ID_NONE.", device->getName().c_str());
+    if (!associatedDisplayId ||
+            *associatedDisplayId == ADISPLAY_ID_NONE) {
         return true;
     }
 
@@ -951,8 +953,8 @@
     return mReader->mPolicy.get();
 }
 
-InputListenerInterface* InputReader::ContextImpl::getListener() {
-    return mReader->mQueuedListener.get();
+InputListenerInterface& InputReader::ContextImpl::getListener() {
+    return mReader->mQueuedListener;
 }
 
 EventHubInterface* InputReader::ContextImpl::getEventHub() {
diff --git a/services/inputflinger/reader/InputReaderFactory.cpp b/services/inputflinger/reader/InputReaderFactory.cpp
index a897141..2d9ffc3 100644
--- a/services/inputflinger/reader/InputReaderFactory.cpp
+++ b/services/inputflinger/reader/InputReaderFactory.cpp
@@ -20,9 +20,9 @@
 
 namespace android {
 
-sp<InputReaderInterface> createInputReader(const sp<InputReaderPolicyInterface>& policy,
-                                           const sp<InputListenerInterface>& listener) {
-    return new InputReader(std::make_unique<EventHub>(), policy, listener);
+std::unique_ptr<InputReaderInterface> createInputReader(
+        const sp<InputReaderPolicyInterface>& policy, InputListenerInterface& listener) {
+    return std::make_unique<InputReader>(std::make_unique<EventHub>(), policy, listener);
 }
 
 } // namespace android
\ No newline at end of file
diff --git a/services/inputflinger/reader/Macros.h b/services/inputflinger/reader/Macros.h
index 827e31a..d837689 100644
--- a/services/inputflinger/reader/Macros.h
+++ b/services/inputflinger/reader/Macros.h
@@ -22,28 +22,25 @@
 //#define LOG_NDEBUG 0
 
 // Log debug messages for each raw event received from the EventHub.
-#define DEBUG_RAW_EVENTS 0
-
-// Log debug messages about touch screen filtering hacks.
-#define DEBUG_HACKS 0
+static constexpr bool DEBUG_RAW_EVENTS = false;
 
 // Log debug messages about virtual key processing.
-#define DEBUG_VIRTUAL_KEYS 0
+static constexpr bool DEBUG_VIRTUAL_KEYS = false;
 
 // Log debug messages about pointers.
-#define DEBUG_POINTERS 0
+static constexpr bool DEBUG_POINTERS = false;
 
 // Log debug messages about pointer assignment calculations.
-#define DEBUG_POINTER_ASSIGNMENT 0
+static constexpr bool DEBUG_POINTER_ASSIGNMENT = false;
 
 // Log debug messages about gesture detection.
-#define DEBUG_GESTURES 0
+static constexpr bool DEBUG_GESTURES = false;
 
 // Log debug messages about the vibrator.
-#define DEBUG_VIBRATOR 0
+static constexpr bool DEBUG_VIBRATOR = false;
 
 // Log debug messages about fusing stylus data.
-#define DEBUG_STYLUS_FUSION 0
+static constexpr bool DEBUG_STYLUS_FUSION = false;
 
 #define INDENT "  "
 #define INDENT2 "    "
diff --git a/services/inputflinger/reader/TouchVideoDevice.cpp b/services/inputflinger/reader/TouchVideoDevice.cpp
index c7c8e28..2f8138b 100644
--- a/services/inputflinger/reader/TouchVideoDevice.cpp
+++ b/services/inputflinger/reader/TouchVideoDevice.cpp
@@ -49,7 +49,7 @@
 };
 
 std::unique_ptr<TouchVideoDevice> TouchVideoDevice::create(std::string devicePath) {
-    unique_fd fd(open(devicePath.c_str(), O_RDWR | O_NONBLOCK));
+    unique_fd fd(open(devicePath.c_str(), O_RDWR | O_NONBLOCK | O_CLOEXEC));
     if (fd.get() == INVALID_FD) {
         ALOGE("Could not open video device %s: %s", devicePath.c_str(), strerror(errno));
         return nullptr;
diff --git a/services/inputflinger/reader/controller/PeripheralController.cpp b/services/inputflinger/reader/controller/PeripheralController.cpp
index 9c8a29a..a693496 100644
--- a/services/inputflinger/reader/controller/PeripheralController.cpp
+++ b/services/inputflinger/reader/controller/PeripheralController.cpp
@@ -17,9 +17,9 @@
 #include <locale>
 #include <regex>
 
-#include "../Macros.h"
+#include <ftl/enum.h>
 
-#include <ftl/NamedEnum.h>
+#include "../Macros.h"
 #include "PeripheralController.h"
 
 // Log detailed debug messages about input device lights.
@@ -286,7 +286,7 @@
         for (const auto& [lightId, light] : mLights) {
             dump += StringPrintf(INDENT4 "Id: %d", lightId);
             dump += StringPrintf(INDENT4 "Name: %s", light->name.c_str());
-            dump += StringPrintf(INDENT4 "Type: %s", NamedEnum::string(light->type).c_str());
+            dump += StringPrintf(INDENT4 "Type: %s", ftl::enum_string(light->type).c_str());
             light->dump(dump);
         }
     }
@@ -487,7 +487,7 @@
     auto& light = it->second;
     if (DEBUG_LIGHT_DETAILS) {
         ALOGD("setLightColor lightId %d type %s color 0x%x", lightId,
-              NamedEnum::string(light->type).c_str(), color);
+              ftl::enum_string(light->type).c_str(), color);
     }
     return light->setLightColor(color);
 }
@@ -501,7 +501,7 @@
     std::optional<int32_t> color = light->getLightColor();
     if (DEBUG_LIGHT_DETAILS) {
         ALOGD("getLightColor lightId %d type %s color 0x%x", lightId,
-              NamedEnum::string(light->type).c_str(), color.value_or(0));
+              ftl::enum_string(light->type).c_str(), color.value_or(0));
     }
     return color;
 }
diff --git a/services/inputflinger/reader/include/EventHub.h b/services/inputflinger/reader/include/EventHub.h
index 3c3f88e..18e912d 100644
--- a/services/inputflinger/reader/include/EventHub.h
+++ b/services/inputflinger/reader/include/EventHub.h
@@ -146,6 +146,8 @@
 enum class SysfsClass : uint32_t {
     POWER_SUPPLY = 0,
     LEDS = 1,
+
+    ftl_last = LEDS
 };
 
 enum class LightColor : uint32_t {
@@ -304,6 +306,7 @@
     virtual int32_t getSwitchState(int32_t deviceId, int32_t sw) const = 0;
     virtual status_t getAbsoluteAxisValue(int32_t deviceId, int32_t axis,
                                           int32_t* outValue) const = 0;
+    virtual int32_t getKeyCodeForKeyLocation(int32_t deviceId, int32_t locationKeyCode) const = 0;
 
     /*
      * Examine key input devices for specific framework keycode support
@@ -312,6 +315,7 @@
                                        uint8_t* outFlags) const = 0;
 
     virtual bool hasScanCode(int32_t deviceId, int32_t scanCode) const = 0;
+    virtual bool hasKeyCode(int32_t deviceId, int32_t keyCode) const = 0;
 
     /* LED related functions expect Android LED constants, not scan codes or HID usages */
     virtual bool hasLed(int32_t deviceId, int32_t led) const = 0;
@@ -479,6 +483,8 @@
     int32_t getScanCodeState(int32_t deviceId, int32_t scanCode) const override final;
     int32_t getKeyCodeState(int32_t deviceId, int32_t keyCode) const override final;
     int32_t getSwitchState(int32_t deviceId, int32_t sw) const override final;
+    int32_t getKeyCodeForKeyLocation(int32_t deviceId,
+                                     int32_t locationKeyCode) const override final;
     status_t getAbsoluteAxisValue(int32_t deviceId, int32_t axis,
                                   int32_t* outValue) const override final;
 
@@ -489,6 +495,7 @@
     std::vector<TouchVideoFrame> getVideoFrames(int32_t deviceId) override final;
 
     bool hasScanCode(int32_t deviceId, int32_t scanCode) const override final;
+    bool hasKeyCode(int32_t deviceId, int32_t keyCode) const override final;
     bool hasLed(int32_t deviceId, int32_t led) const override final;
     void setLedState(int32_t deviceId, int32_t led, bool on) override final;
 
diff --git a/services/inputflinger/reader/include/InputDevice.h b/services/inputflinger/reader/include/InputDevice.h
index f32472d..694daa9 100644
--- a/services/inputflinger/reader/include/InputDevice.h
+++ b/services/inputflinger/reader/include/InputDevice.h
@@ -61,6 +61,9 @@
     inline std::optional<uint8_t> getAssociatedDisplayPort() const {
         return mAssociatedDisplayPort;
     }
+    inline std::optional<std::string> getAssociatedDisplayUniqueId() const {
+        return mAssociatedDisplayUniqueId;
+    }
     inline std::optional<DisplayViewport> getAssociatedViewport() const {
         return mAssociatedViewport;
     }
@@ -84,6 +87,7 @@
     int32_t getKeyCodeState(uint32_t sourceMask, int32_t keyCode);
     int32_t getScanCodeState(uint32_t sourceMask, int32_t scanCode);
     int32_t getSwitchState(uint32_t sourceMask, int32_t switchCode);
+    int32_t getKeyCodeForKeyLocation(int32_t locationKeyCode) const;
     bool markSupportedKeyCodes(uint32_t sourceMask, size_t numCodes, const int32_t* keyCodes,
                                uint8_t* outFlags);
     void vibrate(const VibrationSequence& sequence, ssize_t repeat, int32_t token);
@@ -216,7 +220,8 @@
     // return the first value returned by a function over every mapper.
     // if all mappers return nullopt, return nullopt.
     template <typename T>
-    inline std::optional<T> first_in_mappers(std::function<std::optional<T>(InputMapper&)> f) {
+    inline std::optional<T> first_in_mappers(
+            std::function<std::optional<T>(InputMapper&)> f) const {
         for (auto& deviceEntry : mDevices) {
             auto& devicePair = deviceEntry.second;
             auto& mappers = devicePair.second;
@@ -312,6 +317,9 @@
     inline int32_t getKeyCodeState(int32_t keyCode) const {
         return mEventHub->getKeyCodeState(mId, keyCode);
     }
+    inline int32_t getKeyCodeForKeyLocation(int32_t locationKeyCode) const {
+        return mEventHub->getKeyCodeForKeyLocation(mId, locationKeyCode);
+    }
     inline int32_t getSwitchState(int32_t sw) const { return mEventHub->getSwitchState(mId, sw); }
     inline status_t getAbsoluteAxisValue(int32_t code, int32_t* outValue) const {
         return mEventHub->getAbsoluteAxisValue(mId, code, outValue);
@@ -323,6 +331,7 @@
     inline bool hasScanCode(int32_t scanCode) const {
         return mEventHub->hasScanCode(mId, scanCode);
     }
+    inline bool hasKeyCode(int32_t keyCode) const { return mEventHub->hasKeyCode(mId, keyCode); }
     inline bool hasLed(int32_t led) const { return mEventHub->hasLed(mId, led); }
     inline void setLedState(int32_t led, bool on) { return mEventHub->setLedState(mId, led, on); }
     inline void getVirtualKeyDefinitions(std::vector<VirtualKeyDefinition>& outVirtualKeys) const {
@@ -380,6 +389,9 @@
     inline std::optional<uint8_t> getAssociatedDisplayPort() const {
         return mDevice.getAssociatedDisplayPort();
     }
+    inline std::optional<std::string> getAssociatedDisplayUniqueId() const {
+        return mDevice.getAssociatedDisplayUniqueId();
+    }
     inline std::optional<DisplayViewport> getAssociatedViewport() const {
         return mDevice.getAssociatedViewport();
     }
diff --git a/services/inputflinger/reader/include/InputReader.h b/services/inputflinger/reader/include/InputReader.h
index e44aa0f..daeaa1d 100644
--- a/services/inputflinger/reader/include/InputReader.h
+++ b/services/inputflinger/reader/include/InputReader.h
@@ -52,8 +52,7 @@
 class InputReader : public InputReaderInterface {
 public:
     InputReader(std::shared_ptr<EventHubInterface> eventHub,
-                const sp<InputReaderPolicyInterface>& policy,
-                const sp<InputListenerInterface>& listener);
+                const sp<InputReaderPolicyInterface>& policy, InputListenerInterface& listener);
     virtual ~InputReader();
 
     void dump(std::string& dump) override;
@@ -70,6 +69,8 @@
     int32_t getKeyCodeState(int32_t deviceId, uint32_t sourceMask, int32_t keyCode) override;
     int32_t getSwitchState(int32_t deviceId, uint32_t sourceMask, int32_t sw) override;
 
+    int32_t getKeyCodeForKeyLocation(int32_t deviceId, int32_t locationKeyCode) const override;
+
     void toggleCapsLockState(int32_t deviceId) override;
 
     bool hasKeys(int32_t deviceId, uint32_t sourceMask, size_t numCodes, const int32_t* keyCodes,
@@ -143,7 +144,7 @@
         void dispatchExternalStylusState(const StylusState& outState)
                 REQUIRES(mReader->mLock) override;
         InputReaderPolicyInterface* getPolicy() REQUIRES(mReader->mLock) override;
-        InputListenerInterface* getListener() REQUIRES(mReader->mLock) override;
+        InputListenerInterface& getListener() REQUIRES(mReader->mLock) override;
         EventHubInterface* getEventHub() REQUIRES(mReader->mLock) override;
         int32_t getNextId() NO_THREAD_SAFETY_ANALYSIS override;
         void updateLedMetaState(int32_t metaState) REQUIRES(mReader->mLock) override;
@@ -164,7 +165,7 @@
     // in parallel to passing it to the InputReader.
     std::shared_ptr<EventHubInterface> mEventHub;
     sp<InputReaderPolicyInterface> mPolicy;
-    sp<QueuedInputListener> mQueuedListener;
+    QueuedInputListener mQueuedListener;
 
     InputReaderConfiguration mConfig GUARDED_BY(mLock);
 
@@ -240,7 +241,7 @@
                                      const int32_t* keyCodes, uint8_t* outFlags) REQUIRES(mLock);
 
     // find an InputDevice from an InputDevice id
-    InputDevice* findInputDeviceLocked(int32_t deviceId) REQUIRES(mLock);
+    InputDevice* findInputDeviceLocked(int32_t deviceId) const REQUIRES(mLock);
 };
 
 } // namespace android
diff --git a/services/inputflinger/reader/include/InputReaderContext.h b/services/inputflinger/reader/include/InputReaderContext.h
index dc807f7..823d160 100644
--- a/services/inputflinger/reader/include/InputReaderContext.h
+++ b/services/inputflinger/reader/include/InputReaderContext.h
@@ -55,7 +55,7 @@
     virtual void dispatchExternalStylusState(const StylusState& outState) = 0;
 
     virtual InputReaderPolicyInterface* getPolicy() = 0;
-    virtual InputListenerInterface* getListener() = 0;
+    virtual InputListenerInterface& getListener() = 0;
     virtual EventHubInterface* getEventHub() = 0;
 
     virtual int32_t getNextId() = 0;
diff --git a/services/inputflinger/reader/mapper/CursorInputMapper.cpp b/services/inputflinger/reader/mapper/CursorInputMapper.cpp
index 2ac41b1..dc5fcec 100644
--- a/services/inputflinger/reader/mapper/CursorInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/CursorInputMapper.cpp
@@ -66,7 +66,7 @@
 
 CursorInputMapper::~CursorInputMapper() {}
 
-uint32_t CursorInputMapper::getSources() {
+uint32_t CursorInputMapper::getSources() const {
     return mSource;
 }
 
@@ -176,7 +176,7 @@
         bumpGeneration();
         if (changes) {
             NotifyDeviceResetArgs args(getContext()->getNextId(), when, getDeviceId());
-            getListener()->notifyDeviceReset(&args);
+            getListener().notifyDeviceReset(&args);
         }
     }
 
@@ -188,33 +188,19 @@
 
     if (!changes || (changes & InputReaderConfiguration::CHANGE_DISPLAY_INFO)) {
         mOrientation = DISPLAY_ORIENTATION_0;
-        mDisplayWidth = 0;
-        mDisplayHeight = 0;
         const bool isOrientedDevice =
                 (mParameters.orientationAware && mParameters.hasAssociatedDisplay);
 
-        if (isPerWindowInputRotationEnabled()) {
-            // When per-window input rotation is enabled, InputReader works in the un-rotated
-            // coordinate space, so we don't need to do anything if the device is already
-            // orientation-aware. If the device is not orientation-aware, then we need to apply the
-            // inverse rotation of the display so that when the display rotation is applied later
-            // as a part of the per-window transform, we get the expected screen coordinates.
-            if (!isOrientedDevice) {
-                std::optional<DisplayViewport> internalViewport =
-                        config->getDisplayViewportByType(ViewportType::INTERNAL);
-                if (internalViewport) {
-                    mOrientation = getInverseRotation(internalViewport->orientation);
-                    mDisplayWidth = internalViewport->deviceWidth;
-                    mDisplayHeight = internalViewport->deviceHeight;
-                }
-            }
-        } else {
-            if (isOrientedDevice) {
-                std::optional<DisplayViewport> internalViewport =
-                        config->getDisplayViewportByType(ViewportType::INTERNAL);
-                if (internalViewport) {
-                    mOrientation = internalViewport->orientation;
-                }
+        // InputReader works in the un-rotated display coordinate space, so we don't need to do
+        // anything if the device is already orientation-aware. If the device is not
+        // orientation-aware, then we need to apply the inverse rotation of the display so that
+        // when the display rotation is applied later as a part of the per-window transform, we
+        // get the expected screen coordinates.
+        if (!isOrientedDevice) {
+            std::optional<DisplayViewport> internalViewport =
+                    config->getDisplayViewportByType(ViewportType::INTERNAL);
+            if (internalViewport) {
+                mOrientation = getInverseRotation(internalViewport->orientation);
             }
         }
 
@@ -345,15 +331,7 @@
             mPointerController->setPresentation(PointerControllerInterface::Presentation::POINTER);
 
             if (moved) {
-                float dx = deltaX;
-                float dy = deltaY;
-                if (isPerWindowInputRotationEnabled()) {
-                    // Rotate the delta from InputReader's un-rotated coordinate space to
-                    // PointerController's rotated coordinate space that is oriented with the
-                    // viewport.
-                    rotateDelta(getInverseRotation(mOrientation), &dx, &dy);
-                }
-                mPointerController->move(dx, dy);
+                mPointerController->move(deltaX, deltaY);
             }
 
             if (buttonsChanged) {
@@ -364,12 +342,7 @@
         }
 
         mPointerController->getPosition(&xCursorPosition, &yCursorPosition);
-        if (isPerWindowInputRotationEnabled()) {
-            // Rotate the cursor position that is in PointerController's rotated coordinate space
-            // to InputReader's un-rotated coordinate space.
-            rotatePoint(mOrientation, xCursorPosition /*byRef*/, yCursorPosition /*byRef*/,
-                        mDisplayWidth, mDisplayHeight);
-        }
+
         pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_X, xCursorPosition);
         pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, yCursorPosition);
         pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X, deltaX);
@@ -424,7 +397,7 @@
                                              &pointerCoords, mXPrecision, mYPrecision,
                                              xCursorPosition, yCursorPosition, downTime,
                                              /* videoFrames */ {});
-                getListener()->notifyMotion(&releaseArgs);
+                getListener().notifyMotion(&releaseArgs);
             }
         }
 
@@ -434,7 +407,7 @@
                               AMOTION_EVENT_EDGE_FLAG_NONE, 1, &pointerProperties, &pointerCoords,
                               mXPrecision, mYPrecision, xCursorPosition, yCursorPosition, downTime,
                               /* videoFrames */ {});
-        getListener()->notifyMotion(&args);
+        getListener().notifyMotion(&args);
 
         if (buttonsPressed) {
             BitSet32 pressed(buttonsPressed);
@@ -449,7 +422,7 @@
                                            &pointerCoords, mXPrecision, mYPrecision,
                                            xCursorPosition, yCursorPosition, downTime,
                                            /* videoFrames */ {});
-                getListener()->notifyMotion(&pressArgs);
+                getListener().notifyMotion(&pressArgs);
             }
         }
 
@@ -464,7 +437,7 @@
                                        AMOTION_EVENT_EDGE_FLAG_NONE, 1, &pointerProperties,
                                        &pointerCoords, mXPrecision, mYPrecision, xCursorPosition,
                                        yCursorPosition, downTime, /* videoFrames */ {});
-            getListener()->notifyMotion(&hoverArgs);
+            getListener().notifyMotion(&hoverArgs);
         }
 
         // Send scroll events.
@@ -479,7 +452,7 @@
                                         AMOTION_EVENT_EDGE_FLAG_NONE, 1, &pointerProperties,
                                         &pointerCoords, mXPrecision, mYPrecision, xCursorPosition,
                                         yCursorPosition, downTime, /* videoFrames */ {});
-            getListener()->notifyMotion(&scrollArgs);
+            getListener().notifyMotion(&scrollArgs);
         }
     }
 
diff --git a/services/inputflinger/reader/mapper/CursorInputMapper.h b/services/inputflinger/reader/mapper/CursorInputMapper.h
index 88e947f..c84c6c4 100644
--- a/services/inputflinger/reader/mapper/CursorInputMapper.h
+++ b/services/inputflinger/reader/mapper/CursorInputMapper.h
@@ -56,7 +56,7 @@
     explicit CursorInputMapper(InputDeviceContext& deviceContext);
     virtual ~CursorInputMapper();
 
-    virtual uint32_t getSources() override;
+    virtual uint32_t getSources() const override;
     virtual void populateDeviceInfo(InputDeviceInfo* deviceInfo) override;
     virtual void dump(std::string& dump) override;
     virtual void configure(nsecs_t when, const InputReaderConfiguration* config,
@@ -105,8 +105,6 @@
     VelocityControl mWheelYVelocityControl;
 
     int32_t mOrientation;
-    int32_t mDisplayWidth;
-    int32_t mDisplayHeight;
 
     std::shared_ptr<PointerControllerInterface> mPointerController;
 
diff --git a/services/inputflinger/reader/mapper/ExternalStylusInputMapper.cpp b/services/inputflinger/reader/mapper/ExternalStylusInputMapper.cpp
index 37d1d74..6b5d37f 100644
--- a/services/inputflinger/reader/mapper/ExternalStylusInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/ExternalStylusInputMapper.cpp
@@ -26,7 +26,7 @@
 ExternalStylusInputMapper::ExternalStylusInputMapper(InputDeviceContext& deviceContext)
       : InputMapper(deviceContext) {}
 
-uint32_t ExternalStylusInputMapper::getSources() {
+uint32_t ExternalStylusInputMapper::getSources() const {
     return AINPUT_SOURCE_STYLUS;
 }
 
diff --git a/services/inputflinger/reader/mapper/ExternalStylusInputMapper.h b/services/inputflinger/reader/mapper/ExternalStylusInputMapper.h
index 1d42b30..516aa51 100644
--- a/services/inputflinger/reader/mapper/ExternalStylusInputMapper.h
+++ b/services/inputflinger/reader/mapper/ExternalStylusInputMapper.h
@@ -30,7 +30,7 @@
     explicit ExternalStylusInputMapper(InputDeviceContext& deviceContext);
     virtual ~ExternalStylusInputMapper() = default;
 
-    virtual uint32_t getSources() override;
+    virtual uint32_t getSources() const override;
     virtual void populateDeviceInfo(InputDeviceInfo* deviceInfo) override;
     virtual void dump(std::string& dump) override;
     virtual void configure(nsecs_t when, const InputReaderConfiguration* config,
diff --git a/services/inputflinger/reader/mapper/InputMapper.cpp b/services/inputflinger/reader/mapper/InputMapper.cpp
index df1acd4..7b185e0 100644
--- a/services/inputflinger/reader/mapper/InputMapper.cpp
+++ b/services/inputflinger/reader/mapper/InputMapper.cpp
@@ -51,6 +51,10 @@
     return AKEY_STATE_UNKNOWN;
 }
 
+int32_t InputMapper::getKeyCodeForKeyLocation(int32_t locationKeyCode) const {
+    return AKEYCODE_UNKNOWN;
+}
+
 bool InputMapper::markSupportedKeyCodes(uint32_t sourceMask, size_t numCodes,
                                         const int32_t* keyCodes, uint8_t* outFlags) {
     return false;
@@ -84,7 +88,9 @@
     return 0;
 }
 
-void InputMapper::updateMetaState(int32_t keyCode) {}
+bool InputMapper::updateMetaState(int32_t keyCode) {
+    return false;
+}
 
 void InputMapper::updateExternalStylusState(const StylusState& state) {}
 
diff --git a/services/inputflinger/reader/mapper/InputMapper.h b/services/inputflinger/reader/mapper/InputMapper.h
index 15cff1c..fce6409 100644
--- a/services/inputflinger/reader/mapper/InputMapper.h
+++ b/services/inputflinger/reader/mapper/InputMapper.h
@@ -45,12 +45,13 @@
 
     inline int32_t getDeviceId() { return mDeviceContext.getId(); }
     inline InputDeviceContext& getDeviceContext() { return mDeviceContext; }
-    inline const std::string getDeviceName() { return mDeviceContext.getName(); }
+    inline InputDeviceContext& getDeviceContext() const { return mDeviceContext; };
+    inline const std::string getDeviceName() const { return mDeviceContext.getName(); }
     inline InputReaderContext* getContext() { return mDeviceContext.getContext(); }
     inline InputReaderPolicyInterface* getPolicy() { return getContext()->getPolicy(); }
-    inline InputListenerInterface* getListener() { return getContext()->getListener(); }
+    inline InputListenerInterface& getListener() { return getContext()->getListener(); }
 
-    virtual uint32_t getSources() = 0;
+    virtual uint32_t getSources() const = 0;
     virtual void populateDeviceInfo(InputDeviceInfo* deviceInfo);
     virtual void dump(std::string& dump);
     virtual void configure(nsecs_t when, const InputReaderConfiguration* config, uint32_t changes);
@@ -61,6 +62,8 @@
     virtual int32_t getKeyCodeState(uint32_t sourceMask, int32_t keyCode);
     virtual int32_t getScanCodeState(uint32_t sourceMask, int32_t scanCode);
     virtual int32_t getSwitchState(uint32_t sourceMask, int32_t switchCode);
+    virtual int32_t getKeyCodeForKeyLocation(int32_t locationKeyCode) const;
+
     virtual bool markSupportedKeyCodes(uint32_t sourceMask, size_t numCodes,
                                        const int32_t* keyCodes, uint8_t* outFlags);
     virtual void vibrate(const VibrationSequence& sequence, ssize_t repeat, int32_t token);
@@ -83,7 +86,11 @@
     virtual std::optional<int32_t> getLightPlayerId(int32_t lightId) { return std::nullopt; }
 
     virtual int32_t getMetaState();
-    virtual void updateMetaState(int32_t keyCode);
+    /**
+     * Process the meta key and update the global meta state when changed.
+     * Return true if the meta key could be handled by the InputMapper.
+     */
+    virtual bool updateMetaState(int32_t keyCode);
 
     virtual void updateExternalStylusState(const StylusState& state);
 
diff --git a/services/inputflinger/reader/mapper/JoystickInputMapper.cpp b/services/inputflinger/reader/mapper/JoystickInputMapper.cpp
index 0dc312e..7d30d0c 100644
--- a/services/inputflinger/reader/mapper/JoystickInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/JoystickInputMapper.cpp
@@ -25,15 +25,14 @@
 
 JoystickInputMapper::~JoystickInputMapper() {}
 
-uint32_t JoystickInputMapper::getSources() {
+uint32_t JoystickInputMapper::getSources() const {
     return AINPUT_SOURCE_JOYSTICK;
 }
 
 void JoystickInputMapper::populateDeviceInfo(InputDeviceInfo* info) {
     InputMapper::populateDeviceInfo(info);
 
-    for (std::pair<const int32_t, Axis>& pair : mAxes) {
-        const Axis& axis = pair.second;
+    for (const auto& [_, axis] : mAxes) {
         addMotionRange(axis.axisInfo.axis, axis, info);
 
         if (axis.axisInfo.mode == AxisInfo::MODE_SPLIT) {
@@ -336,15 +335,18 @@
     // button will likely wake the device.
     // TODO: Use the input device configuration to control this behavior more finely.
     uint32_t policyFlags = 0;
+    int32_t displayId = ADISPLAY_ID_NONE;
+    if (getDeviceContext().getAssociatedViewport()) {
+        displayId = getDeviceContext().getAssociatedViewport()->displayId;
+    }
 
     NotifyMotionArgs args(getContext()->getNextId(), when, readTime, getDeviceId(),
-                          AINPUT_SOURCE_JOYSTICK, ADISPLAY_ID_NONE, policyFlags,
-                          AMOTION_EVENT_ACTION_MOVE, 0, 0, metaState, buttonState,
-                          MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, 1,
-                          &pointerProperties, &pointerCoords, 0, 0,
+                          AINPUT_SOURCE_JOYSTICK, displayId, policyFlags, AMOTION_EVENT_ACTION_MOVE,
+                          0, 0, metaState, buttonState, MotionClassification::NONE,
+                          AMOTION_EVENT_EDGE_FLAG_NONE, 1, &pointerProperties, &pointerCoords, 0, 0,
                           AMOTION_EVENT_INVALID_CURSOR_POSITION,
                           AMOTION_EVENT_INVALID_CURSOR_POSITION, 0, /* videoFrames */ {});
-    getListener()->notifyMotion(&args);
+    getListener().notifyMotion(&args);
 }
 
 void JoystickInputMapper::setPointerCoordsAxisValue(PointerCoords* pointerCoords, int32_t axis,
diff --git a/services/inputflinger/reader/mapper/JoystickInputMapper.h b/services/inputflinger/reader/mapper/JoystickInputMapper.h
index bba95ad..307bf5b 100644
--- a/services/inputflinger/reader/mapper/JoystickInputMapper.h
+++ b/services/inputflinger/reader/mapper/JoystickInputMapper.h
@@ -26,7 +26,7 @@
     explicit JoystickInputMapper(InputDeviceContext& deviceContext);
     virtual ~JoystickInputMapper();
 
-    virtual uint32_t getSources() override;
+    virtual uint32_t getSources() const override;
     virtual void populateDeviceInfo(InputDeviceInfo* deviceInfo) override;
     virtual void dump(std::string& dump) override;
     virtual void configure(nsecs_t when, const InputReaderConfiguration* config,
diff --git a/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp b/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp
index 104d087..1d63c0e 100644
--- a/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp
@@ -95,7 +95,7 @@
 
 KeyboardInputMapper::~KeyboardInputMapper() {}
 
-uint32_t KeyboardInputMapper::getSources() {
+uint32_t KeyboardInputMapper::getSources() const {
     return mSource;
 }
 
@@ -354,7 +354,7 @@
                        getDisplayId(), policyFlags,
                        down ? AKEY_EVENT_ACTION_DOWN : AKEY_EVENT_ACTION_UP,
                        AKEY_EVENT_FLAG_FROM_SYSTEM, keyCode, scanCode, keyMetaState, downTime);
-    getListener()->notifyKey(&args);
+    getListener().notifyKey(&args);
 }
 
 ssize_t KeyboardInputMapper::findKeyDown(int32_t scanCode) {
@@ -375,6 +375,10 @@
     return getDeviceContext().getScanCodeState(scanCode);
 }
 
+int32_t KeyboardInputMapper::getKeyCodeForKeyLocation(int32_t locationKeyCode) const {
+    return getDeviceContext().getKeyCodeForKeyLocation(locationKeyCode);
+}
+
 bool KeyboardInputMapper::markSupportedKeyCodes(uint32_t sourceMask, size_t numCodes,
                                                 const int32_t* keyCodes, uint8_t* outFlags) {
     return getDeviceContext().markSupportedKeyCodes(numCodes, keyCodes, outFlags);
@@ -384,8 +388,13 @@
     return mMetaState;
 }
 
-void KeyboardInputMapper::updateMetaState(int32_t keyCode) {
+bool KeyboardInputMapper::updateMetaState(int32_t keyCode) {
+    if (!android::isMetaKey(keyCode) || !getDeviceContext().hasKeyCode(keyCode)) {
+        return false;
+    }
+
     updateMetaStateIfNeeded(keyCode, false);
+    return true;
 }
 
 bool KeyboardInputMapper::updateMetaStateIfNeeded(int32_t keyCode, bool down) {
diff --git a/services/inputflinger/reader/mapper/KeyboardInputMapper.h b/services/inputflinger/reader/mapper/KeyboardInputMapper.h
index ca41712..3787696 100644
--- a/services/inputflinger/reader/mapper/KeyboardInputMapper.h
+++ b/services/inputflinger/reader/mapper/KeyboardInputMapper.h
@@ -26,7 +26,7 @@
     KeyboardInputMapper(InputDeviceContext& deviceContext, uint32_t source, int32_t keyboardType);
     virtual ~KeyboardInputMapper();
 
-    virtual uint32_t getSources() override;
+    virtual uint32_t getSources() const override;
     virtual void populateDeviceInfo(InputDeviceInfo* deviceInfo) override;
     virtual void dump(std::string& dump) override;
     virtual void configure(nsecs_t when, const InputReaderConfiguration* config,
@@ -38,9 +38,10 @@
     virtual int32_t getScanCodeState(uint32_t sourceMask, int32_t scanCode) override;
     virtual bool markSupportedKeyCodes(uint32_t sourceMask, size_t numCodes,
                                        const int32_t* keyCodes, uint8_t* outFlags) override;
+    virtual int32_t getKeyCodeForKeyLocation(int32_t locationKeyCode) const override;
 
     virtual int32_t getMetaState() override;
-    virtual void updateMetaState(int32_t keyCode) override;
+    virtual bool updateMetaState(int32_t keyCode) override;
     virtual std::optional<int32_t> getAssociatedDisplayId() override;
     virtual void updateLedState(bool reset);
 
diff --git a/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp b/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp
index fab7f4c..41a8426 100644
--- a/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp
@@ -18,6 +18,8 @@
 
 #include "MultiTouchInputMapper.h"
 
+#include <android/sysprop/InputProperties.sysprop.h>
+
 namespace android {
 
 // --- Constants ---
@@ -95,13 +97,13 @@
         }
 
         if (mCurrentSlot < 0 || size_t(mCurrentSlot) >= mSlotCount) {
-#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, mSlotCount - 1);
+            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, mSlotCount - 1);
+                }
             }
-#endif
         } else {
             Slot* slot = &mSlots[mCurrentSlot];
             // If mUsingSlotsProtocol is true, it means the raw pointer has axis info of
@@ -273,19 +275,19 @@
             if (id) {
                 outState->rawPointerData.canceledIdBits.markBit(id.value());
             }
-#if DEBUG_POINTERS
-            ALOGI("Stop processing slot %zu for it received a palm event from device %s", inIndex,
-                  getDeviceName().c_str());
-#endif
+            if (DEBUG_POINTERS) {
+                ALOGI("Stop processing slot %zu for it received a palm event from device %s",
+                      inIndex, getDeviceName().c_str());
+            }
             continue;
         }
 
         if (outCount >= MAX_POINTERS) {
-#if DEBUG_POINTERS
-            ALOGD("MultiTouch device %s emitted more than maximum of %d pointers; "
-                  "ignoring the rest.",
-                  getDeviceName().c_str(), MAX_POINTERS);
-#endif
+            if (DEBUG_POINTERS) {
+                ALOGD("MultiTouch device %s emitted more than maximum of %zu pointers; "
+                      "ignoring the rest.",
+                      getDeviceName().c_str(), MAX_POINTERS);
+            }
             break; // too many fingers!
         }
 
@@ -309,6 +311,10 @@
                 outPointer.toolType = AMOTION_EVENT_TOOL_TYPE_FINGER;
             }
         }
+        if (shouldSimulateStylusWithTouch() &&
+            outPointer.toolType == AMOTION_EVENT_TOOL_TYPE_FINGER) {
+            outPointer.toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS;
+        }
 
         bool isHovering = mTouchButtonAccumulator.getToolType() != AMOTION_EVENT_TOOL_TYPE_MOUSE &&
                 (mTouchButtonAccumulator.isHovering() ||
@@ -385,7 +391,15 @@
 }
 
 bool MultiTouchInputMapper::hasStylus() const {
-    return mMultiTouchMotionAccumulator.hasStylus() || mTouchButtonAccumulator.hasStylus();
+    return mMultiTouchMotionAccumulator.hasStylus() || mTouchButtonAccumulator.hasStylus() ||
+            shouldSimulateStylusWithTouch();
+}
+
+bool MultiTouchInputMapper::shouldSimulateStylusWithTouch() const {
+    static const bool SIMULATE_STYLUS_WITH_TOUCH =
+            sysprop::InputProperties::simulate_stylus_with_touch().value_or(false);
+    return SIMULATE_STYLUS_WITH_TOUCH &&
+            mParameters.deviceType == Parameters::DeviceType::TOUCH_SCREEN;
 }
 
 } // namespace android
diff --git a/services/inputflinger/reader/mapper/MultiTouchInputMapper.h b/services/inputflinger/reader/mapper/MultiTouchInputMapper.h
index 225ad49..b7c3457 100644
--- a/services/inputflinger/reader/mapper/MultiTouchInputMapper.h
+++ b/services/inputflinger/reader/mapper/MultiTouchInputMapper.h
@@ -104,6 +104,14 @@
     bool hasStylus() const override;
 
 private:
+    // simulate_stylus_with_touch is a debug mode that converts all finger pointers reported by this
+    // mapper's touchscreen into stylus pointers, and adds SOURCE_STYLUS to the input device.
+    // It is used to simulate stylus events for debugging and testing on a device that does not
+    // support styluses. It can be enabled using
+    // "adb shell setprop persist.debug.input.simulate_stylus_with_touch true",
+    // and requires a reboot to take effect.
+    inline bool shouldSimulateStylusWithTouch() const;
+
     // If the slot is in use, return the bit id. Return std::nullopt otherwise.
     std::optional<int32_t> getActiveBitId(const MultiTouchMotionAccumulator::Slot& inSlot);
     MultiTouchMotionAccumulator mMultiTouchMotionAccumulator;
diff --git a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp
index e9d0189..eca25f6 100644
--- a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp
@@ -31,7 +31,7 @@
 
 RotaryEncoderInputMapper::~RotaryEncoderInputMapper() {}
 
-uint32_t RotaryEncoderInputMapper::getSources() {
+uint32_t RotaryEncoderInputMapper::getSources() const {
     return mSource;
 }
 
@@ -127,7 +127,7 @@
                                     AMOTION_EVENT_EDGE_FLAG_NONE, 1, &pointerProperties,
                                     &pointerCoords, 0, 0, AMOTION_EVENT_INVALID_CURSOR_POSITION,
                                     AMOTION_EVENT_INVALID_CURSOR_POSITION, 0, /* videoFrames */ {});
-        getListener()->notifyMotion(&scrollArgs);
+        getListener().notifyMotion(&scrollArgs);
     }
 
     mRotaryEncoderScrollAccumulator.finishSync();
diff --git a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.h b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.h
index e0c9404..1859355 100644
--- a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.h
+++ b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.h
@@ -27,7 +27,7 @@
     explicit RotaryEncoderInputMapper(InputDeviceContext& deviceContext);
     virtual ~RotaryEncoderInputMapper();
 
-    virtual uint32_t getSources() override;
+    virtual uint32_t getSources() const override;
     virtual void populateDeviceInfo(InputDeviceInfo* deviceInfo) override;
     virtual void dump(std::string& dump) override;
     virtual void configure(nsecs_t when, const InputReaderConfiguration* config,
diff --git a/services/inputflinger/reader/mapper/SensorInputMapper.cpp b/services/inputflinger/reader/mapper/SensorInputMapper.cpp
index a507632..b01c2bc 100644
--- a/services/inputflinger/reader/mapper/SensorInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/SensorInputMapper.cpp
@@ -16,8 +16,9 @@
 
 #include <locale>
 
-#include "../Macros.h"
+#include <ftl/enum.h>
 
+#include "../Macros.h"
 #include "SensorInputMapper.h"
 
 // Log detailed debug messages about each sensor event notification to the dispatcher.
@@ -56,7 +57,7 @@
 
 SensorInputMapper::~SensorInputMapper() {}
 
-uint32_t SensorInputMapper::getSources() {
+uint32_t SensorInputMapper::getSources() const {
     return AINPUT_SOURCE_SENSOR;
 }
 
@@ -93,7 +94,7 @@
     dump += StringPrintf(INDENT3 " mHasHardwareTimestamp %d\n", mHasHardwareTimestamp);
     dump += INDENT3 "Sensors:\n";
     for (const auto& [sensorType, sensor] : mSensors) {
-        dump += StringPrintf(INDENT4 "%s\n", NamedEnum::string(sensorType).c_str());
+        dump += StringPrintf(INDENT4 "%s\n", ftl::enum_string(sensorType).c_str());
         dump += StringPrintf(INDENT5 "enabled: %d\n", sensor.enabled);
         dump += StringPrintf(INDENT5 "samplingPeriod: %lld\n", sensor.samplingPeriod.count());
         dump += StringPrintf(INDENT5 "maxBatchReportLatency: %lld\n",
@@ -208,10 +209,10 @@
                                      axis.max /* maxRange */, axis.scale /* resolution */,
                                      0.0f /* power */, 0 /* minDelay */,
                                      0 /* fifoReservedEventCount */, 0 /* fifoMaxEventCount */,
-                                     NamedEnum::string(sensorType), 0 /* maxDelay */, 0 /* flags */,
+                                     ftl::enum_string(sensorType), 0 /* maxDelay */, 0 /* flags */,
                                      getDeviceId());
 
-    std::string prefix = "sensor." + NamedEnum::string(sensorType);
+    std::string prefix = "sensor." + ftl::enum_string(sensorType);
     transform(prefix.begin(), prefix.end(), prefix.begin(), ::tolower);
 
     int32_t reportingMode = 0;
@@ -335,7 +336,7 @@
                                      std::chrono::microseconds maxBatchReportLatency) {
     if (DEBUG_SENSOR_EVENT_DETAILS) {
         ALOGD("Enable Sensor %s samplingPeriod %lld maxBatchReportLatency %lld",
-              NamedEnum::string(sensorType).c_str(), samplingPeriod.count(),
+              ftl::enum_string(sensorType).c_str(), samplingPeriod.count(),
               maxBatchReportLatency.count());
     }
 
@@ -359,7 +360,7 @@
 
 void SensorInputMapper::disableSensor(InputDeviceSensorType sensorType) {
     if (DEBUG_SENSOR_EVENT_DETAILS) {
-        ALOGD("Disable Sensor %s", NamedEnum::string(sensorType).c_str());
+        ALOGD("Disable Sensor %s", ftl::enum_string(sensorType).c_str());
     }
 
     if (!setSensorEnabled(sensorType, false /* enabled */)) {
@@ -393,13 +394,12 @@
         nsecs_t timestamp = mHasHardwareTimestamp ? mHardwareTimestamp : when;
         if (DEBUG_SENSOR_EVENT_DETAILS) {
             ALOGD("Sensor %s timestamp %" PRIu64 " values [%f %f %f]",
-                  NamedEnum::string(sensorType).c_str(), timestamp, values[0], values[1],
-                  values[2]);
+                  ftl::enum_string(sensorType).c_str(), timestamp, values[0], values[1], values[2]);
         }
         if (sensor.lastSampleTimeNs.has_value() &&
             timestamp - sensor.lastSampleTimeNs.value() < sensor.samplingPeriod.count()) {
             if (DEBUG_SENSOR_EVENT_DETAILS) {
-                ALOGD("Sensor %s Skip a sample.", NamedEnum::string(sensorType).c_str());
+                ALOGD("Sensor %s Skip a sample.", ftl::enum_string(sensorType).c_str());
             }
         } else {
             // Convert to Android unit
@@ -411,7 +411,7 @@
                                           sensor.sensorInfo.accuracy /* accuracyChanged */,
                                   timestamp /* hwTimestamp */, values);
 
-            getListener()->notifySensor(&args);
+            getListener().notifySensor(&args);
             sensor.lastSampleTimeNs = timestamp;
             sensor.accuracy = sensor.sensorInfo.accuracy;
         }
diff --git a/services/inputflinger/reader/mapper/SensorInputMapper.h b/services/inputflinger/reader/mapper/SensorInputMapper.h
index 1797fe3..27a6177 100644
--- a/services/inputflinger/reader/mapper/SensorInputMapper.h
+++ b/services/inputflinger/reader/mapper/SensorInputMapper.h
@@ -28,7 +28,7 @@
     explicit SensorInputMapper(InputDeviceContext& deviceContext);
     ~SensorInputMapper() override;
 
-    uint32_t getSources() override;
+    uint32_t getSources() const override;
     void populateDeviceInfo(InputDeviceInfo* deviceInfo) override;
     void dump(std::string& dump) override;
     void configure(nsecs_t when, const InputReaderConfiguration* config, uint32_t changes) override;
diff --git a/services/inputflinger/reader/mapper/SwitchInputMapper.cpp b/services/inputflinger/reader/mapper/SwitchInputMapper.cpp
index 4f73681..ebb5de6 100644
--- a/services/inputflinger/reader/mapper/SwitchInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/SwitchInputMapper.cpp
@@ -25,7 +25,7 @@
 
 SwitchInputMapper::~SwitchInputMapper() {}
 
-uint32_t SwitchInputMapper::getSources() {
+uint32_t SwitchInputMapper::getSources() const {
     return AINPUT_SOURCE_SWITCH;
 }
 
@@ -58,7 +58,7 @@
         uint32_t updatedSwitchValues = mSwitchValues & mUpdatedSwitchMask;
         NotifySwitchArgs args(getContext()->getNextId(), when, 0 /*policyFlags*/,
                               updatedSwitchValues, mUpdatedSwitchMask);
-        getListener()->notifySwitch(&args);
+        getListener().notifySwitch(&args);
 
         mUpdatedSwitchMask = 0;
     }
diff --git a/services/inputflinger/reader/mapper/SwitchInputMapper.h b/services/inputflinger/reader/mapper/SwitchInputMapper.h
index 4d74163..64b9aa2 100644
--- a/services/inputflinger/reader/mapper/SwitchInputMapper.h
+++ b/services/inputflinger/reader/mapper/SwitchInputMapper.h
@@ -26,7 +26,7 @@
     explicit SwitchInputMapper(InputDeviceContext& deviceContext);
     virtual ~SwitchInputMapper();
 
-    virtual uint32_t getSources() override;
+    virtual uint32_t getSources() const override;
     virtual void process(const RawEvent* rawEvent) override;
 
     virtual int32_t getSwitchState(uint32_t sourceMask, int32_t switchCode) override;
diff --git a/services/inputflinger/reader/mapper/TouchCursorInputMapperCommon.h b/services/inputflinger/reader/mapper/TouchCursorInputMapperCommon.h
index 7347b2c..31a3d2e 100644
--- a/services/inputflinger/reader/mapper/TouchCursorInputMapperCommon.h
+++ b/services/inputflinger/reader/mapper/TouchCursorInputMapperCommon.h
@@ -17,7 +17,6 @@
 #ifndef _UI_INPUTREADER_TOUCH_CURSOR_INPUT_MAPPER_COMMON_H
 #define _UI_INPUTREADER_TOUCH_CURSOR_INPUT_MAPPER_COMMON_H
 
-#include <InputFlingerProperties.sysprop.h>
 #include <input/DisplayViewport.h>
 #include <stdint.h>
 
@@ -29,13 +28,6 @@
 
 // --- Static Definitions ---
 
-// When per-window input rotation is enabled, display transformations such as rotation and
-// projection are part of the input window's transform. This means InputReader should work in the
-// un-rotated coordinate space.
-static bool isPerWindowInputRotationEnabled() {
-    return sysprop::InputFlingerProperties::per_window_input_rotation().value_or(false);
-}
-
 static int32_t getInverseRotation(int32_t orientation) {
     switch (orientation) {
         case DISPLAY_ORIENTATION_90:
@@ -72,26 +64,6 @@
     }
 }
 
-// Rotates the given point (x, y) by the supplied orientation. The width and height are the
-// dimensions of the surface prior to this rotation being applied.
-static void rotatePoint(int32_t orientation, float& x, float& y, int32_t width, int32_t height) {
-    rotateDelta(orientation, &x, &y);
-    switch (orientation) {
-        case DISPLAY_ORIENTATION_90:
-            y += width;
-            break;
-        case DISPLAY_ORIENTATION_180:
-            x += width;
-            y += height;
-            break;
-        case DISPLAY_ORIENTATION_270:
-            x += height;
-            break;
-        default:
-            break;
-    }
-}
-
 // Returns true if the pointer should be reported as being down given the specified
 // button states.  This determines whether the event is reported as a touch event.
 static bool isPointerDown(int32_t buttonState) {
@@ -110,7 +82,7 @@
          !(currentButtonState & buttonState))) {
         NotifyKeyArgs args(context->getNextId(), when, readTime, deviceId, source, displayId,
                            policyFlags, action, 0, keyCode, 0, context->getGlobalMetaState(), when);
-        context->getListener()->notifyKey(&args);
+        context->getListener().notifyKey(&args);
     }
 }
 
diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
index ac5f6b6..c1934ff 100644
--- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
@@ -18,9 +18,10 @@
 #include "../Macros.h"
 // clang-format on
 
-#include <ftl/NamedEnum.h>
 #include "TouchInputMapper.h"
 
+#include <ftl/enum.h>
+
 #include "CursorButtonAccumulator.h"
 #include "CursorScrollAccumulator.h"
 #include "TouchButtonAccumulator.h"
@@ -167,21 +168,17 @@
       : InputMapper(deviceContext),
         mSource(0),
         mDeviceMode(DeviceMode::DISABLED),
-        mRawSurfaceWidth(-1),
-        mRawSurfaceHeight(-1),
-        mSurfaceLeft(0),
-        mSurfaceTop(0),
-        mSurfaceRight(0),
-        mSurfaceBottom(0),
+        mDisplayWidth(-1),
+        mDisplayHeight(-1),
         mPhysicalWidth(-1),
         mPhysicalHeight(-1),
         mPhysicalLeft(0),
         mPhysicalTop(0),
-        mSurfaceOrientation(DISPLAY_ORIENTATION_0) {}
+        mInputDeviceOrientation(DISPLAY_ORIENTATION_0) {}
 
 TouchInputMapper::~TouchInputMapper() {}
 
-uint32_t TouchInputMapper::getSources() {
+uint32_t TouchInputMapper::getSources() const {
     return mSource;
 }
 
@@ -259,17 +256,15 @@
 
 void TouchInputMapper::dump(std::string& dump) {
     dump += StringPrintf(INDENT2 "Touch Input Mapper (mode - %s):\n",
-                         NamedEnum::string(mDeviceMode).c_str());
+                         ftl::enum_string(mDeviceMode).c_str());
     dumpParameters(dump);
     dumpVirtualKeys(dump);
     dumpRawPointerAxes(dump);
     dumpCalibration(dump);
     dumpAffineTransformation(dump);
-    dumpSurface(dump);
+    dumpDisplay(dump);
 
     dump += StringPrintf(INDENT3 "Translation and Scaling Factors:\n");
-    dump += StringPrintf(INDENT4 "XTranslate: %0.3f\n", mXTranslate);
-    dump += StringPrintf(INDENT4 "YTranslate: %0.3f\n", mYTranslate);
     dump += StringPrintf(INDENT4 "XScale: %0.3f\n", mXScale);
     dump += StringPrintf(INDENT4 "YScale: %0.3f\n", mYScale);
     dump += StringPrintf(INDENT4 "XPrecision: %0.3f\n", mXPrecision);
@@ -389,16 +384,16 @@
           InputReaderConfiguration::CHANGE_POINTER_GESTURE_ENABLEMENT |
           InputReaderConfiguration::CHANGE_SHOW_TOUCHES |
           InputReaderConfiguration::CHANGE_EXTERNAL_STYLUS_PRESENCE))) {
-        // Configure device sources, surface dimensions, orientation and
+        // Configure device sources, display dimensions, orientation and
         // scaling factors.
-        configureSurface(when, &resetNeeded);
+        configureInputDevice(when, &resetNeeded);
     }
 
     if (changes && resetNeeded) {
         // 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.
         NotifyDeviceResetArgs args(getContext()->getNextId(), when, getDeviceId());
-        getListener()->notifyDeviceReset(&args);
+        getListener().notifyDeviceReset(&args);
     }
 }
 
@@ -515,9 +510,9 @@
 void TouchInputMapper::dumpParameters(std::string& dump) {
     dump += INDENT3 "Parameters:\n";
 
-    dump += INDENT4 "GestureMode: " + NamedEnum::string(mParameters.gestureMode) + "\n";
+    dump += INDENT4 "GestureMode: " + ftl::enum_string(mParameters.gestureMode) + "\n";
 
-    dump += INDENT4 "DeviceType: " + NamedEnum::string(mParameters.deviceType) + "\n";
+    dump += INDENT4 "DeviceType: " + ftl::enum_string(mParameters.deviceType) + "\n";
 
     dump += StringPrintf(INDENT4 "AssociatedDisplay: hasAssociatedDisplay=%s, isExternal=%s, "
                                  "displayId='%s'\n",
@@ -525,7 +520,7 @@
                          toString(mParameters.associatedDisplayIsExternal),
                          mParameters.uniqueDisplayId.c_str());
     dump += StringPrintf(INDENT4 "OrientationAware: %s\n", toString(mParameters.orientationAware));
-    dump += INDENT4 "Orientation: " + NamedEnum::string(mParameters.orientation) + "\n";
+    dump += INDENT4 "Orientation: " + ftl::enum_string(mParameters.orientation) + "\n";
 }
 
 void TouchInputMapper::configureRawPointerAxes() {
@@ -555,18 +550,21 @@
 
 /**
  * Determine which DisplayViewport to use.
- * 1. If display port is specified, return the matching viewport. If matching viewport not
- * found, then return.
+ * 1. If a device has associated display, get the matching viewport.
  * 2. Always use the suggested viewport from WindowManagerService for pointers.
- * 3. If a device has associated display, get the matching viewport by either unique id or by
- * the display type (internal or external).
+ * 3. Get the matching viewport by either unique id in idc file or by the display type
+ * (internal or external).
  * 4. Otherwise, use a non-display viewport.
  */
 std::optional<DisplayViewport> TouchInputMapper::findViewport() {
     if (mParameters.hasAssociatedDisplay && mDeviceMode != DeviceMode::UNSCALED) {
-        const std::optional<uint8_t> displayPort = getDeviceContext().getAssociatedDisplayPort();
-        if (displayPort) {
-            // Find the viewport that contains the same port
+        if (getDeviceContext().getAssociatedViewport()) {
+            return getDeviceContext().getAssociatedViewport();
+        }
+
+        const std::optional<std::string> associatedDisplayUniqueId =
+                getDeviceContext().getAssociatedDisplayUniqueId();
+        if (associatedDisplayUniqueId) {
             return getDeviceContext().getAssociatedViewport();
         }
 
@@ -614,7 +612,271 @@
     return std::make_optional(newViewport);
 }
 
-void TouchInputMapper::configureSurface(nsecs_t when, bool* outResetNeeded) {
+int32_t TouchInputMapper::clampResolution(const char* axisName, int32_t resolution) const {
+    if (resolution < 0) {
+        ALOGE("Invalid %s resolution %" PRId32 " for device %s", axisName, resolution,
+              getDeviceName().c_str());
+        return 0;
+    }
+    return resolution;
+}
+
+void TouchInputMapper::initializeSizeRanges() {
+    if (mCalibration.sizeCalibration == Calibration::SizeCalibration::NONE) {
+        mSizeScale = 0.0f;
+        return;
+    }
+
+    // Size of diagonal axis.
+    const float diagonalSize = hypotf(mDisplayWidth, mDisplayHeight);
+
+    // Size factors.
+    if (mRawPointerAxes.touchMajor.valid && mRawPointerAxes.touchMajor.maxValue != 0) {
+        mSizeScale = 1.0f / mRawPointerAxes.touchMajor.maxValue;
+    } else if (mRawPointerAxes.toolMajor.valid && mRawPointerAxes.toolMajor.maxValue != 0) {
+        mSizeScale = 1.0f / mRawPointerAxes.toolMajor.maxValue;
+    } else {
+        mSizeScale = 0.0f;
+    }
+
+    mOrientedRanges.haveTouchSize = true;
+    mOrientedRanges.haveToolSize = true;
+    mOrientedRanges.haveSize = true;
+
+    mOrientedRanges.touchMajor.axis = AMOTION_EVENT_AXIS_TOUCH_MAJOR;
+    mOrientedRanges.touchMajor.source = mSource;
+    mOrientedRanges.touchMajor.min = 0;
+    mOrientedRanges.touchMajor.max = diagonalSize;
+    mOrientedRanges.touchMajor.flat = 0;
+    mOrientedRanges.touchMajor.fuzz = 0;
+    mOrientedRanges.touchMajor.resolution = 0;
+    if (mRawPointerAxes.touchMajor.valid) {
+        mRawPointerAxes.touchMajor.resolution =
+                clampResolution("touchMajor", mRawPointerAxes.touchMajor.resolution);
+        mOrientedRanges.touchMajor.resolution = mRawPointerAxes.touchMajor.resolution;
+    }
+
+    mOrientedRanges.touchMinor = mOrientedRanges.touchMajor;
+    mOrientedRanges.touchMinor.axis = AMOTION_EVENT_AXIS_TOUCH_MINOR;
+    if (mRawPointerAxes.touchMinor.valid) {
+        mRawPointerAxes.touchMinor.resolution =
+                clampResolution("touchMinor", mRawPointerAxes.touchMinor.resolution);
+        mOrientedRanges.touchMinor.resolution = mRawPointerAxes.touchMinor.resolution;
+    }
+
+    mOrientedRanges.toolMajor.axis = AMOTION_EVENT_AXIS_TOOL_MAJOR;
+    mOrientedRanges.toolMajor.source = mSource;
+    mOrientedRanges.toolMajor.min = 0;
+    mOrientedRanges.toolMajor.max = diagonalSize;
+    mOrientedRanges.toolMajor.flat = 0;
+    mOrientedRanges.toolMajor.fuzz = 0;
+    mOrientedRanges.toolMajor.resolution = 0;
+    if (mRawPointerAxes.toolMajor.valid) {
+        mRawPointerAxes.toolMajor.resolution =
+                clampResolution("toolMajor", mRawPointerAxes.toolMajor.resolution);
+        mOrientedRanges.toolMajor.resolution = mRawPointerAxes.toolMajor.resolution;
+    }
+
+    mOrientedRanges.toolMinor = mOrientedRanges.toolMajor;
+    mOrientedRanges.toolMinor.axis = AMOTION_EVENT_AXIS_TOOL_MINOR;
+    if (mRawPointerAxes.toolMinor.valid) {
+        mRawPointerAxes.toolMinor.resolution =
+                clampResolution("toolMinor", mRawPointerAxes.toolMinor.resolution);
+        mOrientedRanges.toolMinor.resolution = mRawPointerAxes.toolMinor.resolution;
+    }
+
+    if (mCalibration.sizeCalibration == Calibration::SizeCalibration::GEOMETRIC) {
+        mOrientedRanges.touchMajor.resolution *= mGeometricScale;
+        mOrientedRanges.touchMinor.resolution *= mGeometricScale;
+        mOrientedRanges.toolMajor.resolution *= mGeometricScale;
+        mOrientedRanges.toolMinor.resolution *= mGeometricScale;
+    } else {
+        // Support for other calibrations can be added here.
+        ALOGW("%s calibration is not supported for size ranges at the moment. "
+              "Using raw resolution instead",
+              ftl::enum_string(mCalibration.sizeCalibration).c_str());
+    }
+
+    mOrientedRanges.size.axis = AMOTION_EVENT_AXIS_SIZE;
+    mOrientedRanges.size.source = mSource;
+    mOrientedRanges.size.min = 0;
+    mOrientedRanges.size.max = 1.0;
+    mOrientedRanges.size.flat = 0;
+    mOrientedRanges.size.fuzz = 0;
+    mOrientedRanges.size.resolution = 0;
+}
+
+void TouchInputMapper::initializeOrientedRanges() {
+    // Configure X and Y factors.
+    mXScale = float(mDisplayWidth) / mRawPointerAxes.getRawWidth();
+    mYScale = float(mDisplayHeight) / mRawPointerAxes.getRawHeight();
+    mXPrecision = 1.0f / mXScale;
+    mYPrecision = 1.0f / mYScale;
+
+    mOrientedRanges.x.axis = AMOTION_EVENT_AXIS_X;
+    mOrientedRanges.x.source = mSource;
+    mOrientedRanges.y.axis = AMOTION_EVENT_AXIS_Y;
+    mOrientedRanges.y.source = mSource;
+
+    // Scale factor for terms that are not oriented in a particular axis.
+    // If the pixels are square then xScale == yScale otherwise we fake it
+    // by choosing an average.
+    mGeometricScale = avg(mXScale, mYScale);
+
+    initializeSizeRanges();
+
+    // Pressure factors.
+    mPressureScale = 0;
+    float pressureMax = 1.0;
+    if (mCalibration.pressureCalibration == Calibration::PressureCalibration::PHYSICAL ||
+        mCalibration.pressureCalibration == Calibration::PressureCalibration::AMPLITUDE) {
+        if (mCalibration.havePressureScale) {
+            mPressureScale = mCalibration.pressureScale;
+            pressureMax = mPressureScale * mRawPointerAxes.pressure.maxValue;
+        } else if (mRawPointerAxes.pressure.valid && mRawPointerAxes.pressure.maxValue != 0) {
+            mPressureScale = 1.0f / mRawPointerAxes.pressure.maxValue;
+        }
+    }
+
+    mOrientedRanges.pressure.axis = AMOTION_EVENT_AXIS_PRESSURE;
+    mOrientedRanges.pressure.source = mSource;
+    mOrientedRanges.pressure.min = 0;
+    mOrientedRanges.pressure.max = pressureMax;
+    mOrientedRanges.pressure.flat = 0;
+    mOrientedRanges.pressure.fuzz = 0;
+    mOrientedRanges.pressure.resolution = 0;
+
+    // Tilt
+    mTiltXCenter = 0;
+    mTiltXScale = 0;
+    mTiltYCenter = 0;
+    mTiltYScale = 0;
+    mHaveTilt = mRawPointerAxes.tiltX.valid && mRawPointerAxes.tiltY.valid;
+    if (mHaveTilt) {
+        mTiltXCenter = avg(mRawPointerAxes.tiltX.minValue, mRawPointerAxes.tiltX.maxValue);
+        mTiltYCenter = avg(mRawPointerAxes.tiltY.minValue, mRawPointerAxes.tiltY.maxValue);
+        mTiltXScale = M_PI / 180;
+        mTiltYScale = M_PI / 180;
+
+        if (mRawPointerAxes.tiltX.resolution) {
+            mTiltXScale = 1.0 / mRawPointerAxes.tiltX.resolution;
+        }
+        if (mRawPointerAxes.tiltY.resolution) {
+            mTiltYScale = 1.0 / mRawPointerAxes.tiltY.resolution;
+        }
+
+        mOrientedRanges.haveTilt = true;
+
+        mOrientedRanges.tilt.axis = AMOTION_EVENT_AXIS_TILT;
+        mOrientedRanges.tilt.source = mSource;
+        mOrientedRanges.tilt.min = 0;
+        mOrientedRanges.tilt.max = M_PI_2;
+        mOrientedRanges.tilt.flat = 0;
+        mOrientedRanges.tilt.fuzz = 0;
+        mOrientedRanges.tilt.resolution = 0;
+    }
+
+    // Orientation
+    mOrientationScale = 0;
+    if (mHaveTilt) {
+        mOrientedRanges.haveOrientation = true;
+
+        mOrientedRanges.orientation.axis = AMOTION_EVENT_AXIS_ORIENTATION;
+        mOrientedRanges.orientation.source = mSource;
+        mOrientedRanges.orientation.min = -M_PI;
+        mOrientedRanges.orientation.max = M_PI;
+        mOrientedRanges.orientation.flat = 0;
+        mOrientedRanges.orientation.fuzz = 0;
+        mOrientedRanges.orientation.resolution = 0;
+    } else if (mCalibration.orientationCalibration != Calibration::OrientationCalibration::NONE) {
+        if (mCalibration.orientationCalibration ==
+            Calibration::OrientationCalibration::INTERPOLATED) {
+            if (mRawPointerAxes.orientation.valid) {
+                if (mRawPointerAxes.orientation.maxValue > 0) {
+                    mOrientationScale = M_PI_2 / mRawPointerAxes.orientation.maxValue;
+                } else if (mRawPointerAxes.orientation.minValue < 0) {
+                    mOrientationScale = -M_PI_2 / mRawPointerAxes.orientation.minValue;
+                } else {
+                    mOrientationScale = 0;
+                }
+            }
+        }
+
+        mOrientedRanges.haveOrientation = true;
+
+        mOrientedRanges.orientation.axis = AMOTION_EVENT_AXIS_ORIENTATION;
+        mOrientedRanges.orientation.source = mSource;
+        mOrientedRanges.orientation.min = -M_PI_2;
+        mOrientedRanges.orientation.max = M_PI_2;
+        mOrientedRanges.orientation.flat = 0;
+        mOrientedRanges.orientation.fuzz = 0;
+        mOrientedRanges.orientation.resolution = 0;
+    }
+
+    // Distance
+    mDistanceScale = 0;
+    if (mCalibration.distanceCalibration != Calibration::DistanceCalibration::NONE) {
+        if (mCalibration.distanceCalibration == Calibration::DistanceCalibration::SCALED) {
+            if (mCalibration.haveDistanceScale) {
+                mDistanceScale = mCalibration.distanceScale;
+            } else {
+                mDistanceScale = 1.0f;
+            }
+        }
+
+        mOrientedRanges.haveDistance = true;
+
+        mOrientedRanges.distance.axis = AMOTION_EVENT_AXIS_DISTANCE;
+        mOrientedRanges.distance.source = mSource;
+        mOrientedRanges.distance.min = mRawPointerAxes.distance.minValue * mDistanceScale;
+        mOrientedRanges.distance.max = mRawPointerAxes.distance.maxValue * mDistanceScale;
+        mOrientedRanges.distance.flat = 0;
+        mOrientedRanges.distance.fuzz = mRawPointerAxes.distance.fuzz * mDistanceScale;
+        mOrientedRanges.distance.resolution = 0;
+    }
+
+    // Compute oriented precision, scales and ranges.
+    // 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:
+            mOrientedXPrecision = mYPrecision;
+            mOrientedYPrecision = mXPrecision;
+
+            mOrientedRanges.x.min = 0;
+            mOrientedRanges.x.max = mDisplayHeight - 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.flat = 0;
+            mOrientedRanges.y.fuzz = 0;
+            mOrientedRanges.y.resolution = mRawPointerAxes.x.resolution * mXScale;
+            break;
+
+        default:
+            mOrientedXPrecision = mXPrecision;
+            mOrientedYPrecision = mYPrecision;
+
+            mOrientedRanges.x.min = 0;
+            mOrientedRanges.x.max = mDisplayWidth - 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.flat = 0;
+            mOrientedRanges.y.fuzz = 0;
+            mOrientedRanges.y.resolution = mRawPointerAxes.y.resolution * mYScale;
+            break;
+    }
+}
+
+void TouchInputMapper::configureInputDevice(nsecs_t when, bool* outResetNeeded) {
     DeviceMode oldDeviceMode = mDeviceMode;
 
     resolveExternalStylusPresence();
@@ -672,31 +934,28 @@
     }
 
     // Raw width and height in the natural orientation.
-    int32_t rawWidth = mRawPointerAxes.getRawWidth();
-    int32_t rawHeight = mRawPointerAxes.getRawHeight();
+    const int32_t rawWidth = mRawPointerAxes.getRawWidth();
+    const int32_t rawHeight = mRawPointerAxes.getRawHeight();
 
-    bool viewportChanged = mViewport != *newViewport;
+    const bool viewportChanged = mViewport != *newViewport;
     bool skipViewportUpdate = false;
     if (viewportChanged) {
-        bool viewportOrientationChanged = mViewport.orientation != newViewport->orientation;
+        const bool viewportOrientationChanged = mViewport.orientation != newViewport->orientation;
         mViewport = *newViewport;
 
         if (mDeviceMode == DeviceMode::DIRECT || mDeviceMode == DeviceMode::POINTER) {
-            // Convert rotated viewport to natural surface coordinates.
-            int32_t naturalLogicalWidth, naturalLogicalHeight;
+            // Convert rotated viewport to the natural orientation.
             int32_t naturalPhysicalWidth, naturalPhysicalHeight;
             int32_t naturalPhysicalLeft, naturalPhysicalTop;
             int32_t naturalDeviceWidth, naturalDeviceHeight;
 
-            // Apply the inverse of the input device orientation so that the surface is configured
-            // in the same orientation as the device. The input device orientation will be
-            // re-applied to mSurfaceOrientation.
-            const int32_t naturalSurfaceOrientation =
+            // 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 (naturalSurfaceOrientation) {
+            switch (naturalDeviceOrientation) {
                 case DISPLAY_ORIENTATION_90:
-                    naturalLogicalWidth = mViewport.logicalBottom - mViewport.logicalTop;
-                    naturalLogicalHeight = mViewport.logicalRight - mViewport.logicalLeft;
                     naturalPhysicalWidth = mViewport.physicalBottom - mViewport.physicalTop;
                     naturalPhysicalHeight = mViewport.physicalRight - mViewport.physicalLeft;
                     naturalPhysicalLeft = mViewport.deviceHeight - mViewport.physicalBottom;
@@ -705,8 +964,6 @@
                     naturalDeviceHeight = mViewport.deviceWidth;
                     break;
                 case DISPLAY_ORIENTATION_180:
-                    naturalLogicalWidth = mViewport.logicalRight - mViewport.logicalLeft;
-                    naturalLogicalHeight = mViewport.logicalBottom - mViewport.logicalTop;
                     naturalPhysicalWidth = mViewport.physicalRight - mViewport.physicalLeft;
                     naturalPhysicalHeight = mViewport.physicalBottom - mViewport.physicalTop;
                     naturalPhysicalLeft = mViewport.deviceWidth - mViewport.physicalRight;
@@ -715,8 +972,6 @@
                     naturalDeviceHeight = mViewport.deviceHeight;
                     break;
                 case DISPLAY_ORIENTATION_270:
-                    naturalLogicalWidth = mViewport.logicalBottom - mViewport.logicalTop;
-                    naturalLogicalHeight = mViewport.logicalRight - mViewport.logicalLeft;
                     naturalPhysicalWidth = mViewport.physicalBottom - mViewport.physicalTop;
                     naturalPhysicalHeight = mViewport.physicalRight - mViewport.physicalLeft;
                     naturalPhysicalLeft = mViewport.physicalTop;
@@ -726,8 +981,6 @@
                     break;
                 case DISPLAY_ORIENTATION_0:
                 default:
-                    naturalLogicalWidth = mViewport.logicalRight - mViewport.logicalLeft;
-                    naturalLogicalHeight = mViewport.logicalBottom - mViewport.logicalTop;
                     naturalPhysicalWidth = mViewport.physicalRight - mViewport.physicalLeft;
                     naturalPhysicalHeight = mViewport.physicalBottom - mViewport.physicalTop;
                     naturalPhysicalLeft = mViewport.physicalLeft;
@@ -748,49 +1001,36 @@
             mPhysicalLeft = naturalPhysicalLeft;
             mPhysicalTop = naturalPhysicalTop;
 
-            const int32_t oldSurfaceWidth = mRawSurfaceWidth;
-            const int32_t oldSurfaceHeight = mRawSurfaceHeight;
-            mRawSurfaceWidth = naturalLogicalWidth * naturalDeviceWidth / naturalPhysicalWidth;
-            mRawSurfaceHeight = naturalLogicalHeight * naturalDeviceHeight / naturalPhysicalHeight;
-            mSurfaceLeft = naturalPhysicalLeft * naturalLogicalWidth / naturalPhysicalWidth;
-            mSurfaceTop = naturalPhysicalTop * naturalLogicalHeight / naturalPhysicalHeight;
-            mSurfaceRight = mSurfaceLeft + naturalLogicalWidth;
-            mSurfaceBottom = mSurfaceTop + naturalLogicalHeight;
+            const int32_t oldDisplayWidth = mDisplayWidth;
+            const int32_t oldDisplayHeight = mDisplayHeight;
+            mDisplayWidth = naturalDeviceWidth;
+            mDisplayHeight = naturalDeviceHeight;
 
-            if (isPerWindowInputRotationEnabled()) {
-                // When per-window input rotation is enabled, InputReader works in the un-rotated
-                // coordinate space, so we don't need to do anything if the device is already
-                // orientation-aware. If the device is not orientation-aware, then we need to apply
-                // the inverse rotation of the display so that when the display rotation is applied
-                // later as a part of the per-window transform, we get the expected screen
-                // coordinates.
-                mSurfaceOrientation = mParameters.orientationAware
-                        ? DISPLAY_ORIENTATION_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 = mParameters.orientationAware &&
-                        mRawSurfaceWidth == oldSurfaceWidth &&
-                        mRawSurfaceHeight == oldSurfaceHeight && viewportOrientationChanged;
-            } else {
-                mSurfaceOrientation = mParameters.orientationAware ? mViewport.orientation
-                                                                   : DISPLAY_ORIENTATION_0;
-            }
+            // InputReader works in the un-rotated display coordinate space, so we don't need to do
+            // anything if the device is already orientation-aware. If the device is not
+            // orientation-aware, then we need to apply the inverse rotation of the display so that
+            // when the display rotation is applied later as a part of the per-window transform, we
+            // get the expected screen coordinates.
+            mInputDeviceOrientation = mParameters.orientationAware
+                    ? DISPLAY_ORIENTATION_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 = mParameters.orientationAware && mDisplayWidth == oldDisplayWidth &&
+                    mDisplayHeight == oldDisplayHeight && viewportOrientationChanged;
 
             // Apply the input device orientation for the device.
-            mSurfaceOrientation =
-                    (mSurfaceOrientation + static_cast<int32_t>(mParameters.orientation)) % 4;
+            mInputDeviceOrientation =
+                    (mInputDeviceOrientation + static_cast<int32_t>(mParameters.orientation)) % 4;
         } else {
             mPhysicalWidth = rawWidth;
             mPhysicalHeight = rawHeight;
             mPhysicalLeft = 0;
             mPhysicalTop = 0;
 
-            mRawSurfaceWidth = rawWidth;
-            mRawSurfaceHeight = rawHeight;
-            mSurfaceLeft = 0;
-            mSurfaceTop = 0;
-            mSurfaceOrientation = DISPLAY_ORIENTATION_0;
+            mDisplayWidth = rawWidth;
+            mDisplayHeight = rawHeight;
+            mInputDeviceOrientation = DISPLAY_ORIENTATION_0;
         }
     }
 
@@ -819,229 +1059,12 @@
     if ((viewportChanged && !skipViewportUpdate) || deviceModeChanged) {
         ALOGI("Device reconfigured: id=%d, name='%s', size %dx%d, orientation %d, mode %d, "
               "display id %d",
-              getDeviceId(), getDeviceName().c_str(), mRawSurfaceWidth, mRawSurfaceHeight,
-              mSurfaceOrientation, mDeviceMode, mViewport.displayId);
-
-        // Configure X and Y factors.
-        mXScale = float(mRawSurfaceWidth) / rawWidth;
-        mYScale = float(mRawSurfaceHeight) / rawHeight;
-        mXTranslate = -mSurfaceLeft;
-        mYTranslate = -mSurfaceTop;
-        mXPrecision = 1.0f / mXScale;
-        mYPrecision = 1.0f / mYScale;
-
-        mOrientedRanges.x.axis = AMOTION_EVENT_AXIS_X;
-        mOrientedRanges.x.source = mSource;
-        mOrientedRanges.y.axis = AMOTION_EVENT_AXIS_Y;
-        mOrientedRanges.y.source = mSource;
+              getDeviceId(), getDeviceName().c_str(), mDisplayWidth, mDisplayHeight,
+              mInputDeviceOrientation, mDeviceMode, mViewport.displayId);
 
         configureVirtualKeys();
 
-        // Scale factor for terms that are not oriented in a particular axis.
-        // If the pixels are square then xScale == yScale otherwise we fake it
-        // by choosing an average.
-        mGeometricScale = avg(mXScale, mYScale);
-
-        // Size of diagonal axis.
-        float diagonalSize = hypotf(mRawSurfaceWidth, mRawSurfaceHeight);
-
-        // Size factors.
-        if (mCalibration.sizeCalibration != Calibration::SizeCalibration::NONE) {
-            if (mRawPointerAxes.touchMajor.valid && mRawPointerAxes.touchMajor.maxValue != 0) {
-                mSizeScale = 1.0f / mRawPointerAxes.touchMajor.maxValue;
-            } else if (mRawPointerAxes.toolMajor.valid && mRawPointerAxes.toolMajor.maxValue != 0) {
-                mSizeScale = 1.0f / mRawPointerAxes.toolMajor.maxValue;
-            } else {
-                mSizeScale = 0.0f;
-            }
-
-            mOrientedRanges.haveTouchSize = true;
-            mOrientedRanges.haveToolSize = true;
-            mOrientedRanges.haveSize = true;
-
-            mOrientedRanges.touchMajor.axis = AMOTION_EVENT_AXIS_TOUCH_MAJOR;
-            mOrientedRanges.touchMajor.source = mSource;
-            mOrientedRanges.touchMajor.min = 0;
-            mOrientedRanges.touchMajor.max = diagonalSize;
-            mOrientedRanges.touchMajor.flat = 0;
-            mOrientedRanges.touchMajor.fuzz = 0;
-            mOrientedRanges.touchMajor.resolution = 0;
-
-            mOrientedRanges.touchMinor = mOrientedRanges.touchMajor;
-            mOrientedRanges.touchMinor.axis = AMOTION_EVENT_AXIS_TOUCH_MINOR;
-
-            mOrientedRanges.toolMajor.axis = AMOTION_EVENT_AXIS_TOOL_MAJOR;
-            mOrientedRanges.toolMajor.source = mSource;
-            mOrientedRanges.toolMajor.min = 0;
-            mOrientedRanges.toolMajor.max = diagonalSize;
-            mOrientedRanges.toolMajor.flat = 0;
-            mOrientedRanges.toolMajor.fuzz = 0;
-            mOrientedRanges.toolMajor.resolution = 0;
-
-            mOrientedRanges.toolMinor = mOrientedRanges.toolMajor;
-            mOrientedRanges.toolMinor.axis = AMOTION_EVENT_AXIS_TOOL_MINOR;
-
-            mOrientedRanges.size.axis = AMOTION_EVENT_AXIS_SIZE;
-            mOrientedRanges.size.source = mSource;
-            mOrientedRanges.size.min = 0;
-            mOrientedRanges.size.max = 1.0;
-            mOrientedRanges.size.flat = 0;
-            mOrientedRanges.size.fuzz = 0;
-            mOrientedRanges.size.resolution = 0;
-        } else {
-            mSizeScale = 0.0f;
-        }
-
-        // Pressure factors.
-        mPressureScale = 0;
-        float pressureMax = 1.0;
-        if (mCalibration.pressureCalibration == Calibration::PressureCalibration::PHYSICAL ||
-            mCalibration.pressureCalibration == Calibration::PressureCalibration::AMPLITUDE) {
-            if (mCalibration.havePressureScale) {
-                mPressureScale = mCalibration.pressureScale;
-                pressureMax = mPressureScale * mRawPointerAxes.pressure.maxValue;
-            } else if (mRawPointerAxes.pressure.valid && mRawPointerAxes.pressure.maxValue != 0) {
-                mPressureScale = 1.0f / mRawPointerAxes.pressure.maxValue;
-            }
-        }
-
-        mOrientedRanges.pressure.axis = AMOTION_EVENT_AXIS_PRESSURE;
-        mOrientedRanges.pressure.source = mSource;
-        mOrientedRanges.pressure.min = 0;
-        mOrientedRanges.pressure.max = pressureMax;
-        mOrientedRanges.pressure.flat = 0;
-        mOrientedRanges.pressure.fuzz = 0;
-        mOrientedRanges.pressure.resolution = 0;
-
-        // Tilt
-        mTiltXCenter = 0;
-        mTiltXScale = 0;
-        mTiltYCenter = 0;
-        mTiltYScale = 0;
-        mHaveTilt = mRawPointerAxes.tiltX.valid && mRawPointerAxes.tiltY.valid;
-        if (mHaveTilt) {
-            mTiltXCenter = avg(mRawPointerAxes.tiltX.minValue, mRawPointerAxes.tiltX.maxValue);
-            mTiltYCenter = avg(mRawPointerAxes.tiltY.minValue, mRawPointerAxes.tiltY.maxValue);
-            mTiltXScale = M_PI / 180;
-            mTiltYScale = M_PI / 180;
-
-            if (mRawPointerAxes.tiltX.resolution) {
-                mTiltXScale = 1.0 / mRawPointerAxes.tiltX.resolution;
-            }
-            if (mRawPointerAxes.tiltY.resolution) {
-                mTiltYScale = 1.0 / mRawPointerAxes.tiltY.resolution;
-            }
-
-            mOrientedRanges.haveTilt = true;
-
-            mOrientedRanges.tilt.axis = AMOTION_EVENT_AXIS_TILT;
-            mOrientedRanges.tilt.source = mSource;
-            mOrientedRanges.tilt.min = 0;
-            mOrientedRanges.tilt.max = M_PI_2;
-            mOrientedRanges.tilt.flat = 0;
-            mOrientedRanges.tilt.fuzz = 0;
-            mOrientedRanges.tilt.resolution = 0;
-        }
-
-        // Orientation
-        mOrientationScale = 0;
-        if (mHaveTilt) {
-            mOrientedRanges.haveOrientation = true;
-
-            mOrientedRanges.orientation.axis = AMOTION_EVENT_AXIS_ORIENTATION;
-            mOrientedRanges.orientation.source = mSource;
-            mOrientedRanges.orientation.min = -M_PI;
-            mOrientedRanges.orientation.max = M_PI;
-            mOrientedRanges.orientation.flat = 0;
-            mOrientedRanges.orientation.fuzz = 0;
-            mOrientedRanges.orientation.resolution = 0;
-        } else if (mCalibration.orientationCalibration !=
-                   Calibration::OrientationCalibration::NONE) {
-            if (mCalibration.orientationCalibration ==
-                Calibration::OrientationCalibration::INTERPOLATED) {
-                if (mRawPointerAxes.orientation.valid) {
-                    if (mRawPointerAxes.orientation.maxValue > 0) {
-                        mOrientationScale = M_PI_2 / mRawPointerAxes.orientation.maxValue;
-                    } else if (mRawPointerAxes.orientation.minValue < 0) {
-                        mOrientationScale = -M_PI_2 / mRawPointerAxes.orientation.minValue;
-                    } else {
-                        mOrientationScale = 0;
-                    }
-                }
-            }
-
-            mOrientedRanges.haveOrientation = true;
-
-            mOrientedRanges.orientation.axis = AMOTION_EVENT_AXIS_ORIENTATION;
-            mOrientedRanges.orientation.source = mSource;
-            mOrientedRanges.orientation.min = -M_PI_2;
-            mOrientedRanges.orientation.max = M_PI_2;
-            mOrientedRanges.orientation.flat = 0;
-            mOrientedRanges.orientation.fuzz = 0;
-            mOrientedRanges.orientation.resolution = 0;
-        }
-
-        // Distance
-        mDistanceScale = 0;
-        if (mCalibration.distanceCalibration != Calibration::DistanceCalibration::NONE) {
-            if (mCalibration.distanceCalibration == Calibration::DistanceCalibration::SCALED) {
-                if (mCalibration.haveDistanceScale) {
-                    mDistanceScale = mCalibration.distanceScale;
-                } else {
-                    mDistanceScale = 1.0f;
-                }
-            }
-
-            mOrientedRanges.haveDistance = true;
-
-            mOrientedRanges.distance.axis = AMOTION_EVENT_AXIS_DISTANCE;
-            mOrientedRanges.distance.source = mSource;
-            mOrientedRanges.distance.min = mRawPointerAxes.distance.minValue * mDistanceScale;
-            mOrientedRanges.distance.max = mRawPointerAxes.distance.maxValue * mDistanceScale;
-            mOrientedRanges.distance.flat = 0;
-            mOrientedRanges.distance.fuzz = mRawPointerAxes.distance.fuzz * mDistanceScale;
-            mOrientedRanges.distance.resolution = 0;
-        }
-
-        // Compute oriented precision, scales and ranges.
-        // Note that the maximum value reported is an inclusive maximum value so it is one
-        // unit less than the total width or height of surface.
-        switch (mSurfaceOrientation) {
-            case DISPLAY_ORIENTATION_90:
-            case DISPLAY_ORIENTATION_270:
-                mOrientedXPrecision = mYPrecision;
-                mOrientedYPrecision = mXPrecision;
-
-                mOrientedRanges.x.min = mYTranslate;
-                mOrientedRanges.x.max = mRawSurfaceHeight + mYTranslate - 1;
-                mOrientedRanges.x.flat = 0;
-                mOrientedRanges.x.fuzz = 0;
-                mOrientedRanges.x.resolution = mRawPointerAxes.y.resolution * mYScale;
-
-                mOrientedRanges.y.min = mXTranslate;
-                mOrientedRanges.y.max = mRawSurfaceWidth + mXTranslate - 1;
-                mOrientedRanges.y.flat = 0;
-                mOrientedRanges.y.fuzz = 0;
-                mOrientedRanges.y.resolution = mRawPointerAxes.x.resolution * mXScale;
-                break;
-
-            default:
-                mOrientedXPrecision = mXPrecision;
-                mOrientedYPrecision = mYPrecision;
-
-                mOrientedRanges.x.min = mXTranslate;
-                mOrientedRanges.x.max = mRawSurfaceWidth + mXTranslate - 1;
-                mOrientedRanges.x.flat = 0;
-                mOrientedRanges.x.fuzz = 0;
-                mOrientedRanges.x.resolution = mRawPointerAxes.x.resolution * mXScale;
-
-                mOrientedRanges.y.min = mYTranslate;
-                mOrientedRanges.y.max = mRawSurfaceHeight + mYTranslate - 1;
-                mOrientedRanges.y.flat = 0;
-                mOrientedRanges.y.fuzz = 0;
-                mOrientedRanges.y.resolution = mRawPointerAxes.y.resolution * mYScale;
-                break;
-        }
+        initializeOrientedRanges();
 
         // Location
         updateAffineTransformation();
@@ -1049,7 +1072,7 @@
         if (mDeviceMode == DeviceMode::POINTER) {
             // Compute pointer gesture detection parameters.
             float rawDiagonal = hypotf(rawWidth, rawHeight);
-            float displayDiagonal = hypotf(mRawSurfaceWidth, mRawSurfaceHeight);
+            float displayDiagonal = hypotf(mDisplayWidth, mDisplayHeight);
 
             // 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
@@ -1083,19 +1106,15 @@
     }
 }
 
-void TouchInputMapper::dumpSurface(std::string& dump) {
+void TouchInputMapper::dumpDisplay(std::string& dump) {
     dump += StringPrintf(INDENT3 "%s\n", mViewport.toString().c_str());
-    dump += StringPrintf(INDENT3 "RawSurfaceWidth: %dpx\n", mRawSurfaceWidth);
-    dump += StringPrintf(INDENT3 "RawSurfaceHeight: %dpx\n", mRawSurfaceHeight);
-    dump += StringPrintf(INDENT3 "SurfaceLeft: %d\n", mSurfaceLeft);
-    dump += StringPrintf(INDENT3 "SurfaceTop: %d\n", mSurfaceTop);
-    dump += StringPrintf(INDENT3 "SurfaceRight: %d\n", mSurfaceRight);
-    dump += StringPrintf(INDENT3 "SurfaceBottom: %d\n", mSurfaceBottom);
+    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 "SurfaceOrientation: %d\n", mSurfaceOrientation);
+    dump += StringPrintf(INDENT3 "InputDeviceOrientation: %d\n", mInputDeviceOrientation);
 }
 
 void TouchInputMapper::configureVirtualKeys() {
@@ -1134,16 +1153,16 @@
         int32_t halfHeight = virtualKeyDefinition.height / 2;
 
         virtualKey.hitLeft =
-                (virtualKeyDefinition.centerX - halfWidth) * touchScreenWidth / mRawSurfaceWidth +
+                (virtualKeyDefinition.centerX - halfWidth) * touchScreenWidth / mDisplayWidth +
                 touchScreenLeft;
         virtualKey.hitRight =
-                (virtualKeyDefinition.centerX + halfWidth) * touchScreenWidth / mRawSurfaceWidth +
+                (virtualKeyDefinition.centerX + halfWidth) * touchScreenWidth / mDisplayWidth +
                 touchScreenLeft;
-        virtualKey.hitTop = (virtualKeyDefinition.centerY - halfHeight) * touchScreenHeight /
-                        mRawSurfaceHeight +
+        virtualKey.hitTop =
+                (virtualKeyDefinition.centerY - halfHeight) * touchScreenHeight / mDisplayHeight +
                 touchScreenTop;
-        virtualKey.hitBottom = (virtualKeyDefinition.centerY + halfHeight) * touchScreenHeight /
-                        mRawSurfaceHeight +
+        virtualKey.hitBottom =
+                (virtualKeyDefinition.centerY + halfHeight) * touchScreenHeight / mDisplayHeight +
                 touchScreenTop;
         mVirtualKeys.push_back(virtualKey);
     }
@@ -1300,26 +1319,8 @@
 void TouchInputMapper::dumpCalibration(std::string& dump) {
     dump += INDENT3 "Calibration:\n";
 
-    // Size
-    switch (mCalibration.sizeCalibration) {
-        case Calibration::SizeCalibration::NONE:
-            dump += INDENT4 "touch.size.calibration: none\n";
-            break;
-        case Calibration::SizeCalibration::GEOMETRIC:
-            dump += INDENT4 "touch.size.calibration: geometric\n";
-            break;
-        case Calibration::SizeCalibration::DIAMETER:
-            dump += INDENT4 "touch.size.calibration: diameter\n";
-            break;
-        case Calibration::SizeCalibration::BOX:
-            dump += INDENT4 "touch.size.calibration: box\n";
-            break;
-        case Calibration::SizeCalibration::AREA:
-            dump += INDENT4 "touch.size.calibration: area\n";
-            break;
-        default:
-            ALOG_ASSERT(false);
-    }
+    dump += INDENT4 "touch.size.calibration: ";
+    dump += ftl::enum_string(mCalibration.sizeCalibration) + "\n";
 
     if (mCalibration.haveSizeScale) {
         dump += StringPrintf(INDENT4 "touch.size.scale: %0.3f\n", mCalibration.sizeScale);
@@ -1409,7 +1410,7 @@
 
 void TouchInputMapper::updateAffineTransformation() {
     mAffineTransform = getPolicy()->getTouchAffineTransformation(getDeviceContext().getDescriptor(),
-                                                                 mSurfaceOrientation);
+                                                                 mInputDeviceOrientation);
 }
 
 void TouchInputMapper::reset(nsecs_t when) {
@@ -1498,14 +1499,14 @@
         assignPointerIds(last, next);
     }
 
-#if DEBUG_RAW_EVENTS
-    ALOGD("syncTouch: pointerCount %d -> %d, touching ids 0x%08x -> 0x%08x, "
-          "hovering ids 0x%08x -> 0x%08x, canceled ids 0x%08x",
-          last.rawPointerData.pointerCount, next.rawPointerData.pointerCount,
-          last.rawPointerData.touchingIdBits.value, next.rawPointerData.touchingIdBits.value,
-          last.rawPointerData.hoveringIdBits.value, next.rawPointerData.hoveringIdBits.value,
-          next.rawPointerData.canceledIdBits.value);
-#endif
+    if (DEBUG_RAW_EVENTS) {
+        ALOGD("syncTouch: pointerCount %d -> %d, touching ids 0x%08x -> 0x%08x, "
+              "hovering ids 0x%08x -> 0x%08x, canceled ids 0x%08x",
+              last.rawPointerData.pointerCount, next.rawPointerData.pointerCount,
+              last.rawPointerData.touchingIdBits.value, next.rawPointerData.touchingIdBits.value,
+              last.rawPointerData.hoveringIdBits.value, next.rawPointerData.hoveringIdBits.value,
+              next.rawPointerData.canceledIdBits.value);
+    }
 
     if (!next.rawPointerData.touchingIdBits.isEmpty() &&
         !next.rawPointerData.hoveringIdBits.isEmpty() &&
@@ -1559,9 +1560,9 @@
             nsecs_t when = mExternalStylusFusionTimeout - STYLUS_DATA_LATENCY;
             clearStylusDataPendingFlags();
             mCurrentRawState.copyFrom(mLastRawState);
-#if DEBUG_STYLUS_FUSION
-            ALOGD("Timeout expired, synthesizing event with new stylus data");
-#endif
+            if (DEBUG_STYLUS_FUSION) {
+                ALOGD("Timeout expired, synthesizing event with new stylus data");
+            }
             const nsecs_t readTime = when; // consider this synthetic event to be zero latency
             cookAndDispatch(when, readTime);
         } else if (mExternalStylusFusionTimeout == LLONG_MAX) {
@@ -1701,9 +1702,10 @@
     mPointerController->fade(PointerControllerInterface::Transition::GRADUAL);
 
     mPointerController->setButtonState(mCurrentRawState.buttonState);
-    setTouchSpots(mCurrentCookedState.cookedPointerData.pointerCoords,
-                  mCurrentCookedState.cookedPointerData.idToIndex,
-                  mCurrentCookedState.cookedPointerData.touchingIdBits, mViewport.displayId);
+    mPointerController->setSpots(mCurrentCookedState.cookedPointerData.pointerCoords,
+                                 mCurrentCookedState.cookedPointerData.idToIndex,
+                                 mCurrentCookedState.cookedPointerData.touchingIdBits,
+                                 mViewport.displayId);
 }
 
 bool TouchInputMapper::isTouchScreen() {
@@ -1747,24 +1749,24 @@
             state.rawPointerData.pointerCount != 0;
     if (initialDown) {
         if (mExternalStylusState.pressure != 0.0f) {
-#if DEBUG_STYLUS_FUSION
-            ALOGD("Have both stylus and touch data, beginning fusion");
-#endif
+            if (DEBUG_STYLUS_FUSION) {
+                ALOGD("Have both stylus and touch data, beginning fusion");
+            }
             mExternalStylusId = state.rawPointerData.touchingIdBits.firstMarkedBit();
         } else if (timeout) {
-#if DEBUG_STYLUS_FUSION
-            ALOGD("Timeout expired, assuming touch is not a stylus.");
-#endif
+            if (DEBUG_STYLUS_FUSION) {
+                ALOGD("Timeout expired, assuming touch is not a stylus.");
+            }
             resetExternalStylus();
         } else {
             if (mExternalStylusFusionTimeout == LLONG_MAX) {
                 mExternalStylusFusionTimeout = state.when + EXTERNAL_STYLUS_DATA_TIMEOUT;
             }
-#if DEBUG_STYLUS_FUSION
-            ALOGD("No stylus data but stylus is connected, requesting timeout "
-                  "(%" PRId64 "ms)",
-                  mExternalStylusFusionTimeout);
-#endif
+            if (DEBUG_STYLUS_FUSION) {
+                ALOGD("No stylus data but stylus is connected, requesting timeout "
+                      "(%" PRId64 "ms)",
+                      mExternalStylusFusionTimeout);
+            }
             getContext()->requestTimeoutAtTime(mExternalStylusFusionTimeout);
             return true;
         }
@@ -1772,9 +1774,9 @@
 
     // Check if the stylus pointer has gone up.
     if (mExternalStylusId != -1 && !state.rawPointerData.touchingIdBits.hasBit(mExternalStylusId)) {
-#if DEBUG_STYLUS_FUSION
-        ALOGD("Stylus pointer is going up");
-#endif
+        if (DEBUG_STYLUS_FUSION) {
+            ALOGD("Stylus pointer is going up");
+        }
         mExternalStylusId = -1;
     }
 
@@ -1815,10 +1817,10 @@
             // Pointer went up while virtual key was down.
             mCurrentVirtualKey.down = false;
             if (!mCurrentVirtualKey.ignored) {
-#if DEBUG_VIRTUAL_KEYS
-                ALOGD("VirtualKeys: Generating key up: keyCode=%d, scanCode=%d",
-                      mCurrentVirtualKey.keyCode, mCurrentVirtualKey.scanCode);
-#endif
+                if (DEBUG_VIRTUAL_KEYS) {
+                    ALOGD("VirtualKeys: Generating key up: keyCode=%d, scanCode=%d",
+                          mCurrentVirtualKey.keyCode, mCurrentVirtualKey.scanCode);
+                }
                 dispatchVirtualKey(when, readTime, policyFlags, AKEY_EVENT_ACTION_UP,
                                    AKEY_EVENT_FLAG_FROM_SYSTEM | AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY);
             }
@@ -1842,10 +1844,10 @@
         // into the main display surface.
         mCurrentVirtualKey.down = false;
         if (!mCurrentVirtualKey.ignored) {
-#if DEBUG_VIRTUAL_KEYS
-            ALOGD("VirtualKeys: Canceling key: keyCode=%d, scanCode=%d", mCurrentVirtualKey.keyCode,
-                  mCurrentVirtualKey.scanCode);
-#endif
+            if (DEBUG_VIRTUAL_KEYS) {
+                ALOGD("VirtualKeys: Canceling key: keyCode=%d, scanCode=%d",
+                      mCurrentVirtualKey.keyCode, mCurrentVirtualKey.scanCode);
+            }
             dispatchVirtualKey(when, readTime, policyFlags, AKEY_EVENT_ACTION_UP,
                                AKEY_EVENT_FLAG_FROM_SYSTEM | AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY |
                                        AKEY_EVENT_FLAG_CANCELED);
@@ -1857,8 +1859,10 @@
         // Pointer just went down.  Check for virtual key press or off-screen touches.
         uint32_t id = mCurrentRawState.rawPointerData.touchingIdBits.firstMarkedBit();
         const RawPointerData::Pointer& pointer = mCurrentRawState.rawPointerData.pointerForId(id);
-        // Exclude unscaled device for inside surface checking.
-        if (!isPointInsideSurface(pointer.x, pointer.y) && mDeviceMode != DeviceMode::UNSCALED) {
+        // Skip checking whether the pointer is inside the physical frame if the device is in
+        // unscaled mode.
+        if (!isPointInsidePhysicalFrame(pointer.x, pointer.y) &&
+            mDeviceMode != DeviceMode::UNSCALED) {
             // If exactly one pointer went down, check for virtual key hit.
             // Otherwise we will drop the entire stroke.
             if (mCurrentRawState.rawPointerData.touchingIdBits.count() == 1) {
@@ -1873,10 +1877,10 @@
                                                                virtualKey->scanCode);
 
                     if (!mCurrentVirtualKey.ignored) {
-#if DEBUG_VIRTUAL_KEYS
-                        ALOGD("VirtualKeys: Generating key down: keyCode=%d, scanCode=%d",
-                              mCurrentVirtualKey.keyCode, mCurrentVirtualKey.scanCode);
-#endif
+                        if (DEBUG_VIRTUAL_KEYS) {
+                            ALOGD("VirtualKeys: Generating key down: keyCode=%d, scanCode=%d",
+                                  mCurrentVirtualKey.keyCode, mCurrentVirtualKey.scanCode);
+                        }
                         dispatchVirtualKey(when, readTime, policyFlags, AKEY_EVENT_ACTION_DOWN,
                                            AKEY_EVENT_FLAG_FROM_SYSTEM |
                                                    AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY);
@@ -1919,7 +1923,7 @@
     NotifyKeyArgs args(getContext()->getNextId(), when, readTime, getDeviceId(),
                        AINPUT_SOURCE_KEYBOARD, mViewport.displayId, policyFlags, keyEventAction,
                        keyEventFlags, keyCode, scanCode, metaState, downTime);
-    getListener()->notifyKey(&args);
+    getListener().notifyKey(&args);
 }
 
 void TouchInputMapper::abortTouches(nsecs_t when, nsecs_t readTime, uint32_t policyFlags) {
@@ -2127,7 +2131,7 @@
     }
 
     // Walk through the the active pointers and map device coordinates onto
-    // surface coordinates and adjust for display orientation.
+    // display coordinates and adjust for display orientation.
     for (uint32_t i = 0; i < currentPointerCount; i++) {
         const RawPointerData::Pointer& in = mCurrentRawState.rawPointerData.pointers[i];
 
@@ -2287,15 +2291,15 @@
         mAffineTransform.applyTo(xTransformed, yTransformed);
         rotateAndScale(xTransformed, yTransformed);
 
-        // Adjust X, Y, and coverage coords for surface orientation.
+        // Adjust X, Y, and coverage coords for input device orientation.
         float left, top, right, bottom;
 
-        switch (mSurfaceOrientation) {
+        switch (mInputDeviceOrientation) {
             case DISPLAY_ORIENTATION_90:
-                left = float(rawTop - mRawPointerAxes.y.minValue) * mYScale + mYTranslate;
-                right = float(rawBottom - mRawPointerAxes.y.minValue) * mYScale + mYTranslate;
-                bottom = float(mRawPointerAxes.x.maxValue - rawLeft) * mXScale + mXTranslate;
-                top = float(mRawPointerAxes.x.maxValue - rawRight) * mXScale + mXTranslate;
+                left = float(rawTop - mRawPointerAxes.y.minValue) * mYScale;
+                right = float(rawBottom - mRawPointerAxes.y.minValue) * mYScale;
+                bottom = float(mRawPointerAxes.x.maxValue - rawLeft) * mXScale;
+                top = float(mRawPointerAxes.x.maxValue - rawRight) * mXScale;
                 orientation -= M_PI_2;
                 if (mOrientedRanges.haveOrientation &&
                     orientation < mOrientedRanges.orientation.min) {
@@ -2306,8 +2310,8 @@
             case DISPLAY_ORIENTATION_180:
                 left = float(mRawPointerAxes.x.maxValue - rawRight) * mXScale;
                 right = float(mRawPointerAxes.x.maxValue - rawLeft) * mXScale;
-                bottom = float(mRawPointerAxes.y.maxValue - rawTop) * mYScale + mYTranslate;
-                top = float(mRawPointerAxes.y.maxValue - rawBottom) * mYScale + mYTranslate;
+                bottom = float(mRawPointerAxes.y.maxValue - rawTop) * mYScale;
+                top = float(mRawPointerAxes.y.maxValue - rawBottom) * mYScale;
                 orientation -= M_PI;
                 if (mOrientedRanges.haveOrientation &&
                     orientation < mOrientedRanges.orientation.min) {
@@ -2318,8 +2322,8 @@
             case DISPLAY_ORIENTATION_270:
                 left = float(mRawPointerAxes.y.maxValue - rawBottom) * mYScale;
                 right = float(mRawPointerAxes.y.maxValue - rawTop) * mYScale;
-                bottom = float(rawRight - mRawPointerAxes.x.minValue) * mXScale + mXTranslate;
-                top = float(rawLeft - mRawPointerAxes.x.minValue) * mXScale + mXTranslate;
+                bottom = float(rawRight - mRawPointerAxes.x.minValue) * mXScale;
+                top = float(rawLeft - mRawPointerAxes.x.minValue) * mXScale;
                 orientation += M_PI_2;
                 if (mOrientedRanges.haveOrientation &&
                     orientation > mOrientedRanges.orientation.max) {
@@ -2328,10 +2332,10 @@
                 }
                 break;
             default:
-                left = float(rawLeft - mRawPointerAxes.x.minValue) * mXScale + mXTranslate;
-                right = float(rawRight - mRawPointerAxes.x.minValue) * mXScale + mXTranslate;
-                bottom = float(rawBottom - mRawPointerAxes.y.minValue) * mYScale + mYTranslate;
-                top = float(rawTop - mRawPointerAxes.y.minValue) * mYScale + mYTranslate;
+                left = float(rawLeft - mRawPointerAxes.x.minValue) * mXScale;
+                right = float(rawRight - mRawPointerAxes.x.minValue) * mXScale;
+                bottom = float(rawBottom - mRawPointerAxes.y.minValue) * mYScale;
+                top = float(rawTop - mRawPointerAxes.y.minValue) * mYScale;
                 break;
         }
 
@@ -2441,9 +2445,10 @@
         }
 
         if (mPointerGesture.currentGestureMode == PointerGesture::Mode::FREEFORM) {
-            setTouchSpots(mPointerGesture.currentGestureCoords,
-                          mPointerGesture.currentGestureIdToIndex,
-                          mPointerGesture.currentGestureIdBits, mPointerController->getDisplayId());
+            mPointerController->setSpots(mPointerGesture.currentGestureCoords,
+                                         mPointerGesture.currentGestureIdToIndex,
+                                         mPointerGesture.currentGestureIdBits,
+                                         mPointerController->getDisplayId());
         }
     } else {
         mPointerController->setPresentation(PointerControllerInterface::Presentation::POINTER);
@@ -2593,7 +2598,8 @@
         // the pointer is hovering again even if the user is not currently touching
         // the touch pad.  This ensures that a view will receive a fresh hover enter
         // event after a tap.
-        auto [x, y] = getMouseCursorPosition();
+        float x, y;
+        mPointerController->getPosition(&x, &y);
 
         PointerProperties pointerProperties;
         pointerProperties.clear();
@@ -2611,7 +2617,7 @@
                               metaState, buttonState, MotionClassification::NONE,
                               AMOTION_EVENT_EDGE_FLAG_NONE, 1, &pointerProperties, &pointerCoords,
                               0, 0, x, y, mPointerGesture.downTime, /* videoFrames */ {});
-        getListener()->notifyMotion(&args);
+        getListener().notifyMotion(&args);
     }
 
     // Update state.
@@ -2662,9 +2668,9 @@
 
     // Handle TAP timeout.
     if (isTimeout) {
-#if DEBUG_GESTURES
-        ALOGD("Gestures: Processing timeout");
-#endif
+        if (DEBUG_GESTURES) {
+            ALOGD("Gestures: Processing timeout");
+        }
 
         if (mPointerGesture.lastGestureMode == PointerGesture::Mode::TAP) {
             if (when <= mPointerGesture.tapUpTime + mConfig.pointerGestureTapDragInterval) {
@@ -2673,9 +2679,9 @@
                                                    mConfig.pointerGestureTapDragInterval);
             } else {
                 // The tap is finished.
-#if DEBUG_GESTURES
-                ALOGD("Gestures: TAP finished");
-#endif
+                if (DEBUG_GESTURES) {
+                    ALOGD("Gestures: TAP finished");
+                }
                 *outFinishPreviousGesture = true;
 
                 mPointerGesture.activeGestureId = -1;
@@ -2771,10 +2777,11 @@
     // Switch states based on button and pointer state.
     if (isQuietTime) {
         // Case 1: Quiet time. (QUIET)
-#if DEBUG_GESTURES
-        ALOGD("Gestures: QUIET for next %0.3fms",
-              (mPointerGesture.quietTime + mConfig.pointerGestureQuietInterval - when) * 0.000001f);
-#endif
+        if (DEBUG_GESTURES) {
+            ALOGD("Gestures: QUIET for next %0.3fms",
+                  (mPointerGesture.quietTime + mConfig.pointerGestureQuietInterval - when) *
+                          0.000001f);
+        }
         if (mPointerGesture.lastGestureMode != PointerGesture::Mode::QUIET) {
             *outFinishPreviousGesture = true;
         }
@@ -2798,11 +2805,11 @@
         // active.  If the user first puts one finger down to click then adds another
         // finger to drag then the active pointer should switch to the finger that is
         // being dragged.
-#if DEBUG_GESTURES
-        ALOGD("Gestures: BUTTON_CLICK_OR_DRAG activeTouchId=%d, "
-              "currentFingerCount=%d",
-              activeTouchId, currentFingerCount);
-#endif
+        if (DEBUG_GESTURES) {
+            ALOGD("Gestures: BUTTON_CLICK_OR_DRAG activeTouchId=%d, "
+                  "currentFingerCount=%d",
+                  activeTouchId, currentFingerCount);
+        }
         // Reset state when just starting.
         if (mPointerGesture.lastGestureMode != PointerGesture::Mode::BUTTON_CLICK_OR_DRAG) {
             *outFinishPreviousGesture = true;
@@ -2827,11 +2834,11 @@
             }
             if (bestId >= 0 && bestId != activeTouchId) {
                 mPointerGesture.activeTouchId = activeTouchId = bestId;
-#if DEBUG_GESTURES
-                ALOGD("Gestures: BUTTON_CLICK_OR_DRAG switched pointers, "
-                      "bestId=%d, bestSpeed=%0.3f",
-                      bestId, bestSpeed);
-#endif
+                if (DEBUG_GESTURES) {
+                    ALOGD("Gestures: BUTTON_CLICK_OR_DRAG switched pointers, "
+                          "bestId=%d, bestSpeed=%0.3f",
+                          bestId, bestSpeed);
+                }
             }
         }
 
@@ -2844,18 +2851,19 @@
             deltaX = (currentPointer.x - lastPointer.x) * mPointerXMovementScale;
             deltaY = (currentPointer.y - lastPointer.y) * mPointerYMovementScale;
 
-            rotateDelta(mSurfaceOrientation, &deltaX, &deltaY);
+            rotateDelta(mInputDeviceOrientation, &deltaX, &deltaY);
             mPointerVelocityControl.move(when, &deltaX, &deltaY);
 
             // Move the pointer using a relative motion.
             // When using spots, the click will occur at the position of the anchor
             // spot and all other spots will move there.
-            moveMouseCursor(deltaX, deltaY);
+            mPointerController->move(deltaX, deltaY);
         } else {
             mPointerVelocityControl.reset();
         }
 
-        auto [x, y] = getMouseCursorPosition();
+        float x, y;
+        mPointerController->getPosition(&x, &y);
 
         mPointerGesture.currentGestureMode = PointerGesture::Mode::BUTTON_CLICK_OR_DRAG;
         mPointerGesture.currentGestureIdBits.clear();
@@ -2881,12 +2889,13 @@
              mPointerGesture.lastGestureMode == PointerGesture::Mode::TAP_DRAG) &&
             lastFingerCount == 1) {
             if (when <= mPointerGesture.tapDownTime + mConfig.pointerGestureTapInterval) {
-                auto [x, y] = getMouseCursorPosition();
+                float x, y;
+                mPointerController->getPosition(&x, &y);
                 if (fabs(x - mPointerGesture.tapX) <= mConfig.pointerGestureTapSlop &&
                     fabs(y - mPointerGesture.tapY) <= mConfig.pointerGestureTapSlop) {
-#if DEBUG_GESTURES
-                    ALOGD("Gestures: TAP");
-#endif
+                    if (DEBUG_GESTURES) {
+                        ALOGD("Gestures: TAP");
+                    }
 
                     mPointerGesture.tapUpTime = when;
                     getContext()->requestTimeoutAtTime(when +
@@ -2912,29 +2921,29 @@
 
                     tapped = true;
                 } else {
-#if DEBUG_GESTURES
-                    ALOGD("Gestures: Not a TAP, deltaX=%f, deltaY=%f", x - mPointerGesture.tapX,
-                          y - mPointerGesture.tapY);
-#endif
+                    if (DEBUG_GESTURES) {
+                        ALOGD("Gestures: Not a TAP, deltaX=%f, deltaY=%f", x - mPointerGesture.tapX,
+                              y - mPointerGesture.tapY);
+                    }
                 }
             } else {
-#if DEBUG_GESTURES
-                if (mPointerGesture.tapDownTime != LLONG_MIN) {
-                    ALOGD("Gestures: Not a TAP, %0.3fms since down",
-                          (when - mPointerGesture.tapDownTime) * 0.000001f);
-                } else {
-                    ALOGD("Gestures: Not a TAP, incompatible mode transitions");
+                if (DEBUG_GESTURES) {
+                    if (mPointerGesture.tapDownTime != LLONG_MIN) {
+                        ALOGD("Gestures: Not a TAP, %0.3fms since down",
+                              (when - mPointerGesture.tapDownTime) * 0.000001f);
+                    } else {
+                        ALOGD("Gestures: Not a TAP, incompatible mode transitions");
+                    }
                 }
-#endif
             }
         }
 
         mPointerVelocityControl.reset();
 
         if (!tapped) {
-#if DEBUG_GESTURES
-            ALOGD("Gestures: NEUTRAL");
-#endif
+            if (DEBUG_GESTURES) {
+                ALOGD("Gestures: NEUTRAL");
+            }
             mPointerGesture.activeGestureId = -1;
             mPointerGesture.currentGestureMode = PointerGesture::Mode::NEUTRAL;
             mPointerGesture.currentGestureIdBits.clear();
@@ -2949,21 +2958,22 @@
         mPointerGesture.currentGestureMode = PointerGesture::Mode::HOVER;
         if (mPointerGesture.lastGestureMode == PointerGesture::Mode::TAP) {
             if (when <= mPointerGesture.tapUpTime + mConfig.pointerGestureTapDragInterval) {
-                auto [x, y] = getMouseCursorPosition();
+                float x, y;
+                mPointerController->getPosition(&x, &y);
                 if (fabs(x - mPointerGesture.tapX) <= mConfig.pointerGestureTapSlop &&
                     fabs(y - mPointerGesture.tapY) <= mConfig.pointerGestureTapSlop) {
                     mPointerGesture.currentGestureMode = PointerGesture::Mode::TAP_DRAG;
                 } else {
-#if DEBUG_GESTURES
-                    ALOGD("Gestures: Not a TAP_DRAG, deltaX=%f, deltaY=%f",
-                          x - mPointerGesture.tapX, y - mPointerGesture.tapY);
-#endif
+                    if (DEBUG_GESTURES) {
+                        ALOGD("Gestures: Not a TAP_DRAG, deltaX=%f, deltaY=%f",
+                              x - mPointerGesture.tapX, y - mPointerGesture.tapY);
+                    }
                 }
             } else {
-#if DEBUG_GESTURES
-                ALOGD("Gestures: Not a TAP_DRAG, %0.3fms time since up",
-                      (when - mPointerGesture.tapUpTime) * 0.000001f);
-#endif
+                if (DEBUG_GESTURES) {
+                    ALOGD("Gestures: Not a TAP_DRAG, %0.3fms time since up",
+                          (when - mPointerGesture.tapUpTime) * 0.000001f);
+                }
             }
         } else if (mPointerGesture.lastGestureMode == PointerGesture::Mode::TAP_DRAG) {
             mPointerGesture.currentGestureMode = PointerGesture::Mode::TAP_DRAG;
@@ -2978,26 +2988,26 @@
             deltaX = (currentPointer.x - lastPointer.x) * mPointerXMovementScale;
             deltaY = (currentPointer.y - lastPointer.y) * mPointerYMovementScale;
 
-            rotateDelta(mSurfaceOrientation, &deltaX, &deltaY);
+            rotateDelta(mInputDeviceOrientation, &deltaX, &deltaY);
             mPointerVelocityControl.move(when, &deltaX, &deltaY);
 
             // Move the pointer using a relative motion.
             // When using spots, the hover or drag will occur at the position of the anchor spot.
-            moveMouseCursor(deltaX, deltaY);
+            mPointerController->move(deltaX, deltaY);
         } else {
             mPointerVelocityControl.reset();
         }
 
         bool down;
         if (mPointerGesture.currentGestureMode == PointerGesture::Mode::TAP_DRAG) {
-#if DEBUG_GESTURES
-            ALOGD("Gestures: TAP_DRAG");
-#endif
+            if (DEBUG_GESTURES) {
+                ALOGD("Gestures: TAP_DRAG");
+            }
             down = true;
         } else {
-#if DEBUG_GESTURES
-            ALOGD("Gestures: HOVER");
-#endif
+            if (DEBUG_GESTURES) {
+                ALOGD("Gestures: HOVER");
+            }
             if (mPointerGesture.lastGestureMode != PointerGesture::Mode::HOVER) {
                 *outFinishPreviousGesture = true;
             }
@@ -3005,7 +3015,8 @@
             down = false;
         }
 
-        auto [x, y] = getMouseCursorPosition();
+        float x, y;
+        mPointerController->getPosition(&x, &y);
 
         mPointerGesture.currentGestureIdBits.clear();
         mPointerGesture.currentGestureIdBits.markBit(mPointerGesture.activeGestureId);
@@ -3050,12 +3061,13 @@
         } else if (!settled && currentFingerCount > lastFingerCount) {
             // Additional pointers have gone down but not yet settled.
             // Reset the gesture.
-#if DEBUG_GESTURES
-            ALOGD("Gestures: Resetting gesture since additional pointers went down for MULTITOUCH, "
-                  "settle time remaining %0.3fms",
-                  (mPointerGesture.firstTouchTime + mConfig.pointerGestureMultitouchSettleInterval -
-                   when) * 0.000001f);
-#endif
+            if (DEBUG_GESTURES) {
+                ALOGD("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.
@@ -3069,18 +3081,18 @@
             mPointerVelocityControl.reset();
 
             // Use the centroid and pointer location as the reference points for the gesture.
-#if DEBUG_GESTURES
-            ALOGD("Gestures: Using centroid as reference for MULTITOUCH, "
-                  "settle time remaining %0.3fms",
-                  (mPointerGesture.firstTouchTime + mConfig.pointerGestureMultitouchSettleInterval -
-                   when) * 0.000001f);
-#endif
+            if (DEBUG_GESTURES) {
+                ALOGD("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);
-            auto [x, y] = getMouseCursorPosition();
-            mPointerGesture.referenceGestureX = x;
-            mPointerGesture.referenceGestureY = y;
+            mPointerController->getPosition(&mPointerGesture.referenceGestureX,
+                                            &mPointerGesture.referenceGestureY);
         }
 
         // Clear the reference deltas for fingers not yet included in the reference calculation.
@@ -3133,10 +3145,10 @@
             if (distOverThreshold >= 2) {
                 if (currentFingerCount > 2) {
                     // There are more than two pointers, switch to FREEFORM.
-#if DEBUG_GESTURES
-                    ALOGD("Gestures: PRESS transitioned to FREEFORM, number of pointers %d > 2",
-                          currentFingerCount);
-#endif
+                    if (DEBUG_GESTURES) {
+                        ALOGD("Gestures: PRESS transitioned to FREEFORM, number of pointers %d > 2",
+                              currentFingerCount);
+                    }
                     *outCancelPreviousGesture = true;
                     mPointerGesture.currentGestureMode = PointerGesture::Mode::FREEFORM;
                 } else {
@@ -3152,10 +3164,11 @@
                     if (mutualDistance > mPointerGestureMaxSwipeWidth) {
                         // There are two pointers but they are too far apart for a SWIPE,
                         // switch to FREEFORM.
-#if DEBUG_GESTURES
-                        ALOGD("Gestures: PRESS transitioned to FREEFORM, distance %0.3f > %0.3f",
-                              mutualDistance, mPointerGestureMaxSwipeWidth);
-#endif
+                        if (DEBUG_GESTURES) {
+                            ALOGD("Gestures: PRESS transitioned to FREEFORM, distance %0.3f > "
+                                  "%0.3f",
+                                  mutualDistance, mPointerGestureMaxSwipeWidth);
+                        }
                         *outCancelPreviousGesture = true;
                         mPointerGesture.currentGestureMode = PointerGesture::Mode::FREEFORM;
                     } else {
@@ -3180,25 +3193,25 @@
                             float cosine = dot / (dist1 * dist2); // denominator always > 0
                             if (cosine >= mConfig.pointerGestureSwipeTransitionAngleCosine) {
                                 // Pointers are moving in the same direction.  Switch to SWIPE.
-#if DEBUG_GESTURES
-                                ALOGD("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);
-#endif
+                                if (DEBUG_GESTURES) {
+                                    ALOGD("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.
-#if DEBUG_GESTURES
-                                ALOGD("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);
-#endif
+                                if (DEBUG_GESTURES) {
+                                    ALOGD("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;
                             }
@@ -3210,10 +3223,10 @@
             // Switch from SWIPE to FREEFORM if additional pointers go down.
             // Cancel previous gesture.
             if (currentFingerCount > 2) {
-#if DEBUG_GESTURES
-                ALOGD("Gestures: SWIPE transitioned to FREEFORM, number of pointers %d > 2",
-                      currentFingerCount);
-#endif
+                if (DEBUG_GESTURES) {
+                    ALOGD("Gestures: SWIPE transitioned to FREEFORM, number of pointers %d > 2",
+                          currentFingerCount);
+                }
                 *outCancelPreviousGesture = true;
                 mPointerGesture.currentGestureMode = PointerGesture::Mode::FREEFORM;
             }
@@ -3236,7 +3249,7 @@
             commonDeltaX *= mPointerXMovementScale;
             commonDeltaY *= mPointerYMovementScale;
 
-            rotateDelta(mSurfaceOrientation, &commonDeltaX, &commonDeltaY);
+            rotateDelta(mInputDeviceOrientation, &commonDeltaX, &commonDeltaY);
             mPointerVelocityControl.move(when, &commonDeltaX, &commonDeltaY);
 
             mPointerGesture.referenceGestureX += commonDeltaX;
@@ -3247,11 +3260,11 @@
         if (mPointerGesture.currentGestureMode == PointerGesture::Mode::PRESS ||
             mPointerGesture.currentGestureMode == PointerGesture::Mode::SWIPE) {
             // PRESS or SWIPE mode.
-#if DEBUG_GESTURES
-            ALOGD("Gestures: PRESS or SWIPE activeTouchId=%d,"
-                  "activeGestureId=%d, currentTouchPointerCount=%d",
-                  activeTouchId, mPointerGesture.activeGestureId, currentFingerCount);
-#endif
+            if (DEBUG_GESTURES) {
+                ALOGD("Gestures: PRESS or SWIPE activeTouchId=%d,"
+                      "activeGestureId=%d, currentTouchPointerCount=%d",
+                      activeTouchId, mPointerGesture.activeGestureId, currentFingerCount);
+            }
             ALOG_ASSERT(mPointerGesture.activeGestureId >= 0);
 
             mPointerGesture.currentGestureIdBits.clear();
@@ -3268,11 +3281,11 @@
             mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 1.0f);
         } else if (mPointerGesture.currentGestureMode == PointerGesture::Mode::FREEFORM) {
             // FREEFORM mode.
-#if DEBUG_GESTURES
-            ALOGD("Gestures: FREEFORM activeTouchId=%d,"
-                  "activeGestureId=%d, currentTouchPointerCount=%d",
-                  activeTouchId, mPointerGesture.activeGestureId, currentFingerCount);
-#endif
+            if (DEBUG_GESTURES) {
+                ALOGD("Gestures: FREEFORM activeTouchId=%d,"
+                      "activeGestureId=%d, currentTouchPointerCount=%d",
+                      activeTouchId, mPointerGesture.activeGestureId, currentFingerCount);
+            }
             ALOG_ASSERT(mPointerGesture.activeGestureId >= 0);
 
             mPointerGesture.currentGestureIdBits.clear();
@@ -3311,13 +3324,13 @@
                 }
             }
 
-#if DEBUG_GESTURES
-            ALOGD("Gestures: FREEFORM follow up "
-                  "mappedTouchIdBits=0x%08x, usedGestureIdBits=0x%08x, "
-                  "activeGestureId=%d",
-                  mappedTouchIdBits.value, usedGestureIdBits.value,
-                  mPointerGesture.activeGestureId);
-#endif
+            if (DEBUG_GESTURES) {
+                ALOGD("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++) {
@@ -3326,18 +3339,18 @@
                 if (!mappedTouchIdBits.hasBit(touchId)) {
                     gestureId = usedGestureIdBits.markFirstUnmarkedBit();
                     mPointerGesture.freeformTouchToGestureIdMap[touchId] = gestureId;
-#if DEBUG_GESTURES
-                    ALOGD("Gestures: FREEFORM "
-                          "new mapping for touch id %d -> gesture id %d",
-                          touchId, gestureId);
-#endif
+                    if (DEBUG_GESTURES) {
+                        ALOGD("Gestures: FREEFORM "
+                              "new mapping for touch id %d -> gesture id %d",
+                              touchId, gestureId);
+                    }
                 } else {
                     gestureId = mPointerGesture.freeformTouchToGestureIdMap[touchId];
-#if DEBUG_GESTURES
-                    ALOGD("Gestures: FREEFORM "
-                          "existing mapping for touch id %d -> gesture id %d",
-                          touchId, gestureId);
-#endif
+                    if (DEBUG_GESTURES) {
+                        ALOGD("Gestures: FREEFORM "
+                              "existing mapping for touch id %d -> gesture id %d",
+                              touchId, gestureId);
+                    }
                 }
                 mPointerGesture.currentGestureIdBits.markBit(gestureId);
                 mPointerGesture.currentGestureIdToIndex[gestureId] = i;
@@ -3346,7 +3359,7 @@
                         mCurrentRawState.rawPointerData.pointerForId(touchId);
                 float deltaX = (pointer.x - mPointerGesture.referenceTouchX) * mPointerXZoomScale;
                 float deltaY = (pointer.y - mPointerGesture.referenceTouchY) * mPointerYZoomScale;
-                rotateDelta(mSurfaceOrientation, &deltaX, &deltaY);
+                rotateDelta(mInputDeviceOrientation, &deltaX, &deltaY);
 
                 mPointerGesture.currentGestureProperties[i].clear();
                 mPointerGesture.currentGestureProperties[i].id = gestureId;
@@ -3366,47 +3379,46 @@
             if (mPointerGesture.activeGestureId < 0) {
                 mPointerGesture.activeGestureId =
                         mPointerGesture.currentGestureIdBits.firstMarkedBit();
-#if DEBUG_GESTURES
-                ALOGD("Gestures: FREEFORM new "
-                      "activeGestureId=%d",
-                      mPointerGesture.activeGestureId);
-#endif
+                if (DEBUG_GESTURES) {
+                    ALOGD("Gestures: FREEFORM new activeGestureId=%d",
+                          mPointerGesture.activeGestureId);
+                }
             }
         }
     }
 
     mPointerController->setButtonState(mCurrentRawState.buttonState);
 
-#if DEBUG_GESTURES
-    ALOGD("Gestures: finishPreviousGesture=%s, cancelPreviousGesture=%s, "
-          "currentGestureMode=%d, currentGestureIdBits=0x%08x, "
-          "lastGestureMode=%d, lastGestureIdBits=0x%08x",
-          toString(*outFinishPreviousGesture), toString(*outCancelPreviousGesture),
-          mPointerGesture.currentGestureMode, mPointerGesture.currentGestureIdBits.value,
-          mPointerGesture.lastGestureMode, mPointerGesture.lastGestureIdBits.value);
-    for (BitSet32 idBits = mPointerGesture.currentGestureIdBits; !idBits.isEmpty();) {
-        uint32_t id = idBits.clearFirstMarkedBit();
-        uint32_t index = mPointerGesture.currentGestureIdToIndex[id];
-        const PointerProperties& properties = mPointerGesture.currentGestureProperties[index];
-        const PointerCoords& coords = mPointerGesture.currentGestureCoords[index];
-        ALOGD("  currentGesture[%d]: index=%d, toolType=%d, "
-              "x=%0.3f, y=%0.3f, pressure=%0.3f",
-              id, index, properties.toolType, coords.getAxisValue(AMOTION_EVENT_AXIS_X),
-              coords.getAxisValue(AMOTION_EVENT_AXIS_Y),
-              coords.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE));
+    if (DEBUG_GESTURES) {
+        ALOGD("Gestures: finishPreviousGesture=%s, cancelPreviousGesture=%s, "
+              "currentGestureMode=%d, currentGestureIdBits=0x%08x, "
+              "lastGestureMode=%d, lastGestureIdBits=0x%08x",
+              toString(*outFinishPreviousGesture), toString(*outCancelPreviousGesture),
+              mPointerGesture.currentGestureMode, mPointerGesture.currentGestureIdBits.value,
+              mPointerGesture.lastGestureMode, mPointerGesture.lastGestureIdBits.value);
+        for (BitSet32 idBits = mPointerGesture.currentGestureIdBits; !idBits.isEmpty();) {
+            uint32_t id = idBits.clearFirstMarkedBit();
+            uint32_t index = mPointerGesture.currentGestureIdToIndex[id];
+            const PointerProperties& properties = mPointerGesture.currentGestureProperties[index];
+            const PointerCoords& coords = mPointerGesture.currentGestureCoords[index];
+            ALOGD("  currentGesture[%d]: index=%d, toolType=%d, "
+                  "x=%0.3f, y=%0.3f, pressure=%0.3f",
+                  id, index, properties.toolType, coords.getAxisValue(AMOTION_EVENT_AXIS_X),
+                  coords.getAxisValue(AMOTION_EVENT_AXIS_Y),
+                  coords.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE));
+        }
+        for (BitSet32 idBits = mPointerGesture.lastGestureIdBits; !idBits.isEmpty();) {
+            uint32_t id = idBits.clearFirstMarkedBit();
+            uint32_t index = mPointerGesture.lastGestureIdToIndex[id];
+            const PointerProperties& properties = mPointerGesture.lastGestureProperties[index];
+            const PointerCoords& coords = mPointerGesture.lastGestureCoords[index];
+            ALOGD("  lastGesture[%d]: index=%d, toolType=%d, "
+                  "x=%0.3f, y=%0.3f, pressure=%0.3f",
+                  id, index, properties.toolType, coords.getAxisValue(AMOTION_EVENT_AXIS_X),
+                  coords.getAxisValue(AMOTION_EVENT_AXIS_Y),
+                  coords.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE));
+        }
     }
-    for (BitSet32 idBits = mPointerGesture.lastGestureIdBits; !idBits.isEmpty();) {
-        uint32_t id = idBits.clearFirstMarkedBit();
-        uint32_t index = mPointerGesture.lastGestureIdToIndex[id];
-        const PointerProperties& properties = mPointerGesture.lastGestureProperties[index];
-        const PointerCoords& coords = mPointerGesture.lastGestureCoords[index];
-        ALOGD("  lastGesture[%d]: index=%d, toolType=%d, "
-              "x=%0.3f, y=%0.3f, pressure=%0.3f",
-              id, index, properties.toolType, coords.getAxisValue(AMOTION_EVENT_AXIS_X),
-              coords.getAxisValue(AMOTION_EVENT_AXIS_Y),
-              coords.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE));
-    }
-#endif
     return true;
 }
 
@@ -3418,13 +3430,15 @@
     if (!mCurrentCookedState.stylusIdBits.isEmpty()) {
         uint32_t id = mCurrentCookedState.stylusIdBits.firstMarkedBit();
         uint32_t index = mCurrentCookedState.cookedPointerData.idToIndex[id];
-        setMouseCursorPosition(mCurrentCookedState.cookedPointerData.pointerCoords[index].getX(),
-                               mCurrentCookedState.cookedPointerData.pointerCoords[index].getY());
+        mPointerController
+                ->setPosition(mCurrentCookedState.cookedPointerData.pointerCoords[index].getX(),
+                              mCurrentCookedState.cookedPointerData.pointerCoords[index].getY());
 
         hovering = mCurrentCookedState.cookedPointerData.hoveringIdBits.hasBit(id);
         down = !hovering;
 
-        auto [x, y] = getMouseCursorPosition();
+        float x, y;
+        mPointerController->getPosition(&x, &y);
         mPointerSimple.currentCoords.copyFrom(
                 mCurrentCookedState.cookedPointerData.pointerCoords[index]);
         mPointerSimple.currentCoords.setAxisValue(AMOTION_EVENT_AXIS_X, x);
@@ -3462,10 +3476,10 @@
                       mLastRawState.rawPointerData.pointers[lastIndex].y) *
                     mPointerYMovementScale;
 
-            rotateDelta(mSurfaceOrientation, &deltaX, &deltaY);
+            rotateDelta(mInputDeviceOrientation, &deltaX, &deltaY);
             mPointerVelocityControl.move(when, &deltaX, &deltaY);
 
-            moveMouseCursor(deltaX, deltaY);
+            mPointerController->move(deltaX, deltaY);
         } else {
             mPointerVelocityControl.reset();
         }
@@ -3473,7 +3487,8 @@
         down = isPointerDown(mCurrentRawState.buttonState);
         hovering = !down;
 
-        auto [x, y] = getMouseCursorPosition();
+        float x, y;
+        mPointerController->getPosition(&x, &y);
         mPointerSimple.currentCoords.copyFrom(
                 mCurrentCookedState.cookedPointerData.pointerCoords[currentIndex]);
         mPointerSimple.currentCoords.setAxisValue(AMOTION_EVENT_AXIS_X, x);
@@ -3513,7 +3528,8 @@
     }
     int32_t displayId = mPointerController->getDisplayId();
 
-    auto [xCursorPosition, yCursorPosition] = getMouseCursorPosition();
+    float xCursorPosition, yCursorPosition;
+    mPointerController->getPosition(&xCursorPosition, &yCursorPosition);
 
     if (mPointerSimple.down && !down) {
         mPointerSimple.down = false;
@@ -3526,7 +3542,7 @@
                               &mPointerSimple.lastCoords, mOrientedXPrecision, mOrientedYPrecision,
                               xCursorPosition, yCursorPosition, mPointerSimple.downTime,
                               /* videoFrames */ {});
-        getListener()->notifyMotion(&args);
+        getListener().notifyMotion(&args);
     }
 
     if (mPointerSimple.hovering && !hovering) {
@@ -3540,7 +3556,7 @@
                               &mPointerSimple.lastCoords, mOrientedXPrecision, mOrientedYPrecision,
                               xCursorPosition, yCursorPosition, mPointerSimple.downTime,
                               /* videoFrames */ {});
-        getListener()->notifyMotion(&args);
+        getListener().notifyMotion(&args);
     }
 
     if (down) {
@@ -3556,7 +3572,7 @@
                                   &mPointerSimple.currentProperties, &mPointerSimple.currentCoords,
                                   mOrientedXPrecision, mOrientedYPrecision, xCursorPosition,
                                   yCursorPosition, mPointerSimple.downTime, /* videoFrames */ {});
-            getListener()->notifyMotion(&args);
+            getListener().notifyMotion(&args);
         }
 
         // Send move.
@@ -3567,7 +3583,7 @@
                               &mPointerSimple.currentCoords, mOrientedXPrecision,
                               mOrientedYPrecision, xCursorPosition, yCursorPosition,
                               mPointerSimple.downTime, /* videoFrames */ {});
-        getListener()->notifyMotion(&args);
+        getListener().notifyMotion(&args);
     }
 
     if (hovering) {
@@ -3582,7 +3598,7 @@
                                   &mPointerSimple.currentProperties, &mPointerSimple.currentCoords,
                                   mOrientedXPrecision, mOrientedYPrecision, xCursorPosition,
                                   yCursorPosition, mPointerSimple.downTime, /* videoFrames */ {});
-            getListener()->notifyMotion(&args);
+            getListener().notifyMotion(&args);
         }
 
         // Send hover move.
@@ -3593,7 +3609,7 @@
                               &mPointerSimple.currentCoords, mOrientedXPrecision,
                               mOrientedYPrecision, xCursorPosition, yCursorPosition,
                               mPointerSimple.downTime, /* videoFrames */ {});
-        getListener()->notifyMotion(&args);
+        getListener().notifyMotion(&args);
     }
 
     if (mCurrentRawState.rawVScroll || mCurrentRawState.rawHScroll) {
@@ -3615,7 +3631,7 @@
                               &pointerCoords, mOrientedXPrecision, mOrientedYPrecision,
                               xCursorPosition, yCursorPosition, mPointerSimple.downTime,
                               /* videoFrames */ {});
-        getListener()->notifyMotion(&args);
+        getListener().notifyMotion(&args);
     }
 
     // Save state.
@@ -3679,21 +3695,19 @@
     float xCursorPosition = AMOTION_EVENT_INVALID_CURSOR_POSITION;
     float yCursorPosition = AMOTION_EVENT_INVALID_CURSOR_POSITION;
     if (mDeviceMode == DeviceMode::POINTER) {
-        auto [x, y] = getMouseCursorPosition();
-        xCursorPosition = x;
-        yCursorPosition = y;
+        mPointerController->getPosition(&xCursorPosition, &yCursorPosition);
     }
     const int32_t displayId = getAssociatedDisplayId().value_or(ADISPLAY_ID_NONE);
     const int32_t deviceId = getDeviceId();
     std::vector<TouchVideoFrame> frames = getDeviceContext().getVideoFrames();
     std::for_each(frames.begin(), frames.end(),
-                  [this](TouchVideoFrame& frame) { frame.rotate(this->mSurfaceOrientation); });
+                  [this](TouchVideoFrame& frame) { frame.rotate(this->mInputDeviceOrientation); });
     NotifyMotionArgs args(getContext()->getNextId(), when, readTime, deviceId, source, displayId,
                           policyFlags, action, actionButton, flags, metaState, buttonState,
                           MotionClassification::NONE, edgeFlags, pointerCount, pointerProperties,
                           pointerCoords, xPrecision, yPrecision, xCursorPosition, yCursorPosition,
                           downTime, std::move(frames));
-    getListener()->notifyMotion(&args);
+    getListener().notifyMotion(&args);
 }
 
 bool TouchInputMapper::updateMovedPointers(const PointerProperties* inProperties,
@@ -3731,60 +3745,59 @@
     abortTouches(when, readTime, 0 /* policyFlags*/);
 }
 
-// Transform raw coordinate to surface coordinate
-void TouchInputMapper::rotateAndScale(float& x, float& y) {
-    // Scale to surface coordinate.
+// Transform input device coordinates to display panel coordinates.
+void TouchInputMapper::rotateAndScale(float& x, float& y) const {
     const float xScaled = float(x - mRawPointerAxes.x.minValue) * mXScale;
     const float yScaled = float(y - mRawPointerAxes.y.minValue) * mYScale;
 
     const float xScaledMax = float(mRawPointerAxes.x.maxValue - x) * mXScale;
     const float yScaledMax = float(mRawPointerAxes.y.maxValue - y) * mYScale;
 
-    // Rotate to surface coordinate.
+    // Rotate to display coordinate.
     // 0 - no swap and reverse.
     // 90 - swap x/y and reverse y.
     // 180 - reverse x, y.
     // 270 - swap x/y and reverse x.
-    switch (mSurfaceOrientation) {
+    switch (mInputDeviceOrientation) {
         case DISPLAY_ORIENTATION_0:
-            x = xScaled + mXTranslate;
-            y = yScaled + mYTranslate;
+            x = xScaled;
+            y = yScaled;
             break;
         case DISPLAY_ORIENTATION_90:
-            y = xScaledMax - (mRawSurfaceWidth - mSurfaceRight);
-            x = yScaled + mYTranslate;
+            y = xScaledMax;
+            x = yScaled;
             break;
         case DISPLAY_ORIENTATION_180:
-            x = xScaledMax - (mRawSurfaceWidth - mSurfaceRight);
-            y = yScaledMax - (mRawSurfaceHeight - mSurfaceBottom);
+            x = xScaledMax;
+            y = yScaledMax;
             break;
         case DISPLAY_ORIENTATION_270:
-            y = xScaled + mXTranslate;
-            x = yScaledMax - (mRawSurfaceHeight - mSurfaceBottom);
+            y = xScaled;
+            x = yScaledMax;
             break;
         default:
             assert(false);
     }
 }
 
-bool TouchInputMapper::isPointInsideSurface(int32_t x, int32_t y) {
+bool TouchInputMapper::isPointInsidePhysicalFrame(int32_t x, int32_t y) const {
     const float xScaled = (x - mRawPointerAxes.x.minValue) * mXScale;
     const float yScaled = (y - mRawPointerAxes.y.minValue) * mYScale;
 
     return x >= mRawPointerAxes.x.minValue && x <= mRawPointerAxes.x.maxValue &&
-            xScaled >= mSurfaceLeft && xScaled <= mSurfaceRight &&
+            xScaled >= mPhysicalLeft && xScaled <= (mPhysicalLeft + mPhysicalWidth) &&
             y >= mRawPointerAxes.y.minValue && y <= mRawPointerAxes.y.maxValue &&
-            yScaled >= mSurfaceTop && yScaled <= mSurfaceBottom;
+            yScaled >= mPhysicalTop && yScaled <= (mPhysicalTop + mPhysicalHeight);
 }
 
 const TouchInputMapper::VirtualKey* TouchInputMapper::findVirtualKeyHit(int32_t x, int32_t y) {
     for (const VirtualKey& virtualKey : mVirtualKeys) {
-#if DEBUG_VIRTUAL_KEYS
-        ALOGD("VirtualKeys: Hit test (%d, %d): keyCode=%d, scanCode=%d, "
-              "left=%d, top=%d, right=%d, bottom=%d",
-              x, y, virtualKey.keyCode, virtualKey.scanCode, virtualKey.hitLeft, virtualKey.hitTop,
-              virtualKey.hitRight, virtualKey.hitBottom);
-#endif
+        if (DEBUG_VIRTUAL_KEYS) {
+            ALOGD("VirtualKeys: Hit test (%d, %d): keyCode=%d, scanCode=%d, "
+                  "left=%d, top=%d, right=%d, bottom=%d",
+                  x, y, virtualKey.keyCode, virtualKey.scanCode, virtualKey.hitLeft,
+                  virtualKey.hitTop, virtualKey.hitRight, virtualKey.hitBottom);
+        }
 
         if (virtualKey.isHit(x, y)) {
             return &virtualKey;
@@ -3881,13 +3894,13 @@
         }
     }
 
-#if DEBUG_POINTER_ASSIGNMENT
-    ALOGD("assignPointerIds - initial distance min-heap: size=%d", heapSize);
-    for (size_t i = 0; i < heapSize; i++) {
-        ALOGD("  heap[%zu]: cur=%" PRIu32 ", last=%" PRIu32 ", distance=%" PRIu64, i,
-              heap[i].currentPointerIndex, heap[i].lastPointerIndex, heap[i].distance);
+    if (DEBUG_POINTER_ASSIGNMENT) {
+        ALOGD("assignPointerIds - initial distance min-heap: size=%d", heapSize);
+        for (size_t i = 0; i < heapSize; i++) {
+            ALOGD("  heap[%zu]: cur=%" PRIu32 ", last=%" PRIu32 ", distance=%" PRIu64, i,
+                  heap[i].currentPointerIndex, heap[i].lastPointerIndex, heap[i].distance);
+        }
     }
-#endif
 
     // Pull matches out by increasing order of distance.
     // To avoid reassigning pointers that have already been matched, the loop keeps track
@@ -3926,13 +3939,14 @@
                     parentIndex = childIndex;
                 }
 
-#if DEBUG_POINTER_ASSIGNMENT
-                ALOGD("assignPointerIds - reduced distance min-heap: size=%d", heapSize);
-                for (size_t j = 0; j < heapSize; j++) {
-                    ALOGD("  heap[%zu]: cur=%" PRIu32 ", last=%" PRIu32 ", distance=%" PRIu64, j,
-                          heap[j].currentPointerIndex, heap[j].lastPointerIndex, heap[j].distance);
+                if (DEBUG_POINTER_ASSIGNMENT) {
+                    ALOGD("assignPointerIds - reduced distance min-heap: size=%d", heapSize);
+                    for (size_t j = 0; j < heapSize; j++) {
+                        ALOGD("  heap[%zu]: cur=%" PRIu32 ", last=%" PRIu32 ", distance=%" PRIu64,
+                              j, heap[j].currentPointerIndex, heap[j].lastPointerIndex,
+                              heap[j].distance);
+                    }
                 }
-#endif
             }
 
             heapSize -= 1;
@@ -3954,11 +3968,11 @@
                                                      currentPointerIndex));
             usedIdBits.markBit(id);
 
-#if DEBUG_POINTER_ASSIGNMENT
-            ALOGD("assignPointerIds - matched: cur=%" PRIu32 ", last=%" PRIu32 ", id=%" PRIu32
-                  ", distance=%" PRIu64,
-                  lastPointerIndex, currentPointerIndex, id, heap[0].distance);
-#endif
+            if (DEBUG_POINTER_ASSIGNMENT) {
+                ALOGD("assignPointerIds - matched: cur=%" PRIu32 ", last=%" PRIu32 ", id=%" PRIu32
+                      ", distance=%" PRIu64,
+                      lastPointerIndex, currentPointerIndex, id, heap[0].distance);
+            }
             break;
         }
     }
@@ -3973,9 +3987,10 @@
         current.rawPointerData.markIdBit(id,
                                          current.rawPointerData.isHovering(currentPointerIndex));
 
-#if DEBUG_POINTER_ASSIGNMENT
-        ALOGD("assignPointerIds - assigned: cur=%" PRIu32 ", id=%" PRIu32, currentPointerIndex, id);
-#endif
+        if (DEBUG_POINTER_ASSIGNMENT) {
+            ALOGD("assignPointerIds - assigned: cur=%" PRIu32 ", id=%" PRIu32, currentPointerIndex,
+                  id);
+        }
     }
 }
 
@@ -4031,63 +4046,4 @@
     return std::nullopt;
 }
 
-void TouchInputMapper::moveMouseCursor(float dx, float dy) const {
-    if (isPerWindowInputRotationEnabled()) {
-        // Convert from InputReader's un-rotated coordinate space to PointerController's coordinate
-        // space that is oriented with the viewport.
-        rotateDelta(mViewport.orientation, &dx, &dy);
-    }
-
-    mPointerController->move(dx, dy);
-}
-
-std::pair<float, float> TouchInputMapper::getMouseCursorPosition() const {
-    float x = 0;
-    float y = 0;
-    mPointerController->getPosition(&x, &y);
-
-    if (!isPerWindowInputRotationEnabled()) return {x, y};
-    if (!mViewport.isValid()) return {x, y};
-
-    // Convert from PointerController's rotated coordinate space that is oriented with the viewport
-    // to InputReader's un-rotated coordinate space.
-    const int32_t orientation = getInverseRotation(mViewport.orientation);
-    rotatePoint(orientation, x, y, mViewport.deviceWidth, mViewport.deviceHeight);
-    return {x, y};
-}
-
-void TouchInputMapper::setMouseCursorPosition(float x, float y) const {
-    if (isPerWindowInputRotationEnabled() && mViewport.isValid()) {
-        // Convert from InputReader's un-rotated coordinate space to PointerController's rotated
-        // coordinate space that is oriented with the viewport.
-        rotatePoint(mViewport.orientation, x, y, mRawSurfaceWidth, mRawSurfaceHeight);
-    }
-
-    mPointerController->setPosition(x, y);
-}
-
-void TouchInputMapper::setTouchSpots(const PointerCoords* spotCoords, const uint32_t* spotIdToIndex,
-                                     BitSet32 spotIdBits, int32_t displayId) {
-    std::array<PointerCoords, MAX_POINTERS> outSpotCoords{};
-
-    for (BitSet32 idBits(spotIdBits); !idBits.isEmpty();) {
-        const uint32_t index = spotIdToIndex[idBits.clearFirstMarkedBit()];
-        float x = spotCoords[index].getX();
-        float y = spotCoords[index].getY();
-        float pressure = spotCoords[index].getAxisValue(AMOTION_EVENT_AXIS_PRESSURE);
-
-        if (isPerWindowInputRotationEnabled()) {
-            // Convert from InputReader's un-rotated coordinate space to PointerController's rotated
-            // coordinate space.
-            rotatePoint(mViewport.orientation, x, y, mRawSurfaceWidth, mRawSurfaceHeight);
-        }
-
-        outSpotCoords[index].setAxisValue(AMOTION_EVENT_AXIS_X, x);
-        outSpotCoords[index].setAxisValue(AMOTION_EVENT_AXIS_Y, y);
-        outSpotCoords[index].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, pressure);
-    }
-
-    mPointerController->setSpots(outSpotCoords.data(), spotIdToIndex, spotIdBits, displayId);
-}
-
 } // namespace android
diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.h b/services/inputflinger/reader/mapper/TouchInputMapper.h
index e104220..c948f56 100644
--- a/services/inputflinger/reader/mapper/TouchInputMapper.h
+++ b/services/inputflinger/reader/mapper/TouchInputMapper.h
@@ -138,7 +138,7 @@
     explicit TouchInputMapper(InputDeviceContext& deviceContext);
     ~TouchInputMapper() override;
 
-    uint32_t getSources() override;
+    uint32_t getSources() const override;
     void populateDeviceInfo(InputDeviceInfo* deviceInfo) override;
     void dump(std::string& dump) override;
     void configure(nsecs_t when, const InputReaderConfiguration* config, uint32_t changes) override;
@@ -185,6 +185,8 @@
         UNSCALED,   // unscaled mapping (touchpad)
         NAVIGATION, // unscaled mapping with assist gesture (touch navigation)
         POINTER,    // pointer mapping (pointer)
+
+        ftl_last = POINTER
     };
     DeviceMode mDeviceMode;
 
@@ -198,6 +200,8 @@
             TOUCH_PAD,
             TOUCH_NAVIGATION,
             POINTER,
+
+            ftl_last = POINTER
         };
 
         DeviceType deviceType;
@@ -210,6 +214,8 @@
             ORIENTATION_90 = DISPLAY_ORIENTATION_90,
             ORIENTATION_180 = DISPLAY_ORIENTATION_180,
             ORIENTATION_270 = DISPLAY_ORIENTATION_270,
+
+            ftl_last = ORIENTATION_270
         };
         Orientation orientation;
 
@@ -219,6 +225,8 @@
         enum class GestureMode {
             SINGLE_TOUCH,
             MULTI_TOUCH,
+
+            ftl_last = MULTI_TOUCH
         };
         GestureMode gestureMode;
 
@@ -235,6 +243,7 @@
             DIAMETER,
             BOX,
             AREA,
+            ftl_last = AREA
         };
 
         SizeCalibration sizeCalibration;
@@ -398,8 +407,8 @@
     virtual void dumpParameters(std::string& dump);
     virtual void configureRawPointerAxes();
     virtual void dumpRawPointerAxes(std::string& dump);
-    virtual void configureSurface(nsecs_t when, bool* outResetNeeded);
-    virtual void dumpSurface(std::string& dump);
+    virtual void configureInputDevice(nsecs_t when, bool* outResetNeeded);
+    virtual void dumpDisplay(std::string& dump);
     virtual void configureVirtualKeys();
     virtual void dumpVirtualKeys(std::string& dump);
     virtual void parseCalibration();
@@ -418,38 +427,27 @@
     // The components of the viewport are specified in the display's rotated orientation.
     DisplayViewport mViewport;
 
-    // The surface orientation, width and height set by configureSurface().
-    // The width and height are derived from the viewport but are specified
+    // The width and height are obtained from the viewport and are specified
     // in the natural orientation.
-    // They could be used for calculating diagonal, scaling factors, and virtual keys.
-    int32_t mRawSurfaceWidth;
-    int32_t mRawSurfaceHeight;
+    int32_t mDisplayWidth;
+    int32_t mDisplayHeight;
 
-    // The surface origin specifies how the surface coordinates should be translated
-    // to align with the logical display coordinate space.
-    int32_t mSurfaceLeft;
-    int32_t mSurfaceTop;
-    int32_t mSurfaceRight;
-    int32_t mSurfaceBottom;
-
-    // Similar to the surface coordinates, but in the raw display coordinate space rather than in
-    // the logical coordinate space.
+    // The physical frame is the rectangle in the display's coordinate space that maps to the
+    // the logical display frame.
     int32_t mPhysicalWidth;
     int32_t mPhysicalHeight;
     int32_t mPhysicalLeft;
     int32_t mPhysicalTop;
 
-    // The orientation may be different from the viewport orientation as it specifies
-    // the rotation of the surface coordinates required to produce the viewport's
-    // requested orientation, so it will depend on whether the device is orientation aware.
-    int32_t mSurfaceOrientation;
+    // 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;
 
     // Translation and scaling factors, orientation-independent.
-    float mXTranslate;
     float mXScale;
     float mXPrecision;
 
-    float mYTranslate;
     float mYScale;
     float mYPrecision;
 
@@ -735,6 +733,10 @@
     void resetExternalStylus();
     void clearStylusDataPendingFlags();
 
+    int32_t clampResolution(const char* axisName, int32_t resolution) const;
+    void initializeOrientedRanges();
+    void initializeSizeRanges();
+
     void sync(nsecs_t when, nsecs_t readTime);
 
     bool consumeRawTouches(nsecs_t when, nsecs_t readTime, uint32_t policyFlags);
@@ -799,23 +801,15 @@
     // touchscreen.
     void updateTouchSpots();
 
-    bool isPointInsideSurface(int32_t x, int32_t y);
+    bool isPointInsidePhysicalFrame(int32_t x, int32_t y) const;
     const VirtualKey* findVirtualKeyHit(int32_t x, int32_t y);
 
     static void assignPointerIds(const RawState& last, RawState& current);
 
     const char* modeToString(DeviceMode deviceMode);
-    void rotateAndScale(float& x, float& y);
-
-    // Wrapper methods for interfacing with PointerController. These are used to convert points
-    // between the coordinate spaces used by InputReader and PointerController, if they differ.
-    void moveMouseCursor(float dx, float dy) const;
-    std::pair<float, float> getMouseCursorPosition() const;
-    void setMouseCursorPosition(float x, float y) const;
-    void setTouchSpots(const PointerCoords* spotCoords, const uint32_t* spotIdToIndex,
-                       BitSet32 spotIdBits, int32_t displayId);
+    void rotateAndScale(float& x, float& y) const;
 };
 
 } // namespace android
 
-#endif // _UI_INPUTREADER_TOUCH_INPUT_MAPPER_H
\ No newline at end of file
+#endif // _UI_INPUTREADER_TOUCH_INPUT_MAPPER_H
diff --git a/services/inputflinger/reader/mapper/VibratorInputMapper.cpp b/services/inputflinger/reader/mapper/VibratorInputMapper.cpp
index 3df6f36..33db527 100644
--- a/services/inputflinger/reader/mapper/VibratorInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/VibratorInputMapper.cpp
@@ -25,7 +25,7 @@
 
 VibratorInputMapper::~VibratorInputMapper() {}
 
-uint32_t VibratorInputMapper::getSources() {
+uint32_t VibratorInputMapper::getSources() const {
     return 0;
 }
 
@@ -41,10 +41,10 @@
 
 void VibratorInputMapper::vibrate(const VibrationSequence& sequence, ssize_t repeat,
                                   int32_t token) {
-#if DEBUG_VIBRATOR
-    ALOGD("vibrate: deviceId=%d, pattern=[%s], repeat=%zd, token=%d", getDeviceId(),
-          sequence.toString().c_str(), repeat, token);
-#endif
+    if (DEBUG_VIBRATOR) {
+        ALOGD("vibrate: deviceId=%d, pattern=[%s], repeat=%zd, token=%d", getDeviceId(),
+              sequence.toString().c_str(), repeat, token);
+    }
 
     mVibrating = true;
     mSequence = sequence;
@@ -54,14 +54,14 @@
 
     // Request InputReader to notify InputManagerService for vibration started.
     NotifyVibratorStateArgs args(getContext()->getNextId(), systemTime(), getDeviceId(), true);
-    getListener()->notifyVibratorState(&args);
+    getListener().notifyVibratorState(&args);
     nextStep();
 }
 
 void VibratorInputMapper::cancelVibrate(int32_t token) {
-#if DEBUG_VIBRATOR
-    ALOGD("cancelVibrate: deviceId=%d, token=%d", getDeviceId(), token);
-#endif
+    if (DEBUG_VIBRATOR) {
+        ALOGD("cancelVibrate: deviceId=%d, token=%d", getDeviceId(), token);
+    }
 
     if (mVibrating && mToken == token) {
         stopVibrating();
@@ -87,9 +87,9 @@
 }
 
 void VibratorInputMapper::nextStep() {
-#if DEBUG_VIBRATOR
-    ALOGD("nextStep: index=%d, vibrate deviceId=%d", (int)mIndex, getDeviceId());
-#endif
+    if (DEBUG_VIBRATOR) {
+        ALOGD("nextStep: index=%d, vibrate deviceId=%d", (int)mIndex, getDeviceId());
+    }
     mIndex += 1;
     if (size_t(mIndex) >= mSequence.pattern.size()) {
         if (mRepeat < 0) {
@@ -102,16 +102,16 @@
 
     const VibrationElement& element = mSequence.pattern[mIndex];
     if (element.isOn()) {
-#if DEBUG_VIBRATOR
-        std::string description = element.toString();
-        ALOGD("nextStep: sending vibrate deviceId=%d, element=%s", getDeviceId(),
-              description.c_str());
-#endif
+        if (DEBUG_VIBRATOR) {
+            std::string description = element.toString();
+            ALOGD("nextStep: sending vibrate deviceId=%d, element=%s", getDeviceId(),
+                  description.c_str());
+        }
         getDeviceContext().vibrate(element);
     } else {
-#if DEBUG_VIBRATOR
-        ALOGD("nextStep: sending cancel vibrate deviceId=%d", getDeviceId());
-#endif
+        if (DEBUG_VIBRATOR) {
+            ALOGD("nextStep: sending cancel vibrate deviceId=%d", getDeviceId());
+        }
         getDeviceContext().cancelVibrate();
     }
     nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
@@ -119,21 +119,21 @@
             std::chrono::duration_cast<std::chrono::nanoseconds>(element.duration);
     mNextStepTime = now + duration.count();
     getContext()->requestTimeoutAtTime(mNextStepTime);
-#if DEBUG_VIBRATOR
-    ALOGD("nextStep: scheduled timeout in %lldms", element.duration.count());
-#endif
+    if (DEBUG_VIBRATOR) {
+        ALOGD("nextStep: scheduled timeout in %lldms", element.duration.count());
+    }
 }
 
 void VibratorInputMapper::stopVibrating() {
     mVibrating = false;
-#if DEBUG_VIBRATOR
-    ALOGD("stopVibrating: sending cancel vibrate deviceId=%d", getDeviceId());
-#endif
+    if (DEBUG_VIBRATOR) {
+        ALOGD("stopVibrating: sending cancel vibrate deviceId=%d", getDeviceId());
+    }
     getDeviceContext().cancelVibrate();
 
     // Request InputReader to notify InputManagerService for vibration complete.
     NotifyVibratorStateArgs args(getContext()->getNextId(), systemTime(), getDeviceId(), false);
-    getListener()->notifyVibratorState(&args);
+    getListener().notifyVibratorState(&args);
 }
 
 void VibratorInputMapper::dump(std::string& dump) {
diff --git a/services/inputflinger/reader/mapper/VibratorInputMapper.h b/services/inputflinger/reader/mapper/VibratorInputMapper.h
index 7ce621a..d3c22b6 100644
--- a/services/inputflinger/reader/mapper/VibratorInputMapper.h
+++ b/services/inputflinger/reader/mapper/VibratorInputMapper.h
@@ -26,7 +26,7 @@
     explicit VibratorInputMapper(InputDeviceContext& deviceContext);
     virtual ~VibratorInputMapper();
 
-    virtual uint32_t getSources() override;
+    virtual uint32_t getSources() const override;
     virtual void populateDeviceInfo(InputDeviceInfo* deviceInfo) override;
     virtual void process(const RawEvent* rawEvent) override;
 
diff --git a/services/inputflinger/sysprop/Android.bp b/services/inputflinger/sysprop/Android.bp
deleted file mode 100644
index b9d65ee..0000000
--- a/services/inputflinger/sysprop/Android.bp
+++ /dev/null
@@ -1,15 +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"],
-}
-
-sysprop_library {
-    name: "InputFlingerProperties",
-    srcs: ["*.sysprop"],
-    api_packages: ["android.sysprop"],
-    property_owner: "Platform",
-}
diff --git a/services/inputflinger/sysprop/InputFlingerProperties.sysprop b/services/inputflinger/sysprop/InputFlingerProperties.sysprop
deleted file mode 100644
index 1c7e724..0000000
--- a/services/inputflinger/sysprop/InputFlingerProperties.sysprop
+++ /dev/null
@@ -1,27 +0,0 @@
-# Copyright (C) 2021 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the License);
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an AS IS BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-module: "android.sysprop.InputFlingerProperties"
-owner: Platform
-
-# When per-window-input-rotation is enabled, InputReader works in the un-rotated
-# display coordinate space, and the display rotation is encoded as part of the
-# input window transform that is sent from SurfaceFlinger to InputDispatcher.
-prop {
-    api_name: "per_window_input_rotation"
-    type: Boolean
-    scope: Internal
-    access: ReadWrite
-    prop_name: "persist.debug.per_window_input_rotation"
-}
diff --git a/services/inputflinger/tests/Android.bp b/services/inputflinger/tests/Android.bp
index e686924..9d200bd 100644
--- a/services/inputflinger/tests/Android.bp
+++ b/services/inputflinger/tests/Android.bp
@@ -49,6 +49,7 @@
         "LatencyTracker_test.cpp",
         "TestInputListener.cpp",
         "UinputDevice.cpp",
+        "UnwantedInteractionBlocker_test.cpp",
     ],
     aidl: {
         include_dirs: [
@@ -57,7 +58,7 @@
         ],
     },
     static_libs: [
-        "libc++fs"
+        "libc++fs",
     ],
     require_root: true,
     test_suites: ["device-tests"],
diff --git a/services/inputflinger/tests/FocusResolver_test.cpp b/services/inputflinger/tests/FocusResolver_test.cpp
index 662be80..91be4a3 100644
--- a/services/inputflinger/tests/FocusResolver_test.cpp
+++ b/services/inputflinger/tests/FocusResolver_test.cpp
@@ -37,12 +37,16 @@
                      bool visible) {
         mInfo.token = token;
         mInfo.name = name;
-        mInfo.visible = visible;
-        mInfo.focusable = focusable;
+        setFocusable(focusable);
+        setVisible(visible);
     }
 
-    void setFocusable(bool focusable) { mInfo.focusable = focusable; }
-    void setVisible(bool visible) { mInfo.visible = visible; }
+    void setFocusable(bool focusable) {
+        mInfo.setInputConfig(gui::WindowInfo::InputConfig::NOT_FOCUSABLE, !focusable);
+    }
+    void setVisible(bool visible) {
+        mInfo.setInputConfig(gui::WindowInfo::InputConfig::NOT_VISIBLE, !visible);
+    }
 };
 
 TEST(FocusResolverTest, SetFocusedWindow) {
@@ -80,6 +84,30 @@
     ASSERT_FALSE(changes);
 }
 
+TEST(FocusResolverTest, RemoveFocusFromFocusedWindow) {
+    sp<IBinder> focusableWindowToken = new BBinder();
+    std::vector<sp<WindowInfoHandle>> windows;
+    windows.push_back(new FakeWindowHandle("Focusable", focusableWindowToken, true /* focusable */,
+                                           true /* visible */));
+
+    FocusRequest request;
+    request.displayId = 42;
+    request.token = focusableWindowToken;
+    FocusResolver focusResolver;
+    // Focusable window gets focus.
+    request.token = focusableWindowToken;
+    std::optional<FocusResolver::FocusChanges> changes =
+            focusResolver.setFocusedWindow(request, windows);
+    ASSERT_FOCUS_CHANGE(changes, nullptr, focusableWindowToken);
+
+    // Window token of a request is null, focus should be revoked.
+    request.token = NULL;
+    changes = focusResolver.setFocusedWindow(request, windows);
+    ASSERT_EQ(focusableWindowToken, changes->oldFocus);
+    ASSERT_EQ(nullptr, changes->newFocus);
+    ASSERT_FOCUS_CHANGE(changes, focusableWindowToken, nullptr);
+}
+
 TEST(FocusResolverTest, SetFocusedMirroredWindow) {
     sp<IBinder> focusableWindowToken = new BBinder();
     sp<IBinder> invisibleWindowToken = new BBinder();
diff --git a/services/inputflinger/tests/InputClassifierConverter_test.cpp b/services/inputflinger/tests/InputClassifierConverter_test.cpp
index f626d56..81ef9b9 100644
--- a/services/inputflinger/tests/InputClassifierConverter_test.cpp
+++ b/services/inputflinger/tests/InputClassifierConverter_test.cpp
@@ -14,13 +14,13 @@
  * limitations under the License.
  */
 
-#include "../InputClassifierConverter.h"
+#include "../InputCommonConverter.h"
 
 #include <gtest/gtest.h>
 #include <gui/constants.h>
 #include <utils/BitSet.h>
 
-using namespace android::hardware::input;
+using namespace aidl::android::hardware::input;
 
 namespace android {
 
@@ -50,8 +50,7 @@
     return motionArgs;
 }
 
-static float getMotionEventAxis(common::V1_0::PointerCoords coords,
-        common::V1_0::Axis axis) {
+static float getMotionEventAxis(common::PointerCoords coords, common::Axis axis) {
     uint32_t index = BitSet64::getIndexOfBit(static_cast<uint64_t>(coords.bits),
             static_cast<uint64_t>(axis));
     return coords.values[index];
@@ -68,14 +67,14 @@
     ASSERT_EQ(0.5, motionArgs.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_SIZE));
     ASSERT_EQ(3U, BitSet64::count(motionArgs.pointerCoords[0].bits));
 
-    common::V1_0::MotionEvent motionEvent = notifyMotionArgsToHalMotionEvent(motionArgs);
+    common::MotionEvent motionEvent = notifyMotionArgsToHalMotionEvent(motionArgs);
 
-    ASSERT_EQ(getMotionEventAxis(motionEvent.pointerCoords[0], common::V1_0::Axis::X),
-            motionArgs.pointerCoords[0].getX());
-    ASSERT_EQ(getMotionEventAxis(motionEvent.pointerCoords[0], common::V1_0::Axis::Y),
-            motionArgs.pointerCoords[0].getY());
-    ASSERT_EQ(getMotionEventAxis(motionEvent.pointerCoords[0], common::V1_0::Axis::SIZE),
-            motionArgs.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_SIZE));
+    ASSERT_EQ(getMotionEventAxis(motionEvent.pointerCoords[0], common::Axis::X),
+              motionArgs.pointerCoords[0].getX());
+    ASSERT_EQ(getMotionEventAxis(motionEvent.pointerCoords[0], common::Axis::Y),
+              motionArgs.pointerCoords[0].getY());
+    ASSERT_EQ(getMotionEventAxis(motionEvent.pointerCoords[0], common::Axis::SIZE),
+              motionArgs.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_SIZE));
     ASSERT_EQ(BitSet64::count(motionArgs.pointerCoords[0].bits),
             BitSet64::count(motionEvent.pointerCoords[0].bits));
 }
diff --git a/services/inputflinger/tests/InputClassifier_test.cpp b/services/inputflinger/tests/InputClassifier_test.cpp
index 3a9994e..3a77127 100644
--- a/services/inputflinger/tests/InputClassifier_test.cpp
+++ b/services/inputflinger/tests/InputClassifier_test.cpp
@@ -20,12 +20,14 @@
 
 #include "TestInputListener.h"
 
-#include <android/hardware/input/classifier/1.0/IInputClassifier.h>
+#include <aidl/android/hardware/input/processor/BnInputProcessor.h>
+#include <aidl/android/hardware/input/processor/IInputProcessor.h>
+#include <android/binder_manager.h>
+#include <android/binder_process.h>
 
-using namespace android::hardware::input;
-using android::hardware::Return;
-using android::hardware::Void;
-using android::hardware::input::common::V1_0::Classification;
+using namespace aidl::android::hardware::input;
+using aidl::android::hardware::input::common::Classification;
+using aidl::android::hardware::input::processor::IInputProcessor;
 
 namespace android {
 
@@ -56,18 +58,10 @@
 
 class InputClassifierTest : public testing::Test {
 protected:
-    sp<InputClassifierInterface> mClassifier;
-    sp<TestInputListener> mTestListener;
+    TestInputListener mTestListener;
+    std::unique_ptr<InputClassifierInterface> mClassifier;
 
-    virtual void SetUp() override {
-        mTestListener = new TestInputListener();
-        mClassifier = new InputClassifier(mTestListener);
-    }
-
-    virtual void TearDown() override {
-        mClassifier.clear();
-        mTestListener.clear();
-    }
+    void SetUp() override { mClassifier = std::make_unique<InputClassifier>(mTestListener); }
 };
 
 /**
@@ -80,7 +74,7 @@
 
     mClassifier->notifyConfigurationChanged(&args);
     NotifyConfigurationChangedArgs outArgs;
-    ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyConfigurationChangedWasCalled(&outArgs));
+    ASSERT_NO_FATAL_FAILURE(mTestListener.assertNotifyConfigurationChangedWasCalled(&outArgs));
     ASSERT_EQ(args, outArgs);
 }
 
@@ -93,7 +87,7 @@
 
     mClassifier->notifyKey(&args);
     NotifyKeyArgs outArgs;
-    ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyKeyWasCalled(&outArgs));
+    ASSERT_NO_FATAL_FAILURE(mTestListener.assertNotifyKeyWasCalled(&outArgs));
     ASSERT_EQ(args, outArgs);
 }
 
@@ -106,7 +100,7 @@
     NotifyMotionArgs motionArgs = generateBasicMotionArgs();
     mClassifier->notifyMotion(&motionArgs);
     NotifyMotionArgs args;
-    ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(&args));
+    ASSERT_NO_FATAL_FAILURE(mTestListener.assertNotifyMotionWasCalled(&args));
     ASSERT_EQ(motionArgs, args);
 }
 
@@ -120,7 +114,7 @@
 
     mClassifier->notifySwitch(&args);
     NotifySwitchArgs outArgs;
-    ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifySwitchWasCalled(&outArgs));
+    ASSERT_NO_FATAL_FAILURE(mTestListener.assertNotifySwitchWasCalled(&outArgs));
     ASSERT_EQ(args, outArgs);
 }
 
@@ -133,7 +127,7 @@
 
     mClassifier->notifyDeviceReset(&args);
     NotifyDeviceResetArgs outArgs;
-    ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyDeviceResetWasCalled(&outArgs));
+    ASSERT_NO_FATAL_FAILURE(mTestListener.assertNotifyDeviceResetWasCalled(&outArgs));
     ASSERT_EQ(args, outArgs);
 }
 
@@ -162,22 +156,17 @@
 /**
  * A minimal implementation of IInputClassifier.
  */
-struct TestHal : public android::hardware::input::classifier::V1_0::IInputClassifier {
-    Return<Classification> classify(
-            const android::hardware::input::common::V1_0::MotionEvent& event) override {
-        return Classification::NONE;
-    };
-    Return<void> reset() override { return Void(); };
-    Return<void> resetDevice(int32_t deviceId) override { return Void(); };
-};
-
-/**
- * An entity that will be subscribed to the HAL death.
- */
-class TestDeathRecipient : public android::hardware::hidl_death_recipient {
-public:
-    virtual void serviceDied(uint64_t cookie,
-                             const wp<android::hidl::base::V1_0::IBase>& who) override{};
+class TestHal : public aidl::android::hardware::input::processor::BnInputProcessor {
+    ::ndk::ScopedAStatus classify(
+            const ::aidl::android::hardware::input::common::MotionEvent& in_event,
+            ::aidl::android::hardware::input::common::Classification* _aidl_return) override {
+        *_aidl_return = Classification::NONE;
+        return ndk::ScopedAStatus::ok();
+    }
+    ::ndk::ScopedAStatus reset() override { return ndk::ScopedAStatus::ok(); }
+    ::ndk::ScopedAStatus resetDevice(int32_t in_deviceId) override {
+        return ndk::ScopedAStatus::ok();
+    }
 };
 
 // --- MotionClassifierTest ---
@@ -186,15 +175,9 @@
 protected:
     std::unique_ptr<MotionClassifierInterface> mMotionClassifier;
 
-    virtual void SetUp() override {
-        mMotionClassifier = MotionClassifier::create(new TestDeathRecipient());
-        if (mMotionClassifier == nullptr) {
-            // If the device running this test does not have IInputClassifier service,
-            // use the test HAL instead.
-            // Using 'new' to access non-public constructor
-            mMotionClassifier =
-                    std::unique_ptr<MotionClassifier>(new MotionClassifier(new TestHal()));
-        }
+    void SetUp() override {
+        std::shared_ptr<IInputProcessor> service = ndk::SharedRefBase::make<TestHal>();
+        mMotionClassifier = MotionClassifier::create(std::move(service));
     }
 };
 
diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp
index ee4d3b8..bf58705 100644
--- a/services/inputflinger/tests/InputDispatcher_test.cpp
+++ b/services/inputflinger/tests/InputDispatcher_test.cpp
@@ -16,12 +16,16 @@
 
 #include "../dispatcher/InputDispatcher.h"
 
+#include <android-base/properties.h>
+#include <android-base/silent_death_test.h>
 #include <android-base/stringprintf.h>
 #include <android-base/thread_annotations.h>
 #include <binder/Binder.h>
+#include <fcntl.h>
 #include <gtest/gtest.h>
 #include <input/Input.h>
 #include <linux/input.h>
+#include <sys/epoll.h>
 
 #include <cinttypes>
 #include <thread>
@@ -49,6 +53,11 @@
 static constexpr int32_t DISPLAY_ID = ADISPLAY_ID_DEFAULT;
 static constexpr int32_t SECOND_DISPLAY_ID = 1;
 
+static constexpr int32_t POINTER_1_DOWN =
+        AMOTION_EVENT_ACTION_POINTER_DOWN | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
+static constexpr int32_t POINTER_1_UP =
+        AMOTION_EVENT_ACTION_POINTER_UP | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
+
 // An arbitrary injector pid / uid pair that has permission to inject events.
 static const int32_t INJECTOR_PID = 999;
 static const int32_t INJECTOR_UID = 1001;
@@ -56,6 +65,8 @@
 // An arbitrary pid of the gesture monitor window
 static constexpr int32_t MONITOR_PID = 2001;
 
+static constexpr std::chrono::duration STALE_EVENT_TIMEOUT = 1000ms;
+
 struct PointF {
     float x;
     float y;
@@ -73,11 +84,19 @@
     return event;
 }
 
+static void assertMotionAction(int32_t expectedAction, int32_t receivedAction) {
+    ASSERT_EQ(expectedAction, receivedAction)
+            << "expected " << MotionEvent::actionToString(expectedAction) << ", got "
+            << MotionEvent::actionToString(receivedAction);
+}
+
 // --- FakeInputDispatcherPolicy ---
 
 class FakeInputDispatcherPolicy : public InputDispatcherPolicyInterface {
     InputDispatcherConfiguration mConfig;
 
+    using AnrResult = std::pair<sp<IBinder>, int32_t /*pid*/>;
+
 protected:
     virtual ~FakeInputDispatcherPolicy() {}
 
@@ -85,13 +104,29 @@
     FakeInputDispatcherPolicy() {}
 
     void assertFilterInputEventWasCalled(const NotifyKeyArgs& args) {
-        assertFilterInputEventWasCalled(AINPUT_EVENT_TYPE_KEY, args.eventTime, args.action,
-                                        args.displayId);
+        assertFilterInputEventWasCalledInternal([&args](const InputEvent& event) {
+            ASSERT_EQ(event.getType(), AINPUT_EVENT_TYPE_KEY);
+            EXPECT_EQ(event.getDisplayId(), args.displayId);
+
+            const auto& keyEvent = static_cast<const KeyEvent&>(event);
+            EXPECT_EQ(keyEvent.getEventTime(), args.eventTime);
+            EXPECT_EQ(keyEvent.getAction(), args.action);
+        });
     }
 
-    void assertFilterInputEventWasCalled(const NotifyMotionArgs& args) {
-        assertFilterInputEventWasCalled(AINPUT_EVENT_TYPE_MOTION, args.eventTime, args.action,
-                                        args.displayId);
+    void assertFilterInputEventWasCalled(const NotifyMotionArgs& args, vec2 point) {
+        assertFilterInputEventWasCalledInternal([&](const InputEvent& event) {
+            ASSERT_EQ(event.getType(), AINPUT_EVENT_TYPE_MOTION);
+            EXPECT_EQ(event.getDisplayId(), args.displayId);
+
+            const auto& motionEvent = static_cast<const MotionEvent&>(event);
+            EXPECT_EQ(motionEvent.getEventTime(), args.eventTime);
+            EXPECT_EQ(motionEvent.getAction(), args.action);
+            EXPECT_EQ(motionEvent.getX(0), point.x);
+            EXPECT_EQ(motionEvent.getY(0), point.y);
+            EXPECT_EQ(motionEvent.getRawX(0), point.x);
+            EXPECT_EQ(motionEvent.getRawY(0), point.y);
+        });
     }
 
     void assertFilterInputEventWasNotCalled() {
@@ -135,107 +170,71 @@
     void assertNotifyNoFocusedWindowAnrWasCalled(
             std::chrono::nanoseconds timeout,
             const std::shared_ptr<InputApplicationHandle>& expectedApplication) {
+        std::unique_lock lock(mLock);
+        android::base::ScopedLockAssertion assumeLocked(mLock);
         std::shared_ptr<InputApplicationHandle> application;
-        { // acquire lock
-            std::unique_lock lock(mLock);
-            android::base::ScopedLockAssertion assumeLocked(mLock);
-            ASSERT_NO_FATAL_FAILURE(
-                    application = getAnrTokenLockedInterruptible(timeout, mAnrApplications, lock));
-        } // release lock
+        ASSERT_NO_FATAL_FAILURE(
+                application = getAnrTokenLockedInterruptible(timeout, mAnrApplications, lock));
         ASSERT_EQ(expectedApplication, application);
     }
 
     void assertNotifyWindowUnresponsiveWasCalled(std::chrono::nanoseconds timeout,
-                                                 const sp<IBinder>& expectedConnectionToken) {
-        sp<IBinder> connectionToken = getUnresponsiveWindowToken(timeout);
-        ASSERT_EQ(expectedConnectionToken, connectionToken);
+                                                 const sp<WindowInfoHandle>& window) {
+        LOG_ALWAYS_FATAL_IF(window == nullptr, "window should not be null");
+        assertNotifyWindowUnresponsiveWasCalled(timeout, window->getToken(),
+                                                window->getInfo()->ownerPid);
     }
 
-    void assertNotifyWindowResponsiveWasCalled(const sp<IBinder>& expectedConnectionToken) {
-        sp<IBinder> connectionToken = getResponsiveWindowToken();
-        ASSERT_EQ(expectedConnectionToken, connectionToken);
+    void assertNotifyWindowUnresponsiveWasCalled(std::chrono::nanoseconds timeout,
+                                                 const sp<IBinder>& expectedToken,
+                                                 int32_t expectedPid) {
+        std::unique_lock lock(mLock);
+        android::base::ScopedLockAssertion assumeLocked(mLock);
+        AnrResult result;
+        ASSERT_NO_FATAL_FAILURE(result =
+                                        getAnrTokenLockedInterruptible(timeout, mAnrWindows, lock));
+        const auto& [token, pid] = result;
+        ASSERT_EQ(expectedToken, token);
+        ASSERT_EQ(expectedPid, pid);
     }
 
-    void assertNotifyMonitorUnresponsiveWasCalled(std::chrono::nanoseconds timeout) {
-        int32_t pid = getUnresponsiveMonitorPid(timeout);
-        ASSERT_EQ(MONITOR_PID, pid);
-    }
-
-    void assertNotifyMonitorResponsiveWasCalled() {
-        int32_t pid = getResponsiveMonitorPid();
-        ASSERT_EQ(MONITOR_PID, pid);
-    }
-
+    /** Wrap call with ASSERT_NO_FATAL_FAILURE() to ensure the return value is valid. */
     sp<IBinder> getUnresponsiveWindowToken(std::chrono::nanoseconds timeout) {
         std::unique_lock lock(mLock);
         android::base::ScopedLockAssertion assumeLocked(mLock);
-        return getAnrTokenLockedInterruptible(timeout, mAnrWindowTokens, lock);
+        AnrResult result = getAnrTokenLockedInterruptible(timeout, mAnrWindows, lock);
+        const auto& [token, _] = result;
+        return token;
     }
 
+    void assertNotifyWindowResponsiveWasCalled(const sp<IBinder>& expectedToken,
+                                               int32_t expectedPid) {
+        std::unique_lock lock(mLock);
+        android::base::ScopedLockAssertion assumeLocked(mLock);
+        AnrResult result;
+        ASSERT_NO_FATAL_FAILURE(
+                result = getAnrTokenLockedInterruptible(0s, mResponsiveWindows, lock));
+        const auto& [token, pid] = result;
+        ASSERT_EQ(expectedToken, token);
+        ASSERT_EQ(expectedPid, pid);
+    }
+
+    /** Wrap call with ASSERT_NO_FATAL_FAILURE() to ensure the return value is valid. */
     sp<IBinder> getResponsiveWindowToken() {
         std::unique_lock lock(mLock);
         android::base::ScopedLockAssertion assumeLocked(mLock);
-        return getAnrTokenLockedInterruptible(0s, mResponsiveWindowTokens, lock);
-    }
-
-    int32_t getUnresponsiveMonitorPid(std::chrono::nanoseconds timeout) {
-        std::unique_lock lock(mLock);
-        android::base::ScopedLockAssertion assumeLocked(mLock);
-        return getAnrTokenLockedInterruptible(timeout, mAnrMonitorPids, lock);
-    }
-
-    int32_t getResponsiveMonitorPid() {
-        std::unique_lock lock(mLock);
-        android::base::ScopedLockAssertion assumeLocked(mLock);
-        return getAnrTokenLockedInterruptible(0s, mResponsiveMonitorPids, lock);
-    }
-
-    // All three ANR-related callbacks behave the same way, so we use this generic function to wait
-    // for a specific container to become non-empty. When the container is non-empty, return the
-    // first entry from the container and erase it.
-    template <class T>
-    T getAnrTokenLockedInterruptible(std::chrono::nanoseconds timeout, std::queue<T>& storage,
-                                     std::unique_lock<std::mutex>& lock) REQUIRES(mLock) {
-        const std::chrono::time_point start = std::chrono::steady_clock::now();
-        std::chrono::duration timeToWait = timeout + 100ms; // provide some slack
-
-        // If there is an ANR, Dispatcher won't be idle because there are still events
-        // in the waitQueue that we need to check on. So we can't wait for dispatcher to be idle
-        // before checking if ANR was called.
-        // Since dispatcher is not guaranteed to call notifyNoFocusedWindowAnr right away, we need
-        // to provide it some time to act. 100ms seems reasonable.
-        mNotifyAnr.wait_for(lock, timeToWait,
-                            [&storage]() REQUIRES(mLock) { return !storage.empty(); });
-        const std::chrono::duration waited = std::chrono::steady_clock::now() - start;
-        if (storage.empty()) {
-            ADD_FAILURE() << "Did not receive the ANR callback";
-            return {};
-        }
-        // Ensure that the ANR didn't get raised too early. We can't be too strict here because
-        // the dispatcher started counting before this function was called
-        if (std::chrono::abs(timeout - waited) > 100ms) {
-            ADD_FAILURE() << "ANR was raised too early or too late. Expected "
-                          << std::chrono::duration_cast<std::chrono::milliseconds>(timeout).count()
-                          << "ms, but waited "
-                          << std::chrono::duration_cast<std::chrono::milliseconds>(waited).count()
-                          << "ms instead";
-        }
-        T token = storage.front();
-        storage.pop();
+        AnrResult result = getAnrTokenLockedInterruptible(0s, mResponsiveWindows, lock);
+        const auto& [token, _] = result;
         return token;
     }
 
     void assertNotifyAnrWasNotCalled() {
         std::scoped_lock lock(mLock);
         ASSERT_TRUE(mAnrApplications.empty());
-        ASSERT_TRUE(mAnrWindowTokens.empty());
-        ASSERT_TRUE(mAnrMonitorPids.empty());
-        ASSERT_TRUE(mResponsiveWindowTokens.empty())
+        ASSERT_TRUE(mAnrWindows.empty());
+        ASSERT_TRUE(mResponsiveWindows.empty())
                 << "ANR was not called, but please also consume the 'connection is responsive' "
                    "signal";
-        ASSERT_TRUE(mResponsiveMonitorPids.empty())
-                << "Monitor ANR was not called, but please also consume the 'monitor is responsive'"
-                   " signal";
     }
 
     void setKeyRepeatConfiguration(nsecs_t timeout, nsecs_t delay) {
@@ -280,6 +279,23 @@
         mNotifyDropWindowWasCalled = false;
     }
 
+    void assertNotifyInputChannelBrokenWasCalled(const sp<IBinder>& token) {
+        std::unique_lock lock(mLock);
+        base::ScopedLockAssertion assumeLocked(mLock);
+        std::optional<sp<IBinder>> receivedToken =
+                getItemFromStorageLockedInterruptible(100ms, mBrokenInputChannels, lock,
+                                                      mNotifyInputChannelBroken);
+        ASSERT_TRUE(receivedToken.has_value());
+        ASSERT_EQ(token, *receivedToken);
+    }
+
+    /**
+     * Set policy timeout. A value of zero means next key will not be intercepted.
+     */
+    void setInterceptKeyTimeout(std::chrono::milliseconds timeout) {
+        mInterceptKeyTimeout = timeout;
+    }
+
 private:
     std::mutex mLock;
     std::unique_ptr<InputEvent> mFilteredEvent GUARDED_BY(mLock);
@@ -293,41 +309,85 @@
 
     // ANR handling
     std::queue<std::shared_ptr<InputApplicationHandle>> mAnrApplications GUARDED_BY(mLock);
-    std::queue<sp<IBinder>> mAnrWindowTokens GUARDED_BY(mLock);
-    std::queue<sp<IBinder>> mResponsiveWindowTokens GUARDED_BY(mLock);
-    std::queue<int32_t> mAnrMonitorPids GUARDED_BY(mLock);
-    std::queue<int32_t> mResponsiveMonitorPids GUARDED_BY(mLock);
+    std::queue<AnrResult> mAnrWindows GUARDED_BY(mLock);
+    std::queue<AnrResult> mResponsiveWindows GUARDED_BY(mLock);
     std::condition_variable mNotifyAnr;
+    std::queue<sp<IBinder>> mBrokenInputChannels GUARDED_BY(mLock);
+    std::condition_variable mNotifyInputChannelBroken;
 
     sp<IBinder> mDropTargetWindowToken GUARDED_BY(mLock);
     bool mNotifyDropWindowWasCalled GUARDED_BY(mLock) = false;
 
+    std::chrono::milliseconds mInterceptKeyTimeout = 0ms;
+
+    // All three ANR-related callbacks behave the same way, so we use this generic function to wait
+    // for a specific container to become non-empty. When the container is non-empty, return the
+    // first entry from the container and erase it.
+    template <class T>
+    T getAnrTokenLockedInterruptible(std::chrono::nanoseconds timeout, std::queue<T>& storage,
+                                     std::unique_lock<std::mutex>& lock) REQUIRES(mLock) {
+        // If there is an ANR, Dispatcher won't be idle because there are still events
+        // in the waitQueue that we need to check on. So we can't wait for dispatcher to be idle
+        // before checking if ANR was called.
+        // Since dispatcher is not guaranteed to call notifyNoFocusedWindowAnr right away, we need
+        // to provide it some time to act. 100ms seems reasonable.
+        std::chrono::duration timeToWait = timeout + 100ms; // provide some slack
+        const std::chrono::time_point start = std::chrono::steady_clock::now();
+        std::optional<T> token =
+                getItemFromStorageLockedInterruptible(timeToWait, storage, lock, mNotifyAnr);
+        if (!token.has_value()) {
+            ADD_FAILURE() << "Did not receive the ANR callback";
+            return {};
+        }
+
+        const std::chrono::duration waited = std::chrono::steady_clock::now() - start;
+        // Ensure that the ANR didn't get raised too early. We can't be too strict here because
+        // the dispatcher started counting before this function was called
+        if (std::chrono::abs(timeout - waited) > 100ms) {
+            ADD_FAILURE() << "ANR was raised too early or too late. Expected "
+                          << std::chrono::duration_cast<std::chrono::milliseconds>(timeout).count()
+                          << "ms, but waited "
+                          << std::chrono::duration_cast<std::chrono::milliseconds>(waited).count()
+                          << "ms instead";
+        }
+        return *token;
+    }
+
+    template <class T>
+    std::optional<T> getItemFromStorageLockedInterruptible(std::chrono::nanoseconds timeout,
+                                                           std::queue<T>& storage,
+                                                           std::unique_lock<std::mutex>& lock,
+                                                           std::condition_variable& condition)
+            REQUIRES(mLock) {
+        condition.wait_for(lock, timeout,
+                           [&storage]() REQUIRES(mLock) { return !storage.empty(); });
+        if (storage.empty()) {
+            ADD_FAILURE() << "Did not receive the expected callback";
+            return std::nullopt;
+        }
+        T item = storage.front();
+        storage.pop();
+        return std::make_optional(item);
+    }
+
     void notifyConfigurationChanged(nsecs_t when) override {
         std::scoped_lock lock(mLock);
         mConfigurationChangedTime = when;
     }
 
-    void notifyWindowUnresponsive(const sp<IBinder>& connectionToken, const std::string&) override {
+    void notifyWindowUnresponsive(const sp<IBinder>& connectionToken, std::optional<int32_t> pid,
+                                  const std::string&) override {
         std::scoped_lock lock(mLock);
-        mAnrWindowTokens.push(connectionToken);
+        ASSERT_TRUE(pid.has_value());
+        mAnrWindows.push({connectionToken, *pid});
         mNotifyAnr.notify_all();
     }
 
-    void notifyMonitorUnresponsive(int32_t pid, const std::string&) override {
+    void notifyWindowResponsive(const sp<IBinder>& connectionToken,
+                                std::optional<int32_t> pid) override {
         std::scoped_lock lock(mLock);
-        mAnrMonitorPids.push(pid);
-        mNotifyAnr.notify_all();
-    }
-
-    void notifyWindowResponsive(const sp<IBinder>& connectionToken) override {
-        std::scoped_lock lock(mLock);
-        mResponsiveWindowTokens.push(connectionToken);
-        mNotifyAnr.notify_all();
-    }
-
-    void notifyMonitorResponsive(int32_t pid) override {
-        std::scoped_lock lock(mLock);
-        mResponsiveMonitorPids.push(pid);
+        ASSERT_TRUE(pid.has_value());
+        mResponsiveWindows.push({connectionToken, *pid});
         mNotifyAnr.notify_all();
     }
 
@@ -338,7 +398,11 @@
         mNotifyAnr.notify_all();
     }
 
-    void notifyInputChannelBroken(const sp<IBinder>&) override {}
+    void notifyInputChannelBroken(const sp<IBinder>& connectionToken) override {
+        std::scoped_lock lock(mLock);
+        mBrokenInputChannels.push(connectionToken);
+        mNotifyInputChannelBroken.notify_all();
+    }
 
     void notifyFocusChanged(const sp<IBinder>&, const sp<IBinder>&) override {}
 
@@ -374,12 +438,20 @@
         return true;
     }
 
-    void interceptKeyBeforeQueueing(const KeyEvent*, uint32_t&) override {}
+    void interceptKeyBeforeQueueing(const KeyEvent* inputEvent, uint32_t&) override {
+        if (inputEvent->getAction() == AKEY_EVENT_ACTION_UP) {
+            // Clear intercept state when we handled the event.
+            mInterceptKeyTimeout = 0ms;
+        }
+    }
 
     void interceptMotionBeforeQueueing(int32_t, nsecs_t, uint32_t&) override {}
 
     nsecs_t interceptKeyBeforeDispatching(const sp<IBinder>&, const KeyEvent*, uint32_t) override {
-        return 0;
+        nsecs_t delay = std::chrono::nanoseconds(mInterceptKeyTimeout).count();
+        // Clear intercept state so we could dispatch the event in next wake.
+        mInterceptKeyTimeout = 0ms;
+        return delay;
     }
 
     bool dispatchUnhandledKey(const sp<IBinder>&, const KeyEvent*, uint32_t, KeyEvent*) override {
@@ -418,26 +490,11 @@
         mDropTargetWindowToken = token;
     }
 
-    void assertFilterInputEventWasCalled(int type, nsecs_t eventTime, int32_t action,
-                                         int32_t displayId) {
+    void assertFilterInputEventWasCalledInternal(
+            const std::function<void(const InputEvent&)>& verify) {
         std::scoped_lock lock(mLock);
         ASSERT_NE(nullptr, mFilteredEvent) << "Expected filterInputEvent() to have been called.";
-        ASSERT_EQ(mFilteredEvent->getType(), type);
-
-        if (type == AINPUT_EVENT_TYPE_KEY) {
-            const KeyEvent& keyEvent = static_cast<const KeyEvent&>(*mFilteredEvent);
-            EXPECT_EQ(keyEvent.getEventTime(), eventTime);
-            EXPECT_EQ(keyEvent.getAction(), action);
-            EXPECT_EQ(keyEvent.getDisplayId(), displayId);
-        } else if (type == AINPUT_EVENT_TYPE_MOTION) {
-            const MotionEvent& motionEvent = static_cast<const MotionEvent&>(*mFilteredEvent);
-            EXPECT_EQ(motionEvent.getEventTime(), eventTime);
-            EXPECT_EQ(motionEvent.getAction(), action);
-            EXPECT_EQ(motionEvent.getDisplayId(), displayId);
-        } else {
-            FAIL() << "Unknown type: " << type;
-        }
-
+        verify(*mFilteredEvent);
         mFilteredEvent = nullptr;
     }
 };
@@ -447,11 +504,11 @@
 class InputDispatcherTest : public testing::Test {
 protected:
     sp<FakeInputDispatcherPolicy> mFakePolicy;
-    sp<InputDispatcher> mDispatcher;
+    std::unique_ptr<InputDispatcher> mDispatcher;
 
     void SetUp() override {
         mFakePolicy = new FakeInputDispatcherPolicy();
-        mDispatcher = new InputDispatcher(mFakePolicy);
+        mDispatcher = std::make_unique<InputDispatcher>(mFakePolicy, STALE_EVENT_TIMEOUT);
         mDispatcher->setInputDispatchMode(/*enabled*/ true, /*frozen*/ false);
         // Start InputDispatcher thread
         ASSERT_EQ(OK, mDispatcher->start());
@@ -460,7 +517,7 @@
     void TearDown() override {
         ASSERT_EQ(OK, mDispatcher->stop());
         mFakePolicy.clear();
-        mDispatcher.clear();
+        mDispatcher.reset();
     }
 
     /**
@@ -518,7 +575,7 @@
     MotionEvent event;
     PointerProperties pointerProperties[MAX_POINTERS + 1];
     PointerCoords pointerCoords[MAX_POINTERS + 1];
-    for (int i = 0; i <= MAX_POINTERS; i++) {
+    for (size_t i = 0; i <= MAX_POINTERS; i++) {
         pointerProperties[i].clear();
         pointerProperties[i].id = i;
         pointerCoords[i].clear();
@@ -535,8 +592,8 @@
     event.initialize(InputEvent::nextId(), DEVICE_ID, source, DISPLAY_ID, INVALID_HMAC,
                      /*action*/ -1, 0, 0, edgeFlags, metaState, 0, classification,
                      identityTransform, 0, 0, AMOTION_EVENT_INVALID_CURSOR_POSITION,
-                     AMOTION_EVENT_INVALID_CURSOR_POSITION, ui::Transform::ROT_0,
-                     INVALID_DISPLAY_SIZE, INVALID_DISPLAY_SIZE, ARBITRARY_TIME, ARBITRARY_TIME,
+                     AMOTION_EVENT_INVALID_CURSOR_POSITION, identityTransform, ARBITRARY_TIME,
+                     ARBITRARY_TIME,
                      /*pointerCount*/ 1, pointerProperties, pointerCoords);
     ASSERT_EQ(InputEventInjectionResult::FAILED,
               mDispatcher->injectInputEvent(&event, INJECTOR_PID, INJECTOR_UID,
@@ -545,12 +602,10 @@
 
     // Rejects pointer down with invalid index.
     event.initialize(InputEvent::nextId(), DEVICE_ID, source, DISPLAY_ID, INVALID_HMAC,
-                     AMOTION_EVENT_ACTION_POINTER_DOWN |
-                             (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                     0, 0, edgeFlags, metaState, 0, classification, identityTransform, 0, 0,
-                     AMOTION_EVENT_INVALID_CURSOR_POSITION, AMOTION_EVENT_INVALID_CURSOR_POSITION,
-                     ui::Transform::ROT_0, INVALID_DISPLAY_SIZE, INVALID_DISPLAY_SIZE,
-                     ARBITRARY_TIME, ARBITRARY_TIME,
+                     POINTER_1_DOWN, 0, 0, edgeFlags, metaState, 0, classification,
+                     identityTransform, 0, 0, AMOTION_EVENT_INVALID_CURSOR_POSITION,
+                     AMOTION_EVENT_INVALID_CURSOR_POSITION, identityTransform, ARBITRARY_TIME,
+                     ARBITRARY_TIME,
                      /*pointerCount*/ 1, pointerProperties, pointerCoords);
     ASSERT_EQ(InputEventInjectionResult::FAILED,
               mDispatcher->injectInputEvent(&event, INJECTOR_PID, INJECTOR_UID,
@@ -562,8 +617,7 @@
                              (~0U << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
                      0, 0, edgeFlags, metaState, 0, classification, identityTransform, 0, 0,
                      AMOTION_EVENT_INVALID_CURSOR_POSITION, AMOTION_EVENT_INVALID_CURSOR_POSITION,
-                     ui::Transform::ROT_0, INVALID_DISPLAY_SIZE, INVALID_DISPLAY_SIZE,
-                     ARBITRARY_TIME, ARBITRARY_TIME,
+                     identityTransform, ARBITRARY_TIME, ARBITRARY_TIME,
                      /*pointerCount*/ 1, pointerProperties, pointerCoords);
     ASSERT_EQ(InputEventInjectionResult::FAILED,
               mDispatcher->injectInputEvent(&event, INJECTOR_PID, INJECTOR_UID,
@@ -572,12 +626,10 @@
 
     // Rejects pointer up with invalid index.
     event.initialize(InputEvent::nextId(), DEVICE_ID, source, DISPLAY_ID, INVALID_HMAC,
-                     AMOTION_EVENT_ACTION_POINTER_UP |
-                             (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                     0, 0, edgeFlags, metaState, 0, classification, identityTransform, 0, 0,
-                     AMOTION_EVENT_INVALID_CURSOR_POSITION, AMOTION_EVENT_INVALID_CURSOR_POSITION,
-                     ui::Transform::ROT_0, INVALID_DISPLAY_SIZE, INVALID_DISPLAY_SIZE,
-                     ARBITRARY_TIME, ARBITRARY_TIME,
+                     POINTER_1_UP, 0, 0, edgeFlags, metaState, 0, classification, identityTransform,
+                     0, 0, AMOTION_EVENT_INVALID_CURSOR_POSITION,
+                     AMOTION_EVENT_INVALID_CURSOR_POSITION, identityTransform, ARBITRARY_TIME,
+                     ARBITRARY_TIME,
                      /*pointerCount*/ 1, pointerProperties, pointerCoords);
     ASSERT_EQ(InputEventInjectionResult::FAILED,
               mDispatcher->injectInputEvent(&event, INJECTOR_PID, INJECTOR_UID,
@@ -589,8 +641,7 @@
                              (~0U << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
                      0, 0, edgeFlags, metaState, 0, classification, identityTransform, 0, 0,
                      AMOTION_EVENT_INVALID_CURSOR_POSITION, AMOTION_EVENT_INVALID_CURSOR_POSITION,
-                     ui::Transform::ROT_0, INVALID_DISPLAY_SIZE, INVALID_DISPLAY_SIZE,
-                     ARBITRARY_TIME, ARBITRARY_TIME,
+                     identityTransform, ARBITRARY_TIME, ARBITRARY_TIME,
                      /*pointerCount*/ 1, pointerProperties, pointerCoords);
     ASSERT_EQ(InputEventInjectionResult::FAILED,
               mDispatcher->injectInputEvent(&event, INJECTOR_PID, INJECTOR_UID,
@@ -601,8 +652,8 @@
     event.initialize(InputEvent::nextId(), DEVICE_ID, source, DISPLAY_ID, INVALID_HMAC,
                      AMOTION_EVENT_ACTION_DOWN, 0, 0, edgeFlags, metaState, 0, classification,
                      identityTransform, 0, 0, AMOTION_EVENT_INVALID_CURSOR_POSITION,
-                     AMOTION_EVENT_INVALID_CURSOR_POSITION, ui::Transform::ROT_0,
-                     INVALID_DISPLAY_SIZE, INVALID_DISPLAY_SIZE, ARBITRARY_TIME, ARBITRARY_TIME,
+                     AMOTION_EVENT_INVALID_CURSOR_POSITION, identityTransform, ARBITRARY_TIME,
+                     ARBITRARY_TIME,
                      /*pointerCount*/ 0, pointerProperties, pointerCoords);
     ASSERT_EQ(InputEventInjectionResult::FAILED,
               mDispatcher->injectInputEvent(&event, INJECTOR_PID, INJECTOR_UID,
@@ -612,8 +663,8 @@
     event.initialize(InputEvent::nextId(), DEVICE_ID, source, DISPLAY_ID, INVALID_HMAC,
                      AMOTION_EVENT_ACTION_DOWN, 0, 0, edgeFlags, metaState, 0, classification,
                      identityTransform, 0, 0, AMOTION_EVENT_INVALID_CURSOR_POSITION,
-                     AMOTION_EVENT_INVALID_CURSOR_POSITION, ui::Transform::ROT_0,
-                     INVALID_DISPLAY_SIZE, INVALID_DISPLAY_SIZE, ARBITRARY_TIME, ARBITRARY_TIME,
+                     AMOTION_EVENT_INVALID_CURSOR_POSITION, identityTransform, ARBITRARY_TIME,
+                     ARBITRARY_TIME,
                      /*pointerCount*/ MAX_POINTERS + 1, pointerProperties, pointerCoords);
     ASSERT_EQ(InputEventInjectionResult::FAILED,
               mDispatcher->injectInputEvent(&event, INJECTOR_PID, INJECTOR_UID,
@@ -625,8 +676,8 @@
     event.initialize(InputEvent::nextId(), DEVICE_ID, source, DISPLAY_ID, INVALID_HMAC,
                      AMOTION_EVENT_ACTION_DOWN, 0, 0, edgeFlags, metaState, 0, classification,
                      identityTransform, 0, 0, AMOTION_EVENT_INVALID_CURSOR_POSITION,
-                     AMOTION_EVENT_INVALID_CURSOR_POSITION, ui::Transform::ROT_0,
-                     INVALID_DISPLAY_SIZE, INVALID_DISPLAY_SIZE, ARBITRARY_TIME, ARBITRARY_TIME,
+                     AMOTION_EVENT_INVALID_CURSOR_POSITION, identityTransform, ARBITRARY_TIME,
+                     ARBITRARY_TIME,
                      /*pointerCount*/ 1, pointerProperties, pointerCoords);
     ASSERT_EQ(InputEventInjectionResult::FAILED,
               mDispatcher->injectInputEvent(&event, INJECTOR_PID, INJECTOR_UID,
@@ -637,8 +688,8 @@
     event.initialize(InputEvent::nextId(), DEVICE_ID, source, DISPLAY_ID, INVALID_HMAC,
                      AMOTION_EVENT_ACTION_DOWN, 0, 0, edgeFlags, metaState, 0, classification,
                      identityTransform, 0, 0, AMOTION_EVENT_INVALID_CURSOR_POSITION,
-                     AMOTION_EVENT_INVALID_CURSOR_POSITION, ui::Transform::ROT_0,
-                     INVALID_DISPLAY_SIZE, INVALID_DISPLAY_SIZE, ARBITRARY_TIME, ARBITRARY_TIME,
+                     AMOTION_EVENT_INVALID_CURSOR_POSITION, identityTransform, ARBITRARY_TIME,
+                     ARBITRARY_TIME,
                      /*pointerCount*/ 1, pointerProperties, pointerCoords);
     ASSERT_EQ(InputEventInjectionResult::FAILED,
               mDispatcher->injectInputEvent(&event, INJECTOR_PID, INJECTOR_UID,
@@ -651,8 +702,8 @@
     event.initialize(InputEvent::nextId(), DEVICE_ID, source, DISPLAY_ID, INVALID_HMAC,
                      AMOTION_EVENT_ACTION_DOWN, 0, 0, edgeFlags, metaState, 0, classification,
                      identityTransform, 0, 0, AMOTION_EVENT_INVALID_CURSOR_POSITION,
-                     AMOTION_EVENT_INVALID_CURSOR_POSITION, ui::Transform::ROT_0,
-                     INVALID_DISPLAY_SIZE, INVALID_DISPLAY_SIZE, ARBITRARY_TIME, ARBITRARY_TIME,
+                     AMOTION_EVENT_INVALID_CURSOR_POSITION, identityTransform, ARBITRARY_TIME,
+                     ARBITRARY_TIME,
                      /*pointerCount*/ 2, pointerProperties, pointerCoords);
     ASSERT_EQ(InputEventInjectionResult::FAILED,
               mDispatcher->injectInputEvent(&event, INJECTOR_PID, INJECTOR_UID,
@@ -683,7 +734,11 @@
 
 // --- InputDispatcherTest SetInputWindowTest ---
 static constexpr std::chrono::duration INJECT_EVENT_TIMEOUT = 500ms;
-static constexpr std::chrono::nanoseconds DISPATCHING_TIMEOUT = 5s;
+// Default input dispatching timeout if there is no focused application or paused window
+// from which to determine an appropriate dispatching timeout.
+static const std::chrono::duration DISPATCHING_TIMEOUT = std::chrono::milliseconds(
+        android::os::IInputConstants::UNMULTIPLIED_DEFAULT_DISPATCHING_TIMEOUT_MILLIS *
+        android::base::HwTimeoutMultiplier());
 
 class FakeApplicationHandle : public InputApplicationHandle {
 public:
@@ -796,7 +851,8 @@
             }
             case AINPUT_EVENT_TYPE_MOTION: {
                 const MotionEvent& motionEvent = static_cast<const MotionEvent&>(*event);
-                EXPECT_EQ(expectedAction, motionEvent.getAction());
+                assertMotionAction(expectedAction, motionEvent.getAction());
+
                 if (expectedFlags.has_value()) {
                     EXPECT_EQ(expectedFlags.value(), motionEvent.getFlags());
                 }
@@ -808,6 +864,9 @@
             case AINPUT_EVENT_TYPE_CAPTURE: {
                 FAIL() << "Use 'consumeCaptureEvent' for CAPTURE events";
             }
+            case AINPUT_EVENT_TYPE_TOUCH_MODE: {
+                FAIL() << "Use 'consumeTouchModeEvent' for TOUCH_MODE events";
+            }
             case AINPUT_EVENT_TYPE_DRAG: {
                 FAIL() << "Use 'consumeDragEvent' for DRAG events";
             }
@@ -830,7 +889,6 @@
 
         FocusEvent* focusEvent = static_cast<FocusEvent*>(event);
         EXPECT_EQ(hasFocus, focusEvent->getHasFocus());
-        EXPECT_EQ(inTouchMode, focusEvent->getInTouchMode());
     }
 
     void consumeCaptureEvent(bool hasCapture) {
@@ -865,6 +923,20 @@
         EXPECT_EQ(y, dragEvent.getY());
     }
 
+    void consumeTouchModeEvent(bool inTouchMode) {
+        const InputEvent* event = consume();
+        ASSERT_NE(nullptr, event) << mName.c_str()
+                                  << ": consumer should have returned non-NULL event.";
+        ASSERT_EQ(AINPUT_EVENT_TYPE_TOUCH_MODE, event->getType())
+                << "Got " << inputEventTypeToString(event->getType())
+                << " event instead of TOUCH_MODE event";
+
+        ASSERT_EQ(ADISPLAY_ID_NONE, event->getDisplayId())
+                << mName.c_str() << ": event displayId should always be NONE.";
+        const auto& touchModeEvent = static_cast<const TouchModeEvent&>(*event);
+        EXPECT_EQ(inTouchMode, touchModeEvent.isInTouchMode());
+    }
+
     void assertNoEvents() {
         InputEvent* event = consume();
         if (event == nullptr) {
@@ -886,6 +958,10 @@
             const auto& captureEvent = static_cast<CaptureEvent&>(*event);
             ADD_FAILURE() << "Received capture event, pointerCaptureEnabled = "
                           << (captureEvent.getPointerCaptureEnabled() ? "true" : "false");
+        } else if (event->getType() == AINPUT_EVENT_TYPE_TOUCH_MODE) {
+            const auto& touchModeEvent = static_cast<TouchModeEvent&>(*event);
+            ADD_FAILURE() << "Received touch mode event, inTouchMode = "
+                          << (touchModeEvent.isInTouchMode() ? "true" : "false");
         }
         FAIL() << mName.c_str()
                << ": should not have received any events, so consume() should return NULL";
@@ -893,6 +969,8 @@
 
     sp<IBinder> getToken() { return mConsumer->getChannel()->getConnectionToken(); }
 
+    int getChannelFd() { return mConsumer->getChannel()->getFd().get(); }
+
 protected:
     std::unique_ptr<InputConsumer> mConsumer;
     PreallocatedInputEventFactory mEventFactory;
@@ -906,7 +984,7 @@
     static const int32_t HEIGHT = 800;
 
     FakeWindowHandle(const std::shared_ptr<InputApplicationHandle>& inputApplicationHandle,
-                     const sp<InputDispatcher>& dispatcher, const std::string name,
+                     const std::unique_ptr<InputDispatcher>& dispatcher, const std::string name,
                      int32_t displayId, std::optional<sp<IBinder>> token = std::nullopt)
           : mName(name) {
         if (token == std::nullopt) {
@@ -922,7 +1000,6 @@
         mInfo.token = *token;
         mInfo.id = sId++;
         mInfo.name = name;
-        mInfo.type = WindowInfo::Type::APPLICATION;
         mInfo.dispatchingTimeout = DISPATCHING_TIMEOUT;
         mInfo.alpha = 1.0;
         mInfo.frameLeft = 0;
@@ -933,33 +1010,70 @@
         mInfo.globalScaleFactor = 1.0;
         mInfo.touchableRegion.clear();
         mInfo.addTouchableRegion(Rect(0, 0, WIDTH, HEIGHT));
-        mInfo.visible = true;
-        mInfo.focusable = false;
-        mInfo.hasWallpaper = false;
-        mInfo.paused = false;
         mInfo.ownerPid = INJECTOR_PID;
         mInfo.ownerUid = INJECTOR_UID;
         mInfo.displayId = displayId;
+        mInfo.inputConfig = WindowInfo::InputConfig::DEFAULT;
     }
 
     sp<FakeWindowHandle> clone(
             const std::shared_ptr<InputApplicationHandle>& inputApplicationHandle,
-            const sp<InputDispatcher>& dispatcher, int32_t displayId) {
+            const std::unique_ptr<InputDispatcher>& dispatcher, int32_t displayId) {
         sp<FakeWindowHandle> handle =
                 new FakeWindowHandle(inputApplicationHandle, dispatcher, mInfo.name + "(Mirror)",
                                      displayId, mInfo.token);
         return handle;
     }
 
-    void setFocusable(bool focusable) { mInfo.focusable = focusable; }
+    void setTouchable(bool touchable) {
+        mInfo.setInputConfig(WindowInfo::InputConfig::NOT_TOUCHABLE, !touchable);
+    }
 
-    void setVisible(bool visible) { mInfo.visible = visible; }
+    void setFocusable(bool focusable) {
+        mInfo.setInputConfig(WindowInfo::InputConfig::NOT_FOCUSABLE, !focusable);
+    }
+
+    void setVisible(bool visible) {
+        mInfo.setInputConfig(WindowInfo::InputConfig::NOT_VISIBLE, !visible);
+    }
 
     void setDispatchingTimeout(std::chrono::nanoseconds timeout) {
         mInfo.dispatchingTimeout = timeout;
     }
 
-    void setPaused(bool paused) { mInfo.paused = paused; }
+    void setPaused(bool paused) {
+        mInfo.setInputConfig(WindowInfo::InputConfig::PAUSE_DISPATCHING, paused);
+    }
+
+    void setPreventSplitting(bool preventSplitting) {
+        mInfo.setInputConfig(WindowInfo::InputConfig::PREVENT_SPLITTING, preventSplitting);
+    }
+
+    void setSlippery(bool slippery) {
+        mInfo.setInputConfig(WindowInfo::InputConfig::SLIPPERY, slippery);
+    }
+
+    void setWatchOutsideTouch(bool watchOutside) {
+        mInfo.setInputConfig(WindowInfo::InputConfig::WATCH_OUTSIDE_TOUCH, watchOutside);
+    }
+
+    void setSpy(bool spy) { mInfo.setInputConfig(WindowInfo::InputConfig::SPY, spy); }
+
+    void setInterceptsStylus(bool interceptsStylus) {
+        mInfo.setInputConfig(WindowInfo::InputConfig::INTERCEPTS_STYLUS, interceptsStylus);
+    }
+
+    void setDropInput(bool dropInput) {
+        mInfo.setInputConfig(WindowInfo::InputConfig::DROP_INPUT, dropInput);
+    }
+
+    void setDropInputIfObscured(bool dropInputIfObscured) {
+        mInfo.setInputConfig(WindowInfo::InputConfig::DROP_INPUT_IF_OBSCURED, dropInputIfObscured);
+    }
+
+    void setNoInputChannel(bool noInputChannel) {
+        mInfo.setInputConfig(WindowInfo::InputConfig::NO_INPUT_CHANNEL, noInputChannel);
+    }
 
     void setAlpha(float alpha) { mInfo.alpha = alpha; }
 
@@ -967,21 +1081,33 @@
 
     void setApplicationToken(sp<IBinder> token) { mInfo.applicationInfo.token = token; }
 
-    void setFrame(const Rect& frame) {
+    void setFrame(const Rect& frame, const ui::Transform& displayTransform = ui::Transform()) {
         mInfo.frameLeft = frame.left;
         mInfo.frameTop = frame.top;
         mInfo.frameRight = frame.right;
         mInfo.frameBottom = frame.bottom;
-        mInfo.transform.set(-frame.left, -frame.top);
         mInfo.touchableRegion.clear();
         mInfo.addTouchableRegion(frame);
+
+        const Rect logicalDisplayFrame = displayTransform.transform(frame);
+        ui::Transform translate;
+        translate.set(-logicalDisplayFrame.left, -logicalDisplayFrame.top);
+        mInfo.transform = translate * displayTransform;
     }
 
-    void addFlags(Flags<WindowInfo::Flag> flags) { mInfo.flags |= flags; }
+    void setTouchableRegion(const Region& region) { mInfo.touchableRegion = region; }
 
-    void setFlags(Flags<WindowInfo::Flag> flags) { mInfo.flags = flags; }
+    void setIsWallpaper(bool isWallpaper) {
+        mInfo.setInputConfig(WindowInfo::InputConfig::IS_WALLPAPER, isWallpaper);
+    }
 
-    void setInputFeatures(WindowInfo::Feature features) { mInfo.inputFeatures = features; }
+    void setDupTouchToWallpaper(bool hasWallpaper) {
+        mInfo.setInputConfig(WindowInfo::InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER, hasWallpaper);
+    }
+
+    void setTrustedOverlay(bool trustedOverlay) {
+        mInfo.setInputConfig(WindowInfo::InputConfig::TRUSTED_OVERLAY, trustedOverlay);
+    }
 
     void setWindowTransform(float dsdx, float dtdx, float dtdy, float dsdy) {
         mInfo.transform.set(dsdx, dtdx, dtdy, dsdy);
@@ -1050,6 +1176,16 @@
                      expectedFlags);
     }
 
+    void consumeMotionOutsideWithZeroedCoords(int32_t expectedDisplayId = ADISPLAY_ID_DEFAULT,
+                                              int32_t expectedFlags = 0) {
+        InputEvent* event = consume();
+        ASSERT_EQ(AINPUT_EVENT_TYPE_MOTION, event->getType());
+        const MotionEvent& motionEvent = static_cast<MotionEvent&>(*event);
+        EXPECT_EQ(AMOTION_EVENT_ACTION_OUTSIDE, motionEvent.getActionMasked());
+        EXPECT_EQ(0.f, motionEvent.getRawPointerCoords(0)->getX());
+        EXPECT_EQ(0.f, motionEvent.getRawPointerCoords(0)->getY());
+    }
+
     void consumeFocusEvent(bool hasFocus, bool inTouchMode = true) {
         ASSERT_NE(mInputReceiver, nullptr)
                 << "Cannot consume events from a window with no receiver";
@@ -1074,6 +1210,12 @@
         mInputReceiver->consumeDragEvent(isExiting, x, y);
     }
 
+    void consumeTouchModeEvent(bool inTouchMode) {
+        ASSERT_NE(mInputReceiver, nullptr)
+                << "Cannot consume events from a window with no receiver";
+        mInputReceiver->consumeTouchModeEvent(inTouchMode);
+    }
+
     std::optional<uint32_t> receiveEvent(InputEvent** outEvent = nullptr) {
         if (mInputReceiver == nullptr) {
             ADD_FAILURE() << "Invalid receive event on window with no receiver";
@@ -1115,7 +1257,7 @@
 
     void assertNoEvents() {
         if (mInputReceiver == nullptr &&
-            mInfo.inputFeatures.test(WindowInfo::Feature::NO_INPUT_CHANNEL)) {
+            mInfo.inputConfig.test(WindowInfo::InputConfig::NO_INPUT_CHANNEL)) {
             return; // Can't receive events if the window does not have input channel
         }
         ASSERT_NE(nullptr, mInputReceiver)
@@ -1132,6 +1274,12 @@
         mInfo.ownerUid = ownerUid;
     }
 
+    int32_t getPid() const { return mInfo.ownerPid; }
+
+    void destroyReceiver() { mInputReceiver = nullptr; }
+
+    int getChannelFd() { return mInputReceiver->getChannelFd(); }
+
 private:
     const std::string mName;
     std::unique_ptr<FakeInputReceiver> mInputReceiver;
@@ -1141,7 +1289,7 @@
 std::atomic<int32_t> FakeWindowHandle::sId{1};
 
 static InputEventInjectionResult injectKey(
-        const sp<InputDispatcher>& dispatcher, int32_t action, int32_t repeatCount,
+        const std::unique_ptr<InputDispatcher>& dispatcher, int32_t action, int32_t repeatCount,
         int32_t displayId = ADISPLAY_ID_NONE,
         InputEventInjectionSync syncMode = InputEventInjectionSync::WAIT_FOR_RESULT,
         std::chrono::milliseconds injectionTimeout = INJECT_EVENT_TIMEOUT,
@@ -1163,7 +1311,7 @@
                                         injectionTimeout, policyFlags);
 }
 
-static InputEventInjectionResult injectKeyDown(const sp<InputDispatcher>& dispatcher,
+static InputEventInjectionResult injectKeyDown(const std::unique_ptr<InputDispatcher>& dispatcher,
                                                int32_t displayId = ADISPLAY_ID_NONE) {
     return injectKey(dispatcher, AKEY_EVENT_ACTION_DOWN, /* repeatCount */ 0, displayId);
 }
@@ -1171,14 +1319,14 @@
 // Inject a down event that has key repeat disabled. This allows InputDispatcher to idle without
 // sending a subsequent key up. When key repeat is enabled, the dispatcher cannot idle because it
 // has to be woken up to process the repeating key.
-static InputEventInjectionResult injectKeyDownNoRepeat(const sp<InputDispatcher>& dispatcher,
-                                                       int32_t displayId = ADISPLAY_ID_NONE) {
+static InputEventInjectionResult injectKeyDownNoRepeat(
+        const std::unique_ptr<InputDispatcher>& dispatcher, int32_t displayId = ADISPLAY_ID_NONE) {
     return injectKey(dispatcher, AKEY_EVENT_ACTION_DOWN, /* repeatCount */ 0, displayId,
                      InputEventInjectionSync::WAIT_FOR_RESULT, INJECT_EVENT_TIMEOUT,
                      /* allowKeyRepeat */ false);
 }
 
-static InputEventInjectionResult injectKeyUp(const sp<InputDispatcher>& dispatcher,
+static InputEventInjectionResult injectKeyUp(const std::unique_ptr<InputDispatcher>& dispatcher,
                                              int32_t displayId = ADISPLAY_ID_NONE) {
     return injectKey(dispatcher, AKEY_EVENT_ACTION_UP, /* repeatCount */ 0, displayId);
 }
@@ -1280,9 +1428,8 @@
                          mAction, mActionButton, mFlags, /* edgeFlags */ 0, AMETA_NONE,
                          mButtonState, MotionClassification::NONE, identityTransform,
                          /* xPrecision */ 0, /* yPrecision */ 0, mRawXCursorPosition,
-                         mRawYCursorPosition, mDisplayOrientation, mDisplayWidth, mDisplayHeight,
-                         mEventTime, mEventTime, mPointers.size(), pointerProperties.data(),
-                         pointerCoords.data());
+                         mRawYCursorPosition, identityTransform, mEventTime, mEventTime,
+                         mPointers.size(), pointerProperties.data(), pointerCoords.data());
 
         return event;
     }
@@ -1297,15 +1444,12 @@
     int32_t mFlags{0};
     float mRawXCursorPosition{AMOTION_EVENT_INVALID_CURSOR_POSITION};
     float mRawYCursorPosition{AMOTION_EVENT_INVALID_CURSOR_POSITION};
-    uint32_t mDisplayOrientation{ui::Transform::ROT_0};
-    int32_t mDisplayWidth{INVALID_DISPLAY_SIZE};
-    int32_t mDisplayHeight{INVALID_DISPLAY_SIZE};
 
     std::vector<PointerBuilder> mPointers;
 };
 
 static InputEventInjectionResult injectMotionEvent(
-        const sp<InputDispatcher>& dispatcher, const MotionEvent& event,
+        const std::unique_ptr<InputDispatcher>& dispatcher, const MotionEvent& event,
         std::chrono::milliseconds injectionTimeout = INJECT_EVENT_TIMEOUT,
         InputEventInjectionSync injectionMode = InputEventInjectionSync::WAIT_FOR_RESULT) {
     return dispatcher->injectInputEvent(&event, INJECTOR_PID, INJECTOR_UID, injectionMode,
@@ -1314,8 +1458,8 @@
 }
 
 static InputEventInjectionResult injectMotionEvent(
-        const sp<InputDispatcher>& dispatcher, int32_t action, int32_t source, int32_t displayId,
-        const PointF& position,
+        const std::unique_ptr<InputDispatcher>& dispatcher, int32_t action, int32_t source,
+        int32_t displayId, const PointF& position = {100, 200},
         const PointF& cursorPosition = {AMOTION_EVENT_INVALID_CURSOR_POSITION,
                                         AMOTION_EVENT_INVALID_CURSOR_POSITION},
         std::chrono::milliseconds injectionTimeout = INJECT_EVENT_TIMEOUT,
@@ -1335,13 +1479,13 @@
     return injectMotionEvent(dispatcher, event, injectionTimeout, injectionMode);
 }
 
-static InputEventInjectionResult injectMotionDown(const sp<InputDispatcher>& dispatcher,
-                                                  int32_t source, int32_t displayId,
-                                                  const PointF& location = {100, 200}) {
+static InputEventInjectionResult injectMotionDown(
+        const std::unique_ptr<InputDispatcher>& dispatcher, int32_t source, int32_t displayId,
+        const PointF& location = {100, 200}) {
     return injectMotionEvent(dispatcher, AMOTION_EVENT_ACTION_DOWN, source, displayId, location);
 }
 
-static InputEventInjectionResult injectMotionUp(const sp<InputDispatcher>& dispatcher,
+static InputEventInjectionResult injectMotionUp(const std::unique_ptr<InputDispatcher>& dispatcher,
                                                 int32_t source, int32_t displayId,
                                                 const PointF& location = {100, 200}) {
     return injectMotionEvent(dispatcher, AMOTION_EVENT_ACTION_UP, source, displayId, location);
@@ -1390,6 +1534,10 @@
     return args;
 }
 
+static NotifyMotionArgs generateTouchArgs(int32_t action, const std::vector<PointF>& points) {
+    return generateMotionArgs(action, AINPUT_SOURCE_TOUCHSCREEN, DISPLAY_ID, points);
+}
+
 static NotifyMotionArgs generateMotionArgs(int32_t action, int32_t source, int32_t displayId) {
     return generateMotionArgs(action, source, displayId, {PointF{100, 200}});
 }
@@ -1399,6 +1547,23 @@
     return NotifyPointerCaptureChangedArgs(/* id */ 0, systemTime(SYSTEM_TIME_MONOTONIC), request);
 }
 
+/**
+ * When a window unexpectedly disposes of its input channel, policy should be notified about the
+ * broken channel.
+ */
+TEST_F(InputDispatcherTest, WhenInputChannelBreaks_PolicyIsNotified) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> window =
+            new FakeWindowHandle(application, mDispatcher, "Window that breaks its input channel",
+                                 ADISPLAY_ID_DEFAULT);
+
+    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+
+    // Window closes its channel, but the window remains.
+    window->destroyReceiver();
+    mFakePolicy->assertNotifyInputChannelBrokenWasCalled(window->getInfo()->token);
+}
+
 TEST_F(InputDispatcherTest, SetInputWindow_SingleWindowTouch) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
     sp<FakeWindowHandle> window =
@@ -1413,20 +1578,31 @@
     window->consumeMotionDown(ADISPLAY_ID_DEFAULT);
 }
 
+TEST_F(InputDispatcherTest, WhenDisplayNotSpecified_InjectMotionToDefaultDisplay) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> window =
+            new FakeWindowHandle(application, mDispatcher, "Fake Window", ADISPLAY_ID_DEFAULT);
+
+    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+    // Inject a MotionEvent to an unknown display.
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_NONE))
+            << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
+
+    // Window should receive motion event.
+    window->consumeMotionDown(ADISPLAY_ID_DEFAULT);
+}
+
 /**
- * Calling setInputWindows once with FLAG_NOT_TOUCH_MODAL should not cause any issues.
- * To ensure that window receives only events that were directly inside of it, add
- * FLAG_NOT_TOUCH_MODAL. This will enforce using the touchableRegion of the input
- * when finding touched windows.
+ * Calling setInputWindows once should not cause any issues.
  * This test serves as a sanity check for the next test, where setInputWindows is
  * called twice.
  */
-TEST_F(InputDispatcherTest, SetInputWindowOnce_SingleWindowTouch) {
+TEST_F(InputDispatcherTest, SetInputWindowOnceWithSingleTouchWindow) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
     sp<FakeWindowHandle> window =
             new FakeWindowHandle(application, mDispatcher, "Fake Window", ADISPLAY_ID_DEFAULT);
     window->setFrame(Rect(0, 0, 100, 100));
-    window->setFlags(WindowInfo::Flag::NOT_TOUCH_MODAL);
 
     mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
@@ -1440,16 +1616,12 @@
 
 /**
  * Calling setInputWindows twice, with the same info, should not cause any issues.
- * To ensure that window receives only events that were directly inside of it, add
- * FLAG_NOT_TOUCH_MODAL. This will enforce using the touchableRegion of the input
- * when finding touched windows.
  */
 TEST_F(InputDispatcherTest, SetInputWindowTwice_SingleWindowTouch) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
     sp<FakeWindowHandle> window =
             new FakeWindowHandle(application, mDispatcher, "Fake Window", ADISPLAY_ID_DEFAULT);
     window->setFrame(Rect(0, 0, 100, 100));
-    window->setFlags(WindowInfo::Flag::NOT_TOUCH_MODAL);
 
     mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
     mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
@@ -1480,16 +1652,301 @@
     windowSecond->assertNoEvents();
 }
 
+/**
+ * Two windows: A top window, and a wallpaper behind the window.
+ * Touch goes to the top window, and then top window disappears. Ensure that wallpaper window
+ * gets ACTION_CANCEL.
+ * 1. foregroundWindow <-- dup touch to wallpaper
+ * 2. wallpaperWindow <-- is wallpaper
+ */
+TEST_F(InputDispatcherTest, WhenForegroundWindowDisappears_WallpaperTouchIsCanceled) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> foregroundWindow =
+            new FakeWindowHandle(application, mDispatcher, "Foreground", ADISPLAY_ID_DEFAULT);
+    foregroundWindow->setDupTouchToWallpaper(true);
+    sp<FakeWindowHandle> wallpaperWindow =
+            new FakeWindowHandle(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;
+
+    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {foregroundWindow, wallpaperWindow}}});
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+                               {100, 200}))
+            << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
+
+    // Both foreground window and its wallpaper should receive the touch down
+    foregroundWindow->consumeMotionDown();
+    wallpaperWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
+
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionEvent(mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
+                                ADISPLAY_ID_DEFAULT, {110, 200}))
+            << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
+
+    foregroundWindow->consumeMotionMove();
+    wallpaperWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
+
+    // Now the foreground window goes away, but the wallpaper stays
+    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {wallpaperWindow}}});
+    foregroundWindow->consumeMotionCancel();
+    // Since the "parent" window of the wallpaper is gone, wallpaper should receive cancel, too.
+    wallpaperWindow->consumeMotionCancel(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
+}
+
+/**
+ * Same test as WhenForegroundWindowDisappears_WallpaperTouchIsCanceled above,
+ * with the following differences:
+ * After ACTION_DOWN, Wallpaper window hangs up its channel, which forces the dispatcher to
+ * clean up the connection.
+ * This later may crash dispatcher during ACTION_CANCEL synthesis, if the dispatcher is not careful.
+ * Ensure that there's no crash in the dispatcher.
+ */
+TEST_F(InputDispatcherTest, WhenWallpaperDisappears_NoCrash) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> foregroundWindow =
+            new FakeWindowHandle(application, mDispatcher, "Foreground", ADISPLAY_ID_DEFAULT);
+    foregroundWindow->setDupTouchToWallpaper(true);
+    sp<FakeWindowHandle> wallpaperWindow =
+            new FakeWindowHandle(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;
+
+    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {foregroundWindow, wallpaperWindow}}});
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+                               {100, 200}))
+            << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
+
+    // Both foreground window and its wallpaper should receive the touch down
+    foregroundWindow->consumeMotionDown();
+    wallpaperWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
+
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionEvent(mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
+                                ADISPLAY_ID_DEFAULT, {110, 200}))
+            << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
+
+    foregroundWindow->consumeMotionMove();
+    wallpaperWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
+
+    // Wallpaper closes its channel, but the window remains.
+    wallpaperWindow->destroyReceiver();
+    mFakePolicy->assertNotifyInputChannelBrokenWasCalled(wallpaperWindow->getInfo()->token);
+
+    // Now the foreground window goes away, but the wallpaper stays, even though its channel
+    // is no longer valid.
+    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {wallpaperWindow}}});
+    foregroundWindow->consumeMotionCancel();
+}
+
+/**
+ * A single window that receives touch (on top), and a wallpaper window underneath it.
+ * The top window gets a multitouch gesture.
+ * Ensure that wallpaper gets the same gesture.
+ */
+TEST_F(InputDispatcherTest, WallpaperWindow_ReceivesMultiTouch) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> window =
+            new FakeWindowHandle(application, mDispatcher, "Top", ADISPLAY_ID_DEFAULT);
+    window->setDupTouchToWallpaper(true);
+
+    sp<FakeWindowHandle> wallpaperWindow =
+            new FakeWindowHandle(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;
+
+    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window, wallpaperWindow}}});
+
+    // Touch down on top window
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+                               {100, 100}))
+            << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
+
+    // Both top window and its wallpaper should receive the touch down
+    window->consumeMotionDown();
+    wallpaperWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
+
+    // Second finger down on the top window
+    const MotionEvent secondFingerDownEvent =
+            MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                    .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
+                    .pointer(PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER)
+                                     .x(100)
+                                     .y(100))
+                    .pointer(PointerBuilder(/* id */ 1, AMOTION_EVENT_TOOL_TYPE_FINGER)
+                                     .x(150)
+                                     .y(150))
+                    .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 /* pointerIndex */);
+    wallpaperWindow->consumeMotionPointerDown(1 /* pointerIndex */, ADISPLAY_ID_DEFAULT,
+                                              expectedWallpaperFlags);
+    window->assertNoEvents();
+    wallpaperWindow->assertNoEvents();
+}
+
+/**
+ * Two windows: a window on the left and window on the right.
+ * A third window, wallpaper, is behind both windows, and spans both top windows.
+ * The first touch down goes to the left window. A second pointer touches down on the right window.
+ * The touch is split, so both left and right windows should receive ACTION_DOWN.
+ * The wallpaper will get the full event, so it should receive ACTION_DOWN followed by
+ * ACTION_POINTER_DOWN(1).
+ */
+TEST_F(InputDispatcherTest, TwoWindows_SplitWallpaperTouch) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> leftWindow =
+            new FakeWindowHandle(application, mDispatcher, "Left", ADISPLAY_ID_DEFAULT);
+    leftWindow->setFrame(Rect(0, 0, 200, 200));
+    leftWindow->setDupTouchToWallpaper(true);
+
+    sp<FakeWindowHandle> rightWindow =
+            new FakeWindowHandle(application, mDispatcher, "Right", ADISPLAY_ID_DEFAULT);
+    rightWindow->setFrame(Rect(200, 0, 400, 200));
+    rightWindow->setDupTouchToWallpaper(true);
+
+    sp<FakeWindowHandle> wallpaperWindow =
+            new FakeWindowHandle(application, mDispatcher, "Wallpaper", ADISPLAY_ID_DEFAULT);
+    wallpaperWindow->setFrame(Rect(0, 0, 400, 200));
+    wallpaperWindow->setIsWallpaper(true);
+    constexpr int expectedWallpaperFlags =
+            AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED | AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED;
+
+    mDispatcher->setInputWindows(
+            {{ADISPLAY_ID_DEFAULT, {leftWindow, rightWindow, wallpaperWindow}}});
+
+    // Touch down on left window
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+                               {100, 100}))
+            << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
+
+    // Both foreground window and its wallpaper should receive the touch down
+    leftWindow->consumeMotionDown();
+    wallpaperWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
+
+    // Second finger down on the right window
+    const MotionEvent secondFingerDownEvent =
+            MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                    .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
+                    .pointer(PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER)
+                                     .x(100)
+                                     .y(100))
+                    .pointer(PointerBuilder(/* id */ 1, AMOTION_EVENT_TOOL_TYPE_FINGER)
+                                     .x(300)
+                                     .y(100))
+                    .build();
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionEvent(mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT,
+                                InputEventInjectionSync::WAIT_FOR_RESULT))
+            << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
+
+    leftWindow->consumeMotionMove();
+    // Since the touch is split, right window gets ACTION_DOWN
+    rightWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT);
+    wallpaperWindow->consumeMotionPointerDown(1 /* pointerIndex */, ADISPLAY_ID_DEFAULT,
+                                              expectedWallpaperFlags);
+
+    // Now, leftWindow, which received the first finger, disappears.
+    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {rightWindow, wallpaperWindow}}});
+    leftWindow->consumeMotionCancel();
+    // Since a "parent" window of the wallpaper is gone, wallpaper should receive cancel, too.
+    wallpaperWindow->consumeMotionCancel(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
+
+    // The pointer that's still down on the right window moves, and goes to the right window only.
+    // As far as the dispatcher's concerned though, both pointers are still present.
+    const MotionEvent secondFingerMoveEvent =
+            MotionEventBuilder(AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+                    .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
+                    .pointer(PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER)
+                                     .x(100)
+                                     .y(100))
+                    .pointer(PointerBuilder(/* id */ 1, AMOTION_EVENT_TOOL_TYPE_FINGER)
+                                     .x(310)
+                                     .y(110))
+                    .build();
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionEvent(mDispatcher, secondFingerMoveEvent, INJECT_EVENT_TIMEOUT,
+                                InputEventInjectionSync::WAIT_FOR_RESULT));
+    rightWindow->consumeMotionMove();
+
+    leftWindow->assertNoEvents();
+    rightWindow->assertNoEvents();
+    wallpaperWindow->assertNoEvents();
+}
+
+/**
+ * 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.
+ * Make sure that the window receives the second pointer, and first pointer is simply ignored.
+ */
+TEST_F(InputDispatcherTest, SplitWorksWhenEmptyAreaIsTouched) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> window =
+            new FakeWindowHandle(application, mDispatcher, "Window", DISPLAY_ID);
+
+    mDispatcher->setInputWindows({{DISPLAY_ID, {window}}});
+    NotifyMotionArgs args;
+
+    // Touch down on the empty space
+    mDispatcher->notifyMotion(&(args = generateTouchArgs(AMOTION_EVENT_ACTION_DOWN, {{-1, -1}})));
+
+    mDispatcher->waitForIdle();
+    window->assertNoEvents();
+
+    // Now touch down on the window with another pointer
+    mDispatcher->notifyMotion(&(args = generateTouchArgs(POINTER_1_DOWN, {{-1, -1}, {10, 10}})));
+    mDispatcher->waitForIdle();
+    window->consumeMotionDown();
+}
+
+/**
+ * Same test as above, but instead of touching the empty space, the first touch goes to
+ * non-touchable window.
+ */
+TEST_F(InputDispatcherTest, SplitWorksWhenNonTouchableWindowIsTouched) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> window1 =
+            new FakeWindowHandle(application, mDispatcher, "Window1", DISPLAY_ID);
+    window1->setTouchableRegion(Region{{0, 0, 100, 100}});
+    window1->setTouchable(false);
+    sp<FakeWindowHandle> window2 =
+            new FakeWindowHandle(application, mDispatcher, "Window2", DISPLAY_ID);
+    window2->setTouchableRegion(Region{{100, 0, 200, 100}});
+
+    mDispatcher->setInputWindows({{DISPLAY_ID, {window1, window2}}});
+
+    NotifyMotionArgs args;
+    // Touch down on the non-touchable window
+    mDispatcher->notifyMotion(&(args = generateTouchArgs(AMOTION_EVENT_ACTION_DOWN, {{50, 50}})));
+
+    mDispatcher->waitForIdle();
+    window1->assertNoEvents();
+    window2->assertNoEvents();
+
+    // Now touch down on the window with another pointer
+    mDispatcher->notifyMotion(&(args = generateTouchArgs(POINTER_1_DOWN, {{50, 50}, {150, 50}})));
+    mDispatcher->waitForIdle();
+    window2->consumeMotionDown();
+}
+
 TEST_F(InputDispatcherTest, HoverMoveEnterMouseClickAndHoverMoveExit) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
     sp<FakeWindowHandle> windowLeft =
             new FakeWindowHandle(application, mDispatcher, "Left", ADISPLAY_ID_DEFAULT);
     windowLeft->setFrame(Rect(0, 0, 600, 800));
-    windowLeft->setFlags(WindowInfo::Flag::NOT_TOUCH_MODAL);
     sp<FakeWindowHandle> windowRight =
             new FakeWindowHandle(application, mDispatcher, "Right", ADISPLAY_ID_DEFAULT);
     windowRight->setFrame(Rect(600, 0, 1200, 800));
-    windowRight->setFlags(WindowInfo::Flag::NOT_TOUCH_MODAL);
 
     mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
 
@@ -1596,7 +2053,6 @@
     sp<FakeWindowHandle> window =
             new FakeWindowHandle(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
     window->setFrame(Rect(0, 0, 1200, 800));
-    window->setFlags(WindowInfo::Flag::NOT_TOUCH_MODAL);
 
     mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
 
@@ -1678,11 +2134,9 @@
     sp<FakeWindowHandle> windowLeft =
             new FakeWindowHandle(application, mDispatcher, "Left", ADISPLAY_ID_DEFAULT);
     windowLeft->setFrame(Rect(0, 0, 600, 800));
-    windowLeft->setFlags(WindowInfo::Flag::NOT_TOUCH_MODAL);
     sp<FakeWindowHandle> windowRight =
             new FakeWindowHandle(application, mDispatcher, "Right", ADISPLAY_ID_DEFAULT);
     windowRight->setFrame(Rect(600, 0, 1200, 800));
-    windowRight->setFlags(WindowInfo::Flag::NOT_TOUCH_MODAL);
 
     mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
 
@@ -1745,8 +2199,190 @@
                          0 /*expectedFlags*/);
 }
 
-using TransferFunction =
-        std::function<bool(sp<InputDispatcher> dispatcher, sp<IBinder>, sp<IBinder>)>;
+TEST_F(InputDispatcherTest, InterceptKeyByPolicy) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> window =
+            new FakeWindowHandle(application, mDispatcher, "Fake Window", ADISPLAY_ID_DEFAULT);
+    window->setFocusable(true);
+
+    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+    setFocusedWindow(window);
+
+    window->consumeFocusEvent(true);
+
+    NotifyKeyArgs keyArgs = generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT);
+    const std::chrono::milliseconds interceptKeyTimeout = 50ms;
+    const nsecs_t injectTime = keyArgs.eventTime;
+    mFakePolicy->setInterceptKeyTimeout(interceptKeyTimeout);
+    mDispatcher->notifyKey(&keyArgs);
+    // The dispatching time should be always greater than or equal to intercept key timeout.
+    window->consumeKeyDown(ADISPLAY_ID_DEFAULT);
+    ASSERT_TRUE((systemTime(SYSTEM_TIME_MONOTONIC) - injectTime) >=
+                std::chrono::nanoseconds(interceptKeyTimeout).count());
+}
+
+TEST_F(InputDispatcherTest, InterceptKeyIfKeyUp) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> window =
+            new FakeWindowHandle(application, mDispatcher, "Fake Window", ADISPLAY_ID_DEFAULT);
+    window->setFocusable(true);
+
+    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+    setFocusedWindow(window);
+
+    window->consumeFocusEvent(true);
+
+    NotifyKeyArgs keyDown = generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT);
+    NotifyKeyArgs keyUp = generateKeyArgs(AKEY_EVENT_ACTION_UP, ADISPLAY_ID_DEFAULT);
+    mFakePolicy->setInterceptKeyTimeout(150ms);
+    mDispatcher->notifyKey(&keyDown);
+    mDispatcher->notifyKey(&keyUp);
+
+    // Window should receive key event immediately when same key up.
+    window->consumeKeyDown(ADISPLAY_ID_DEFAULT);
+    window->consumeKeyUp(ADISPLAY_ID_DEFAULT);
+}
+
+/**
+ * Ensure the correct coordinate spaces are used by InputDispatcher.
+ *
+ * InputDispatcher works in the display space, so its coordinate system is relative to the display
+ * panel. Windows get events in the window space, and get raw coordinates in the logical display
+ * space.
+ */
+class InputDispatcherDisplayProjectionTest : public InputDispatcherTest {
+public:
+    void SetUp() override {
+        InputDispatcherTest::SetUp();
+        mDisplayInfos.clear();
+        mWindowInfos.clear();
+    }
+
+    void addDisplayInfo(int displayId, const ui::Transform& transform) {
+        gui::DisplayInfo info;
+        info.displayId = displayId;
+        info.transform = transform;
+        mDisplayInfos.push_back(std::move(info));
+        mDispatcher->onWindowInfosChanged(mWindowInfos, mDisplayInfos);
+    }
+
+    void addWindow(const sp<WindowInfoHandle>& windowHandle) {
+        mWindowInfos.push_back(*windowHandle->getInfo());
+        mDispatcher->onWindowInfosChanged(mWindowInfos, mDisplayInfos);
+    }
+
+    // Set up a test scenario where the display has a scaled projection and there are two windows
+    // on the display.
+    std::pair<sp<FakeWindowHandle>, sp<FakeWindowHandle>> setupScaledDisplayScenario() {
+        // The display has a projection that has a scale factor of 2 and 4 in the x and y directions
+        // respectively.
+        ui::Transform displayTransform;
+        displayTransform.set(2, 0, 0, 4);
+        addDisplayInfo(ADISPLAY_ID_DEFAULT, displayTransform);
+
+        std::shared_ptr<FakeApplicationHandle> application =
+                std::make_shared<FakeApplicationHandle>();
+
+        // Add two windows to the display. Their frames are represented in the display space.
+        sp<FakeWindowHandle> firstWindow =
+                new FakeWindowHandle(application, mDispatcher, "First Window", ADISPLAY_ID_DEFAULT);
+        firstWindow->setFrame(Rect(0, 0, 100, 200), displayTransform);
+        addWindow(firstWindow);
+
+        sp<FakeWindowHandle> secondWindow =
+                new FakeWindowHandle(application, mDispatcher, "Second Window",
+                                     ADISPLAY_ID_DEFAULT);
+        secondWindow->setFrame(Rect(100, 200, 200, 400), displayTransform);
+        addWindow(secondWindow);
+        return {std::move(firstWindow), std::move(secondWindow)};
+    }
+
+private:
+    std::vector<gui::DisplayInfo> mDisplayInfos;
+    std::vector<gui::WindowInfo> mWindowInfos;
+};
+
+TEST_F(InputDispatcherDisplayProjectionTest, HitTestsInDisplaySpace) {
+    auto [firstWindow, secondWindow] = setupScaledDisplayScenario();
+    // Send down to the first window. The point is represented in the display space. The point is
+    // selected so that if the hit test was done with the transform applied to it, then it would
+    // end up in the incorrect window.
+    NotifyMotionArgs downMotionArgs =
+            generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
+                               ADISPLAY_ID_DEFAULT, {PointF{75, 55}});
+    mDispatcher->notifyMotion(&downMotionArgs);
+
+    firstWindow->consumeMotionDown();
+    secondWindow->assertNoEvents();
+}
+
+// Ensure that when a MotionEvent is injected through the InputDispatcher::injectInputEvent() API,
+// the event should be treated as being in the logical display space.
+TEST_F(InputDispatcherDisplayProjectionTest, InjectionInLogicalDisplaySpace) {
+    auto [firstWindow, secondWindow] = setupScaledDisplayScenario();
+    // Send down to the first window. The point is represented in the logical display space. The
+    // point is selected so that if the hit test was done in logical display space, then it would
+    // end up in the incorrect window.
+    injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+                     PointF{75 * 2, 55 * 4});
+
+    firstWindow->consumeMotionDown();
+    secondWindow->assertNoEvents();
+}
+
+// Ensure that when a MotionEvent that has a custom transform is injected, the post-transformed
+// event should be treated as being in the logical display space.
+TEST_F(InputDispatcherDisplayProjectionTest, InjectionWithTransformInLogicalDisplaySpace) {
+    auto [firstWindow, secondWindow] = setupScaledDisplayScenario();
+
+    const std::array<float, 9> matrix = {1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 0.0, 0.0, 1.0};
+    ui::Transform injectedEventTransform;
+    injectedEventTransform.set(matrix);
+    const vec2 expectedPoint{75, 55}; // The injected point in the logical display space.
+    const vec2 untransformedPoint = injectedEventTransform.inverse().transform(expectedPoint);
+
+    MotionEvent event = MotionEventBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                .displayId(ADISPLAY_ID_DEFAULT)
+                                .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
+                                .pointer(PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER)
+                                                 .x(untransformedPoint.x)
+                                                 .y(untransformedPoint.y))
+                                .build();
+    event.transform(matrix);
+
+    injectMotionEvent(mDispatcher, event, INJECT_EVENT_TIMEOUT,
+                      InputEventInjectionSync::WAIT_FOR_RESULT);
+
+    firstWindow->consumeMotionDown();
+    secondWindow->assertNoEvents();
+}
+
+TEST_F(InputDispatcherDisplayProjectionTest, WindowGetsEventsInCorrectCoordinateSpace) {
+    auto [firstWindow, secondWindow] = setupScaledDisplayScenario();
+
+    // Send down to the second window.
+    NotifyMotionArgs downMotionArgs =
+            generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
+                               ADISPLAY_ID_DEFAULT, {PointF{150, 220}});
+    mDispatcher->notifyMotion(&downMotionArgs);
+
+    firstWindow->assertNoEvents();
+    const MotionEvent* event = secondWindow->consumeMotion();
+    EXPECT_EQ(AMOTION_EVENT_ACTION_DOWN, event->getAction());
+
+    // Ensure that the events from the "getRaw" API are in logical display coordinates.
+    EXPECT_EQ(300, event->getRawX(0));
+    EXPECT_EQ(880, event->getRawY(0));
+
+    // Ensure that the x and y values are in the window's coordinate space.
+    // The left-top of the second window is at (100, 200) in display space, which is (200, 800) in
+    // the logical display space. This will be the origin of the window space.
+    EXPECT_EQ(100, event->getX(0));
+    EXPECT_EQ(80, event->getY(0));
+}
+
+using TransferFunction = std::function<bool(const std::unique_ptr<InputDispatcher>& dispatcher,
+                                            sp<IBinder>, sp<IBinder>)>;
 
 class TransferTouchFixture : public InputDispatcherTest,
                              public ::testing::WithParamInterface<TransferFunction> {};
@@ -1798,8 +2434,10 @@
     // Create a couple of windows
     sp<FakeWindowHandle> firstWindow =
             new FakeWindowHandle(application, mDispatcher, "First Window", ADISPLAY_ID_DEFAULT);
+    firstWindow->setPreventSplitting(true);
     sp<FakeWindowHandle> secondWindow =
             new FakeWindowHandle(application, mDispatcher, "Second Window", ADISPLAY_ID_DEFAULT);
+    secondWindow->setPreventSplitting(true);
 
     // Add the windows to the dispatcher
     mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {firstWindow, secondWindow}}});
@@ -1815,9 +2453,7 @@
 
     // Send pointer down to the first window
     NotifyMotionArgs pointerDownMotionArgs =
-            generateMotionArgs(AMOTION_EVENT_ACTION_POINTER_DOWN |
-                                       (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                               AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+            generateMotionArgs(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
                                {touchPoint, touchPoint});
     mDispatcher->notifyMotion(&pointerDownMotionArgs);
     // Only the first window should get the pointer down event
@@ -1835,9 +2471,7 @@
 
     // Send pointer up to the second window
     NotifyMotionArgs pointerUpMotionArgs =
-            generateMotionArgs(AMOTION_EVENT_ACTION_POINTER_UP |
-                                       (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                               AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+            generateMotionArgs(POINTER_1_UP, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
                                {touchPoint, touchPoint});
     mDispatcher->notifyMotion(&pointerUpMotionArgs);
     // The first window gets nothing and the second gets pointer up
@@ -1859,12 +2493,12 @@
 // for the case where there are multiple pointers split across several windows.
 INSTANTIATE_TEST_SUITE_P(TransferFunctionTests, TransferTouchFixture,
                          ::testing::Values(
-                                 [&](sp<InputDispatcher> dispatcher, sp<IBinder> /*ignored*/,
-                                     sp<IBinder> destChannelToken) {
+                                 [&](const std::unique_ptr<InputDispatcher>& dispatcher,
+                                     sp<IBinder> /*ignored*/, sp<IBinder> destChannelToken) {
                                      return dispatcher->transferTouch(destChannelToken);
                                  },
-                                 [&](sp<InputDispatcher> dispatcher, sp<IBinder> from,
-                                     sp<IBinder> to) {
+                                 [&](const std::unique_ptr<InputDispatcher>& dispatcher,
+                                     sp<IBinder> from, sp<IBinder> to) {
                                      return dispatcher->transferTouchFocus(from, to,
                                                                            false /*isDragAndDrop*/);
                                  }));
@@ -1872,17 +2506,13 @@
 TEST_F(InputDispatcherTest, TransferTouchFocus_TwoPointersSplitTouch) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
 
-    // Create a non touch modal window that supports split touch
     sp<FakeWindowHandle> firstWindow =
             new FakeWindowHandle(application, mDispatcher, "First Window", ADISPLAY_ID_DEFAULT);
     firstWindow->setFrame(Rect(0, 0, 600, 400));
-    firstWindow->setFlags(WindowInfo::Flag::NOT_TOUCH_MODAL | WindowInfo::Flag::SPLIT_TOUCH);
 
-    // Create a non touch modal window that supports split touch
     sp<FakeWindowHandle> secondWindow =
             new FakeWindowHandle(application, mDispatcher, "Second Window", ADISPLAY_ID_DEFAULT);
     secondWindow->setFrame(Rect(0, 400, 600, 800));
-    secondWindow->setFlags(WindowInfo::Flag::NOT_TOUCH_MODAL | WindowInfo::Flag::SPLIT_TOUCH);
 
     // Add the windows to the dispatcher
     mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {firstWindow, secondWindow}}});
@@ -1901,9 +2531,7 @@
 
     // Send down to the second window
     NotifyMotionArgs secondDownMotionArgs =
-            generateMotionArgs(AMOTION_EVENT_ACTION_POINTER_DOWN |
-                                       (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                               AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+            generateMotionArgs(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
                                {pointInFirst, pointInSecond});
     mDispatcher->notifyMotion(&secondDownMotionArgs);
     // The first window gets a move and the second a down
@@ -1918,9 +2546,7 @@
 
     // Send pointer up to the second window
     NotifyMotionArgs pointerUpMotionArgs =
-            generateMotionArgs(AMOTION_EVENT_ACTION_POINTER_UP |
-                                       (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                               AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+            generateMotionArgs(POINTER_1_UP, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
                                {pointInFirst, pointInSecond});
     mDispatcher->notifyMotion(&pointerUpMotionArgs);
     // The first window gets nothing and the second gets pointer up
@@ -1944,17 +2570,13 @@
 TEST_F(InputDispatcherTest, TransferTouch_TwoPointersSplitTouch) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
 
-    // Create a non touch modal window that supports split touch
     sp<FakeWindowHandle> firstWindow =
             new FakeWindowHandle(application, mDispatcher, "First Window", ADISPLAY_ID_DEFAULT);
     firstWindow->setFrame(Rect(0, 0, 600, 400));
-    firstWindow->setFlags(WindowInfo::Flag::NOT_TOUCH_MODAL | WindowInfo::Flag::SPLIT_TOUCH);
 
-    // Create a non touch modal window that supports split touch
     sp<FakeWindowHandle> secondWindow =
             new FakeWindowHandle(application, mDispatcher, "Second Window", ADISPLAY_ID_DEFAULT);
     secondWindow->setFrame(Rect(0, 400, 600, 800));
-    secondWindow->setFlags(WindowInfo::Flag::NOT_TOUCH_MODAL | WindowInfo::Flag::SPLIT_TOUCH);
 
     // Add the windows to the dispatcher
     mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {firstWindow, secondWindow}}});
@@ -1973,9 +2595,7 @@
 
     // Send down to the second window
     NotifyMotionArgs secondDownMotionArgs =
-            generateMotionArgs(AMOTION_EVENT_ACTION_POINTER_DOWN |
-                                       (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                               AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+            generateMotionArgs(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
                                {pointInFirst, pointInSecond});
     mDispatcher->notifyMotion(&secondDownMotionArgs);
     // The first window gets a move and the second a down
@@ -1992,9 +2612,7 @@
     // The rest of the dispatch should proceed as normal
     // Send pointer up to the second window
     NotifyMotionArgs pointerUpMotionArgs =
-            generateMotionArgs(AMOTION_EVENT_ACTION_POINTER_UP |
-                                       (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                               AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+            generateMotionArgs(POINTER_1_UP, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
                                {pointInFirst, pointInSecond});
     mDispatcher->notifyMotion(&pointerUpMotionArgs);
     // The first window gets MOVE and the second gets pointer up
@@ -2019,26 +2637,21 @@
     sp<FakeWindowHandle> firstWindowInPrimary =
             new FakeWindowHandle(application, mDispatcher, "D_1_W1", ADISPLAY_ID_DEFAULT);
     firstWindowInPrimary->setFrame(Rect(0, 0, 100, 100));
-    firstWindowInPrimary->setFlags(WindowInfo::Flag::NOT_TOUCH_MODAL);
     sp<FakeWindowHandle> secondWindowInPrimary =
             new FakeWindowHandle(application, mDispatcher, "D_1_W2", ADISPLAY_ID_DEFAULT);
     secondWindowInPrimary->setFrame(Rect(100, 0, 200, 100));
-    secondWindowInPrimary->setFlags(WindowInfo::Flag::NOT_TOUCH_MODAL);
 
     sp<FakeWindowHandle> mirrorWindowInPrimary =
             firstWindowInPrimary->clone(application, mDispatcher, ADISPLAY_ID_DEFAULT);
     mirrorWindowInPrimary->setFrame(Rect(0, 100, 100, 200));
-    mirrorWindowInPrimary->setFlags(WindowInfo::Flag::NOT_TOUCH_MODAL);
 
     sp<FakeWindowHandle> firstWindowInSecondary =
             firstWindowInPrimary->clone(application, mDispatcher, SECOND_DISPLAY_ID);
     firstWindowInSecondary->setFrame(Rect(0, 0, 100, 100));
-    firstWindowInSecondary->setFlags(WindowInfo::Flag::NOT_TOUCH_MODAL);
 
     sp<FakeWindowHandle> secondWindowInSecondary =
             secondWindowInPrimary->clone(application, mDispatcher, SECOND_DISPLAY_ID);
     secondWindowInPrimary->setFrame(Rect(100, 0, 200, 100));
-    secondWindowInPrimary->setFlags(WindowInfo::Flag::NOT_TOUCH_MODAL);
 
     // Update window info, let it find window handle of second display first.
     mDispatcher->setInputWindows(
@@ -2083,26 +2696,21 @@
     sp<FakeWindowHandle> firstWindowInPrimary =
             new FakeWindowHandle(application, mDispatcher, "D_1_W1", ADISPLAY_ID_DEFAULT);
     firstWindowInPrimary->setFrame(Rect(0, 0, 100, 100));
-    firstWindowInPrimary->setFlags(WindowInfo::Flag::NOT_TOUCH_MODAL);
     sp<FakeWindowHandle> secondWindowInPrimary =
             new FakeWindowHandle(application, mDispatcher, "D_1_W2", ADISPLAY_ID_DEFAULT);
     secondWindowInPrimary->setFrame(Rect(100, 0, 200, 100));
-    secondWindowInPrimary->setFlags(WindowInfo::Flag::NOT_TOUCH_MODAL);
 
     sp<FakeWindowHandle> mirrorWindowInPrimary =
             firstWindowInPrimary->clone(application, mDispatcher, ADISPLAY_ID_DEFAULT);
     mirrorWindowInPrimary->setFrame(Rect(0, 100, 100, 200));
-    mirrorWindowInPrimary->setFlags(WindowInfo::Flag::NOT_TOUCH_MODAL);
 
     sp<FakeWindowHandle> firstWindowInSecondary =
             firstWindowInPrimary->clone(application, mDispatcher, SECOND_DISPLAY_ID);
     firstWindowInSecondary->setFrame(Rect(0, 0, 100, 100));
-    firstWindowInSecondary->setFlags(WindowInfo::Flag::NOT_TOUCH_MODAL);
 
     sp<FakeWindowHandle> secondWindowInSecondary =
             secondWindowInPrimary->clone(application, mDispatcher, SECOND_DISPLAY_ID);
     secondWindowInPrimary->setFrame(Rect(100, 0, 200, 100));
-    secondWindowInPrimary->setFlags(WindowInfo::Flag::NOT_TOUCH_MODAL);
 
     // Update window info, let it find window handle of second display first.
     mDispatcher->setInputWindows(
@@ -2196,17 +2804,13 @@
 TEST_F(InputDispatcherTest, PointerCancel_SendCancelWhenSplitTouch) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
 
-    // Create first non touch modal window that supports split touch
     sp<FakeWindowHandle> firstWindow =
             new FakeWindowHandle(application, mDispatcher, "First Window", ADISPLAY_ID_DEFAULT);
     firstWindow->setFrame(Rect(0, 0, 600, 400));
-    firstWindow->setFlags(WindowInfo::Flag::NOT_TOUCH_MODAL | WindowInfo::Flag::SPLIT_TOUCH);
 
-    // Create second non touch modal window that supports split touch
     sp<FakeWindowHandle> secondWindow =
             new FakeWindowHandle(application, mDispatcher, "Second Window", ADISPLAY_ID_DEFAULT);
     secondWindow->setFrame(Rect(0, 400, 600, 800));
-    secondWindow->setFlags(WindowInfo::Flag::NOT_TOUCH_MODAL | WindowInfo::Flag::SPLIT_TOUCH);
 
     // Add the windows to the dispatcher
     mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {firstWindow, secondWindow}}});
@@ -2225,9 +2829,7 @@
 
     // Send down to the second window
     NotifyMotionArgs secondDownMotionArgs =
-            generateMotionArgs(AMOTION_EVENT_ACTION_POINTER_DOWN |
-                                       (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                               AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+            generateMotionArgs(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
                                {pointInFirst, pointInSecond});
     mDispatcher->notifyMotion(&secondDownMotionArgs);
     // The first window gets a move and the second a down
@@ -2236,9 +2838,7 @@
 
     // Send pointer cancel to the second window
     NotifyMotionArgs pointerUpMotionArgs =
-            generateMotionArgs(AMOTION_EVENT_ACTION_POINTER_UP |
-                                       (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                               AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+            generateMotionArgs(POINTER_1_UP, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
                                {pointInFirst, pointInSecond});
     pointerUpMotionArgs.flags |= AMOTION_EVENT_FLAG_CANCELED;
     mDispatcher->notifyMotion(&pointerUpMotionArgs);
@@ -2273,10 +2873,10 @@
 
 class FakeMonitorReceiver {
 public:
-    FakeMonitorReceiver(const sp<InputDispatcher>& dispatcher, const std::string name,
-                        int32_t displayId, bool isGestureMonitor = false) {
+    FakeMonitorReceiver(const std::unique_ptr<InputDispatcher>& dispatcher, const std::string name,
+                        int32_t displayId) {
         base::Result<std::unique_ptr<InputChannel>> channel =
-                dispatcher->createInputMonitor(displayId, isGestureMonitor, name, MONITOR_PID);
+                dispatcher->createInputMonitor(displayId, name, MONITOR_PID);
         mInputReceiver = std::make_unique<FakeInputReceiver>(std::move(*channel), name);
     }
 
@@ -2296,6 +2896,11 @@
                                      expectedDisplayId, expectedFlags);
     }
 
+    void consumeMotionMove(int32_t expectedDisplayId, int32_t expectedFlags = 0) {
+        mInputReceiver->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_MOVE,
+                                     expectedDisplayId, expectedFlags);
+    }
+
     void consumeMotionUp(int32_t expectedDisplayId, int32_t expectedFlags = 0) {
         mInputReceiver->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_UP,
                                      expectedDisplayId, expectedFlags);
@@ -2332,15 +2937,64 @@
     std::unique_ptr<FakeInputReceiver> mInputReceiver;
 };
 
-// Tests for gesture monitors
-TEST_F(InputDispatcherTest, GestureMonitor_ReceivesMotionEvents) {
+using InputDispatcherMonitorTest = InputDispatcherTest;
+
+/**
+ * Two entities that receive touch: A window, and a global monitor.
+ * The touch goes to the window, and then the window disappears.
+ * The monitor does not get cancel right away. But if more events come in, the touch gets canceled
+ * for the monitor, as well.
+ * 1. foregroundWindow
+ * 2. monitor <-- global monitor (doesn't observe z order, receives all events)
+ */
+TEST_F(InputDispatcherMonitorTest, MonitorTouchIsCanceledWhenForegroundWindowDisappears) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> window =
+            new FakeWindowHandle(application, mDispatcher, "Foreground", ADISPLAY_ID_DEFAULT);
+
+    FakeMonitorReceiver monitor = FakeMonitorReceiver(mDispatcher, "M_1", ADISPLAY_ID_DEFAULT);
+
+    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+                               {100, 200}))
+            << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
+
+    // Both the foreground window and the global monitor should receive the touch down
+    window->consumeMotionDown();
+    monitor.consumeMotionDown(ADISPLAY_ID_DEFAULT);
+
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionEvent(mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
+                                ADISPLAY_ID_DEFAULT, {110, 200}))
+            << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
+
+    window->consumeMotionMove();
+    monitor.consumeMotionMove(ADISPLAY_ID_DEFAULT);
+
+    // Now the foreground window goes away
+    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {}}});
+    window->consumeMotionCancel();
+    monitor.assertNoEvents(); // Global monitor does not get a cancel yet
+
+    // If more events come in, there will be no more foreground window to send them to. This will
+    // cause a cancel for the monitor, as well.
+    ASSERT_EQ(InputEventInjectionResult::FAILED,
+              injectMotionEvent(mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
+                                ADISPLAY_ID_DEFAULT, {120, 200}))
+            << "Injection should fail because the window was removed";
+    window->assertNoEvents();
+    // Global monitor now gets the cancel
+    monitor.consumeMotionCancel(ADISPLAY_ID_DEFAULT);
+}
+
+TEST_F(InputDispatcherMonitorTest, ReceivesMotionEvents) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
     sp<FakeWindowHandle> window =
             new FakeWindowHandle(application, mDispatcher, "Fake Window", ADISPLAY_ID_DEFAULT);
     mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
 
-    FakeMonitorReceiver monitor = FakeMonitorReceiver(mDispatcher, "GM_1", ADISPLAY_ID_DEFAULT,
-                                                      true /*isGestureMonitor*/);
+    FakeMonitorReceiver monitor = FakeMonitorReceiver(mDispatcher, "M_1", ADISPLAY_ID_DEFAULT);
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
@@ -2349,71 +3003,34 @@
     monitor.consumeMotionDown(ADISPLAY_ID_DEFAULT);
 }
 
-TEST_F(InputDispatcherTest, GestureMonitor_DoesNotReceiveKeyEvents) {
-    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window =
-            new FakeWindowHandle(application, mDispatcher, "Fake Window", ADISPLAY_ID_DEFAULT);
+TEST_F(InputDispatcherMonitorTest, MonitorCannotPilferPointers) {
+    FakeMonitorReceiver monitor = FakeMonitorReceiver(mDispatcher, "M_1", ADISPLAY_ID_DEFAULT);
 
-    mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
-    window->setFocusable(true);
-
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
-    setFocusedWindow(window);
-
-    window->consumeFocusEvent(true);
-
-    FakeMonitorReceiver monitor = FakeMonitorReceiver(mDispatcher, "GM_1", ADISPLAY_ID_DEFAULT,
-                                                      true /*isGestureMonitor*/);
-
-    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyDown(mDispatcher, ADISPLAY_ID_DEFAULT))
-            << "Inject key event should return InputEventInjectionResult::SUCCEEDED";
-    window->consumeKeyDown(ADISPLAY_ID_DEFAULT);
-    monitor.assertNoEvents();
-}
-
-TEST_F(InputDispatcherTest, GestureMonitor_CanPilferAfterWindowIsRemovedMidStream) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
     sp<FakeWindowHandle> window =
             new FakeWindowHandle(application, mDispatcher, "Fake Window", ADISPLAY_ID_DEFAULT);
     mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
 
-    FakeMonitorReceiver monitor = FakeMonitorReceiver(mDispatcher, "GM_1", ADISPLAY_ID_DEFAULT,
-                                                      true /*isGestureMonitor*/);
-
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
-    window->consumeMotionDown(ADISPLAY_ID_DEFAULT);
     monitor.consumeMotionDown(ADISPLAY_ID_DEFAULT);
+    window->consumeMotionDown(ADISPLAY_ID_DEFAULT);
 
-    window->releaseChannel();
-
-    mDispatcher->pilferPointers(monitor.getToken());
+    // Pilfer pointers from the monitor.
+    // This should not do anything and the window should continue to receive events.
+    EXPECT_NE(OK, mDispatcher->pilferPointers(monitor.getToken()));
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionUp(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
+              injectMotionEvent(mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
+                                ADISPLAY_ID_DEFAULT))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
-    monitor.consumeMotionUp(ADISPLAY_ID_DEFAULT);
+
+    monitor.consumeMotionMove(ADISPLAY_ID_DEFAULT);
+    window->consumeMotionMove(ADISPLAY_ID_DEFAULT);
 }
 
-TEST_F(InputDispatcherTest, UnresponsiveGestureMonitor_GetsAnr) {
-    FakeMonitorReceiver monitor =
-            FakeMonitorReceiver(mDispatcher, "Gesture monitor", ADISPLAY_ID_DEFAULT,
-                                true /*isGestureMonitor*/);
-
-    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT));
-    std::optional<uint32_t> consumeSeq = monitor.receiveEvent();
-    ASSERT_TRUE(consumeSeq);
-
-    mFakePolicy->assertNotifyMonitorUnresponsiveWasCalled(DISPATCHING_TIMEOUT);
-    monitor.finishEvent(*consumeSeq);
-    ASSERT_TRUE(mDispatcher->waitForIdle());
-    mFakePolicy->assertNotifyMonitorResponsiveWasCalled();
-}
-
-// Tests for gesture monitors
-TEST_F(InputDispatcherTest, GestureMonitor_NoWindowTransform) {
+TEST_F(InputDispatcherMonitorTest, NoWindowTransform) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
     sp<FakeWindowHandle> window =
             new FakeWindowHandle(application, mDispatcher, "Fake Window", ADISPLAY_ID_DEFAULT);
@@ -2421,8 +3038,7 @@
     window->setWindowOffset(20, 40);
     window->setWindowTransform(0, 1, -1, 0);
 
-    FakeMonitorReceiver monitor = FakeMonitorReceiver(mDispatcher, "GM_1", ADISPLAY_ID_DEFAULT,
-                                                      true /*isGestureMonitor*/);
+    FakeMonitorReceiver monitor = FakeMonitorReceiver(mDispatcher, "M_1", ADISPLAY_ID_DEFAULT);
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
@@ -2433,6 +3049,16 @@
     ASSERT_EQ(ui::Transform(), event->getTransform());
 }
 
+TEST_F(InputDispatcherMonitorTest, InjectionFailsWithNoWindow) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    FakeMonitorReceiver monitor = FakeMonitorReceiver(mDispatcher, "M_1", ADISPLAY_ID_DEFAULT);
+
+    ASSERT_EQ(InputEventInjectionResult::FAILED,
+              injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
+            << "Injection should fail if there is a monitor, but no touchable window";
+    monitor.assertNoEvents();
+}
+
 TEST_F(InputDispatcherTest, TestMoveEvent) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
     sp<FakeWindowHandle> window =
@@ -2459,91 +3085,6 @@
                          0 /*expectedFlags*/);
 }
 
-TEST_F(InputDispatcherTest, GestureMonitor_SplitIfNoWindowTouched) {
-    FakeMonitorReceiver monitor = FakeMonitorReceiver(mDispatcher, "GM_1", ADISPLAY_ID_DEFAULT,
-                                                      true /*isGestureMonitor*/);
-
-    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    // Create a non touch modal window that supports split touch
-    sp<FakeWindowHandle> window =
-            new FakeWindowHandle(application, mDispatcher, "Fake Window", ADISPLAY_ID_DEFAULT);
-    window->setFrame(Rect(0, 0, 100, 100));
-    window->setFlags(WindowInfo::Flag::NOT_TOUCH_MODAL | WindowInfo::Flag::SPLIT_TOUCH);
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
-
-    // First finger down, no window touched.
-    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                               {100, 200}))
-            << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
-    monitor.consumeMotionDown(ADISPLAY_ID_DEFAULT);
-    window->assertNoEvents();
-
-    // Second finger down on window, the window should receive touch down.
-    const MotionEvent secondFingerDownEvent =
-            MotionEventBuilder(AMOTION_EVENT_ACTION_POINTER_DOWN |
-                                       (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                               AINPUT_SOURCE_TOUCHSCREEN)
-                    .displayId(ADISPLAY_ID_DEFAULT)
-                    .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
-                    .pointer(PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER)
-                                     .x(100)
-                                     .y(200))
-                    .pointer(PointerBuilder(/* id */ 1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50))
-                    .build();
-    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionEvent(mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT,
-                                InputEventInjectionSync::WAIT_FOR_RESULT))
-            << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
-
-    window->consumeMotionDown(ADISPLAY_ID_DEFAULT);
-    monitor.consumeMotionPointerDown(1 /* pointerIndex */);
-}
-
-TEST_F(InputDispatcherTest, GestureMonitor_NoSplitAfterPilfer) {
-    FakeMonitorReceiver monitor = FakeMonitorReceiver(mDispatcher, "GM_1", ADISPLAY_ID_DEFAULT,
-                                                      true /*isGestureMonitor*/);
-
-    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    // Create a non touch modal window that supports split touch
-    sp<FakeWindowHandle> window =
-            new FakeWindowHandle(application, mDispatcher, "Fake Window", ADISPLAY_ID_DEFAULT);
-    window->setFrame(Rect(0, 0, 100, 100));
-    window->setFlags(WindowInfo::Flag::NOT_TOUCH_MODAL | WindowInfo::Flag::SPLIT_TOUCH);
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
-
-    // First finger down, no window touched.
-    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                               {100, 200}))
-            << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
-    monitor.consumeMotionDown(ADISPLAY_ID_DEFAULT);
-    window->assertNoEvents();
-
-    // Gesture monitor pilfer the pointers.
-    mDispatcher->pilferPointers(monitor.getToken());
-
-    // Second finger down on window, the window should not receive touch down.
-    const MotionEvent secondFingerDownEvent =
-            MotionEventBuilder(AMOTION_EVENT_ACTION_POINTER_DOWN |
-                                       (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                               AINPUT_SOURCE_TOUCHSCREEN)
-                    .displayId(ADISPLAY_ID_DEFAULT)
-                    .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
-                    .pointer(PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER)
-                                     .x(100)
-                                     .y(200))
-                    .pointer(PointerBuilder(/* id */ 1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50))
-                    .build();
-    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionEvent(mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT,
-                                InputEventInjectionSync::WAIT_FOR_RESULT))
-            << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
-
-    window->assertNoEvents();
-    monitor.consumeMotionPointerDown(1 /* pointerIndex */);
-}
-
 /**
  * Dispatcher has touch mode enabled by default. Typically, the policy overrides that value to
  * the device default right away. In the test scenario, we check both the default value,
@@ -2553,6 +3094,7 @@
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
     sp<FakeWindowHandle> window =
             new FakeWindowHandle(application, mDispatcher, "Test window", ADISPLAY_ID_DEFAULT);
+    const WindowInfo& windowInfo = *window->getInfo();
 
     // Set focused application.
     mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
@@ -2561,7 +3103,6 @@
     SCOPED_TRACE("Check default value of touch mode");
     mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
     setFocusedWindow(window);
-
     window->consumeFocusEvent(true /*hasFocus*/, true /*inTouchMode*/);
 
     SCOPED_TRACE("Remove the window to trigger focus loss");
@@ -2570,7 +3111,9 @@
     window->consumeFocusEvent(false /*hasFocus*/, true /*inTouchMode*/);
 
     SCOPED_TRACE("Disable touch mode");
-    mDispatcher->setInTouchMode(false);
+    mDispatcher->setInTouchMode(false, windowInfo.ownerPid, windowInfo.ownerUid,
+                                /* hasPermission */ true);
+    window->consumeTouchModeEvent(false);
     window->setFocusable(true);
     mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
     setFocusedWindow(window);
@@ -2582,7 +3125,9 @@
     window->consumeFocusEvent(false /*hasFocus*/, false /*inTouchMode*/);
 
     SCOPED_TRACE("Enable touch mode again");
-    mDispatcher->setInTouchMode(true);
+    mDispatcher->setInTouchMode(true, windowInfo.ownerPid, windowInfo.ownerUid,
+                                /* hasPermission */ true);
+    window->consumeTouchModeEvent(true);
     window->setFocusable(true);
     mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
     setFocusedWindow(window);
@@ -2622,8 +3167,8 @@
     const VerifiedKeyEvent& verifiedKey = static_cast<const VerifiedKeyEvent&>(*verified);
 
     ASSERT_EQ(keyArgs.action, verifiedKey.action);
-    ASSERT_EQ(keyArgs.downTime, verifiedKey.downTimeNanos);
     ASSERT_EQ(keyArgs.flags & VERIFIED_KEY_EVENT_FLAGS, verifiedKey.flags);
+    ASSERT_EQ(keyArgs.downTime, verifiedKey.downTimeNanos);
     ASSERT_EQ(keyArgs.keyCode, verifiedKey.keyCode);
     ASSERT_EQ(keyArgs.scanCode, verifiedKey.scanCode);
     ASSERT_EQ(keyArgs.metaState, verifiedKey.metaState);
@@ -2637,7 +3182,14 @@
 
     mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
 
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+    ui::Transform transform;
+    transform.set({1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 0, 0, 1});
+
+    gui::DisplayInfo displayInfo;
+    displayInfo.displayId = ADISPLAY_ID_DEFAULT;
+    displayInfo.transform = transform;
+
+    mDispatcher->onWindowInfosChanged({*window->getInfo()}, {displayInfo});
 
     NotifyMotionArgs motionArgs =
             generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
@@ -2658,68 +3210,18 @@
 
     const VerifiedMotionEvent& verifiedMotion = static_cast<const VerifiedMotionEvent&>(*verified);
 
-    EXPECT_EQ(motionArgs.pointerCoords[0].getX(), verifiedMotion.rawX);
-    EXPECT_EQ(motionArgs.pointerCoords[0].getY(), verifiedMotion.rawY);
+    const vec2 rawXY =
+            MotionEvent::calculateTransformedXY(motionArgs.source, transform,
+                                                motionArgs.pointerCoords[0].getXYValue());
+    EXPECT_EQ(rawXY.x, verifiedMotion.rawX);
+    EXPECT_EQ(rawXY.y, verifiedMotion.rawY);
     EXPECT_EQ(motionArgs.action & AMOTION_EVENT_ACTION_MASK, verifiedMotion.actionMasked);
-    EXPECT_EQ(motionArgs.downTime, verifiedMotion.downTimeNanos);
     EXPECT_EQ(motionArgs.flags & VERIFIED_MOTION_EVENT_FLAGS, verifiedMotion.flags);
+    EXPECT_EQ(motionArgs.downTime, verifiedMotion.downTimeNanos);
     EXPECT_EQ(motionArgs.metaState, verifiedMotion.metaState);
     EXPECT_EQ(motionArgs.buttonState, verifiedMotion.buttonState);
 }
 
-TEST_F(InputDispatcherTest, NonPointerMotionEvent_JoystickAndTouchpadNotTransformed) {
-    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window =
-            new FakeWindowHandle(application, mDispatcher, "Test window", ADISPLAY_ID_DEFAULT);
-    const std::string name = window->getName();
-
-    // Window gets transformed by offset values.
-    window->setWindowOffset(500.0f, 500.0f);
-
-    mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
-    window->setFocusable(true);
-
-    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
-
-    // First, we set focused window so that focusedWindowHandle is not null.
-    setFocusedWindow(window);
-
-    // Second, we consume focus event if it is right or wrong according to onFocusChangedLocked.
-    window->consumeFocusEvent(true);
-
-    constexpr const std::array nonTransformedSources = {std::pair(AINPUT_SOURCE_TOUCHPAD,
-                                                                  AMOTION_EVENT_ACTION_DOWN),
-                                                        std::pair(AINPUT_SOURCE_JOYSTICK,
-                                                                  AMOTION_EVENT_ACTION_MOVE)};
-    for (const auto& [source, action] : nonTransformedSources) {
-        const NotifyMotionArgs motionArgs = generateMotionArgs(action, source, ADISPLAY_ID_DEFAULT);
-        mDispatcher->notifyMotion(&motionArgs);
-
-        MotionEvent* event = window->consumeMotion();
-        ASSERT_NE(event, nullptr);
-
-        const MotionEvent& motionEvent = *event;
-        EXPECT_EQ(action, motionEvent.getAction());
-        EXPECT_EQ(motionArgs.pointerCount, motionEvent.getPointerCount());
-
-        float expectedX = motionArgs.pointerCoords[0].getX();
-        float expectedY = motionArgs.pointerCoords[0].getY();
-
-        // Ensure the axis values from the final motion event are not transformed.
-        EXPECT_EQ(expectedX, motionEvent.getX(0))
-                << "expected " << expectedX << " for x coord of " << name.c_str() << ", got "
-                << motionEvent.getX(0);
-        EXPECT_EQ(expectedY, motionEvent.getY(0))
-                << "expected " << expectedY << " for y coord of " << name.c_str() << ", got "
-                << motionEvent.getY(0);
-        // Ensure the raw and transformed axis values for the motion event are the same.
-        EXPECT_EQ(motionEvent.getRawX(0), motionEvent.getX(0))
-                << "expected raw and transformed X-axis values to be equal";
-        EXPECT_EQ(motionEvent.getRawY(0), motionEvent.getY(0))
-                << "expected raw and transformed Y-axis values to be equal";
-    }
-}
-
 /**
  * Ensure that separate calls to sign the same data are generating the same key.
  * We avoid asserting against INVALID_HMAC. Since the key is random, there is a non-zero chance
@@ -2824,9 +3326,9 @@
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
     sp<FakeWindowHandle> window =
             new FakeWindowHandle(application, mDispatcher, "TestWindow", ADISPLAY_ID_DEFAULT);
+    window->setFocusable(false);
     mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
 
-    // Window is not focusable.
     mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
     setFocusedWindow(window);
 
@@ -2834,7 +3336,7 @@
     ASSERT_EQ(InputEventInjectionResult::TIMED_OUT, injectKeyDown(mDispatcher))
             << "Inject key event should return InputEventInjectionResult::TIMED_OUT";
 
-    // window is invalid, so it should not receive any input event.
+    // window is not focusable, so it should not receive any input event.
     window->assertNoEvents();
 }
 
@@ -2971,7 +3473,7 @@
 
     sp<FakeWindowHandle> slipperyExitWindow =
             new FakeWindowHandle(application, mDispatcher, "Top", ADISPLAY_ID_DEFAULT);
-    slipperyExitWindow->setFlags(WindowInfo::Flag::NOT_TOUCH_MODAL | WindowInfo::Flag::SLIPPERY);
+    slipperyExitWindow->setSlippery(true);
     // Make sure this one overlaps the bottom window
     slipperyExitWindow->setFrame(Rect(25, 25, 75, 75));
     // Change the owner uid/pid of the window so that it is considered to be occluding the bottom
@@ -3015,7 +3517,7 @@
     virtual void SetUp() override {
         mFakePolicy = new FakeInputDispatcherPolicy();
         mFakePolicy->setKeyRepeatConfiguration(KEY_REPEAT_TIMEOUT, KEY_REPEAT_DELAY);
-        mDispatcher = new InputDispatcher(mFakePolicy);
+        mDispatcher = std::make_unique<InputDispatcher>(mFakePolicy);
         mDispatcher->setInputDispatchMode(/*enabled*/ true, /*frozen*/ false);
         ASSERT_EQ(OK, mDispatcher->start());
 
@@ -3316,9 +3818,53 @@
     secondWindowInPrimary->consumeKeyDown(ADISPLAY_ID_DEFAULT);
 }
 
+TEST_F(InputDispatcherFocusOnTwoDisplaysTest, CancelTouch_MultiDisplay) {
+    FakeMonitorReceiver monitorInPrimary =
+            FakeMonitorReceiver(mDispatcher, "M_1", ADISPLAY_ID_DEFAULT);
+    FakeMonitorReceiver monitorInSecondary =
+            FakeMonitorReceiver(mDispatcher, "M_2", SECOND_DISPLAY_ID);
+
+    // Test touch down on primary display.
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
+            << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
+    windowInPrimary->consumeMotionDown(ADISPLAY_ID_DEFAULT);
+    monitorInPrimary.consumeMotionDown(ADISPLAY_ID_DEFAULT);
+
+    // Test touch down on second display.
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, SECOND_DISPLAY_ID))
+            << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
+    windowInSecondary->consumeMotionDown(SECOND_DISPLAY_ID);
+    monitorInSecondary.consumeMotionDown(SECOND_DISPLAY_ID);
+
+    // Trigger cancel touch.
+    mDispatcher->cancelCurrentTouch();
+    windowInPrimary->consumeMotionCancel(ADISPLAY_ID_DEFAULT);
+    monitorInPrimary.consumeMotionCancel(ADISPLAY_ID_DEFAULT);
+    windowInSecondary->consumeMotionCancel(SECOND_DISPLAY_ID);
+    monitorInSecondary.consumeMotionCancel(SECOND_DISPLAY_ID);
+
+    // Test inject a move motion event, no window/monitor should receive the event.
+    ASSERT_EQ(InputEventInjectionResult::FAILED,
+              injectMotionEvent(mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
+                                ADISPLAY_ID_DEFAULT, {110, 200}))
+            << "Inject motion event should return InputEventInjectionResult::FAILED";
+    windowInPrimary->assertNoEvents();
+    monitorInPrimary.assertNoEvents();
+
+    ASSERT_EQ(InputEventInjectionResult::FAILED,
+              injectMotionEvent(mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
+                                SECOND_DISPLAY_ID, {110, 200}))
+            << "Inject motion event should return InputEventInjectionResult::FAILED";
+    windowInSecondary->assertNoEvents();
+    monitorInSecondary.assertNoEvents();
+}
+
 class InputFilterTest : public InputDispatcherTest {
 protected:
-    void testNotifyMotion(int32_t displayId, bool expectToBeFiltered) {
+    void testNotifyMotion(int32_t displayId, bool expectToBeFiltered,
+                          const ui::Transform& transform = ui::Transform()) {
         NotifyMotionArgs motionArgs;
 
         motionArgs =
@@ -3329,7 +3875,8 @@
         mDispatcher->notifyMotion(&motionArgs);
         ASSERT_TRUE(mDispatcher->waitForIdle());
         if (expectToBeFiltered) {
-            mFakePolicy->assertFilterInputEventWasCalled(motionArgs);
+            const auto xy = transform.transform(motionArgs.pointerCoords->getXYValue());
+            mFakePolicy->assertFilterInputEventWasCalled(motionArgs, xy);
         } else {
             mFakePolicy->assertFilterInputEventWasNotCalled();
         }
@@ -3387,6 +3934,30 @@
     testNotifyKey(/*expectToBeFiltered*/ false);
 }
 
+// Ensure that MotionEvents sent to the InputFilter through InputListener are converted to the
+// logical display coordinate space.
+TEST_F(InputFilterTest, MotionEvent_UsesLogicalDisplayCoordinates_notifyMotion) {
+    ui::Transform firstDisplayTransform;
+    firstDisplayTransform.set({1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 0, 0, 1});
+    ui::Transform secondDisplayTransform;
+    secondDisplayTransform.set({-6.6, -5.5, -4.4, -3.3, -2.2, -1.1, 0, 0, 1});
+
+    std::vector<gui::DisplayInfo> displayInfos(2);
+    displayInfos[0].displayId = ADISPLAY_ID_DEFAULT;
+    displayInfos[0].transform = firstDisplayTransform;
+    displayInfos[1].displayId = SECOND_DISPLAY_ID;
+    displayInfos[1].transform = secondDisplayTransform;
+
+    mDispatcher->onWindowInfosChanged({}, displayInfos);
+
+    // Enable InputFilter
+    mDispatcher->setInputFilterEnabled(true);
+
+    // Ensure the correct transforms are used for the displays.
+    testNotifyMotion(ADISPLAY_ID_DEFAULT, /*expectToBeFiltered*/ true, firstDisplayTransform);
+    testNotifyMotion(SECOND_DISPLAY_ID, /*expectToBeFiltered*/ true, secondDisplayTransform);
+}
+
 class InputFilterInjectionPolicyTest : public InputDispatcherTest {
 protected:
     virtual void SetUp() override {
@@ -3451,8 +4022,7 @@
                          DISPLAY_ID, INVALID_HMAC, AMOTION_EVENT_ACTION_DOWN, 0, 0,
                          AMOTION_EVENT_EDGE_FLAG_NONE, AMETA_NONE, 0, MotionClassification::NONE,
                          identityTransform, 0, 0, AMOTION_EVENT_INVALID_CURSOR_POSITION,
-                         AMOTION_EVENT_INVALID_CURSOR_POSITION, ui::Transform::ROT_0,
-                         0 /*INVALID_DISPLAY_SIZE*/, 0 /*INVALID_DISPLAY_SIZE*/, eventTime,
+                         AMOTION_EVENT_INVALID_CURSOR_POSITION, identityTransform, eventTime,
                          eventTime,
                          /*pointerCount*/ 1, pointerProperties, pointerCoords);
 
@@ -3509,14 +4079,10 @@
         mUnfocusedWindow =
                 new FakeWindowHandle(application, mDispatcher, "Top", ADISPLAY_ID_DEFAULT);
         mUnfocusedWindow->setFrame(Rect(0, 0, 30, 30));
-        // Adding FLAG_NOT_TOUCH_MODAL to ensure taps outside this window are not sent to this
-        // window.
-        mUnfocusedWindow->setFlags(WindowInfo::Flag::NOT_TOUCH_MODAL);
 
         mFocusedWindow =
                 new FakeWindowHandle(application, mDispatcher, "Second", ADISPLAY_ID_DEFAULT);
         mFocusedWindow->setFrame(Rect(50, 50, 100, 100));
-        mFocusedWindow->setFlags(WindowInfo::Flag::NOT_TOUCH_MODAL);
 
         // Set focused application.
         mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
@@ -3623,14 +4189,10 @@
                 std::make_shared<FakeApplicationHandle>();
         mWindow1 = new FakeWindowHandle(application, mDispatcher, "Fake Window 1",
                                         ADISPLAY_ID_DEFAULT);
-        // Adding FLAG_NOT_TOUCH_MODAL otherwise all taps will go to the top most window.
-        // We also need FLAG_SPLIT_TOUCH or we won't be able to get touches for both windows.
-        mWindow1->setFlags(WindowInfo::Flag::NOT_TOUCH_MODAL | WindowInfo::Flag::SPLIT_TOUCH);
         mWindow1->setFrame(Rect(0, 0, 100, 100));
 
         mWindow2 = new FakeWindowHandle(application, mDispatcher, "Fake Window 2",
                                         ADISPLAY_ID_DEFAULT, mWindow1->getToken());
-        mWindow2->setFlags(WindowInfo::Flag::NOT_TOUCH_MODAL | WindowInfo::Flag::SPLIT_TOUCH);
         mWindow2->setFrame(Rect(100, 100, 200, 200));
 
         mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {mWindow1, mWindow2}}});
@@ -3659,7 +4221,7 @@
                 << " event, got " << inputEventTypeToString(event->getType()) << " event";
 
         const MotionEvent& motionEvent = static_cast<const MotionEvent&>(*event);
-        EXPECT_EQ(expectedAction, motionEvent.getAction());
+        assertMotionAction(expectedAction, motionEvent.getAction());
 
         for (size_t i = 0; i < points.size(); i++) {
             float expectedX = points[i].x;
@@ -3674,7 +4236,7 @@
         }
     }
 
-    void touchAndAssertPositions(int32_t action, std::vector<PointF> touchedPoints,
+    void touchAndAssertPositions(int32_t action, const std::vector<PointF>& touchedPoints,
                                  std::vector<PointF> expectedPoints) {
         NotifyMotionArgs motionArgs = generateMotionArgs(action, AINPUT_SOURCE_TOUCHSCREEN,
                                                          ADISPLAY_ID_DEFAULT, touchedPoints);
@@ -3732,22 +4294,18 @@
     touchAndAssertPositions(AMOTION_EVENT_ACTION_DOWN, touchedPoints, expectedPoints);
 
     // Touch Window 2
-    int32_t actionPointerDown =
-            AMOTION_EVENT_ACTION_POINTER_DOWN + (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
     touchedPoints.push_back(PointF{150, 150});
     expectedPoints.push_back(getPointInWindow(mWindow2->getInfo(), touchedPoints[1]));
-    touchAndAssertPositions(actionPointerDown, touchedPoints, expectedPoints);
+    touchAndAssertPositions(POINTER_1_DOWN, touchedPoints, expectedPoints);
 
     // Release Window 2
-    int32_t actionPointerUp =
-            AMOTION_EVENT_ACTION_POINTER_UP + (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
-    touchAndAssertPositions(actionPointerUp, touchedPoints, expectedPoints);
+    touchAndAssertPositions(POINTER_1_UP, touchedPoints, expectedPoints);
     expectedPoints.pop_back();
 
     // Update the transform so rotation is set for Window 2
     mWindow2->setWindowTransform(0, -1, 1, 0);
     expectedPoints.push_back(getPointInWindow(mWindow2->getInfo(), touchedPoints[1]));
-    touchAndAssertPositions(actionPointerDown, touchedPoints, expectedPoints);
+    touchAndAssertPositions(POINTER_1_DOWN, touchedPoints, expectedPoints);
 }
 
 TEST_F(InputDispatcherMultiWindowSameTokenTests, MultipleTouchMoveDifferentTransform) {
@@ -3759,12 +4317,10 @@
     touchAndAssertPositions(AMOTION_EVENT_ACTION_DOWN, touchedPoints, expectedPoints);
 
     // Touch Window 2
-    int32_t actionPointerDown =
-            AMOTION_EVENT_ACTION_POINTER_DOWN + (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
     touchedPoints.push_back(PointF{150, 150});
     expectedPoints.push_back(getPointInWindow(mWindow2->getInfo(), touchedPoints[1]));
 
-    touchAndAssertPositions(actionPointerDown, touchedPoints, expectedPoints);
+    touchAndAssertPositions(POINTER_1_DOWN, touchedPoints, expectedPoints);
 
     // Move both windows
     touchedPoints = {{20, 20}, {175, 175}};
@@ -3774,15 +4330,13 @@
     touchAndAssertPositions(AMOTION_EVENT_ACTION_MOVE, touchedPoints, expectedPoints);
 
     // Release Window 2
-    int32_t actionPointerUp =
-            AMOTION_EVENT_ACTION_POINTER_UP + (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
-    touchAndAssertPositions(actionPointerUp, touchedPoints, expectedPoints);
+    touchAndAssertPositions(POINTER_1_UP, touchedPoints, expectedPoints);
     expectedPoints.pop_back();
 
     // Touch Window 2
     mWindow2->setWindowTransform(0, -1, 1, 0);
     expectedPoints.push_back(getPointInWindow(mWindow2->getInfo(), touchedPoints[1]));
-    touchAndAssertPositions(actionPointerDown, touchedPoints, expectedPoints);
+    touchAndAssertPositions(POINTER_1_DOWN, touchedPoints, expectedPoints);
 
     // Move both windows
     touchedPoints = {{20, 20}, {175, 175}};
@@ -3801,12 +4355,10 @@
     touchAndAssertPositions(AMOTION_EVENT_ACTION_DOWN, touchedPoints, expectedPoints);
 
     // Touch Window 2
-    int32_t actionPointerDown =
-            AMOTION_EVENT_ACTION_POINTER_DOWN + (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
     touchedPoints.push_back(PointF{150, 150});
     expectedPoints.push_back(getPointInWindow(mWindow2->getInfo(), touchedPoints[1]));
 
-    touchAndAssertPositions(actionPointerDown, touchedPoints, expectedPoints);
+    touchAndAssertPositions(POINTER_1_DOWN, touchedPoints, expectedPoints);
 
     // Move both windows
     touchedPoints = {{20, 20}, {175, 175}};
@@ -3827,9 +4379,6 @@
         mWindow->setFrame(Rect(0, 0, 30, 30));
         mWindow->setDispatchingTimeout(30ms);
         mWindow->setFocusable(true);
-        // Adding FLAG_NOT_TOUCH_MODAL to ensure taps outside this window are not sent to this
-        // window.
-        mWindow->setFlags(WindowInfo::Flag::NOT_TOUCH_MODAL);
 
         // Set focused application.
         mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, mApplication);
@@ -3857,6 +4406,17 @@
                   injectMotionUp(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
                                  WINDOW_LOCATION));
     }
+
+    sp<FakeWindowHandle> addSpyWindow() {
+        sp<FakeWindowHandle> spy =
+                new FakeWindowHandle(mApplication, mDispatcher, "Spy", ADISPLAY_ID_DEFAULT);
+        spy->setTrustedOverlay(true);
+        spy->setFocusable(false);
+        spy->setSpy(true);
+        spy->setDispatchingTimeout(30ms);
+        mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {spy, mWindow}}});
+        return spy;
+    }
 };
 
 // Send a tap and respond, which should not cause an ANR.
@@ -3908,13 +4468,13 @@
     std::optional<uint32_t> sequenceNum = mWindow->receiveEvent(); // ACTION_DOWN
     ASSERT_TRUE(sequenceNum);
     const std::chrono::duration timeout = mWindow->getDispatchingTimeout(DISPATCHING_TIMEOUT);
-    mFakePolicy->assertNotifyWindowUnresponsiveWasCalled(timeout, mWindow->getToken());
+    mFakePolicy->assertNotifyWindowUnresponsiveWasCalled(timeout, mWindow);
 
     mWindow->finishEvent(*sequenceNum);
     mWindow->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_CANCEL,
                           ADISPLAY_ID_DEFAULT, 0 /*flags*/);
     ASSERT_TRUE(mDispatcher->waitForIdle());
-    mFakePolicy->assertNotifyWindowResponsiveWasCalled(mWindow->getToken());
+    mFakePolicy->assertNotifyWindowResponsiveWasCalled(mWindow->getToken(), mWindow->getPid());
 }
 
 // Send a key to the app and have the app not respond right away.
@@ -3924,7 +4484,7 @@
     std::optional<uint32_t> sequenceNum = mWindow->receiveEvent();
     ASSERT_TRUE(sequenceNum);
     const std::chrono::duration timeout = mWindow->getDispatchingTimeout(DISPATCHING_TIMEOUT);
-    mFakePolicy->assertNotifyWindowUnresponsiveWasCalled(timeout, mWindow->getToken());
+    mFakePolicy->assertNotifyWindowUnresponsiveWasCalled(timeout, mWindow);
     ASSERT_TRUE(mDispatcher->waitForIdle());
 }
 
@@ -3954,6 +4514,38 @@
     ASSERT_TRUE(mDispatcher->waitForIdle());
 }
 
+/**
+ * Make sure the stale key is dropped before causing an ANR. So even if there's no focused window,
+ * there will not be an ANR.
+ */
+TEST_F(InputDispatcherSingleWindowAnr, StaleKeyEventDoesNotAnr) {
+    mWindow->setFocusable(false);
+    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {mWindow}}});
+    mWindow->consumeFocusEvent(false);
+
+    KeyEvent event;
+    const nsecs_t eventTime = systemTime(SYSTEM_TIME_MONOTONIC) -
+            std::chrono::nanoseconds(STALE_EVENT_TIMEOUT).count();
+
+    // Define a valid key down event that is stale (too old).
+    event.initialize(InputEvent::nextId(), DEVICE_ID, AINPUT_SOURCE_KEYBOARD, ADISPLAY_ID_NONE,
+                     INVALID_HMAC, AKEY_EVENT_ACTION_DOWN, /* flags */ 0, AKEYCODE_A, KEY_A,
+                     AMETA_NONE, 1 /*repeatCount*/, eventTime, eventTime);
+
+    const int32_t policyFlags = POLICY_FLAG_FILTERED | POLICY_FLAG_PASS_TO_USER;
+
+    InputEventInjectionResult result =
+            mDispatcher->injectInputEvent(&event, INJECTOR_PID, INJECTOR_UID,
+                                          InputEventInjectionSync::WAIT_FOR_RESULT,
+                                          INJECT_EVENT_TIMEOUT, policyFlags);
+    ASSERT_EQ(InputEventInjectionResult::FAILED, result)
+            << "Injection should fail because the event is stale";
+
+    ASSERT_TRUE(mDispatcher->waitForIdle());
+    mFakePolicy->assertNotifyAnrWasNotCalled();
+    mWindow->assertNoEvents();
+}
+
 // We have a focused application, but no focused window
 // Make sure that we don't notify policy twice about the same ANR.
 TEST_F(InputDispatcherSingleWindowAnr, NoFocusedWindow_DoesNotSendDuplicateAnr) {
@@ -4029,15 +4621,34 @@
     // We have now sent down and up. Let's consume first event and then ANR on the second.
     mWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT);
     const std::chrono::duration timeout = mWindow->getDispatchingTimeout(DISPATCHING_TIMEOUT);
-    mFakePolicy->assertNotifyWindowUnresponsiveWasCalled(timeout, mWindow->getToken());
+    mFakePolicy->assertNotifyWindowUnresponsiveWasCalled(timeout, mWindow);
 }
 
-// If an app is not responding to a key event, gesture monitors should continue to receive
+// A spy window can receive an ANR
+TEST_F(InputDispatcherSingleWindowAnr, SpyWindowAnr) {
+    sp<FakeWindowHandle> spy = addSpyWindow();
+
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+                               WINDOW_LOCATION));
+    mWindow->consumeMotionDown();
+
+    std::optional<uint32_t> sequenceNum = spy->receiveEvent(); // ACTION_DOWN
+    ASSERT_TRUE(sequenceNum);
+    const std::chrono::duration timeout = spy->getDispatchingTimeout(DISPATCHING_TIMEOUT);
+    mFakePolicy->assertNotifyWindowUnresponsiveWasCalled(timeout, spy);
+
+    spy->finishEvent(*sequenceNum);
+    spy->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_CANCEL, ADISPLAY_ID_DEFAULT,
+                      0 /*flags*/);
+    ASSERT_TRUE(mDispatcher->waitForIdle());
+    mFakePolicy->assertNotifyWindowResponsiveWasCalled(spy->getToken(), mWindow->getPid());
+}
+
+// If an app is not responding to a key event, spy windows should continue to receive
 // new motion events
-TEST_F(InputDispatcherSingleWindowAnr, GestureMonitors_ReceiveEventsDuringAppAnrOnKey) {
-    FakeMonitorReceiver monitor =
-            FakeMonitorReceiver(mDispatcher, "Gesture monitor", ADISPLAY_ID_DEFAULT,
-                                true /*isGestureMonitor*/);
+TEST_F(InputDispatcherSingleWindowAnr, SpyWindowReceivesEventsDuringAppAnrOnKey) {
+    sp<FakeWindowHandle> spy = addSpyWindow();
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectKeyDown(mDispatcher, ADISPLAY_ID_DEFAULT));
@@ -4046,46 +4657,66 @@
 
     // Stuck on the ACTION_UP
     const std::chrono::duration timeout = mWindow->getDispatchingTimeout(DISPATCHING_TIMEOUT);
-    mFakePolicy->assertNotifyWindowUnresponsiveWasCalled(timeout, mWindow->getToken());
+    mFakePolicy->assertNotifyWindowUnresponsiveWasCalled(timeout, mWindow);
 
-    // New tap will go to the gesture monitor, but not to the window
+    // New tap will go to the spy window, but not to the window
     tapOnWindow();
-    monitor.consumeMotionDown(ADISPLAY_ID_DEFAULT);
-    monitor.consumeMotionUp(ADISPLAY_ID_DEFAULT);
+    spy->consumeMotionDown(ADISPLAY_ID_DEFAULT);
+    spy->consumeMotionUp(ADISPLAY_ID_DEFAULT);
 
     mWindow->consumeKeyUp(ADISPLAY_ID_DEFAULT); // still the previous motion
     mDispatcher->waitForIdle();
-    mFakePolicy->assertNotifyWindowResponsiveWasCalled(mWindow->getToken());
+    mFakePolicy->assertNotifyWindowResponsiveWasCalled(mWindow->getToken(), mWindow->getPid());
     mWindow->assertNoEvents();
-    monitor.assertNoEvents();
+    spy->assertNoEvents();
 }
 
-// If an app is not responding to a motion event, gesture monitors should continue to receive
+// If an app is not responding to a motion event, spy windows should continue to receive
 // new motion events
-TEST_F(InputDispatcherSingleWindowAnr, GestureMonitors_ReceiveEventsDuringAppAnrOnMotion) {
-    FakeMonitorReceiver monitor =
-            FakeMonitorReceiver(mDispatcher, "Gesture monitor", ADISPLAY_ID_DEFAULT,
-                                true /*isGestureMonitor*/);
+TEST_F(InputDispatcherSingleWindowAnr, SpyWindowReceivesEventsDuringAppAnrOnMotion) {
+    sp<FakeWindowHandle> spy = addSpyWindow();
 
     tapOnWindow();
-    monitor.consumeMotionDown(ADISPLAY_ID_DEFAULT);
-    monitor.consumeMotionUp(ADISPLAY_ID_DEFAULT);
+    spy->consumeMotionDown(ADISPLAY_ID_DEFAULT);
+    spy->consumeMotionUp(ADISPLAY_ID_DEFAULT);
 
     mWindow->consumeMotionDown();
     // Stuck on the ACTION_UP
     const std::chrono::duration timeout = mWindow->getDispatchingTimeout(DISPATCHING_TIMEOUT);
-    mFakePolicy->assertNotifyWindowUnresponsiveWasCalled(timeout, mWindow->getToken());
+    mFakePolicy->assertNotifyWindowUnresponsiveWasCalled(timeout, mWindow);
 
-    // New tap will go to the gesture monitor, but not to the window
+    // New tap will go to the spy window, but not to the window
     tapOnWindow();
-    monitor.consumeMotionDown(ADISPLAY_ID_DEFAULT);
-    monitor.consumeMotionUp(ADISPLAY_ID_DEFAULT);
+    spy->consumeMotionDown(ADISPLAY_ID_DEFAULT);
+    spy->consumeMotionUp(ADISPLAY_ID_DEFAULT);
 
     mWindow->consumeMotionUp(ADISPLAY_ID_DEFAULT); // still the previous motion
     mDispatcher->waitForIdle();
-    mFakePolicy->assertNotifyWindowResponsiveWasCalled(mWindow->getToken());
+    mFakePolicy->assertNotifyWindowResponsiveWasCalled(mWindow->getToken(), mWindow->getPid());
     mWindow->assertNoEvents();
-    monitor.assertNoEvents();
+    spy->assertNoEvents();
+}
+
+TEST_F(InputDispatcherSingleWindowAnr, UnresponsiveMonitorAnr) {
+    mDispatcher->setMonitorDispatchingTimeoutForTest(30ms);
+
+    FakeMonitorReceiver monitor = FakeMonitorReceiver(mDispatcher, "M_1", ADISPLAY_ID_DEFAULT);
+
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+                               WINDOW_LOCATION));
+
+    mWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT);
+    const std::optional<uint32_t> consumeSeq = monitor.receiveEvent();
+    ASSERT_TRUE(consumeSeq);
+
+    mFakePolicy->assertNotifyWindowUnresponsiveWasCalled(30ms, monitor.getToken(), MONITOR_PID);
+
+    monitor.finishEvent(*consumeSeq);
+    monitor.consumeMotionCancel(ADISPLAY_ID_DEFAULT);
+
+    ASSERT_TRUE(mDispatcher->waitForIdle());
+    mFakePolicy->assertNotifyWindowResponsiveWasCalled(monitor.getToken(), MONITOR_PID);
 }
 
 // If a window is unresponsive, then you get anr. if the window later catches up and starts to
@@ -4100,19 +4731,19 @@
     mWindow->consumeMotionDown();
     // Block on ACTION_UP
     const std::chrono::duration timeout = mWindow->getDispatchingTimeout(DISPATCHING_TIMEOUT);
-    mFakePolicy->assertNotifyWindowUnresponsiveWasCalled(timeout, mWindow->getToken());
+    mFakePolicy->assertNotifyWindowUnresponsiveWasCalled(timeout, mWindow);
     mWindow->consumeMotionUp(); // Now the connection should be healthy again
     mDispatcher->waitForIdle();
-    mFakePolicy->assertNotifyWindowResponsiveWasCalled(mWindow->getToken());
+    mFakePolicy->assertNotifyWindowResponsiveWasCalled(mWindow->getToken(), mWindow->getPid());
     mWindow->assertNoEvents();
 
     tapOnWindow();
     mWindow->consumeMotionDown();
-    mFakePolicy->assertNotifyWindowUnresponsiveWasCalled(timeout, mWindow->getToken());
+    mFakePolicy->assertNotifyWindowUnresponsiveWasCalled(timeout, mWindow);
     mWindow->consumeMotionUp();
 
     mDispatcher->waitForIdle();
-    mFakePolicy->assertNotifyWindowResponsiveWasCalled(mWindow->getToken());
+    mFakePolicy->assertNotifyWindowResponsiveWasCalled(mWindow->getToken(), mWindow->getPid());
     mFakePolicy->assertNotifyAnrWasNotCalled();
     mWindow->assertNoEvents();
 }
@@ -4125,7 +4756,7 @@
                                WINDOW_LOCATION));
 
     const std::chrono::duration windowTimeout = mWindow->getDispatchingTimeout(DISPATCHING_TIMEOUT);
-    mFakePolicy->assertNotifyWindowUnresponsiveWasCalled(windowTimeout, mWindow->getToken());
+    mFakePolicy->assertNotifyWindowUnresponsiveWasCalled(windowTimeout, mWindow);
     std::this_thread::sleep_for(windowTimeout);
     // 'notifyConnectionUnresponsive' should only be called once per connection
     mFakePolicy->assertNotifyAnrWasNotCalled();
@@ -4135,7 +4766,7 @@
                           ADISPLAY_ID_DEFAULT, 0 /*flags*/);
     mWindow->assertNoEvents();
     mDispatcher->waitForIdle();
-    mFakePolicy->assertNotifyWindowResponsiveWasCalled(mWindow->getToken());
+    mFakePolicy->assertNotifyWindowResponsiveWasCalled(mWindow->getToken(), mWindow->getPid());
     mFakePolicy->assertNotifyAnrWasNotCalled();
 }
 
@@ -4227,18 +4858,13 @@
         mUnfocusedWindow =
                 new FakeWindowHandle(mApplication, mDispatcher, "Unfocused", ADISPLAY_ID_DEFAULT);
         mUnfocusedWindow->setFrame(Rect(0, 0, 30, 30));
-        // Adding FLAG_NOT_TOUCH_MODAL to ensure taps outside this window are not sent to this
-        // window.
         // Adding FLAG_WATCH_OUTSIDE_TOUCH to receive ACTION_OUTSIDE when another window is tapped
-        mUnfocusedWindow->setFlags(WindowInfo::Flag::NOT_TOUCH_MODAL |
-                                   WindowInfo::Flag::WATCH_OUTSIDE_TOUCH |
-                                   WindowInfo::Flag::SPLIT_TOUCH);
+        mUnfocusedWindow->setWatchOutsideTouch(true);
 
         mFocusedWindow =
                 new FakeWindowHandle(mApplication, mDispatcher, "Focused", ADISPLAY_ID_DEFAULT);
         mFocusedWindow->setDispatchingTimeout(30ms);
         mFocusedWindow->setFrame(Rect(50, 50, 100, 100));
-        mFocusedWindow->setFlags(WindowInfo::Flag::NOT_TOUCH_MODAL | WindowInfo::Flag::SPLIT_TOUCH);
 
         // Set focused application.
         mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, mApplication);
@@ -4302,7 +4928,7 @@
 
     const std::chrono::duration timeout =
             mFocusedWindow->getDispatchingTimeout(DISPATCHING_TIMEOUT);
-    mFakePolicy->assertNotifyWindowUnresponsiveWasCalled(timeout, mFocusedWindow->getToken());
+    mFakePolicy->assertNotifyWindowUnresponsiveWasCalled(timeout, mFocusedWindow);
     // Because we injected two DOWN events in a row, CANCEL is enqueued for the first event
     // sequence to make it consistent
     mFocusedWindow->consumeMotionCancel();
@@ -4313,7 +4939,8 @@
     mFocusedWindow->assertNoEvents();
     mUnfocusedWindow->assertNoEvents();
     ASSERT_TRUE(mDispatcher->waitForIdle());
-    mFakePolicy->assertNotifyWindowResponsiveWasCalled(mFocusedWindow->getToken());
+    mFakePolicy->assertNotifyWindowResponsiveWasCalled(mFocusedWindow->getToken(),
+                                                       mFocusedWindow->getPid());
     mFakePolicy->assertNotifyAnrWasNotCalled();
 }
 
@@ -4327,8 +4954,9 @@
 
     tapOnFocusedWindow();
     // we should have ACTION_DOWN/ACTION_UP on focused window and ACTION_OUTSIDE on unfocused window
-    sp<IBinder> anrConnectionToken1 = mFakePolicy->getUnresponsiveWindowToken(10ms);
-    sp<IBinder> anrConnectionToken2 = mFakePolicy->getUnresponsiveWindowToken(0ms);
+    sp<IBinder> anrConnectionToken1, anrConnectionToken2;
+    ASSERT_NO_FATAL_FAILURE(anrConnectionToken1 = mFakePolicy->getUnresponsiveWindowToken(10ms));
+    ASSERT_NO_FATAL_FAILURE(anrConnectionToken2 = mFakePolicy->getUnresponsiveWindowToken(0ms));
 
     // We don't know which window will ANR first. But both of them should happen eventually.
     ASSERT_TRUE(mFocusedWindow->getToken() == anrConnectionToken1 ||
@@ -4343,8 +4971,9 @@
     mFocusedWindow->consumeMotionUp();
     mUnfocusedWindow->consumeMotionOutside();
 
-    sp<IBinder> responsiveToken1 = mFakePolicy->getResponsiveWindowToken();
-    sp<IBinder> responsiveToken2 = mFakePolicy->getResponsiveWindowToken();
+    sp<IBinder> responsiveToken1, responsiveToken2;
+    ASSERT_NO_FATAL_FAILURE(responsiveToken1 = mFakePolicy->getResponsiveWindowToken());
+    ASSERT_NO_FATAL_FAILURE(responsiveToken2 = mFakePolicy->getResponsiveWindowToken());
 
     // Both applications should be marked as responsive, in any order
     ASSERT_TRUE(mFocusedWindow->getToken() == responsiveToken1 ||
@@ -4368,7 +4997,7 @@
     ASSERT_TRUE(upEventSequenceNum);
     const std::chrono::duration timeout =
             mFocusedWindow->getDispatchingTimeout(DISPATCHING_TIMEOUT);
-    mFakePolicy->assertNotifyWindowUnresponsiveWasCalled(timeout, mFocusedWindow->getToken());
+    mFakePolicy->assertNotifyWindowUnresponsiveWasCalled(timeout, mFocusedWindow);
 
     // Tap once again
     // We cannot use "tapOnFocusedWindow" because it asserts the injection result to be success
@@ -4389,7 +5018,8 @@
     // The second tap did not go to the focused window
     mFocusedWindow->assertNoEvents();
     // Since all events are finished, connection should be deemed healthy again
-    mFakePolicy->assertNotifyWindowResponsiveWasCalled(mFocusedWindow->getToken());
+    mFakePolicy->assertNotifyWindowResponsiveWasCalled(mFocusedWindow->getToken(),
+                                                       mFocusedWindow->getPid());
     mFakePolicy->assertNotifyAnrWasNotCalled();
 }
 
@@ -4491,17 +5121,13 @@
                                    ADISPLAY_ID_DEFAULT, 0 /*flags*/);
 
     // Touch Window 2
-    int32_t actionPointerDown =
-            AMOTION_EVENT_ACTION_POINTER_DOWN + (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
-
-    motionArgs =
-            generateMotionArgs(actionPointerDown, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                               {FOCUSED_WINDOW_LOCATION, UNFOCUSED_WINDOW_LOCATION});
+    motionArgs = generateMotionArgs(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+                                    {FOCUSED_WINDOW_LOCATION, UNFOCUSED_WINDOW_LOCATION});
     mDispatcher->notifyMotion(&motionArgs);
 
     const std::chrono::duration timeout =
             mFocusedWindow->getDispatchingTimeout(DISPATCHING_TIMEOUT);
-    mFakePolicy->assertNotifyWindowUnresponsiveWasCalled(timeout, mFocusedWindow->getToken());
+    mFakePolicy->assertNotifyWindowUnresponsiveWasCalled(timeout, mFocusedWindow);
 
     mUnfocusedWindow->consumeMotionDown();
     mFocusedWindow->consumeMotionDown();
@@ -4520,7 +5146,8 @@
         ASSERT_EQ(AMOTION_EVENT_ACTION_CANCEL, motionEvent.getAction());
     }
     ASSERT_TRUE(mDispatcher->waitForIdle());
-    mFakePolicy->assertNotifyWindowResponsiveWasCalled(mFocusedWindow->getToken());
+    mFakePolicy->assertNotifyWindowResponsiveWasCalled(mFocusedWindow->getToken(),
+                                                       mFocusedWindow->getPid());
 
     mUnfocusedWindow->assertNoEvents();
     mFocusedWindow->assertNoEvents();
@@ -4606,7 +5233,7 @@
                                               "Window without input channel", ADISPLAY_ID_DEFAULT,
                                               std::make_optional<sp<IBinder>>(nullptr) /*token*/);
 
-        mNoInputWindow->setInputFeatures(WindowInfo::Feature::NO_INPUT_CHANNEL);
+        mNoInputWindow->setNoInputChannel(true);
         mNoInputWindow->setFrame(Rect(0, 0, 100, 100));
         // It's perfectly valid for this window to not have an associated input channel
 
@@ -4648,7 +5275,7 @@
                                           "Window with input channel and NO_INPUT_CHANNEL",
                                           ADISPLAY_ID_DEFAULT);
 
-    mNoInputWindow->setInputFeatures(WindowInfo::Feature::NO_INPUT_CHANNEL);
+    mNoInputWindow->setNoInputChannel(true);
     mNoInputWindow->setFrame(Rect(0, 0, 100, 100));
     mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {mNoInputWindow, mBottomWindow}}});
 
@@ -4972,7 +5599,7 @@
     sp<FakeWindowHandle> getOccludingWindow(int32_t uid, std::string name, TouchOcclusionMode mode,
                                             float alpha = 1.0f) {
         sp<FakeWindowHandle> window = getWindow(uid, name);
-        window->setFlags(WindowInfo::Flag::NOT_TOUCHABLE);
+        window->setTouchable(false);
         window->setTouchOcclusionMode(mode);
         window->setAlpha(alpha);
         return window;
@@ -5086,7 +5713,7 @@
        WindowWithZeroOpacityAndWatchOutside_ReceivesOutsideEvent) {
     const sp<FakeWindowHandle>& w =
             getOccludingWindow(APP_B_UID, "B", TouchOcclusionMode::BLOCK_UNTRUSTED, 0.0f);
-    w->addFlags(WindowInfo::Flag::WATCH_OUTSIDE_TOUCH);
+    w->setWatchOutsideTouch(true);
     mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {w, mTouchWindow}}});
 
     touch();
@@ -5097,16 +5724,12 @@
 TEST_F(InputDispatcherUntrustedTouchesTest, OutsideEvent_HasZeroCoordinates) {
     const sp<FakeWindowHandle>& w =
             getOccludingWindow(APP_B_UID, "B", TouchOcclusionMode::BLOCK_UNTRUSTED, 0.0f);
-    w->addFlags(WindowInfo::Flag::WATCH_OUTSIDE_TOUCH);
+    w->setWatchOutsideTouch(true);
     mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {w, mTouchWindow}}});
 
     touch();
 
-    InputEvent* event = w->consume();
-    ASSERT_EQ(AINPUT_EVENT_TYPE_MOTION, event->getType());
-    MotionEvent& motionEvent = static_cast<MotionEvent&>(*event);
-    EXPECT_EQ(0.0f, motionEvent.getRawPointerCoords(0)->getX());
-    EXPECT_EQ(0.0f, motionEvent.getRawPointerCoords(0)->getY());
+    w->consumeMotionOutsideWithZeroedCoords();
 }
 
 TEST_F(InputDispatcherUntrustedTouchesTest, WindowWithOpacityBelowThreshold_AllowsTouch) {
@@ -5351,11 +5974,9 @@
         mApp = std::make_shared<FakeApplicationHandle>();
         mWindow = new FakeWindowHandle(mApp, mDispatcher, "TestWindow", ADISPLAY_ID_DEFAULT);
         mWindow->setFrame(Rect(0, 0, 100, 100));
-        mWindow->setFlags(WindowInfo::Flag::NOT_TOUCH_MODAL);
 
         mSecondWindow = new FakeWindowHandle(mApp, mDispatcher, "TestWindow2", ADISPLAY_ID_DEFAULT);
         mSecondWindow->setFrame(Rect(100, 0, 200, 100));
-        mSecondWindow->setFlags(WindowInfo::Flag::NOT_TOUCH_MODAL);
 
         mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, mApp);
         mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {mWindow, mSecondWindow}}});
@@ -5569,7 +6190,7 @@
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
     sp<FakeWindowHandle> window =
             new FakeWindowHandle(application, mDispatcher, "Test window", ADISPLAY_ID_DEFAULT);
-    window->setInputFeatures(WindowInfo::Feature::DROP_INPUT);
+    window->setDropInput(true);
     mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
     window->setFocusable(true);
     mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
@@ -5588,7 +6209,7 @@
     window->assertNoEvents();
 
     // With the flag cleared, the window should get input
-    window->setInputFeatures({});
+    window->setDropInput(false);
     mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
 
     keyArgs = generateKeyArgs(AKEY_EVENT_ACTION_UP, ADISPLAY_ID_DEFAULT);
@@ -5610,11 +6231,11 @@
                                  ADISPLAY_ID_DEFAULT);
     obscuringWindow->setFrame(Rect(0, 0, 50, 50));
     obscuringWindow->setOwnerInfo(111, 111);
-    obscuringWindow->setFlags(WindowInfo::Flag::NOT_TOUCHABLE);
+    obscuringWindow->setTouchable(false);
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
     sp<FakeWindowHandle> window =
             new FakeWindowHandle(application, mDispatcher, "Test window", ADISPLAY_ID_DEFAULT);
-    window->setInputFeatures(WindowInfo::Feature::DROP_INPUT_IF_OBSCURED);
+    window->setDropInputIfObscured(true);
     window->setOwnerInfo(222, 222);
     mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
     window->setFocusable(true);
@@ -5634,7 +6255,7 @@
     window->assertNoEvents();
 
     // With the flag cleared, the window should get input
-    window->setInputFeatures({});
+    window->setDropInputIfObscured(false);
     mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {obscuringWindow, window}}});
 
     keyArgs = generateKeyArgs(AKEY_EVENT_ACTION_UP, ADISPLAY_ID_DEFAULT);
@@ -5656,11 +6277,11 @@
                                  ADISPLAY_ID_DEFAULT);
     obscuringWindow->setFrame(Rect(0, 0, 50, 50));
     obscuringWindow->setOwnerInfo(111, 111);
-    obscuringWindow->setFlags(WindowInfo::Flag::NOT_TOUCHABLE);
+    obscuringWindow->setTouchable(false);
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
     sp<FakeWindowHandle> window =
             new FakeWindowHandle(application, mDispatcher, "Test window", ADISPLAY_ID_DEFAULT);
-    window->setInputFeatures(WindowInfo::Feature::DROP_INPUT_IF_OBSCURED);
+    window->setDropInputIfObscured(true);
     window->setOwnerInfo(222, 222);
     mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
     window->setFocusable(true);
@@ -5693,4 +6314,639 @@
     window->assertNoEvents();
 }
 
+class InputDispatcherTouchModeChangedTests : public InputDispatcherTest {
+protected:
+    std::shared_ptr<FakeApplicationHandle> mApp;
+    sp<FakeWindowHandle> mWindow;
+    sp<FakeWindowHandle> mSecondWindow;
+
+    void SetUp() override {
+        InputDispatcherTest::SetUp();
+
+        mApp = std::make_shared<FakeApplicationHandle>();
+        mWindow = new FakeWindowHandle(mApp, mDispatcher, "TestWindow", ADISPLAY_ID_DEFAULT);
+        mWindow->setFocusable(true);
+        setFocusedWindow(mWindow);
+        mSecondWindow = new FakeWindowHandle(mApp, mDispatcher, "TestWindow2", ADISPLAY_ID_DEFAULT);
+        mSecondWindow->setFocusable(true);
+
+        mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, mApp);
+        mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {mWindow, mSecondWindow}}});
+        mWindow->consumeFocusEvent(true);
+
+        // Set initial touch mode to InputDispatcher::kDefaultInTouchMode.
+        mDispatcher->setInTouchMode(InputDispatcher::kDefaultInTouchMode, INJECTOR_PID,
+                                    INJECTOR_UID, /* hasPermission */ true);
+    }
+
+    void changeAndVerifyTouchMode(bool inTouchMode, int32_t pid, int32_t uid, bool hasPermission) {
+        ASSERT_TRUE(mDispatcher->setInTouchMode(inTouchMode, pid, uid, hasPermission));
+        mWindow->consumeTouchModeEvent(inTouchMode);
+        mSecondWindow->consumeTouchModeEvent(inTouchMode);
+    }
+};
+
+TEST_F(InputDispatcherTouchModeChangedTests, FocusedWindowCanChangeTouchMode) {
+    const WindowInfo& windowInfo = *mWindow->getInfo();
+    changeAndVerifyTouchMode(!InputDispatcher::kDefaultInTouchMode, windowInfo.ownerPid,
+                             windowInfo.ownerUid, /* hasPermission */ false);
+}
+
+TEST_F(InputDispatcherTouchModeChangedTests, NonFocusedWindowOwnerCannotChangeTouchMode) {
+    const WindowInfo& windowInfo = *mWindow->getInfo();
+    int32_t ownerPid = windowInfo.ownerPid;
+    int32_t ownerUid = windowInfo.ownerUid;
+    mWindow->setOwnerInfo(/* pid */ -1, /* uid */ -1);
+    ASSERT_FALSE(mDispatcher->setInTouchMode(InputDispatcher::kDefaultInTouchMode, ownerPid,
+                                             ownerUid, /* hasPermission */ false));
+    mWindow->assertNoEvents();
+    mSecondWindow->assertNoEvents();
+}
+
+TEST_F(InputDispatcherTouchModeChangedTests, NonWindowOwnerMayChangeTouchModeOnPermissionGranted) {
+    const WindowInfo& windowInfo = *mWindow->getInfo();
+    int32_t ownerPid = windowInfo.ownerPid;
+    int32_t ownerUid = windowInfo.ownerUid;
+    mWindow->setOwnerInfo(/* pid */ -1, /* uid */ -1);
+    changeAndVerifyTouchMode(!InputDispatcher::kDefaultInTouchMode, ownerPid, ownerUid,
+                             /* hasPermission */ true);
+}
+
+TEST_F(InputDispatcherTouchModeChangedTests, EventIsNotGeneratedIfNotChangingTouchMode) {
+    const WindowInfo& windowInfo = *mWindow->getInfo();
+    ASSERT_FALSE(mDispatcher->setInTouchMode(InputDispatcher::kDefaultInTouchMode,
+                                             windowInfo.ownerPid, windowInfo.ownerUid,
+                                             /* hasPermission */ true));
+    mWindow->assertNoEvents();
+    mSecondWindow->assertNoEvents();
+}
+
+class InputDispatcherSpyWindowTest : public InputDispatcherTest {
+public:
+    sp<FakeWindowHandle> createSpy() {
+        std::shared_ptr<FakeApplicationHandle> application =
+                std::make_shared<FakeApplicationHandle>();
+        std::string name = "Fake Spy ";
+        name += std::to_string(mSpyCount++);
+        sp<FakeWindowHandle> spy =
+                new FakeWindowHandle(application, mDispatcher, name.c_str(), ADISPLAY_ID_DEFAULT);
+        spy->setSpy(true);
+        spy->setTrustedOverlay(true);
+        return spy;
+    }
+
+    sp<FakeWindowHandle> createForeground() {
+        std::shared_ptr<FakeApplicationHandle> application =
+                std::make_shared<FakeApplicationHandle>();
+        sp<FakeWindowHandle> window =
+                new FakeWindowHandle(application, mDispatcher, "Fake Window", ADISPLAY_ID_DEFAULT);
+        window->setFocusable(true);
+        return window;
+    }
+
+private:
+    int mSpyCount{0};
+};
+
+using InputDispatcherSpyWindowDeathTest = InputDispatcherSpyWindowTest;
+/**
+ * Adding a spy window that is not a trusted overlay causes Dispatcher to abort.
+ */
+TEST_F(InputDispatcherSpyWindowDeathTest, UntrustedSpy_AbortsDispatcher) {
+    ScopedSilentDeath _silentDeath;
+
+    auto spy = createSpy();
+    spy->setTrustedOverlay(false);
+    ASSERT_DEATH(mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {spy}}}),
+                 ".* not a trusted overlay");
+}
+
+/**
+ * Input injection into a display with a spy window but no foreground windows should succeed.
+ */
+TEST_F(InputDispatcherSpyWindowTest, NoForegroundWindow) {
+    auto spy = createSpy();
+    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {spy}}});
+
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
+            << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
+    spy->consumeMotionDown(ADISPLAY_ID_DEFAULT);
+}
+
+/**
+ * Verify the order in which different input windows receive events. The touched foreground window
+ * (if there is one) should always receive the event first. When there are multiple spy windows, the
+ * spy windows will receive the event according to their Z-order, where the top-most spy window will
+ * receive events before ones belows it.
+ *
+ * Here, we set up a scenario with four windows in the following Z order from the top:
+ *    spy1, spy2, window, spy3.
+ * We then inject an event and verify that the foreground "window" receives it first, followed by
+ * "spy1" and "spy2". The "spy3" does not receive the event because it is underneath the foreground
+ * window.
+ */
+TEST_F(InputDispatcherSpyWindowTest, ReceivesInputInOrder) {
+    auto window = createForeground();
+    auto spy1 = createSpy();
+    auto spy2 = createSpy();
+    auto spy3 = createSpy();
+    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {spy1, spy2, window, spy3}}});
+    const std::vector<sp<FakeWindowHandle>> channels{spy1, spy2, window, spy3};
+    const size_t numChannels = channels.size();
+
+    base::unique_fd epollFd(epoll_create1(EPOLL_CLOEXEC));
+    if (!epollFd.ok()) {
+        FAIL() << "Failed to create epoll fd";
+    }
+
+    for (size_t i = 0; i < numChannels; i++) {
+        struct epoll_event event = {.events = EPOLLIN, .data.u64 = i};
+        if (epoll_ctl(epollFd.get(), EPOLL_CTL_ADD, channels[i]->getChannelFd(), &event) < 0) {
+            FAIL() << "Failed to add fd to epoll";
+        }
+    }
+
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
+            << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
+
+    std::vector<size_t> eventOrder;
+    std::vector<struct epoll_event> events(numChannels);
+    for (;;) {
+        const int nFds = epoll_wait(epollFd.get(), events.data(), static_cast<int>(numChannels),
+                                    (100ms).count());
+        if (nFds < 0) {
+            FAIL() << "Failed to call epoll_wait";
+        }
+        if (nFds == 0) {
+            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);
+            channels[i]->consumeMotionDown();
+        }
+    }
+
+    // Verify the order in which the events were received.
+    EXPECT_EQ(3u, eventOrder.size());
+    EXPECT_EQ(2u, eventOrder[0]); // index 2: window
+    EXPECT_EQ(0u, eventOrder[1]); // index 0: spy1
+    EXPECT_EQ(1u, eventOrder[2]); // index 1: spy2
+}
+
+/**
+ * A spy window using the NOT_TOUCHABLE flag does not receive events.
+ */
+TEST_F(InputDispatcherSpyWindowTest, NotTouchable) {
+    auto window = createForeground();
+    auto spy = createSpy();
+    spy->setTouchable(false);
+    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {spy, window}}});
+
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
+            << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
+    window->consumeMotionDown(ADISPLAY_ID_DEFAULT);
+    spy->assertNoEvents();
+}
+
+/**
+ * A spy window will only receive gestures that originate within its touchable region. Gestures that
+ * have their ACTION_DOWN outside of the touchable region of the spy window will not be dispatched
+ * to the window.
+ */
+TEST_F(InputDispatcherSpyWindowTest, TouchableRegion) {
+    auto window = createForeground();
+    auto spy = createSpy();
+    spy->setTouchableRegion(Region{{0, 0, 20, 20}});
+    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {spy, window}}});
+
+    // Inject an event outside the spy window's touchable region.
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
+            << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
+    window->consumeMotionDown();
+    spy->assertNoEvents();
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionUp(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
+            << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
+    window->consumeMotionUp();
+    spy->assertNoEvents();
+
+    // Inject an event inside the spy window's touchable region.
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+                               {5, 10}))
+            << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
+    window->consumeMotionDown();
+    spy->consumeMotionDown();
+}
+
+/**
+ * A spy window can listen for touches outside its touchable region using the WATCH_OUTSIDE_TOUCHES
+ * flag, but it will get zero-ed out coordinates if the foreground has a different owner.
+ */
+TEST_F(InputDispatcherSpyWindowTest, WatchOutsideTouches) {
+    auto window = createForeground();
+    window->setOwnerInfo(12, 34);
+    auto spy = createSpy();
+    spy->setWatchOutsideTouch(true);
+    spy->setOwnerInfo(56, 78);
+    spy->setFrame(Rect{0, 0, 20, 20});
+    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {spy, window}}});
+
+    // Inject an event outside the spy window's frame and touchable region.
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+                               {100, 200}))
+            << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
+    window->consumeMotionDown();
+    spy->consumeMotionOutsideWithZeroedCoords();
+}
+
+/**
+ * A spy window can pilfer pointers. When this happens, touch gestures that are currently sent to
+ * any other windows - including other spy windows - will also be cancelled.
+ */
+TEST_F(InputDispatcherSpyWindowTest, PilferPointers) {
+    auto window = createForeground();
+    auto spy1 = createSpy();
+    auto spy2 = createSpy();
+    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {spy1, spy2, window}}});
+
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
+            << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
+    window->consumeMotionDown();
+    spy1->consumeMotionDown();
+    spy2->consumeMotionDown();
+
+    // Pilfer pointers from the second spy window.
+    EXPECT_EQ(OK, mDispatcher->pilferPointers(spy2->getToken()));
+    spy2->assertNoEvents();
+    spy1->consumeMotionCancel();
+    window->consumeMotionCancel();
+
+    // The rest of the gesture should only be sent to the second spy window.
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionEvent(mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
+                                ADISPLAY_ID_DEFAULT))
+            << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
+    spy2->consumeMotionMove();
+    spy1->assertNoEvents();
+    window->assertNoEvents();
+}
+
+/**
+ * A spy window can pilfer pointers for a gesture even after the foreground window has been removed
+ * in the middle of the gesture.
+ */
+TEST_F(InputDispatcherSpyWindowTest, CanPilferAfterWindowIsRemovedMidStream) {
+    auto window = createForeground();
+    auto spy = createSpy();
+    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {spy, window}}});
+
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
+            << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
+    window->consumeMotionDown(ADISPLAY_ID_DEFAULT);
+    spy->consumeMotionDown(ADISPLAY_ID_DEFAULT);
+
+    window->releaseChannel();
+
+    EXPECT_EQ(OK, mDispatcher->pilferPointers(spy->getToken()));
+
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionUp(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
+            << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
+    spy->consumeMotionUp(ADISPLAY_ID_DEFAULT);
+}
+
+/**
+ * After a spy window pilfers pointers, new pointers that go down in its bounds should be sent to
+ * the spy, but not to any other windows.
+ */
+TEST_F(InputDispatcherSpyWindowTest, ContinuesToReceiveGestureAfterPilfer) {
+    auto spy = createSpy();
+    auto window = createForeground();
+
+    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {spy, window}}});
+
+    // First finger down on the window and the spy.
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+                               {100, 200}))
+            << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
+    spy->consumeMotionDown();
+    window->consumeMotionDown();
+
+    // Spy window pilfers the pointers.
+    EXPECT_EQ(OK, mDispatcher->pilferPointers(spy->getToken()));
+    window->consumeMotionCancel();
+
+    // Second finger down on the window and spy, but the window should not receive the pointer down.
+    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(100)
+                                     .y(200))
+                    .pointer(PointerBuilder(/* id */ 1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50))
+                    .build();
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionEvent(mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT,
+                                InputEventInjectionSync::WAIT_FOR_RESULT))
+            << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
+
+    spy->consumeMotionPointerDown(1 /*pointerIndex*/);
+
+    // Third finger goes down outside all windows, so injection should fail.
+    const MotionEvent thirdFingerDownEvent =
+            MotionEventBuilder(AMOTION_EVENT_ACTION_POINTER_DOWN |
+                                       (2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                               AINPUT_SOURCE_TOUCHSCREEN)
+                    .displayId(ADISPLAY_ID_DEFAULT)
+                    .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
+                    .pointer(PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER)
+                                     .x(100)
+                                     .y(200))
+                    .pointer(PointerBuilder(/* id */ 1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50))
+                    .pointer(PointerBuilder(/* id */ 2, AMOTION_EVENT_TOOL_TYPE_FINGER).x(-5).y(-5))
+                    .build();
+    ASSERT_EQ(InputEventInjectionResult::FAILED,
+              injectMotionEvent(mDispatcher, thirdFingerDownEvent, INJECT_EVENT_TIMEOUT,
+                                InputEventInjectionSync::WAIT_FOR_RESULT))
+            << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
+
+    spy->assertNoEvents();
+    window->assertNoEvents();
+}
+
+/**
+ * Even when a spy window spans over multiple foreground windows, the spy should receive all
+ * pointers that are down within its bounds.
+ */
+TEST_F(InputDispatcherSpyWindowTest, ReceivesMultiplePointers) {
+    auto windowLeft = createForeground();
+    windowLeft->setFrame({0, 0, 100, 200});
+    auto windowRight = createForeground();
+    windowRight->setFrame({100, 0, 200, 200});
+    auto spy = createSpy();
+    spy->setFrame({0, 0, 200, 200});
+    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {spy, windowLeft, windowRight}}});
+
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+                               {50, 50}))
+            << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
+    windowLeft->consumeMotionDown();
+    spy->consumeMotionDown();
+
+    const MotionEvent secondFingerDownEvent =
+            MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                    .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(150).y(50))
+                    .build();
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionEvent(mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT,
+                                InputEventInjectionSync::WAIT_FOR_RESULT))
+            << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
+    windowRight->consumeMotionDown();
+    spy->consumeMotionPointerDown(1 /*pointerIndex*/);
+}
+
+/**
+ * When the first pointer lands outside the spy window and the second pointer lands inside it, the
+ * the spy should receive the second pointer with ACTION_DOWN.
+ */
+TEST_F(InputDispatcherSpyWindowTest, ReceivesSecondPointerAsDown) {
+    auto window = createForeground();
+    window->setFrame({0, 0, 200, 200});
+    auto spyRight = createSpy();
+    spyRight->setFrame({100, 0, 200, 200});
+    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {spyRight, window}}});
+
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+                               {50, 50}))
+            << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
+    window->consumeMotionDown();
+    spyRight->assertNoEvents();
+
+    const MotionEvent secondFingerDownEvent =
+            MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                    .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(150).y(50))
+                    .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 /*pointerIndex*/);
+    spyRight->consumeMotionDown();
+}
+
+/**
+ * The spy window should not be able to affect whether or not touches are split. Only the foreground
+ * windows should be allowed to control split touch.
+ */
+TEST_F(InputDispatcherSpyWindowTest, SplitIfNoForegroundWindowTouched) {
+    // This spy window prevents touch splitting. However, we still expect to split touches
+    // because a foreground window has not disabled splitting.
+    auto spy = createSpy();
+    spy->setPreventSplitting(true);
+
+    auto window = createForeground();
+    window->setFrame(Rect(0, 0, 100, 100));
+
+    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {spy, window}}});
+
+    // First finger down, no window touched.
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+                               {100, 200}))
+            << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
+    spy->consumeMotionDown(ADISPLAY_ID_DEFAULT);
+    window->assertNoEvents();
+
+    // Second finger down on window, the window should receive touch down.
+    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(100)
+                                     .y(200))
+                    .pointer(PointerBuilder(/* id */ 1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50))
+                    .build();
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionEvent(mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT,
+                                InputEventInjectionSync::WAIT_FOR_RESULT))
+            << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
+
+    window->consumeMotionDown(ADISPLAY_ID_DEFAULT);
+    spy->consumeMotionPointerDown(1 /* pointerIndex */);
+}
+
+/**
+ * A spy window will usually be implemented as an un-focusable window. Verify that these windows
+ * do not receive key events.
+ */
+TEST_F(InputDispatcherSpyWindowTest, UnfocusableSpyDoesNotReceiveKeyEvents) {
+    auto spy = createSpy();
+    spy->setFocusable(false);
+
+    auto window = createForeground();
+    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {spy, window}}});
+    setFocusedWindow(window);
+    window->consumeFocusEvent(true);
+
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyDown(mDispatcher))
+            << "Inject key event should return InputEventInjectionResult::SUCCEEDED";
+    window->consumeKeyDown(ADISPLAY_ID_NONE);
+
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyUp(mDispatcher))
+            << "Inject key event should return InputEventInjectionResult::SUCCEEDED";
+    window->consumeKeyUp(ADISPLAY_ID_NONE);
+
+    spy->assertNoEvents();
+}
+
+class InputDispatcherStylusInterceptorTest : public InputDispatcherTest {
+public:
+    std::pair<sp<FakeWindowHandle>, sp<FakeWindowHandle>> setupStylusOverlayScenario() {
+        std::shared_ptr<FakeApplicationHandle> overlayApplication =
+                std::make_shared<FakeApplicationHandle>();
+        sp<FakeWindowHandle> overlay =
+                new FakeWindowHandle(overlayApplication, mDispatcher, "Stylus interceptor window",
+                                     ADISPLAY_ID_DEFAULT);
+        overlay->setFocusable(false);
+        overlay->setOwnerInfo(111, 111);
+        overlay->setTouchable(false);
+        overlay->setInterceptsStylus(true);
+        overlay->setTrustedOverlay(true);
+
+        std::shared_ptr<FakeApplicationHandle> application =
+                std::make_shared<FakeApplicationHandle>();
+        sp<FakeWindowHandle> window =
+                new FakeWindowHandle(application, mDispatcher, "Application window",
+                                     ADISPLAY_ID_DEFAULT);
+        window->setFocusable(true);
+        window->setOwnerInfo(222, 222);
+
+        mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
+        mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {overlay, window}}});
+        setFocusedWindow(window);
+        window->consumeFocusEvent(true /*hasFocus*/, true /*inTouchMode*/);
+        return {std::move(overlay), std::move(window)};
+    }
+
+    void sendFingerEvent(int32_t action) {
+        NotifyMotionArgs motionArgs =
+                generateMotionArgs(action, AINPUT_SOURCE_TOUCHSCREEN | AINPUT_SOURCE_STYLUS,
+                                   ADISPLAY_ID_DEFAULT, {PointF{20, 20}});
+        mDispatcher->notifyMotion(&motionArgs);
+    }
+
+    void sendStylusEvent(int32_t action) {
+        NotifyMotionArgs motionArgs =
+                generateMotionArgs(action, AINPUT_SOURCE_TOUCHSCREEN | AINPUT_SOURCE_STYLUS,
+                                   ADISPLAY_ID_DEFAULT, {PointF{30, 40}});
+        motionArgs.pointerProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS;
+        mDispatcher->notifyMotion(&motionArgs);
+    }
+};
+
+using InputDispatcherStylusInterceptorDeathTest = InputDispatcherStylusInterceptorTest;
+
+TEST_F(InputDispatcherStylusInterceptorDeathTest, UntrustedOverlay_AbortsDispatcher) {
+    ScopedSilentDeath _silentDeath;
+
+    auto [overlay, window] = setupStylusOverlayScenario();
+    overlay->setTrustedOverlay(false);
+    // Configuring an untrusted overlay as a stylus interceptor should cause Dispatcher to abort.
+    ASSERT_DEATH(mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {overlay, window}}}),
+                 ".* not a trusted overlay");
+}
+
+TEST_F(InputDispatcherStylusInterceptorTest, ConsmesOnlyStylusEvents) {
+    auto [overlay, window] = setupStylusOverlayScenario();
+    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {overlay, window}}});
+
+    sendStylusEvent(AMOTION_EVENT_ACTION_DOWN);
+    overlay->consumeMotionDown();
+    sendStylusEvent(AMOTION_EVENT_ACTION_UP);
+    overlay->consumeMotionUp();
+
+    sendFingerEvent(AMOTION_EVENT_ACTION_DOWN);
+    window->consumeMotionDown();
+    sendFingerEvent(AMOTION_EVENT_ACTION_UP);
+    window->consumeMotionUp();
+
+    overlay->assertNoEvents();
+    window->assertNoEvents();
+}
+
+TEST_F(InputDispatcherStylusInterceptorTest, SpyWindowStylusInterceptor) {
+    auto [overlay, window] = setupStylusOverlayScenario();
+    overlay->setSpy(true);
+    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {overlay, window}}});
+
+    sendStylusEvent(AMOTION_EVENT_ACTION_DOWN);
+    overlay->consumeMotionDown();
+    window->consumeMotionDown();
+    sendStylusEvent(AMOTION_EVENT_ACTION_UP);
+    overlay->consumeMotionUp();
+    window->consumeMotionUp();
+
+    sendFingerEvent(AMOTION_EVENT_ACTION_DOWN);
+    window->consumeMotionDown();
+    sendFingerEvent(AMOTION_EVENT_ACTION_UP);
+    window->consumeMotionUp();
+
+    overlay->assertNoEvents();
+    window->assertNoEvents();
+}
+
+/**
+ * Set up a scenario to test the behavior used by the stylus handwriting detection feature.
+ * The scenario is as follows:
+ *   - The stylus interceptor overlay is configured as a spy window.
+ *   - The stylus interceptor spy receives the start of a new stylus gesture.
+ *   - It pilfers pointers and then configures itself to no longer be a spy.
+ *   - The stylus interceptor continues to receive the rest of the gesture.
+ */
+TEST_F(InputDispatcherStylusInterceptorTest, StylusHandwritingScenario) {
+    auto [overlay, window] = setupStylusOverlayScenario();
+    overlay->setSpy(true);
+    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {overlay, window}}});
+
+    sendStylusEvent(AMOTION_EVENT_ACTION_DOWN);
+    overlay->consumeMotionDown();
+    window->consumeMotionDown();
+
+    // The interceptor pilfers the pointers.
+    EXPECT_EQ(OK, mDispatcher->pilferPointers(overlay->getToken()));
+    window->consumeMotionCancel();
+
+    // The interceptor configures itself so that it is no longer a spy.
+    overlay->setSpy(false);
+    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {overlay, window}}});
+
+    // It continues to receive the rest of the stylus gesture.
+    sendStylusEvent(AMOTION_EVENT_ACTION_MOVE);
+    overlay->consumeMotionMove();
+    sendStylusEvent(AMOTION_EVENT_ACTION_UP);
+    overlay->consumeMotionUp();
+
+    window->assertNoEvents();
+}
+
 } // namespace android::inputdispatcher
diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp
index b419d9a..9f33d23 100644
--- a/services/inputflinger/tests/InputReader_test.cpp
+++ b/services/inputflinger/tests/InputReader_test.cpp
@@ -16,11 +16,11 @@
 
 #include <CursorInputMapper.h>
 #include <InputDevice.h>
-#include <InputFlingerProperties.sysprop.h>
 #include <InputMapper.h>
 #include <InputReader.h>
 #include <InputReaderBase.h>
 #include <InputReaderFactory.h>
+#include <JoystickInputMapper.h>
 #include <KeyboardInputMapper.h>
 #include <MultiTouchInputMapper.h>
 #include <PeripheralController.h>
@@ -79,6 +79,15 @@
 static constexpr int32_t LIGHT_COLOR = 0x7F448866;
 static constexpr int32_t LIGHT_PLAYER_ID = 2;
 
+static constexpr int32_t ACTION_POINTER_0_DOWN =
+        AMOTION_EVENT_ACTION_POINTER_DOWN | (0 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
+static constexpr int32_t ACTION_POINTER_0_UP =
+        AMOTION_EVENT_ACTION_POINTER_UP | (0 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
+static constexpr int32_t ACTION_POINTER_1_DOWN =
+        AMOTION_EVENT_ACTION_POINTER_DOWN | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
+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;
 
@@ -107,6 +116,24 @@
     }
 }
 
+static void assertAxisResolution(MultiTouchInputMapper& mapper, int axis, float resolution) {
+    InputDeviceInfo info;
+    mapper.populateDeviceInfo(&info);
+
+    const InputDeviceInfo::MotionRange* motionRange =
+            info.getMotionRange(axis, AINPUT_SOURCE_TOUCHSCREEN);
+    ASSERT_NEAR(motionRange->resolution, resolution, EPSILON);
+}
+
+static void assertAxisNotPresent(MultiTouchInputMapper& mapper, int axis) {
+    InputDeviceInfo info;
+    mapper.populateDeviceInfo(&info);
+
+    const InputDeviceInfo::MotionRange* motionRange =
+            info.getMotionRange(axis, AINPUT_SOURCE_TOUCHSCREEN);
+    ASSERT_EQ(nullptr, motionRange);
+}
+
 // --- FakePointerController ---
 
 class FakePointerController : public PointerControllerInterface {
@@ -205,7 +232,7 @@
     std::condition_variable mDevicesChangedCondition;
 
     InputReaderConfiguration mConfig;
-    std::unordered_map<int32_t, std::shared_ptr<FakePointerController>> mPointerControllers;
+    std::shared_ptr<FakePointerController> mPointerController;
     std::vector<InputDeviceInfo> mInputDevices GUARDED_BY(mLock);
     bool mInputDevicesChanged GUARDED_BY(mLock){false};
     std::vector<DisplayViewport> mViewports;
@@ -250,14 +277,35 @@
         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 viewportType) {
-        const DisplayViewport viewport =
-                createDisplayViewport(displayId, width, height, orientation, isActive, uniqueId,
-                                      physicalPort, viewportType);
-        mViewports.push_back(viewport);
-        mConfig.setDisplayViewports(mViewports);
+                            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) {
@@ -291,8 +339,8 @@
 
     void removeDisabledDevice(int32_t deviceId) { mConfig.disabledDevices.erase(deviceId); }
 
-    void setPointerController(int32_t deviceId, std::shared_ptr<FakePointerController> controller) {
-        mPointerControllers.insert_or_assign(deviceId, std::move(controller));
+    void setPointerController(std::shared_ptr<FakePointerController> controller) {
+        mPointerController = std::move(controller);
     }
 
     const InputReaderConfiguration* getReaderConfiguration() const {
@@ -330,38 +378,13 @@
 private:
     uint32_t mNextPointerCaptureSequenceNumber = 0;
 
-    DisplayViewport createDisplayViewport(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) {
-        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;
-        return v;
-    }
-
     void getReaderConfiguration(InputReaderConfiguration* outConfig) override {
         *outConfig = mConfig;
     }
 
-    std::shared_ptr<PointerControllerInterface> obtainPointerController(int32_t deviceId) override {
-        return mPointerControllers[deviceId];
+    std::shared_ptr<PointerControllerInterface> obtainPointerController(
+            int32_t /*deviceId*/) override {
+        return mPointerController;
     }
 
     void notifyInputDevicesChanged(const std::vector<InputDeviceInfo>& inputDevices) override {
@@ -417,6 +440,8 @@
         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;
@@ -578,6 +603,11 @@
         }
     }
 
+    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);
@@ -841,6 +871,15 @@
         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, size_t numCodes, const int32_t* keyCodes,
                                uint8_t* outFlags) const override {
@@ -873,6 +912,24 @@
         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;
@@ -994,6 +1051,8 @@
     KeyedVector<int32_t, int32_t> mKeyCodeStates;
     KeyedVector<int32_t, int32_t> mScanCodeStates;
     KeyedVector<int32_t, int32_t> mSwitchStates;
+    // fake mapping which would normally come from keyCharacterMap
+    std::unordered_map<int32_t, int32_t> mKeyCodeMapping;
     std::vector<int32_t> mSupportedKeyCodes;
 
     std::mutex mLock;
@@ -1082,8 +1141,12 @@
         mSupportedKeyCodes.push_back(keyCode);
     }
 
+    void addKeyCodeMapping(int32_t fromKeyCode, int32_t toKeyCode) {
+        mKeyCodeMapping.insert_or_assign(fromKeyCode, toKeyCode);
+    }
+
 private:
-    uint32_t getSources() override { return mSources; }
+    uint32_t getSources() const override { return mSources; }
 
     void populateDeviceInfo(InputDeviceInfo* deviceInfo) override {
         InputMapper::populateDeviceInfo(deviceInfo);
@@ -1124,6 +1187,11 @@
         return index >= 0 ? mKeyCodeStates.valueAt(index) : AKEY_STATE_UNKNOWN;
     }
 
+    int32_t getKeyCodeForKeyLocation(int32_t locationKeyCode) const override {
+        auto it = mKeyCodeMapping.find(locationKeyCode);
+        return it != mKeyCodeMapping.end() ? it->second : locationKeyCode;
+    }
+
     int32_t getScanCodeState(uint32_t, int32_t scanCode) override {
         ssize_t index = mScanCodeStates.indexOfKey(scanCode);
         return index >= 0 ? mScanCodeStates.valueAt(index) : AKEY_STATE_UNKNOWN;
@@ -1172,7 +1240,7 @@
 public:
     InstrumentedInputReader(std::shared_ptr<EventHubInterface> eventHub,
                             const sp<InputReaderPolicyInterface>& policy,
-                            const sp<InputListenerInterface>& listener)
+                            InputListenerInterface& listener)
           : InputReader(eventHub, policy, listener), mFakeContext(this) {}
 
     virtual ~InstrumentedInputReader() {}
@@ -1485,7 +1553,7 @@
 
 class InputReaderTest : public testing::Test {
 protected:
-    sp<TestInputListener> mFakeListener;
+    std::unique_ptr<TestInputListener> mFakeListener;
     sp<FakeInputReaderPolicy> mFakePolicy;
     std::shared_ptr<FakeEventHub> mFakeEventHub;
     std::unique_ptr<InstrumentedInputReader> mReader;
@@ -1493,14 +1561,14 @@
     void SetUp() override {
         mFakeEventHub = std::make_unique<FakeEventHub>();
         mFakePolicy = new FakeInputReaderPolicy();
-        mFakeListener = new TestInputListener();
+        mFakeListener = std::make_unique<TestInputListener>();
 
         mReader = std::make_unique<InstrumentedInputReader>(mFakeEventHub, mFakePolicy,
-                                                            mFakeListener);
+                                                            *mFakeListener);
     }
 
     void TearDown() override {
-        mFakeListener.clear();
+        mFakeListener.reset();
         mFakePolicy.clear();
     }
 
@@ -1672,6 +1740,37 @@
             << "Should return value provided by mapper when device id is < 0 and one of the devices supports some of the sources.";
 }
 
+TEST_F(InputReaderTest, GetKeyCodeForKeyLocation_ForwardsRequestsToMappers) {
+    constexpr int32_t deviceId = END_RESERVED_ID + 1000;
+    constexpr int32_t eventHubId = 1;
+    FakeInputMapper& mapper = addDeviceWithFakeInputMapper(deviceId, eventHubId, "keyboard",
+                                                           InputDeviceClass::KEYBOARD,
+                                                           AINPUT_SOURCE_KEYBOARD, nullptr);
+    mapper.addKeyCodeMapping(AKEYCODE_Y, AKEYCODE_Z);
+
+    ASSERT_EQ(AKEYCODE_UNKNOWN, mReader->getKeyCodeForKeyLocation(0, AKEYCODE_Y))
+            << "Should return unknown when the device with the specified id is not found.";
+
+    ASSERT_EQ(AKEYCODE_Z, mReader->getKeyCodeForKeyLocation(deviceId, AKEYCODE_Y))
+            << "Should return correct mapping when device id is valid and mapping exists.";
+
+    ASSERT_EQ(AKEYCODE_A, mReader->getKeyCodeForKeyLocation(deviceId, AKEYCODE_A))
+            << "Should return the location key code when device id is valid and there's no "
+               "mapping.";
+}
+
+TEST_F(InputReaderTest, GetKeyCodeForKeyLocation_NoKeyboardMapper) {
+    constexpr int32_t deviceId = END_RESERVED_ID + 1000;
+    constexpr int32_t eventHubId = 1;
+    FakeInputMapper& mapper = addDeviceWithFakeInputMapper(deviceId, eventHubId, "joystick",
+                                                           InputDeviceClass::JOYSTICK,
+                                                           AINPUT_SOURCE_GAMEPAD, nullptr);
+    mapper.addKeyCodeMapping(AKEYCODE_Y, AKEYCODE_Z);
+
+    ASSERT_EQ(AKEYCODE_UNKNOWN, mReader->getKeyCodeForKeyLocation(deviceId, AKEYCODE_Y))
+            << "Should return unknown when the device id is valid but there is no keyboard mapper";
+}
+
 TEST_F(InputReaderTest, GetScanCodeState_ForwardsRequestsToMappers) {
     constexpr int32_t deviceId = END_RESERVED_ID + 1000;
     constexpr Flags<InputDeviceClass> deviceClass = InputDeviceClass::KEYBOARD;
@@ -2131,16 +2230,21 @@
 // the tests to fail.
 class InputReaderIntegrationTest : public testing::Test {
 protected:
-    sp<TestInputListener> mTestListener;
+    std::unique_ptr<TestInputListener> mTestListener;
     sp<FakeInputReaderPolicy> mFakePolicy;
-    sp<InputReaderInterface> mReader;
+    std::unique_ptr<InputReaderInterface> mReader;
+
+    std::shared_ptr<FakePointerController> mFakePointerController;
 
     void SetUp() override {
         mFakePolicy = new FakeInputReaderPolicy();
-        mTestListener = new TestInputListener(2000ms /*eventHappenedTimeout*/,
-                                              30ms /*eventDidNotHappenTimeout*/);
+        mFakePointerController = std::make_shared<FakePointerController>();
+        mFakePolicy->setPointerController(mFakePointerController);
+        mTestListener = std::make_unique<TestInputListener>(2000ms /*eventHappenedTimeout*/,
+                                                            30ms /*eventDidNotHappenTimeout*/);
 
-        mReader = new InputReader(std::make_shared<EventHub>(), mFakePolicy, mTestListener);
+        mReader = std::make_unique<InputReader>(std::make_shared<EventHub>(), mFakePolicy,
+                                                *mTestListener);
         ASSERT_EQ(mReader->start(), OK);
 
         // Since this test is run on a real device, all the input devices connected
@@ -2152,7 +2256,8 @@
 
     void TearDown() override {
         ASSERT_EQ(mReader->stop(), OK);
-        mTestListener.clear();
+        mReader.reset();
+        mTestListener.reset();
         mFakePolicy.clear();
     }
 };
@@ -2284,6 +2389,17 @@
         mReader->requestRefreshConfiguration(InputReaderConfiguration::CHANGE_DISPLAY_INFO);
     }
 
+    void assertReceivedMotion(int32_t action, const std::vector<Point>& points) {
+        NotifyMotionArgs args;
+        ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(&args));
+        EXPECT_EQ(action, args.action);
+        ASSERT_EQ(points.size(), args.pointerCount);
+        for (size_t i = 0; i < args.pointerCount; i++) {
+            EXPECT_EQ(points[i].x, args.pointerCoords[i].getX());
+            EXPECT_EQ(points[i].y, args.pointerCoords[i].getY());
+        }
+    }
+
     std::unique_ptr<UinputTouchScreen> mDevice;
 };
 
@@ -2294,16 +2410,19 @@
     // ACTION_DOWN
     mDevice->sendTrackingId(FIRST_TRACKING_ID);
     mDevice->sendDown(centerPoint);
+    mDevice->sendSync();
     ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(&args));
     ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, args.action);
 
     // ACTION_MOVE
     mDevice->sendMove(centerPoint + Point(1, 1));
+    mDevice->sendSync();
     ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(&args));
     ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action);
 
     // ACTION_UP
     mDevice->sendUp();
+    mDevice->sendSync();
     ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(&args));
     ASSERT_EQ(AMOTION_EVENT_ACTION_UP, args.action);
 }
@@ -2316,6 +2435,7 @@
     mDevice->sendSlot(FIRST_SLOT);
     mDevice->sendTrackingId(FIRST_TRACKING_ID);
     mDevice->sendDown(centerPoint);
+    mDevice->sendSync();
     ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(&args));
     ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, args.action);
 
@@ -2323,29 +2443,129 @@
     const Point secondPoint = centerPoint + Point(100, 100);
     mDevice->sendSlot(SECOND_SLOT);
     mDevice->sendTrackingId(SECOND_TRACKING_ID);
-    mDevice->sendDown(secondPoint + Point(1, 1));
+    mDevice->sendDown(secondPoint);
+    mDevice->sendSync();
     ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_POINTER_DOWN | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-              args.action);
+    ASSERT_EQ(ACTION_POINTER_1_DOWN, args.action);
 
     // ACTION_MOVE (Second slot)
-    mDevice->sendMove(secondPoint);
+    mDevice->sendMove(secondPoint + Point(1, 1));
+    mDevice->sendSync();
     ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(&args));
     ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action);
 
     // ACTION_POINTER_UP (Second slot)
     mDevice->sendPointerUp();
+    mDevice->sendSync();
     ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_POINTER_UP | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-              args.action);
+    ASSERT_EQ(ACTION_POINTER_1_UP, args.action);
 
     // ACTION_UP
     mDevice->sendSlot(FIRST_SLOT);
     mDevice->sendUp();
+    mDevice->sendSync();
     ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(&args));
     ASSERT_EQ(AMOTION_EVENT_ACTION_UP, args.action);
 }
 
+/**
+ * What happens when a pointer goes up while another pointer moves in the same frame? Are POINTER_UP
+ * events guaranteed to contain the same data as a preceding MOVE, or can they contain different
+ * data?
+ * In this test, we try to send a change in coordinates in Pointer 0 in the same frame as the
+ * liftoff of Pointer 1. We check that POINTER_UP event is generated first, and the MOVE event
+ * for Pointer 0 only is generated after.
+ * Suppose we are only interested in learning the movement of Pointer 0. If we only observe MOVE
+ * events, we will not miss any information.
+ * Even though the Pointer 1 up event contains updated Pointer 0 coordinates, there is another MOVE
+ * event generated afterwards that contains the newest movement of pointer 0.
+ * This is important for palm rejection. If there is a subsequent InputListener stage that detects
+ * palms, and wants to cancel Pointer 1, then it is safe to simply drop POINTER_1_UP event without
+ * losing information about non-palm pointers.
+ */
+TEST_F(TouchIntegrationTest, MultiTouch_PointerMoveAndSecondPointerUp) {
+    NotifyMotionArgs args;
+    const Point centerPoint = mDevice->getCenterPoint();
+
+    // ACTION_DOWN
+    mDevice->sendSlot(FIRST_SLOT);
+    mDevice->sendTrackingId(FIRST_TRACKING_ID);
+    mDevice->sendDown(centerPoint);
+    mDevice->sendSync();
+    assertReceivedMotion(AMOTION_EVENT_ACTION_DOWN, {centerPoint});
+
+    // ACTION_POINTER_DOWN (Second slot)
+    const Point secondPoint = centerPoint + Point(100, 100);
+    mDevice->sendSlot(SECOND_SLOT);
+    mDevice->sendTrackingId(SECOND_TRACKING_ID);
+    mDevice->sendDown(secondPoint);
+    mDevice->sendSync();
+    assertReceivedMotion(ACTION_POINTER_1_DOWN, {centerPoint, secondPoint});
+
+    // ACTION_MOVE (First slot)
+    mDevice->sendSlot(FIRST_SLOT);
+    mDevice->sendMove(centerPoint + Point(5, 5));
+    // ACTION_POINTER_UP (Second slot)
+    mDevice->sendSlot(SECOND_SLOT);
+    mDevice->sendPointerUp();
+    // Send a single sync for the above 2 pointer updates
+    mDevice->sendSync();
+
+    // First, we should get POINTER_UP for the second pointer
+    assertReceivedMotion(ACTION_POINTER_1_UP,
+                         {/*first pointer */ centerPoint + Point(5, 5),
+                          /*second pointer*/ secondPoint});
+
+    // Next, the MOVE event for the first pointer
+    assertReceivedMotion(AMOTION_EVENT_ACTION_MOVE, {centerPoint + Point(5, 5)});
+}
+
+/**
+ * Similar scenario as above. The difference is that when the second pointer goes up, it will first
+ * move, and then it will go up, all in the same frame.
+ * In this scenario, the movement of the second pointer just prior to liftoff is ignored, and never
+ * gets sent to the listener.
+ */
+TEST_F(TouchIntegrationTest, MultiTouch_PointerMoveAndSecondPointerMoveAndUp) {
+    NotifyMotionArgs args;
+    const Point centerPoint = mDevice->getCenterPoint();
+
+    // ACTION_DOWN
+    mDevice->sendSlot(FIRST_SLOT);
+    mDevice->sendTrackingId(FIRST_TRACKING_ID);
+    mDevice->sendDown(centerPoint);
+    mDevice->sendSync();
+    assertReceivedMotion(AMOTION_EVENT_ACTION_DOWN, {centerPoint});
+
+    // ACTION_POINTER_DOWN (Second slot)
+    const Point secondPoint = centerPoint + Point(100, 100);
+    mDevice->sendSlot(SECOND_SLOT);
+    mDevice->sendTrackingId(SECOND_TRACKING_ID);
+    mDevice->sendDown(secondPoint);
+    mDevice->sendSync();
+    assertReceivedMotion(ACTION_POINTER_1_DOWN, {centerPoint, secondPoint});
+
+    // ACTION_MOVE (First slot)
+    mDevice->sendSlot(FIRST_SLOT);
+    mDevice->sendMove(centerPoint + Point(5, 5));
+    // ACTION_POINTER_UP (Second slot)
+    mDevice->sendSlot(SECOND_SLOT);
+    mDevice->sendMove(secondPoint + Point(6, 6));
+    mDevice->sendPointerUp();
+    // Send a single sync for the above 2 pointer updates
+    mDevice->sendSync();
+
+    // First, we should get POINTER_UP for the second pointer
+    // The movement of the second pointer during the liftoff frame is ignored.
+    // The coordinates 'secondPoint + Point(6, 6)' are never sent to the listener.
+    assertReceivedMotion(ACTION_POINTER_1_UP,
+                         {/*first pointer */ centerPoint + Point(5, 5),
+                          /*second pointer*/ secondPoint});
+
+    // Next, the MOVE event for the first pointer
+    assertReceivedMotion(AMOTION_EVENT_ACTION_MOVE, {centerPoint + Point(5, 5)});
+}
+
 TEST_F(TouchIntegrationTest, InputEvent_ProcessPalm) {
     NotifyMotionArgs args;
     const Point centerPoint = mDevice->getCenterPoint();
@@ -2354,6 +2574,7 @@
     mDevice->sendSlot(FIRST_SLOT);
     mDevice->sendTrackingId(FIRST_TRACKING_ID);
     mDevice->sendDown(centerPoint);
+    mDevice->sendSync();
     ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(&args));
     ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, args.action);
 
@@ -2362,12 +2583,13 @@
     mDevice->sendSlot(SECOND_SLOT);
     mDevice->sendTrackingId(SECOND_TRACKING_ID);
     mDevice->sendDown(secondPoint);
+    mDevice->sendSync();
     ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_POINTER_DOWN | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-              args.action);
+    ASSERT_EQ(ACTION_POINTER_1_DOWN, args.action);
 
     // ACTION_MOVE (second slot)
     mDevice->sendMove(secondPoint + Point(1, 1));
+    mDevice->sendSync();
     ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(&args));
     ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action);
 
@@ -2375,19 +2597,21 @@
     // a palm event.
     // Expect to receive the ACTION_POINTER_UP with cancel flag.
     mDevice->sendToolType(MT_TOOL_PALM);
+    mDevice->sendSync();
     ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_POINTER_UP | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-              args.action);
+    ASSERT_EQ(ACTION_POINTER_1_UP, args.action);
     ASSERT_EQ(AMOTION_EVENT_FLAG_CANCELED, args.flags);
 
     // Send up to second slot, expect first slot send moving.
     mDevice->sendPointerUp();
+    mDevice->sendSync();
     ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(&args));
     ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action);
 
     // Send ACTION_UP (first slot)
     mDevice->sendSlot(FIRST_SLOT);
     mDevice->sendUp();
+    mDevice->sendSync();
 
     ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(&args));
     ASSERT_EQ(AMOTION_EVENT_ACTION_UP, args.action);
@@ -2406,16 +2630,16 @@
 
     std::shared_ptr<FakeEventHub> mFakeEventHub;
     sp<FakeInputReaderPolicy> mFakePolicy;
-    sp<TestInputListener> mFakeListener;
+    std::unique_ptr<TestInputListener> mFakeListener;
     std::unique_ptr<InstrumentedInputReader> mReader;
     std::shared_ptr<InputDevice> mDevice;
 
     void SetUp() override {
         mFakeEventHub = std::make_unique<FakeEventHub>();
         mFakePolicy = new FakeInputReaderPolicy();
-        mFakeListener = new TestInputListener();
+        mFakeListener = std::make_unique<TestInputListener>();
         mReader = std::make_unique<InstrumentedInputReader>(mFakeEventHub, mFakePolicy,
-                                                            mFakeListener);
+                                                            *mFakeListener);
         InputDeviceIdentifier identifier;
         identifier.name = DEVICE_NAME;
         identifier.location = DEVICE_LOCATION;
@@ -2427,7 +2651,7 @@
     }
 
     void TearDown() override {
-        mFakeListener.clear();
+        mFakeListener.reset();
         mFakePolicy.clear();
     }
 };
@@ -2639,7 +2863,7 @@
     // Device should be disabled because it is associated with a specific display, but the
     // corresponding display is not found.
     const std::string DISPLAY_UNIQUE_ID = "displayUniqueId";
-    mFakePolicy->addInputUniqueIdAssociation(DEVICE_NAME, DISPLAY_UNIQUE_ID);
+    mFakePolicy->addInputUniqueIdAssociation(DEVICE_LOCATION, DISPLAY_UNIQUE_ID);
     mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
                        InputReaderConfiguration::CHANGE_DISPLAY_INFO);
     ASSERT_FALSE(mDevice->isEnabled());
@@ -2664,6 +2888,21 @@
     ASSERT_FALSE(mDevice->isEnabled());
 }
 
+TEST_F(InputDeviceTest, Configure_UniqueId_CorrectlyMatches) {
+    mFakePolicy->clearViewports();
+    mDevice->addMapper<FakeInputMapper>(EVENTHUB_ID, AINPUT_SOURCE_KEYBOARD);
+    mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), 0);
+
+    const std::string DISPLAY_UNIQUE_ID = "displayUniqueId";
+    mFakePolicy->addInputUniqueIdAssociation(DEVICE_LOCATION, DISPLAY_UNIQUE_ID);
+    mFakePolicy->addDisplayViewport(SECONDARY_DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT,
+                                    DISPLAY_ORIENTATION_0, /* isActive= */ true, DISPLAY_UNIQUE_ID,
+                                    NO_PORT, ViewportType::INTERNAL);
+    mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
+                       InputReaderConfiguration::CHANGE_DISPLAY_INFO);
+    ASSERT_EQ(DISPLAY_UNIQUE_ID, mDevice->getAssociatedDisplayUniqueId());
+}
+
 // --- InputMapperTest ---
 
 class InputMapperTest : public testing::Test {
@@ -2675,36 +2914,29 @@
     static const int32_t DEVICE_CONTROLLER_NUMBER;
     static const Flags<InputDeviceClass> DEVICE_CLASSES;
     static const int32_t EVENTHUB_ID;
-    static const std::optional<bool> INITIAL_PER_WINDOW_INPUT_ROTATION_FLAG_VALUE;
 
     std::shared_ptr<FakeEventHub> mFakeEventHub;
     sp<FakeInputReaderPolicy> mFakePolicy;
-    sp<TestInputListener> mFakeListener;
+    std::unique_ptr<TestInputListener> mFakeListener;
     std::unique_ptr<InstrumentedInputReader> mReader;
     std::shared_ptr<InputDevice> mDevice;
 
     virtual void SetUp(Flags<InputDeviceClass> classes) {
         mFakeEventHub = std::make_unique<FakeEventHub>();
         mFakePolicy = new FakeInputReaderPolicy();
-        mFakeListener = new TestInputListener();
+        mFakeListener = std::make_unique<TestInputListener>();
         mReader = std::make_unique<InstrumentedInputReader>(mFakeEventHub, mFakePolicy,
-                                                            mFakeListener);
+                                                            *mFakeListener);
         mDevice = newDevice(DEVICE_ID, DEVICE_NAME, DEVICE_LOCATION, EVENTHUB_ID, classes);
     }
 
     void SetUp() override {
-        // Ensure per_window_input_rotation is enabled.
-        sysprop::InputFlingerProperties::per_window_input_rotation(true);
-
         SetUp(DEVICE_CLASSES);
     }
 
     void TearDown() override {
-        mFakeListener.clear();
+        mFakeListener.reset();
         mFakePolicy.clear();
-
-        sysprop::InputFlingerProperties::per_window_input_rotation(
-                INITIAL_PER_WINDOW_INPUT_ROTATION_FLAG_VALUE);
     }
 
     void addConfigurationProperty(const char* key, const char* value) {
@@ -2816,8 +3048,6 @@
 const Flags<InputDeviceClass> InputMapperTest::DEVICE_CLASSES =
         Flags<InputDeviceClass>(0); // not needed for current tests
 const int32_t InputMapperTest::EVENTHUB_ID = 1;
-const std::optional<bool> InputMapperTest::INITIAL_PER_WINDOW_INPUT_ROTATION_FLAG_VALUE =
-        sysprop::InputFlingerProperties::per_window_input_rotation();
 
 // --- SwitchInputMapperTest ---
 
@@ -3453,6 +3683,19 @@
     ASSERT_EQ(0, mapper.getKeyCodeState(AINPUT_SOURCE_ANY, AKEYCODE_A));
 }
 
+TEST_F(KeyboardInputMapperTest, GetKeyCodeForKeyLocation) {
+    KeyboardInputMapper& mapper =
+            addMapperAndConfigure<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
+                                                       AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+
+    mFakeEventHub->addKeyCodeMapping(EVENTHUB_ID, AKEYCODE_Y, AKEYCODE_Z);
+    ASSERT_EQ(AKEYCODE_Z, mapper.getKeyCodeForKeyLocation(AKEYCODE_Y))
+            << "If a mapping is available, the result is equal to the mapping";
+
+    ASSERT_EQ(AKEYCODE_A, mapper.getKeyCodeForKeyLocation(AKEYCODE_A))
+            << "If no mapping is available, the result is the key location";
+}
+
 TEST_F(KeyboardInputMapperTest, GetScanCodeState) {
     KeyboardInputMapper& mapper =
             addMapperAndConfigure<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
@@ -3732,6 +3975,25 @@
               mapper2.getMetaState());
 }
 
+TEST_F(KeyboardInputMapperTest, Process_toggleCapsLockState) {
+    mFakeEventHub->addKey(EVENTHUB_ID, KEY_CAPSLOCK, 0, AKEYCODE_CAPS_LOCK, 0);
+    mFakeEventHub->addKey(EVENTHUB_ID, KEY_NUMLOCK, 0, AKEYCODE_NUM_LOCK, 0);
+    mFakeEventHub->addKey(EVENTHUB_ID, KEY_SCROLLLOCK, 0, AKEYCODE_SCROLL_LOCK, 0);
+
+    // Suppose we have two mappers. (DPAD + KEYBOARD)
+    addMapperAndConfigure<KeyboardInputMapper>(AINPUT_SOURCE_DPAD,
+                                               AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC);
+    KeyboardInputMapper& mapper =
+            addMapperAndConfigure<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
+                                                       AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+    // Initialize metastate to AMETA_NUM_LOCK_ON.
+    ASSERT_EQ(AMETA_NUM_LOCK_ON, mapper.getMetaState());
+    mapper.updateMetaState(AKEYCODE_NUM_LOCK);
+
+    mReader->toggleCapsLockState(DEVICE_ID);
+    ASSERT_EQ(AMETA_CAPS_LOCK_ON, mapper.getMetaState());
+}
+
 // --- KeyboardInputMapperTest_ExternalDevice ---
 
 class KeyboardInputMapperTest_ExternalDevice : public InputMapperTest {
@@ -3828,7 +4090,7 @@
         InputMapperTest::SetUp();
 
         mFakePointerController = std::make_shared<FakePointerController>();
-        mFakePolicy->setPointerController(mDevice->getId(), mFakePointerController);
+        mFakePolicy->setPointerController(mFakePointerController);
     }
 
     void testMotionRotation(CursorInputMapper& mapper, int32_t originalX, int32_t originalY,
@@ -6254,6 +6516,172 @@
             toDisplayX(150), toDisplayY(250), 0, 0, 0, 0, 0, 0, 0, 0));
 }
 
+// --- TouchDisplayProjectionTest ---
+
+class TouchDisplayProjectionTest : public SingleTouchInputMapperTest {
+public:
+    // 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) {
+        uint32_t inverseRotationFlags;
+        auto width = DISPLAY_WIDTH;
+        auto height = DISPLAY_HEIGHT;
+        switch (orientation) {
+            case DISPLAY_ORIENTATION_90:
+                inverseRotationFlags = ui::Transform::ROT_270;
+                std::swap(width, height);
+                break;
+            case DISPLAY_ORIENTATION_180:
+                inverseRotationFlags = ui::Transform::ROT_180;
+                break;
+            case DISPLAY_ORIENTATION_270:
+                inverseRotationFlags = ui::Transform::ROT_90;
+                std::swap(width, height);
+                break;
+            case DISPLAY_ORIENTATION_0:
+                inverseRotationFlags = ui::Transform::ROT_0;
+                break;
+            default:
+                FAIL() << "Invalid orientation: " << orientation;
+        }
+
+        const ui::Transform rotation(inverseRotationFlags, width, height);
+        const Rect rotatedPhysicalDisplay = rotation.transform(naturalPhysicalDisplay);
+
+        std::optional<DisplayViewport> internalViewport =
+                *mFakePolicy->getDisplayViewportByType(ViewportType::INTERNAL);
+        DisplayViewport& v = *internalViewport;
+        v.displayId = DISPLAY_ID;
+        v.orientation = orientation;
+
+        v.logicalLeft = 0;
+        v.logicalTop = 0;
+        v.logicalRight = 100;
+        v.logicalBottom = 100;
+
+        v.physicalLeft = rotatedPhysicalDisplay.left;
+        v.physicalTop = rotatedPhysicalDisplay.top;
+        v.physicalRight = rotatedPhysicalDisplay.right;
+        v.physicalBottom = rotatedPhysicalDisplay.bottom;
+
+        v.deviceWidth = width;
+        v.deviceHeight = height;
+
+        v.isActive = true;
+        v.uniqueId = UNIQUE_ID;
+        v.type = ViewportType::INTERNAL;
+        mFakePolicy->updateViewport(v);
+        configureDevice(InputReaderConfiguration::CHANGE_DISPLAY_INFO);
+    }
+
+    void assertReceivedMove(const Point& point) {
+        NotifyMotionArgs motionArgs;
+        ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+        ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
+        ASSERT_EQ(size_t(1), motionArgs.pointerCount);
+        ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], point.x, point.y,
+                                                    1, 0, 0, 0, 0, 0, 0, 0));
+    }
+};
+
+TEST_F(TouchDisplayProjectionTest, IgnoresTouchesOutsidePhysicalDisplay) {
+    addConfigurationProperty("touch.deviceType", "touchScreen");
+    prepareDisplay(DISPLAY_ORIENTATION_0);
+
+    prepareButtons();
+    prepareAxes(POSITION);
+    SingleTouchInputMapper& mapper = addMapperAndConfigure<SingleTouchInputMapper>();
+
+    NotifyMotionArgs motionArgs;
+
+    // Configure the DisplayViewport such that the logical display maps to a subsection of
+    // the display panel called the physical display. Here, the physical display is bounded by the
+    // 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};
+    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}) {
+        configurePhysicalDisplay(orientation, kPhysicalDisplay);
+
+        // Touches outside the physical display should be ignored, and should not generate any
+        // events. Ensure touches at the following points that lie outside of the physical display
+        // area do not generate any events.
+        for (const auto& point : kPointsOutsidePhysicalDisplay) {
+            processDown(mapper, toRawX(point.x), toRawY(point.y));
+            processSync(mapper);
+            processUp(mapper);
+            processSync(mapper);
+            ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled())
+                    << "Unexpected event generated for touch outside physical display at point: "
+                    << point.x << ", " << point.y;
+        }
+    }
+}
+
+TEST_F(TouchDisplayProjectionTest, EmitsTouchDownAfterEnteringPhysicalDisplay) {
+    addConfigurationProperty("touch.deviceType", "touchScreen");
+    prepareDisplay(DISPLAY_ORIENTATION_0);
+
+    prepareButtons();
+    prepareAxes(POSITION);
+    SingleTouchInputMapper& mapper = addMapperAndConfigure<SingleTouchInputMapper>();
+
+    NotifyMotionArgs motionArgs;
+
+    // Configure the DisplayViewport such that the logical display maps to a subsection of
+    // the display panel called the physical display. Here, the physical display is bounded by the
+    // 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}) {
+        configurePhysicalDisplay(orientation, kPhysicalDisplay);
+
+        // Touches that start outside the physical display should be ignored until it enters the
+        // physical display bounds, at which point it should generate a down event. Start a touch at
+        // the point (5, 100), which is outside the physical display bounds.
+        static const Point kOutsidePoint{5, 100};
+        processDown(mapper, toRawX(kOutsidePoint.x), toRawY(kOutsidePoint.y));
+        processSync(mapper);
+        ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled());
+
+        // Move the touch into the physical display area. This should generate a pointer down.
+        processMove(mapper, toRawX(11), toRawY(21));
+        processSync(mapper);
+        ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+        ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action);
+        ASSERT_EQ(size_t(1), motionArgs.pointerCount);
+        ASSERT_NO_FATAL_FAILURE(
+                assertPointerCoords(motionArgs.pointerCoords[0], 11, 21, 1, 0, 0, 0, 0, 0, 0, 0));
+
+        // Move the touch inside the physical display area. This should generate a pointer move.
+        processMove(mapper, toRawX(69), toRawY(159));
+        processSync(mapper);
+        assertReceivedMove({69, 159});
+
+        // Move outside the physical display area. Since the pointer is already down, this should
+        // now continue generating events.
+        processMove(mapper, toRawX(kOutsidePoint.x), toRawY(kOutsidePoint.y));
+        processSync(mapper);
+        assertReceivedMove(kOutsidePoint);
+
+        // Release. This should generate a pointer up.
+        processUp(mapper);
+        processSync(mapper);
+        ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+        ASSERT_EQ(AMOTION_EVENT_ACTION_UP, motionArgs.action);
+        ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], kOutsidePoint.x,
+                                                    kOutsidePoint.y, 1, 0, 0, 0, 0, 0, 0, 0));
+
+        // Ensure no more events were generated.
+        ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasNotCalled());
+        ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled());
+    }
+}
+
 // --- MultiTouchInputMapperTest ---
 
 class MultiTouchInputMapperTest : public TouchInputMapperTest {
@@ -6293,7 +6721,7 @@
         mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_WIDTH_MAJOR, RAW_TOOL_MIN, RAW_TOOL_MAX,
                                        0, 0);
         if (axes & MINOR) {
-            mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_WIDTH_MINOR, RAW_TOOL_MAX,
+            mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_WIDTH_MINOR, RAW_TOOL_MIN,
                                            RAW_TOOL_MAX, 0, 0);
         }
     }
@@ -6427,8 +6855,7 @@
     ASSERT_EQ(DEVICE_ID, motionArgs.deviceId);
     ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, motionArgs.source);
     ASSERT_EQ(uint32_t(0), motionArgs.policyFlags);
-    ASSERT_EQ(AMOTION_EVENT_ACTION_POINTER_DOWN | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-            motionArgs.action);
+    ASSERT_EQ(ACTION_POINTER_1_DOWN, motionArgs.action);
     ASSERT_EQ(0, motionArgs.flags);
     ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, motionArgs.metaState);
     ASSERT_EQ(0, motionArgs.buttonState);
@@ -6488,8 +6915,7 @@
     ASSERT_EQ(DEVICE_ID, motionArgs.deviceId);
     ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, motionArgs.source);
     ASSERT_EQ(uint32_t(0), motionArgs.policyFlags);
-    ASSERT_EQ(AMOTION_EVENT_ACTION_POINTER_UP | (0 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-            motionArgs.action);
+    ASSERT_EQ(ACTION_POINTER_0_UP, motionArgs.action);
     ASSERT_EQ(0, motionArgs.flags);
     ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, motionArgs.metaState);
     ASSERT_EQ(0, motionArgs.buttonState);
@@ -6564,8 +6990,7 @@
     ASSERT_EQ(DEVICE_ID, motionArgs.deviceId);
     ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, motionArgs.source);
     ASSERT_EQ(uint32_t(0), motionArgs.policyFlags);
-    ASSERT_EQ(AMOTION_EVENT_ACTION_POINTER_DOWN | (0 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-            motionArgs.action);
+    ASSERT_EQ(ACTION_POINTER_0_DOWN, motionArgs.action);
     ASSERT_EQ(0, motionArgs.flags);
     ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, motionArgs.metaState);
     ASSERT_EQ(0, motionArgs.buttonState);
@@ -6594,8 +7019,7 @@
     ASSERT_EQ(DEVICE_ID, motionArgs.deviceId);
     ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, motionArgs.source);
     ASSERT_EQ(uint32_t(0), motionArgs.policyFlags);
-    ASSERT_EQ(AMOTION_EVENT_ACTION_POINTER_UP | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-            motionArgs.action);
+    ASSERT_EQ(ACTION_POINTER_1_UP, motionArgs.action);
     ASSERT_EQ(0, motionArgs.flags);
     ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, motionArgs.metaState);
     ASSERT_EQ(0, motionArgs.buttonState);
@@ -6660,6 +7084,57 @@
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled());
 }
 
+TEST_F(MultiTouchInputMapperTest, AxisResolution_IsPopulated) {
+    addConfigurationProperty("touch.deviceType", "touchScreen");
+    prepareDisplay(DISPLAY_ORIENTATION_0);
+
+    mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_POSITION_X, RAW_X_MIN, RAW_X_MAX, /*flat*/ 0,
+                                   /*fuzz*/ 0, /*resolution*/ 10);
+    mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_POSITION_Y, RAW_Y_MIN, RAW_Y_MAX, /*flat*/ 0,
+                                   /*fuzz*/ 0, /*resolution*/ 11);
+    mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_TOUCH_MAJOR, RAW_TOUCH_MIN, RAW_TOUCH_MAX,
+                                   /*flat*/ 0, /*fuzz*/ 0, /*resolution*/ 12);
+    mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_TOUCH_MINOR, RAW_TOUCH_MIN, RAW_TOUCH_MAX,
+                                   /*flat*/ 0, /*fuzz*/ 0, /*resolution*/ 13);
+    mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_WIDTH_MAJOR, RAW_TOOL_MIN, RAW_TOOL_MAX,
+                                   /*flat*/ 0, /*flat*/ 0, /*resolution*/ 14);
+    mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_WIDTH_MINOR, RAW_TOOL_MIN, RAW_TOOL_MAX,
+                                   /*flat*/ 0, /*flat*/ 0, /*resolution*/ 15);
+
+    MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
+
+    // X and Y axes
+    assertAxisResolution(mapper, AMOTION_EVENT_AXIS_X, 10 / X_PRECISION);
+    assertAxisResolution(mapper, AMOTION_EVENT_AXIS_Y, 11 / Y_PRECISION);
+    // Touch major and minor
+    assertAxisResolution(mapper, AMOTION_EVENT_AXIS_TOUCH_MAJOR, 12 * GEOMETRIC_SCALE);
+    assertAxisResolution(mapper, AMOTION_EVENT_AXIS_TOUCH_MINOR, 13 * GEOMETRIC_SCALE);
+    // Tool major and minor
+    assertAxisResolution(mapper, AMOTION_EVENT_AXIS_TOOL_MAJOR, 14 * GEOMETRIC_SCALE);
+    assertAxisResolution(mapper, AMOTION_EVENT_AXIS_TOOL_MINOR, 15 * GEOMETRIC_SCALE);
+}
+
+TEST_F(MultiTouchInputMapperTest, TouchMajorAndMinorAxes_DoNotAppearIfNotSupported) {
+    addConfigurationProperty("touch.deviceType", "touchScreen");
+    prepareDisplay(DISPLAY_ORIENTATION_0);
+
+    mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_POSITION_X, RAW_X_MIN, RAW_X_MAX, /*flat*/ 0,
+                                   /*fuzz*/ 0, /*resolution*/ 10);
+    mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_POSITION_Y, RAW_Y_MIN, RAW_Y_MAX, /*flat*/ 0,
+                                   /*fuzz*/ 0, /*resolution*/ 11);
+
+    // We do not add ABS_MT_TOUCH_MAJOR / MINOR or ABS_MT_WIDTH_MAJOR / MINOR axes
+
+    MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
+
+    // Touch major and minor
+    assertAxisNotPresent(mapper, AMOTION_EVENT_AXIS_TOUCH_MAJOR);
+    assertAxisNotPresent(mapper, AMOTION_EVENT_AXIS_TOUCH_MINOR);
+    // Tool major and minor
+    assertAxisNotPresent(mapper, AMOTION_EVENT_AXIS_TOOL_MAJOR);
+    assertAxisNotPresent(mapper, AMOTION_EVENT_AXIS_TOOL_MINOR);
+}
+
 TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithTrackingIds) {
     addConfigurationProperty("touch.deviceType", "touchScreen");
     prepareDisplay(DISPLAY_ORIENTATION_0);
@@ -6690,8 +7165,7 @@
             toDisplayX(x1), toDisplayY(y1), 1, 0, 0, 0, 0, 0, 0, 0));
 
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_POINTER_DOWN | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-            motionArgs.action);
+    ASSERT_EQ(ACTION_POINTER_1_DOWN, motionArgs.action);
     ASSERT_EQ(size_t(2), motionArgs.pointerCount);
     ASSERT_EQ(0, motionArgs.pointerProperties[0].id);
     ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
@@ -6732,8 +7206,7 @@
     processSync(mapper);
 
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_POINTER_UP | (0 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-            motionArgs.action);
+    ASSERT_EQ(ACTION_POINTER_0_UP, motionArgs.action);
     ASSERT_EQ(size_t(2), motionArgs.pointerCount);
     ASSERT_EQ(0, motionArgs.pointerProperties[0].id);
     ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
@@ -6778,8 +7251,7 @@
     processSync(mapper);
 
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_POINTER_DOWN | (0 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-            motionArgs.action);
+    ASSERT_EQ(ACTION_POINTER_0_DOWN, motionArgs.action);
     ASSERT_EQ(size_t(2), motionArgs.pointerCount);
     ASSERT_EQ(0, motionArgs.pointerProperties[0].id);
     ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
@@ -6798,8 +7270,7 @@
     processSync(mapper);
 
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_POINTER_UP | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-            motionArgs.action);
+    ASSERT_EQ(ACTION_POINTER_1_UP, motionArgs.action);
     ASSERT_EQ(size_t(2), motionArgs.pointerCount);
     ASSERT_EQ(0, motionArgs.pointerProperties[0].id);
     ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
@@ -6864,8 +7335,7 @@
             toDisplayX(x1), toDisplayY(y1), 1, 0, 0, 0, 0, 0, 0, 0));
 
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_POINTER_DOWN | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-            motionArgs.action);
+    ASSERT_EQ(ACTION_POINTER_1_DOWN, motionArgs.action);
     ASSERT_EQ(size_t(2), motionArgs.pointerCount);
     ASSERT_EQ(0, motionArgs.pointerProperties[0].id);
     ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
@@ -6905,8 +7375,7 @@
     processSync(mapper);
 
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_POINTER_UP | (0 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-            motionArgs.action);
+    ASSERT_EQ(ACTION_POINTER_0_UP, motionArgs.action);
     ASSERT_EQ(size_t(2), motionArgs.pointerCount);
     ASSERT_EQ(0, motionArgs.pointerProperties[0].id);
     ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
@@ -6947,8 +7416,7 @@
     processSync(mapper);
 
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_POINTER_DOWN | (0 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-            motionArgs.action);
+    ASSERT_EQ(ACTION_POINTER_0_DOWN, motionArgs.action);
     ASSERT_EQ(size_t(2), motionArgs.pointerCount);
     ASSERT_EQ(0, motionArgs.pointerProperties[0].id);
     ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
@@ -6968,8 +7436,7 @@
     processSync(mapper);
 
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_POINTER_UP | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-            motionArgs.action);
+    ASSERT_EQ(ACTION_POINTER_1_UP, motionArgs.action);
     ASSERT_EQ(size_t(2), motionArgs.pointerCount);
     ASSERT_EQ(0, motionArgs.pointerProperties[0].id);
     ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
@@ -7134,8 +7601,7 @@
     ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, args.action);
 
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_POINTER_DOWN | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-            args.action);
+    ASSERT_EQ(ACTION_POINTER_1_DOWN, args.action);
     ASSERT_EQ(size_t(2), args.pointerCount);
     ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
             x, y, 1.0f, size, touch, touch, tool, tool, 0, 0));
@@ -7786,6 +8252,25 @@
     ASSERT_EQ(DISPLAY_ID, args.displayId);
 }
 
+TEST_F(MultiTouchInputMapperTest, Configure_AssignsDisplayUniqueId) {
+    addConfigurationProperty("touch.deviceType", "touchScreen");
+    prepareAxes(POSITION);
+    MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
+
+    mFakePolicy->addInputUniqueIdAssociation(DEVICE_LOCATION, VIRTUAL_DISPLAY_UNIQUE_ID);
+
+    prepareDisplay(DISPLAY_ORIENTATION_0);
+    prepareVirtualDisplay(DISPLAY_ORIENTATION_0);
+
+    // Send a touch event
+    processPosition(mapper, 100, 100);
+    processSync(mapper);
+
+    NotifyMotionArgs args;
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
+    ASSERT_EQ(VIRTUAL_DISPLAY_ID, args.displayId);
+}
+
 TEST_F(MultiTouchInputMapperTest, Process_Pointer_ShouldHandleDisplayId) {
     // Setup for second display.
     std::shared_ptr<FakePointerController> fakePointerController =
@@ -7793,7 +8278,7 @@
     fakePointerController->setBounds(0, 0, DISPLAY_WIDTH - 1, DISPLAY_HEIGHT - 1);
     fakePointerController->setPosition(100, 200);
     fakePointerController->setButtonState(0);
-    mFakePolicy->setPointerController(mDevice->getId(), fakePointerController);
+    mFakePolicy->setPointerController(fakePointerController);
 
     mFakePolicy->setDefaultPointerDisplayId(SECONDARY_DISPLAY_ID);
     prepareSecondaryDisplay(ViewportType::EXTERNAL);
@@ -7946,8 +8431,7 @@
     // Setup PointerController.
     std::shared_ptr<FakePointerController> fakePointerController =
             std::make_shared<FakePointerController>();
-    mFakePolicy->setPointerController(mDevice->getId(), fakePointerController);
-    mFakePolicy->setPointerController(SECOND_DEVICE_ID, fakePointerController);
+    mFakePolicy->setPointerController(fakePointerController);
 
     // Setup policy for associated displays and show touches.
     const uint8_t hdmi1 = 0;
@@ -8266,8 +8750,7 @@
     processPosition(mapper, x2, y2);
     processSync(mapper);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_POINTER_DOWN | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-              motionArgs.action);
+    ASSERT_EQ(ACTION_POINTER_1_DOWN, motionArgs.action);
     ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[1].toolType);
 
     // If the tool type of the first finger changes to MT_TOOL_PALM,
@@ -8277,8 +8760,7 @@
     processToolType(mapper, MT_TOOL_PALM);
     processSync(mapper);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_POINTER_UP | (0 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-              motionArgs.action);
+    ASSERT_EQ(ACTION_POINTER_0_UP, motionArgs.action);
     ASSERT_EQ(AMOTION_EVENT_FLAG_CANCELED, motionArgs.flags);
 
     // The following MOVE events of second finger should be processed.
@@ -8343,8 +8825,7 @@
     processPosition(mapper, x2, y2);
     processSync(mapper);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_POINTER_DOWN | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-              motionArgs.action);
+    ASSERT_EQ(ACTION_POINTER_1_DOWN, motionArgs.action);
     ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
 
     // If the tool type of the first finger changes to MT_TOOL_PALM,
@@ -8354,8 +8835,7 @@
     processToolType(mapper, MT_TOOL_PALM);
     processSync(mapper);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_POINTER_UP | (0 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-              motionArgs.action);
+    ASSERT_EQ(ACTION_POINTER_0_UP, motionArgs.action);
     ASSERT_EQ(AMOTION_EVENT_FLAG_CANCELED, motionArgs.flags);
 
     // Second finger keeps moving.
@@ -8443,8 +8923,7 @@
     processPosition(mapper, x2, y2);
     processSync(mapper);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_POINTER_DOWN | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-              motionArgs.action);
+    ASSERT_EQ(ACTION_POINTER_1_DOWN, motionArgs.action);
     ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
 
     // If the tool type of the second finger changes to MT_TOOL_PALM,
@@ -8453,8 +8932,7 @@
     processToolType(mapper, MT_TOOL_PALM);
     processSync(mapper);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_POINTER_UP | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-              motionArgs.action);
+    ASSERT_EQ(ACTION_POINTER_1_UP, motionArgs.action);
     ASSERT_EQ(AMOTION_EVENT_FLAG_CANCELED, motionArgs.flags);
 
     // The following MOVE event should be processed.
@@ -8528,8 +9006,7 @@
     processPressure(mapper, RAW_PRESSURE_MAX);
     processSync(mapper);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_POINTER_DOWN | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-              motionArgs.action);
+    ASSERT_EQ(ACTION_POINTER_1_DOWN, motionArgs.action);
     ASSERT_EQ(uint32_t(2), motionArgs.pointerCount);
 
     // second finger up with some unexpected data.
@@ -8538,8 +9015,7 @@
     processPosition(mapper, x2, y2);
     processSync(mapper);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_POINTER_UP | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-              motionArgs.action);
+    ASSERT_EQ(ACTION_POINTER_1_UP, motionArgs.action);
     ASSERT_EQ(uint32_t(2), motionArgs.pointerCount);
 
     // first finger up with some unexpected data.
@@ -8588,173 +9064,6 @@
     ASSERT_EQ(SECONDARY_DISPLAY_ID, motionArgs.displayId);
 }
 
-/**
- * Test touch should not work if outside of surface.
- */
-class MultiTouchInputMapperTest_SurfaceRange : public MultiTouchInputMapperTest {
-protected:
-    void halfDisplayToCenterHorizontal(int32_t orientation) {
-        std::optional<DisplayViewport> internalViewport =
-                mFakePolicy->getDisplayViewportByType(ViewportType::INTERNAL);
-
-        // Half display to (width/4, 0, width * 3/4, height) to make display has offset.
-        internalViewport->orientation = orientation;
-        if (orientation == DISPLAY_ORIENTATION_90 || orientation == DISPLAY_ORIENTATION_270) {
-            internalViewport->logicalLeft = 0;
-            internalViewport->logicalTop = 0;
-            internalViewport->logicalRight = DISPLAY_HEIGHT;
-            internalViewport->logicalBottom = DISPLAY_WIDTH / 2;
-
-            internalViewport->physicalLeft = 0;
-            internalViewport->physicalTop = DISPLAY_WIDTH / 4;
-            internalViewport->physicalRight = DISPLAY_HEIGHT;
-            internalViewport->physicalBottom = DISPLAY_WIDTH * 3 / 4;
-
-            internalViewport->deviceWidth = DISPLAY_HEIGHT;
-            internalViewport->deviceHeight = DISPLAY_WIDTH;
-        } else {
-            internalViewport->logicalLeft = 0;
-            internalViewport->logicalTop = 0;
-            internalViewport->logicalRight = DISPLAY_WIDTH / 2;
-            internalViewport->logicalBottom = DISPLAY_HEIGHT;
-
-            internalViewport->physicalLeft = DISPLAY_WIDTH / 4;
-            internalViewport->physicalTop = 0;
-            internalViewport->physicalRight = DISPLAY_WIDTH * 3 / 4;
-            internalViewport->physicalBottom = DISPLAY_HEIGHT;
-
-            internalViewport->deviceWidth = DISPLAY_WIDTH;
-            internalViewport->deviceHeight = DISPLAY_HEIGHT;
-        }
-
-        mFakePolicy->updateViewport(internalViewport.value());
-        configureDevice(InputReaderConfiguration::CHANGE_DISPLAY_INFO);
-    }
-
-    void processPositionAndVerify(MultiTouchInputMapper& mapper, int32_t xOutside, int32_t yOutside,
-                                  int32_t xInside, int32_t yInside, int32_t xExpected,
-                                  int32_t yExpected) {
-        // touch on outside area should not work.
-        processPosition(mapper, toRawX(xOutside), toRawY(yOutside));
-        processSync(mapper);
-        ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled());
-
-        // touch on inside area should receive the event.
-        NotifyMotionArgs args;
-        processPosition(mapper, toRawX(xInside), toRawY(yInside));
-        processSync(mapper);
-        ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
-        ASSERT_NEAR(xExpected, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_X), 1);
-        ASSERT_NEAR(yExpected, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_Y), 1);
-
-        // Reset.
-        mapper.reset(ARBITRARY_TIME);
-    }
-};
-
-TEST_F(MultiTouchInputMapperTest_SurfaceRange, Viewports_SurfaceRange) {
-    addConfigurationProperty("touch.deviceType", "touchScreen");
-    prepareDisplay(DISPLAY_ORIENTATION_0);
-    prepareAxes(POSITION);
-    MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
-
-    // Touch on center of normal display should work.
-    const int32_t x = DISPLAY_WIDTH / 4;
-    const int32_t y = DISPLAY_HEIGHT / 2;
-    processPosition(mapper, toRawX(x), toRawY(y));
-    processSync(mapper);
-    NotifyMotionArgs args;
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0], x, y, 1.0f, 0.0f, 0.0f, 0.0f,
-                                                0.0f, 0.0f, 0.0f, 0.0f));
-    // Reset.
-    mapper.reset(ARBITRARY_TIME);
-
-    // Let physical display be different to device, and make surface and physical could be 1:1 in
-    // all four orientations.
-    for (int orientation : {DISPLAY_ORIENTATION_0, DISPLAY_ORIENTATION_90, DISPLAY_ORIENTATION_180,
-                            DISPLAY_ORIENTATION_270}) {
-        halfDisplayToCenterHorizontal(orientation);
-
-        const int32_t xExpected = (x + 1) - (DISPLAY_WIDTH / 4);
-        const int32_t yExpected = y;
-        processPositionAndVerify(mapper, x - 1, y, x + 1, y, xExpected, yExpected);
-    }
-}
-
-TEST_F(MultiTouchInputMapperTest_SurfaceRange, Viewports_SurfaceRange_90_NotOrientationAware) {
-    addConfigurationProperty("touch.deviceType", "touchScreen");
-    prepareDisplay(DISPLAY_ORIENTATION_0);
-    prepareAxes(POSITION);
-    // Since InputReader works in the un-rotated coordinate space, only devices that are not
-    // orientation-aware are affected by display rotation.
-    addConfigurationProperty("touch.orientationAware", "0");
-    MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
-
-    // Half display to (width/4, 0, width * 3/4, height) and rotate 90-degrees.
-    halfDisplayToCenterHorizontal(DISPLAY_ORIENTATION_90);
-
-    const int32_t x = DISPLAY_WIDTH / 4;
-    const int32_t y = DISPLAY_HEIGHT / 2;
-
-    // expect x/y = swap x/y then reverse x.
-    constexpr int32_t xExpected = DISPLAY_HEIGHT - y;
-    constexpr int32_t yExpected = (x + 1) - DISPLAY_WIDTH / 4;
-    processPositionAndVerify(mapper, x - 1, y, x + 1, y, xExpected, yExpected);
-}
-
-TEST_F(MultiTouchInputMapperTest_SurfaceRange, Viewports_SurfaceRange_270_NotOrientationAware) {
-    addConfigurationProperty("touch.deviceType", "touchScreen");
-    prepareDisplay(DISPLAY_ORIENTATION_0);
-    prepareAxes(POSITION);
-    // Since InputReader works in the un-rotated coordinate space, only devices that are not
-    // orientation-aware are affected by display rotation.
-    addConfigurationProperty("touch.orientationAware", "0");
-    MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
-
-    // Half display to (width/4, 0, width * 3/4, height) and rotate 270-degrees.
-    halfDisplayToCenterHorizontal(DISPLAY_ORIENTATION_270);
-
-    const int32_t x = DISPLAY_WIDTH / 4;
-    const int32_t y = DISPLAY_HEIGHT / 2;
-
-    // expect x/y = swap x/y then reverse y.
-    const int32_t xExpected = y;
-    const int32_t yExpected = (DISPLAY_WIDTH * 3 / 4) - (x + 1);
-    processPositionAndVerify(mapper, x - 1, y, x + 1, y, xExpected, yExpected);
-}
-
-TEST_F(MultiTouchInputMapperTest_SurfaceRange, Viewports_SurfaceRange_Corner_NotOrientationAware) {
-    addConfigurationProperty("touch.deviceType", "touchScreen");
-    prepareDisplay(DISPLAY_ORIENTATION_0);
-    prepareAxes(POSITION);
-    // Since InputReader works in the un-rotated coordinate space, only devices that are not
-    // orientation-aware are affected by display rotation.
-    addConfigurationProperty("touch.orientationAware", "0");
-    MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
-
-    const int32_t x = 0;
-    const int32_t y = 0;
-
-    const int32_t xExpected = x;
-    const int32_t yExpected = y;
-    processPositionAndVerify(mapper, x - 1, y, x, y, xExpected, yExpected);
-
-    clearViewports();
-    prepareDisplay(DISPLAY_ORIENTATION_90);
-    // expect x/y = swap x/y then reverse x.
-    const int32_t xExpected90 = DISPLAY_HEIGHT - 1;
-    const int32_t yExpected90 = x;
-    processPositionAndVerify(mapper, x - 1, y, x, y, xExpected90, yExpected90);
-
-    clearViewports();
-    prepareDisplay(DISPLAY_ORIENTATION_270);
-    // expect x/y = swap x/y then reverse y.
-    const int32_t xExpected270 = y;
-    const int32_t yExpected270 = DISPLAY_WIDTH - 1;
-    processPositionAndVerify(mapper, x - 1, y, x, y, xExpected270, yExpected270);
-}
-
 TEST_F(MultiTouchInputMapperTest, Process_TouchpadCapture) {
     // we need a pointer controller for mouse mode of touchpad (start pointer at 0,0)
     std::shared_ptr<FakePointerController> fakePointerController =
@@ -8769,7 +9078,7 @@
     mFakeEventHub->addKey(EVENTHUB_ID, BTN_LEFT, 0, AKEYCODE_UNKNOWN, 0);
     mFakeEventHub->addKey(EVENTHUB_ID, BTN_TOUCH, 0, AKEYCODE_UNKNOWN, 0);
     mFakePolicy->setPointerCapture(true);
-    mFakePolicy->setPointerController(mDevice->getId(), fakePointerController);
+    mFakePolicy->setPointerController(fakePointerController);
     MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
 
     // captured touchpad should be a touchpad source
@@ -8818,8 +9127,7 @@
 
     // expect coord[0] to contain previous location, coord[1] to contain new touch 1 location
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_POINTER_DOWN | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-            args.action);
+    ASSERT_EQ(ACTION_POINTER_1_DOWN, args.action);
     ASSERT_EQ(2U, args.pointerCount);
     ASSERT_EQ(0, args.pointerProperties[0].id);
     ASSERT_EQ(1, args.pointerProperties[1].id);
@@ -8919,7 +9227,7 @@
     prepareAxes(POSITION | ID | SLOT);
     mFakeEventHub->addKey(EVENTHUB_ID, BTN_LEFT, 0, AKEYCODE_UNKNOWN, 0);
     mFakeEventHub->addKey(EVENTHUB_ID, BTN_TOUCH, 0, AKEYCODE_UNKNOWN, 0);
-    mFakePolicy->setPointerController(mDevice->getId(), fakePointerController);
+    mFakePolicy->setPointerController(fakePointerController);
     MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
     // run uncaptured pointer tests - pushes out generic events
     // FINGER 0 DOWN
@@ -8959,7 +9267,7 @@
     prepareDisplay(DISPLAY_ORIENTATION_0);
     prepareAxes(POSITION | ID | SLOT);
     mFakeEventHub->addKey(EVENTHUB_ID, BTN_LEFT, 0, AKEYCODE_UNKNOWN, 0);
-    mFakePolicy->setPointerController(mDevice->getId(), fakePointerController);
+    mFakePolicy->setPointerController(fakePointerController);
     mFakePolicy->setPointerCapture(false);
     MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
 
@@ -8972,6 +9280,67 @@
     ASSERT_EQ(AINPUT_SOURCE_TOUCHPAD, mapper.getSources());
 }
 
+// --- JoystickInputMapperTest ---
+
+class JoystickInputMapperTest : public InputMapperTest {
+protected:
+    static const int32_t RAW_X_MIN;
+    static const int32_t RAW_X_MAX;
+    static const int32_t RAW_Y_MIN;
+    static const int32_t RAW_Y_MAX;
+
+    void SetUp() override {
+        InputMapperTest::SetUp(InputDeviceClass::JOYSTICK | InputDeviceClass::EXTERNAL);
+    }
+    void prepareAxes() {
+        mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_X, RAW_X_MIN, RAW_X_MAX, 0, 0);
+        mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_Y, RAW_Y_MIN, RAW_Y_MAX, 0, 0);
+    }
+
+    void processAxis(JoystickInputMapper& mapper, int32_t axis, int32_t value) {
+        process(mapper, ARBITRARY_TIME, READ_TIME, EV_ABS, axis, value);
+    }
+
+    void processSync(JoystickInputMapper& mapper) {
+        process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
+    }
+
+    void prepareVirtualDisplay(int32_t orientation) {
+        setDisplayInfoAndReconfigure(VIRTUAL_DISPLAY_ID, VIRTUAL_DISPLAY_WIDTH,
+                                     VIRTUAL_DISPLAY_HEIGHT, orientation, VIRTUAL_DISPLAY_UNIQUE_ID,
+                                     NO_PORT, ViewportType::VIRTUAL);
+    }
+};
+
+const int32_t JoystickInputMapperTest::RAW_X_MIN = -32767;
+const int32_t JoystickInputMapperTest::RAW_X_MAX = 32767;
+const int32_t JoystickInputMapperTest::RAW_Y_MIN = -32767;
+const int32_t JoystickInputMapperTest::RAW_Y_MAX = 32767;
+
+TEST_F(JoystickInputMapperTest, Configure_AssignsDisplayUniqueId) {
+    prepareAxes();
+    JoystickInputMapper& mapper = addMapperAndConfigure<JoystickInputMapper>();
+
+    mFakePolicy->addInputUniqueIdAssociation(DEVICE_LOCATION, VIRTUAL_DISPLAY_UNIQUE_ID);
+
+    prepareVirtualDisplay(DISPLAY_ORIENTATION_0);
+
+    // Send an axis event
+    processAxis(mapper, ABS_X, 100);
+    processSync(mapper);
+
+    NotifyMotionArgs args;
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
+    ASSERT_EQ(VIRTUAL_DISPLAY_ID, args.displayId);
+
+    // Send another axis event
+    processAxis(mapper, ABS_Y, 100);
+    processSync(mapper);
+
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
+    ASSERT_EQ(VIRTUAL_DISPLAY_ID, args.displayId);
+}
+
 // --- PeripheralControllerTest ---
 
 class PeripheralControllerTest : public testing::Test {
@@ -8986,23 +9355,23 @@
 
     std::shared_ptr<FakeEventHub> mFakeEventHub;
     sp<FakeInputReaderPolicy> mFakePolicy;
-    sp<TestInputListener> mFakeListener;
+    std::unique_ptr<TestInputListener> mFakeListener;
     std::unique_ptr<InstrumentedInputReader> mReader;
     std::shared_ptr<InputDevice> mDevice;
 
     virtual void SetUp(Flags<InputDeviceClass> classes) {
         mFakeEventHub = std::make_unique<FakeEventHub>();
         mFakePolicy = new FakeInputReaderPolicy();
-        mFakeListener = new TestInputListener();
+        mFakeListener = std::make_unique<TestInputListener>();
         mReader = std::make_unique<InstrumentedInputReader>(mFakeEventHub, mFakePolicy,
-                                                            mFakeListener);
+                                                            *mFakeListener);
         mDevice = newDevice(DEVICE_ID, DEVICE_NAME, DEVICE_LOCATION, EVENTHUB_ID, classes);
     }
 
     void SetUp() override { SetUp(DEVICE_CLASSES); }
 
     void TearDown() override {
-        mFakeListener.clear();
+        mFakeListener.reset();
         mFakePolicy.clear();
     }
 
diff --git a/services/inputflinger/tests/TestInputListener.h b/services/inputflinger/tests/TestInputListener.h
index 0a1dc4b..626cdfc 100644
--- a/services/inputflinger/tests/TestInputListener.h
+++ b/services/inputflinger/tests/TestInputListener.h
@@ -28,12 +28,10 @@
 // --- TestInputListener ---
 
 class TestInputListener : public InputListenerInterface {
-protected:
-    virtual ~TestInputListener();
-
 public:
     TestInputListener(std::chrono::milliseconds eventHappenedTimeout = 0ms,
                       std::chrono::milliseconds eventDidNotHappenTimeout = 0ms);
+    virtual ~TestInputListener();
 
     void assertNotifyConfigurationChangedWasCalled(
             NotifyConfigurationChangedArgs* outEventArgs = nullptr);
diff --git a/services/inputflinger/tests/UinputDevice.cpp b/services/inputflinger/tests/UinputDevice.cpp
index 7fec2c8..9c93919 100644
--- a/services/inputflinger/tests/UinputDevice.cpp
+++ b/services/inputflinger/tests/UinputDevice.cpp
@@ -17,6 +17,7 @@
 #include "UinputDevice.h"
 
 #include <android-base/stringprintf.h>
+#include <fcntl.h>
 
 namespace android {
 
@@ -32,7 +33,7 @@
 }
 
 void UinputDevice::init() {
-    mDeviceFd = android::base::unique_fd(open("/dev/uinput", O_WRONLY | O_NONBLOCK));
+    mDeviceFd = android::base::unique_fd(open("/dev/uinput", O_WRONLY | O_NONBLOCK | O_CLOEXEC));
     if (mDeviceFd < 0) {
         FAIL() << "Can't open /dev/uinput :" << strerror(errno);
     }
@@ -170,28 +171,27 @@
     injectEvent(EV_KEY, BTN_TOUCH, 1);
     injectEvent(EV_ABS, ABS_MT_POSITION_X, point.x);
     injectEvent(EV_ABS, ABS_MT_POSITION_Y, point.y);
-    injectEvent(EV_SYN, SYN_REPORT, 0);
 }
 
 void UinputTouchScreen::sendMove(const Point& point) {
     injectEvent(EV_ABS, ABS_MT_POSITION_X, point.x);
     injectEvent(EV_ABS, ABS_MT_POSITION_Y, point.y);
-    injectEvent(EV_SYN, SYN_REPORT, 0);
 }
 
 void UinputTouchScreen::sendPointerUp() {
     sendTrackingId(0xffffffff);
-    injectEvent(EV_SYN, SYN_REPORT, 0);
 }
 
 void UinputTouchScreen::sendUp() {
     sendTrackingId(0xffffffff);
     injectEvent(EV_KEY, BTN_TOUCH, 0);
-    injectEvent(EV_SYN, SYN_REPORT, 0);
 }
 
 void UinputTouchScreen::sendToolType(int32_t toolType) {
     injectEvent(EV_ABS, ABS_MT_TOOL_TYPE, toolType);
+}
+
+void UinputTouchScreen::sendSync() {
     injectEvent(EV_SYN, SYN_REPORT, 0);
 }
 
diff --git a/services/inputflinger/tests/UinputDevice.h b/services/inputflinger/tests/UinputDevice.h
index 01a557c..a37fc2b 100644
--- a/services/inputflinger/tests/UinputDevice.h
+++ b/services/inputflinger/tests/UinputDevice.h
@@ -142,6 +142,7 @@
     void sendPointerUp();
     void sendUp();
     void sendToolType(int32_t toolType);
+    void sendSync();
 
     const Point getCenterPoint();
 
diff --git a/services/inputflinger/tests/UnwantedInteractionBlocker_test.cpp b/services/inputflinger/tests/UnwantedInteractionBlocker_test.cpp
new file mode 100644
index 0000000..a3220cc
--- /dev/null
+++ b/services/inputflinger/tests/UnwantedInteractionBlocker_test.cpp
@@ -0,0 +1,938 @@
+/*
+ * 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 "../UnwantedInteractionBlocker.h"
+#include <android-base/silent_death_test.h>
+#include <gtest/gtest.h>
+#include <gui/constants.h>
+#include <linux/input.h>
+
+#include "TestInputListener.h"
+
+namespace android {
+
+constexpr int32_t DEVICE_ID = 3;
+constexpr int32_t X_RESOLUTION = 11;
+constexpr int32_t Y_RESOLUTION = 11;
+constexpr int32_t MAJOR_RESOLUTION = 1;
+
+constexpr int POINTER_0_DOWN =
+        AMOTION_EVENT_ACTION_POINTER_DOWN | (0 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
+constexpr int POINTER_1_DOWN =
+        AMOTION_EVENT_ACTION_POINTER_DOWN | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
+constexpr int POINTER_2_DOWN =
+        AMOTION_EVENT_ACTION_POINTER_DOWN | (2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
+constexpr int POINTER_0_UP =
+        AMOTION_EVENT_ACTION_POINTER_UP | (0 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
+constexpr int POINTER_1_UP =
+        AMOTION_EVENT_ACTION_POINTER_UP | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
+constexpr int POINTER_2_UP =
+        AMOTION_EVENT_ACTION_POINTER_UP | (2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
+constexpr int DOWN = AMOTION_EVENT_ACTION_DOWN;
+constexpr int MOVE = AMOTION_EVENT_ACTION_MOVE;
+constexpr int UP = AMOTION_EVENT_ACTION_UP;
+constexpr int CANCEL = AMOTION_EVENT_ACTION_CANCEL;
+
+struct PointerData {
+    float x;
+    float y;
+    float major;
+};
+
+static NotifyMotionArgs generateMotionArgs(nsecs_t downTime, nsecs_t eventTime, int32_t action,
+                                           const std::vector<PointerData>& points) {
+    size_t pointerCount = points.size();
+    if (action == AMOTION_EVENT_ACTION_DOWN || action == AMOTION_EVENT_ACTION_UP) {
+        EXPECT_EQ(1U, pointerCount) << "Actions DOWN and UP can only contain a single pointer";
+    }
+
+    PointerProperties pointerProperties[pointerCount];
+    PointerCoords pointerCoords[pointerCount];
+
+    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, points[i].x);
+        pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_Y, points[i].y);
+        pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR, points[i].major);
+    }
+
+    // Define a valid motion event.
+    NotifyMotionArgs args(/* id */ 0, eventTime, 0 /*readTime*/, DEVICE_ID,
+                          AINPUT_SOURCE_TOUCHSCREEN, 0 /*displayId*/, POLICY_FLAG_PASS_TO_USER,
+                          action, /* actionButton */ 0,
+                          /* flags */ 0, AMETA_NONE, /* buttonState */ 0,
+                          MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, pointerCount,
+                          pointerProperties, pointerCoords, /* xPrecision */ 0, /* yPrecision */ 0,
+                          AMOTION_EVENT_INVALID_CURSOR_POSITION,
+                          AMOTION_EVENT_INVALID_CURSOR_POSITION, downTime, /* videoFrames */ {});
+
+    return args;
+}
+
+static InputDeviceInfo generateTestDeviceInfo() {
+    InputDeviceIdentifier identifier;
+
+    auto info = InputDeviceInfo();
+    info.initialize(DEVICE_ID, /*generation*/ 1, /*controllerNumber*/ 1, identifier, "alias",
+                    /*isExternal*/ false, /*hasMic*/ false);
+    info.addSource(AINPUT_SOURCE_TOUCHSCREEN);
+    info.addMotionRange(AMOTION_EVENT_AXIS_X, AINPUT_SOURCE_TOUCHSCREEN, 0, 1599, /*flat*/ 0,
+                        /*fuzz*/ 0, X_RESOLUTION);
+    info.addMotionRange(AMOTION_EVENT_AXIS_Y, AINPUT_SOURCE_TOUCHSCREEN, 0, 2559, /*flat*/ 0,
+                        /*fuzz*/ 0, Y_RESOLUTION);
+    info.addMotionRange(AMOTION_EVENT_AXIS_TOUCH_MAJOR, AINPUT_SOURCE_TOUCHSCREEN, 0, 255,
+                        /*flat*/ 0, /*fuzz*/ 0, MAJOR_RESOLUTION);
+
+    return info;
+}
+
+static AndroidPalmFilterDeviceInfo generatePalmFilterDeviceInfo() {
+    InputDeviceInfo androidInfo = generateTestDeviceInfo();
+    std::optional<AndroidPalmFilterDeviceInfo> info = createPalmFilterDeviceInfo(androidInfo);
+    if (!info) {
+        ADD_FAILURE() << "Could not convert android device info to ::ui version";
+        return {};
+    }
+    return *info;
+}
+
+TEST(DeviceInfoConversionTest, TabletDeviceTest) {
+    AndroidPalmFilterDeviceInfo info = generatePalmFilterDeviceInfo();
+    ASSERT_EQ(X_RESOLUTION, info.x_res);
+    ASSERT_EQ(Y_RESOLUTION, info.y_res);
+    ASSERT_EQ(MAJOR_RESOLUTION, info.touch_major_res);
+    ASSERT_EQ(1599, info.max_x);
+    ASSERT_EQ(2559, info.max_y);
+}
+
+static void assertArgs(const NotifyMotionArgs& args, int32_t action,
+                       const std::vector<std::pair<int32_t /*pointerId*/, PointerData>>& pointers) {
+    ASSERT_EQ(action, args.action);
+    ASSERT_EQ(pointers.size(), args.pointerCount);
+    for (size_t i = 0; i < args.pointerCount; i++) {
+        const auto& [pointerId, pointerData] = pointers[i];
+        ASSERT_EQ(pointerId, args.pointerProperties[i].id);
+        ASSERT_EQ(pointerData.x, args.pointerCoords[i].getX());
+        ASSERT_EQ(pointerData.y, args.pointerCoords[i].getY());
+        ASSERT_EQ(pointerData.major,
+                  args.pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR));
+    }
+}
+
+TEST(RemovePointerIdsTest, RemoveOnePointer) {
+    NotifyMotionArgs args = generateMotionArgs(/*downTime*/ 0, /*eventTime*/ 0,
+                                               AMOTION_EVENT_ACTION_MOVE, {{1, 2, 3}, {4, 5, 6}});
+
+    NotifyMotionArgs pointer1Only = removePointerIds(args, {0});
+    assertArgs(pointer1Only, AMOTION_EVENT_ACTION_MOVE, {{1, {4, 5, 6}}});
+
+    NotifyMotionArgs pointer0Only = removePointerIds(args, {1});
+    assertArgs(pointer0Only, AMOTION_EVENT_ACTION_MOVE, {{0, {1, 2, 3}}});
+}
+
+/**
+ * Remove 2 out of 3 pointers during a MOVE event.
+ */
+TEST(RemovePointerIdsTest, RemoveTwoPointers) {
+    NotifyMotionArgs args =
+            generateMotionArgs(/*downTime*/ 0, /*eventTime*/ 0, AMOTION_EVENT_ACTION_MOVE,
+                               {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}});
+
+    NotifyMotionArgs pointer1Only = removePointerIds(args, {0, 2});
+    assertArgs(pointer1Only, AMOTION_EVENT_ACTION_MOVE, {{1, {4, 5, 6}}});
+}
+
+/**
+ * Remove an active pointer during a POINTER_DOWN event, and also remove a non-active
+ * pointer during a POINTER_DOWN event.
+ */
+TEST(RemovePointerIdsTest, ActionPointerDown) {
+    NotifyMotionArgs args = generateMotionArgs(/*downTime*/ 0, /*eventTime*/ 0, POINTER_1_DOWN,
+                                               {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}});
+
+    NotifyMotionArgs pointers0And2 = removePointerIds(args, {1});
+    assertArgs(pointers0And2, ACTION_UNKNOWN, {{0, {1, 2, 3}}, {2, {7, 8, 9}}});
+
+    NotifyMotionArgs pointers1And2 = removePointerIds(args, {0});
+    assertArgs(pointers1And2, POINTER_0_DOWN, {{1, {4, 5, 6}}, {2, {7, 8, 9}}});
+}
+
+/**
+ * Remove all pointers during a MOVE event.
+ */
+TEST(RemovePointerIdsTest, RemoveAllPointersDuringMove) {
+    NotifyMotionArgs args = generateMotionArgs(/*downTime*/ 0, /*eventTime*/ 0,
+                                               AMOTION_EVENT_ACTION_MOVE, {{1, 2, 3}, {4, 5, 6}});
+
+    NotifyMotionArgs noPointers = removePointerIds(args, {0, 1});
+    ASSERT_EQ(0u, noPointers.pointerCount);
+}
+
+/**
+ * If we have ACTION_POINTER_DOWN, and we remove all pointers except for the active pointer,
+ * then we should just have ACTION_DOWN. Likewise, a POINTER_UP event should become an UP event.
+ */
+TEST(RemovePointerIdsTest, PointerDownBecomesDown) {
+    NotifyMotionArgs args = generateMotionArgs(/*downTime*/ 0, /*eventTime*/ 0, POINTER_1_DOWN,
+                                               {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}});
+
+    NotifyMotionArgs pointer1 = removePointerIds(args, {0, 2});
+    assertArgs(pointer1, DOWN, {{1, {4, 5, 6}}});
+
+    args.action = POINTER_1_UP;
+    pointer1 = removePointerIds(args, {0, 2});
+    assertArgs(pointer1, UP, {{1, {4, 5, 6}}});
+}
+
+/**
+ * If a pointer that is now going down is canceled, then we can just drop the POINTER_DOWN event.
+ */
+TEST(CancelSuppressedPointersTest, CanceledPointerDownIsDropped) {
+    NotifyMotionArgs args = generateMotionArgs(/*downTime*/ 0, /*eventTime*/ 0, POINTER_1_DOWN,
+                                               {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}});
+    std::vector<NotifyMotionArgs> result =
+            cancelSuppressedPointers(args, /*oldSuppressedPointerIds*/ {},
+                                     /*newSuppressedPointerIds*/ {1});
+    ASSERT_TRUE(result.empty());
+}
+
+/**
+ * If a pointer is already suppressed, the POINTER_UP event for this pointer should be dropped
+ */
+TEST(CancelSuppressedPointersTest, SuppressedPointerUpIsDropped) {
+    NotifyMotionArgs args = generateMotionArgs(/*downTime*/ 0, /*eventTime*/ 0, POINTER_1_UP,
+                                               {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}});
+    std::vector<NotifyMotionArgs> result =
+            cancelSuppressedPointers(args, /*oldSuppressedPointerIds*/ {1},
+                                     /*newSuppressedPointerIds*/ {1});
+    ASSERT_TRUE(result.empty());
+}
+
+/**
+ * If a pointer is already suppressed, it should be removed from a MOVE event.
+ */
+TEST(CancelSuppressedPointersTest, SuppressedPointerIsRemovedDuringMove) {
+    NotifyMotionArgs args = generateMotionArgs(/*downTime*/ 0, /*eventTime*/ 0, MOVE,
+                                               {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}});
+    std::vector<NotifyMotionArgs> result =
+            cancelSuppressedPointers(args, /*oldSuppressedPointerIds*/ {1},
+                                     /*newSuppressedPointerIds*/ {1});
+    ASSERT_EQ(1u, result.size());
+    assertArgs(result[0], MOVE, {{0, {1, 2, 3}}, {2, {7, 8, 9}}});
+}
+
+/**
+ * If a pointer just got canceled during a MOVE event, we should see two events:
+ * 1) ACTION_POINTER_UP with FLAG_CANCELED so that this pointer is lifted
+ * 2) A MOVE event without this pointer
+ */
+TEST(CancelSuppressedPointersTest, NewlySuppressedPointerIsCanceled) {
+    NotifyMotionArgs args = generateMotionArgs(/*downTime*/ 0, /*eventTime*/ 0, MOVE,
+                                               {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}});
+    std::vector<NotifyMotionArgs> result =
+            cancelSuppressedPointers(args, /*oldSuppressedPointerIds*/ {},
+                                     /*newSuppressedPointerIds*/ {1});
+    ASSERT_EQ(2u, result.size());
+    assertArgs(result[0], POINTER_1_UP, {{0, {1, 2, 3}}, {1, {4, 5, 6}}, {2, {7, 8, 9}}});
+    ASSERT_EQ(AMOTION_EVENT_FLAG_CANCELED, result[0].flags);
+    assertArgs(result[1], MOVE, {{0, {1, 2, 3}}, {2, {7, 8, 9}}});
+}
+
+/**
+ * If we have a single pointer that gets canceled during a MOVE, the entire gesture
+ * should be canceled with ACTION_CANCEL.
+ */
+TEST(CancelSuppressedPointersTest, SingleSuppressedPointerIsCanceled) {
+    NotifyMotionArgs args = generateMotionArgs(/*downTime*/ 0, /*eventTime*/ 0, MOVE, {{1, 2, 3}});
+    std::vector<NotifyMotionArgs> result =
+            cancelSuppressedPointers(args, /*oldSuppressedPointerIds*/ {},
+                                     /*newSuppressedPointerIds*/ {0});
+    ASSERT_EQ(1u, result.size());
+    assertArgs(result[0], CANCEL, {{0, {1, 2, 3}}});
+    ASSERT_EQ(AMOTION_EVENT_FLAG_CANCELED, result[0].flags);
+}
+
+/**
+ * If one of 3 pointers gets canceled during a POINTER_UP event, we should proceed with POINTER_UP,
+ * but this event should also have FLAG_CANCELED to indicate that this pointer was unintentional.
+ */
+TEST(CancelSuppressedPointersTest, SuppressedPointer1GoingUpIsCanceled) {
+    NotifyMotionArgs args = generateMotionArgs(/*downTime*/ 0, /*eventTime*/ 0, POINTER_1_UP,
+                                               {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}});
+    std::vector<NotifyMotionArgs> result =
+            cancelSuppressedPointers(args, /*oldSuppressedPointerIds*/ {},
+                                     /*newSuppressedPointerIds*/ {1});
+    ASSERT_EQ(1u, result.size());
+    assertArgs(result[0], POINTER_1_UP, {{0, {1, 2, 3}}, {1, {4, 5, 6}}, {2, {7, 8, 9}}});
+    ASSERT_EQ(AMOTION_EVENT_FLAG_CANCELED, result[0].flags);
+}
+
+/**
+ * Same test as above, but we change the pointer's index to 0 instead of 1. This helps detect
+ * errors with handling pointer index inside the action.
+ */
+TEST(CancelSuppressedPointersTest, SuppressedPointer0GoingUpIsCanceled) {
+    NotifyMotionArgs args = generateMotionArgs(/*downTime*/ 0, /*eventTime*/ 0, POINTER_0_UP,
+                                               {{1, 2, 3}, {4, 5, 6}});
+    std::vector<NotifyMotionArgs> result =
+            cancelSuppressedPointers(args, /*oldSuppressedPointerIds*/ {},
+                                     /*newSuppressedPointerIds*/ {0});
+    ASSERT_EQ(1u, result.size());
+    assertArgs(result[0], POINTER_0_UP, {{0, {1, 2, 3}}, {1, {4, 5, 6}}});
+    ASSERT_EQ(AMOTION_EVENT_FLAG_CANCELED, result[0].flags);
+}
+
+/**
+ * If two pointers are canceled simultaneously during MOVE, we should see a single ACTION_CANCEL
+ * event. This event would cancel the entire gesture.
+ */
+TEST(CancelSuppressedPointersTest, TwoNewlySuppressedPointersAreBothCanceled) {
+    NotifyMotionArgs args =
+            generateMotionArgs(/*downTime*/ 0, /*eventTime*/ 0, MOVE, {{1, 2, 3}, {4, 5, 6}});
+    std::vector<NotifyMotionArgs> result =
+            cancelSuppressedPointers(args, /*oldSuppressedPointerIds*/ {},
+                                     /*newSuppressedPointerIds*/ {0, 1});
+    ASSERT_EQ(1u, result.size());
+    assertArgs(result[0], CANCEL, {{0, {1, 2, 3}}, {1, {4, 5, 6}}});
+    ASSERT_EQ(AMOTION_EVENT_FLAG_CANCELED, result[0].flags);
+}
+
+/**
+ * Similar test to above. During a POINTER_UP event, both pointers are detected as 'palm' and
+ * therefore should be removed. In this case, we should send a single ACTION_CANCEL that
+ * would undo the entire gesture.
+ */
+TEST(CancelSuppressedPointersTest, TwoPointersAreCanceledDuringPointerUp) {
+    NotifyMotionArgs args = generateMotionArgs(/*downTime*/ 0, /*eventTime*/ 0, POINTER_1_UP,
+                                               {{1, 2, 3}, {4, 5, 6}});
+    std::vector<NotifyMotionArgs> result =
+            cancelSuppressedPointers(args, /*oldSuppressedPointerIds*/ {1},
+                                     /*newSuppressedPointerIds*/ {0, 1});
+    ASSERT_EQ(1u, result.size());
+    assertArgs(result[0], CANCEL, {{0, {1, 2, 3}}});
+    ASSERT_EQ(AMOTION_EVENT_FLAG_CANCELED, result[0].flags);
+}
+
+/**
+ * When all pointers have been removed from the touch stream, and we have a new POINTER_DOWN,
+ * this should become a regular DOWN event because it's the only pointer that will be valid now.
+ */
+TEST(CancelSuppressedPointersTest, NewPointerDownBecomesDown) {
+    NotifyMotionArgs args = generateMotionArgs(/*downTime*/ 0, /*eventTime*/ 0, POINTER_2_DOWN,
+                                               {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}});
+    std::vector<NotifyMotionArgs> result =
+            cancelSuppressedPointers(args, /*oldSuppressedPointerIds*/ {0, 1},
+                                     /*newSuppressedPointerIds*/ {0, 1});
+    ASSERT_EQ(1u, result.size());
+    assertArgs(result[0], DOWN, {{2, {7, 8, 9}}});
+    ASSERT_EQ(0, result[0].flags);
+}
+
+/**
+ * Call 'getTouches' for a DOWN event and check that the resulting 'InProgressTouchEvdev'
+ * struct is populated as expected.
+ */
+TEST(GetTouchesTest, ConvertDownEvent) {
+    NotifyMotionArgs args = generateMotionArgs(/*downTime*/ 0, /*eventTime*/ 0, DOWN, {{1, 2, 3}});
+    AndroidPalmFilterDeviceInfo deviceInfo = generatePalmFilterDeviceInfo();
+    SlotState slotState;
+    SlotState oldSlotState = slotState;
+    slotState.update(args);
+    std::vector<::ui::InProgressTouchEvdev> touches =
+            getTouches(args, deviceInfo, oldSlotState, slotState);
+    ASSERT_EQ(1u, touches.size());
+    ::ui::InProgressTouchEvdev expected;
+
+    expected.major = 3;
+    expected.minor = 0;
+    expected.tool_type = MT_TOOL_FINGER;
+    expected.altered = true;
+    expected.was_cancelled = false;
+    expected.cancelled = false;
+    expected.delayed = false;
+    expected.was_delayed = false;
+    expected.held = false;
+    expected.was_held = false;
+    expected.was_touching = false;
+    expected.touching = true;
+    expected.x = 1;
+    expected.y = 2;
+    expected.tracking_id = 0;
+    std::optional<size_t> slot = slotState.getSlotForPointerId(0);
+    ASSERT_TRUE(slot);
+    expected.slot = *slot;
+    expected.pressure = 0;
+    expected.tool_code = BTN_TOOL_FINGER;
+    expected.reported_tool_type = ::ui::EventPointerType::kTouch;
+    expected.stylus_button = false;
+
+    ASSERT_EQ(expected, touches[0]) << toString(touches[0]);
+}
+
+// --- UnwantedInteractionBlockerTest ---
+
+class UnwantedInteractionBlockerTest : public testing::Test {
+protected:
+    TestInputListener mTestListener;
+    std::unique_ptr<UnwantedInteractionBlockerInterface> mBlocker;
+
+    void SetUp() override {
+        mBlocker = std::make_unique<UnwantedInteractionBlocker>(mTestListener,
+                                                                /*enablePalmRejection*/ true);
+    }
+};
+
+/**
+ * Create a basic configuration change and send it to input classifier.
+ * Expect that the event is received by the next input stage, unmodified.
+ */
+TEST_F(UnwantedInteractionBlockerTest, ConfigurationChangedIsPassedToNextListener) {
+    // Create a basic configuration change and send to classifier
+    NotifyConfigurationChangedArgs args(1 /*sequenceNum*/, 2 /*eventTime*/);
+
+    mBlocker->notifyConfigurationChanged(&args);
+    NotifyConfigurationChangedArgs outArgs;
+    ASSERT_NO_FATAL_FAILURE(mTestListener.assertNotifyConfigurationChangedWasCalled(&outArgs));
+    ASSERT_EQ(args, outArgs);
+}
+
+/**
+ * Keys are not handled in 'UnwantedInteractionBlocker' and should be passed
+ * to next stage unmodified.
+ */
+TEST_F(UnwantedInteractionBlockerTest, KeyIsPassedToNextListener) {
+    // Create a basic key event and send to classifier
+    NotifyKeyArgs args(1 /*sequenceNum*/, 2 /*eventTime*/, 21 /*readTime*/, 3 /*deviceId*/,
+                       AINPUT_SOURCE_KEYBOARD, ADISPLAY_ID_DEFAULT, 0 /*policyFlags*/,
+                       AKEY_EVENT_ACTION_DOWN, 4 /*flags*/, AKEYCODE_HOME, 5 /*scanCode*/,
+                       AMETA_NONE, 6 /*downTime*/);
+
+    mBlocker->notifyKey(&args);
+    NotifyKeyArgs outArgs;
+    ASSERT_NO_FATAL_FAILURE(mTestListener.assertNotifyKeyWasCalled(&outArgs));
+    ASSERT_EQ(args, outArgs);
+}
+
+/**
+ * Create a basic motion event. Since it's just a DOWN event, it should not
+ * be detected as palm and should be sent to the next listener stage
+ * unmodified.
+ */
+TEST_F(UnwantedInteractionBlockerTest, DownEventIsPassedToNextListener) {
+    NotifyMotionArgs motionArgs =
+            generateMotionArgs(0 /*downTime*/, 0 /*eventTime*/, DOWN, {{1, 2, 3}});
+    mBlocker->notifyMotion(&motionArgs);
+    NotifyMotionArgs args;
+    ASSERT_NO_FATAL_FAILURE(mTestListener.assertNotifyMotionWasCalled(&args));
+    ASSERT_EQ(motionArgs, args);
+}
+
+/**
+ * Create a basic switch event and send it to the UnwantedInteractionBlocker.
+ * Expect that the event is received by the next input stage, unmodified.
+ */
+TEST_F(UnwantedInteractionBlockerTest, SwitchIsPassedToNextListener) {
+    NotifySwitchArgs args(1 /*sequenceNum*/, 2 /*eventTime*/, 3 /*policyFlags*/, 4 /*switchValues*/,
+                          5 /*switchMask*/);
+
+    mBlocker->notifySwitch(&args);
+    NotifySwitchArgs outArgs;
+    ASSERT_NO_FATAL_FAILURE(mTestListener.assertNotifySwitchWasCalled(&outArgs));
+    ASSERT_EQ(args, outArgs);
+}
+
+/**
+ * Create a basic device reset event and send it to UnwantedInteractionBlocker.
+ * Expect that the event is received by the next input stage, unmodified.
+ */
+TEST_F(UnwantedInteractionBlockerTest, DeviceResetIsPassedToNextListener) {
+    NotifyDeviceResetArgs args(1 /*sequenceNum*/, 2 /*eventTime*/, DEVICE_ID);
+
+    mBlocker->notifyDeviceReset(&args);
+    NotifyDeviceResetArgs outArgs;
+    ASSERT_NO_FATAL_FAILURE(mTestListener.assertNotifyDeviceResetWasCalled(&outArgs));
+    ASSERT_EQ(args, outArgs);
+}
+
+/**
+ * The state should be reset when device reset happens. That means, we can reset in the middle of a
+ * gesture, and start a new stream. There should be no crash. If the state wasn't reset correctly,
+ * a crash due to inconsistent event stream could have occurred.
+ */
+TEST_F(UnwantedInteractionBlockerTest, NoCrashWhenResetHappens) {
+    NotifyMotionArgs args;
+    mBlocker->notifyInputDevicesChanged({generateTestDeviceInfo()});
+    mBlocker->notifyMotion(
+            &(args = generateMotionArgs(0 /*downTime*/, 1 /*eventTime*/, DOWN, {{1, 2, 3}})));
+    mBlocker->notifyMotion(
+            &(args = generateMotionArgs(0 /*downTime*/, 2 /*eventTime*/, MOVE, {{4, 5, 6}})));
+    NotifyDeviceResetArgs resetArgs(1 /*sequenceNum*/, 3 /*eventTime*/, DEVICE_ID);
+    mBlocker->notifyDeviceReset(&resetArgs);
+    // Start a new gesture with a DOWN event, even though the previous event stream was incomplete.
+    mBlocker->notifyMotion(
+            &(args = generateMotionArgs(0 /*downTime*/, 4 /*eventTime*/, DOWN, {{7, 8, 9}})));
+}
+
+/**
+ * If input devices have changed, but the important device info that's used by the
+ * UnwantedInteractionBlocker has not changed, there should not be a reset.
+ */
+TEST_F(UnwantedInteractionBlockerTest, NoResetIfDeviceInfoChanges) {
+    NotifyMotionArgs args;
+    mBlocker->notifyInputDevicesChanged({generateTestDeviceInfo()});
+    mBlocker->notifyMotion(
+            &(args = generateMotionArgs(0 /*downTime*/, 1 /*eventTime*/, DOWN, {{1, 2, 3}})));
+    mBlocker->notifyMotion(
+            &(args = generateMotionArgs(0 /*downTime*/, 2 /*eventTime*/, MOVE, {{4, 5, 6}})));
+
+    // Now pretend the device changed, even though nothing is different for DEVICE_ID in practice.
+    mBlocker->notifyInputDevicesChanged({generateTestDeviceInfo()});
+
+    // The MOVE event continues the gesture that started before 'devices changed', so it should not
+    // cause a crash.
+    mBlocker->notifyMotion(
+            &(args = generateMotionArgs(0 /*downTime*/, 4 /*eventTime*/, MOVE, {{7, 8, 9}})));
+}
+
+using UnwantedInteractionBlockerTestDeathTest = UnwantedInteractionBlockerTest;
+
+/**
+ * The state should be reset when device reset happens. If we receive an inconsistent event after
+ * the reset happens, crash should occur.
+ */
+TEST_F(UnwantedInteractionBlockerTestDeathTest, InconsistentEventAfterResetCausesACrash) {
+    ScopedSilentDeath _silentDeath;
+    NotifyMotionArgs args;
+    mBlocker->notifyInputDevicesChanged({generateTestDeviceInfo()});
+    mBlocker->notifyMotion(
+            &(args = generateMotionArgs(0 /*downTime*/, 1 /*eventTime*/, DOWN, {{1, 2, 3}})));
+    mBlocker->notifyMotion(
+            &(args = generateMotionArgs(0 /*downTime*/, 2 /*eventTime*/, MOVE, {{4, 5, 6}})));
+    NotifyDeviceResetArgs resetArgs(1 /*sequenceNum*/, 3 /*eventTime*/, DEVICE_ID);
+    mBlocker->notifyDeviceReset(&resetArgs);
+    // Sending MOVE without a DOWN -> should crash!
+    ASSERT_DEATH(
+            {
+                mBlocker->notifyMotion(&(args = generateMotionArgs(0 /*downTime*/, 4 /*eventTime*/,
+                                                                   MOVE, {{7, 8, 9}})));
+            },
+            "Could not find slot");
+}
+
+/**
+ * There should be a crash when an inconsistent event is received.
+ */
+TEST_F(UnwantedInteractionBlockerTestDeathTest, WhenMoveWithoutDownCausesACrash) {
+    ScopedSilentDeath _silentDeath;
+    NotifyMotionArgs args = generateMotionArgs(0 /*downTime*/, 1 /*eventTime*/, MOVE, {{1, 2, 3}});
+    mBlocker->notifyInputDevicesChanged({generateTestDeviceInfo()});
+    ASSERT_DEATH({ mBlocker->notifyMotion(&args); }, "Could not find slot");
+}
+
+class PalmRejectorTest : public testing::Test {
+protected:
+    std::unique_ptr<PalmRejector> mPalmRejector;
+
+    void SetUp() override {
+        AndroidPalmFilterDeviceInfo info = generatePalmFilterDeviceInfo();
+        mPalmRejector = std::make_unique<PalmRejector>(info);
+    }
+};
+
+using PalmRejectorTestDeathTest = PalmRejectorTest;
+
+TEST_F(PalmRejectorTestDeathTest, InconsistentEventCausesACrash) {
+    ScopedSilentDeath _silentDeath;
+    constexpr nsecs_t downTime = 0;
+    NotifyMotionArgs args =
+            generateMotionArgs(downTime, 2 /*eventTime*/, MOVE, {{1406.0, 650.0, 52.0}});
+    ASSERT_DEATH({ mPalmRejector->processMotion(args); }, "Could not find slot");
+}
+
+/**
+ * Use PalmRejector with actual touchscreen data and real model.
+ * Two pointers that should both be classified as palms.
+ */
+TEST_F(PalmRejectorTest, TwoPointersAreCanceled) {
+    std::vector<NotifyMotionArgs> argsList;
+    constexpr nsecs_t downTime = 255955749837000;
+
+    mPalmRejector->processMotion(
+            generateMotionArgs(downTime, downTime, DOWN, {{1342.0, 613.0, 79.0}}));
+    mPalmRejector->processMotion(
+            generateMotionArgs(downTime, 255955759313000, MOVE, {{1406.0, 650.0, 52.0}}));
+    mPalmRejector->processMotion(
+            generateMotionArgs(downTime, 255955766361000, MOVE, {{1429.0, 672.0, 46.0}}));
+    mPalmRejector->processMotion(
+            generateMotionArgs(downTime, 255955775989000, MOVE, {{1417.0, 685.0, 41.0}}));
+    mPalmRejector->processMotion(
+            generateMotionArgs(downTime, 255955775989000, POINTER_1_DOWN,
+                               {{1417.0, 685.0, 41.0}, {1062.0, 697.0, 10.0}}));
+    mPalmRejector->processMotion(
+            generateMotionArgs(downTime, 255955783039000, MOVE,
+                               {{1414.0, 702.0, 41.0}, {1059.0, 731.0, 12.0}}));
+    mPalmRejector->processMotion(
+            generateMotionArgs(downTime, 255955792536000, MOVE,
+                               {{1415.0, 719.0, 44.0}, {1060.0, 760.0, 11.0}}));
+    mPalmRejector->processMotion(
+            generateMotionArgs(downTime, 255955799474000, MOVE,
+                               {{1421.0, 733.0, 42.0}, {1065.0, 769.0, 13.0}}));
+    mPalmRejector->processMotion(
+            generateMotionArgs(downTime, 255955809177000, MOVE,
+                               {{1426.0, 742.0, 43.0}, {1068.0, 771.0, 13.0}}));
+    mPalmRejector->processMotion(
+            generateMotionArgs(downTime, 255955816131000, MOVE,
+                               {{1430.0, 748.0, 45.0}, {1069.0, 772.0, 13.0}}));
+    argsList = mPalmRejector->processMotion(
+            generateMotionArgs(downTime, 255955825907000, MOVE,
+                               {{1432.0, 750.0, 44.0}, {1069.0, 772.0, 13.0}}));
+    ASSERT_EQ(1u, argsList.size());
+    ASSERT_EQ(0 /* No FLAG_CANCELED */, argsList[0].flags);
+    argsList = mPalmRejector->processMotion(
+            generateMotionArgs(downTime, 255955832736000, MOVE,
+                               {{1433.0, 751.0, 44.0}, {1070.0, 771.0, 13.0}}));
+    ASSERT_EQ(2u, argsList.size());
+    ASSERT_EQ(POINTER_0_UP, argsList[0].action);
+    ASSERT_EQ(AMOTION_EVENT_FLAG_CANCELED, argsList[0].flags);
+    ASSERT_EQ(MOVE, argsList[1].action);
+    ASSERT_EQ(1u, argsList[1].pointerCount);
+    ASSERT_EQ(0, argsList[1].flags);
+
+    mPalmRejector->processMotion(
+            generateMotionArgs(downTime, 255955842432000, MOVE,
+                               {{1433.0, 751.0, 42.0}, {1071.0, 770.0, 13.0}}));
+    mPalmRejector->processMotion(
+            generateMotionArgs(downTime, 255955849380000, MOVE,
+                               {{1433.0, 751.0, 45.0}, {1072.0, 769.0, 13.0}}));
+    mPalmRejector->processMotion(
+            generateMotionArgs(downTime, 255955859046000, MOVE,
+                               {{1433.0, 751.0, 43.0}, {1072.0, 768.0, 13.0}}));
+    argsList = mPalmRejector->processMotion(
+            generateMotionArgs(downTime, 255955869823000, MOVE,
+                               {{1433.0, 751.0, 45.0}, {1072.0, 767.0, 13.0}}));
+    ASSERT_EQ(1u, argsList.size());
+    ASSERT_EQ(AMOTION_EVENT_ACTION_CANCEL, argsList[0].action);
+    mPalmRejector->processMotion(
+            generateMotionArgs(downTime, 255955875641000, MOVE,
+                               {{1433.0, 751.0, 43.0}, {1072.0, 766.0, 13.0}}));
+    mPalmRejector->processMotion(
+            generateMotionArgs(downTime, 255955882693000, MOVE,
+                               {{1433.0, 750.0, 44.0}, {1072.0, 765.0, 13.0}}));
+    mPalmRejector->processMotion(
+            generateMotionArgs(downTime, 255955892324000, MOVE,
+                               {{1433.0, 750.0, 42.0}, {1072.0, 763.0, 14.0}}));
+    mPalmRejector->processMotion(
+            generateMotionArgs(downTime, 255955899425000, MOVE,
+                               {{1434.0, 750.0, 44.0}, {1073.0, 761.0, 14.0}}));
+    mPalmRejector->processMotion(
+            generateMotionArgs(downTime, 255955909400000, MOVE,
+                               {{1435.0, 750.0, 43.0}, {1073.0, 759.0, 15.0}}));
+    mPalmRejector->processMotion(
+            generateMotionArgs(downTime, 255955915885000, MOVE,
+                               {{1436.0, 750.0, 45.0}, {1074.0, 757.0, 15.0}}));
+    mPalmRejector->processMotion(
+            generateMotionArgs(downTime, 255955925607000, MOVE,
+                               {{1436.0, 750.0, 44.0}, {1074.0, 755.0, 15.0}}));
+    mPalmRejector->processMotion(
+            generateMotionArgs(downTime, 255955932580000, MOVE,
+                               {{1436.0, 750.0, 45.0}, {1074.0, 753.0, 15.0}}));
+    mPalmRejector->processMotion(
+            generateMotionArgs(downTime, 255955942231000, MOVE,
+                               {{1436.0, 749.0, 44.0}, {1074.0, 751.0, 15.0}}));
+    mPalmRejector->processMotion(
+            generateMotionArgs(downTime, 255955949204000, MOVE,
+                               {{1435.0, 748.0, 45.0}, {1074.0, 749.0, 15.0}}));
+    mPalmRejector->processMotion(
+            generateMotionArgs(downTime, 255955959103000, MOVE,
+                               {{1434.0, 746.0, 44.0}, {1074.0, 747.0, 14.0}}));
+    mPalmRejector->processMotion(
+            generateMotionArgs(downTime, 255955965884000, MOVE,
+                               {{1433.0, 744.0, 44.0}, {1075.0, 745.0, 14.0}}));
+    mPalmRejector->processMotion(
+            generateMotionArgs(downTime, 255955975649000, MOVE,
+                               {{1431.0, 741.0, 43.0}, {1075.0, 742.0, 13.0}}));
+    mPalmRejector->processMotion(
+            generateMotionArgs(downTime, 255955982537000, MOVE,
+                               {{1428.0, 738.0, 43.0}, {1076.0, 739.0, 12.0}}));
+    mPalmRejector->processMotion(
+            generateMotionArgs(downTime, 255955992284000, MOVE,
+                               {{1400.0, 726.0, 54.0}, {1076.0, 739.0, 13.0}}));
+    argsList = mPalmRejector->processMotion(
+            generateMotionArgs(downTime, 255955999348000, POINTER_1_UP,
+                               {{1362.0, 716.0, 55.0}, {1076.0, 739.0, 13.0}}));
+    ASSERT_TRUE(argsList.empty());
+    mPalmRejector->processMotion(
+            generateMotionArgs(downTime, 255955999348000, MOVE, {{1362.0, 716.0, 55.0}}));
+    mPalmRejector->processMotion(
+            generateMotionArgs(downTime, 255956008885000, MOVE, {{1347.0, 707.0, 54.0}}));
+    mPalmRejector->processMotion(
+            generateMotionArgs(downTime, 255956015791000, MOVE, {{1340.0, 698.0, 54.0}}));
+    mPalmRejector->processMotion(
+            generateMotionArgs(downTime, 255956025804000, MOVE, {{1338.0, 694.0, 55.0}}));
+    mPalmRejector->processMotion(
+            generateMotionArgs(downTime, 255956032314000, MOVE, {{1336.0, 690.0, 53.0}}));
+    mPalmRejector->processMotion(
+            generateMotionArgs(downTime, 255956042329000, MOVE, {{1334.0, 685.0, 47.0}}));
+    mPalmRejector->processMotion(
+            generateMotionArgs(downTime, 255956048979000, MOVE, {{1333.0, 679.0, 46.0}}));
+    mPalmRejector->processMotion(
+            generateMotionArgs(downTime, 255956058813000, MOVE, {{1332.0, 672.0, 45.0}}));
+    mPalmRejector->processMotion(
+            generateMotionArgs(downTime, 255956065592000, MOVE, {{1333.0, 666.0, 40.0}}));
+    mPalmRejector->processMotion(
+            generateMotionArgs(downTime, 255956075276000, MOVE, {{1336.0, 661.0, 24.0}}));
+    mPalmRejector->processMotion(
+            generateMotionArgs(downTime, 255956082198000, MOVE, {{1338.0, 656.0, 16.0}}));
+    mPalmRejector->processMotion(
+            generateMotionArgs(downTime, 255956092059000, MOVE, {{1341.0, 649.0, 1.0}}));
+    argsList = mPalmRejector->processMotion(
+            generateMotionArgs(downTime, 255956098764000, UP, {{1341.0, 649.0, 1.0}}));
+    ASSERT_TRUE(argsList.empty());
+}
+
+/**
+ * A test implementation of PalmDetectionFilter that allows you to specify which pointer you want
+ * the model to consider 'suppressed'. The pointer is specified using its position (x, y).
+ * Current limitation:
+ *      Pointers may not cross each other in space during motion. Otherwise, any pointer with the
+ *      position matching the suppressed position will be considered "palm".
+ */
+class TestFilter : public ::ui::PalmDetectionFilter {
+public:
+    TestFilter(::ui::SharedPalmDetectionFilterState* state,
+               std::vector<std::pair<float, float>>& suppressedPointers)
+          : ::ui::PalmDetectionFilter(state), mSuppressedPointers(suppressedPointers) {}
+
+    void Filter(const std::vector<::ui::InProgressTouchEvdev>& touches, ::base::TimeTicks time,
+                std::bitset<::ui::kNumTouchEvdevSlots>* slots_to_hold,
+                std::bitset<::ui::kNumTouchEvdevSlots>* slots_to_suppress) override {
+        updateSuppressedSlots(touches);
+        *slots_to_suppress = mSuppressedSlots;
+    }
+
+    std::string FilterNameForTesting() const override { return "test filter"; }
+
+private:
+    void updateSuppressedSlots(const std::vector<::ui::InProgressTouchEvdev>& touches) {
+        for (::ui::InProgressTouchEvdev touch : touches) {
+            for (const auto& [x, y] : mSuppressedPointers) {
+                const float dx = (touch.x - x);
+                const float dy = (touch.y - y);
+                const float distanceSquared = dx * dx + dy * dy;
+                if (distanceSquared < 1) {
+                    mSuppressedSlots.set(touch.slot, true);
+                }
+            }
+        }
+    }
+
+    std::bitset<::ui::kNumTouchEvdevSlots> mSuppressedSlots;
+    std::vector<std::pair<float, float>>& mSuppressedPointers;
+};
+
+class PalmRejectorFakeFilterTest : public testing::Test {
+protected:
+    std::unique_ptr<PalmRejector> mPalmRejector;
+
+    void SetUp() override {
+        std::unique_ptr<::ui::PalmDetectionFilter> filter =
+                std::make_unique<TestFilter>(&mSharedPalmState, /*byref*/ mSuppressedPointers);
+        mPalmRejector =
+                std::make_unique<PalmRejector>(generatePalmFilterDeviceInfo(), std::move(filter));
+    }
+
+    void suppressPointerAtPosition(float x, float y) { mSuppressedPointers.push_back({x, y}); }
+
+private:
+    std::vector<std::pair<float, float>> mSuppressedPointers;
+    ::ui::SharedPalmDetectionFilterState mSharedPalmState; // unused, but we must retain ownership
+};
+
+/**
+ * When a MOVE event happens, the model identifies the pointer as palm. At that time, the palm
+ * rejector should send a POINTER_UP event for this pointer with FLAG_CANCELED, and subsequent
+ * events should have this pointer removed.
+ */
+TEST_F(PalmRejectorFakeFilterTest, OneOfTwoPointersIsCanceled) {
+    std::vector<NotifyMotionArgs> argsList;
+    constexpr nsecs_t downTime = 0;
+
+    mPalmRejector->processMotion(
+            generateMotionArgs(downTime, downTime, DOWN, {{1342.0, 613.0, 79.0}}));
+    mPalmRejector->processMotion(
+            generateMotionArgs(downTime, 1, POINTER_1_DOWN,
+                               {{1417.0, 685.0, 41.0}, {1062.0, 697.0, 10.0}}));
+    // Cancel the second pointer
+    suppressPointerAtPosition(1059, 731);
+    argsList = mPalmRejector->processMotion(
+            generateMotionArgs(downTime, 255955783039000, MOVE,
+                               {{1414.0, 702.0, 41.0}, {1059.0, 731.0, 12.0}}));
+    ASSERT_EQ(2u, argsList.size());
+    // First event - cancel pointer 1
+    ASSERT_EQ(POINTER_1_UP, argsList[0].action);
+    ASSERT_EQ(AMOTION_EVENT_FLAG_CANCELED, argsList[0].flags);
+    // Second event - send MOVE for the remaining pointer
+    ASSERT_EQ(MOVE, argsList[1].action);
+    ASSERT_EQ(0, argsList[1].flags);
+
+    // Future move events only contain 1 pointer, because the second pointer will continue
+    // to be suppressed
+    argsList = mPalmRejector->processMotion(
+            generateMotionArgs(downTime, 255955783039000, MOVE,
+                               {{1433.0, 751.0, 43.0}, {1072.0, 766.0, 13.0}}));
+    ASSERT_EQ(1u, argsList.size());
+    ASSERT_EQ(MOVE, argsList[0].action);
+    ASSERT_EQ(1u, argsList[0].pointerCount);
+    ASSERT_EQ(1433, argsList[0].pointerCoords[0].getX());
+    ASSERT_EQ(751, argsList[0].pointerCoords[0].getY());
+}
+
+/**
+ * Send two pointers, and suppress both of them. Check that ACTION_CANCEL is generated.
+ * Afterwards:
+ *  1) Future MOVE events are ignored.
+ *  2) When a new pointer goes down, ACTION_DOWN is generated
+ */
+TEST_F(PalmRejectorFakeFilterTest, NewDownEventAfterCancel) {
+    std::vector<NotifyMotionArgs> argsList;
+    constexpr nsecs_t downTime = 0;
+
+    mPalmRejector->processMotion(
+            generateMotionArgs(downTime, downTime, DOWN, {{1342.0, 613.0, 79.0}}));
+    mPalmRejector->processMotion(
+            generateMotionArgs(downTime, 1, POINTER_1_DOWN,
+                               {{1417.0, 685.0, 41.0}, {1062.0, 697.0, 10.0}}));
+    // Cancel both pointers
+    suppressPointerAtPosition(1059, 731);
+    suppressPointerAtPosition(1400, 680);
+    argsList = mPalmRejector->processMotion(
+            generateMotionArgs(downTime, 1, MOVE, {{1400, 680, 41}, {1059, 731, 10}}));
+    ASSERT_EQ(1u, argsList.size());
+    // Cancel all
+    ASSERT_EQ(CANCEL, argsList[0].action);
+    ASSERT_EQ(2u, argsList[0].pointerCount);
+    ASSERT_EQ(AMOTION_EVENT_FLAG_CANCELED, argsList[0].flags);
+
+    // Future move events are ignored
+    argsList = mPalmRejector->processMotion(
+            generateMotionArgs(downTime, 255955783039000, MOVE,
+                               {{1433.0, 751.0, 43.0}, {1072.0, 766.0, 13.0}}));
+    ASSERT_EQ(0u, argsList.size());
+
+    // When a new pointer goes down, a new DOWN event is generated
+    argsList = mPalmRejector->processMotion(
+            generateMotionArgs(downTime, 255955783039000, POINTER_2_DOWN,
+                               {{1433.0, 751.0, 43.0}, {1072.0, 766.0, 13.0}, {1000, 700, 10}}));
+    ASSERT_EQ(1u, argsList.size());
+    ASSERT_EQ(DOWN, argsList[0].action);
+    ASSERT_EQ(1u, argsList[0].pointerCount);
+    ASSERT_EQ(2, argsList[0].pointerProperties[0].id);
+}
+
+/**
+ * 2 pointers are classified as palm simultaneously. When they are later
+ * released by Android, make sure that we drop both of these POINTER_UP events.
+ * Since they are classified as palm at the same time, we just need to receive a single CANCEL
+ * event. From MotionEvent docs: """A pointer id remains valid until the pointer eventually goes up
+ * (indicated by ACTION_UP or ACTION_POINTER_UP) or when the gesture is canceled (indicated by
+ *  ACTION_CANCEL)."""
+ * This means that generating additional POINTER_UP events is not necessary.
+ * The risk here is that "oldSuppressedPointerIds" will not be correct, because it will update after
+ * each motion, but pointers are canceled one at a time by Android.
+ */
+TEST_F(PalmRejectorFakeFilterTest, TwoPointersCanceledWhenOnePointerGoesUp) {
+    std::vector<NotifyMotionArgs> argsList;
+    constexpr nsecs_t downTime = 0;
+
+    mPalmRejector->processMotion(
+            generateMotionArgs(downTime, downTime, DOWN, {{1342.0, 613.0, 79.0}}));
+    mPalmRejector->processMotion(
+            generateMotionArgs(downTime, /*eventTime*/ 1, POINTER_1_DOWN,
+                               {{1417.0, 685.0, 41.0}, {1062.0, 697.0, 10.0}}));
+    // Suppress both pointers!!
+    suppressPointerAtPosition(1414, 702);
+    suppressPointerAtPosition(1059, 731);
+    argsList = mPalmRejector->processMotion(
+            generateMotionArgs(downTime, 255955783039000, POINTER_1_UP,
+                               {{1414.0, 702.0, 41.0}, {1059.0, 731.0, 12.0}}));
+    ASSERT_EQ(1u, argsList.size());
+    ASSERT_EQ(CANCEL, argsList[0].action) << MotionEvent::actionToString(argsList[0].action);
+    ASSERT_EQ(AMOTION_EVENT_FLAG_CANCELED, argsList[0].flags);
+
+    // Future move events should not go to the listener.
+    argsList = mPalmRejector->processMotion(
+            generateMotionArgs(downTime, 255955783049000, MOVE, {{1435.0, 755.0, 43.0}}));
+    ASSERT_EQ(0u, argsList.size());
+
+    argsList = mPalmRejector->processMotion(
+            generateMotionArgs(downTime, 255955783059000, UP, {{1436.0, 756.0, 43.0}}));
+    ASSERT_EQ(0u, argsList.size());
+}
+
+/**
+ * Send 3 pointers, and then cancel one of them during a MOVE event. We should see ACTION_POINTER_UP
+ * generated for that. Next, another pointer is canceled during ACTION_POINTER_DOWN. For that
+ * pointer, we simply shouldn't send the event.
+ */
+TEST_F(PalmRejectorFakeFilterTest, CancelTwoPointers) {
+    std::vector<NotifyMotionArgs> argsList;
+    constexpr nsecs_t downTime = 0;
+
+    mPalmRejector->processMotion(
+            generateMotionArgs(downTime, downTime, DOWN, {{1342.0, 613.0, 79.0}}));
+    mPalmRejector->processMotion(
+            generateMotionArgs(downTime, /*eventTime*/ 1, POINTER_1_DOWN,
+                               {{1417.0, 685.0, 41.0}, {1062.0, 697.0, 10.0}}));
+
+    // Suppress second pointer (pointer 1)
+    suppressPointerAtPosition(1060, 700);
+    argsList = mPalmRejector->processMotion(
+            generateMotionArgs(downTime, /*eventTime*/ 1, MOVE,
+                               {{1417.0, 685.0, 41.0}, {1060, 700, 10.0}}));
+    ASSERT_EQ(2u, argsList.size());
+    ASSERT_EQ(POINTER_1_UP, argsList[0].action);
+    ASSERT_EQ(AMOTION_EVENT_FLAG_CANCELED, argsList[0].flags);
+
+    ASSERT_EQ(MOVE, argsList[1].action) << MotionEvent::actionToString(argsList[1].action);
+    ASSERT_EQ(0, argsList[1].flags);
+
+    // A new pointer goes down and gets suppressed right away. It should just be dropped
+    suppressPointerAtPosition(1001, 601);
+    argsList = mPalmRejector->processMotion(
+            generateMotionArgs(downTime, /*eventTime*/ 1, POINTER_2_DOWN,
+                               {{1417.0, 685.0, 41.0}, {1062.0, 697.0, 10.0}, {1001, 601, 5}}));
+
+    ASSERT_EQ(0u, argsList.size());
+    // Likewise, pointer that's already canceled should be ignored
+    argsList = mPalmRejector->processMotion(
+            generateMotionArgs(downTime, /*eventTime*/ 1, POINTER_2_UP,
+                               {{1417.0, 685.0, 41.0}, {1062.0, 697.0, 10.0}, {1001, 601, 5}}));
+    ASSERT_EQ(0u, argsList.size());
+
+    // Cancel all pointers when pointer 1 goes up. Pointer 1 was already canceled earlier.
+    suppressPointerAtPosition(1417, 685);
+    argsList = mPalmRejector->processMotion(
+            generateMotionArgs(downTime, /*eventTime*/ 1, POINTER_1_UP,
+                               {{1417.0, 685.0, 41.0}, {1062.0, 697.0, 10.0}}));
+    ASSERT_EQ(1u, argsList.size());
+    ASSERT_EQ(CANCEL, argsList[0].action);
+}
+
+} // namespace android
diff --git a/services/powermanager/Android.bp b/services/powermanager/Android.bp
index d828aa9..6fbba3f 100644
--- a/services/powermanager/Android.bp
+++ b/services/powermanager/Android.bp
@@ -24,11 +24,11 @@
     ],
 
     aidl: {
-       local_include_dirs: ["include"],
-       include_dirs: [
-           "frameworks/base/core/java/android/os",
-       ],
-       export_aidl_headers: true
+        local_include_dirs: ["include"],
+        include_dirs: [
+            "frameworks/base/core/java/android/os",
+        ],
+        export_aidl_headers: true,
     },
 
     shared_libs: [
@@ -38,7 +38,7 @@
         "libutils",
         "android.hardware.power@1.0",
         "android.hardware.power@1.1",
-        "android.hardware.power-V2-cpp",
+        "android.hardware.power-V3-cpp",
     ],
 
     cflags: [
@@ -50,6 +50,6 @@
 
     local_include_dirs: ["include"],
     export_include_dirs: [
-         "include",
+        "include",
     ],
 }
diff --git a/services/powermanager/benchmarks/Android.bp b/services/powermanager/benchmarks/Android.bp
index 3997929..fcb012f 100644
--- a/services/powermanager/benchmarks/Android.bp
+++ b/services/powermanager/benchmarks/Android.bp
@@ -38,7 +38,7 @@
         "libutils",
         "android.hardware.power@1.0",
         "android.hardware.power@1.1",
-        "android.hardware.power-V2-cpp",
+        "android.hardware.power-V3-cpp",
     ],
     static_libs: [
         "libtestUtil",
diff --git a/services/powermanager/benchmarks/PowerHalAidlBenchmarks.cpp b/services/powermanager/benchmarks/PowerHalAidlBenchmarks.cpp
index 1100cad..6e5e14d 100644
--- a/services/powermanager/benchmarks/PowerHalAidlBenchmarks.cpp
+++ b/services/powermanager/benchmarks/PowerHalAidlBenchmarks.cpp
@@ -66,13 +66,13 @@
     sp<IPower> hal = waitForVintfService<IPower>();
 
     if (hal == nullptr) {
-        ALOGI("Power HAL not available, skipping test...");
+        ALOGV("Power HAL not available, skipping test...");
         return;
     }
 
     binder::Status ret = (*hal.*fn)(std::forward<Args1>(args1)...);
     if (ret.exceptionCode() == binder::Status::Exception::EX_UNSUPPORTED_OPERATION) {
-        ALOGI("Power HAL does not support this operation, skipping test...");
+        ALOGV("Power HAL does not support this operation, skipping test...");
         return;
     }
 
@@ -93,7 +93,7 @@
     sp<IPower> pwHal = waitForVintfService<IPower>();
 
     if (pwHal == nullptr) {
-        ALOGI("Power HAL not available, skipping test...");
+        ALOGV("Power HAL not available, skipping test...");
         return;
     }
 
@@ -105,13 +105,13 @@
     auto status = pwHal->createHintSession(1, 0, threadIds, durationNanos, &hal);
 
     if (hal == nullptr) {
-        ALOGI("Power HAL doesn't support session, skipping test...");
+        ALOGV("Power HAL doesn't support session, skipping test...");
         return;
     }
 
     binder::Status ret = (*hal.*fn)(std::forward<Args1>(args1)...);
     if (ret.exceptionCode() == binder::Status::Exception::EX_UNSUPPORTED_OPERATION) {
-        ALOGI("Power HAL does not support this operation, skipping test...");
+        ALOGV("Power HAL does not support this operation, skipping test...");
         return;
     }
 
@@ -159,13 +159,13 @@
     sp<IPower> hal = waitForVintfService<IPower>();
 
     if (hal == nullptr) {
-        ALOGI("Power HAL not available, skipping test...");
+        ALOGV("Power HAL not available, skipping test...");
         return;
     }
 
     binder::Status ret = hal->createHintSession(tgid, uid, threadIds, durationNanos, &appSession);
     if (ret.exceptionCode() == binder::Status::Exception::EX_UNSUPPORTED_OPERATION) {
-        ALOGI("Power HAL does not support this operation, skipping test...");
+        ALOGV("Power HAL does not support this operation, skipping test...");
         return;
     }
 
diff --git a/services/powermanager/benchmarks/PowerHalHidlBenchmarks.cpp b/services/powermanager/benchmarks/PowerHalHidlBenchmarks.cpp
index 97e026b..167f3a6 100644
--- a/services/powermanager/benchmarks/PowerHalHidlBenchmarks.cpp
+++ b/services/powermanager/benchmarks/PowerHalHidlBenchmarks.cpp
@@ -51,7 +51,7 @@
     sp<I> hal = I::getService();
 
     if (hal == nullptr) {
-        ALOGI("Power HAL HIDL not available, skipping test...");
+        ALOGV("Power HAL HIDL not available, skipping test...");
         return;
     }
 
diff --git a/services/powermanager/tests/Android.bp b/services/powermanager/tests/Android.bp
index 659b2d2..2d1558a 100644
--- a/services/powermanager/tests/Android.bp
+++ b/services/powermanager/tests/Android.bp
@@ -46,7 +46,7 @@
         "libutils",
         "android.hardware.power@1.0",
         "android.hardware.power@1.1",
-        "android.hardware.power-V2-cpp",
+        "android.hardware.power-V3-cpp",
     ],
     static_libs: [
         "libgmock",
diff --git a/services/powermanager/tests/PowerHalWrapperAidlTest.cpp b/services/powermanager/tests/PowerHalWrapperAidlTest.cpp
index d890f5c..cb1a77a 100644
--- a/services/powermanager/tests/PowerHalWrapperAidlTest.cpp
+++ b/services/powermanager/tests/PowerHalWrapperAidlTest.cpp
@@ -183,6 +183,10 @@
 
     auto result = mWrapper->setMode(Mode::LAUNCH, true);
     ASSERT_TRUE(result.isUnsupported());
+
+    EXPECT_CALL(*mMockHal.get(), isModeSupported(Eq(Mode::CAMERA_STREAMING_HIGH), _))
+            .Times(Exactly(1))
+            .WillRepeatedly(DoAll(SetArgPointee<1>(false), Return(Status())));
     result = mWrapper->setMode(Mode::CAMERA_STREAMING_HIGH, true);
     ASSERT_TRUE(result.isUnsupported());
 }
diff --git a/services/sensorservice/AidlSensorHalWrapper.cpp b/services/sensorservice/AidlSensorHalWrapper.cpp
new file mode 100644
index 0000000..4d1de96
--- /dev/null
+++ b/services/sensorservice/AidlSensorHalWrapper.cpp
@@ -0,0 +1,776 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "AidlSensorHalWrapper.h"
+#include "ISensorsWrapper.h"
+#include "SensorDeviceUtils.h"
+#include "android/hardware/sensors/2.0/types.h"
+
+#include <aidl/android/hardware/sensors/BnSensorsCallback.h>
+#include <aidlcommonsupport/NativeHandle.h>
+#include <android-base/logging.h>
+#include <android/binder_manager.h>
+
+using ::aidl::android::hardware::sensors::AdditionalInfo;
+using ::aidl::android::hardware::sensors::DynamicSensorInfo;
+using ::aidl::android::hardware::sensors::Event;
+using ::aidl::android::hardware::sensors::ISensors;
+using ::aidl::android::hardware::sensors::SensorInfo;
+using ::aidl::android::hardware::sensors::SensorStatus;
+using ::aidl::android::hardware::sensors::SensorType;
+using ::android::AidlMessageQueue;
+using ::android::hardware::EventFlag;
+using ::android::hardware::sensors::V2_1::implementation::MAX_RECEIVE_BUFFER_EVENT_COUNT;
+
+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();
+}
+
+template <typename EnumType>
+constexpr typename std::underlying_type<EnumType>::type asBaseType(EnumType value) {
+    return static_cast<typename std::underlying_type<EnumType>::type>(value);
+}
+
+enum EventQueueFlagBitsInternal : uint32_t {
+    INTERNAL_WAKE = 1 << 16,
+};
+
+} // anonymous namespace
+
+class AidlSensorsCallback : public ::aidl::android::hardware::sensors::BnSensorsCallback {
+public:
+    AidlSensorsCallback(AidlSensorHalWrapper::SensorDeviceCallback *sensorDeviceCallback)
+          : mSensorDeviceCallback(sensorDeviceCallback) {}
+
+    ::ndk::ScopedAStatus onDynamicSensorsConnected(
+            const std::vector<SensorInfo> &sensorInfos) override {
+        std::vector<sensor_t> sensors;
+        for (const SensorInfo &sensorInfo : sensorInfos) {
+            sensor_t sensor;
+            convertToSensor(sensorInfo, &sensor);
+            sensors.push_back(sensor);
+        }
+
+        mSensorDeviceCallback->onDynamicSensorsConnected(sensors);
+        return ::ndk::ScopedAStatus::ok();
+    }
+
+    ::ndk::ScopedAStatus onDynamicSensorsDisconnected(
+            const std::vector<int32_t> &sensorHandles) override {
+        mSensorDeviceCallback->onDynamicSensorsDisconnected(sensorHandles);
+        return ::ndk::ScopedAStatus::ok();
+    }
+
+private:
+    ISensorHalWrapper::SensorDeviceCallback *mSensorDeviceCallback;
+};
+
+AidlSensorHalWrapper::AidlSensorHalWrapper()
+      : mEventQueueFlag(nullptr),
+        mWakeLockQueueFlag(nullptr),
+        mDeathRecipient(AIBinder_DeathRecipient_new(serviceDied)) {}
+
+bool AidlSensorHalWrapper::supportsPolling() {
+    return false;
+}
+
+bool AidlSensorHalWrapper::supportsMessageQueues() {
+    return true;
+}
+
+bool AidlSensorHalWrapper::connect(SensorDeviceCallback *callback) {
+    mSensorDeviceCallback = callback;
+    mSensors = nullptr;
+
+    auto aidlServiceName = std::string() + ISensors::descriptor + "/default";
+    if (AServiceManager_isDeclared(aidlServiceName.c_str())) {
+        if (mSensors != nullptr) {
+            AIBinder_unlinkToDeath(mSensors->asBinder().get(), mDeathRecipient.get(), this);
+        }
+
+        ndk::SpAIBinder binder(AServiceManager_waitForService(aidlServiceName.c_str()));
+        if (binder.get() != nullptr) {
+            mSensors = ISensors::fromBinder(binder);
+            mEventQueue = std::make_unique<AidlMessageQueue<
+                    Event, SynchronizedReadWrite>>(MAX_RECEIVE_BUFFER_EVENT_COUNT,
+                                                   /*configureEventFlagWord=*/true);
+
+            mWakeLockQueue = std::make_unique<AidlMessageQueue<
+                    int32_t, SynchronizedReadWrite>>(MAX_RECEIVE_BUFFER_EVENT_COUNT,
+                                                     /*configureEventFlagWord=*/true);
+            if (mEventQueueFlag != nullptr) {
+                EventFlag::deleteEventFlag(&mEventQueueFlag);
+            }
+            EventFlag::createEventFlag(mEventQueue->getEventFlagWord(), &mEventQueueFlag);
+            if (mWakeLockQueueFlag != nullptr) {
+                EventFlag::deleteEventFlag(&mWakeLockQueueFlag);
+            }
+            EventFlag::createEventFlag(mWakeLockQueue->getEventFlagWord(), &mWakeLockQueueFlag);
+
+            CHECK(mEventQueue != nullptr && mEventQueueFlag != nullptr &&
+                  mWakeLockQueue != nullptr && mWakeLockQueueFlag != nullptr);
+
+            mCallback = ndk::SharedRefBase::make<AidlSensorsCallback>(mSensorDeviceCallback);
+            mSensors->initialize(mEventQueue->dupeDesc(), mWakeLockQueue->dupeDesc(), mCallback);
+
+            AIBinder_linkToDeath(mSensors->asBinder().get(), mDeathRecipient.get(), this);
+        } else {
+            ALOGE("Could not connect to declared sensors AIDL HAL");
+        }
+    }
+
+    return mSensors != nullptr;
+}
+
+void AidlSensorHalWrapper::prepareForReconnect() {
+    mReconnecting = true;
+    if (mEventQueueFlag != nullptr) {
+        mEventQueueFlag->wake(asBaseType(INTERNAL_WAKE));
+    }
+}
+
+ssize_t AidlSensorHalWrapper::poll(sensors_event_t * /* buffer */, size_t /* count */) {
+    return 0;
+}
+
+ssize_t AidlSensorHalWrapper::pollFmq(sensors_event_t *buffer, size_t maxNumEventsToRead) {
+    ssize_t eventsRead = 0;
+    size_t availableEvents = mEventQueue->availableToRead();
+
+    if (availableEvents == 0) {
+        uint32_t eventFlagState = 0;
+
+        // Wait for events to become available. This is necessary so that the Event FMQ's read() is
+        // able to be called with the correct number of events to read. If the specified number of
+        // events is not available, then read() would return no events, possibly introducing
+        // additional latency in delivering events to applications.
+        if (mEventQueueFlag != nullptr) {
+            mEventQueueFlag->wait(asBaseType(ISensors::EVENT_QUEUE_FLAG_BITS_READ_AND_PROCESS) |
+                                          asBaseType(INTERNAL_WAKE),
+                                  &eventFlagState);
+        }
+        availableEvents = mEventQueue->availableToRead();
+
+        if ((eventFlagState & asBaseType(INTERNAL_WAKE)) && mReconnecting) {
+            ALOGD("Event FMQ internal wake, returning from poll with no events");
+            return DEAD_OBJECT;
+        }
+    }
+
+    size_t eventsToRead = std::min({availableEvents, maxNumEventsToRead, mEventBuffer.size()});
+    if (eventsToRead > 0) {
+        if (mEventQueue->read(mEventBuffer.data(), eventsToRead)) {
+            // Notify the Sensors HAL that sensor events have been read. This is required to support
+            // the use of writeBlocking by the Sensors HAL.
+            if (mEventQueueFlag != nullptr) {
+                mEventQueueFlag->wake(asBaseType(ISensors::EVENT_QUEUE_FLAG_BITS_EVENTS_READ));
+            }
+
+            for (size_t i = 0; i < eventsToRead; i++) {
+                convertToSensorEvent(mEventBuffer[i], &buffer[i]);
+            }
+            eventsRead = eventsToRead;
+        } else {
+            ALOGW("Failed to read %zu events, currently %zu events available", eventsToRead,
+                  availableEvents);
+        }
+    }
+
+    return eventsRead;
+}
+
+std::vector<sensor_t> AidlSensorHalWrapper::getSensorsList() {
+    std::vector<sensor_t> sensorsFound;
+
+    if (mSensors != nullptr) {
+        std::vector<SensorInfo> list;
+        mSensors->getSensorsList(&list);
+        for (size_t i = 0; i < list.size(); i++) {
+            sensor_t sensor;
+            convertToSensor(list[i], &sensor);
+            sensorsFound.push_back(sensor);
+        }
+    }
+
+    return sensorsFound;
+}
+
+status_t AidlSensorHalWrapper::setOperationMode(SensorService::Mode mode) {
+    if (mSensors == nullptr) return NO_INIT;
+    return convertToStatus(mSensors->setOperationMode(static_cast<ISensors::OperationMode>(mode)));
+}
+
+status_t AidlSensorHalWrapper::activate(int32_t sensorHandle, bool enabled) {
+    if (mSensors == nullptr) return NO_INIT;
+    return convertToStatus(mSensors->activate(sensorHandle, enabled));
+}
+
+status_t AidlSensorHalWrapper::batch(int32_t sensorHandle, int64_t samplingPeriodNs,
+                                     int64_t maxReportLatencyNs) {
+    if (mSensors == nullptr) return NO_INIT;
+    return convertToStatus(mSensors->batch(sensorHandle, samplingPeriodNs, maxReportLatencyNs));
+}
+
+status_t AidlSensorHalWrapper::flush(int32_t sensorHandle) {
+    if (mSensors == nullptr) return NO_INIT;
+    return convertToStatus(mSensors->flush(sensorHandle));
+}
+
+status_t AidlSensorHalWrapper::injectSensorData(const sensors_event_t *event) {
+    if (mSensors == nullptr) return NO_INIT;
+
+    Event ev;
+    convertFromSensorEvent(*event, &ev);
+    return convertToStatus(mSensors->injectSensorData(ev));
+}
+
+status_t AidlSensorHalWrapper::registerDirectChannel(const sensors_direct_mem_t *memory,
+                                                     int32_t *channelHandle) {
+    if (mSensors == nullptr) return NO_INIT;
+
+    ISensors::SharedMemInfo::SharedMemType type;
+    switch (memory->type) {
+        case SENSOR_DIRECT_MEM_TYPE_ASHMEM:
+            type = ISensors::SharedMemInfo::SharedMemType::ASHMEM;
+            break;
+        case SENSOR_DIRECT_MEM_TYPE_GRALLOC:
+            type = ISensors::SharedMemInfo::SharedMemType::GRALLOC;
+            break;
+        default:
+            return BAD_VALUE;
+    }
+
+    if (memory->format != SENSOR_DIRECT_FMT_SENSORS_EVENT) {
+        return BAD_VALUE;
+    }
+    ISensors::SharedMemInfo::SharedMemFormat format =
+            ISensors::SharedMemInfo::SharedMemFormat::SENSORS_EVENT;
+
+    ISensors::SharedMemInfo mem = {
+            .type = type,
+            .format = format,
+            .size = static_cast<int32_t>(memory->size),
+            .memoryHandle = makeToAidl(memory->handle),
+    };
+
+    return convertToStatus(mSensors->registerDirectChannel(mem, channelHandle));
+}
+
+status_t AidlSensorHalWrapper::unregisterDirectChannel(int32_t channelHandle) {
+    if (mSensors == nullptr) return NO_INIT;
+    return convertToStatus(mSensors->unregisterDirectChannel(channelHandle));
+}
+
+status_t AidlSensorHalWrapper::configureDirectChannel(int32_t sensorHandle, int32_t channelHandle,
+                                                      const struct sensors_direct_cfg_t *config) {
+    if (mSensors == nullptr) return NO_INIT;
+
+    ISensors::RateLevel rate;
+    switch (config->rate_level) {
+        case SENSOR_DIRECT_RATE_STOP:
+            rate = ISensors::RateLevel::STOP;
+            break;
+        case SENSOR_DIRECT_RATE_NORMAL:
+            rate = ISensors::RateLevel::NORMAL;
+            break;
+        case SENSOR_DIRECT_RATE_FAST:
+            rate = ISensors::RateLevel::FAST;
+            break;
+        case SENSOR_DIRECT_RATE_VERY_FAST:
+            rate = ISensors::RateLevel::VERY_FAST;
+            break;
+        default:
+            return BAD_VALUE;
+    }
+
+    int32_t token;
+    mSensors->configDirectReport(sensorHandle, channelHandle, rate, &token);
+    return token;
+}
+
+void AidlSensorHalWrapper::writeWakeLockHandled(uint32_t count) {
+    int signedCount = (int)count;
+    if (mWakeLockQueue->write(&signedCount)) {
+        mWakeLockQueueFlag->wake(asBaseType(ISensors::WAKE_LOCK_QUEUE_FLAG_BITS_DATA_WRITTEN));
+    } else {
+        ALOGW("Failed to write wake lock handled");
+    }
+}
+
+} // namespace android
diff --git a/services/sensorservice/AidlSensorHalWrapper.h b/services/sensorservice/AidlSensorHalWrapper.h
new file mode 100644
index 0000000..9f61993
--- /dev/null
+++ b/services/sensorservice/AidlSensorHalWrapper.h
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_AIDL_SENSOR_HAL_WRAPPER_H
+#define ANDROID_AIDL_SENSOR_HAL_WRAPPER_H
+
+#include "ISensorHalWrapper.h"
+
+#include <aidl/android/hardware/sensors/ISensors.h>
+#include <fmq/AidlMessageQueue.h>
+#include <sensor/SensorEventQueue.h>
+
+namespace android {
+
+class AidlSensorHalWrapper : public ISensorHalWrapper {
+public:
+    AidlSensorHalWrapper();
+
+    ~AidlSensorHalWrapper() override {
+        if (mEventQueueFlag != nullptr) {
+            ::android::hardware::EventFlag::deleteEventFlag(&mEventQueueFlag);
+            mEventQueueFlag = nullptr;
+        }
+        if (mWakeLockQueueFlag != nullptr) {
+            ::android::hardware::EventFlag::deleteEventFlag(&mWakeLockQueueFlag);
+            mWakeLockQueueFlag = nullptr;
+        }
+    }
+
+    virtual bool connect(SensorDeviceCallback *callback) override;
+
+    virtual void prepareForReconnect() override;
+
+    virtual bool supportsPolling() override;
+
+    virtual bool supportsMessageQueues() override;
+
+    virtual ssize_t poll(sensors_event_t *buffer, size_t count) override;
+
+    virtual ssize_t pollFmq(sensors_event_t *buffer, size_t count) override;
+
+    virtual std::vector<sensor_t> getSensorsList() override;
+
+    virtual status_t setOperationMode(SensorService::Mode mode) override;
+
+    virtual status_t activate(int32_t sensorHandle, bool enabled) override;
+
+    virtual status_t batch(int32_t sensorHandle, int64_t samplingPeriodNs,
+                           int64_t maxReportLatencyNs) override;
+
+    virtual status_t flush(int32_t sensorHandle) override;
+
+    virtual status_t injectSensorData(const sensors_event_t *event) override;
+
+    virtual status_t registerDirectChannel(const sensors_direct_mem_t *memory,
+                                           int32_t *channelHandle) override;
+
+    virtual status_t unregisterDirectChannel(int32_t channelHandle) override;
+
+    virtual status_t configureDirectChannel(int32_t sensorHandle, int32_t channelHandle,
+                                            const struct sensors_direct_cfg_t *config) override;
+
+    virtual void writeWakeLockHandled(uint32_t count) override;
+
+private:
+    std::shared_ptr<aidl::android::hardware::sensors::ISensors> mSensors;
+    std::shared_ptr<::aidl::android::hardware::sensors::ISensorsCallback> mCallback;
+    std::unique_ptr<::android::AidlMessageQueue<::aidl::android::hardware::sensors::Event,
+                                                SynchronizedReadWrite>>
+            mEventQueue;
+    std::unique_ptr<::android::AidlMessageQueue<int, SynchronizedReadWrite>> mWakeLockQueue;
+    ::android::hardware::EventFlag *mEventQueueFlag;
+    ::android::hardware::EventFlag *mWakeLockQueueFlag;
+    SensorDeviceCallback *mSensorDeviceCallback;
+    std::array<::aidl::android::hardware::sensors::Event,
+               ::android::SensorEventQueue::MAX_RECEIVE_BUFFER_EVENT_COUNT>
+            mEventBuffer;
+
+    ndk::ScopedAIBinder_DeathRecipient mDeathRecipient;
+};
+
+} // namespace android
+
+#endif // ANDROID_AIDL_SENSOR_HAL_WRAPPER_H
diff --git a/services/sensorservice/Android.bp b/services/sensorservice/Android.bp
index e6b118b..3c4f8d9 100644
--- a/services/sensorservice/Android.bp
+++ b/services/sensorservice/Android.bp
@@ -11,10 +11,13 @@
     name: "libsensorservice",
 
     srcs: [
+        "AidlSensorHalWrapper.cpp",
         "BatteryService.cpp",
         "CorrectedGyroSensor.cpp",
         "Fusion.cpp",
         "GravitySensor.cpp",
+        "HidlSensorHalWrapper.cpp",
+        "LimitedAxesImuSensor.cpp",
         "LinearAccelerationSensor.cpp",
         "OrientationSensor.cpp",
         "RecentEventLogger.cpp",
@@ -60,14 +63,19 @@
         "libbase",
         "libhidlbase",
         "libfmq",
+        "libbinder_ndk",
         "packagemanager_aidl-cpp",
         "android.hardware.sensors@1.0",
         "android.hardware.sensors@2.0",
         "android.hardware.sensors@2.1",
+        "android.hardware.common-V2-ndk",
+        "android.hardware.common.fmq-V1-ndk",
     ],
 
     static_libs: [
+        "libaidlcommonsupport",
         "android.hardware.sensors@1.0-convert",
+        "android.hardware.sensors-V1-ndk",
     ],
 
     generated_headers: ["framework-cppstream-protos"],
diff --git a/services/sensorservice/HidlSensorHalWrapper.cpp b/services/sensorservice/HidlSensorHalWrapper.cpp
new file mode 100644
index 0000000..4c64e59
--- /dev/null
+++ b/services/sensorservice/HidlSensorHalWrapper.cpp
@@ -0,0 +1,515 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "HidlSensorHalWrapper.h"
+#include "android/hardware/sensors/2.0/types.h"
+#include "android/hardware/sensors/2.1/ISensorsCallback.h"
+#include "android/hardware/sensors/2.1/types.h"
+#include "convertV2_1.h"
+
+#include <android-base/logging.h>
+
+using android::hardware::hidl_vec;
+using android::hardware::sensors::V1_0::RateLevel;
+using android::hardware::sensors::V1_0::Result;
+using android::hardware::sensors::V1_0::SharedMemFormat;
+using android::hardware::sensors::V1_0::SharedMemInfo;
+using android::hardware::sensors::V1_0::SharedMemType;
+using android::hardware::sensors::V2_0::EventQueueFlagBits;
+using android::hardware::sensors::V2_0::WakeLockQueueFlagBits;
+using android::hardware::sensors::V2_1::Event;
+using android::hardware::sensors::V2_1::ISensorsCallback;
+using android::hardware::sensors::V2_1::implementation::convertFromSensorEvent;
+using android::hardware::sensors::V2_1::implementation::convertToNewEvents;
+using android::hardware::sensors::V2_1::implementation::convertToNewSensorInfos;
+using android::hardware::sensors::V2_1::implementation::convertToSensor;
+using android::hardware::sensors::V2_1::implementation::ISensorsWrapperV1_0;
+using android::hardware::sensors::V2_1::implementation::ISensorsWrapperV2_0;
+using android::hardware::sensors::V2_1::implementation::ISensorsWrapperV2_1;
+
+namespace android {
+
+namespace {
+
+status_t statusFromResult(Result result) {
+    switch (result) {
+        case Result::OK:
+            return OK;
+        case Result::BAD_VALUE:
+            return BAD_VALUE;
+        case Result::PERMISSION_DENIED:
+            return PERMISSION_DENIED;
+        case Result::INVALID_OPERATION:
+            return INVALID_OPERATION;
+        case Result::NO_MEMORY:
+            return NO_MEMORY;
+    }
+}
+
+template <typename EnumType>
+constexpr typename std::underlying_type<EnumType>::type asBaseType(EnumType value) {
+    return static_cast<typename std::underlying_type<EnumType>::type>(value);
+}
+
+enum EventQueueFlagBitsInternal : uint32_t {
+    INTERNAL_WAKE = 1 << 16,
+};
+
+} // anonymous namespace
+
+void SensorsHalDeathReceiver::serviceDied(
+        uint64_t /* cookie */, const wp<::android::hidl::base::V1_0::IBase>& /* service */) {
+    ALOGW("Sensors HAL died, attempting to reconnect.");
+    mHidlSensorHalWrapper->prepareForReconnect();
+}
+
+struct HidlSensorsCallback : public ISensorsCallback {
+    using Result = ::android::hardware::sensors::V1_0::Result;
+    using SensorInfo = ::android::hardware::sensors::V2_1::SensorInfo;
+
+    HidlSensorsCallback(ISensorHalWrapper::SensorDeviceCallback* sensorDeviceCallback) {
+        mSensorDeviceCallback = sensorDeviceCallback;
+    }
+
+    Return<void> onDynamicSensorsConnected_2_1(
+            const hidl_vec<SensorInfo>& dynamicSensorsAdded) override {
+        std::vector<sensor_t> sensors;
+        for (const android::hardware::sensors::V2_1::SensorInfo& info : dynamicSensorsAdded) {
+            sensor_t sensor;
+            convertToSensor(info, &sensor);
+            sensors.push_back(sensor);
+        }
+
+        mSensorDeviceCallback->onDynamicSensorsConnected(sensors);
+        return Return<void>();
+    }
+
+    Return<void> onDynamicSensorsConnected(
+            const hidl_vec<android::hardware::sensors::V1_0::SensorInfo>& dynamicSensorsAdded)
+            override {
+        return onDynamicSensorsConnected_2_1(convertToNewSensorInfos(dynamicSensorsAdded));
+    }
+
+    Return<void> onDynamicSensorsDisconnected(
+            const hidl_vec<int32_t>& dynamicSensorHandlesRemoved) override {
+        mSensorDeviceCallback->onDynamicSensorsDisconnected(dynamicSensorHandlesRemoved);
+        return Return<void>();
+    }
+
+private:
+    ISensorHalWrapper::SensorDeviceCallback* mSensorDeviceCallback;
+};
+
+bool HidlSensorHalWrapper::supportsPolling() {
+    return mSensors->supportsPolling();
+}
+
+bool HidlSensorHalWrapper::supportsMessageQueues() {
+    return mSensors->supportsMessageQueues();
+}
+
+bool HidlSensorHalWrapper::connect(SensorDeviceCallback* callback) {
+    mSensorDeviceCallback = callback;
+    bool ret = connectHidlService();
+    if (mEventQueueFlag != nullptr) {
+        mEventQueueFlag->wake(asBaseType(INTERNAL_WAKE));
+    }
+    return ret;
+}
+
+void HidlSensorHalWrapper::prepareForReconnect() {
+    mReconnecting = true;
+    if (mEventQueueFlag != nullptr) {
+        mEventQueueFlag->wake(asBaseType(INTERNAL_WAKE));
+    }
+}
+
+ssize_t HidlSensorHalWrapper::poll(sensors_event_t* buffer, size_t count) {
+    ssize_t err;
+    int numHidlTransportErrors = 0;
+    bool hidlTransportError = false;
+
+    do {
+        auto ret = mSensors->poll(count,
+                                  [&](auto result, const auto& events,
+                                      const auto& dynamicSensorsAdded) {
+                                      if (result == Result::OK) {
+                                          convertToSensorEvents(convertToNewEvents(events),
+                                                                convertToNewSensorInfos(
+                                                                        dynamicSensorsAdded),
+                                                                buffer);
+                                          err = (ssize_t)events.size();
+                                      } else {
+                                          err = statusFromResult(result);
+                                      }
+                                  });
+
+        if (ret.isOk()) {
+            hidlTransportError = false;
+        } else {
+            hidlTransportError = true;
+            numHidlTransportErrors++;
+            if (numHidlTransportErrors > 50) {
+                // Log error and bail
+                ALOGE("Max Hidl transport errors this cycle : %d", numHidlTransportErrors);
+                handleHidlDeath(ret.description());
+            } else {
+                std::this_thread::sleep_for(std::chrono::milliseconds(10));
+            }
+        }
+    } while (hidlTransportError);
+
+    if (numHidlTransportErrors > 0) {
+        ALOGE("Saw %d Hidl transport failures", numHidlTransportErrors);
+        HidlTransportErrorLog errLog(time(nullptr), numHidlTransportErrors);
+        mHidlTransportErrors.add(errLog);
+        mTotalHidlTransportErrors++;
+    }
+
+    return err;
+}
+
+ssize_t HidlSensorHalWrapper::pollFmq(sensors_event_t* buffer, size_t maxNumEventsToRead) {
+    ssize_t eventsRead = 0;
+    size_t availableEvents = mSensors->getEventQueue()->availableToRead();
+
+    if (availableEvents == 0) {
+        uint32_t eventFlagState = 0;
+
+        // Wait for events to become available. This is necessary so that the Event FMQ's read() is
+        // able to be called with the correct number of events to read. If the specified number of
+        // events is not available, then read() would return no events, possibly introducing
+        // additional latency in delivering events to applications.
+        if (mEventQueueFlag != nullptr) {
+            mEventQueueFlag->wait(asBaseType(EventQueueFlagBits::READ_AND_PROCESS) |
+                                          asBaseType(INTERNAL_WAKE),
+                                  &eventFlagState);
+        }
+        availableEvents = mSensors->getEventQueue()->availableToRead();
+
+        if ((eventFlagState & asBaseType(INTERNAL_WAKE)) && mReconnecting) {
+            ALOGD("Event FMQ internal wake, returning from poll with no events");
+            return DEAD_OBJECT;
+        }
+    }
+
+    size_t eventsToRead = std::min({availableEvents, maxNumEventsToRead, mEventBuffer.size()});
+    if (eventsToRead > 0) {
+        if (mSensors->getEventQueue()->read(mEventBuffer.data(), eventsToRead)) {
+            // Notify the Sensors HAL that sensor events have been read. This is required to support
+            // the use of writeBlocking by the Sensors HAL.
+            if (mEventQueueFlag != nullptr) {
+                mEventQueueFlag->wake(asBaseType(EventQueueFlagBits::EVENTS_READ));
+            }
+
+            for (size_t i = 0; i < eventsToRead; i++) {
+                convertToSensorEvent(mEventBuffer[i], &buffer[i]);
+            }
+            eventsRead = eventsToRead;
+        } else {
+            ALOGW("Failed to read %zu events, currently %zu events available", eventsToRead,
+                  availableEvents);
+        }
+    }
+
+    return eventsRead;
+}
+
+std::vector<sensor_t> HidlSensorHalWrapper::getSensorsList() {
+    std::vector<sensor_t> sensorsFound;
+    if (mSensors != nullptr) {
+        checkReturn(mSensors->getSensorsList([&](const auto& list) {
+            for (size_t i = 0; i < list.size(); i++) {
+                sensor_t sensor;
+                convertToSensor(list[i], &sensor);
+                sensorsFound.push_back(sensor);
+
+                // Only disable all sensors on HAL 1.0 since HAL 2.0
+                // handles this in its initialize method
+                if (!mSensors->supportsMessageQueues()) {
+                    checkReturn(mSensors->activate(list[i].sensorHandle, 0 /* enabled */));
+                }
+            }
+        }));
+    }
+
+    return sensorsFound;
+}
+
+status_t HidlSensorHalWrapper::setOperationMode(SensorService::Mode mode) {
+    if (mSensors == nullptr) return NO_INIT;
+    return checkReturnAndGetStatus(
+            mSensors->setOperationMode(static_cast<hardware::sensors::V1_0::OperationMode>(mode)));
+}
+
+status_t HidlSensorHalWrapper::activate(int32_t sensorHandle, bool enabled) {
+    if (mSensors == nullptr) return NO_INIT;
+    return checkReturnAndGetStatus(mSensors->activate(sensorHandle, enabled));
+}
+
+status_t HidlSensorHalWrapper::batch(int32_t sensorHandle, int64_t samplingPeriodNs,
+                                     int64_t maxReportLatencyNs) {
+    if (mSensors == nullptr) return NO_INIT;
+    return checkReturnAndGetStatus(
+            mSensors->batch(sensorHandle, samplingPeriodNs, maxReportLatencyNs));
+}
+
+status_t HidlSensorHalWrapper::flush(int32_t sensorHandle) {
+    if (mSensors == nullptr) return NO_INIT;
+    return checkReturnAndGetStatus(mSensors->flush(sensorHandle));
+}
+
+status_t HidlSensorHalWrapper::injectSensorData(const sensors_event_t* event) {
+    if (mSensors == nullptr) return NO_INIT;
+
+    Event ev;
+    convertFromSensorEvent(*event, &ev);
+    return checkReturnAndGetStatus(mSensors->injectSensorData(ev));
+}
+
+status_t HidlSensorHalWrapper::registerDirectChannel(const sensors_direct_mem_t* memory,
+                                                     int32_t* /*channelHandle*/) {
+    if (mSensors == nullptr) return NO_INIT;
+
+    SharedMemType type;
+    switch (memory->type) {
+        case SENSOR_DIRECT_MEM_TYPE_ASHMEM:
+            type = SharedMemType::ASHMEM;
+            break;
+        case SENSOR_DIRECT_MEM_TYPE_GRALLOC:
+            type = SharedMemType::GRALLOC;
+            break;
+        default:
+            return BAD_VALUE;
+    }
+
+    SharedMemFormat format;
+    if (memory->format != SENSOR_DIRECT_FMT_SENSORS_EVENT) {
+        return BAD_VALUE;
+    }
+    format = SharedMemFormat::SENSORS_EVENT;
+
+    SharedMemInfo mem = {
+            .type = type,
+            .format = format,
+            .size = static_cast<uint32_t>(memory->size),
+            .memoryHandle = memory->handle,
+    };
+
+    status_t ret;
+    checkReturn(mSensors->registerDirectChannel(mem, [&ret](auto result, auto channelHandle) {
+        if (result == Result::OK) {
+            ret = channelHandle;
+        } else {
+            ret = statusFromResult(result);
+        }
+    }));
+    return ret;
+}
+
+status_t HidlSensorHalWrapper::unregisterDirectChannel(int32_t channelHandle) {
+    if (mSensors == nullptr) return NO_INIT;
+    return checkReturnAndGetStatus(mSensors->unregisterDirectChannel(channelHandle));
+}
+
+status_t HidlSensorHalWrapper::configureDirectChannel(int32_t sensorHandle, int32_t channelHandle,
+                                                      const struct sensors_direct_cfg_t* config) {
+    if (mSensors == nullptr) return NO_INIT;
+
+    RateLevel rate;
+    switch (config->rate_level) {
+        case SENSOR_DIRECT_RATE_STOP:
+            rate = RateLevel::STOP;
+            break;
+        case SENSOR_DIRECT_RATE_NORMAL:
+            rate = RateLevel::NORMAL;
+            break;
+        case SENSOR_DIRECT_RATE_FAST:
+            rate = RateLevel::FAST;
+            break;
+        case SENSOR_DIRECT_RATE_VERY_FAST:
+            rate = RateLevel::VERY_FAST;
+            break;
+        default:
+            return BAD_VALUE;
+    }
+
+    status_t ret;
+    checkReturn(mSensors->configDirectReport(sensorHandle, channelHandle, rate,
+                                             [&ret, rate](auto result, auto token) {
+                                                 if (rate == RateLevel::STOP) {
+                                                     ret = statusFromResult(result);
+                                                 } else {
+                                                     if (result == Result::OK) {
+                                                         ret = token;
+                                                     } else {
+                                                         ret = statusFromResult(result);
+                                                     }
+                                                 }
+                                             }));
+
+    return ret;
+}
+
+void HidlSensorHalWrapper::writeWakeLockHandled(uint32_t count) {
+    if (mWakeLockQueue->write(&count)) {
+        mWakeLockQueueFlag->wake(asBaseType(WakeLockQueueFlagBits::DATA_WRITTEN));
+    } else {
+        ALOGW("Failed to write wake lock handled");
+    }
+}
+
+status_t HidlSensorHalWrapper::checkReturnAndGetStatus(const hardware::Return<Result>& ret) {
+    checkReturn(ret);
+    return (!ret.isOk()) ? DEAD_OBJECT : statusFromResult(ret);
+}
+
+void HidlSensorHalWrapper::handleHidlDeath(const std::string& detail) {
+    if (!mSensors->supportsMessageQueues()) {
+        // restart is the only option at present.
+        LOG_ALWAYS_FATAL("Abort due to ISensors hidl service failure, detail: %s.", detail.c_str());
+    } else {
+        ALOGD("ISensors HAL died, death recipient will attempt reconnect");
+    }
+}
+
+bool HidlSensorHalWrapper::connectHidlService() {
+    HalConnectionStatus status = connectHidlServiceV2_1();
+    if (status == HalConnectionStatus::DOES_NOT_EXIST) {
+        status = connectHidlServiceV2_0();
+    }
+
+    if (status == HalConnectionStatus::DOES_NOT_EXIST) {
+        status = connectHidlServiceV1_0();
+    }
+    return (status == HalConnectionStatus::CONNECTED);
+}
+
+ISensorHalWrapper::HalConnectionStatus HidlSensorHalWrapper::connectHidlServiceV1_0() {
+    // SensorDevice will wait for HAL service to start if HAL is declared in device manifest.
+    size_t retry = 10;
+    HalConnectionStatus connectionStatus = HalConnectionStatus::UNKNOWN;
+
+    while (retry-- > 0) {
+        sp<android::hardware::sensors::V1_0::ISensors> sensors =
+                android::hardware::sensors::V1_0::ISensors::getService();
+        if (sensors == nullptr) {
+            // no sensor hidl service found
+            connectionStatus = HalConnectionStatus::DOES_NOT_EXIST;
+            break;
+        }
+
+        mSensors = new ISensorsWrapperV1_0(sensors);
+        mRestartWaiter->reset();
+        // Poke ISensor service. If it has lingering connection from previous generation of
+        // system server, it will kill itself. There is no intention to handle the poll result,
+        // which will be done since the size is 0.
+        if (mSensors->poll(0, [](auto, const auto&, const auto&) {}).isOk()) {
+            // ok to continue
+            connectionStatus = HalConnectionStatus::CONNECTED;
+            break;
+        }
+
+        // hidl service is restarting, pointer is invalid.
+        mSensors = nullptr;
+        connectionStatus = HalConnectionStatus::FAILED_TO_CONNECT;
+        ALOGI("%s unsuccessful, remaining retry %zu.", __FUNCTION__, retry);
+        mRestartWaiter->wait();
+    }
+
+    return connectionStatus;
+}
+
+ISensorHalWrapper::HalConnectionStatus HidlSensorHalWrapper::connectHidlServiceV2_0() {
+    HalConnectionStatus connectionStatus = HalConnectionStatus::UNKNOWN;
+    sp<android::hardware::sensors::V2_0::ISensors> sensors =
+            android::hardware::sensors::V2_0::ISensors::getService();
+
+    if (sensors == nullptr) {
+        connectionStatus = HalConnectionStatus::DOES_NOT_EXIST;
+    } else {
+        mSensors = new ISensorsWrapperV2_0(sensors);
+        connectionStatus = initializeHidlServiceV2_X();
+    }
+
+    return connectionStatus;
+}
+
+ISensorHalWrapper::HalConnectionStatus HidlSensorHalWrapper::connectHidlServiceV2_1() {
+    HalConnectionStatus connectionStatus = HalConnectionStatus::UNKNOWN;
+    sp<android::hardware::sensors::V2_1::ISensors> sensors =
+            android::hardware::sensors::V2_1::ISensors::getService();
+
+    if (sensors == nullptr) {
+        connectionStatus = HalConnectionStatus::DOES_NOT_EXIST;
+    } else {
+        mSensors = new ISensorsWrapperV2_1(sensors);
+        connectionStatus = initializeHidlServiceV2_X();
+    }
+
+    return connectionStatus;
+}
+
+ISensorHalWrapper::HalConnectionStatus HidlSensorHalWrapper::initializeHidlServiceV2_X() {
+    HalConnectionStatus connectionStatus = HalConnectionStatus::UNKNOWN;
+
+    mWakeLockQueue =
+            std::make_unique<WakeLockQueue>(SensorEventQueue::MAX_RECEIVE_BUFFER_EVENT_COUNT,
+                                            true /* configureEventFlagWord */);
+
+    hardware::EventFlag::deleteEventFlag(&mEventQueueFlag);
+    hardware::EventFlag::createEventFlag(mSensors->getEventQueue()->getEventFlagWord(),
+                                         &mEventQueueFlag);
+
+    hardware::EventFlag::deleteEventFlag(&mWakeLockQueueFlag);
+    hardware::EventFlag::createEventFlag(mWakeLockQueue->getEventFlagWord(), &mWakeLockQueueFlag);
+
+    CHECK(mSensors != nullptr && mWakeLockQueue != nullptr && mEventQueueFlag != nullptr &&
+          mWakeLockQueueFlag != nullptr);
+
+    mCallback = sp<HidlSensorsCallback>::make(mSensorDeviceCallback);
+    status_t status =
+            checkReturnAndGetStatus(mSensors->initialize(*mWakeLockQueue->getDesc(), mCallback));
+
+    if (status != NO_ERROR) {
+        connectionStatus = HalConnectionStatus::FAILED_TO_CONNECT;
+        ALOGE("Failed to initialize Sensors HAL (%s)", strerror(-status));
+    } else {
+        connectionStatus = HalConnectionStatus::CONNECTED;
+        mSensorsHalDeathReceiver = new SensorsHalDeathReceiver(this);
+        mSensors->linkToDeath(mSensorsHalDeathReceiver, 0 /* cookie */);
+    }
+
+    return connectionStatus;
+}
+
+void HidlSensorHalWrapper::convertToSensorEvent(const Event& src, sensors_event_t* dst) {
+    android::hardware::sensors::V2_1::implementation::convertToSensorEvent(src, dst);
+}
+
+void HidlSensorHalWrapper::convertToSensorEvents(const hidl_vec<Event>& src,
+                                                 const hidl_vec<SensorInfo>& dynamicSensorsAdded,
+                                                 sensors_event_t* dst) {
+    if (dynamicSensorsAdded.size() > 0 && mCallback != nullptr) {
+        mCallback->onDynamicSensorsConnected_2_1(dynamicSensorsAdded);
+    }
+
+    for (size_t i = 0; i < src.size(); ++i) {
+        convertToSensorEvent(src[i], &dst[i]);
+    }
+}
+
+} // namespace android
diff --git a/services/sensorservice/HidlSensorHalWrapper.h b/services/sensorservice/HidlSensorHalWrapper.h
new file mode 100644
index 0000000..71c3512
--- /dev/null
+++ b/services/sensorservice/HidlSensorHalWrapper.h
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_HIDL_SENSOR_HAL_WRAPPER_H
+#define ANDROID_HIDL_SENSOR_HAL_WRAPPER_H
+
+#include <sensor/SensorEventQueue.h>
+#include <utils/Singleton.h>
+
+#include "ISensorHalWrapper.h"
+
+#include "ISensorsWrapper.h"
+#include "SensorDeviceUtils.h"
+
+namespace android {
+
+using android::hardware::sensors::V1_0::Result;
+using android::hardware::sensors::V2_1::Event;
+using android::hardware::sensors::V2_1::SensorInfo;
+
+class HidlTransportErrorLog {
+public:
+    HidlTransportErrorLog() {
+        mTs = 0;
+        mCount = 0;
+    }
+
+    HidlTransportErrorLog(time_t ts, int count) {
+        mTs = ts;
+        mCount = count;
+    }
+
+    String8 toString() const {
+        String8 result;
+        struct tm* timeInfo = localtime(&mTs);
+        result.appendFormat("%02d:%02d:%02d :: %d", timeInfo->tm_hour, timeInfo->tm_min,
+                            timeInfo->tm_sec, mCount);
+        return result;
+    }
+
+private:
+    time_t mTs; // timestamp of the error
+    int mCount; // number of transport errors observed
+};
+
+class SensorsHalDeathReceiver : public android::hardware::hidl_death_recipient {
+public:
+    SensorsHalDeathReceiver(ISensorHalWrapper* wrapper) : mHidlSensorHalWrapper(wrapper) {}
+
+    virtual void serviceDied(uint64_t cookie,
+                             const wp<::android::hidl::base::V1_0::IBase>& service) override;
+
+private:
+    ISensorHalWrapper* mHidlSensorHalWrapper;
+};
+
+class HidlSensorHalWrapper : public ISensorHalWrapper {
+public:
+    HidlSensorHalWrapper()
+          : mHidlTransportErrors(20),
+            mTotalHidlTransportErrors(0),
+            mRestartWaiter(new SensorDeviceUtils::HidlServiceRegistrationWaiter()),
+            mEventQueueFlag(nullptr),
+            mWakeLockQueueFlag(nullptr) {}
+
+    ~HidlSensorHalWrapper() override {
+        if (mEventQueueFlag != nullptr) {
+            hardware::EventFlag::deleteEventFlag(&mEventQueueFlag);
+            mEventQueueFlag = nullptr;
+        }
+        if (mWakeLockQueueFlag != nullptr) {
+            hardware::EventFlag::deleteEventFlag(&mWakeLockQueueFlag);
+            mWakeLockQueueFlag = nullptr;
+        }
+    }
+    virtual bool connect(SensorDeviceCallback* callback) override;
+
+    virtual void prepareForReconnect() override;
+
+    virtual bool supportsPolling() override;
+
+    virtual bool supportsMessageQueues() override;
+
+    virtual ssize_t poll(sensors_event_t* buffer, size_t count) override;
+
+    virtual ssize_t pollFmq(sensors_event_t* buffer, size_t count) override;
+
+    virtual std::vector<sensor_t> getSensorsList() override;
+
+    virtual status_t setOperationMode(SensorService::Mode mode) override;
+
+    virtual status_t activate(int32_t sensorHandle, bool enabled) override;
+
+    virtual status_t batch(int32_t sensorHandle, int64_t samplingPeriodNs,
+                           int64_t maxReportLatencyNs) override;
+
+    virtual status_t flush(int32_t sensorHandle) override;
+
+    virtual status_t injectSensorData(const sensors_event_t* event) override;
+
+    virtual status_t registerDirectChannel(const sensors_direct_mem_t* memory,
+                                           int32_t* channelHandle) override;
+
+    virtual status_t unregisterDirectChannel(int32_t channelHandle) override;
+
+    virtual status_t configureDirectChannel(int32_t sensorHandle, int32_t channelHandle,
+                                            const struct sensors_direct_cfg_t* config) override;
+
+    virtual void writeWakeLockHandled(uint32_t count) override;
+
+private:
+    sp<::android::hardware::sensors::V2_1::implementation::ISensorsWrapperBase> mSensors;
+    sp<::android::hardware::sensors::V2_1::ISensorsCallback> mCallback;
+
+    // Keep track of any hidl transport failures
+    SensorServiceUtil::RingBuffer<HidlTransportErrorLog> mHidlTransportErrors;
+    int mTotalHidlTransportErrors;
+
+    SensorDeviceCallback* mSensorDeviceCallback = nullptr;
+
+    // TODO(b/67425500): remove waiter after bug is resolved.
+    sp<SensorDeviceUtils::HidlServiceRegistrationWaiter> mRestartWaiter;
+
+    template <typename T>
+    void checkReturn(const hardware::Return<T>& ret) {
+        if (!ret.isOk()) {
+            handleHidlDeath(ret.description());
+        }
+    }
+
+    status_t checkReturnAndGetStatus(const hardware::Return<Result>& ret);
+
+    void handleHidlDeath(const std::string& detail);
+
+    void convertToSensorEvent(const Event& src, sensors_event_t* dst);
+
+    void convertToSensorEvents(const hardware::hidl_vec<Event>& src,
+                               const hardware::hidl_vec<SensorInfo>& dynamicSensorsAdded,
+                               sensors_event_t* dst);
+
+    bool connectHidlService();
+
+    HalConnectionStatus connectHidlServiceV1_0();
+    HalConnectionStatus connectHidlServiceV2_0();
+    HalConnectionStatus connectHidlServiceV2_1();
+    HalConnectionStatus initializeHidlServiceV2_X();
+
+    typedef hardware::MessageQueue<uint32_t, hardware::kSynchronizedReadWrite> WakeLockQueue;
+    std::unique_ptr<WakeLockQueue> mWakeLockQueue;
+
+    hardware::EventFlag* mEventQueueFlag;
+    hardware::EventFlag* mWakeLockQueueFlag;
+
+    std::array<Event, SensorEventQueue::MAX_RECEIVE_BUFFER_EVENT_COUNT> mEventBuffer;
+
+    sp<SensorsHalDeathReceiver> mSensorsHalDeathReceiver;
+};
+
+} // namespace android
+
+#endif // ANDROID_HIDL_SENSOR_HAL_WRAPPER_H
diff --git a/services/sensorservice/ISensorHalWrapper.h b/services/sensorservice/ISensorHalWrapper.h
new file mode 100644
index 0000000..3d33540
--- /dev/null
+++ b/services/sensorservice/ISensorHalWrapper.h
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_ISENSOR_HAL_WRAPPER_H
+#define ANDROID_ISENSOR_HAL_WRAPPER_H
+
+#include <hardware/sensors.h>
+#include <stdint.h>
+#include <sys/types.h>
+
+#include "SensorService.h"
+
+namespace android {
+
+/**
+ * A wrapper for various types of HAL implementation, e.g. to distinguish HIDL and AIDL versions.
+ */
+class ISensorHalWrapper {
+public:
+    class SensorDeviceCallback {
+    public:
+        virtual void onDynamicSensorsConnected(
+                const std::vector<sensor_t> &dynamicSensorsAdded) = 0;
+
+        virtual void onDynamicSensorsDisconnected(
+                const std::vector<int32_t> &dynamicSensorHandlesRemoved) = 0;
+
+        virtual ~SensorDeviceCallback(){};
+    };
+
+    enum HalConnectionStatus {
+        CONNECTED,         // Successfully connected to the HAL
+        DOES_NOT_EXIST,    // Could not find the HAL
+        FAILED_TO_CONNECT, // Found the HAL but failed to connect/initialize
+        UNKNOWN,
+    };
+
+    virtual ~ISensorHalWrapper(){};
+
+    /**
+     * Connects to the underlying sensors HAL. This should also be used for any reconnections
+     * due to HAL resets.
+     */
+    virtual bool connect(SensorDeviceCallback *callback) = 0;
+
+    virtual void prepareForReconnect() = 0;
+
+    virtual bool supportsPolling() = 0;
+
+    virtual bool supportsMessageQueues() = 0;
+
+    /**
+     * Polls for available sensor events. This could be using the traditional sensors
+     * polling or from a FMQ.
+     */
+    virtual ssize_t poll(sensors_event_t *buffer, size_t count) = 0;
+
+    virtual ssize_t pollFmq(sensors_event_t *buffer, size_t maxNumEventsToRead) = 0;
+
+    /**
+     * The below functions directly mirrors the sensors HAL definitions.
+     */
+    virtual std::vector<sensor_t> getSensorsList() = 0;
+
+    virtual status_t setOperationMode(SensorService::Mode mode) = 0;
+
+    virtual status_t activate(int32_t sensorHandle, bool enabled) = 0;
+
+    virtual status_t batch(int32_t sensorHandle, int64_t samplingPeriodNs,
+                           int64_t maxReportLatencyNs) = 0;
+
+    virtual status_t flush(int32_t sensorHandle) = 0;
+
+    virtual status_t injectSensorData(const sensors_event_t *event) = 0;
+
+    virtual status_t registerDirectChannel(const sensors_direct_mem_t *memory,
+                                           int32_t *channelHandle) = 0;
+
+    virtual status_t unregisterDirectChannel(int32_t channelHandle) = 0;
+
+    virtual status_t configureDirectChannel(int32_t sensorHandle, int32_t channelHandle,
+                                            const struct sensors_direct_cfg_t *config) = 0;
+
+    virtual void writeWakeLockHandled(uint32_t count) = 0;
+
+    std::atomic_bool mReconnecting = false;
+};
+
+} // namespace android
+
+#endif // ANDROID_ISENSOR_HAL_WRAPPER_H
diff --git a/services/sensorservice/LimitedAxesImuSensor.cpp b/services/sensorservice/LimitedAxesImuSensor.cpp
new file mode 100644
index 0000000..2f91479
--- /dev/null
+++ b/services/sensorservice/LimitedAxesImuSensor.cpp
@@ -0,0 +1,134 @@
+/*
+ * 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 <math.h>
+#include <stdint.h>
+#include <sys/types.h>
+
+#include <utils/Errors.h>
+
+#include <hardware/sensors.h>
+
+#include "LimitedAxesImuSensor.h"
+#include "SensorDevice.h"
+#include "SensorFusion.h"
+#include "SensorServiceUtils.h"
+
+namespace android {
+
+namespace {
+const sensor_t DUMMY_SENSOR = {.name = "",
+                               .vendor = "",
+                               .stringType = "",
+                               .requiredPermission = ""};
+} // unnamed namespace
+
+LimitedAxesImuSensor::LimitedAxesImuSensor(sensor_t const* list, size_t count,
+                                           int32_t imu3dSensorType)
+      : BaseSensor(DUMMY_SENSOR) {
+    for (size_t i = 0; i < count; i++) {
+        if (list[i].type == imu3dSensorType) {
+            mImu3dSensor = Sensor(list + i);
+            break;
+        }
+    }
+
+    const int32_t imuLimitedAxesSensorType = convertImu3dToLimitedAxesSensorType(imu3dSensorType);
+
+    const sensor_t sensor = {
+            .name = convertLimitedAxesSensorTypeToName(imuLimitedAxesSensorType),
+            .vendor = "AOSP",
+            .version = 1,
+            .handle = convertLimitedAxesSensorTypeToHandle(imuLimitedAxesSensorType),
+            .type = imuLimitedAxesSensorType,
+            .maxRange = mImu3dSensor.getMaxValue(),
+            .resolution = mImu3dSensor.getResolution(),
+            .power = mImu3dSensor.getPowerUsage(),
+            .minDelay = mImu3dSensor.getMinDelay(),
+    };
+    mSensor = Sensor(&sensor);
+}
+
+bool LimitedAxesImuSensor::process(sensors_event_t* outEvent, const sensors_event_t& event) {
+    if (event.type == mImu3dSensor.getType()) {
+        *outEvent = event;
+        size_t imu3dDataSize = SensorServiceUtil::eventSizeBySensorType(mImu3dSensor.getType());
+        outEvent->data[0 + imu3dDataSize] = 1;
+        outEvent->data[1 + imu3dDataSize] = 1;
+        outEvent->data[2 + imu3dDataSize] = 1;
+        outEvent->sensor = mSensor.getHandle();
+        outEvent->type = mSensor.getType();
+        return true;
+    }
+    return false;
+}
+
+status_t LimitedAxesImuSensor::activate(void* ident, bool enabled) {
+    return mSensorDevice.activate(ident, mImu3dSensor.getHandle(), enabled);
+}
+
+status_t LimitedAxesImuSensor::setDelay(void* ident, int /*handle*/, int64_t ns) {
+    return mSensorDevice.setDelay(ident, mImu3dSensor.getHandle(), ns);
+}
+
+int32_t LimitedAxesImuSensor::convertImu3dToLimitedAxesSensorType(int32_t imu3dSensorType) {
+    switch (imu3dSensorType) {
+        case SENSOR_TYPE_ACCELEROMETER:
+            return SENSOR_TYPE_ACCELEROMETER_LIMITED_AXES;
+        case SENSOR_TYPE_GYROSCOPE:
+            return SENSOR_TYPE_GYROSCOPE_LIMITED_AXES;
+        case SENSOR_TYPE_ACCELEROMETER_UNCALIBRATED:
+            return SENSOR_TYPE_ACCELEROMETER_LIMITED_AXES_UNCALIBRATED;
+        case SENSOR_TYPE_GYROSCOPE_UNCALIBRATED:
+            return SENSOR_TYPE_GYROSCOPE_LIMITED_AXES_UNCALIBRATED;
+        default:
+            return 0;
+    }
+}
+
+int32_t LimitedAxesImuSensor::convertLimitedAxesSensorTypeToHandle(
+        int32_t imuLimitedAxesSensorType) {
+    switch (imuLimitedAxesSensorType) {
+        case SENSOR_TYPE_ACCELEROMETER_LIMITED_AXES:
+            return '_ala';
+        case SENSOR_TYPE_GYROSCOPE_LIMITED_AXES:
+            return '_gla';
+        case SENSOR_TYPE_ACCELEROMETER_LIMITED_AXES_UNCALIBRATED:
+            return '_alc';
+        case SENSOR_TYPE_GYROSCOPE_LIMITED_AXES_UNCALIBRATED:
+            return '_glc';
+        default:
+            return 0;
+    }
+}
+
+const char* LimitedAxesImuSensor::convertLimitedAxesSensorTypeToName(
+        int32_t imuLimitedAxesSensorType) {
+    switch (imuLimitedAxesSensorType) {
+        case SENSOR_TYPE_ACCELEROMETER_LIMITED_AXES:
+            return "Accelerometer Limited Axes Sensor";
+        case SENSOR_TYPE_GYROSCOPE_LIMITED_AXES:
+            return "Gyroscope Limited Axes Sensor";
+        case SENSOR_TYPE_ACCELEROMETER_LIMITED_AXES_UNCALIBRATED:
+            return "Accelerometer Limited Axes Uncalibrated Sensor";
+        case SENSOR_TYPE_GYROSCOPE_LIMITED_AXES_UNCALIBRATED:
+            return "Gyroscope Limited Axes Uncalibrated Sensor";
+        default:
+            return "";
+    }
+}
+
+}; // namespace android
diff --git a/services/sensorservice/LimitedAxesImuSensor.h b/services/sensorservice/LimitedAxesImuSensor.h
new file mode 100644
index 0000000..fd46a98
--- /dev/null
+++ b/services/sensorservice/LimitedAxesImuSensor.h
@@ -0,0 +1,46 @@
+/*
+ * 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 <stdint.h>
+#include <sys/types.h>
+
+#include <sensor/Sensor.h>
+
+#include "SensorInterface.h"
+
+namespace android {
+
+class SensorDevice;
+
+class LimitedAxesImuSensor : public BaseSensor {
+    Sensor mImu3dSensor;
+
+public:
+    LimitedAxesImuSensor(sensor_t const* list, size_t count, int32_t imuSensorType);
+    virtual bool process(sensors_event_t* outEvent, const sensors_event_t& event) override;
+    virtual status_t activate(void* ident, bool enabled) override;
+    virtual status_t setDelay(void* ident, int handle, int64_t ns) override;
+    virtual bool isVirtual() const override { return true; }
+
+private:
+    int32_t convertImu3dToLimitedAxesSensorType(int32_t imu3dSensorType);
+    int32_t convertLimitedAxesSensorTypeToHandle(int32_t imuLimitedAxesSensorType);
+    const char* convertLimitedAxesSensorTypeToName(int32_t imuLimitedAxesSensorType);
+};
+
+}; // namespace android
\ No newline at end of file
diff --git a/services/sensorservice/SensorDevice.cpp b/services/sensorservice/SensorDevice.cpp
index e4efde2..a0e30ac 100644
--- a/services/sensorservice/SensorDevice.cpp
+++ b/services/sensorservice/SensorDevice.cpp
@@ -17,38 +17,29 @@
 #include "SensorDevice.h"
 
 #include "android/hardware/sensors/2.0/types.h"
-#include "android/hardware/sensors/2.1/ISensorsCallback.h"
 #include "android/hardware/sensors/2.1/types.h"
 #include "convertV2_1.h"
 
+#include "AidlSensorHalWrapper.h"
+#include "HidlSensorHalWrapper.h"
+
 #include <android-base/logging.h>
 #include <android/util/ProtoOutputStream.h>
-#include <frameworks/base/core/proto/android/service/sensor_service.proto.h>
-#include <sensors/convert.h>
 #include <cutils/atomic.h>
+#include <frameworks/base/core/proto/android/service/sensor_service.proto.h>
+#include <hardware/sensors-base.h>
+#include <hardware/sensors.h>
+#include <sensors/convert.h>
 #include <utils/Errors.h>
 #include <utils/Singleton.h>
 
-#include <cstddef>
 #include <chrono>
 #include <cinttypes>
+#include <cstddef>
 #include <thread>
 
 using namespace android::hardware::sensors;
-using namespace android::hardware::sensors::V1_0;
-using namespace android::hardware::sensors::V1_0::implementation;
-using android::hardware::sensors::V2_0::EventQueueFlagBits;
-using android::hardware::sensors::V2_0::WakeLockQueueFlagBits;
-using android::hardware::sensors::V2_1::ISensorsCallback;
-using android::hardware::sensors::V2_1::implementation::convertToOldSensorInfo;
-using android::hardware::sensors::V2_1::implementation::convertToNewSensorInfos;
-using android::hardware::sensors::V2_1::implementation::convertToNewEvents;
-using android::hardware::sensors::V2_1::implementation::ISensorsWrapperV1_0;
-using android::hardware::sensors::V2_1::implementation::ISensorsWrapperV2_0;
-using android::hardware::sensors::V2_1::implementation::ISensorsWrapperV2_1;
-using android::hardware::hidl_vec;
 using android::hardware::Return;
-using android::SensorDeviceUtils::HidlServiceRegistrationWaiter;
 using android::util::ProtoOutputStream;
 
 namespace android {
@@ -58,22 +49,7 @@
 
 namespace {
 
-status_t statusFromResult(Result result) {
-    switch (result) {
-        case Result::OK:
-            return OK;
-        case Result::BAD_VALUE:
-            return BAD_VALUE;
-        case Result::PERMISSION_DENIED:
-            return PERMISSION_DENIED;
-        case Result::INVALID_OPERATION:
-            return INVALID_OPERATION;
-        case Result::NO_MEMORY:
-            return NO_MEMORY;
-    }
-}
-
-template<typename EnumType>
+template <typename EnumType>
 constexpr typename std::underlying_type<EnumType>::type asBaseType(EnumType value) {
     return static_cast<typename std::underlying_type<EnumType>::type>(value);
 }
@@ -81,244 +57,108 @@
 // Used internally by the framework to wake the Event FMQ. These values must start after
 // the last value of EventQueueFlagBits
 enum EventQueueFlagBitsInternal : uint32_t {
-    INTERNAL_WAKE =  1 << 16,
+    INTERNAL_WAKE = 1 << 16,
 };
 
-}  // anonymous namespace
-
-void SensorsHalDeathReceivier::serviceDied(
-        uint64_t /* cookie */,
-        const wp<::android::hidl::base::V1_0::IBase>& /* service */) {
-    ALOGW("Sensors HAL died, attempting to reconnect.");
-    SensorDevice::getInstance().prepareForReconnect();
-}
-
-struct SensorsCallback : public ISensorsCallback {
-    using Result = ::android::hardware::sensors::V1_0::Result;
-    using SensorInfo = ::android::hardware::sensors::V2_1::SensorInfo;
-
-    Return<void> onDynamicSensorsConnected_2_1(
-            const hidl_vec<SensorInfo> &dynamicSensorsAdded) override {
-        return SensorDevice::getInstance().onDynamicSensorsConnected(dynamicSensorsAdded);
-    }
-
-    Return<void> onDynamicSensorsConnected(
-            const hidl_vec<V1_0::SensorInfo> &dynamicSensorsAdded) override {
-        return SensorDevice::getInstance().onDynamicSensorsConnected(
-                convertToNewSensorInfos(dynamicSensorsAdded));
-    }
-
-    Return<void> onDynamicSensorsDisconnected(
-            const hidl_vec<int32_t> &dynamicSensorHandlesRemoved) override {
-        return SensorDevice::getInstance().onDynamicSensorsDisconnected(
-                dynamicSensorHandlesRemoved);
-    }
+enum DevicePrivateBase : int32_t {
+    DEVICE_PRIVATE_BASE = 65536,
 };
 
-SensorDevice::SensorDevice()
-        : mHidlTransportErrors(20),
-          mRestartWaiter(new HidlServiceRegistrationWaiter()),
-          mEventQueueFlag(nullptr),
-          mWakeLockQueueFlag(nullptr),
-          mReconnecting(false) {
-    if (!connectHidlService()) {
+} // anonymous namespace
+
+SensorDevice::SensorDevice() {
+    if (!connectHalService()) {
         return;
     }
 
     initializeSensorList();
 
-    mIsDirectReportSupported =
-            (checkReturnAndGetStatus(mSensors->unregisterDirectChannel(-1)) != INVALID_OPERATION);
+    mIsDirectReportSupported = (mHalWrapper->unregisterDirectChannel(-1) != INVALID_OPERATION);
 }
 
 void SensorDevice::initializeSensorList() {
-    checkReturn(mSensors->getSensorsList(
-            [&](const auto &list) {
-                const size_t count = list.size();
+    if (mHalWrapper == nullptr) {
+        return;
+    }
 
-                mActivationCount.setCapacity(count);
-                Info model;
-                for (size_t i=0 ; i < count; i++) {
-                    sensor_t sensor;
-                    convertToSensor(convertToOldSensorInfo(list[i]), &sensor);
+    auto list = mHalWrapper->getSensorsList();
+    const size_t count = list.size();
 
-                    if (sensor.type < static_cast<int>(SensorType::DEVICE_PRIVATE_BASE)) {
-                        sensor.resolution = SensorDeviceUtils::resolutionForSensor(sensor);
+    mActivationCount.setCapacity(count);
+    Info model;
+    for (size_t i = 0; i < count; i++) {
+        sensor_t sensor = list[i];
 
-                        // Some sensors don't have a default resolution and will be left at 0.
-                        // Don't crash in this case since CTS will verify that devices don't go to
-                        // production with a resolution of 0.
-                        if (sensor.resolution != 0) {
-                            float quantizedRange = sensor.maxRange;
-                            SensorDeviceUtils::quantizeValue(
-                                    &quantizedRange, sensor.resolution, /*factor=*/ 1);
-                            // Only rewrite maxRange if the requantization produced a "significant"
-                            // change, which is fairly arbitrarily defined as resolution / 8.
-                            // Smaller deltas are permitted, as they may simply be due to floating
-                            // point representation error, etc.
-                            if (fabsf(sensor.maxRange - quantizedRange) > sensor.resolution / 8) {
-                                ALOGW("%s's max range %.12f is not a multiple of the resolution "
-                                      "%.12f - updated to %.12f", sensor.name, sensor.maxRange,
-                                      sensor.resolution, quantizedRange);
-                                sensor.maxRange = quantizedRange;
-                            }
-                        } else {
-                            // Don't crash here or the device will go into a crashloop.
-                            ALOGW("%s should have a non-zero resolution", sensor.name);
-                        }
-                    }
+        if (sensor.type < DEVICE_PRIVATE_BASE) {
+            sensor.resolution = SensorDeviceUtils::resolutionForSensor(sensor);
 
-                    // Sanity check and clamp power if it is 0 (or close)
-                    constexpr float MIN_POWER_MA = 0.001; // 1 microAmp
-                    if (sensor.power < MIN_POWER_MA) {
-                        ALOGI("%s's reported power %f invalid, clamped to %f",
-                              sensor.name, sensor.power, MIN_POWER_MA);
-                        sensor.power = MIN_POWER_MA;
-                    }
-                    mSensorList.push_back(sensor);
-
-                    mActivationCount.add(list[i].sensorHandle, model);
-
-                    // Only disable all sensors on HAL 1.0 since HAL 2.0
-                    // handles this in its initialize method
-                    if (!mSensors->supportsMessageQueues()) {
-                        checkReturn(mSensors->activate(list[i].sensorHandle,
-                                    0 /* enabled */));
-                    }
+            // Some sensors don't have a default resolution and will be left at 0.
+            // Don't crash in this case since CTS will verify that devices don't go to
+            // production with a resolution of 0.
+            if (sensor.resolution != 0) {
+                float quantizedRange = sensor.maxRange;
+                SensorDeviceUtils::quantizeValue(&quantizedRange, sensor.resolution,
+                                                 /*factor=*/1);
+                // Only rewrite maxRange if the requantization produced a "significant"
+                // change, which is fairly arbitrarily defined as resolution / 8.
+                // Smaller deltas are permitted, as they may simply be due to floating
+                // point representation error, etc.
+                if (fabsf(sensor.maxRange - quantizedRange) > sensor.resolution / 8) {
+                    ALOGW("%s's max range %.12f is not a multiple of the resolution "
+                          "%.12f - updated to %.12f",
+                          sensor.name, sensor.maxRange, sensor.resolution, quantizedRange);
+                    sensor.maxRange = quantizedRange;
                 }
-            }));
-}
-
-SensorDevice::~SensorDevice() {
-    if (mEventQueueFlag != nullptr) {
-        hardware::EventFlag::deleteEventFlag(&mEventQueueFlag);
-        mEventQueueFlag = nullptr;
-    }
-
-    if (mWakeLockQueueFlag != nullptr) {
-        hardware::EventFlag::deleteEventFlag(&mWakeLockQueueFlag);
-        mWakeLockQueueFlag = nullptr;
-    }
-}
-
-bool SensorDevice::connectHidlService() {
-    HalConnectionStatus status = connectHidlServiceV2_1();
-    if (status == HalConnectionStatus::DOES_NOT_EXIST) {
-        status = connectHidlServiceV2_0();
-    }
-
-    if (status == HalConnectionStatus::DOES_NOT_EXIST) {
-        status = connectHidlServiceV1_0();
-    }
-    return (status == HalConnectionStatus::CONNECTED);
-}
-
-SensorDevice::HalConnectionStatus SensorDevice::connectHidlServiceV1_0() {
-    // SensorDevice will wait for HAL service to start if HAL is declared in device manifest.
-    size_t retry = 10;
-    HalConnectionStatus connectionStatus = HalConnectionStatus::UNKNOWN;
-
-    while (retry-- > 0) {
-        sp<V1_0::ISensors> sensors = V1_0::ISensors::getService();
-        if (sensors == nullptr) {
-            // no sensor hidl service found
-            connectionStatus = HalConnectionStatus::DOES_NOT_EXIST;
-            break;
+            } else {
+                // Don't crash here or the device will go into a crashloop.
+                ALOGW("%s should have a non-zero resolution", sensor.name);
+            }
         }
 
-        mSensors = new ISensorsWrapperV1_0(sensors);
-        mRestartWaiter->reset();
-        // Poke ISensor service. If it has lingering connection from previous generation of
-        // system server, it will kill itself. There is no intention to handle the poll result,
-        // which will be done since the size is 0.
-        if(mSensors->poll(0, [](auto, const auto &, const auto &) {}).isOk()) {
-            // ok to continue
-            connectionStatus = HalConnectionStatus::CONNECTED;
-            break;
+        // Check and clamp power if it is 0 (or close)
+        constexpr float MIN_POWER_MA = 0.001; // 1 microAmp
+        if (sensor.power < MIN_POWER_MA) {
+            ALOGI("%s's reported power %f invalid, clamped to %f", sensor.name, sensor.power,
+                  MIN_POWER_MA);
+            sensor.power = MIN_POWER_MA;
         }
+        mSensorList.push_back(sensor);
 
-        // hidl service is restarting, pointer is invalid.
-        mSensors = nullptr;
-        connectionStatus = HalConnectionStatus::FAILED_TO_CONNECT;
-        ALOGI("%s unsuccessful, remaining retry %zu.", __FUNCTION__, retry);
-        mRestartWaiter->wait();
+        mActivationCount.add(list[i].handle, model);
+
+        // Only disable all sensors on HAL 1.0 since HAL 2.0
+        // handles this in its initialize method
+        if (!mHalWrapper->supportsMessageQueues()) {
+            mHalWrapper->activate(list[i].handle, 0 /* enabled */);
+        }
     }
-
-    return connectionStatus;
 }
 
-SensorDevice::HalConnectionStatus SensorDevice::connectHidlServiceV2_0() {
-    HalConnectionStatus connectionStatus = HalConnectionStatus::UNKNOWN;
-    sp<V2_0::ISensors> sensors = V2_0::ISensors::getService();
+SensorDevice::~SensorDevice() {}
 
-    if (sensors == nullptr) {
-        connectionStatus = HalConnectionStatus::DOES_NOT_EXIST;
-    } else {
-        mSensors = new ISensorsWrapperV2_0(sensors);
-        connectionStatus = initializeHidlServiceV2_X();
+bool SensorDevice::connectHalService() {
+    std::unique_ptr<ISensorHalWrapper> aidl_wrapper = std::make_unique<AidlSensorHalWrapper>();
+    if (aidl_wrapper->connect(this)) {
+        mHalWrapper = std::move(aidl_wrapper);
+        return true;
     }
 
-    return connectionStatus;
-}
-
-SensorDevice::HalConnectionStatus SensorDevice::connectHidlServiceV2_1() {
-    HalConnectionStatus connectionStatus = HalConnectionStatus::UNKNOWN;
-    sp<V2_1::ISensors> sensors = V2_1::ISensors::getService();
-
-    if (sensors == nullptr) {
-        connectionStatus = HalConnectionStatus::DOES_NOT_EXIST;
-    } else {
-        mSensors = new ISensorsWrapperV2_1(sensors);
-        connectionStatus = initializeHidlServiceV2_X();
+    std::unique_ptr<ISensorHalWrapper> hidl_wrapper = std::make_unique<HidlSensorHalWrapper>();
+    if (hidl_wrapper->connect(this)) {
+        mHalWrapper = std::move(hidl_wrapper);
+        return true;
     }
 
-    return connectionStatus;
-}
-
-SensorDevice::HalConnectionStatus SensorDevice::initializeHidlServiceV2_X() {
-    HalConnectionStatus connectionStatus = HalConnectionStatus::UNKNOWN;
-
-    mWakeLockQueue = std::make_unique<WakeLockQueue>(
-            SensorEventQueue::MAX_RECEIVE_BUFFER_EVENT_COUNT,
-            true /* configureEventFlagWord */);
-
-    hardware::EventFlag::deleteEventFlag(&mEventQueueFlag);
-    hardware::EventFlag::createEventFlag(mSensors->getEventQueue()->getEventFlagWord(), &mEventQueueFlag);
-
-    hardware::EventFlag::deleteEventFlag(&mWakeLockQueueFlag);
-    hardware::EventFlag::createEventFlag(mWakeLockQueue->getEventFlagWord(),
-                                            &mWakeLockQueueFlag);
-
-    CHECK(mSensors != nullptr && mWakeLockQueue != nullptr &&
-            mEventQueueFlag != nullptr && mWakeLockQueueFlag != nullptr);
-
-    status_t status = checkReturnAndGetStatus(mSensors->initialize(
-            *mWakeLockQueue->getDesc(),
-            new SensorsCallback()));
-
-    if (status != NO_ERROR) {
-        connectionStatus = HalConnectionStatus::FAILED_TO_CONNECT;
-        ALOGE("Failed to initialize Sensors HAL (%s)", strerror(-status));
-    } else {
-        connectionStatus = HalConnectionStatus::CONNECTED;
-        mSensorsHalDeathReceiver = new SensorsHalDeathReceivier();
-        mSensors->linkToDeath(mSensorsHalDeathReceiver, 0 /* cookie */);
-    }
-
-    return connectionStatus;
+    // TODO: check aidl connection;
+    return false;
 }
 
 void SensorDevice::prepareForReconnect() {
-    mReconnecting = true;
-
-    // Wake up the polling thread so it returns and allows the SensorService to initiate
-    // a reconnect.
-    mEventQueueFlag->wake(asBaseType(INTERNAL_WAKE));
+    mHalWrapper->prepareForReconnect();
 }
 
 void SensorDevice::reconnect() {
     Mutex::Autolock _l(mLock);
-    mSensors = nullptr;
 
     auto previousActivations = mActivationCount;
     auto previousSensorList = mSensorList;
@@ -326,7 +166,7 @@
     mActivationCount.clear();
     mSensorList.clear();
 
-    if (connectHidlService()) {
+    if (mHalWrapper->connect(this)) {
         initializeSensorList();
 
         if (sensorHandlesChanged(previousSensorList, mSensorList)) {
@@ -335,11 +175,11 @@
             reactivateSensors(previousActivations);
         }
     }
-    mReconnecting = false;
+    mHalWrapper->mReconnecting = false;
 }
 
-bool SensorDevice::sensorHandlesChanged(const Vector<sensor_t>& oldSensorList,
-                                        const Vector<sensor_t>& newSensorList) {
+bool SensorDevice::sensorHandlesChanged(const std::vector<sensor_t>& oldSensorList,
+                                        const std::vector<sensor_t>& newSensorList) {
     bool didChange = false;
 
     if (oldSensorList.size() != newSensorList.size()) {
@@ -375,19 +215,17 @@
 bool SensorDevice::sensorIsEquivalent(const sensor_t& prevSensor, const sensor_t& newSensor) {
     bool equivalent = true;
     if (prevSensor.handle != newSensor.handle ||
-            (strcmp(prevSensor.vendor, newSensor.vendor) != 0) ||
-            (strcmp(prevSensor.stringType, newSensor.stringType) != 0) ||
-            (strcmp(prevSensor.requiredPermission, newSensor.requiredPermission) != 0) ||
-            (prevSensor.version != newSensor.version) ||
-            (prevSensor.type != newSensor.type) ||
-            (std::abs(prevSensor.maxRange - newSensor.maxRange) > 0.001f) ||
-            (std::abs(prevSensor.resolution - newSensor.resolution) > 0.001f) ||
-            (std::abs(prevSensor.power - newSensor.power) > 0.001f) ||
-            (prevSensor.minDelay != newSensor.minDelay) ||
-            (prevSensor.fifoReservedEventCount != newSensor.fifoReservedEventCount) ||
-            (prevSensor.fifoMaxEventCount != newSensor.fifoMaxEventCount) ||
-            (prevSensor.maxDelay != newSensor.maxDelay) ||
-            (prevSensor.flags != newSensor.flags)) {
+        (strcmp(prevSensor.vendor, newSensor.vendor) != 0) ||
+        (strcmp(prevSensor.stringType, newSensor.stringType) != 0) ||
+        (strcmp(prevSensor.requiredPermission, newSensor.requiredPermission) != 0) ||
+        (prevSensor.version != newSensor.version) || (prevSensor.type != newSensor.type) ||
+        (std::abs(prevSensor.maxRange - newSensor.maxRange) > 0.001f) ||
+        (std::abs(prevSensor.resolution - newSensor.resolution) > 0.001f) ||
+        (std::abs(prevSensor.power - newSensor.power) > 0.001f) ||
+        (prevSensor.minDelay != newSensor.minDelay) ||
+        (prevSensor.fifoReservedEventCount != newSensor.fifoReservedEventCount) ||
+        (prevSensor.fifoMaxEventCount != newSensor.fifoMaxEventCount) ||
+        (prevSensor.maxDelay != newSensor.maxDelay) || (prevSensor.flags != newSensor.flags)) {
         equivalent = false;
     }
     return equivalent;
@@ -405,7 +243,7 @@
         for (size_t j = 0; j < info.batchParams.size(); j++) {
             const BatchParams& batchParams = info.batchParams[j];
             status_t res = batchLocked(info.batchParams.keyAt(j), handle, 0 /* flags */,
-                    batchParams.mTSample, batchParams.mTBatch);
+                                       batchParams.mTSample, batchParams.mTBatch);
 
             if (res == NO_ERROR) {
                 activateLocked(info.batchParams.keyAt(j), handle, true /* enabled */);
@@ -419,21 +257,21 @@
     if (connected) {
         Info model;
         mActivationCount.add(handle, model);
-        checkReturn(mSensors->activate(handle, 0 /* enabled */));
+        mHalWrapper->activate(handle, 0 /* enabled */);
     } else {
         mActivationCount.removeItem(handle);
     }
 }
 
 std::string SensorDevice::dump() const {
-    if (mSensors == nullptr) return "HAL not initialized\n";
+    if (mHalWrapper == nullptr) return "HAL not initialized\n";
 
     String8 result;
     result.appendFormat("Total %zu h/w sensors, %zu running %zu disabled clients:\n",
                         mSensorList.size(), mActivationCount.size(), mDisabledClients.size());
 
     Mutex::Autolock _l(mLock);
-    for (const auto & s : mSensorList) {
+    for (const auto& s : mSensorList) {
         int32_t handle = s.handle;
         const Info& info = mActivationCount.valueFor(handle);
         if (info.numActiveClients() == 0) continue;
@@ -444,8 +282,9 @@
         for (size_t j = 0; j < info.batchParams.size(); j++) {
             const BatchParams& params = info.batchParams[j];
             result.appendFormat("%.1f%s%s", params.mTSample / 1e6f,
-                isClientDisabledLocked(info.batchParams.keyAt(j)) ? "(disabled)" : "",
-                (j < info.batchParams.size() - 1) ? ", " : "");
+                                isClientDisabledLocked(info.batchParams.keyAt(j)) ? "(disabled)"
+                                                                                  : "",
+                                (j < info.batchParams.size() - 1) ? ", " : "");
         }
         result.appendFormat("}, selected = %.2f ms; ", info.bestBatchParams.mTSample / 1e6f);
 
@@ -453,8 +292,9 @@
         for (size_t j = 0; j < info.batchParams.size(); j++) {
             const BatchParams& params = info.batchParams[j];
             result.appendFormat("%.1f%s%s", params.mTBatch / 1e6f,
-                    isClientDisabledLocked(info.batchParams.keyAt(j)) ? "(disabled)" : "",
-                    (j < info.batchParams.size() - 1) ? ", " : "");
+                                isClientDisabledLocked(info.batchParams.keyAt(j)) ? "(disabled)"
+                                                                                  : "",
+                                (j < info.batchParams.size() - 1) ? ", " : "");
         }
         result.appendFormat("}, selected = %.2f ms\n", info.bestBatchParams.mTBatch / 1e6f);
     }
@@ -471,30 +311,30 @@
  */
 void SensorDevice::dump(ProtoOutputStream* proto) const {
     using namespace service::SensorDeviceProto;
-    if (mSensors == nullptr) {
-        proto->write(INITIALIZED , false);
+    if (mHalWrapper == nullptr) {
+        proto->write(INITIALIZED, false);
         return;
     }
-    proto->write(INITIALIZED , true);
-    proto->write(TOTAL_SENSORS , int(mSensorList.size()));
-    proto->write(ACTIVE_SENSORS , int(mActivationCount.size()));
+    proto->write(INITIALIZED, true);
+    proto->write(TOTAL_SENSORS, int(mSensorList.size()));
+    proto->write(ACTIVE_SENSORS, int(mActivationCount.size()));
 
     Mutex::Autolock _l(mLock);
-    for (const auto & s : mSensorList) {
+    for (const auto& s : mSensorList) {
         int32_t handle = s.handle;
         const Info& info = mActivationCount.valueFor(handle);
         if (info.numActiveClients() == 0) continue;
 
         uint64_t token = proto->start(SENSORS);
-        proto->write(SensorProto::HANDLE , handle);
-        proto->write(SensorProto::ACTIVE_COUNT , int(info.batchParams.size()));
+        proto->write(SensorProto::HANDLE, handle);
+        proto->write(SensorProto::ACTIVE_COUNT, int(info.batchParams.size()));
         for (size_t j = 0; j < info.batchParams.size(); j++) {
             const BatchParams& params = info.batchParams[j];
-            proto->write(SensorProto::SAMPLING_PERIOD_MS , params.mTSample / 1e6f);
-            proto->write(SensorProto::BATCHING_PERIOD_MS , params.mTBatch / 1e6f);
+            proto->write(SensorProto::SAMPLING_PERIOD_MS, params.mTSample / 1e6f);
+            proto->write(SensorProto::BATCHING_PERIOD_MS, params.mTBatch / 1e6f);
         }
-        proto->write(SensorProto::SAMPLING_PERIOD_SELECTED , info.bestBatchParams.mTSample / 1e6f);
-        proto->write(SensorProto::BATCHING_PERIOD_SELECTED , info.bestBatchParams.mTBatch / 1e6f);
+        proto->write(SensorProto::SAMPLING_PERIOD_SELECTED, info.bestBatchParams.mTSample / 1e6f);
+        proto->write(SensorProto::BATCHING_PERIOD_SELECTED, info.bestBatchParams.mTBatch / 1e6f);
         proto->end(token);
     }
 }
@@ -506,154 +346,82 @@
 }
 
 status_t SensorDevice::initCheck() const {
-    return mSensors != nullptr ? NO_ERROR : NO_INIT;
+    return mHalWrapper != nullptr ? NO_ERROR : NO_INIT;
 }
 
 ssize_t SensorDevice::poll(sensors_event_t* buffer, size_t count) {
-    if (mSensors == nullptr) return NO_INIT;
+    if (mHalWrapper == nullptr) return NO_INIT;
 
     ssize_t eventsRead = 0;
-    if (mSensors->supportsMessageQueues()) {
-        eventsRead = pollFmq(buffer, count);
-    } else if (mSensors->supportsPolling()) {
-        eventsRead = pollHal(buffer, count);
+    if (mHalWrapper->supportsMessageQueues()) {
+        eventsRead = mHalWrapper->pollFmq(buffer, count);
+    } else if (mHalWrapper->supportsPolling()) {
+        eventsRead = mHalWrapper->poll(buffer, count);
     } else {
         ALOGE("Must support polling or FMQ");
         eventsRead = -1;
     }
-    return eventsRead;
-}
 
-ssize_t SensorDevice::pollHal(sensors_event_t* buffer, size_t count) {
-    ssize_t err;
-    int numHidlTransportErrors = 0;
-    bool hidlTransportError = false;
+    if (eventsRead > 0) {
+        for (ssize_t i = 0; i < eventsRead; i++) {
+            float resolution = getResolutionForSensor(buffer[i].sensor);
+            android::SensorDeviceUtils::quantizeSensorEventValues(&buffer[i], resolution);
 
-    do {
-        auto ret = mSensors->poll(
-                count,
-                [&](auto result,
-                    const auto &events,
-                    const auto &dynamicSensorsAdded) {
-                    if (result == Result::OK) {
-                        convertToSensorEventsAndQuantize(convertToNewEvents(events),
-                                convertToNewSensorInfos(dynamicSensorsAdded), buffer);
-                        err = (ssize_t)events.size();
-                    } else {
-                        err = statusFromResult(result);
+            if (buffer[i].type == SENSOR_TYPE_DYNAMIC_SENSOR_META) {
+                struct dynamic_sensor_meta_event& dyn = buffer[i].dynamic_sensor_meta;
+                if (dyn.connected) {
+                    std::unique_lock<std::mutex> lock(mDynamicSensorsMutex);
+                    // Give MAX_DYN_SENSOR_WAIT_SEC for onDynamicSensorsConnected to be invoked
+                    // since it can be received out of order from this event due to a bug in the
+                    // HIDL spec that marks it as oneway.
+                    auto it = mConnectedDynamicSensors.find(dyn.handle);
+                    if (it == mConnectedDynamicSensors.end()) {
+                        mDynamicSensorsCv.wait_for(lock, MAX_DYN_SENSOR_WAIT, [&, dyn] {
+                            return mConnectedDynamicSensors.find(dyn.handle) !=
+                                    mConnectedDynamicSensors.end();
+                        });
+                        it = mConnectedDynamicSensors.find(dyn.handle);
+                        CHECK(it != mConnectedDynamicSensors.end());
                     }
-                });
 
-        if (ret.isOk())  {
-            hidlTransportError = false;
-        } else {
-            hidlTransportError = true;
-            numHidlTransportErrors++;
-            if (numHidlTransportErrors > 50) {
-                // Log error and bail
-                ALOGE("Max Hidl transport errors this cycle : %d", numHidlTransportErrors);
-                handleHidlDeath(ret.description());
-            } else {
-                std::this_thread::sleep_for(std::chrono::milliseconds(10));
+                    dyn.sensor = &it->second;
+                }
             }
         }
-    } while (hidlTransportError);
-
-    if(numHidlTransportErrors > 0) {
-        ALOGE("Saw %d Hidl transport failures", numHidlTransportErrors);
-        HidlTransportErrorLog errLog(time(nullptr), numHidlTransportErrors);
-        mHidlTransportErrors.add(errLog);
-        mTotalHidlTransportErrors++;
-    }
-
-    return err;
-}
-
-ssize_t SensorDevice::pollFmq(sensors_event_t* buffer, size_t maxNumEventsToRead) {
-    ssize_t eventsRead = 0;
-    size_t availableEvents = mSensors->getEventQueue()->availableToRead();
-
-    if (availableEvents == 0) {
-        uint32_t eventFlagState = 0;
-
-        // Wait for events to become available. This is necessary so that the Event FMQ's read() is
-        // able to be called with the correct number of events to read. If the specified number of
-        // events is not available, then read() would return no events, possibly introducing
-        // additional latency in delivering events to applications.
-        mEventQueueFlag->wait(asBaseType(EventQueueFlagBits::READ_AND_PROCESS) |
-                              asBaseType(INTERNAL_WAKE), &eventFlagState);
-        availableEvents = mSensors->getEventQueue()->availableToRead();
-
-        if ((eventFlagState & asBaseType(INTERNAL_WAKE)) && mReconnecting) {
-            ALOGD("Event FMQ internal wake, returning from poll with no events");
-            return DEAD_OBJECT;
-        }
-    }
-
-    size_t eventsToRead = std::min({availableEvents, maxNumEventsToRead, mEventBuffer.size()});
-    if (eventsToRead > 0) {
-        if (mSensors->getEventQueue()->read(mEventBuffer.data(), eventsToRead)) {
-            // Notify the Sensors HAL that sensor events have been read. This is required to support
-            // the use of writeBlocking by the Sensors HAL.
-            mEventQueueFlag->wake(asBaseType(EventQueueFlagBits::EVENTS_READ));
-
-            for (size_t i = 0; i < eventsToRead; i++) {
-                convertToSensorEvent(mEventBuffer[i], &buffer[i]);
-                android::SensorDeviceUtils::quantizeSensorEventValues(&buffer[i],
-                        getResolutionForSensor(buffer[i].sensor));
-            }
-            eventsRead = eventsToRead;
-        } else {
-            ALOGW("Failed to read %zu events, currently %zu events available",
-                    eventsToRead, availableEvents);
-        }
     }
 
     return eventsRead;
 }
 
-Return<void> SensorDevice::onDynamicSensorsConnected(
-        const hidl_vec<SensorInfo> &dynamicSensorsAdded) {
+void SensorDevice::onDynamicSensorsConnected(const std::vector<sensor_t>& dynamicSensorsAdded) {
     std::unique_lock<std::mutex> lock(mDynamicSensorsMutex);
 
     // Allocate a sensor_t structure for each dynamic sensor added and insert
     // it into the dictionary of connected dynamic sensors keyed by handle.
     for (size_t i = 0; i < dynamicSensorsAdded.size(); ++i) {
-        const SensorInfo &info = dynamicSensorsAdded[i];
+        const sensor_t& sensor = dynamicSensorsAdded[i];
 
-        auto it = mConnectedDynamicSensors.find(info.sensorHandle);
+        auto it = mConnectedDynamicSensors.find(sensor.handle);
         CHECK(it == mConnectedDynamicSensors.end());
 
-        sensor_t *sensor = new sensor_t();
-        convertToSensor(convertToOldSensorInfo(info), sensor);
-
-        mConnectedDynamicSensors.insert(
-                std::make_pair(sensor->handle, sensor));
+        mConnectedDynamicSensors.insert(std::make_pair(sensor.handle, sensor));
     }
 
     mDynamicSensorsCv.notify_all();
-
-    return Return<void>();
 }
 
-Return<void> SensorDevice::onDynamicSensorsDisconnected(
-        const hidl_vec<int32_t> &dynamicSensorHandlesRemoved) {
-    (void) dynamicSensorHandlesRemoved;
+void SensorDevice::onDynamicSensorsDisconnected(
+        const std::vector<int32_t>& /* dynamicSensorHandlesRemoved */) {
     // TODO: Currently dynamic sensors do not seem to be removed
-    return Return<void>();
 }
 
 void SensorDevice::writeWakeLockHandled(uint32_t count) {
-    if (mSensors != nullptr && mSensors->supportsMessageQueues()) {
-        if (mWakeLockQueue->write(&count)) {
-            mWakeLockQueueFlag->wake(asBaseType(WakeLockQueueFlagBits::DATA_WRITTEN));
-        } else {
-            ALOGW("Failed to write wake lock handled");
-        }
+    if (mHalWrapper != nullptr && mHalWrapper->supportsMessageQueues()) {
+        mHalWrapper->writeWakeLockHandled(count);
     }
 }
 
-void SensorDevice::autoDisable(void *ident, int handle) {
+void SensorDevice::autoDisable(void* ident, int handle) {
     Mutex::Autolock _l(mLock);
     ssize_t activationIndex = mActivationCount.indexOfKey(handle);
     if (activationIndex < 0) {
@@ -668,7 +436,7 @@
 }
 
 status_t SensorDevice::activate(void* ident, int handle, int enabled) {
-    if (mSensors == nullptr) return NO_INIT;
+    if (mHalWrapper == nullptr) return NO_INIT;
 
     Mutex::Autolock _l(mLock);
     return activateLocked(ident, handle, enabled);
@@ -687,15 +455,15 @@
     Info& info(mActivationCount.editValueAt(activationIndex));
 
     ALOGD_IF(DEBUG_CONNECTIONS,
-             "SensorDevice::activate: ident=%p, handle=0x%08x, enabled=%d, count=%zu",
-             ident, handle, enabled, info.batchParams.size());
+             "SensorDevice::activate: ident=%p, handle=0x%08x, enabled=%d, count=%zu", ident,
+             handle, enabled, info.batchParams.size());
 
     if (enabled) {
         ALOGD_IF(DEBUG_CONNECTIONS, "enable index=%zd", info.batchParams.indexOfKey(ident));
 
         if (isClientDisabledLocked(ident)) {
-            ALOGW("SensorDevice::activate, isClientDisabledLocked(%p):true, handle:%d",
-                    ident, handle);
+            ALOGW("SensorDevice::activate, isClientDisabledLocked(%p):true, handle:%d", ident,
+                  handle);
             return NO_ERROR;
         }
 
@@ -714,7 +482,6 @@
         // dictionary.
         auto it = mConnectedDynamicSensors.find(handle);
         if (it != mConnectedDynamicSensors.end()) {
-            delete it->second;
             mConnectedDynamicSensors.erase(it);
         }
 
@@ -726,11 +493,10 @@
                 // Call batch for this sensor with the previously calculated best effort
                 // batch_rate and timeout. One of the apps has unregistered for sensor
                 // events, and the best effort batch parameters might have changed.
-                ALOGD_IF(DEBUG_CONNECTIONS,
-                         "\t>>> actuating h/w batch 0x%08x %" PRId64 " %" PRId64, handle,
-                         info.bestBatchParams.mTSample, info.bestBatchParams.mTBatch);
-                checkReturn(mSensors->batch(
-                        handle, info.bestBatchParams.mTSample, info.bestBatchParams.mTBatch));
+                ALOGD_IF(DEBUG_CONNECTIONS, "\t>>> actuating h/w batch 0x%08x %" PRId64 " %" PRId64,
+                         handle, info.bestBatchParams.mTSample, info.bestBatchParams.mTBatch);
+                mHalWrapper->batch(handle, info.bestBatchParams.mTSample,
+                                   info.bestBatchParams.mTBatch);
             }
         } else {
             // sensor wasn't enabled for this ident
@@ -761,19 +527,15 @@
 status_t SensorDevice::doActivateHardwareLocked(int handle, bool enabled) {
     ALOGD_IF(DEBUG_CONNECTIONS, "\t>>> actuating h/w activate handle=%d enabled=%d", handle,
              enabled);
-    status_t err = checkReturnAndGetStatus(mSensors->activate(handle, enabled));
+    status_t err = mHalWrapper->activate(handle, enabled);
     ALOGE_IF(err, "Error %s sensor %d (%s)", enabled ? "activating" : "disabling", handle,
              strerror(-err));
     return err;
 }
 
-status_t SensorDevice::batch(
-        void* ident,
-        int handle,
-        int flags,
-        int64_t samplingPeriodNs,
-        int64_t maxBatchReportLatencyNs) {
-    if (mSensors == nullptr) return NO_INIT;
+status_t SensorDevice::batch(void* ident, int handle, int flags, int64_t samplingPeriodNs,
+                             int64_t maxBatchReportLatencyNs) {
+    if (mHalWrapper == nullptr) return NO_INIT;
 
     if (samplingPeriodNs < MINIMUM_EVENTS_PERIOD) {
         samplingPeriodNs = MINIMUM_EVENTS_PERIOD;
@@ -783,7 +545,8 @@
     }
 
     ALOGD_IF(DEBUG_CONNECTIONS,
-             "SensorDevice::batch: ident=%p, handle=0x%08x, flags=%d, period_ns=%" PRId64 " timeout=%" PRId64,
+             "SensorDevice::batch: ident=%p, handle=0x%08x, flags=%d, period_ns=%" PRId64
+             " timeout=%" PRId64,
              ident, handle, flags, samplingPeriodNs, maxBatchReportLatencyNs);
 
     Mutex::Autolock _l(mLock);
@@ -807,25 +570,24 @@
         info.setBatchParamsForIdent(ident, flags, samplingPeriodNs, maxBatchReportLatencyNs);
     }
 
-    status_t err =  updateBatchParamsLocked(handle, info);
+    status_t err = updateBatchParamsLocked(handle, info);
     if (err != NO_ERROR) {
-        ALOGE("sensor batch failed %p 0x%08x %" PRId64 " %" PRId64 " err=%s",
-              mSensors.get(), handle, info.bestBatchParams.mTSample,
-              info.bestBatchParams.mTBatch, strerror(-err));
+        ALOGE("sensor batch failed 0x%08x %" PRId64 " %" PRId64 " err=%s", handle,
+              info.bestBatchParams.mTSample, info.bestBatchParams.mTBatch, strerror(-err));
         info.removeBatchParamsForIdent(ident);
     }
 
     return err;
 }
 
-status_t SensorDevice::updateBatchParamsLocked(int handle, Info &info) {
+status_t SensorDevice::updateBatchParamsLocked(int handle, Info& info) {
     BatchParams prevBestBatchParams = info.bestBatchParams;
     // Find the minimum of all timeouts and batch_rates for this sensor.
     info.selectBatchParams();
 
     ALOGD_IF(DEBUG_CONNECTIONS,
-             "\t>>> curr_period=%" PRId64 " min_period=%" PRId64
-             " curr_timeout=%" PRId64 " min_timeout=%" PRId64,
+             "\t>>> curr_period=%" PRId64 " min_period=%" PRId64 " curr_timeout=%" PRId64
+             " min_timeout=%" PRId64,
              prevBestBatchParams.mTSample, info.bestBatchParams.mTSample,
              prevBestBatchParams.mTBatch, info.bestBatchParams.mTBatch);
 
@@ -834,8 +596,8 @@
     if (prevBestBatchParams != info.bestBatchParams && info.numActiveClients() > 0) {
         ALOGD_IF(DEBUG_CONNECTIONS, "\t>>> actuating h/w BATCH 0x%08x %" PRId64 " %" PRId64, handle,
                  info.bestBatchParams.mTSample, info.bestBatchParams.mTBatch);
-        err = checkReturnAndGetStatus(mSensors->batch(
-                handle, info.bestBatchParams.mTSample, info.bestBatchParams.mTBatch));
+        err = mHalWrapper->batch(handle, info.bestBatchParams.mTSample,
+                                 info.bestBatchParams.mTBatch);
     }
 
     return err;
@@ -846,15 +608,13 @@
 }
 
 int SensorDevice::getHalDeviceVersion() const {
-    if (mSensors == nullptr) return -1;
+    if (mHalWrapper == nullptr) return -1;
     return SENSORS_DEVICE_API_VERSION_1_4;
 }
 
-status_t SensorDevice::flush(void* ident, int handle) {
-    if (mSensors == nullptr) return NO_INIT;
-    if (isClientDisabled(ident)) return INVALID_OPERATION;
-    ALOGD_IF(DEBUG_CONNECTIONS, "\t>>> actuating h/w flush %d", handle);
-    return checkReturnAndGetStatus(mSensors->flush(handle));
+status_t SensorDevice::flush(void* /*ident*/, int handle) {
+    if (mHalWrapper == nullptr) return NO_INIT;
+    return mHalWrapper->flush(handle);
 }
 
 bool SensorDevice::isClientDisabled(void* ident) const {
@@ -866,8 +626,8 @@
     return mDisabledClients.count(ident) > 0;
 }
 
-std::vector<void *> SensorDevice::getDisabledClientsLocked() const {
-    std::vector<void *> vec;
+std::vector<void*> SensorDevice::getDisabledClientsLocked() const {
+    std::vector<void*> vec;
     for (const auto& it : mDisabledClients) {
         vec.push_back(it.first);
     }
@@ -896,7 +656,7 @@
         addDisabledReasonForIdentLocked(ident, DisabledReason::DISABLED_REASON_UID_IDLE);
     }
 
-    for (size_t i = 0; i< mActivationCount.size(); ++i) {
+    for (size_t i = 0; i < mActivationCount.size(); ++i) {
         int handle = mActivationCount.keyAt(i);
         Info& info = mActivationCount.editValueAt(i);
 
@@ -905,8 +665,7 @@
             bool disable = info.numActiveClients() == 0 && info.isActive;
             bool enable = info.numActiveClients() > 0 && !info.isActive;
 
-            if ((enable || disable) &&
-                doActivateHardwareLocked(handle, enable) == NO_ERROR) {
+            if ((enable || disable) && doActivateHardwareLocked(handle, enable) == NO_ERROR) {
                 info.isActive = enable;
             }
         }
@@ -938,29 +697,27 @@
 }
 
 void SensorDevice::enableAllSensors() {
-    if (mSensors == nullptr) return;
+    if (mHalWrapper == nullptr) return;
     Mutex::Autolock _l(mLock);
 
-    for (void *client : getDisabledClientsLocked()) {
-        removeDisabledReasonForIdentLocked(
-            client, DisabledReason::DISABLED_REASON_SERVICE_RESTRICTED);
+    for (void* client : getDisabledClientsLocked()) {
+        removeDisabledReasonForIdentLocked(client,
+                                           DisabledReason::DISABLED_REASON_SERVICE_RESTRICTED);
     }
 
-    for (size_t i = 0; i< mActivationCount.size(); ++i) {
+    for (size_t i = 0; i < mActivationCount.size(); ++i) {
         Info& info = mActivationCount.editValueAt(i);
         if (info.batchParams.isEmpty()) continue;
         info.selectBatchParams();
         const int sensor_handle = mActivationCount.keyAt(i);
         ALOGD_IF(DEBUG_CONNECTIONS, "\t>> reenable actuating h/w sensor enable handle=%d ",
-                   sensor_handle);
-        status_t err = checkReturnAndGetStatus(mSensors->batch(
-                sensor_handle,
-                info.bestBatchParams.mTSample,
-                info.bestBatchParams.mTBatch));
+                 sensor_handle);
+        status_t err = mHalWrapper->batch(sensor_handle, info.bestBatchParams.mTSample,
+                                          info.bestBatchParams.mTBatch);
         ALOGE_IF(err, "Error calling batch on sensor %d (%s)", sensor_handle, strerror(-err));
 
         if (err == NO_ERROR) {
-            err = checkReturnAndGetStatus(mSensors->activate(sensor_handle, 1 /* enabled */));
+            err = mHalWrapper->activate(sensor_handle, 1 /* enabled */);
             ALOGE_IF(err, "Error activating sensor %d (%s)", sensor_handle, strerror(-err));
         }
 
@@ -971,138 +728,57 @@
 }
 
 void SensorDevice::disableAllSensors() {
-    if (mSensors == nullptr) return;
+    if (mHalWrapper == nullptr) return;
     Mutex::Autolock _l(mLock);
-    for (size_t i = 0; i< mActivationCount.size(); ++i) {
+    for (size_t i = 0; i < mActivationCount.size(); ++i) {
         Info& info = mActivationCount.editValueAt(i);
         // Check if this sensor has been activated previously and disable it.
         if (info.batchParams.size() > 0) {
-           const int sensor_handle = mActivationCount.keyAt(i);
-           ALOGD_IF(DEBUG_CONNECTIONS, "\t>> actuating h/w sensor disable handle=%d ",
-                   sensor_handle);
-           checkReturn(mSensors->activate(sensor_handle, 0 /* enabled */));
+            const int sensor_handle = mActivationCount.keyAt(i);
+            ALOGD_IF(DEBUG_CONNECTIONS, "\t>> actuating h/w sensor disable handle=%d ",
+                     sensor_handle);
+            mHalWrapper->activate(sensor_handle, 0 /* enabled */);
 
-           // Add all the connections that were registered for this sensor to the disabled
-           // clients list.
-           for (size_t j = 0; j < info.batchParams.size(); ++j) {
-               addDisabledReasonForIdentLocked(
-                   info.batchParams.keyAt(j), DisabledReason::DISABLED_REASON_SERVICE_RESTRICTED);
-               ALOGI("added %p to mDisabledClients", info.batchParams.keyAt(j));
-           }
+            // Add all the connections that were registered for this sensor to the disabled
+            // clients list.
+            for (size_t j = 0; j < info.batchParams.size(); ++j) {
+                addDisabledReasonForIdentLocked(info.batchParams.keyAt(j),
+                                                DisabledReason::DISABLED_REASON_SERVICE_RESTRICTED);
+                ALOGI("added %p to mDisabledClients", info.batchParams.keyAt(j));
+            }
 
-           info.isActive = false;
+            info.isActive = false;
         }
     }
 }
 
-status_t SensorDevice::injectSensorData(
-        const sensors_event_t *injected_sensor_event) {
-    if (mSensors == nullptr) return NO_INIT;
-    ALOGD_IF(DEBUG_CONNECTIONS,
-            "sensor_event handle=%d ts=%" PRId64 " data=%.2f, %.2f, %.2f %.2f %.2f %.2f",
-            injected_sensor_event->sensor,
-            injected_sensor_event->timestamp, injected_sensor_event->data[0],
-            injected_sensor_event->data[1], injected_sensor_event->data[2],
-            injected_sensor_event->data[3], injected_sensor_event->data[4],
-            injected_sensor_event->data[5]);
-
-    Event ev;
-    V2_1::implementation::convertFromSensorEvent(*injected_sensor_event, &ev);
-
-    return checkReturnAndGetStatus(mSensors->injectSensorData(ev));
+status_t SensorDevice::injectSensorData(const sensors_event_t* injected_sensor_event) {
+    if (mHalWrapper == nullptr) return NO_INIT;
+    return mHalWrapper->injectSensorData(injected_sensor_event);
 }
 
 status_t SensorDevice::setMode(uint32_t mode) {
-    if (mSensors == nullptr) return NO_INIT;
-    return checkReturnAndGetStatus(mSensors->setOperationMode(
-            static_cast<hardware::sensors::V1_0::OperationMode>(mode)));
+    if (mHalWrapper == nullptr) return NO_INIT;
+    return mHalWrapper->setOperationMode(static_cast<SensorService::Mode>(mode));
 }
 
 int32_t SensorDevice::registerDirectChannel(const sensors_direct_mem_t* memory) {
-    if (mSensors == nullptr) return NO_INIT;
+    if (mHalWrapper == nullptr) return NO_INIT;
     Mutex::Autolock _l(mLock);
 
-    SharedMemType type;
-    switch (memory->type) {
-        case SENSOR_DIRECT_MEM_TYPE_ASHMEM:
-            type = SharedMemType::ASHMEM;
-            break;
-        case SENSOR_DIRECT_MEM_TYPE_GRALLOC:
-            type = SharedMemType::GRALLOC;
-            break;
-        default:
-            return BAD_VALUE;
-    }
-
-    SharedMemFormat format;
-    if (memory->format != SENSOR_DIRECT_FMT_SENSORS_EVENT) {
-        return BAD_VALUE;
-    }
-    format = SharedMemFormat::SENSORS_EVENT;
-
-    SharedMemInfo mem = {
-        .type = type,
-        .format = format,
-        .size = static_cast<uint32_t>(memory->size),
-        .memoryHandle = memory->handle,
-    };
-
-    int32_t ret;
-    checkReturn(mSensors->registerDirectChannel(mem,
-            [&ret](auto result, auto channelHandle) {
-                if (result == Result::OK) {
-                    ret = channelHandle;
-                } else {
-                    ret = statusFromResult(result);
-                }
-            }));
-    return ret;
+    return mHalWrapper->registerDirectChannel(memory, nullptr);
 }
 
 void SensorDevice::unregisterDirectChannel(int32_t channelHandle) {
-    if (mSensors == nullptr) return;
-    Mutex::Autolock _l(mLock);
-    checkReturn(mSensors->unregisterDirectChannel(channelHandle));
+    mHalWrapper->unregisterDirectChannel(channelHandle);
 }
 
-int32_t SensorDevice::configureDirectChannel(int32_t sensorHandle,
-        int32_t channelHandle, const struct sensors_direct_cfg_t *config) {
-    if (mSensors == nullptr) return NO_INIT;
+int32_t SensorDevice::configureDirectChannel(int32_t sensorHandle, int32_t channelHandle,
+                                             const struct sensors_direct_cfg_t* config) {
+    if (mHalWrapper == nullptr) return NO_INIT;
     Mutex::Autolock _l(mLock);
 
-    RateLevel rate;
-    switch(config->rate_level) {
-        case SENSOR_DIRECT_RATE_STOP:
-            rate = RateLevel::STOP;
-            break;
-        case SENSOR_DIRECT_RATE_NORMAL:
-            rate = RateLevel::NORMAL;
-            break;
-        case SENSOR_DIRECT_RATE_FAST:
-            rate = RateLevel::FAST;
-            break;
-        case SENSOR_DIRECT_RATE_VERY_FAST:
-            rate = RateLevel::VERY_FAST;
-            break;
-        default:
-            return BAD_VALUE;
-    }
-
-    int32_t ret;
-    checkReturn(mSensors->configDirectReport(sensorHandle, channelHandle, rate,
-            [&ret, rate] (auto result, auto token) {
-                if (rate == RateLevel::STOP) {
-                    ret = statusFromResult(result);
-                } else {
-                    if (result == Result::OK) {
-                        ret = token;
-                    } else {
-                        ret = statusFromResult(result);
-                    }
-                }
-            }));
-
-    return ret;
+    return mHalWrapper->configureDirectChannel(sensorHandle, channelHandle, config);
 }
 
 // ---------------------------------------------------------------------------
@@ -1118,13 +794,12 @@
     return num;
 }
 
-status_t SensorDevice::Info::setBatchParamsForIdent(void* ident, int,
-                                                    int64_t samplingPeriodNs,
+status_t SensorDevice::Info::setBatchParamsForIdent(void* ident, int, int64_t samplingPeriodNs,
                                                     int64_t maxBatchReportLatencyNs) {
     ssize_t index = batchParams.indexOfKey(ident);
     if (index < 0) {
-        ALOGE("Info::setBatchParamsForIdent(ident=%p, period_ns=%" PRId64
-              " timeout=%" PRId64 ") failed (%s)",
+        ALOGE("Info::setBatchParamsForIdent(ident=%p, period_ns=%" PRId64 " timeout=%" PRId64
+              ") failed (%s)",
               ident, samplingPeriodNs, maxBatchReportLatencyNs, strerror(-index));
         return BAD_INDEX;
     }
@@ -1168,84 +843,20 @@
     return mIsDirectReportSupported;
 }
 
-void SensorDevice::convertToSensorEvent(
-        const Event &src, sensors_event_t *dst) {
-    V2_1::implementation::convertToSensorEvent(src, dst);
-
-    if (src.sensorType == V2_1::SensorType::DYNAMIC_SENSOR_META) {
-        const DynamicSensorInfo &dyn = src.u.dynamic;
-
-        dst->dynamic_sensor_meta.connected = dyn.connected;
-        dst->dynamic_sensor_meta.handle = dyn.sensorHandle;
-        if (dyn.connected) {
-            std::unique_lock<std::mutex> lock(mDynamicSensorsMutex);
-            // Give MAX_DYN_SENSOR_WAIT_SEC for onDynamicSensorsConnected to be invoked since it
-            // can be received out of order from this event due to a bug in the HIDL spec that
-            // marks it as oneway.
-            auto it = mConnectedDynamicSensors.find(dyn.sensorHandle);
-            if (it == mConnectedDynamicSensors.end()) {
-                mDynamicSensorsCv.wait_for(lock, MAX_DYN_SENSOR_WAIT,
-                        [&, dyn]{
-                            return mConnectedDynamicSensors.find(dyn.sensorHandle)
-                                    != mConnectedDynamicSensors.end();
-                });
-                it = mConnectedDynamicSensors.find(dyn.sensorHandle);
-                CHECK(it != mConnectedDynamicSensors.end());
-            }
-
-            dst->dynamic_sensor_meta.sensor = it->second;
-
-            memcpy(dst->dynamic_sensor_meta.uuid,
-                   dyn.uuid.data(),
-                   sizeof(dst->dynamic_sensor_meta.uuid));
-        }
-    }
-}
-
-void SensorDevice::convertToSensorEventsAndQuantize(
-        const hidl_vec<Event> &src,
-        const hidl_vec<SensorInfo> &dynamicSensorsAdded,
-        sensors_event_t *dst) {
-
-    if (dynamicSensorsAdded.size() > 0) {
-        onDynamicSensorsConnected(dynamicSensorsAdded);
-    }
-
-    for (size_t i = 0; i < src.size(); ++i) {
-        V2_1::implementation::convertToSensorEvent(src[i], &dst[i]);
-        android::SensorDeviceUtils::quantizeSensorEventValues(&dst[i],
-                getResolutionForSensor(dst[i].sensor));
-    }
-}
-
 float SensorDevice::getResolutionForSensor(int sensorHandle) {
     for (size_t i = 0; i < mSensorList.size(); i++) {
-      if (sensorHandle == mSensorList[i].handle) {
-        return mSensorList[i].resolution;
-      }
+        if (sensorHandle == mSensorList[i].handle) {
+            return mSensorList[i].resolution;
+        }
     }
 
     auto it = mConnectedDynamicSensors.find(sensorHandle);
     if (it != mConnectedDynamicSensors.end()) {
-      return it->second->resolution;
+        return it->second.resolution;
     }
 
     return 0;
 }
 
-void SensorDevice::handleHidlDeath(const std::string & detail) {
-    if (!mSensors->supportsMessageQueues()) {
-        // restart is the only option at present.
-        LOG_ALWAYS_FATAL("Abort due to ISensors hidl service failure, detail: %s.", detail.c_str());
-    } else {
-        ALOGD("ISensors HAL died, death recipient will attempt reconnect");
-    }
-}
-
-status_t SensorDevice::checkReturnAndGetStatus(const Return<Result>& ret) {
-    checkReturn(ret);
-    return (!ret.isOk()) ? DEAD_OBJECT : statusFromResult(ret);
-}
-
 // ---------------------------------------------------------------------------
 }; // namespace android
diff --git a/services/sensorservice/SensorDevice.h b/services/sensorservice/SensorDevice.h
index bc8d20f..747a6b0 100644
--- a/services/sensorservice/SensorDevice.h
+++ b/services/sensorservice/SensorDevice.h
@@ -17,14 +17,16 @@
 #ifndef ANDROID_SENSOR_DEVICE_H
 #define ANDROID_SENSOR_DEVICE_H
 
+#include "HidlSensorHalWrapper.h"
+#include "ISensorHalWrapper.h"
+
+#include "ISensorsWrapper.h"
 #include "SensorDeviceUtils.h"
 #include "SensorService.h"
 #include "SensorServiceUtils.h"
-#include "ISensorsWrapper.h"
 
-#include <fmq/MessageQueue.h>
-#include <sensor/SensorEventQueue.h>
 #include <sensor/Sensor.h>
+#include <sensor/SensorEventQueue.h>
 #include <stdint.h>
 #include <sys/types.h>
 #include <utils/KeyedVector.h>
@@ -32,9 +34,10 @@
 #include <utils/String8.h>
 #include <utils/Timers.h>
 
+#include <algorithm> //std::max std::min
 #include <string>
 #include <unordered_map>
-#include <algorithm> //std::max std::min
+#include <vector>
 
 #include "RingBuffer.h"
 
@@ -43,40 +46,11 @@
 namespace android {
 
 // ---------------------------------------------------------------------------
-class SensorsHalDeathReceivier : public android::hardware::hidl_death_recipient {
-    virtual void serviceDied(uint64_t cookie,
-                             const wp<::android::hidl::base::V1_0::IBase>& service) override;
-};
 
 class SensorDevice : public Singleton<SensorDevice>,
-                     public SensorServiceUtil::Dumpable {
+                     public SensorServiceUtil::Dumpable,
+                     public ISensorHalWrapper::SensorDeviceCallback {
 public:
-    class HidlTransportErrorLog {
-     public:
-
-        HidlTransportErrorLog() {
-            mTs = 0;
-            mCount = 0;
-        }
-
-        HidlTransportErrorLog(time_t ts, int count) {
-            mTs = ts;
-            mCount = count;
-        }
-
-        String8 toString() const {
-            String8 result;
-            struct tm *timeInfo = localtime(&mTs);
-            result.appendFormat("%02d:%02d:%02d :: %d", timeInfo->tm_hour, timeInfo->tm_min,
-                                timeInfo->tm_sec, mCount);
-            return result;
-        }
-
-    private:
-        time_t mTs; // timestamp of the error
-        int mCount;   // number of transport errors observed
-    };
-
     ~SensorDevice();
     void prepareForReconnect();
     void reconnect();
@@ -99,29 +73,27 @@
     status_t setMode(uint32_t mode);
 
     bool isDirectReportSupported() const;
-    int32_t registerDirectChannel(const sensors_direct_mem_t *memory);
+    int32_t registerDirectChannel(const sensors_direct_mem_t* memory);
     void unregisterDirectChannel(int32_t channelHandle);
-    int32_t configureDirectChannel(int32_t sensorHandle,
-            int32_t channelHandle, const struct sensors_direct_cfg_t *config);
+    int32_t configureDirectChannel(int32_t sensorHandle, int32_t channelHandle,
+                                   const struct sensors_direct_cfg_t* config);
 
     void disableAllSensors();
     void enableAllSensors();
-    void autoDisable(void *ident, int handle);
+    void autoDisable(void* ident, int handle);
 
-    status_t injectSensorData(const sensors_event_t *event);
-    void notifyConnectionDestroyed(void *ident);
+    status_t injectSensorData(const sensors_event_t* event);
+    void notifyConnectionDestroyed(void* ident);
 
-    using Result = ::android::hardware::sensors::V1_0::Result;
-    hardware::Return<void> onDynamicSensorsConnected(
-            const hardware::hidl_vec<hardware::sensors::V2_1::SensorInfo> &dynamicSensorsAdded);
-    hardware::Return<void> onDynamicSensorsDisconnected(
-            const hardware::hidl_vec<int32_t> &dynamicSensorHandlesRemoved);
+    // SensorDeviceCallback
+    virtual void onDynamicSensorsConnected(
+            const std::vector<sensor_t>& dynamicSensorsAdded) override;
+    virtual void onDynamicSensorsDisconnected(
+            const std::vector<int32_t>& dynamicSensorHandlesRemoved) override;
 
     void setUidStateForConnection(void* ident, SensorService::UidState state);
 
-    bool isReconnecting() const {
-        return mReconnecting;
-    }
+    bool isReconnecting() const { return mHalWrapper->mReconnecting; }
 
     bool isSensorActive(int handle) const;
 
@@ -132,12 +104,14 @@
     // Dumpable
     virtual std::string dump() const override;
     virtual void dump(util::ProtoOutputStream* proto) const override;
+
 private:
     friend class Singleton<SensorDevice>;
 
-    sp<::android::hardware::sensors::V2_1::implementation::ISensorsWrapperBase> mSensors;
-    Vector<sensor_t> mSensorList;
-    std::unordered_map<int32_t, sensor_t*> mConnectedDynamicSensors;
+    std::unique_ptr<ISensorHalWrapper> mHalWrapper;
+
+    std::vector<sensor_t> mSensorList;
+    std::unordered_map<int32_t, sensor_t> mConnectedDynamicSensors;
 
     // A bug in the Sensors HIDL spec which marks onDynamicSensorsConnected as oneway causes dynamic
     // meta events and onDynamicSensorsConnected to be received out of order. This mutex + CV are
@@ -147,26 +121,26 @@
     std::condition_variable mDynamicSensorsCv;
     static constexpr std::chrono::seconds MAX_DYN_SENSOR_WAIT{5};
 
-    static const nsecs_t MINIMUM_EVENTS_PERIOD =   1000000; // 1000 Hz
-    mutable Mutex mLock; // protect mActivationCount[].batchParams
+    static const nsecs_t MINIMUM_EVENTS_PERIOD = 1000000; // 1000 Hz
+    mutable Mutex mLock;                                  // protect mActivationCount[].batchParams
     // fixed-size array after construction
 
     // Struct to store all the parameters(samplingPeriod, maxBatchReportLatency and flags) from
     // batch call. For continous mode clients, maxBatchReportLatency is set to zero.
     struct BatchParams {
-      nsecs_t mTSample, mTBatch;
-      BatchParams() : mTSample(INT64_MAX), mTBatch(INT64_MAX) {}
-      BatchParams(nsecs_t tSample, nsecs_t tBatch): mTSample(tSample), mTBatch(tBatch) {}
-      bool operator != (const BatchParams& other) {
-          return !(mTSample == other.mTSample && mTBatch == other.mTBatch);
-      }
-      // Merge another parameter with this one. The updated mTSample will be the min of the two.
-      // The update mTBatch will be the min of original mTBatch and the apparent batch period
-      // of the other. the apparent batch is the maximum of mTBatch and mTSample,
-      void merge(const BatchParams &other) {
-          mTSample = std::min(mTSample, other.mTSample);
-          mTBatch = std::min(mTBatch, std::max(other.mTBatch, other.mTSample));
-      }
+        nsecs_t mTSample, mTBatch;
+        BatchParams() : mTSample(INT64_MAX), mTBatch(INT64_MAX) {}
+        BatchParams(nsecs_t tSample, nsecs_t tBatch) : mTSample(tSample), mTBatch(tBatch) {}
+        bool operator!=(const BatchParams& other) {
+            return !(mTSample == other.mTSample && mTBatch == other.mTBatch);
+        }
+        // Merge another parameter with this one. The updated mTSample will be the min of the two.
+        // The update mTBatch will be the min of original mTBatch and the apparent batch period
+        // of the other. the apparent batch is the maximum of mTBatch and mTSample,
+        void merge(const BatchParams& other) {
+            mTSample = std::min(mTSample, other.mTSample);
+            mTBatch = std::min(mTBatch, std::max(other.mTBatch, other.mTSample));
+        }
     };
 
     // Store batch parameters in the KeyedVector and the optimal batch_rate and timeout in
@@ -205,8 +179,6 @@
     };
     DefaultKeyedVector<int, Info> mActivationCount;
 
-    // Keep track of any hidl transport failures
-    SensorServiceUtil::RingBuffer<HidlTransportErrorLog> mHidlTransportErrors;
     int mTotalHidlTransportErrors;
 
     /**
@@ -224,32 +196,19 @@
     static_assert(DisabledReason::DISABLED_REASON_MAX < sizeof(uint8_t) * CHAR_BIT);
 
     // Use this map to determine which client is activated or deactivated.
-    std::unordered_map<void *, uint8_t> mDisabledClients;
+    std::unordered_map<void*, uint8_t> mDisabledClients;
 
     void addDisabledReasonForIdentLocked(void* ident, DisabledReason reason);
     void removeDisabledReasonForIdentLocked(void* ident, DisabledReason reason);
 
     SensorDevice();
-    bool connectHidlService();
+    bool connectHalService();
     void initializeSensorList();
     void reactivateSensors(const DefaultKeyedVector<int, Info>& previousActivations);
-    static bool sensorHandlesChanged(const Vector<sensor_t>& oldSensorList,
-                                     const Vector<sensor_t>& newSensorList);
+    static bool sensorHandlesChanged(const std::vector<sensor_t>& oldSensorList,
+                                     const std::vector<sensor_t>& newSensorList);
     static bool sensorIsEquivalent(const sensor_t& prevSensor, const sensor_t& newSensor);
 
-    enum HalConnectionStatus {
-        CONNECTED, // Successfully connected to the HAL
-        DOES_NOT_EXIST, // Could not find the HAL
-        FAILED_TO_CONNECT, // Found the HAL but failed to connect/initialize
-        UNKNOWN,
-    };
-    HalConnectionStatus connectHidlServiceV1_0();
-    HalConnectionStatus connectHidlServiceV2_0();
-    HalConnectionStatus connectHidlServiceV2_1();
-    HalConnectionStatus initializeHidlServiceV2_X();
-
-    ssize_t pollHal(sensors_event_t* buffer, size_t count);
-    ssize_t pollFmq(sensors_event_t* buffer, size_t count);
     status_t activateLocked(void* ident, int handle, int enabled);
     status_t batchLocked(void* ident, int handle, int flags, int64_t samplingPeriodNs,
                          int64_t maxBatchReportLatencyNs);
@@ -257,47 +216,15 @@
     status_t updateBatchParamsLocked(int handle, Info& info);
     status_t doActivateHardwareLocked(int handle, bool enable);
 
-    void handleHidlDeath(const std::string &detail);
-    template<typename T>
-    void checkReturn(const Return<T>& ret) {
-        if (!ret.isOk()) {
-            handleHidlDeath(ret.description());
-        }
-    }
-    status_t checkReturnAndGetStatus(const Return<Result>& ret);
-    //TODO(b/67425500): remove waiter after bug is resolved.
-    sp<SensorDeviceUtils::HidlServiceRegistrationWaiter> mRestartWaiter;
-
     bool isClientDisabled(void* ident) const;
     bool isClientDisabledLocked(void* ident) const;
-    std::vector<void *> getDisabledClientsLocked() const;
+    std::vector<void*> getDisabledClientsLocked() const;
 
     bool clientHasNoAccessLocked(void* ident) const;
 
-    using Event = hardware::sensors::V2_1::Event;
-    using SensorInfo = hardware::sensors::V2_1::SensorInfo;
-
-    void convertToSensorEvent(const Event &src, sensors_event_t *dst);
-
-    void convertToSensorEventsAndQuantize(
-            const hardware::hidl_vec<Event> &src,
-            const hardware::hidl_vec<SensorInfo> &dynamicSensorsAdded,
-            sensors_event_t *dst);
-
     float getResolutionForSensor(int sensorHandle);
 
     bool mIsDirectReportSupported;
-
-    typedef hardware::MessageQueue<uint32_t, hardware::kSynchronizedReadWrite> WakeLockQueue;
-    std::unique_ptr<WakeLockQueue> mWakeLockQueue;
-
-    hardware::EventFlag* mEventQueueFlag;
-    hardware::EventFlag* mWakeLockQueueFlag;
-
-    std::array<Event, SensorEventQueue::MAX_RECEIVE_BUFFER_EVENT_COUNT> mEventBuffer;
-
-    sp<SensorsHalDeathReceivier> mSensorsHalDeathReceiver;
-    std::atomic_bool mReconnecting;
 };
 
 // ---------------------------------------------------------------------------
diff --git a/services/sensorservice/SensorDirectConnection.cpp b/services/sensorservice/SensorDirectConnection.cpp
index af86d09..fae16f6 100644
--- a/services/sensorservice/SensorDirectConnection.cpp
+++ b/services/sensorservice/SensorDirectConnection.cpp
@@ -32,7 +32,6 @@
         : mService(service), mUid(uid), mMem(*mem),
         mHalChannelHandle(halChannelHandle),
         mOpPackageName(opPackageName), mDestroyed(false) {
-    mIsRateCappedBasedOnPermission = mService->isRateCappedBasedOnPermission(mOpPackageName);
     mUserId = multiuser_get_user_id(mUid);
     ALOGD_IF(DEBUG_CONNECTIONS, "Created SensorDirectConnection");
 }
@@ -197,8 +196,8 @@
             if (mService->isSensorInCappedSet(s.getType())) {
                 // Back up the rates that the app is allowed to have if the mic toggle is off
                 // This is used in the uncapRates() function.
-                if (!mIsRateCappedBasedOnPermission ||
-                            requestedRateLevel <= SENSOR_SERVICE_CAPPED_SAMPLING_RATE_LEVEL) {
+                if ((requestedRateLevel <= SENSOR_SERVICE_CAPPED_SAMPLING_RATE_LEVEL) ||
+                    !isRateCappedBasedOnPermission()) {
                     mMicRateBackup[handle] = requestedRateLevel;
                 } else {
                     mMicRateBackup[handle] = SENSOR_SERVICE_CAPPED_SAMPLING_RATE_LEVEL;
diff --git a/services/sensorservice/SensorDirectConnection.h b/services/sensorservice/SensorDirectConnection.h
index a3f348b..d39a073 100644
--- a/services/sensorservice/SensorDirectConnection.h
+++ b/services/sensorservice/SensorDirectConnection.h
@@ -17,6 +17,7 @@
 #ifndef ANDROID_SENSOR_DIRECT_CONNECTION_H
 #define ANDROID_SENSOR_DIRECT_CONNECTION_H
 
+#include <optional>
 #include <stdint.h>
 #include <sys/types.h>
 
@@ -100,10 +101,19 @@
     std::unordered_map<int, int> mActivatedBackup;
     std::unordered_map<int, int> mMicRateBackup;
 
-    std::atomic_bool mIsRateCappedBasedOnPermission;
     mutable Mutex mDestroyLock;
     bool mDestroyed;
     userid_t mUserId;
+
+    std::optional<bool> mIsRateCappedBasedOnPermission;
+
+    bool isRateCappedBasedOnPermission() {
+      if (!mIsRateCappedBasedOnPermission.has_value()) {
+        mIsRateCappedBasedOnPermission =
+            mService->isRateCappedBasedOnPermission(mOpPackageName);
+      }
+      return mIsRateCappedBasedOnPermission.value();
+    }
 };
 
 } // namepsace android
diff --git a/services/sensorservice/SensorEventConnection.cpp b/services/sensorservice/SensorEventConnection.cpp
index 9ce8d9b..6948895 100644
--- a/services/sensorservice/SensorEventConnection.cpp
+++ b/services/sensorservice/SensorEventConnection.cpp
@@ -44,7 +44,6 @@
       mCacheSize(0), mMaxCacheSize(0), mTimeOfLastEventDrop(0), mEventsDropped(0),
       mPackageName(packageName), mOpPackageName(opPackageName), mAttributionTag(attributionTag),
       mTargetSdk(kTargetSdkUnknown), mDestroyed(false) {
-    mIsRateCappedBasedOnPermission = mService->isRateCappedBasedOnPermission(mOpPackageName);
     mUserId = multiuser_get_user_id(mUid);
     mChannel = new BitTube(mService->mSocketBufferSize);
 #if DEBUG_CONNECTIONS
@@ -706,8 +705,8 @@
         err = mService->enable(this, handle, samplingPeriodNs, maxBatchReportLatencyNs,
                                reservedFlags, mOpPackageName);
         if (err == OK && isSensorCapped) {
-            if (!mIsRateCappedBasedOnPermission ||
-                        requestedSamplingPeriodNs >= SENSOR_SERVICE_CAPPED_SAMPLING_PERIOD_NS) {
+            if ((requestedSamplingPeriodNs >= SENSOR_SERVICE_CAPPED_SAMPLING_PERIOD_NS) ||
+                !isRateCappedBasedOnPermission()) {
                 mMicSamplingPeriodBackup[handle] = requestedSamplingPeriodNs;
             } else {
                 mMicSamplingPeriodBackup[handle] = SENSOR_SERVICE_CAPPED_SAMPLING_PERIOD_NS;
@@ -745,8 +744,8 @@
     }
     status_t ret = mService->setEventRate(this, handle, samplingPeriodNs, mOpPackageName);
     if (ret == OK && isSensorCapped) {
-        if (!mIsRateCappedBasedOnPermission ||
-                    requestedSamplingPeriodNs >= SENSOR_SERVICE_CAPPED_SAMPLING_PERIOD_NS) {
+        if ((requestedSamplingPeriodNs >= SENSOR_SERVICE_CAPPED_SAMPLING_PERIOD_NS) ||
+            !isRateCappedBasedOnPermission()) {
             mMicSamplingPeriodBackup[handle] = requestedSamplingPeriodNs;
         } else {
             mMicSamplingPeriodBackup[handle] = SENSOR_SERVICE_CAPPED_SAMPLING_PERIOD_NS;
@@ -867,7 +866,7 @@
             } else if (numBytesRead == sizeof(uint32_t)) {
                 uint32_t numAcks = 0;
                 memcpy(&numAcks, buf, numBytesRead);
-                // Sanity check to ensure  there are no read errors in recv, numAcks is always
+                // Check to ensure  there are no read errors in recv, numAcks is always
                 // within the range and not zero. If any of the above don't hold reset
                 // mWakeLockRefCount to zero.
                 if (numAcks > 0 && numAcks < mWakeLockRefCount) {
diff --git a/services/sensorservice/SensorEventConnection.h b/services/sensorservice/SensorEventConnection.h
index 909053b..6a98a40 100644
--- a/services/sensorservice/SensorEventConnection.h
+++ b/services/sensorservice/SensorEventConnection.h
@@ -18,6 +18,7 @@
 #define ANDROID_SENSOR_EVENT_CONNECTION_H
 
 #include <atomic>
+#include <optional>
 #include <stdint.h>
 #include <sys/types.h>
 #include <unordered_map>
@@ -148,7 +149,6 @@
     sp<SensorService> const mService;
     sp<BitTube> mChannel;
     uid_t mUid;
-    std::atomic_bool mIsRateCappedBasedOnPermission;
     mutable Mutex mConnectionLock;
     // Number of events from wake up sensors which are still pending and haven't been delivered to
     // the corresponding application. It is incremented by one unit for each write to the socket.
@@ -201,6 +201,16 @@
     // Mapping of sensor handles to its rate before being capped by the mic toggle.
     std::unordered_map<int, nsecs_t> mMicSamplingPeriodBackup;
     userid_t mUserId;
+
+    std::optional<bool> mIsRateCappedBasedOnPermission;
+
+    bool isRateCappedBasedOnPermission() {
+      if (!mIsRateCappedBasedOnPermission.has_value()) {
+        mIsRateCappedBasedOnPermission
+            = mService->isRateCappedBasedOnPermission(mOpPackageName);
+      }
+      return mIsRateCappedBasedOnPermission.value();
+    }
 };
 
 } // namepsace android
diff --git a/services/sensorservice/SensorService.cpp b/services/sensorservice/SensorService.cpp
index bdbae7b..3c164aa 100644
--- a/services/sensorservice/SensorService.cpp
+++ b/services/sensorservice/SensorService.cpp
@@ -38,6 +38,7 @@
 #include "BatteryService.h"
 #include "CorrectedGyroSensor.h"
 #include "GravitySensor.h"
+#include "LimitedAxesImuSensor.h"
 #include "LinearAccelerationSensor.h"
 #include "OrientationSensor.h"
 #include "RotationVectorSensor.h"
@@ -101,11 +102,39 @@
 static const String16 sLocationHardwarePermission("android.permission.LOCATION_HARDWARE");
 static const String16 sManageSensorsPermission("android.permission.MANAGE_SENSORS");
 
+static bool isAutomotive() {
+    sp<IServiceManager> serviceManager = defaultServiceManager();
+    if (serviceManager.get() == nullptr) {
+        ALOGE("%s: unable to access native ServiceManager", __func__);
+        return false;
+    }
+
+    sp<content::pm::IPackageManagerNative> packageManager;
+    sp<IBinder> binder = serviceManager->waitForService(String16("package_native"));
+    packageManager = interface_cast<content::pm::IPackageManagerNative>(binder);
+    if (packageManager == nullptr) {
+        ALOGE("%s: unable to access native PackageManager", __func__);
+        return false;
+    }
+
+    bool isAutomotive = false;
+    binder::Status status =
+        packageManager->hasSystemFeature(String16("android.hardware.type.automotive"), 0,
+                                         &isAutomotive);
+    if (!status.isOk()) {
+        ALOGE("%s: hasSystemFeature failed: %s", __func__, status.exceptionMessage().c_str());
+        return false;
+    }
+
+    return isAutomotive;
+}
+
 SensorService::SensorService()
     : mInitCheck(NO_INIT), mSocketBufferSize(SOCKET_BUFFER_SIZE_NON_BATCHED),
       mWakeLockAcquired(false), mLastReportedProxIsActive(false) {
     mUidPolicy = new UidPolicy(this);
     mSensorPrivacyPolicy = new SensorPrivacyPolicy(this);
+    mMicSensorPrivacyPolicy = new MicrophonePrivacyPolicy(this);
 }
 
 bool SensorService::initializeHmacKey() {
@@ -164,8 +193,9 @@
         sensor_t const* list;
         ssize_t count = dev.getSensorList(&list);
         if (count > 0) {
-            ssize_t orientationIndex = -1;
             bool hasGyro = false, hasAccel = false, hasMag = false;
+            bool hasGyroUncalibrated = false;
+            bool hasAccelUncalibrated = false;
             uint32_t virtualSensorsNeeds =
                     (1<<SENSOR_TYPE_GRAVITY) |
                     (1<<SENSOR_TYPE_LINEAR_ACCELERATION) |
@@ -180,16 +210,18 @@
                     case SENSOR_TYPE_ACCELEROMETER:
                         hasAccel = true;
                         break;
+                    case SENSOR_TYPE_ACCELEROMETER_UNCALIBRATED:
+                        hasAccelUncalibrated = true;
+                        break;
                     case SENSOR_TYPE_MAGNETIC_FIELD:
                         hasMag = true;
                         break;
-                    case SENSOR_TYPE_ORIENTATION:
-                        orientationIndex = i;
-                        break;
                     case SENSOR_TYPE_GYROSCOPE:
-                    case SENSOR_TYPE_GYROSCOPE_UNCALIBRATED:
                         hasGyro = true;
                         break;
+                    case SENSOR_TYPE_GYROSCOPE_UNCALIBRATED:
+                        hasGyroUncalibrated = true;
+                        break;
                     case SENSOR_TYPE_GRAVITY:
                     case SENSOR_TYPE_LINEAR_ACCELERATION:
                     case SENSOR_TYPE_ROTATION_VECTOR:
@@ -201,6 +233,8 @@
                             virtualSensorsNeeds &= ~(1<<list[i].type);
                         }
                         break;
+                    default:
+                        break;
                 }
                 if (useThisSensor) {
                     if (list[i].type == SENSOR_TYPE_PROXIMITY) {
@@ -218,7 +252,7 @@
             // registered)
             SensorFusion::getInstance();
 
-            if (hasGyro && hasAccel && hasMag) {
+            if ((hasGyro || hasGyroUncalibrated) && hasAccel && hasMag) {
                 // Add Android virtual sensors if they're not already
                 // available in the HAL
                 bool needRotationVector =
@@ -232,7 +266,7 @@
                 registerSensor( new GyroDriftSensor(), true, true);
             }
 
-            if (hasAccel && hasGyro) {
+            if (hasAccel && (hasGyro || hasGyroUncalibrated)) {
                 bool needGravitySensor = (virtualSensorsNeeds & (1<<SENSOR_TYPE_GRAVITY)) != 0;
                 registerSensor(new GravitySensor(list, count), !needGravitySensor, true);
 
@@ -252,6 +286,30 @@
                 registerSensor(new GeoMagRotationVectorSensor(), !needGeoMagRotationVector, true);
             }
 
+            if (isAutomotive()) {
+                if (hasAccel) {
+                   registerSensor(new LimitedAxesImuSensor(list, count, SENSOR_TYPE_ACCELEROMETER),
+                                  /*isDebug=*/false, /*isVirtual=*/true);
+               }
+
+               if (hasGyro) {
+                   registerSensor(new LimitedAxesImuSensor(list, count, SENSOR_TYPE_GYROSCOPE),
+                                  /*isDebug=*/false, /*isVirtual=*/true);
+               }
+
+               if (hasAccelUncalibrated) {
+                   registerSensor(new LimitedAxesImuSensor(list, count,
+                                                           SENSOR_TYPE_ACCELEROMETER_UNCALIBRATED),
+                                  /*isDebug=*/false, /*isVirtual=*/true);
+               }
+
+               if (hasGyroUncalibrated) {
+                   registerSensor(new LimitedAxesImuSensor(list, count,
+                                                           SENSOR_TYPE_GYROSCOPE_UNCALIBRATED),
+                                  /*isDebug=*/false, /*isVirtual=*/true);
+               }
+            }
+
             // Check if the device really supports batching by looking at the FIFO event
             // counts for each sensor.
             bool batchingSupported = false;
@@ -312,6 +370,9 @@
 
             // Start watching sensor privacy changes
             mSensorPrivacyPolicy->registerSelf();
+
+            // Start watching mic sensor privacy changes
+            mMicSensorPrivacyPolicy->registerSelf();
         }
     }
 }
@@ -382,9 +443,7 @@
     }
     mUidPolicy->unregisterSelf();
     mSensorPrivacyPolicy->unregisterSelf();
-    for (auto const& [userId, policy] : mMicSensorPrivacyPolicies) {
-        policy->unregisterSelf();
-    }
+    mMicSensorPrivacyPolicy->unregisterSelf();
 }
 
 status_t SensorService::dump(int fd, const Vector<String16>& args) {
@@ -716,33 +775,25 @@
     checkAndReportProxStateChangeLocked();
 }
 
-void SensorService::capRates(userid_t userId) {
+void SensorService::capRates() {
     ConnectionSafeAutolock connLock = mConnectionHolder.lock(mLock);
     for (const sp<SensorDirectConnection>& conn : connLock.getDirectConnections()) {
-        if (conn->getUserId() == userId) {
-            conn->onMicSensorAccessChanged(true);
-        }
+        conn->onMicSensorAccessChanged(true);
     }
 
     for (const sp<SensorEventConnection>& conn : connLock.getActiveConnections()) {
-        if (conn->getUserId() == userId) {
-            conn->onMicSensorAccessChanged(true);
-        }
+        conn->onMicSensorAccessChanged(true);
     }
 }
 
-void SensorService::uncapRates(userid_t userId) {
+void SensorService::uncapRates() {
     ConnectionSafeAutolock connLock = mConnectionHolder.lock(mLock);
     for (const sp<SensorDirectConnection>& conn : connLock.getDirectConnections()) {
-        if (conn->getUserId() == userId) {
-            conn->onMicSensorAccessChanged(false);
-        }
+        conn->onMicSensorAccessChanged(false);
     }
 
     for (const sp<SensorEventConnection>& conn : connLock.getActiveConnections()) {
-        if (conn->getUserId() == userId) {
-            conn->onMicSensorAccessChanged(false);
-        }
+        conn->onMicSensorAccessChanged(false);
     }
 }
 
@@ -1194,7 +1245,7 @@
     // We have a dynamic sensor.
 
     if (!sHmacGlobalKeyIsValid) {
-        // Rather than risk exposing UUIDs, we cripple dynamic sensors.
+        // Rather than risk exposing UUIDs, we slow down dynamic sensors.
         ALOGW("HMAC key failure; dynamic sensor getId() will be wrong.");
         return 0;
     }
@@ -1220,7 +1271,7 @@
              sHmacGlobalKey, sizeof(sHmacGlobalKey),
              uuidAndApp, sizeof(uuidAndApp),
              hash, &hashLen) == nullptr) {
-        // Rather than risk exposing UUIDs, we cripple dynamic sensors.
+        // Rather than risk exposing UUIDs, we slow down dynamic sensors.
         ALOGW("HMAC failure; dynamic sensor getId() will be wrong.");
         return 0;
     }
@@ -1256,6 +1307,11 @@
     for (auto &sensor : sensorList) {
         int32_t id = getIdFromUuid(sensor.getUuid());
         sensor.setId(id);
+        // The sensor UUID must always be anonymized here for non privileged clients.
+        // There is no other checks after this point before returning to client process.
+        if (!isAudioServerOrSystemServerUid(IPCThreadState::self()->getCallingUid())) {
+            sensor.anonymizeUuid();
+        }
     }
 }
 
@@ -2181,11 +2237,10 @@
 
 status_t SensorService::adjustSamplingPeriodBasedOnMicAndPermission(nsecs_t* requestedPeriodNs,
         const String16& opPackageName) {
-    uid_t uid = IPCThreadState::self()->getCallingUid();
-    bool shouldCapBasedOnPermission = isRateCappedBasedOnPermission(opPackageName);
     if (*requestedPeriodNs >= SENSOR_SERVICE_CAPPED_SAMPLING_PERIOD_NS) {
         return OK;
     }
+    bool shouldCapBasedOnPermission = isRateCappedBasedOnPermission(opPackageName);
     if (shouldCapBasedOnPermission) {
         *requestedPeriodNs = SENSOR_SERVICE_CAPPED_SAMPLING_PERIOD_NS;
         if (isPackageDebuggable(opPackageName)) {
@@ -2193,7 +2248,7 @@
         }
         return OK;
     }
-    if (isMicSensorPrivacyEnabledForUid(uid)) {
+    if (mMicSensorPrivacyPolicy->isSensorPrivacyEnabled()) {
         *requestedPeriodNs = SENSOR_SERVICE_CAPPED_SAMPLING_PERIOD_NS;
         return OK;
     }
@@ -2202,12 +2257,10 @@
 
 status_t SensorService::adjustRateLevelBasedOnMicAndPermission(int* requestedRateLevel,
         const String16& opPackageName) {
-    uid_t uid = IPCThreadState::self()->getCallingUid();
-    bool shouldCapBasedOnPermission = isRateCappedBasedOnPermission(opPackageName);
-
     if (*requestedRateLevel <= SENSOR_SERVICE_CAPPED_SAMPLING_RATE_LEVEL) {
         return OK;
     }
+    bool shouldCapBasedOnPermission = isRateCappedBasedOnPermission(opPackageName);
     if (shouldCapBasedOnPermission) {
         *requestedRateLevel = SENSOR_SERVICE_CAPPED_SAMPLING_RATE_LEVEL;
         if (isPackageDebuggable(opPackageName)) {
@@ -2215,7 +2268,7 @@
         }
         return OK;
     }
-    if (isMicSensorPrivacyEnabledForUid(uid)) {
+    if (mMicSensorPrivacyPolicy->isSensorPrivacyEnabled()) {
         *requestedRateLevel = SENSOR_SERVICE_CAPPED_SAMPLING_RATE_LEVEL;
         return OK;
     }
@@ -2232,69 +2285,63 @@
 void SensorService::SensorPrivacyPolicy::unregisterSelf() {
     AutoCallerClear acc;
     SensorPrivacyManager spm;
-    if (mIsIndividualMic) {
-        spm.removeIndividualSensorPrivacyListener(
-                SensorPrivacyManager::INDIVIDUAL_SENSOR_MICROPHONE, this);
-    } else {
-        spm.removeSensorPrivacyListener(this);
-    }
+    spm.removeSensorPrivacyListener(this);
 }
 
 bool SensorService::SensorPrivacyPolicy::isSensorPrivacyEnabled() {
     return mSensorPrivacyEnabled;
 }
 
-binder::Status SensorService::SensorPrivacyPolicy::onSensorPrivacyChanged(bool enabled) {
+binder::Status SensorService::SensorPrivacyPolicy::onSensorPrivacyChanged(int toggleType __unused,
+        int sensor __unused, bool enabled) {
     mSensorPrivacyEnabled = enabled;
     sp<SensorService> service = mService.promote();
 
     if (service != nullptr) {
-        if (mIsIndividualMic) {
-            if (enabled) {
-                service->capRates(mUserId);
-            } else {
-                service->uncapRates(mUserId);
-            }
+        if (enabled) {
+            service->disableAllSensors();
         } else {
-            if (enabled) {
-                service->disableAllSensors();
-            } else {
-                service->enableAllSensors();
-            }
+            service->enableAllSensors();
         }
     }
     return binder::Status::ok();
 }
 
-status_t SensorService::SensorPrivacyPolicy::registerSelfForIndividual(int userId) {
-    Mutex::Autolock _l(mSensorPrivacyLock);
+void SensorService::MicrophonePrivacyPolicy::registerSelf() {
     AutoCallerClear acc;
     SensorPrivacyManager spm;
-    status_t err = spm.addIndividualSensorPrivacyListener(userId,
-            SensorPrivacyManager::INDIVIDUAL_SENSOR_MICROPHONE, this);
-
-    if (err != OK) {
-        ALOGE("Cannot register a mic listener.");
-        return err;
-    }
-    mSensorPrivacyEnabled = spm.isIndividualSensorPrivacyEnabled(userId,
-                SensorPrivacyManager::INDIVIDUAL_SENSOR_MICROPHONE);
-
-    mIsIndividualMic = true;
-    mUserId = userId;
-    return OK;
+    mSensorPrivacyEnabled =
+            spm.isToggleSensorPrivacyEnabled(
+                    SensorPrivacyManager::TOGGLE_TYPE_SOFTWARE,
+            SensorPrivacyManager::TOGGLE_SENSOR_MICROPHONE)
+                    || spm.isToggleSensorPrivacyEnabled(
+                            SensorPrivacyManager::TOGGLE_TYPE_HARDWARE,
+                            SensorPrivacyManager::TOGGLE_SENSOR_MICROPHONE);
+    spm.addToggleSensorPrivacyListener(this);
 }
 
-bool SensorService::isMicSensorPrivacyEnabledForUid(uid_t uid) {
-    userid_t userId = multiuser_get_user_id(uid);
-    if (mMicSensorPrivacyPolicies.find(userId) == mMicSensorPrivacyPolicies.end()) {
-        sp<SensorPrivacyPolicy> userPolicy = new SensorPrivacyPolicy(this);
-        if (userPolicy->registerSelfForIndividual(userId) != OK) {
-            return false;
-        }
-        mMicSensorPrivacyPolicies[userId] = userPolicy;
+void SensorService::MicrophonePrivacyPolicy::unregisterSelf() {
+    AutoCallerClear acc;
+    SensorPrivacyManager spm;
+    spm.removeToggleSensorPrivacyListener(this);
+}
+
+binder::Status SensorService::MicrophonePrivacyPolicy::onSensorPrivacyChanged(int toggleType __unused,
+        int sensor, bool enabled) {
+    if (sensor != SensorPrivacyManager::TOGGLE_SENSOR_MICROPHONE) {
+        return binder::Status::ok();
     }
-    return mMicSensorPrivacyPolicies[userId]->isSensorPrivacyEnabled();
+    mSensorPrivacyEnabled = enabled;
+    sp<SensorService> service = mService.promote();
+
+    if (service != nullptr) {
+        if (enabled) {
+            service->capRates();
+        } else {
+            service->uncapRates();
+        }
+    }
+    return binder::Status::ok();
 }
 
 SensorService::ConnectionSafeAutolock::ConnectionSafeAutolock(
diff --git a/services/sensorservice/SensorService.h b/services/sensorservice/SensorService.h
index b059e61..7194db3 100644
--- a/services/sensorservice/SensorService.h
+++ b/services/sensorservice/SensorService.h
@@ -26,6 +26,7 @@
 #include <binder/IUidObserver.h>
 #include <cutils/compiler.h>
 #include <cutils/multiuser.h>
+#include <private/android_filesystem_config.h>
 #include <sensor/ISensorServer.h>
 #include <sensor/ISensorEventConnection.h>
 #include <sensor/Sensor.h>
@@ -89,6 +90,52 @@
       UID_STATE_IDLE,
     };
 
+    enum Mode {
+       // The regular operating mode where any application can register/unregister/call flush on
+       // sensors.
+       NORMAL = 0,
+       // This mode is only used for testing purposes. Not all HALs support this mode. In this mode,
+       // the HAL ignores the sensor data provided by physical sensors and accepts the data that is
+       // injected from the SensorService as if it were the real sensor data. This mode is primarily
+       // used for testing various algorithms like vendor provided SensorFusion, Step Counter and
+       // Step Detector etc. Typically in this mode, there will be a client (a
+       // SensorEventConnection) which will be injecting sensor data into the HAL. Normal apps can
+       // unregister and register for any sensor that supports injection. Registering to sensors
+       // that do not support injection will give an error.  TODO: Allow exactly one
+       // client to inject sensor data at a time.
+       DATA_INJECTION = 1,
+       // This mode is used only for testing sensors. Each sensor can be tested in isolation with
+       // the required sampling_rate and maxReportLatency parameters without having to think about
+       // the data rates requested by other applications. End user devices are always expected to be
+       // in NORMAL mode. When this mode is first activated, all active sensors from all connections
+       // are disabled. Calling flush() will return an error. In this mode, only the requests from
+       // selected apps whose package names are allowlisted are allowed (typically CTS apps).  Only
+       // these apps can register/unregister/call flush() on sensors. If SensorService switches to
+       // NORMAL mode again, all sensors that were previously registered to are activated with the
+       // corresponding parameters if the application hasn't unregistered for sensors in the mean
+       // time.  NOTE: Non allowlisted app whose sensors were previously deactivated may still
+       // receive events if a allowlisted app requests data from the same sensor.
+       RESTRICTED = 2
+
+      // State Transitions supported.
+      //     RESTRICTED   <---  NORMAL   ---> DATA_INJECTION
+      //                  --->           <---
+
+      // Shell commands to switch modes in SensorService.
+      // 1) Put SensorService in RESTRICTED mode with packageName .cts. If it is already in
+      // restricted mode it is treated as a NO_OP (and packageName is NOT changed).
+      //
+      //     $ adb shell dumpsys sensorservice restrict .cts.
+      //
+      // 2) Put SensorService in DATA_INJECTION mode with packageName .xts. If it is already in
+      // data_injection mode it is treated as a NO_OP (and packageName is NOT changed).
+      //
+      //     $ adb shell dumpsys sensorservice data_injection .xts.
+      //
+      // 3) Reset sensorservice back to NORMAL mode.
+      //     $ adb shell dumpsys sensorservice enable
+    };
+
     class ProximityActiveListener : public virtual RefBase {
     public:
         // Note that the callback is invoked from an async thread and can interact with the
@@ -242,22 +289,32 @@
     class SensorPrivacyPolicy : public hardware::BnSensorPrivacyListener {
         public:
             explicit SensorPrivacyPolicy(wp<SensorService> service)
-                    : mService(service), mIsIndividualMic(false), mUserId(0) {}
+                    : mService(service) {}
             void registerSelf();
             void unregisterSelf();
 
-            status_t registerSelfForIndividual(int userId);
-
             bool isSensorPrivacyEnabled();
 
-            binder::Status onSensorPrivacyChanged(bool enabled);
+            binder::Status onSensorPrivacyChanged(int toggleType, int sensor,
+                                                  bool enabled);
+
+        protected:
+            std::atomic_bool mSensorPrivacyEnabled;
+            wp<SensorService> mService;
 
         private:
-            wp<SensorService> mService;
             Mutex mSensorPrivacyLock;
-            std::atomic_bool mSensorPrivacyEnabled;
-            bool mIsIndividualMic;
-            userid_t mUserId;
+    };
+
+    class MicrophonePrivacyPolicy : public SensorPrivacyPolicy {
+        public:
+            explicit MicrophonePrivacyPolicy(wp<SensorService> service)
+                    : SensorPrivacyPolicy(service) {}
+            void registerSelf();
+            void unregisterSelf();
+
+            binder::Status onSensorPrivacyChanged(int toggleType, int sensor,
+                                                  bool enabled);
     };
 
     // A class automatically clearing and restoring binder caller identity inside
@@ -276,52 +333,6 @@
             const int64_t mToken;
     };
 
-    enum Mode {
-       // The regular operating mode where any application can register/unregister/call flush on
-       // sensors.
-       NORMAL = 0,
-       // This mode is only used for testing purposes. Not all HALs support this mode. In this mode,
-       // the HAL ignores the sensor data provided by physical sensors and accepts the data that is
-       // injected from the SensorService as if it were the real sensor data. This mode is primarily
-       // used for testing various algorithms like vendor provided SensorFusion, Step Counter and
-       // Step Detector etc. Typically in this mode, there will be a client (a
-       // SensorEventConnection) which will be injecting sensor data into the HAL. Normal apps can
-       // unregister and register for any sensor that supports injection. Registering to sensors
-       // that do not support injection will give an error.  TODO(aakella) : Allow exactly one
-       // client to inject sensor data at a time.
-       DATA_INJECTION = 1,
-       // This mode is used only for testing sensors. Each sensor can be tested in isolation with
-       // the required sampling_rate and maxReportLatency parameters without having to think about
-       // the data rates requested by other applications. End user devices are always expected to be
-       // in NORMAL mode. When this mode is first activated, all active sensors from all connections
-       // are disabled. Calling flush() will return an error. In this mode, only the requests from
-       // selected apps whose package names are whitelisted are allowed (typically CTS apps).  Only
-       // these apps can register/unregister/call flush() on sensors. If SensorService switches to
-       // NORMAL mode again, all sensors that were previously registered to are activated with the
-       // corresponding paramaters if the application hasn't unregistered for sensors in the mean
-       // time.  NOTE: Non whitelisted app whose sensors were previously deactivated may still
-       // receive events if a whitelisted app requests data from the same sensor.
-       RESTRICTED = 2
-
-      // State Transitions supported.
-      //     RESTRICTED   <---  NORMAL   ---> DATA_INJECTION
-      //                  --->           <---
-
-      // Shell commands to switch modes in SensorService.
-      // 1) Put SensorService in RESTRICTED mode with packageName .cts. If it is already in
-      // restricted mode it is treated as a NO_OP (and packageName is NOT changed).
-      //
-      //     $ adb shell dumpsys sensorservice restrict .cts.
-      //
-      // 2) Put SensorService in DATA_INJECTION mode with packageName .xts. If it is already in
-      // data_injection mode it is treated as a NO_OP (and packageName is NOT changed).
-      //
-      //     $ adb shell dumpsys sensorservice data_injection .xts.
-      //
-      // 3) Reset sensorservice back to NORMAL mode.
-      //     $ adb shell dumpsys sensorservice enable
-    };
-
     static const char* WAKE_LOCK_NAME;
     virtual ~SensorService();
 
@@ -443,9 +454,13 @@
     void enableAllSensorsLocked(ConnectionSafeAutolock* connLock);
 
     // Caps active direct connections (when the mic toggle is flipped to on)
-    void capRates(userid_t userId);
+    void capRates();
     // Removes the capped rate on active direct connections (when the mic toggle is flipped to off)
-    void uncapRates(userid_t userId);
+    void uncapRates();
+
+    static inline bool isAudioServerOrSystemServerUid(uid_t uid) {
+        return multiuser_get_app_id(uid) == AID_SYSTEM || uid == AID_AUDIOSERVER;
+    }
 
     static uint8_t sHmacGlobalKey[128];
     static bool sHmacGlobalKeyIsValid;
@@ -492,10 +507,7 @@
     static Mutex sPackageTargetVersionLock;
     static String16 sSensorInterfaceDescriptorPrefix;
 
-    // Map from user to SensorPrivacyPolicy
-    std::map<userid_t, sp<SensorPrivacyPolicy>> mMicSensorPrivacyPolicies;
-    // Checks if the mic sensor privacy is enabled for the uid
-    bool isMicSensorPrivacyEnabledForUid(uid_t uid);
+    sp<MicrophonePrivacyPolicy> mMicSensorPrivacyPolicy;
 
     // Keeps track of the handles of all proximity sensors in the system.
     std::vector<int32_t> mProxSensorHandles;
diff --git a/services/sensorservice/SensorServiceUtils.cpp b/services/sensorservice/SensorServiceUtils.cpp
index fdd56b3..7dd2331 100644
--- a/services/sensorservice/SensorServiceUtils.cpp
+++ b/services/sensorservice/SensorServiceUtils.cpp
@@ -30,12 +30,18 @@
         case SENSOR_TYPE_POSE_6DOF:
             return 16;
 
+        case SENSOR_TYPE_ACCELEROMETER_LIMITED_AXES_UNCALIBRATED:
+        case SENSOR_TYPE_GYROSCOPE_LIMITED_AXES_UNCALIBRATED:
+            return 9;
+
         case SENSOR_TYPE_ROTATION_VECTOR:
         case SENSOR_TYPE_GEOMAGNETIC_ROTATION_VECTOR:
             return 5;
 
         case SENSOR_TYPE_MAGNETIC_FIELD_UNCALIBRATED:
         case SENSOR_TYPE_GYROSCOPE_UNCALIBRATED:
+        case SENSOR_TYPE_ACCELEROMETER_LIMITED_AXES:
+        case SENSOR_TYPE_GYROSCOPE_LIMITED_AXES:
             return 6;
 
         case SENSOR_TYPE_GAME_ROTATION_VECTOR:
@@ -58,6 +64,12 @@
         case SENSOR_TYPE_HINGE_ANGLE:
             return 1;
 
+        case SENSOR_TYPE_HEAD_TRACKER:
+            return 7;
+
+        case SENSOR_TYPE_HEADING:
+            return 2;
+
         default:
             return 3;
     }
diff --git a/services/surfaceflinger/Android.bp b/services/surfaceflinger/Android.bp
index 9e69283..000a2cb 100644
--- a/services/surfaceflinger/Android.bp
+++ b/services/surfaceflinger/Android.bp
@@ -39,15 +39,19 @@
         "android.hardware.graphics.allocator@2.0",
         "android.hardware.graphics.allocator@3.0",
         "android.hardware.graphics.common@1.2",
+        "android.hardware.common-V2-ndk",
+        "android.hardware.common.fmq-V1-ndk",
         "android.hardware.graphics.composer@2.1",
         "android.hardware.graphics.composer@2.2",
         "android.hardware.graphics.composer@2.3",
         "android.hardware.graphics.composer@2.4",
+        "android.hardware.graphics.composer3-V1-ndk",
         "android.hardware.power@1.0",
         "android.hardware.power@1.3",
-        "android.hardware.power-V1-cpp",
+        "android.hardware.power-V2-cpp",
         "libbase",
         "libbinder",
+        "libbinder_ndk",
         "libcutils",
         "libEGL",
         "libfmq",
@@ -66,13 +70,19 @@
         "libinput",
         "libutils",
         "libSurfaceFlingerProp",
+        "server_configurable_flags",
     ],
     static_libs: [
+        "libaidlcommonsupport",
         "libcompositionengine",
         "libframetimeline",
+        "libgui_aidl_static",
         "libperfetto_client_experimental",
         "librenderengine",
+        "libscheduler",
         "libserviceutils",
+        "libshaders",
+        "libtonemap",
         "libtrace_proto",
     ],
     header_libs: [
@@ -80,6 +90,7 @@
         "android.hardware.graphics.composer@2.2-command-buffer",
         "android.hardware.graphics.composer@2.3-command-buffer",
         "android.hardware.graphics.composer@2.4-command-buffer",
+        "android.hardware.graphics.composer3-command-buffer",
     ],
     export_static_lib_headers: [
         "libcompositionengine",
@@ -94,6 +105,7 @@
         "android.hardware.graphics.composer@2.2",
         "android.hardware.graphics.composer@2.3",
         "android.hardware.graphics.composer@2.4",
+        "android.hardware.graphics.composer3-V1-ndk",
         "android.hardware.power@1.3",
         "libhidlbase",
         "libtimestats",
@@ -107,9 +119,6 @@
 cc_defaults {
     name: "libsurfaceflinger_production_defaults",
     defaults: ["libsurfaceflinger_defaults"],
-    cflags: [
-        "-fvisibility=hidden",
-    ],
     lto: {
         thin: true,
     },
@@ -131,6 +140,7 @@
 filegroup {
     name: "libsurfaceflinger_sources",
     srcs: [
+        "BackgroundExecutor.cpp",
         "BufferLayer.cpp",
         "BufferLayerConsumer.cpp",
         "BufferQueueLayer.cpp",
@@ -140,10 +150,10 @@
         "EffectLayer.cpp",
         "ContainerLayer.cpp",
         "DisplayDevice.cpp",
+        "DisplayHardware/AidlComposerHal.cpp",
+        "DisplayHardware/HidlComposerHal.cpp",
         "DisplayHardware/ComposerHal.cpp",
-        "DisplayHardware/DisplayIdentification.cpp",
         "DisplayHardware/FramebufferSurface.cpp",
-        "DisplayHardware/Hash.cpp",
         "DisplayHardware/HWC2.cpp",
         "DisplayHardware/HWComposer.cpp",
         "DisplayHardware/PowerAdvisor.cpp",
@@ -151,6 +161,7 @@
         "DisplayRenderArea.cpp",
         "Effects/Daltonizer.cpp",
         "EventLog/EventLog.cpp",
+        "FlagManager.cpp",
         "FpsReporter.cpp",
         "FrameTracer/FrameTracer.cpp",
         "FrameTracker.cpp",
@@ -168,24 +179,26 @@
         "RenderArea.cpp",
         "Scheduler/DispSyncSource.cpp",
         "Scheduler/EventThread.cpp",
+        "Scheduler/FrameRateOverrideMappings.cpp",
         "Scheduler/OneShotTimer.cpp",
         "Scheduler/LayerHistory.cpp",
         "Scheduler/LayerInfo.cpp",
         "Scheduler/MessageQueue.cpp",
         "Scheduler/RefreshRateConfigs.cpp",
         "Scheduler/Scheduler.cpp",
-        "Scheduler/SchedulerUtils.cpp",
-        "Scheduler/Timer.cpp",
         "Scheduler/VSyncDispatchTimerQueue.cpp",
         "Scheduler/VSyncPredictor.cpp",
-        "Scheduler/VsyncModulator.cpp",
         "Scheduler/VSyncReactor.cpp",
         "Scheduler/VsyncConfiguration.cpp",
+        "Scheduler/VsyncModulator.cpp",
+        "Scheduler/VsyncSchedule.cpp",
         "StartPropertySetThread.cpp",
         "SurfaceFlinger.cpp",
         "SurfaceFlingerDefaultFactory.cpp",
         "SurfaceInterceptor.cpp",
-        "SurfaceTracing.cpp",
+        "Tracing/LayerTracing.cpp",
+        "Tracing/TransactionTracing.cpp",
+        "Tracing/TransactionProtoParser.cpp",
         "TransactionCallbackInvoker.cpp",
         "TunnelModeEnabledReporter.cpp",
     ],
@@ -210,7 +223,6 @@
         "libcutils",
         "libdisplayservicehidl",
         "libhidlbase",
-        "liblayers_proto",
         "liblog",
         "libprocessgroup",
         "libsync",
diff --git a/services/surfaceflinger/BackgroundExecutor.cpp b/services/surfaceflinger/BackgroundExecutor.cpp
new file mode 100644
index 0000000..de8e6b3
--- /dev/null
+++ b/services/surfaceflinger/BackgroundExecutor.cpp
@@ -0,0 +1,67 @@
+/*
+ * 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.
+ */
+
+//#define LOG_NDEBUG 0
+#undef LOG_TAG
+#define LOG_TAG "BackgroundExecutor"
+#define ATRACE_TAG ATRACE_TAG_GRAPHICS
+
+#include "BackgroundExecutor.h"
+
+namespace android {
+
+ANDROID_SINGLETON_STATIC_INSTANCE(BackgroundExecutor);
+
+BackgroundExecutor::BackgroundExecutor() : Singleton<BackgroundExecutor>() {
+    mThread = std::thread([&]() {
+        bool done = false;
+        while (!done) {
+            std::vector<std::function<void()>> tasks;
+            {
+                std::unique_lock lock(mMutex);
+                android::base::ScopedLockAssertion assumeLock(mMutex);
+                mWorkAvailableCv.wait(lock,
+                                      [&]() REQUIRES(mMutex) { return mDone || !mTasks.empty(); });
+                tasks = std::move(mTasks);
+                mTasks.clear();
+                done = mDone;
+            } // unlock mMutex
+
+            for (auto& task : tasks) {
+                task();
+            }
+        }
+    });
+}
+
+BackgroundExecutor::~BackgroundExecutor() {
+    {
+        std::scoped_lock lock(mMutex);
+        mDone = true;
+        mWorkAvailableCv.notify_all();
+    }
+    if (mThread.joinable()) {
+        mThread.join();
+    }
+}
+
+void BackgroundExecutor::execute(std::function<void()> task) {
+    std::scoped_lock lock(mMutex);
+    mTasks.emplace_back(std::move(task));
+    mWorkAvailableCv.notify_all();
+}
+
+} // namespace android
\ No newline at end of file
diff --git a/services/surfaceflinger/BackgroundExecutor.h b/services/surfaceflinger/BackgroundExecutor.h
new file mode 100644
index 0000000..6db7dda
--- /dev/null
+++ b/services/surfaceflinger/BackgroundExecutor.h
@@ -0,0 +1,43 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <android-base/thread_annotations.h>
+#include <utils/Singleton.h>
+#include <condition_variable>
+#include <mutex>
+#include <queue>
+#include <thread>
+
+namespace android {
+
+// Executes tasks off the main thread.
+class BackgroundExecutor : public Singleton<BackgroundExecutor> {
+public:
+    BackgroundExecutor();
+    ~BackgroundExecutor();
+    void execute(std::function<void()>);
+
+private:
+    std::mutex mMutex;
+    std::condition_variable mWorkAvailableCv GUARDED_BY(mMutex);
+    bool mDone GUARDED_BY(mMutex) = false;
+    std::vector<std::function<void()>> mTasks GUARDED_BY(mMutex);
+    std::thread mThread;
+};
+
+} // namespace android
diff --git a/services/surfaceflinger/BufferLayer.cpp b/services/surfaceflinger/BufferLayer.cpp
index 636555e..635b088 100644
--- a/services/surfaceflinger/BufferLayer.cpp
+++ b/services/surfaceflinger/BufferLayer.cpp
@@ -111,6 +111,10 @@
     return ((s.flags & layer_state_t::eLayerOpaque) != 0) || getOpacityForFormat(getPixelFormat());
 }
 
+bool BufferLayer::canReceiveInput() const {
+    return !isHiddenByPolicy() && (mBufferInfo.mBuffer == nullptr || getAlpha() > 0.0f);
+}
+
 bool BufferLayer::isVisible() const {
     return !isHiddenByPolicy() && getAlpha() > 0.0f &&
             (mBufferInfo.mBuffer != nullptr || mSidebandStream != nullptr);
@@ -152,44 +156,15 @@
         return result;
     }
 
-    if (CC_UNLIKELY(mBufferInfo.mBuffer == 0)) {
-        // the texture has not been created yet, this Layer has
-        // in fact never been drawn into. This happens frequently with
-        // SurfaceView because the WindowManager can't know when the client
-        // has drawn the first time.
-
-        // If there is nothing under us, we paint the screen in black, otherwise
-        // we just skip this update.
-
-        // figure out if there is something below us
-        Region under;
-        bool finished = false;
-        mFlinger->mDrawingState.traverseInZOrder([&](Layer* layer) {
-            if (finished || layer == static_cast<BufferLayer const*>(this)) {
-                finished = true;
-                return;
-            }
-
-            under.orSelf(layer->getScreenBounds());
-        });
-        // if not everything below us is covered, we plug the holes!
-        Region holes(targetSettings.clip.subtract(under));
-        if (!holes.isEmpty()) {
-            targetSettings.clearRegion.orSelf(holes);
-        }
-
-        if (mSidebandStream != nullptr) {
-            // For surfaceview of tv sideband, there is no activeBuffer
-            // in bufferqueue, we need return LayerSettings.
-            return result;
-        } else {
-            return std::nullopt;
-        }
+    if (CC_UNLIKELY(mBufferInfo.mBuffer == 0) && mSidebandStream != nullptr) {
+        // For surfaceview of tv sideband, there is no activeBuffer
+        // in bufferqueue, we need return LayerSettings.
+        return result;
     }
     const bool blackOutLayer = (isProtected() && !targetSettings.supportsProtectedContent) ||
             ((isSecure() || isProtected()) && !targetSettings.isSecure);
     const bool bufferCanBeUsedAsHwTexture =
-            mBufferInfo.mBuffer->getBuffer()->getUsage() & GraphicBuffer::USAGE_HW_TEXTURE;
+            mBufferInfo.mBuffer->getUsage() & GraphicBuffer::USAGE_HW_TEXTURE;
     compositionengine::LayerFE::LayerSettings& layer = *result;
     if (blackOutLayer || !bufferCanBeUsedAsHwTexture) {
         ALOGE_IF(!bufferCanBeUsedAsHwTexture, "%s is blacked out as buffer is not gpu readable",
@@ -226,7 +201,7 @@
     }
     layer.source.buffer.maxLuminanceNits = maxLuminance;
     layer.frameNumber = mCurrentFrameNumber;
-    layer.bufferId = mBufferInfo.mBuffer ? mBufferInfo.mBuffer->getBuffer()->getId() : 0;
+    layer.bufferId = mBufferInfo.mBuffer ? mBufferInfo.mBuffer->getId() : 0;
 
     const bool useFiltering =
             targetSettings.needsFiltering || mNeedsFiltering || bufferNeedsFiltering();
@@ -316,15 +291,19 @@
 
     // Sideband layers
     auto* compositionState = editCompositionState();
-    if (compositionState->sidebandStream.get()) {
-        compositionState->compositionType = Hwc2::IComposerClient::Composition::SIDEBAND;
+    if (compositionState->sidebandStream.get() && !compositionState->sidebandStreamHasFrame) {
+        compositionState->compositionType =
+                aidl::android::hardware::graphics::composer3::Composition::SIDEBAND;
         return;
+    } else if ((mDrawingState.flags & layer_state_t::eLayerIsDisplayDecoration) != 0) {
+        compositionState->compositionType =
+                aidl::android::hardware::graphics::composer3::Composition::DISPLAY_DECORATION;
     } else {
         // Normal buffer layers
         compositionState->hdrMetadata = mBufferInfo.mHdrMetadata;
         compositionState->compositionType = mPotentialCursor
-                ? Hwc2::IComposerClient::Composition::CURSOR
-                : Hwc2::IComposerClient::Composition::DEVICE;
+                ? aidl::android::hardware::graphics::composer3::Composition::CURSOR
+                : aidl::android::hardware::graphics::composer3::Composition::DEVICE;
     }
 
     compositionState->buffer = getBuffer();
@@ -332,6 +311,8 @@
             ? 0
             : mBufferInfo.mBufferSlot;
     compositionState->acquireFence = mBufferInfo.mFence;
+    compositionState->frameNumber = mBufferInfo.mFrameNumber;
+    compositionState->sidebandStreamHasFrame = false;
 }
 
 bool BufferLayer::onPreComposition(nsecs_t refreshStartTime) {
@@ -339,7 +320,6 @@
         Mutex::Autolock lock(mFrameEventHistoryMutex);
         mFrameEventHistory.addPreComposition(mCurrentFrameNumber, refreshStartTime);
     }
-    mRefreshPending = false;
     return hasReadyFrame();
 }
 namespace {
@@ -374,13 +354,15 @@
 }
 } // namespace
 
-bool BufferLayer::onPostComposition(const DisplayDevice* display,
+void BufferLayer::onPostComposition(const DisplayDevice* display,
                                     const std::shared_ptr<FenceTime>& glDoneFence,
                                     const std::shared_ptr<FenceTime>& presentFence,
                                     const CompositorTiming& compositorTiming) {
     // mFrameLatencyNeeded is true when a new frame was latched for the
     // composition.
-    if (!mBufferInfo.mFrameLatencyNeeded) return false;
+    if (!mBufferInfo.mFrameLatencyNeeded) return;
+
+    mAlreadyDisplayedThisCompose = false;
 
     // Update mFrameEventHistory.
     {
@@ -422,15 +404,16 @@
     }
 
     if (display) {
-        const Fps refreshRate = display->refreshRateConfigs().getCurrentRefreshRate().getFps();
+        const Fps refreshRate = display->refreshRateConfigs().getActiveMode()->getFps();
         const std::optional<Fps> renderRate =
                 mFlinger->mScheduler->getFrameRateOverride(getOwnerUid());
+
+        const auto vote = frameRateToSetFrameRateVotePayload(mDrawingState.frameRate);
+        const auto gameMode = getGameMode();
+
         if (presentFence->isValid()) {
             mFlinger->mTimeStats->setPresentFence(layerId, mCurrentFrameNumber, presentFence,
-                                                  refreshRate, renderRate,
-                                                  frameRateToSetFrameRateVotePayload(
-                                                          mDrawingState.frameRate),
-                                                  getGameMode());
+                                                  refreshRate, renderRate, vote, gameMode);
             mFlinger->mFrameTracer->traceFence(layerId, getCurrentBufferId(), mCurrentFrameNumber,
                                                presentFence,
                                                FrameTracer::FrameEvent::PRESENT_FENCE);
@@ -441,10 +424,7 @@
             // timestamp instead.
             const nsecs_t actualPresentTime = display->getRefreshTimestamp();
             mFlinger->mTimeStats->setPresentTime(layerId, mCurrentFrameNumber, actualPresentTime,
-                                                 refreshRate, renderRate,
-                                                 frameRateToSetFrameRateVotePayload(
-                                                         mDrawingState.frameRate),
-                                                 getGameMode());
+                                                 refreshRate, renderRate, vote, gameMode);
             mFlinger->mFrameTracer->traceTimestamp(layerId, getCurrentBufferId(),
                                                    mCurrentFrameNumber, actualPresentTime,
                                                    FrameTracer::FrameEvent::PRESENT_FENCE);
@@ -454,12 +434,11 @@
 
     mFrameTracker.advanceFrame();
     mBufferInfo.mFrameLatencyNeeded = false;
-    return true;
 }
 
 void BufferLayer::gatherBufferInfo() {
     mBufferInfo.mPixelFormat =
-            !mBufferInfo.mBuffer ? PIXEL_FORMAT_NONE : mBufferInfo.mBuffer->getBuffer()->format;
+            !mBufferInfo.mBuffer ? PIXEL_FORMAT_NONE : mBufferInfo.mBuffer->getPixelFormat();
     mBufferInfo.mFrameLatencyNeeded = true;
 }
 
@@ -497,24 +476,11 @@
         return refreshRequired;
     }
 
-    if (!hasReadyFrame()) {
-        return false;
-    }
-
-    // if we've already called updateTexImage() without going through
-    // a composition step, we have to skip this layer at this point
-    // because we cannot call updateTeximage() without a corresponding
-    // compositionComplete() call.
-    // we'll trigger an update in onPreComposition().
-    if (mRefreshPending) {
-        return false;
-    }
-
     // If the head buffer's acquire fence hasn't signaled yet, return and
     // try again later
     if (!fenceHasSignaled()) {
         ATRACE_NAME("!fenceHasSignaled()");
-        mFlinger->signalLayerUpdate();
+        mFlinger->onLayerUpdate();
         return false;
     }
 
@@ -541,7 +507,6 @@
 
     gatherBufferInfo();
 
-    mRefreshPending = true;
     if (oldBufferInfo.mBuffer == nullptr) {
         // the first time we receive a buffer, we need to trigger a
         // geometry invalidation.
@@ -556,10 +521,10 @@
     }
 
     if (oldBufferInfo.mBuffer != nullptr) {
-        uint32_t bufWidth = mBufferInfo.mBuffer->getBuffer()->getWidth();
-        uint32_t bufHeight = mBufferInfo.mBuffer->getBuffer()->getHeight();
-        if (bufWidth != uint32_t(oldBufferInfo.mBuffer->getBuffer()->width) ||
-            bufHeight != uint32_t(oldBufferInfo.mBuffer->getBuffer()->height)) {
+        uint32_t bufWidth = mBufferInfo.mBuffer->getWidth();
+        uint32_t bufHeight = mBufferInfo.mBuffer->getHeight();
+        if (bufWidth != oldBufferInfo.mBuffer->getWidth() ||
+            bufHeight != oldBufferInfo.mBuffer->getHeight()) {
             recomputeVisibleRegions = true;
         }
     }
@@ -581,7 +546,7 @@
 
 bool BufferLayer::isProtected() const {
     return (mBufferInfo.mBuffer != nullptr) &&
-            (mBufferInfo.mBuffer->getBuffer()->getUsage() & GRALLOC_USAGE_PROTECTED);
+            (mBufferInfo.mBuffer->getUsage() & GRALLOC_USAGE_PROTECTED);
 }
 
 // As documented in libhardware header, formats in the range
@@ -591,15 +556,16 @@
 // hardware.h, instead of using hard-coded values here.
 #define HARDWARE_IS_DEVICE_FORMAT(f) ((f) >= 0x100 && (f) <= 0x1FF)
 
-bool BufferLayer::getOpacityForFormat(uint32_t format) {
+bool BufferLayer::getOpacityForFormat(PixelFormat format) {
     if (HARDWARE_IS_DEVICE_FORMAT(format)) {
         return true;
     }
     switch (format) {
-        case HAL_PIXEL_FORMAT_RGBA_8888:
-        case HAL_PIXEL_FORMAT_BGRA_8888:
-        case HAL_PIXEL_FORMAT_RGBA_FP16:
-        case HAL_PIXEL_FORMAT_RGBA_1010102:
+        case PIXEL_FORMAT_RGBA_8888:
+        case PIXEL_FORMAT_BGRA_8888:
+        case PIXEL_FORMAT_RGBA_FP16:
+        case PIXEL_FORMAT_RGBA_1010102:
+        case PIXEL_FORMAT_R_8:
             return false;
     }
     // in all other case, we have no blending (also for unknown formats)
@@ -648,14 +614,6 @@
     return sourceCrop.getHeight() != frameHeight || sourceCrop.getWidth() != frameWidth;
 }
 
-uint64_t BufferLayer::getHeadFrameNumber(nsecs_t expectedPresentTime) const {
-    if (hasFrameUpdate()) {
-        return getFrameNumber(expectedPresentTime);
-    } else {
-        return mCurrentFrameNumber;
-    }
-}
-
 Rect BufferLayer::getBufferSize(const State& s) const {
     // If we have a sideband stream, or we are scaling the buffer then return the layer size since
     // we cannot determine the buffer size.
@@ -668,8 +626,8 @@
         return Rect::INVALID_RECT;
     }
 
-    uint32_t bufWidth = mBufferInfo.mBuffer->getBuffer()->getWidth();
-    uint32_t bufHeight = mBufferInfo.mBuffer->getBuffer()->getHeight();
+    uint32_t bufWidth = mBufferInfo.mBuffer->getWidth();
+    uint32_t bufHeight = mBufferInfo.mBuffer->getHeight();
 
     // Undo any transformations on the buffer and return the result.
     if (mBufferInfo.mTransform & ui::Transform::ROT_90) {
@@ -700,8 +658,8 @@
         return parentBounds;
     }
 
-    uint32_t bufWidth = mBufferInfo.mBuffer->getBuffer()->getWidth();
-    uint32_t bufHeight = mBufferInfo.mBuffer->getBuffer()->getHeight();
+    uint32_t bufWidth = mBufferInfo.mBuffer->getWidth();
+    uint32_t bufHeight = mBufferInfo.mBuffer->getHeight();
 
     // Undo any transformations on the buffer and return the result.
     if (mBufferInfo.mTransform & ui::Transform::ROT_90) {
@@ -719,7 +677,6 @@
 }
 
 void BufferLayer::latchAndReleaseBuffer() {
-    mRefreshPending = false;
     if (hasReadyFrame()) {
         bool ignored = false;
         latchBuffer(ignored, systemTime(), 0 /* expectedPresentTime */);
@@ -743,7 +700,7 @@
         return mBufferInfo.mCrop;
     } else if (mBufferInfo.mBuffer != nullptr) {
         // otherwise we use the whole buffer
-        return mBufferInfo.mBuffer->getBuffer()->getBounds();
+        return mBufferInfo.mBuffer->getBounds();
     } else {
         // if we don't have a buffer yet, we use an empty/invalid crop
         return Rect();
@@ -850,6 +807,10 @@
     return isFixedSize();
 }
 
+const std::shared_ptr<renderengine::ExternalTexture>& BufferLayer::getExternalTexture() const {
+    return mBufferInfo.mBuffer;
+}
+
 } // namespace android
 
 #if defined(__gl_h_)
diff --git a/services/surfaceflinger/BufferLayer.h b/services/surfaceflinger/BufferLayer.h
index 760c8b9..3e70493 100644
--- a/services/surfaceflinger/BufferLayer.h
+++ b/services/surfaceflinger/BufferLayer.h
@@ -62,6 +62,7 @@
     void useEmptyDamage() override;
 
     bool isOpaque(const Layer::State& s) const override;
+    bool canReceiveInput() const override;
 
     // isVisible - true if this layer is visible, false otherwise
     bool isVisible() const override;
@@ -77,7 +78,7 @@
 
     bool isHdrY410() const override;
 
-    bool onPostComposition(const DisplayDevice*, const std::shared_ptr<FenceTime>& glDoneFence,
+    void onPostComposition(const DisplayDevice*, const std::shared_ptr<FenceTime>& glDoneFence,
                            const std::shared_ptr<FenceTime>& presentFence,
                            const CompositorTiming&) override;
 
@@ -87,9 +88,6 @@
     // to figure out if the content or size of a surface has changed.
     bool latchBuffer(bool& recomputeVisibleRegions, nsecs_t latchTime,
                      nsecs_t expectedPresentTime) override;
-
-    bool isBufferLatched() const override { return mRefreshPending; }
-
     bool hasReadyFrame() const override;
 
     // Returns the current scaling mode
@@ -110,6 +108,7 @@
     ui::Dataspace getDataSpace() const override;
 
     sp<GraphicBuffer> getBuffer() const override;
+    const std::shared_ptr<renderengine::ExternalTexture>& getExternalTexture() const override;
 
     ui::Transform::RotationFlags getTransformHint() const override { return mTransformHint; }
 
@@ -152,27 +151,22 @@
     bool onPreComposition(nsecs_t) override;
     void preparePerFrameCompositionState() override;
 
-    static bool getOpacityForFormat(uint32_t format);
+    static bool getOpacityForFormat(PixelFormat format);
 
     // from graphics API
     const uint32_t mTextureName;
-
-    bool mRefreshPending{false};
-
     ui::Dataspace translateDataspace(ui::Dataspace dataspace);
     void setInitialValuesForClone(const sp<Layer>& clonedFrom);
     void updateCloneBufferInfo() override;
     uint64_t mPreviousFrameNumber = 0;
 
-    uint64_t getHeadFrameNumber(nsecs_t expectedPresentTime) const override;
-
     void setTransformHint(ui::Transform::RotationFlags displayTransformHint) override;
 
     // Transform hint provided to the producer. This must be accessed holding
     // the mStateLock.
     ui::Transform::RotationFlags mTransformHint = ui::Transform::ROT_0;
 
-    bool getAutoRefresh() const { return mAutoRefresh; }
+    bool getAutoRefresh() const { return mDrawingState.autoRefresh; }
     bool getSidebandStreamChanged() const { return mSidebandStreamChanged; }
 
     // Returns true if the next buffer should be presented at the expected present time
@@ -183,14 +177,11 @@
     // specific logic
     virtual bool isBufferDue(nsecs_t /*expectedPresentTime*/) const = 0;
 
-    std::atomic<bool> mAutoRefresh{false};
     std::atomic<bool> mSidebandStreamChanged{false};
 
 private:
     virtual bool fenceHasSignaled() const = 0;
     virtual bool framePresentTimeIsCurrent(nsecs_t expectedPresentTime) const = 0;
-    virtual uint64_t getFrameNumber(nsecs_t expectedPresentTime) const = 0;
-
 
     // Latch sideband stream and returns true if the dirty region should be updated.
     virtual bool latchSidebandStream(bool& recomputeVisibleRegions) = 0;
diff --git a/services/surfaceflinger/BufferLayerConsumer.cpp b/services/surfaceflinger/BufferLayerConsumer.cpp
index 96b2247..9ae45fc 100644
--- a/services/surfaceflinger/BufferLayerConsumer.cpp
+++ b/services/surfaceflinger/BufferLayerConsumer.cpp
@@ -41,6 +41,7 @@
 #include <gui/SurfaceComposerClient.h>
 #include <private/gui/ComposerService.h>
 #include <renderengine/RenderEngine.h>
+#include <renderengine/impl/ExternalTexture.h>
 #include <utils/Log.h>
 #include <utils/String8.h>
 #include <utils/Trace.h>
@@ -208,8 +209,9 @@
         if (mImages[item->mSlot] == nullptr || mImages[item->mSlot]->getBuffer() == nullptr ||
             mImages[item->mSlot]->getBuffer()->getId() != item->mGraphicBuffer->getId()) {
             mImages[item->mSlot] = std::make_shared<
-                    renderengine::ExternalTexture>(item->mGraphicBuffer, mRE,
-                                                   renderengine::ExternalTexture::Usage::READABLE);
+                    renderengine::impl::ExternalTexture>(item->mGraphicBuffer, mRE,
+                                                         renderengine::impl::ExternalTexture::
+                                                                 Usage::READABLE);
         }
     }
 
@@ -438,7 +440,7 @@
 }
 
 void BufferLayerConsumer::onSidebandStreamChanged() {
-    FrameAvailableListener* unsafeFrameAvailableListener = nullptr;
+    [[maybe_unused]] FrameAvailableListener* unsafeFrameAvailableListener = nullptr;
     {
         Mutex::Autolock lock(mFrameAvailableMutex);
         unsafeFrameAvailableListener = mFrameAvailableListener.unsafe_get();
@@ -462,8 +464,9 @@
         if (oldImage == nullptr || oldImage->getBuffer() == nullptr ||
             oldImage->getBuffer()->getId() != item.mGraphicBuffer->getId()) {
             mImages[item.mSlot] = std::make_shared<
-                    renderengine::ExternalTexture>(item.mGraphicBuffer, mRE,
-                                                   renderengine::ExternalTexture::Usage::READABLE);
+                    renderengine::impl::ExternalTexture>(item.mGraphicBuffer, mRE,
+                                                         renderengine::impl::ExternalTexture::
+                                                                 Usage::READABLE);
         }
     }
 }
diff --git a/services/surfaceflinger/BufferQueueLayer.cpp b/services/surfaceflinger/BufferQueueLayer.cpp
index 99e470d..926aa1d 100644
--- a/services/surfaceflinger/BufferQueueLayer.cpp
+++ b/services/surfaceflinger/BufferQueueLayer.cpp
@@ -17,7 +17,6 @@
 // TODO(b/129481165): remove the #pragma below and fix conversion issues
 #pragma clang diagnostic push
 #pragma clang diagnostic ignored "-Wconversion"
-#pragma clang diagnostic ignored "-Wextra"
 
 #undef LOG_TAG
 #define LOG_TAG "BufferQueueLayer"
@@ -49,7 +48,9 @@
 // Interface implementation for Layer
 // -----------------------------------------------------------------------
 
-void BufferQueueLayer::onLayerDisplayed(const sp<Fence>& releaseFence) {
+void BufferQueueLayer::onLayerDisplayed(
+        std::shared_future<renderengine::RenderEngineResult> futureRenderEngineResult) {
+    sp<Fence> releaseFence = new Fence(dup(futureRenderEngineResult.get().drawFence));
     mConsumer->setReleaseFence(releaseFence);
 
     // Prevent tracing the same release multiple times.
@@ -66,16 +67,6 @@
     mConsumer->setTransformHint(mTransformHint);
 }
 
-std::vector<OccupancyTracker::Segment> BufferQueueLayer::getOccupancyHistory(bool forceFlush) {
-    std::vector<OccupancyTracker::Segment> history;
-    status_t result = mConsumer->getOccupancyHistory(forceFlush, &history);
-    if (result != NO_ERROR) {
-        ALOGW("[%s] Failed to obtain occupancy history (%d)", getDebugName(), result);
-        return {};
-    }
-    return history;
-}
-
 void BufferQueueLayer::releasePendingBuffer(nsecs_t dequeueReadyTime) {
     if (!mConsumer->releasePendingBuffer()) {
         return;
@@ -127,7 +118,7 @@
 bool BufferQueueLayer::fenceHasSignaled() const {
     Mutex::Autolock lock(mQueueItemLock);
 
-    if (SurfaceFlinger::enableLatchUnsignaled) {
+    if (SurfaceFlinger::enableLatchUnsignaledConfig != LatchUnsignaledConfig::Disabled) {
         return true;
     }
 
@@ -153,54 +144,21 @@
 }
 
 bool BufferQueueLayer::framePresentTimeIsCurrent(nsecs_t expectedPresentTime) const {
+    Mutex::Autolock lock(mQueueItemLock);
+
     if (!hasFrameUpdate() || isRemovedFromCurrentState()) {
         return true;
     }
 
-    Mutex::Autolock lock(mQueueItemLock);
     return mQueueItems[0].item.mTimestamp <= expectedPresentTime;
 }
 
-uint64_t BufferQueueLayer::getFrameNumber(nsecs_t expectedPresentTime) const {
-    Mutex::Autolock lock(mQueueItemLock);
-    uint64_t frameNumber = mQueueItems[0].item.mFrameNumber;
-
-    // The head of the queue will be dropped if there are signaled and timely frames behind it
-    if (isRemovedFromCurrentState()) {
-        expectedPresentTime = 0;
-    }
-
-    for (int i = 1; i < mQueueItems.size(); i++) {
-        const bool fenceSignaled =
-                mQueueItems[i].item.mFenceTime->getSignalTime() != Fence::SIGNAL_TIME_PENDING;
-        if (!fenceSignaled) {
-            break;
-        }
-
-        // We don't drop frames without explicit timestamps
-        if (mQueueItems[i].item.mIsAutoTimestamp) {
-            break;
-        }
-
-        const nsecs_t desiredPresent = mQueueItems[i].item.mTimestamp;
-        if (desiredPresent < expectedPresentTime - BufferQueueConsumer::MAX_REASONABLE_NSEC ||
-            desiredPresent > expectedPresentTime) {
-            break;
-        }
-
-        frameNumber = mQueueItems[i].item.mFrameNumber;
-    }
-
-    return frameNumber;
-}
-
 bool BufferQueueLayer::latchSidebandStream(bool& recomputeVisibleRegions) {
     // We need to update the sideband stream if the layer has both a buffer and a sideband stream.
-    const bool updateSidebandStream = hasFrameUpdate() && mSidebandStream.get();
+    editCompositionState()->sidebandStreamHasFrame = hasFrameUpdate() && mSidebandStream.get();
 
     bool sidebandStreamChanged = true;
-    if (mSidebandStreamChanged.compare_exchange_strong(sidebandStreamChanged, false) ||
-        updateSidebandStream) {
+    if (mSidebandStreamChanged.compare_exchange_strong(sidebandStreamChanged, false)) {
         // mSidebandStreamChanged was changed to false
         mSidebandStream = mConsumer->getSidebandStream();
         auto* layerCompositionState = editCompositionState();
@@ -243,7 +201,7 @@
     uint64_t lastSignaledFrameNumber = mLastFrameNumberReceived;
     {
         Mutex::Autolock lock(mQueueItemLock);
-        for (int i = 0; i < mQueueItems.size(); i++) {
+        for (size_t i = 0; i < mQueueItems.size(); i++) {
             bool fenceSignaled =
                     mQueueItems[i].item.mFenceTime->getSignalTime() != Fence::SIGNAL_TIME_PENDING;
             if (!fenceSignaled) {
@@ -258,24 +216,26 @@
     bool autoRefresh;
     status_t updateResult = mConsumer->updateTexImage(&r, expectedPresentTime, &autoRefresh,
                                                       &queuedBuffer, maxFrameNumberToAcquire);
-    mAutoRefresh = autoRefresh;
+    mDrawingState.autoRefresh = autoRefresh;
     if (updateResult == BufferQueue::PRESENT_LATER) {
         // Producer doesn't want buffer to be displayed yet.  Signal a
         // layer update so we check again at the next opportunity.
-        mFlinger->signalLayerUpdate();
+        mFlinger->onLayerUpdate();
         return BAD_VALUE;
     } else if (updateResult == BufferLayerConsumer::BUFFER_REJECTED) {
         // If the buffer has been rejected, remove it from the shadow queue
         // and return early
         if (queuedBuffer) {
             Mutex::Autolock lock(mQueueItemLock);
-            mConsumer->mergeSurfaceDamage(mQueueItems[0].item.mSurfaceDamage);
-            mFlinger->mTimeStats->removeTimeRecord(layerId, mQueueItems[0].item.mFrameNumber);
-            if (mQueueItems[0].surfaceFrame) {
-                addSurfaceFrameDroppedForBuffer(mQueueItems[0].surfaceFrame);
+            if (mQueuedFrames > 0) {
+                mConsumer->mergeSurfaceDamage(mQueueItems[0].item.mSurfaceDamage);
+                mFlinger->mTimeStats->removeTimeRecord(layerId, mQueueItems[0].item.mFrameNumber);
+                if (mQueueItems[0].surfaceFrame) {
+                    addSurfaceFrameDroppedForBuffer(mQueueItems[0].surfaceFrame);
+                }
+                mQueueItems.erase(mQueueItems.begin());
+                mQueuedFrames--;
             }
-            mQueueItems.erase(mQueueItems.begin());
-            mQueuedFrames--;
         }
         return BAD_VALUE;
     } else if (updateResult != NO_ERROR || mUpdateTexImageFailed) {
@@ -305,6 +265,7 @@
         return BAD_VALUE;
     }
 
+    bool more_frames_pending = false;
     if (queuedBuffer) {
         // Autolock scope
         auto currentFrameNumber = mConsumer->getFrameNumber();
@@ -313,7 +274,7 @@
 
         // Remove any stale buffers that have been dropped during
         // updateTexImage
-        while (mQueueItems[0].item.mFrameNumber != currentFrameNumber) {
+        while (mQueuedFrames > 0 && mQueueItems[0].item.mFrameNumber != currentFrameNumber) {
             mConsumer->mergeSurfaceDamage(mQueueItems[0].item.mSurfaceDamage);
             mFlinger->mTimeStats->removeTimeRecord(layerId, mQueueItems[0].item.mFrameNumber);
             if (mQueueItems[0].surfaceFrame) {
@@ -334,12 +295,13 @@
                                               latchTime);
         }
         mQueueItems.erase(mQueueItems.begin());
+        more_frames_pending = (mQueuedFrames.fetch_sub(1) > 1);
     }
 
     // Decrement the queued-frames count.  Signal another event if we
     // have more frames pending.
-    if ((queuedBuffer && mQueuedFrames.fetch_sub(1) > 1) || mAutoRefresh) {
-        mFlinger->signalLayerUpdate();
+    if ((queuedBuffer && more_frames_pending) || mDrawingState.autoRefresh) {
+        mFlinger->onLayerUpdate();
     }
 
     return NO_ERROR;
@@ -410,8 +372,9 @@
     // Add this buffer from our internal queue tracker
     { // Autolock scope
         const nsecs_t presentTime = item.mIsAutoTimestamp ? 0 : item.mTimestamp;
-        mFlinger->mScheduler->recordLayerHistory(this, presentTime,
-                                                 LayerHistory::LayerUpdateType::Buffer);
+
+        using LayerUpdateType = scheduler::LayerHistory::LayerUpdateType;
+        mFlinger->mScheduler->recordLayerHistory(this, presentTime, LayerUpdateType::Buffer);
 
         Mutex::Autolock lock(mQueueItemLock);
         // Reset the frame number tracker when we receive the first buffer after
@@ -442,7 +405,7 @@
     mFlinger->mInterceptor->saveBufferUpdate(layerId, item.mGraphicBuffer->getWidth(),
                                              item.mGraphicBuffer->getHeight(), item.mFrameNumber);
 
-    mFlinger->signalLayerUpdate();
+    mFlinger->onLayerUpdate();
     mConsumer->onBufferAvailable(item);
 }
 
@@ -488,7 +451,7 @@
     bool sidebandStreamChanged = false;
     if (mSidebandStreamChanged.compare_exchange_strong(sidebandStreamChanged, true)) {
         // mSidebandStreamChanged was changed to true
-        mFlinger->signalLayerUpdate();
+        mFlinger->onLayerUpdate();
     }
 }
 
@@ -561,7 +524,7 @@
 }
 
 sp<Layer> BufferQueueLayer::createClone() {
-    LayerCreationArgs args(mFlinger.get(), nullptr, mName + " (Mirror)", 0, 0, 0, LayerMetadata());
+    LayerCreationArgs args(mFlinger.get(), nullptr, mName + " (Mirror)", 0, LayerMetadata());
     args.textureName = mTextureName;
     sp<BufferQueueLayer> layer = mFlinger->getFactory().createBufferQueueLayer(args);
     layer->setInitialValuesForClone(this);
@@ -625,4 +588,4 @@
 } // namespace android
 
 // TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic pop // ignored "-Wconversion -Wextra"
+#pragma clang diagnostic pop // ignored "-Wconversion"
diff --git a/services/surfaceflinger/BufferQueueLayer.h b/services/surfaceflinger/BufferQueueLayer.h
index b3b7948..c6e0727 100644
--- a/services/surfaceflinger/BufferQueueLayer.h
+++ b/services/surfaceflinger/BufferQueueLayer.h
@@ -42,9 +42,8 @@
     // Implements Layer.
     const char* getType() const override { return "BufferQueueLayer"; }
 
-    void onLayerDisplayed(const sp<Fence>& releaseFence) override;
-
-    std::vector<OccupancyTracker::Segment> getOccupancyHistory(bool forceFlush) override;
+    void onLayerDisplayed(
+            std::shared_future<renderengine::RenderEngineResult> futureRenderEngineResult) override;
 
     // If a buffer was replaced this frame, release the former buffer
     void releasePendingBuffer(nsecs_t dequeueReadyTime) override;
@@ -63,6 +62,11 @@
     status_t setDefaultBufferProperties(uint32_t w, uint32_t h, PixelFormat format);
     sp<IGraphicBufferProducer> getProducer() const;
 
+    void setSizeForTest(uint32_t w, uint32_t h) {
+        mDrawingState.active_legacy.w = w;
+        mDrawingState.active_legacy.h = h;
+    }
+
 protected:
     void gatherBufferInfo() override;
 
@@ -89,7 +93,6 @@
     };
 
 private:
-    uint64_t getFrameNumber(nsecs_t expectedPresentTime) const override;
 
     bool latchSidebandStream(bool& recomputeVisibleRegions) override;
     void setTransformHint(ui::Transform::RotationFlags displayTransformHint) override;
diff --git a/services/surfaceflinger/BufferStateLayer.cpp b/services/surfaceflinger/BufferStateLayer.cpp
index df91904..bcae8d9 100644
--- a/services/surfaceflinger/BufferStateLayer.cpp
+++ b/services/surfaceflinger/BufferStateLayer.cpp
@@ -14,11 +14,6 @@
  * limitations under the License.
  */
 
-// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wconversion"
-#pragma clang diagnostic ignored "-Wextra"
-
 //#define LOG_NDEBUG 0
 #undef LOG_TAG
 #define LOG_TAG "BufferStateLayer"
@@ -45,13 +40,13 @@
 namespace {
 void callReleaseBufferCallback(const sp<ITransactionCompletedListener>& listener,
                                const sp<GraphicBuffer>& buffer, uint64_t framenumber,
-                               const sp<Fence>& releaseFence, uint32_t transformHint,
+                               const sp<Fence>& releaseFence,
                                uint32_t currentMaxAcquiredBufferCount) {
     if (!listener) {
         return;
     }
     listener->onReleaseBuffer({buffer->getId(), framenumber},
-                              releaseFence ? releaseFence : Fence::NO_FENCE, transformHint,
+                              releaseFence ? releaseFence : Fence::NO_FENCE,
                               currentMaxAcquiredBufferCount);
 }
 } // namespace
@@ -69,77 +64,28 @@
     if (mBufferInfo.mBuffer != nullptr) {
         callReleaseBufferCallback(mDrawingState.releaseBufferListener,
                                   mBufferInfo.mBuffer->getBuffer(), mBufferInfo.mFrameNumber,
-                                  mBufferInfo.mFence, mTransformHint,
+                                  mBufferInfo.mFence,
                                   mFlinger->getMaxAcquiredBufferCountForCurrentRefreshRate(
                                           mOwnerUid));
     }
 }
 
-status_t BufferStateLayer::addReleaseFence(const sp<CallbackHandle>& ch,
-                                           const sp<Fence>& fence) {
-    if (ch == nullptr) {
-        return OK;
-    }
-    ch->previousReleaseCallbackId = mPreviousReleaseCallbackId;
-    if (!ch->previousReleaseFence.get()) {
-        ch->previousReleaseFence = fence;
-        return OK;
-    }
-
-    // Below logic is lifted from ConsumerBase.cpp:
-    // Check status of fences first because merging is expensive.
-    // Merging an invalid fence with any other fence results in an
-    // invalid fence.
-    auto currentStatus = ch->previousReleaseFence->getStatus();
-    if (currentStatus == Fence::Status::Invalid) {
-        ALOGE("Existing fence has invalid state, layer: %s", mName.c_str());
-        return BAD_VALUE;
-    }
-
-    auto incomingStatus = fence->getStatus();
-    if (incomingStatus == Fence::Status::Invalid) {
-        ALOGE("New fence has invalid state, layer: %s", mName.c_str());
-        ch->previousReleaseFence = fence;
-        return BAD_VALUE;
-    }
-
-    // If both fences are signaled or both are unsignaled, we need to merge
-    // them to get an accurate timestamp.
-    if (currentStatus == incomingStatus) {
-        char fenceName[32] = {};
-        snprintf(fenceName, 32, "%.28s", mName.c_str());
-        sp<Fence> mergedFence = Fence::merge(
-                fenceName, ch->previousReleaseFence, fence);
-        if (!mergedFence.get()) {
-            ALOGE("failed to merge release fences, layer: %s", mName.c_str());
-            // synchronization is broken, the best we can do is hope fences
-            // signal in order so the new fence will act like a union
-            ch->previousReleaseFence = fence;
-            return BAD_VALUE;
-        }
-        ch->previousReleaseFence = mergedFence;
-    } else if (incomingStatus == Fence::Status::Unsignaled) {
-        // If one fence has signaled and the other hasn't, the unsignaled
-        // fence will approximately correspond with the correct timestamp.
-        // There's a small race if both fences signal at about the same time
-        // and their statuses are retrieved with unfortunate timing. However,
-        // by this point, they will have both signaled and only the timestamp
-        // will be slightly off; any dependencies after this point will
-        // already have been met.
-        ch->previousReleaseFence = fence;
-    }
-    // else if (currentStatus == Fence::Status::Unsignaled) is a no-op.
-
-    return OK;
-}
-
 // -----------------------------------------------------------------------
 // Interface implementation for Layer
 // -----------------------------------------------------------------------
-void BufferStateLayer::onLayerDisplayed(const sp<Fence>& releaseFence) {
-    if (!releaseFence->isValid()) {
-        return;
+void BufferStateLayer::onLayerDisplayed(
+        std::shared_future<renderengine::RenderEngineResult> futureRenderEngineResult) {
+    // If we are displayed on multiple displays in a single composition cycle then we would
+    // need to do careful tracking to enable the use of the mLastClientCompositionFence.
+    //  For example we can only use it if all the displays are client comp, and we need
+    //  to merge all the client comp fences. We could do this, but for now we just
+    // disable the optimization when a layer is composed on multiple displays.
+    if (mAlreadyDisplayedThisCompose) {
+        mLastClientCompositionFence = nullptr;
+    } else {
+        mAlreadyDisplayedThisCompose = true;
     }
+
     // The previous release fence notifies the client that SurfaceFlinger is done with the previous
     // buffer that was presented on this layer. The first transaction that came in this frame that
     // replaced the previous buffer on this layer needs this release fence, because the fence will
@@ -164,17 +110,17 @@
             break;
         }
     }
-    auto status = addReleaseFence(ch, releaseFence);
-    if (status != OK) {
-        ALOGE("Failed to add release fence for layer %s", getName().c_str());
-    }
-
-    mPreviousReleaseFence = releaseFence;
 
     // Prevent tracing the same release multiple times.
     if (mPreviousFrameNumber != mPreviousReleasedFrameNumber) {
         mPreviousReleasedFrameNumber = mPreviousFrameNumber;
     }
+
+    if (ch != nullptr) {
+        ch->previousReleaseCallbackId = mPreviousReleaseCallbackId;
+        ch->previousReleaseFences.emplace_back(futureRenderEngineResult);
+        ch->name = mName;
+    }
 }
 
 void BufferStateLayer::onSurfaceFrameCreated(
@@ -219,12 +165,21 @@
                 JankData(surfaceFrame->getToken(), surfaceFrame->getJankType().value()));
     }
 
-    mFlinger->getTransactionCallbackInvoker().finalizePendingCallbackHandles(
+    mFlinger->getTransactionCallbackInvoker().addCallbackHandles(
             mDrawingState.callbackHandles, jankData);
 
+    sp<Fence> releaseFence = Fence::NO_FENCE;
+    for (auto& handle : mDrawingState.callbackHandles) {
+        if (handle->releasePreviousBuffer &&
+            mDrawingState.releaseBufferEndpoint == handle->listener) {
+            releaseFence =
+                    handle->previousReleaseFence ? handle->previousReleaseFence : Fence::NO_FENCE;
+            break;
+        }
+    }
+
     mDrawingState.callbackHandles = {};
 
-    const sp<Fence>& releaseFence(mPreviousReleaseFence);
     std::shared_ptr<FenceTime> releaseFenceTime = std::make_shared<FenceTime>(releaseFence);
     {
         Mutex::Autolock lock(mFrameEventHistoryMutex);
@@ -314,7 +269,8 @@
 // Translate destination frame into scale and position. If a destination frame is not set, use the
 // provided scale and position
 bool BufferStateLayer::updateGeometry() {
-    if (mDrawingState.destinationFrame.isEmpty()) {
+    if ((mDrawingState.flags & layer_state_t::eIgnoreDestinationFrame) ||
+        mDrawingState.destinationFrame.isEmpty()) {
         // If destination frame is not set, use the requested transform set via
         // BufferStateLayer::setPosition and BufferStateLayer::setMatrix.
         return assignTransform(&mDrawingState.transform, mRequestedTransform);
@@ -338,8 +294,8 @@
         return assignTransform(&mDrawingState.transform, t);
     }
 
-    uint32_t bufferWidth = mDrawingState.buffer->getBuffer()->getWidth();
-    uint32_t bufferHeight = mDrawingState.buffer->getBuffer()->getHeight();
+    uint32_t bufferWidth = mDrawingState.buffer->getWidth();
+    uint32_t bufferHeight = mDrawingState.buffer->getHeight();
     // Undo any transformations on the buffer.
     if (mDrawingState.bufferTransform & ui::Transform::ROT_90) {
         std::swap(bufferWidth, bufferHeight);
@@ -359,8 +315,7 @@
     return assignTransform(&mDrawingState.transform, t);
 }
 
-bool BufferStateLayer::setMatrix(const layer_state_t::matrix22_t& matrix,
-                                 bool allowNonRectPreservingTransforms) {
+bool BufferStateLayer::setMatrix(const layer_state_t::matrix22_t& matrix) {
     if (mRequestedTransform.dsdx() == matrix.dsdx && mRequestedTransform.dtdy() == matrix.dtdy &&
         mRequestedTransform.dtdx() == matrix.dtdx && mRequestedTransform.dsdy() == matrix.dsdy) {
         return false;
@@ -369,12 +324,6 @@
     ui::Transform t;
     t.set(matrix.dsdx, matrix.dtdy, matrix.dtdx, matrix.dsdy);
 
-    if (!allowNonRectPreservingTransforms && !t.preserveRects()) {
-        ALOGW("Attempt to set rotation matrix without permission ACCESS_SURFACE_FLINGER nor "
-              "ROTATE_SURFACE_FLINGER ignored");
-        return false;
-    }
-
     mRequestedTransform.set(matrix.dsdx, matrix.dtdy, matrix.dtdx, matrix.dsdy);
 
     mDrawingState.sequence++;
@@ -411,26 +360,34 @@
     return true;
 }
 
-bool BufferStateLayer::setBuffer(const std::shared_ptr<renderengine::ExternalTexture>& buffer,
-                                 const sp<Fence>& acquireFence, nsecs_t postTime,
+bool BufferStateLayer::setBuffer(std::shared_ptr<renderengine::ExternalTexture>& buffer,
+                                 const BufferData& bufferData, nsecs_t postTime,
                                  nsecs_t desiredPresentTime, bool isAutoTimestamp,
-                                 const client_cache_t& clientCacheId, uint64_t frameNumber,
-                                 std::optional<nsecs_t> dequeueTime, const FrameTimelineInfo& info,
-                                 const sp<ITransactionCompletedListener>& releaseBufferListener,
-                                 const sp<IBinder>& releaseBufferEndpoint) {
+                                 std::optional<nsecs_t> dequeueTime,
+                                 const FrameTimelineInfo& info) {
     ATRACE_CALL();
 
+    if (!buffer) {
+        return false;
+    }
+
+    const bool frameNumberChanged =
+            bufferData.flags.test(BufferData::BufferDataChange::frameNumberChanged);
+    const uint64_t frameNumber =
+            frameNumberChanged ? bufferData.frameNumber : mDrawingState.frameNumber + 1;
+
     if (mDrawingState.buffer) {
         mReleasePreviousBuffer = true;
-        if (mDrawingState.buffer != mBufferInfo.mBuffer ||
-            mDrawingState.frameNumber != mBufferInfo.mFrameNumber) {
+        if (!mBufferInfo.mBuffer ||
+            (!mDrawingState.buffer->hasSameBuffer(*mBufferInfo.mBuffer) ||
+             mDrawingState.frameNumber != mBufferInfo.mFrameNumber)) {
             // If mDrawingState has a buffer, and we are about to update again
             // before swapping to drawing state, then the first buffer will be
             // dropped and we should decrement the pending buffer count and
             // call any release buffer callbacks if set.
             callReleaseBufferCallback(mDrawingState.releaseBufferListener,
                                       mDrawingState.buffer->getBuffer(), mDrawingState.frameNumber,
-                                      mDrawingState.acquireFence, mTransformHint,
+                                      mDrawingState.acquireFence,
                                       mFlinger->getMaxAcquiredBufferCountForCurrentRefreshRate(
                                               mOwnerUid));
             decrementPendingBufferCount();
@@ -439,13 +396,34 @@
               addSurfaceFrameDroppedForBuffer(mDrawingState.bufferSurfaceFrameTX);
               mDrawingState.bufferSurfaceFrameTX.reset();
             }
+        } else if (mLastClientCompositionFence != nullptr) {
+            callReleaseBufferCallback(mDrawingState.releaseBufferListener,
+                                      mDrawingState.buffer->getBuffer(), mDrawingState.frameNumber,
+                                      mLastClientCompositionFence,
+                                      mFlinger->getMaxAcquiredBufferCountForCurrentRefreshRate(
+                                              mOwnerUid));
+            mLastClientCompositionFence = nullptr;
         }
     }
 
     mDrawingState.frameNumber = frameNumber;
-    mDrawingState.releaseBufferListener = releaseBufferListener;
-    mDrawingState.buffer = buffer;
-    mDrawingState.clientCacheId = clientCacheId;
+    mDrawingState.releaseBufferListener = bufferData.releaseBufferListener;
+    mDrawingState.buffer = std::move(buffer);
+    mDrawingState.clientCacheId = bufferData.cachedBuffer;
+
+    mDrawingState.acquireFence = bufferData.flags.test(BufferData::BufferDataChange::fenceChanged)
+            ? bufferData.acquireFence
+            : Fence::NO_FENCE;
+    mDrawingState.acquireFenceTime = std::make_unique<FenceTime>(mDrawingState.acquireFence);
+    if (mDrawingState.acquireFenceTime->getSignalTime() == Fence::SIGNAL_TIME_PENDING) {
+        // We latched this buffer unsiganled, so we need to pass the acquire fence
+        // on the callback instead of just the acquire time, since it's unknown at
+        // this point.
+        mCallbackHandleAcquireTimeOrFence = mDrawingState.acquireFence;
+    } else {
+        mCallbackHandleAcquireTimeOrFence = mDrawingState.acquireFenceTime->getSignalTime();
+    }
+
     mDrawingState.modified = true;
     setTransactionFlags(eTransactionNeeded);
 
@@ -464,15 +442,16 @@
 
         return static_cast<nsecs_t>(0);
     }();
-    mFlinger->mScheduler->recordLayerHistory(this, presentTime,
-                                             LayerHistory::LayerUpdateType::Buffer);
 
-    addFrameEvent(acquireFence, postTime, isAutoTimestamp ? 0 : desiredPresentTime);
+    using LayerUpdateType = scheduler::LayerHistory::LayerUpdateType;
+    mFlinger->mScheduler->recordLayerHistory(this, presentTime, LayerUpdateType::Buffer);
+
+    addFrameEvent(mDrawingState.acquireFence, postTime, isAutoTimestamp ? 0 : desiredPresentTime);
 
     setFrameTimelineVsyncForBufferTransaction(info, postTime);
 
-    if (buffer && dequeueTime && *dequeueTime != 0) {
-        const uint64_t bufferId = buffer->getBuffer()->getId();
+    if (dequeueTime && *dequeueTime != 0) {
+        const uint64_t bufferId = mDrawingState.buffer->getId();
         mFlinger->mFrameTracer->traceNewLayer(layerId, getName().c_str());
         mFlinger->mFrameTracer->traceTimestamp(layerId, bufferId, frameNumber, *dequeueTime,
                                                FrameTracer::FrameEvent::DEQUEUE);
@@ -480,22 +459,9 @@
                                                FrameTracer::FrameEvent::QUEUE);
     }
 
-    mDrawingState.width = mDrawingState.buffer->getBuffer()->getWidth();
-    mDrawingState.height = mDrawingState.buffer->getBuffer()->getHeight();
-    mDrawingState.releaseBufferEndpoint = releaseBufferEndpoint;
-
-    return true;
-}
-
-bool BufferStateLayer::setAcquireFence(const sp<Fence>& fence) {
-    mDrawingState.acquireFence = fence;
-    mDrawingState.acquireFenceTime = std::make_unique<FenceTime>(fence);
-
-    // The acquire fences of BufferStateLayers have already signaled before they are set
-    mCallbackHandleAcquireTime = mDrawingState.acquireFenceTime->getSignalTime();
-
-    mDrawingState.modified = true;
-    setTransactionFlags(eTransactionNeeded);
+    mDrawingState.width = mDrawingState.buffer->getWidth();
+    mDrawingState.height = mDrawingState.buffer->getHeight();
+    mDrawingState.releaseBufferEndpoint = bufferData.releaseBufferEndpoint;
     return true;
 }
 
@@ -544,7 +510,7 @@
     setTransactionFlags(eTransactionNeeded);
     if (!mSidebandStreamChanged.exchange(true)) {
         // mSidebandStreamChanged was false
-        mFlinger->signalLayerUpdate();
+        mFlinger->onLayerUpdate();
     }
     return true;
 }
@@ -566,13 +532,9 @@
         // If this layer will be presented in this frame
         if (willPresent) {
             // If this transaction set an acquire fence on this layer, set its acquire time
-            handle->acquireTime = mCallbackHandleAcquireTime;
+            handle->acquireTimeOrFence = mCallbackHandleAcquireTimeOrFence;
             handle->frameNumber = mDrawingState.frameNumber;
 
-            // Notify the transaction completed thread that there is a pending latched callback
-            // handle
-            mFlinger->getTransactionCallbackInvoker().registerPendingCallbackHandle(handle);
-
             // Store so latched time and release fence can be set
             mDrawingState.callbackHandles.push_back(handle);
 
@@ -583,7 +545,7 @@
     }
 
     mReleasePreviousBuffer = false;
-    mCallbackHandleAcquireTime = -1;
+    mCallbackHandleAcquireTimeOrFence = -1;
 
     return willPresent;
 }
@@ -596,15 +558,15 @@
     return true;
 }
 
-Rect BufferStateLayer::getBufferSize(const State& s) const {
+Rect BufferStateLayer::getBufferSize(const State& /*s*/) const {
     // for buffer state layers we use the display frame size as the buffer size.
 
     if (mBufferInfo.mBuffer == nullptr) {
         return Rect::INVALID_RECT;
     }
 
-    uint32_t bufWidth = mBufferInfo.mBuffer->getBuffer()->getWidth();
-    uint32_t bufHeight = mBufferInfo.mBuffer->getBuffer()->getHeight();
+    uint32_t bufWidth = mBufferInfo.mBuffer->getWidth();
+    uint32_t bufHeight = mBufferInfo.mBuffer->getHeight();
 
     // Undo any transformations on the buffer and return the result.
     if (mBufferInfo.mTransform & ui::Transform::ROT_90) {
@@ -618,7 +580,7 @@
         }
     }
 
-    return Rect(0, 0, bufWidth, bufHeight);
+    return Rect(0, 0, static_cast<int32_t>(bufWidth), static_cast<int32_t>(bufHeight));
 }
 
 FloatRect BufferStateLayer::computeSourceBounds(const FloatRect& parentBounds) const {
@@ -635,7 +597,7 @@
 // Interface implementation for BufferLayer
 // -----------------------------------------------------------------------
 bool BufferStateLayer::fenceHasSignaled() const {
-    if (SurfaceFlinger::enableLatchUnsignaled) {
+    if (SurfaceFlinger::enableLatchUnsignaledConfig != LatchUnsignaledConfig::Disabled) {
         return true;
     }
 
@@ -664,47 +626,15 @@
     return BufferLayer::onPreComposition(refreshStartTime);
 }
 
-uint64_t BufferStateLayer::getFrameNumber(nsecs_t /*expectedPresentTime*/) const {
-    return mDrawingState.frameNumber;
-}
-
-/**
- * This is the frameNumber used for deferred transaction signalling. We need to use this because
- * of cases where we defer a transaction for a surface to itself. In the BLAST world this
- * may not make a huge amount of sense (Why not just merge the Buffer transaction with the
- * deferred transaction?) but this is an important legacy use case, for example moving
- * a window at the same time it draws makes use of this kind of technique. So anyway
- * imagine we have something like this:
- *
- * Transaction { // containing
- *     Buffer -> frameNumber = 2
- *     DeferTransactionUntil -> frameNumber = 2
- *     Random other stuff
- *  }
- * Now imagine mFrameNumber returned mDrawingState.frameNumber (or mCurrentFrameNumber).
- * Prior to doTransaction SurfaceFlinger will call notifyAvailableFrames, but because we
- * haven't swapped mDrawingState to mDrawingState yet we will think the sync point
- * is not ready. So we will return false from applyPendingState and not swap
- * current state to drawing state. But because we don't swap current state
- * to drawing state the number will never update and we will be stuck. This way
- * we can see we need to return the frame number for the buffer we are about
- * to apply.
- */
-uint64_t BufferStateLayer::getHeadFrameNumber(nsecs_t /* expectedPresentTime */) const {
-    return mDrawingState.frameNumber;
-}
-
 void BufferStateLayer::setAutoRefresh(bool autoRefresh) {
-    if (!mAutoRefresh.exchange(autoRefresh)) {
-        mFlinger->signalLayerUpdate();
-    }
+    mDrawingState.autoRefresh = autoRefresh;
 }
 
 bool BufferStateLayer::latchSidebandStream(bool& recomputeVisibleRegions) {
     // We need to update the sideband stream if the layer has both a buffer and a sideband stream.
-    const bool updateSidebandStream = hasFrameUpdate() && mSidebandStream.get();
+    editCompositionState()->sidebandStreamHasFrame = hasFrameUpdate() && mSidebandStream.get();
 
-    if (mSidebandStreamChanged.exchange(false) || updateSidebandStream) {
+    if (mSidebandStreamChanged.exchange(false)) {
         const State& s(getDrawingState());
         // mSidebandStreamChanged was true
         mSidebandStream = s.sidebandStream;
@@ -745,7 +675,7 @@
     }
 
     const int32_t layerId = getSequence();
-    const uint64_t bufferId = mDrawingState.buffer->getBuffer()->getId();
+    const uint64_t bufferId = mDrawingState.buffer->getId();
     const uint64_t frameNumber = mDrawingState.frameNumber;
     const auto acquireFence = std::make_shared<FenceTime>(mDrawingState.acquireFence);
     mFlinger->mTimeStats->setAcquireFence(layerId, frameNumber, acquireFence);
@@ -770,7 +700,7 @@
 
     std::deque<sp<CallbackHandle>> remainingHandles;
     mFlinger->getTransactionCallbackInvoker()
-            .finalizeOnCommitCallbackHandles(mDrawingState.callbackHandles, remainingHandles);
+            .addOnCommitCallbackHandles(mDrawingState.callbackHandles, remainingHandles);
     mDrawingState.callbackHandles = remainingHandles;
 
     mDrawingStateModified = false;
@@ -785,7 +715,7 @@
         return BAD_VALUE;
     }
 
-    if (!mBufferInfo.mBuffer || s.buffer->getBuffer() != mBufferInfo.mBuffer->getBuffer()) {
+    if (!mBufferInfo.mBuffer || !s.buffer->hasSameBuffer(*mBufferInfo.mBuffer)) {
         decrementPendingBufferCount();
     }
 
@@ -817,7 +747,7 @@
     eraseBufferLocked(clientCacheId);
 }
 
-uint32_t BufferStateLayer::HwcSlotGenerator::getHwcCacheSlot(const client_cache_t& clientCacheId) {
+int BufferStateLayer::HwcSlotGenerator::getHwcCacheSlot(const client_cache_t& clientCacheId) {
     std::lock_guard<std::mutex> lock(mMutex);
     auto itr = mCachedBuffers.find(clientCacheId);
     if (itr == mCachedBuffers.end()) {
@@ -828,7 +758,7 @@
     return hwcCacheSlot;
 }
 
-uint32_t BufferStateLayer::HwcSlotGenerator::addCachedBuffer(const client_cache_t& clientCacheId)
+int BufferStateLayer::HwcSlotGenerator::addCachedBuffer(const client_cache_t& clientCacheId)
         REQUIRES(mMutex) {
     if (!clientCacheId.isValid()) {
         ALOGE("invalid process, returning invalid slot");
@@ -837,17 +767,17 @@
 
     ClientCache::getInstance().registerErasedRecipient(clientCacheId, wp<ErasedRecipient>(this));
 
-    uint32_t hwcCacheSlot = getFreeHwcCacheSlot();
+    int hwcCacheSlot = getFreeHwcCacheSlot();
     mCachedBuffers[clientCacheId] = {hwcCacheSlot, mCounter++};
     return hwcCacheSlot;
 }
 
-uint32_t BufferStateLayer::HwcSlotGenerator::getFreeHwcCacheSlot() REQUIRES(mMutex) {
+int BufferStateLayer::HwcSlotGenerator::getFreeHwcCacheSlot() REQUIRES(mMutex) {
     if (mFreeHwcCacheSlots.empty()) {
         evictLeastRecentlyUsed();
     }
 
-    uint32_t hwcCacheSlot = mFreeHwcCacheSlots.top();
+    int hwcCacheSlot = mFreeHwcCacheSlots.top();
     mFreeHwcCacheSlots.pop();
     return hwcCacheSlot;
 }
@@ -910,17 +840,17 @@
 Rect BufferStateLayer::computeBufferCrop(const State& s) {
     if (s.buffer && !s.bufferCrop.isEmpty()) {
         Rect bufferCrop;
-        s.buffer->getBuffer()->getBounds().intersect(s.bufferCrop, &bufferCrop);
+        s.buffer->getBounds().intersect(s.bufferCrop, &bufferCrop);
         return bufferCrop;
     } else if (s.buffer) {
-        return s.buffer->getBuffer()->getBounds();
+        return s.buffer->getBounds();
     } else {
         return s.bufferCrop;
     }
 }
 
 sp<Layer> BufferStateLayer::createClone() {
-    LayerCreationArgs args(mFlinger.get(), nullptr, mName + " (Mirror)", 0, 0, 0, LayerMetadata());
+    LayerCreationArgs args(mFlinger.get(), nullptr, mName + " (Mirror)", 0, LayerMetadata());
     args.textureName = mTextureName;
     sp<BufferStateLayer> layer = mFlinger->getFactory().createBufferStateLayer(args);
     layer->mHwcSlotGenerator = mHwcSlotGenerator;
@@ -934,8 +864,8 @@
         return false;
     }
 
-    uint32_t bufferWidth = s.buffer->getBuffer()->width;
-    uint32_t bufferHeight = s.buffer->getBuffer()->height;
+    int32_t bufferWidth = static_cast<int32_t>(s.buffer->getWidth());
+    int32_t bufferHeight = static_cast<int32_t>(s.buffer->getHeight());
 
     // Undo any transformations on the buffer and return the result.
     if (s.bufferTransform & ui::Transform::ROT_90) {
@@ -993,7 +923,183 @@
     return mDrawingState.transform.transform(bufferBounds);
 }
 
-} // namespace android
+bool BufferStateLayer::simpleBufferUpdate(const layer_state_t& s) const {
+    const uint64_t requiredFlags = layer_state_t::eBufferChanged;
 
-// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic pop // ignored "-Wconversion -Wextra"
+    const uint64_t deniedFlags = layer_state_t::eProducerDisconnect | layer_state_t::eLayerChanged |
+            layer_state_t::eRelativeLayerChanged | layer_state_t::eTransparentRegionChanged |
+            layer_state_t::eFlagsChanged | layer_state_t::eBlurRegionsChanged |
+            layer_state_t::eLayerStackChanged | layer_state_t::eAutoRefreshChanged |
+            layer_state_t::eReparent;
+
+    const uint64_t allowedFlags = layer_state_t::eHasListenerCallbacksChanged |
+            layer_state_t::eFrameRateSelectionPriority | layer_state_t::eFrameRateChanged |
+            layer_state_t::eSurfaceDamageRegionChanged | layer_state_t::eApiChanged |
+            layer_state_t::eMetadataChanged | layer_state_t::eDropInputModeChanged |
+            layer_state_t::eInputInfoChanged;
+
+    if ((s.what & requiredFlags) != requiredFlags) {
+        ALOGV("%s: false [missing required flags 0x%" PRIx64 "]", __func__,
+              (s.what | requiredFlags) & ~s.what);
+        return false;
+    }
+
+    if (s.what & deniedFlags) {
+        ALOGV("%s: false [has denied flags 0x%" PRIx64 "]", __func__, s.what & deniedFlags);
+        return false;
+    }
+
+    if (s.what & allowedFlags) {
+        ALOGV("%s: [has allowed flags 0x%" PRIx64 "]", __func__, s.what & allowedFlags);
+    }
+
+    if (s.what & layer_state_t::ePositionChanged) {
+        if (mRequestedTransform.tx() != s.x || mRequestedTransform.ty() != s.y) {
+            ALOGV("%s: false [ePositionChanged changed]", __func__);
+            return false;
+        }
+    }
+
+    if (s.what & layer_state_t::eAlphaChanged) {
+        if (mDrawingState.color.a != s.alpha) {
+            ALOGV("%s: false [eAlphaChanged changed]", __func__);
+            return false;
+        }
+    }
+
+    if (s.what & layer_state_t::eColorTransformChanged) {
+        if (mDrawingState.colorTransform != s.colorTransform) {
+            ALOGV("%s: false [eColorTransformChanged changed]", __func__);
+            return false;
+        }
+    }
+
+    if (s.what & layer_state_t::eBackgroundColorChanged) {
+        if (mDrawingState.bgColorLayer || s.bgColorAlpha != 0) {
+            ALOGV("%s: false [eBackgroundColorChanged changed]", __func__);
+            return false;
+        }
+    }
+
+    if (s.what & layer_state_t::eMatrixChanged) {
+        if (mRequestedTransform.dsdx() != s.matrix.dsdx ||
+            mRequestedTransform.dtdy() != s.matrix.dtdy ||
+            mRequestedTransform.dtdx() != s.matrix.dtdx ||
+            mRequestedTransform.dsdy() != s.matrix.dsdy) {
+            ALOGV("%s: false [eMatrixChanged changed]", __func__);
+            return false;
+        }
+    }
+
+    if (s.what & layer_state_t::eCornerRadiusChanged) {
+        if (mDrawingState.cornerRadius != s.cornerRadius) {
+            ALOGV("%s: false [eCornerRadiusChanged changed]", __func__);
+            return false;
+        }
+    }
+
+    if (s.what & layer_state_t::eBackgroundBlurRadiusChanged) {
+        if (mDrawingState.backgroundBlurRadius != static_cast<int>(s.backgroundBlurRadius)) {
+            ALOGV("%s: false [eBackgroundBlurRadiusChanged changed]", __func__);
+            return false;
+        }
+    }
+
+    if (s.what & layer_state_t::eTransformChanged) {
+        if (mDrawingState.bufferTransform != s.transform) {
+            ALOGV("%s: false [eTransformChanged changed]", __func__);
+            return false;
+        }
+    }
+
+    if (s.what & layer_state_t::eTransformToDisplayInverseChanged) {
+        if (mDrawingState.transformToDisplayInverse != s.transformToDisplayInverse) {
+            ALOGV("%s: false [eTransformToDisplayInverseChanged changed]", __func__);
+            return false;
+        }
+    }
+
+    if (s.what & layer_state_t::eCropChanged) {
+        if (mDrawingState.crop != s.crop) {
+            ALOGV("%s: false [eCropChanged changed]", __func__);
+            return false;
+        }
+    }
+
+    if (s.what & layer_state_t::eDataspaceChanged) {
+        if (mDrawingState.dataspace != s.dataspace) {
+            ALOGV("%s: false [eDataspaceChanged changed]", __func__);
+            return false;
+        }
+    }
+
+    if (s.what & layer_state_t::eHdrMetadataChanged) {
+        if (mDrawingState.hdrMetadata != s.hdrMetadata) {
+            ALOGV("%s: false [eHdrMetadataChanged changed]", __func__);
+            return false;
+        }
+    }
+
+    if (s.what & layer_state_t::eSidebandStreamChanged) {
+        if (mDrawingState.sidebandStream != s.sidebandStream) {
+            ALOGV("%s: false [eSidebandStreamChanged changed]", __func__);
+            return false;
+        }
+    }
+
+    if (s.what & layer_state_t::eColorSpaceAgnosticChanged) {
+        if (mDrawingState.colorSpaceAgnostic != s.colorSpaceAgnostic) {
+            ALOGV("%s: false [eColorSpaceAgnosticChanged changed]", __func__);
+            return false;
+        }
+    }
+
+    if (s.what & layer_state_t::eShadowRadiusChanged) {
+        if (mDrawingState.shadowRadius != s.shadowRadius) {
+            ALOGV("%s: false [eShadowRadiusChanged changed]", __func__);
+            return false;
+        }
+    }
+
+    if (s.what & layer_state_t::eFixedTransformHintChanged) {
+        if (mDrawingState.fixedTransformHint != s.fixedTransformHint) {
+            ALOGV("%s: false [eFixedTransformHintChanged changed]", __func__);
+            return false;
+        }
+    }
+
+    if (s.what & layer_state_t::eTrustedOverlayChanged) {
+        if (mDrawingState.isTrustedOverlay != s.isTrustedOverlay) {
+            ALOGV("%s: false [eTrustedOverlayChanged changed]", __func__);
+            return false;
+        }
+    }
+
+    if (s.what & layer_state_t::eStretchChanged) {
+        StretchEffect temp = s.stretchEffect;
+        temp.sanitize();
+        if (mDrawingState.stretchEffect != temp) {
+            ALOGV("%s: false [eStretchChanged changed]", __func__);
+            return false;
+        }
+    }
+
+    if (s.what & layer_state_t::eBufferCropChanged) {
+        if (mDrawingState.bufferCrop != s.bufferCrop) {
+            ALOGV("%s: false [eBufferCropChanged changed]", __func__);
+            return false;
+        }
+    }
+
+    if (s.what & layer_state_t::eDestinationFrameChanged) {
+        if (mDrawingState.destinationFrame != s.destinationFrame) {
+            ALOGV("%s: false [eDestinationFrameChanged changed]", __func__);
+            return false;
+        }
+    }
+
+    ALOGV("%s: true", __func__);
+    return true;
+}
+
+} // namespace android
diff --git a/services/surfaceflinger/BufferStateLayer.h b/services/surfaceflinger/BufferStateLayer.h
index 0a0527c..8a696f1 100644
--- a/services/surfaceflinger/BufferStateLayer.h
+++ b/services/surfaceflinger/BufferStateLayer.h
@@ -39,7 +39,9 @@
     // Implements Layer.
     const char* getType() const override { return "BufferStateLayer"; }
 
-    void onLayerDisplayed(const sp<Fence>& releaseFence) override;
+    void onLayerDisplayed(
+            std::shared_future<renderengine::RenderEngineResult> futureRenderEngineResult) override;
+
     void releasePendingBuffer(nsecs_t dequeueReadyTime) override;
 
     void finalizeFrameEventHistory(const std::shared_ptr<FenceTime>& glDoneFence,
@@ -55,13 +57,10 @@
     bool setTransform(uint32_t transform) override;
     bool setTransformToDisplayInverse(bool transformToDisplayInverse) override;
     bool setCrop(const Rect& crop) override;
-    bool setBuffer(const std::shared_ptr<renderengine::ExternalTexture>& buffer,
-                   const sp<Fence>& acquireFence, nsecs_t postTime, nsecs_t desiredPresentTime,
-                   bool isAutoTimestamp, const client_cache_t& clientCacheId, uint64_t frameNumber,
-                   std::optional<nsecs_t> dequeueTime, const FrameTimelineInfo& info,
-                   const sp<ITransactionCompletedListener>& transactionListener,
-                   const sp<IBinder>& releaseBufferEndpoint) override;
-    bool setAcquireFence(const sp<Fence>& fence) override;
+    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) override;
     bool setDataspace(ui::Dataspace dataspace) override;
     bool setHdrMetadata(const HdrMetadata& hdrMetadata) override;
     bool setSurfaceDamageRegion(const Region& surfaceDamage) override;
@@ -71,8 +70,7 @@
     bool addFrameEvent(const sp<Fence>& acquireFence, nsecs_t postedTime,
                        nsecs_t requestedPresentTime) override;
     bool setPosition(float /*x*/, float /*y*/) override;
-    bool setMatrix(const layer_state_t::matrix22_t& /*matrix*/,
-                   bool /*allowNonRectPreservingTransforms*/);
+    bool setMatrix(const layer_state_t::matrix22_t& /*matrix*/);
 
     // Override to ignore legacy layer state properties that are not used by BufferStateLayer
     bool setSize(uint32_t /*w*/, uint32_t /*h*/) override { return false; }
@@ -105,7 +103,6 @@
 
 protected:
     void gatherBufferInfo() override;
-    uint64_t getHeadFrameNumber(nsecs_t expectedPresentTime) const;
     void onSurfaceFrameCreated(const std::shared_ptr<frametimeline::SurfaceFrame>& surfaceFrame);
     ui::Transform getInputTransform() const override;
     Rect getInputBounds() const override;
@@ -120,10 +117,6 @@
     bool updateFrameEventHistory(const sp<Fence>& acquireFence, nsecs_t postedTime,
                                  nsecs_t requestedPresentTime);
 
-    status_t addReleaseFence(const sp<CallbackHandle>& ch, const sp<Fence>& releaseFence);
-
-    uint64_t getFrameNumber(nsecs_t expectedPresentTime) const override;
-
     bool latchSidebandStream(bool& recomputeVisibleRegions) override;
 
     bool hasFrameUpdate() const override;
@@ -143,15 +136,18 @@
 
     bool bufferNeedsFiltering() const override;
 
-    sp<Fence> mPreviousReleaseFence;
+    bool simpleBufferUpdate(const layer_state_t& s) const override;
+
     ReleaseCallbackId mPreviousReleaseCallbackId = ReleaseCallbackId::INVALID_ID;
     uint64_t mPreviousReleasedFrameNumber = 0;
 
+    uint64_t mPreviousBarrierFrameNumber = 0;
+
     bool mReleasePreviousBuffer = false;
 
     // Stores the last set acquire fence signal time used to populate the callback handle's acquire
     // time.
-    nsecs_t mCallbackHandleAcquireTime = -1;
+    std::variant<nsecs_t, sp<Fence>> mCallbackHandleAcquireTimeOrFence = -1;
 
     std::deque<std::shared_ptr<android::frametimeline::SurfaceFrame>> mPendingJankClassifications;
     // An upper bound on the number of SurfaceFrames in the pending classifications deque.
@@ -178,19 +174,19 @@
     class HwcSlotGenerator : public ClientCache::ErasedRecipient {
     public:
         HwcSlotGenerator() {
-            for (uint32_t i = 0; i < BufferQueue::NUM_BUFFER_SLOTS; i++) {
+            for (int i = 0; i < BufferQueue::NUM_BUFFER_SLOTS; i++) {
                 mFreeHwcCacheSlots.push(i);
             }
         }
 
         void bufferErased(const client_cache_t& clientCacheId);
 
-        uint32_t getHwcCacheSlot(const client_cache_t& clientCacheId);
+        int getHwcCacheSlot(const client_cache_t& clientCacheId);
 
     private:
         friend class SlotGenerationTest;
-        uint32_t addCachedBuffer(const client_cache_t& clientCacheId) REQUIRES(mMutex);
-        uint32_t getFreeHwcCacheSlot() REQUIRES(mMutex);
+        int addCachedBuffer(const client_cache_t& clientCacheId) REQUIRES(mMutex);
+        int getFreeHwcCacheSlot() REQUIRES(mMutex);
         void evictLeastRecentlyUsed() REQUIRES(mMutex);
         void eraseBufferLocked(const client_cache_t& clientCacheId) REQUIRES(mMutex);
 
@@ -202,11 +198,10 @@
 
         std::mutex mMutex;
 
-        std::unordered_map<client_cache_t,
-                           std::pair<uint32_t /*HwcCacheSlot*/, uint32_t /*counter*/>,
+        std::unordered_map<client_cache_t, std::pair<int /*HwcCacheSlot*/, uint64_t /*counter*/>,
                            CachedBufferHash>
                 mCachedBuffers GUARDED_BY(mMutex);
-        std::stack<uint32_t /*HwcCacheSlot*/> mFreeHwcCacheSlots GUARDED_BY(mMutex);
+        std::stack<int /*HwcCacheSlot*/> mFreeHwcCacheSlots GUARDED_BY(mMutex);
 
         // The cache increments this counter value when a slot is updated or used.
         // Used to track the least recently-used buffer
diff --git a/services/surfaceflinger/Client.cpp b/services/surfaceflinger/Client.cpp
index aac6c91..6d7b732 100644
--- a/services/surfaceflinger/Client.cpp
+++ b/services/surfaceflinger/Client.cpp
@@ -14,10 +14,6 @@
  * limitations under the License.
  */
 
-// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wconversion"
-
 #include <stdint.h>
 #include <sys/types.h>
 
@@ -76,40 +72,34 @@
     return lbc;
 }
 
-status_t Client::createSurface(const String8& name, uint32_t w, uint32_t h, PixelFormat format,
-                               uint32_t flags, const sp<IBinder>& parentHandle,
-                               LayerMetadata metadata, sp<IBinder>* handle,
-                               sp<IGraphicBufferProducer>* gbp, int32_t* outLayerId,
-                               uint32_t* outTransformHint) {
+status_t Client::createSurface(const String8& name, uint32_t /* w */, uint32_t /* h */,
+                               PixelFormat /* format */, uint32_t flags,
+                               const sp<IBinder>& parentHandle, LayerMetadata metadata,
+                               sp<IBinder>* outHandle, sp<IGraphicBufferProducer>* /* gbp */,
+                               int32_t* outLayerId, uint32_t* outTransformHint) {
     // We rely on createLayer to check permissions.
-    return mFlinger->createLayer(name, this, w, h, format, flags, std::move(metadata), handle, gbp,
-                                 parentHandle, outLayerId, nullptr, outTransformHint);
+    LayerCreationArgs args(mFlinger.get(), this, name.c_str(), flags, std::move(metadata));
+    return mFlinger->createLayer(args, outHandle, parentHandle, outLayerId, nullptr,
+                                 outTransformHint);
 }
 
-status_t Client::createWithSurfaceParent(const String8& name, uint32_t w, uint32_t h,
-                                         PixelFormat format, uint32_t flags,
-                                         const sp<IGraphicBufferProducer>& parent,
-                                         LayerMetadata metadata, sp<IBinder>* handle,
-                                         sp<IGraphicBufferProducer>* gbp, int32_t* outLayerId,
-                                         uint32_t* outTransformHint) {
-    if (mFlinger->authenticateSurfaceTexture(parent) == false) {
-        ALOGE("failed to authenticate surface texture");
-        return BAD_VALUE;
-    }
-
-    const auto& layer = (static_cast<MonitoredProducer*>(parent.get()))->getLayer();
-    if (layer == nullptr) {
-        ALOGE("failed to find parent layer");
-        return BAD_VALUE;
-    }
-
-    return mFlinger->createLayer(name, this, w, h, format, flags, std::move(metadata), handle, gbp,
-                                 nullptr, outLayerId, layer, outTransformHint);
+status_t Client::createWithSurfaceParent(const String8& /* name */, uint32_t /* w */,
+                                         uint32_t /* h */, PixelFormat /* format */,
+                                         uint32_t /* flags */,
+                                         const sp<IGraphicBufferProducer>& /* parent */,
+                                         LayerMetadata /* metadata */, sp<IBinder>* /* handle */,
+                                         sp<IGraphicBufferProducer>* /* gbp */,
+                                         int32_t* /* outLayerId */,
+                                         uint32_t* /* outTransformHint */) {
+    // This api does not make sense with blast since SF no longer tracks IGBP. This api should be
+    // removed.
+    return BAD_VALUE;
 }
 
 status_t Client::mirrorSurface(const sp<IBinder>& mirrorFromHandle, sp<IBinder>* outHandle,
                                int32_t* outLayerId) {
-    return mFlinger->mirrorLayer(this, mirrorFromHandle, outHandle, outLayerId);
+    LayerCreationArgs args(mFlinger.get(), this, "MirrorRoot", 0 /* flags */, LayerMetadata());
+    return mFlinger->mirrorLayer(args, mirrorFromHandle, outHandle, outLayerId);
 }
 
 status_t Client::clearLayerFrameStats(const sp<IBinder>& handle) const {
@@ -132,6 +122,3 @@
 
 // ---------------------------------------------------------------------------
 }; // namespace android
-
-// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic pop // ignored "-Wconversion"
diff --git a/services/surfaceflinger/ClientCache.cpp b/services/surfaceflinger/ClientCache.cpp
index f310738..3c7b9d9 100644
--- a/services/surfaceflinger/ClientCache.cpp
+++ b/services/surfaceflinger/ClientCache.cpp
@@ -21,12 +21,13 @@
 
 #include <cinttypes>
 
+#include <android-base/stringprintf.h>
+#include <renderengine/impl/ExternalTexture.h>
+
 #include "ClientCache.h"
 
 namespace android {
 
-using base::StringAppendF;
-
 ANDROID_SINGLETON_STATIC_INSTANCE(ClientCache);
 
 ClientCache::ClientCache() : mDeathRecipient(new CacheDeathRecipient) {}
@@ -82,10 +83,13 @@
             return false;
         }
 
-        status_t err = token->linkToDeath(mDeathRecipient);
-        if (err != NO_ERROR) {
-            ALOGE("failed to cache buffer: could not link to death");
-            return false;
+        // 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;
+            }
         }
         auto [itr, success] =
                 mBuffers.emplace(processToken,
@@ -106,8 +110,9 @@
                         "Attempted to build the ClientCache before a RenderEngine instance was "
                         "ready!");
     processBuffers[id].buffer = std::make_shared<
-            renderengine::ExternalTexture>(buffer, *mRenderEngine,
-                                           renderengine::ExternalTexture::Usage::READABLE);
+            renderengine::impl::ExternalTexture>(buffer, *mRenderEngine,
+                                                 renderengine::impl::ExternalTexture::Usage::
+                                                         READABLE);
     return true;
 }
 
@@ -212,16 +217,15 @@
 
 void ClientCache::dump(std::string& result) {
     std::lock_guard lock(mMutex);
-    for (auto i : mBuffers) {
-        const sp<IBinder>& cacheOwner = i.second.first;
-        StringAppendF(&result," Cache owner: %p\n", cacheOwner.get());
-        auto &buffers = i.second.second;
-        for (auto& [id, clientCacheBuffer] : buffers) {
-            StringAppendF(&result, "\t ID: %d, Width/Height: %d,%d\n", (int)id,
-                          (int)clientCacheBuffer.buffer->getBuffer()->getWidth(),
-                          (int)clientCacheBuffer.buffer->getBuffer()->getHeight());
+    for (const auto& [_, cache] : mBuffers) {
+        base::StringAppendF(&result, " Cache owner: %p\n", cache.first.get());
+
+        for (const auto& [id, entry] : cache.second) {
+            const auto& buffer = entry.buffer->getBuffer();
+            base::StringAppendF(&result, "\tID: %" PRIu64 ", size: %ux%u\n", id, buffer->getWidth(),
+                                buffer->getHeight());
         }
     }
 }
 
-}; // namespace android
+} // namespace android
diff --git a/services/surfaceflinger/CompositionEngine/Android.bp b/services/surfaceflinger/CompositionEngine/Android.bp
index d738ccd..aefc014 100644
--- a/services/surfaceflinger/CompositionEngine/Android.bp
+++ b/services/surfaceflinger/CompositionEngine/Android.bp
@@ -20,6 +20,7 @@
         "android.hardware.graphics.composer@2.2",
         "android.hardware.graphics.composer@2.3",
         "android.hardware.graphics.composer@2.4",
+        "android.hardware.graphics.composer3-V1-ndk",
         "android.hardware.power@1.0",
         "android.hardware.power@1.3",
         "libbase",
@@ -37,13 +38,16 @@
     static_libs: [
         "libmath",
         "librenderengine",
+        "libtonemap",
         "libtrace_proto",
+        "libaidlcommonsupport",
     ],
     header_libs: [
         "android.hardware.graphics.composer@2.1-command-buffer",
         "android.hardware.graphics.composer@2.2-command-buffer",
         "android.hardware.graphics.composer@2.3-command-buffer",
         "android.hardware.graphics.composer@2.4-command-buffer",
+        "android.hardware.graphics.composer3-command-buffer",
         "libsurfaceflinger_headers",
     ],
 }
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionRefreshArgs.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionRefreshArgs.h
index 554e2f4..f201751 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionRefreshArgs.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionRefreshArgs.h
@@ -47,9 +47,6 @@
     // All the layers that have queued updates.
     Layers layersWithQueuedFrames;
 
-    // If true, forces the entire display to be considered dirty and repainted
-    bool repaintEverything{false};
-
     // Controls how the color mode is chosen for an output
     OutputColorSetting outputColorSetting{OutputColorSetting::kEnhanced};
 
@@ -88,8 +85,11 @@
     // to prevent an early presentation of a frame.
     std::shared_ptr<FenceTime> previousPresentFence;
 
-    // The predicted next invalidation time
-    std::optional<std::chrono::steady_clock::time_point> nextInvalidateTime;
+    // The expected time for the next present
+    nsecs_t expectedPresentTime{0};
+
+    // If set, a frame has been scheduled for that time.
+    std::optional<std::chrono::steady_clock::time_point> scheduledFrameTime;
 };
 
 } // namespace android::compositionengine
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/Display.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/Display.h
index 01dd534..16cb41b 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/Display.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/Display.h
@@ -19,7 +19,7 @@
 #include <cstdint>
 #include <optional>
 
-#include "DisplayHardware/DisplayIdentification.h"
+#include <ui/DisplayIdentification.h>
 
 #include <compositionengine/Output.h>
 
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/DisplayCreationArgs.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/DisplayCreationArgs.h
index 14eddb1..98c4af4 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/DisplayCreationArgs.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/DisplayCreationArgs.h
@@ -36,18 +36,12 @@
 struct DisplayCreationArgs {
     DisplayId id;
 
-    // Unset for virtual displays
-    std::optional<ui::DisplayConnectionType> connectionType;
-
     // Size of the display in pixels
-    ui::Size pixels = ui::Size::INVALID;
+    ui::Size pixels = ui::kInvalidSize;
 
     // True if this display should be considered secure
     bool isSecure = false;
 
-    // Gives the initial layer stack id to be used for the display
-    uint32_t layerStackId = ~0u;
-
     // Optional pointer to the power advisor interface, if one is needed for
     // this display.
     Hwc2::PowerAdvisor* powerAdvisor = nullptr;
@@ -69,11 +63,6 @@
         return *this;
     }
 
-    DisplayCreationArgsBuilder& setConnectionType(ui::DisplayConnectionType connectionType) {
-        mArgs.connectionType = connectionType;
-        return *this;
-    }
-
     DisplayCreationArgsBuilder& setPixels(ui::Size pixels) {
         mArgs.pixels = pixels;
         return *this;
@@ -84,11 +73,6 @@
         return *this;
     }
 
-    DisplayCreationArgsBuilder& setLayerStackId(uint32_t layerStackId) {
-        mArgs.layerStackId = layerStackId;
-        return *this;
-    }
-
     DisplayCreationArgsBuilder& setPowerAdvisor(Hwc2::PowerAdvisor* powerAdvisor) {
         mArgs.powerAdvisor = powerAdvisor;
         return *this;
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/DisplaySurface.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/DisplaySurface.h
index 4502eee..c553fce 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/DisplaySurface.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/DisplaySurface.h
@@ -16,6 +16,8 @@
 
 #pragma once
 
+#include <cinttypes>
+
 #include <ui/Size.h>
 #include <utils/Errors.h>
 #include <utils/RefBase.h>
@@ -47,13 +49,8 @@
     // before composition takes place. The DisplaySurface can use the
     // composition type to decide how to manage the flow of buffers between
     // GPU and HWC for this frame.
-    enum CompositionType {
-        COMPOSITION_UNKNOWN = 0,
-        COMPOSITION_GPU = 1,
-        COMPOSITION_HWC = 2,
-        COMPOSITION_MIXED = COMPOSITION_GPU | COMPOSITION_HWC
-    };
-    virtual status_t prepareFrame(CompositionType compositionType) = 0;
+    enum class CompositionType : uint8_t { Unknown = 0, Gpu = 0b1, Hwc = 0b10, Mixed = Gpu | Hwc };
+    virtual status_t prepareFrame(CompositionType) = 0;
 
     // Inform the surface that GPU composition is complete for this frame, and
     // the surface should make sure that HWComposer has the correct buffer for
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h
index e51019a..e77e155 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h
@@ -16,6 +16,7 @@
 
 #pragma once
 
+#include <future>
 #include <optional>
 #include <ostream>
 #include <unordered_set>
@@ -26,6 +27,7 @@
 #pragma clang diagnostic ignored "-Wextra"
 
 #include <renderengine/LayerSettings.h>
+#include <renderengine/RenderEngine.h>
 
 // TODO(b/129481165): remove the #pragma below and fix conversion issues
 #pragma clang diagnostic pop // ignored "-Wconversion -Wextra"
@@ -115,10 +117,6 @@
         // If set to true, the target buffer has protected content support.
         const bool supportsProtectedContent;
 
-        // Modified by each call to prepareClientComposition to indicate the
-        // region of the target buffer that should be cleared.
-        Region& clearRegion;
-
         // Viewport of the target being rendered to. This is used to determine
         // the shadow light position.
         const Rect& viewport;
@@ -136,6 +134,9 @@
 
         // Configure layer settings for using blurs
         BlurSetting blurSetting;
+
+        // Requested white point of the layer in nits
+        const float whitePointNits;
     };
 
     // A superset of LayerSettings required by RenderEngine to compose a layer
@@ -155,7 +156,7 @@
             ClientCompositionTargetSettings&) = 0;
 
     // Called after the layer is displayed to update the presentation fence
-    virtual void onLayerDisplayed(const sp<Fence>&) = 0;
+    virtual void onLayerDisplayed(std::shared_future<renderengine::RenderEngineResult>) = 0;
 
     // Gets some kind of identifier for the layer for debug purposes.
     virtual const char* getDebugName() const = 0;
@@ -165,6 +166,7 @@
 
     // Whether the layer should be rendered with rounded corners.
     virtual bool hasRoundedCorners() const = 0;
+    virtual void setWasClientComposed(const sp<Fence>&) {}
 };
 
 // TODO(b/121291683): Specialize std::hash<> for sp<T> so these and others can
@@ -177,11 +179,10 @@
 
 static inline bool operator==(const LayerFE::ClientCompositionTargetSettings& lhs,
                               const LayerFE::ClientCompositionTargetSettings& rhs) {
-    return lhs.clip.hasSameRects(rhs.clip) &&
-            lhs.needsFiltering == rhs.needsFiltering && lhs.isSecure == rhs.isSecure &&
+    return lhs.clip.hasSameRects(rhs.clip) && lhs.needsFiltering == rhs.needsFiltering &&
+            lhs.isSecure == rhs.isSecure &&
             lhs.supportsProtectedContent == rhs.supportsProtectedContent &&
-            lhs.clearRegion.hasSameRects(rhs.clearRegion) && lhs.viewport == rhs.viewport &&
-            lhs.dataspace == rhs.dataspace &&
+            lhs.viewport == rhs.viewport && lhs.dataspace == rhs.dataspace &&
             lhs.realContentIsVisible == rhs.realContentIsVisible &&
             lhs.clearContent == rhs.clearContent;
 }
@@ -202,8 +203,6 @@
     *os << "\n    .needsFiltering = " << settings.needsFiltering;
     *os << "\n    .isSecure = " << settings.isSecure;
     *os << "\n    .supportsProtectedContent = " << settings.supportsProtectedContent;
-    *os << "\n    .clearRegion = ";
-    PrintTo(settings.clearRegion, os);
     *os << "\n    .viewport = ";
     PrintTo(settings.viewport, os);
     *os << "\n    .dataspace = ";
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFECompositionState.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFECompositionState.h
index a45be8a..283fe86 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFECompositionState.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFECompositionState.h
@@ -22,6 +22,7 @@
 #include <math/mat4.h>
 #include <ui/BlurRegion.h>
 #include <ui/FloatRect.h>
+#include <ui/LayerStack.h>
 #include <ui/Rect.h>
 #include <ui/Region.h>
 #include <ui/Transform.h>
@@ -38,6 +39,8 @@
 
 #include "DisplayHardware/Hal.h"
 
+#include <aidl/android/hardware/graphics/composer3/Composition.h>
+
 // TODO(b/129481165): remove the #pragma below and fix conversion issues
 #pragma clang diagnostic pop // ignored "-Wconversion -Wextra"
 
@@ -93,11 +96,9 @@
     /*
      * Visibility state
      */
-    // the layer stack this layer belongs to
-    std::optional<uint32_t> layerStackId;
 
-    // If true, this layer should be only visible on the internal display
-    bool internalOnly{false};
+    // The filter that determines which outputs include this layer
+    ui::LayerFilter outputFilter;
 
     // If false, this layer should not be considered visible
     bool isVisible{true};
@@ -157,16 +158,20 @@
      */
 
     // The type of composition for this layer
-    hal::Composition compositionType{hal::Composition::INVALID};
+    aidl::android::hardware::graphics::composer3::Composition compositionType{
+            aidl::android::hardware::graphics::composer3::Composition::INVALID};
 
     // The buffer and related state
     sp<GraphicBuffer> buffer;
     int bufferSlot{BufferQueue::INVALID_BUFFER_SLOT};
-    sp<Fence> acquireFence;
+    sp<Fence> acquireFence = Fence::NO_FENCE;
     Region surfaceDamage;
+    uint64_t frameNumber = 0;
 
     // The handle to use for a sideband stream for this layer
     sp<NativeHandle> sidebandStream;
+    // If true, this sideband layer has a frame update
+    bool sidebandStreamHasFrame{false};
 
     // The color for this layer
     half4 color;
@@ -199,6 +204,9 @@
     // The output-independent frame for the cursor
     Rect cursorFrame;
 
+    // framerate of the layer as measured by LayerHistory
+    float fps;
+
     virtual ~LayerFECompositionState();
 
     // Debugging
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h
index 0ef0b99..d8644a4 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h
@@ -28,12 +28,13 @@
 #include <renderengine/LayerSettings.h>
 #include <ui/Fence.h>
 #include <ui/GraphicTypes.h>
+#include <ui/LayerStack.h>
 #include <ui/Region.h>
 #include <ui/Transform.h>
 #include <utils/StrongPointer.h>
 #include <utils/Vector.h>
 
-#include "DisplayHardware/DisplayIdentification.h"
+#include <ui/DisplayIdentification.h>
 
 namespace android {
 
@@ -172,6 +173,8 @@
     // Sets the projection state to use
     virtual void setProjection(ui::Rotation orientation, const Rect& layerStackSpaceRect,
                                const Rect& orientedDisplaySpaceRect) = 0;
+    // Sets the brightness that will take effect next frame.
+    virtual void setNextBrightness(float brightness) = 0;
     // Sets the bounds to use
     virtual void setDisplaySize(const ui::Size&) = 0;
     // Gets the transform hint used in layers that belong to this output. Used to guide
@@ -180,9 +183,8 @@
     // output.
     virtual ui::Transform::RotationFlags getTransformHint() const = 0;
 
-    // Sets the layer stack filtering settings for this output. See
-    // belongsInOutput for full details.
-    virtual void setLayerStackFilter(uint32_t layerStackId, bool isInternal) = 0;
+    // Sets the filter for this output. See Output::includesLayer.
+    virtual void setLayerFilter(ui::LayerFilter) = 0;
 
     // Sets the output color mode
     virtual void setColorProfile(const ColorProfile&) = 0;
@@ -221,20 +223,12 @@
     virtual OutputCompositionState& editState() = 0;
 
     // Gets the dirty region in layer stack space.
-    // If repaintEverything is true, this will be the full display bounds.
-    virtual Region getDirtyRegion(bool repaintEverything) const = 0;
+    virtual Region getDirtyRegion() const = 0;
 
-    // Tests whether a given layerStackId belongs in this output.
-    // A layer belongs to the output if its layerStackId matches the of the output layerStackId,
-    // unless the layer should display on the primary output only and this is not the primary output
-
-    // A layer belongs to the output if its layerStackId matches. Additionally
-    // if the layer should only show in the internal (primary) display only and
-    // this output allows that.
-    virtual bool belongsInOutput(std::optional<uint32_t> layerStackId, bool internalOnly) const = 0;
-
-    // Determines if a layer belongs to the output.
-    virtual bool belongsInOutput(const sp<LayerFE>&) const = 0;
+    // Returns whether the output includes a layer, based on their respective filters.
+    // See Output::setLayerFilter.
+    virtual bool includesLayer(ui::LayerFilter) const = 0;
+    virtual bool includesLayer(const sp<LayerFE>&) const = 0;
 
     // Returns a pointer to the output layer corresponding to the given layer on
     // this output, or nullptr if the layer does not have one
@@ -294,7 +288,8 @@
     virtual bool getSkipColorTransform() const = 0;
     virtual FrameFences presentAndGetFrameFences() = 0;
     virtual std::vector<LayerFE::LayerSettings> generateClientCompositionRequests(
-            bool supportsProtectedContent, Region& clearRegion, ui::Dataspace outputDataspace) = 0;
+            bool supportsProtectedContent, ui::Dataspace outputDataspace,
+            std::vector<LayerFE*> &outLayerRef) = 0;
     virtual void appendRegionFlashRequests(
             const Region& flashRegion,
             std::vector<LayerFE::LayerSettings>& clientCompositionLayers) = 0;
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/OutputLayer.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/OutputLayer.h
index ead941d..bf5184e 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/OutputLayer.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/OutputLayer.h
@@ -28,11 +28,13 @@
 #pragma clang diagnostic ignored "-Wconversion"
 #pragma clang diagnostic ignored "-Wextra"
 
+#include <ui/DisplayIdentification.h>
 #include "DisplayHardware/ComposerHal.h"
-#include "DisplayHardware/DisplayIdentification.h"
 
 #include "LayerFE.h"
 
+#include <aidl/android/hardware/graphics/composer3/Composition.h>
+
 // TODO(b/129481165): remove the #pragma below and fix conversion issues
 #pragma clang diagnostic pop // ignored "-Wconversion -Wextra"
 
@@ -112,7 +114,8 @@
     virtual bool isHardwareCursor() const = 0;
 
     // Applies a HWC device requested composition type change
-    virtual void applyDeviceCompositionTypeChange(Hwc2::IComposerClient::Composition) = 0;
+    virtual void applyDeviceCompositionTypeChange(
+            aidl::android::hardware::graphics::composer3::Composition) = 0;
 
     // Prepares to apply any HWC device layer requests
     virtual void prepareForDeviceLayerRequests() = 0;
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/ProjectionSpace.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/ProjectionSpace.h
index 58bb41a..a63145a 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/ProjectionSpace.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/ProjectionSpace.h
@@ -28,51 +28,36 @@
 
 // Geometrical space to which content is projected.
 // For example, this can be the layer space or the physical display space.
-struct ProjectionSpace {
+class ProjectionSpace {
+public:
     ProjectionSpace() = default;
-    ProjectionSpace(ui::Size size, Rect content)
-          : bounds(std::move(size)), content(std::move(content)) {}
-
-    // Bounds of this space. Always starts at (0,0).
-    Rect bounds;
-
-    // Rect onto which content is projected.
-    Rect content;
-
-    // The orientation of this space. This value is meaningful only in relation to the rotation
-    // of another projection space and it's used to determine the rotating transformation when
-    // mapping between the two.
-    // As a convention when using this struct orientation = 0 for the "oriented*" projection
-    // spaces. For example when the display is rotated 90 degress counterclockwise, the orientation
-    // of the display space will become 90, while  the orientation of the layer stack space will
-    // remain the same.
-    ui::Rotation orientation = ui::ROTATION_0;
+    ProjectionSpace(ui::Size size, Rect content) : mBounds(size), mContent(std::move(content)) {}
 
     // Returns a transform which maps this.content into destination.content
     // and also rotates according to this.orientation and destination.orientation
     ui::Transform getTransform(const ProjectionSpace& destination) const {
-        ui::Rotation rotation = destination.orientation - orientation;
+        ui::Rotation rotation = destination.getOrientation() - mOrientation;
 
         // Compute a transformation which rotates the destination in a way it has the same
         // orientation as us.
         const uint32_t inverseRotationFlags = ui::Transform::toRotationFlags(-rotation);
         ui::Transform inverseRotatingTransform;
-        inverseRotatingTransform.set(inverseRotationFlags, destination.bounds.width(),
-                                     destination.bounds.height());
+        inverseRotatingTransform.set(inverseRotationFlags, destination.getBounds().width,
+                                     destination.getBounds().height);
         // The destination content rotated so it has the same orientation as us.
-        Rect orientedDestContent = inverseRotatingTransform.transform(destination.content);
+        Rect orientedDestContent = inverseRotatingTransform.transform(destination.getContent());
 
         // Compute translation from the source content to (0, 0).
-        const float sourceX = content.left;
-        const float sourceY = content.top;
+        const float sourceX = mContent.left;
+        const float sourceY = mContent.top;
         ui::Transform sourceTranslation;
         sourceTranslation.set(-sourceX, -sourceY);
 
         // Compute scaling transform which maps source content to destination content, assuming
         // they are both at (0, 0).
         ui::Transform scale;
-        const float scaleX = static_cast<float>(orientedDestContent.width()) / content.width();
-        const float scaleY = static_cast<float>(orientedDestContent.height()) / content.height();
+        const float scaleX = static_cast<float>(orientedDestContent.width()) / mContent.width();
+        const float scaleY = static_cast<float>(orientedDestContent.height()) / mContent.height();
         scale.set(scaleX, 0, 0, scaleY);
 
         // Compute translation from (0, 0) to the orientated destination content.
@@ -83,8 +68,8 @@
 
         // Compute rotation transform.
         const uint32_t orientationFlags = ui::Transform::toRotationFlags(rotation);
-        auto orientedDestWidth = destination.bounds.width();
-        auto orientedDestHeight = destination.bounds.height();
+        auto orientedDestWidth = destination.getBounds().width;
+        auto orientedDestHeight = destination.getBounds().height;
         if (rotation == ui::ROTATION_90 || rotation == ui::ROTATION_270) {
             std::swap(orientedDestWidth, orientedDestHeight);
         }
@@ -98,9 +83,39 @@
     }
 
     bool operator==(const ProjectionSpace& other) const {
-        return bounds == other.bounds && content == other.content &&
-                orientation == other.orientation;
+        return mBounds == other.mBounds && mContent == other.mContent &&
+                mOrientation == other.mOrientation;
     }
+
+    void setBounds(ui::Size newBounds) { mBounds = std::move(newBounds); }
+
+    void setContent(Rect newContent) { mContent = std::move(newContent); }
+
+    void setOrientation(ui::Rotation newOrientation) { mOrientation = newOrientation; }
+
+    Rect getBoundsAsRect() const { return Rect(mBounds.getWidth(), mBounds.getHeight()); }
+
+    const ui::Size& getBounds() const { return mBounds; }
+
+    const Rect& getContent() const { return mContent; }
+
+    ui::Rotation getOrientation() const { return mOrientation; }
+
+private:
+    // Bounds of this space. Always starts at (0,0).
+    ui::Size mBounds = ui::Size();
+
+    // Rect onto which content is projected.
+    Rect mContent = Rect();
+
+    // The orientation of this space. This value is meaningful only in relation to the rotation
+    // of another projection space and it's used to determine the rotating transformation when
+    // mapping between the two.
+    // As a convention when using this struct orientation = 0 for the "oriented*" projection
+    // spaces. For example when the display is rotated 90 degress counterclockwise, the orientation
+    // of the display space will become 90, while  the orientation of the layer stack space will
+    // remain the same.
+    ui::Rotation mOrientation = ui::ROTATION_0;
 };
 
 } // namespace compositionengine
@@ -108,8 +123,8 @@
 inline std::string to_string(const android::compositionengine::ProjectionSpace& space) {
     return android::base::
             StringPrintf("ProjectionSpace(bounds = %s, content = %s, orientation = %s)",
-                         to_string(space.bounds).c_str(), to_string(space.content).c_str(),
-                         toCString(space.orientation));
+                         to_string(space.getBoundsAsRect()).c_str(),
+                         to_string(space.getContent()).c_str(), toCString(space.getOrientation()));
 }
 
 // Defining PrintTo helps with Google Tests.
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Display.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Display.h
index bb540ea..e12d1b4 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Display.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Display.h
@@ -26,7 +26,8 @@
 #include <ui/PixelFormat.h>
 #include <ui/Size.h>
 
-#include "DisplayHardware/DisplayIdentification.h"
+#include <ui/DisplayIdentification.h>
+
 #include "DisplayHardware/HWComposer.h"
 #include "DisplayHardware/PowerAdvisor.h"
 
@@ -76,16 +77,15 @@
     virtual void applyChangedTypesToLayers(const ChangedTypes&);
     virtual void applyDisplayRequests(const DisplayRequests&);
     virtual void applyLayerRequestsToLayers(const LayerRequests&);
-    virtual void applyClientTargetRequests(const ClientTargetProperty&);
+    virtual void applyClientTargetRequests(const ClientTargetProperty&, float brightness);
 
     // Internal
     virtual void setConfiguration(const compositionengine::DisplayCreationArgs&);
     std::unique_ptr<compositionengine::OutputLayer> createOutputLayer(const sp<LayerFE>&) const;
 
 private:
-    bool mIsVirtual = false;
-    bool mIsDisconnected = false;
     DisplayId mId;
+    bool mIsDisconnected = false;
     Hwc2::PowerAdvisor* mPowerAdvisor = nullptr;
 };
 
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/DumpHelpers.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/DumpHelpers.h
index 6b9597b..196af70 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/DumpHelpers.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/DumpHelpers.h
@@ -17,11 +17,13 @@
 #pragma once
 
 #include <cstdint>
+#include <optional>
 #include <string>
 #include <type_traits>
 
 #include <math/mat4.h>
 #include <ui/FloatRect.h>
+#include <ui/LayerStack.h>
 #include <ui/Rect.h>
 #include <ui/Region.h>
 #include <ui/StretchEffect.h>
@@ -52,13 +54,23 @@
     dumpVal(out, name, valueName, static_cast<std::underlying_type_t<EnumType>>(value));
 }
 
-void dumpVal(std::string& out, const char* name, const FloatRect& rect);
-void dumpVal(std::string& out, const char* name, const Rect& rect);
-void dumpVal(std::string& out, const char* name, const Region& region);
-void dumpVal(std::string& out, const char* name, const ui::Transform&);
-void dumpVal(std::string& out, const char* name, const ui::Size&);
+void dumpVal(std::string& out, const char* name, ui::LayerFilter);
+void dumpVal(std::string& out, const char* name, ui::Size);
 
-void dumpVal(std::string& out, const char* name, const mat4& tr);
+void dumpVal(std::string& out, const char* name, const FloatRect&);
+void dumpVal(std::string& out, const char* name, const Rect&);
+void dumpVal(std::string& out, const char* name, const Region&);
+void dumpVal(std::string& out, const char* name, const ui::Transform&);
+void dumpVal(std::string& out, const char* name, const mat4&);
 void dumpVal(std::string& out, const char* name, const StretchEffect&);
 
+template <typename T>
+void dumpVal(std::string& out, const char* name, std::optional<T> value) {
+    if (value.has_value()) {
+        return dumpVal(out, name, *value);
+    } else {
+        return dumpVal(out, name, "nullopt");
+    }
+}
+
 } // namespace android::compositionengine::impl
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h
index ddcc907..a7a8e97 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h
@@ -23,6 +23,7 @@
 #include <compositionengine/impl/planner/Planner.h>
 #include <renderengine/DisplaySettings.h>
 #include <renderengine/LayerSettings.h>
+
 #include <memory>
 #include <utility>
 #include <vector>
@@ -44,8 +45,9 @@
     void setLayerCachingTexturePoolEnabled(bool) override;
     void setProjection(ui::Rotation orientation, const Rect& layerStackSpaceRect,
                        const Rect& orientedDisplaySpaceRect) override;
+    void setNextBrightness(float brightness) override;
     void setDisplaySize(const ui::Size&) override;
-    void setLayerStackFilter(uint32_t layerStackId, bool isInternal) override;
+    void setLayerFilter(ui::LayerFilter) override;
     ui::Transform::RotationFlags getTransformHint() const override;
 
     void setColorTransform(const compositionengine::CompositionRefreshArgs&) override;
@@ -64,9 +66,10 @@
     compositionengine::RenderSurface* getRenderSurface() const override;
     void setRenderSurface(std::unique_ptr<compositionengine::RenderSurface>) override;
 
-    Region getDirtyRegion(bool repaintEverything) const override;
-    bool belongsInOutput(std::optional<uint32_t>, bool) const override;
-    bool belongsInOutput(const sp<LayerFE>&) const override;
+    Region getDirtyRegion() const override;
+
+    bool includesLayer(ui::LayerFilter) const override;
+    bool includesLayer(const sp<LayerFE>&) const override;
 
     compositionengine::OutputLayer* getOutputLayerForLayer(const sp<LayerFE>&) const override;
 
@@ -111,8 +114,8 @@
     bool getSkipColorTransform() const override;
     compositionengine::Output::FrameFences presentAndGetFrameFences() override;
     std::vector<LayerFE::LayerSettings> generateClientCompositionRequests(
-            bool supportsProtectedContent, Region& clearRegion,
-            ui::Dataspace outputDataspace) override;
+          bool supportsProtectedContent, ui::Dataspace outputDataspace,
+          std::vector<LayerFE*> &outLayerFEs) override;
     void appendRegionFlashRequests(const Region&, std::vector<LayerFE::LayerSettings>&) override;
     void setExpensiveRenderingExpected(bool enabled) override;
     void dumpBase(std::string&) const;
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputCompositionState.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputCompositionState.h
index f34cb94..66dd825 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputCompositionState.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputCompositionState.h
@@ -32,6 +32,7 @@
 #pragma clang diagnostic pop // ignored "-Wconversion -Wextra"
 
 #include <compositionengine/ProjectionSpace.h>
+#include <ui/LayerStack.h>
 #include <ui/Rect.h>
 #include <ui/Region.h>
 #include <ui/Transform.h>
@@ -59,11 +60,8 @@
     // If true, the current frame reused the buffer from a previous client composition
     bool reusedClientComposition{false};
 
-    // If true, this output displays layers that are internal-only
-    bool layerStackInternal{false};
-
-    // The layer stack to display on this display
-    uint32_t layerStackId{~0u};
+    // The conditions for including a layer on this output
+    ui::LayerFilter layerFilter;
 
     // The common space for all layers in the layer stack. layerStackSpace.content is the Rect
     // which gets projected on the display. The orientation of this space is always ROTATION_0.
@@ -123,12 +121,22 @@
     // to prevent an early presentation of a frame.
     std::shared_ptr<FenceTime> previousPresentFence;
 
+    // The expected time for the next present
+    nsecs_t expectedPresentTime{0};
+
     // Current display brightness
     float displayBrightnessNits{-1.f};
 
     // SDR white point
     float sdrWhitePointNits{-1.f};
 
+    // Brightness of the client target, normalized to display brightness
+    float clientTargetBrightness{1.f};
+
+    // Display brightness that will take effect this frame.
+    // This is slightly distinct from nits, in that nits cannot be passed to hw composer.
+    std::optional<float> displayBrightness = std::nullopt;
+
     // Debugging
     void dump(std::string& result) const;
 };
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayer.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayer.h
index 244f8ab..d64d676 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayer.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayer.h
@@ -25,7 +25,9 @@
 #include <ui/FloatRect.h>
 #include <ui/Rect.h>
 
-#include "DisplayHardware/DisplayIdentification.h"
+#include <ui/DisplayIdentification.h>
+
+#include <aidl/android/hardware/graphics/composer3/Composition.h>
 
 namespace android::compositionengine {
 
@@ -50,7 +52,8 @@
     HWC2::Layer* getHwcLayer() const override;
     bool requiresClientComposition() const override;
     bool isHardwareCursor() const override;
-    void applyDeviceCompositionTypeChange(Hwc2::IComposerClient::Composition) override;
+    void applyDeviceCompositionTypeChange(
+            aidl::android::hardware::graphics::composer3::Composition) override;
     void prepareForDeviceLayerRequests() override;
     void applyDeviceLayerRequest(Hwc2::IComposerClient::LayerRequest request) override;
     bool needsFiltering() const override;
@@ -68,20 +71,24 @@
 
 private:
     Rect calculateInitialCrop() const;
-    void writeOutputDependentGeometryStateToHWC(HWC2::Layer*, Hwc2::IComposerClient::Composition,
-                                                uint32_t z);
+    void writeOutputDependentGeometryStateToHWC(
+            HWC2::Layer*, aidl::android::hardware::graphics::composer3::Composition, uint32_t z);
     void writeOutputIndependentGeometryStateToHWC(HWC2::Layer*, const LayerFECompositionState&,
                                                   bool skipLayer);
     void writeOutputDependentPerFrameStateToHWC(HWC2::Layer*);
-    void writeOutputIndependentPerFrameStateToHWC(HWC2::Layer*, const LayerFECompositionState&,
-                                                  bool skipLayer);
+    void writeOutputIndependentPerFrameStateToHWC(
+            HWC2::Layer*, const LayerFECompositionState&,
+            aidl::android::hardware::graphics::composer3::Composition compositionType,
+            bool skipLayer);
     void writeSolidColorStateToHWC(HWC2::Layer*, const LayerFECompositionState&);
     void writeSidebandStateToHWC(HWC2::Layer*, const LayerFECompositionState&);
     void writeBufferStateToHWC(HWC2::Layer*, const LayerFECompositionState&, bool skipLayer);
-    void writeCompositionTypeToHWC(HWC2::Layer*, Hwc2::IComposerClient::Composition,
+    void writeCompositionTypeToHWC(HWC2::Layer*,
+                                   aidl::android::hardware::graphics::composer3::Composition,
                                    bool isPeekingThrough, bool skipLayer);
-    void detectDisallowedCompositionTypeChange(Hwc2::IComposerClient::Composition from,
-                                               Hwc2::IComposerClient::Composition to) const;
+    void detectDisallowedCompositionTypeChange(
+            aidl::android::hardware::graphics::composer3::Composition from,
+            aidl::android::hardware::graphics::composer3::Composition to) const;
     bool isClientCompositionForced(bool isPeekingThrough) const;
 };
 
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayerCompositionState.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayerCompositionState.h
index 7564c54..2b383c1 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayerCompositionState.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayerCompositionState.h
@@ -35,6 +35,8 @@
 
 #include "DisplayHardware/ComposerHal.h"
 
+#include <aidl/android/hardware/graphics/composer3/Composition.h>
+
 // TODO(b/129481165): remove the #pragma below and fix conversion issues
 #pragma clang diagnostic pop // ignored "-Wconversion -Wextra"
 
@@ -88,6 +90,10 @@
     // The dataspace for this layer
     ui::Dataspace dataspace{ui::Dataspace::UNKNOWN};
 
+    // A hint to the HWC that this region is transparent and may be skipped in
+    // order to save power.
+    Region outputSpaceBlockingRegionHint;
+
     // Overrides the buffer, acquire fence, and display frame stored in LayerFECompositionState
     struct {
         std::shared_ptr<renderengine::ExternalTexture> buffer = nullptr;
@@ -123,8 +129,8 @@
         std::shared_ptr<HWC2::Layer> hwcLayer;
 
         // The most recently set HWC composition type for this layer
-        Hwc2::IComposerClient::Composition hwcCompositionType{
-                Hwc2::IComposerClient::Composition::INVALID};
+        aidl::android::hardware::graphics::composer3::Composition hwcCompositionType{
+                aidl::android::hardware::graphics::composer3::Composition::INVALID};
 
         // The buffer cache for this layer. This is used to lower the
         // cost of sending reused buffers to the HWC.
@@ -146,6 +152,12 @@
 
     // Timestamp for when the layer is queued for client composition
     nsecs_t clientCompositionTimestamp{0};
+
+    static constexpr float kDefaultWhitePointNits = 200.f;
+    float whitePointNits = kDefaultWhitePointNits;
+    // Dimming ratio of the layer from [0, 1]
+    static constexpr float kDefaultDimmingRatio = 1.f;
+    float dimmingRatio = kDefaultDimmingRatio;
 };
 
 } // namespace compositionengine::impl
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Flattener.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Flattener.h
index a040fa9..92cc484 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Flattener.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Flattener.h
@@ -87,6 +87,13 @@
         const bool mEnableHolePunch;
     };
 
+    // Constants not yet backed by a sysprop
+    // CachedSets that contain no more than this many layers may be considered inactive on the basis
+    // of FPS.
+    static constexpr int kNumLayersFpsConsideration = 1;
+    // Frames/Second threshold below which these CachedSets may be considered inactive.
+    static constexpr float kFpsActiveThreshold = 1.f;
+
     Flattener(renderengine::RenderEngine& renderEngine, const Tunables& tunables);
 
     void setDisplaySize(ui::Size size) {
@@ -119,29 +126,30 @@
                              std::chrono::steady_clock::time_point now);
 
     // A Run is a sequence of CachedSets, which is a candidate for flattening into a single
-    // CachedSet. Because it is wasteful to flatten 1 CachedSet, a Run must contain more than 1
-    // CachedSet
+    // CachedSet. Because it is wasteful to flatten 1 CachedSet, a run must contain more than
+    // 1 CachedSet or be used for a hole punch.
     class Run {
     public:
         // A builder for a Run, to aid in construction
         class Builder {
         private:
             std::vector<CachedSet>::const_iterator mStart;
-            std::vector<size_t> mLengths;
+            int32_t mNumSets = 0;
             const CachedSet* mHolePunchCandidate = nullptr;
             const CachedSet* mBlurringLayer = nullptr;
+            bool mBuilt = false;
 
         public:
             // Initializes a Builder a CachedSet to start from.
             // This start iterator must be an iterator for mLayers
             void init(const std::vector<CachedSet>::const_iterator& start) {
                 mStart = start;
-                mLengths.push_back(start->getLayerCount());
+                mNumSets = 1;
             }
 
             // Appends a new CachedSet to the end of the run
             // The provided length must be the size of the next sequential CachedSet in layers
-            void append(size_t length) { mLengths.push_back(length); }
+            void increment() { mNumSets++; }
 
             // Sets the hole punch candidate for the Run.
             void setHolePunchCandidate(const CachedSet* holePunchCandidate) {
@@ -154,13 +162,36 @@
 
             // Builds a Run instance, if a valid Run may be built.
             std::optional<Run> validateAndBuild() {
-                if (mLengths.size() <= 1) {
+                const bool built = mBuilt;
+                mBuilt = true;
+                if (mNumSets <= 0 || built) {
                     return std::nullopt;
                 }
 
+                const bool requiresHolePunch =
+                        mHolePunchCandidate && mHolePunchCandidate->requiresHolePunch();
+
+                if (!requiresHolePunch) {
+                    // If we don't require a hole punch, then treat solid color layers at the front
+                    // to be "cheap", so remove them from the candidate cached set.
+                    while (mNumSets > 1 && mStart->getLayerCount() == 1 &&
+                           mStart->getFirstLayer().getBuffer() == nullptr) {
+                        mStart++;
+                        mNumSets--;
+                    }
+
+                    // Only allow for single cached sets if a hole punch is required. If we're here,
+                    // then we don't require a hole punch, so don't build a run.
+                    if (mNumSets <= 1) {
+                        return std::nullopt;
+                    }
+                }
+
                 return Run(mStart,
-                           std::reduce(mLengths.cbegin(), mLengths.cend(), 0u,
-                                       [](size_t left, size_t right) { return left + right; }),
+                           std::reduce(mStart, mStart + mNumSets, 0u,
+                                       [](size_t length, const CachedSet& set) {
+                                           return length + set.getLayerCount();
+                                       }),
                            mHolePunchCandidate, mBlurringLayer);
             }
 
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/LayerState.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/LayerState.h
index 5237527..cb00e71 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/LayerState.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/LayerState.h
@@ -28,6 +28,9 @@
 #include "DisplayHardware/Hal.h"
 #include "math/HashCombine.h"
 
+#include <aidl/android/hardware/graphics/common/BufferUsage.h>
+#include <aidl/android/hardware/graphics/composer3/Composition.h>
+
 namespace std {
 template <typename T>
 struct hash<android::sp<T>> {
@@ -232,7 +235,7 @@
         return mBackgroundBlurRadius.get() > 0 || !mBlurRegions.get().empty();
     }
     int32_t getBackgroundBlurRadius() const { return mBackgroundBlurRadius.get(); }
-    hardware::graphics::composer::hal::Composition getCompositionType() const {
+    aidl::android::hardware::graphics::composer3::Composition getCompositionType() const {
         return mCompositionType.get();
     }
 
@@ -245,6 +248,7 @@
     bool isProtected() const {
         return getOutputLayer()->getLayerFE().getCompositionState()->hasProtectedContent;
     }
+    float getFps() const { return getOutputLayer()->getLayerFE().getCompositionState()->fps; }
 
     void dump(std::string& result) const;
     std::optional<std::string> compare(const LayerState& other) const;
@@ -369,17 +373,18 @@
 
     OutputLayerState<mat4, LayerStateField::ColorTransform> mColorTransform;
 
-    using CompositionTypeState = OutputLayerState<hardware::graphics::composer::hal::Composition,
-                                                  LayerStateField::CompositionType>;
-    CompositionTypeState
-            mCompositionType{[](auto layer) {
-                                 return layer->getState().forceClientComposition
-                                         ? hardware::graphics::composer::hal::Composition::CLIENT
-                                         : layer->getLayerFE()
-                                                   .getCompositionState()
-                                                   ->compositionType;
-                             },
-                             CompositionTypeState::getHalToStrings()};
+    using CompositionTypeState =
+            OutputLayerState<aidl::android::hardware::graphics::composer3::Composition,
+                             LayerStateField::CompositionType>;
+    CompositionTypeState mCompositionType{[](auto layer) {
+                                              return layer->getState().forceClientComposition
+                                                      ? aidl::android::hardware::graphics::
+                                                                composer3::Composition::CLIENT
+                                                      : layer->getLayerFE()
+                                                                .getCompositionState()
+                                                                ->compositionType;
+                                          },
+                                          CompositionTypeState::getHalToStrings()};
 
     OutputLayerState<void*, LayerStateField::SidebandStream>
             mSidebandStream{[](auto layer) {
@@ -391,6 +396,21 @@
                                 return std::vector<std::string>{base::StringPrintf("%p", p)};
                             }};
 
+    static auto constexpr BufferEquals = [](const wp<GraphicBuffer>& lhs,
+                                            const wp<GraphicBuffer>& rhs) -> bool {
+        // Avoid a promotion if the wp<>'s aren't equal
+        if (lhs != rhs) return false;
+
+        // Even if the buffer didn't change, check to see if we need to act as if the buffer changed
+        // anyway. Specifically, look to see if the buffer is FRONT_BUFFER & if so act as if it's
+        // always different
+        using ::aidl::android::hardware::graphics::common::BufferUsage;
+        sp<GraphicBuffer> promotedBuffer = lhs.promote();
+        return !(promotedBuffer &&
+                 ((promotedBuffer->getUsage() & static_cast<int64_t>(BufferUsage::FRONT_BUFFER)) !=
+                  0));
+    };
+
     OutputLayerState<wp<GraphicBuffer>, LayerStateField::Buffer>
             mBuffer{[](auto layer) { return layer->getLayerFE().getCompositionState()->buffer; },
                     [](const wp<GraphicBuffer>& buffer) {
@@ -399,7 +419,14 @@
                                 base::StringPrintf("%p",
                                                    promotedBuffer ? promotedBuffer.get()
                                                                   : nullptr)};
-                    }};
+                    },
+                    BufferEquals};
+
+    // Even if the same buffer is passed to BLAST's setBuffer(), we still increment the frame
+    // number and need to treat it as if the buffer changed. Otherwise we break existing
+    // front-buffer rendering paths (such as egl's EGL_SINGLE_BUFFER).
+    OutputLayerState<uint64_t, LayerStateField::Buffer> mFrameNumber{
+            [](auto layer) { return layer->getLayerFE().getCompositionState()->frameNumber; }};
 
     int64_t mFramesSinceBufferUpdate = 0;
 
@@ -449,7 +476,7 @@
                                       return hash;
                                   }};
 
-    static const constexpr size_t kNumNonUniqueFields = 16;
+    static const constexpr size_t kNumNonUniqueFields = 17;
 
     std::array<StateInterface*, kNumNonUniqueFields> getNonUniqueFields() {
         std::array<const StateInterface*, kNumNonUniqueFields> constFields =
@@ -468,6 +495,7 @@
                 &mAlpha,        &mLayerMetadata,  &mVisibleRegion,        &mOutputDataspace,
                 &mPixelFormat,  &mColorTransform, &mCompositionType,      &mSidebandStream,
                 &mBuffer,       &mSolidColor,     &mBackgroundBlurRadius, &mBlurRegions,
+                &mFrameNumber,
         };
     }
 };
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Predictor.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Predictor.h
index fe486d3..ef1560e 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Predictor.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Predictor.h
@@ -109,7 +109,7 @@
     static std::optional<Plan> fromString(const std::string&);
 
     void reset() { mLayerTypes.clear(); }
-    void addLayerType(hardware::graphics::composer::hal::Composition type) {
+    void addLayerType(aidl::android::hardware::graphics::composer3::Composition type) {
         mLayerTypes.emplace_back(type);
     }
 
@@ -125,7 +125,7 @@
     }
 
 private:
-    std::vector<hardware::graphics::composer::hal::Composition> mLayerTypes;
+    std::vector<aidl::android::hardware::graphics::composer3::Composition> mLayerTypes;
 };
 
 } // namespace android::compositionengine::impl::planner
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Display.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Display.h
index 08a8b84..d90cc90 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Display.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Display.h
@@ -22,8 +22,7 @@
 #include <compositionengine/mock/Output.h>
 #include <gmock/gmock.h>
 #include <system/window.h>
-
-#include "DisplayHardware/DisplayIdentification.h"
+#include <ui/DisplayIdentification.h>
 
 namespace android::compositionengine::mock {
 
@@ -35,6 +34,7 @@
     MOCK_CONST_METHOD0(getId, DisplayId());
     MOCK_CONST_METHOD0(isSecure, bool());
     MOCK_CONST_METHOD0(isVirtual, bool());
+    MOCK_CONST_METHOD0(getPreferredBootHwcConfigId, int32_t());
 
     MOCK_METHOD0(disconnect, void());
 
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/LayerFE.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/LayerFE.h
index d215bda..ee0c53d 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/LayerFE.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/LayerFE.h
@@ -26,8 +26,18 @@
 // Defines the interface used by the CompositionEngine to make requests
 // of the front-end layer.
 class LayerFE : public compositionengine::LayerFE {
-public:
+private:
+    // Making the constructor private as this class implements RefBase,
+    // and constructing it with a different way than sp<LayerFE>::make() causes
+    // a memory leak of the shared state.
     LayerFE();
+
+    // friends class to allow instantiation via sp<LayerFE>::make() and
+    // sp<StrictMock<LayerFE>>::make()
+    friend class sp<LayerFE>;
+    friend class testing::StrictMock<LayerFE>;
+
+public:
     virtual ~LayerFE();
 
     MOCK_CONST_METHOD0(getCompositionState, const LayerFECompositionState*());
@@ -39,7 +49,7 @@
                  std::vector<compositionengine::LayerFE::LayerSettings>(
                          compositionengine::LayerFE::ClientCompositionTargetSettings&));
 
-    MOCK_METHOD1(onLayerDisplayed, void(const sp<Fence>&));
+    MOCK_METHOD1(onLayerDisplayed, void(std::shared_future<renderengine::RenderEngineResult>));
 
     MOCK_CONST_METHOD0(getDebugName, const char*());
     MOCK_CONST_METHOD0(getSequence, int32_t());
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Output.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Output.h
index 8fdf3ae..b68b95d 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Output.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Output.h
@@ -39,10 +39,14 @@
     MOCK_METHOD1(setLayerCachingEnabled, void(bool));
     MOCK_METHOD1(setLayerCachingTexturePoolEnabled, void(bool));
     MOCK_METHOD3(setProjection, void(ui::Rotation, const Rect&, const Rect&));
+    MOCK_METHOD1(setNextBrightness, void(float));
     MOCK_METHOD1(setDisplaySize, void(const ui::Size&));
-    MOCK_METHOD2(setLayerStackFilter, void(uint32_t, bool));
     MOCK_CONST_METHOD0(getTransformHint, ui::Transform::RotationFlags());
 
+    MOCK_METHOD(void, setLayerFilter, (ui::LayerFilter));
+    MOCK_METHOD(bool, includesLayer, (ui::LayerFilter), (const));
+    MOCK_METHOD(bool, includesLayer, (const sp<compositionengine::LayerFE>&), (const));
+
     MOCK_METHOD1(setColorTransform, void(const compositionengine::CompositionRefreshArgs&));
     MOCK_METHOD1(setColorProfile, void(const ColorProfile&));
     MOCK_METHOD2(setDisplayBrightness, void(float, float));
@@ -62,9 +66,7 @@
     MOCK_CONST_METHOD0(getState, const OutputCompositionState&());
     MOCK_METHOD0(editState, OutputCompositionState&());
 
-    MOCK_CONST_METHOD1(getDirtyRegion, Region(bool));
-    MOCK_CONST_METHOD2(belongsInOutput, bool(std::optional<uint32_t>, bool));
-    MOCK_CONST_METHOD1(belongsInOutput, bool(const sp<compositionengine::LayerFE>&));
+    MOCK_METHOD(Region, getDirtyRegion, (), (const));
 
     MOCK_CONST_METHOD1(getOutputLayerForLayer,
                        compositionengine::OutputLayer*(const sp<compositionengine::LayerFE>&));
@@ -114,7 +116,7 @@
     MOCK_METHOD0(presentAndGetFrameFences, compositionengine::Output::FrameFences());
 
     MOCK_METHOD3(generateClientCompositionRequests,
-                 std::vector<LayerFE::LayerSettings>(bool, Region&, ui::Dataspace));
+                 std::vector<LayerFE::LayerSettings>(bool, ui::Dataspace, std::vector<compositionengine::LayerFE*>&));
     MOCK_METHOD2(appendRegionFlashRequests,
                  void(const Region&, std::vector<LayerFE::LayerSettings>&));
     MOCK_METHOD1(setExpensiveRenderingExpected, void(bool));
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/OutputLayer.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/OutputLayer.h
index 358ed5a..a6cb811 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/OutputLayer.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/OutputLayer.h
@@ -46,7 +46,8 @@
     MOCK_CONST_METHOD0(getHwcLayer, HWC2::Layer*());
     MOCK_CONST_METHOD0(requiresClientComposition, bool());
     MOCK_CONST_METHOD0(isHardwareCursor, bool());
-    MOCK_METHOD1(applyDeviceCompositionTypeChange, void(Hwc2::IComposerClient::Composition));
+    MOCK_METHOD1(applyDeviceCompositionTypeChange,
+                 void(aidl::android::hardware::graphics::composer3::Composition));
     MOCK_METHOD0(prepareForDeviceLayerRequests, void());
     MOCK_METHOD1(applyDeviceLayerRequest, void(Hwc2::IComposerClient::LayerRequest request));
     MOCK_CONST_METHOD0(needsFiltering, bool());
diff --git a/services/surfaceflinger/CompositionEngine/src/Display.cpp b/services/surfaceflinger/CompositionEngine/src/Display.cpp
index 2f2c686..2165e1d 100644
--- a/services/surfaceflinger/CompositionEngine/src/Display.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/Display.cpp
@@ -39,6 +39,9 @@
 
 #include "DisplayHardware/PowerAdvisor.h"
 
+using aidl::android::hardware::graphics::composer3::Capability;
+using aidl::android::hardware::graphics::composer3::DisplayCapability;
+
 namespace android::compositionengine::impl {
 
 std::shared_ptr<Display> createDisplay(
@@ -51,12 +54,9 @@
 
 void Display::setConfiguration(const compositionengine::DisplayCreationArgs& args) {
     mId = args.id;
-    mIsVirtual = !args.connectionType;
     mPowerAdvisor = args.powerAdvisor;
     editState().isSecure = args.isSecure;
-    editState().displaySpace.bounds = Rect(args.pixels);
-    setLayerStackFilter(args.layerStackId,
-                        args.connectionType == ui::DisplayConnectionType::Internal);
+    editState().displaySpace.setBounds(args.pixels);
     setName(args.name);
 }
 
@@ -73,7 +73,7 @@
 }
 
 bool Display::isVirtual() const {
-    return mIsVirtual;
+    return VirtualDisplayId::tryCast(mId).has_value();
 }
 
 std::optional<DisplayId> Display::getDisplayId() const {
@@ -117,8 +117,8 @@
         return;
     }
 
-    if (mIsVirtual) {
-        ALOGW("%s: Invalid operation on virtual display", __FUNCTION__);
+    if (isVirtual()) {
+        ALOGW("%s: Invalid operation on virtual display", __func__);
         return;
     }
 
@@ -136,7 +136,7 @@
     StringAppendF(&out, "   Composition Display State: [\"%s\"]", getName().c_str());
 
     out.append("\n   ");
-    dumpVal(out, "isVirtual", mIsVirtual);
+    dumpVal(out, "isVirtual", isVirtual());
     dumpVal(out, "DisplayId", to_string(mId));
     out.append("\n");
 
@@ -227,10 +227,22 @@
     // Get any composition changes requested by the HWC device, and apply them.
     std::optional<android::HWComposer::DeviceRequestedChanges> changes;
     auto& hwc = getCompositionEngine().getHwComposer();
+    if (const auto physicalDisplayId = PhysicalDisplayId::tryCast(*halDisplayId);
+        physicalDisplayId && getState().displayBrightness) {
+        const status_t result =
+                hwc.setDisplayBrightness(*physicalDisplayId, *getState().displayBrightness,
+                                         Hwc2::Composer::DisplayBrightnessOptions{
+                                                 .applyImmediately = false})
+                        .get();
+        ALOGE_IF(result != NO_ERROR, "setDisplayBrightness failed for %s: %d, (%s)",
+                 getName().c_str(), result, strerror(-result));
+    }
+
     if (status_t result =
                 hwc.getDeviceCompositionChanges(*halDisplayId, anyLayersRequireClientComposition(),
                                                 getState().earliestPresentTime,
-                                                getState().previousPresentFence, &changes);
+                                                getState().previousPresentFence,
+                                                getState().expectedPresentTime, &changes);
         result != NO_ERROR) {
         ALOGE("chooseCompositionStrategy failed for %s: %d (%s)", getName().c_str(), result,
               strerror(-result));
@@ -240,23 +252,25 @@
         applyChangedTypesToLayers(changes->changedTypes);
         applyDisplayRequests(changes->displayRequests);
         applyLayerRequestsToLayers(changes->layerRequests);
-        applyClientTargetRequests(changes->clientTargetProperty);
+        applyClientTargetRequests(changes->clientTargetProperty, changes->clientTargetBrightness);
     }
 
     // Determine what type of composition we are doing from the final state
     auto& state = editState();
     state.usesClientComposition = anyLayersRequireClientComposition();
     state.usesDeviceComposition = !allLayersRequireClientComposition();
+    // Clear out the display brightness now that it's been communicated to composer.
+    state.displayBrightness.reset();
 }
 
 bool Display::getSkipColorTransform() const {
     const auto& hwc = getCompositionEngine().getHwComposer();
     if (const auto halDisplayId = HalDisplayId::tryCast(mId)) {
         return hwc.hasDisplayCapability(*halDisplayId,
-                                        hal::DisplayCapability::SKIP_CLIENT_COLOR_TRANSFORM);
+                                        DisplayCapability::SKIP_CLIENT_COLOR_TRANSFORM);
     }
 
-    return hwc.hasCapability(hal::Capability::SKIP_CLIENT_COLOR_TRANSFORM);
+    return hwc.hasCapability(Capability::SKIP_CLIENT_COLOR_TRANSFORM);
 }
 
 bool Display::anyLayersRequireClientComposition() const {
@@ -284,7 +298,8 @@
 
         if (auto it = changedTypes.find(hwcLayer); it != changedTypes.end()) {
             layer->applyDeviceCompositionTypeChange(
-                    static_cast<Hwc2::IComposerClient::Composition>(it->second));
+                    static_cast<aidl::android::hardware::graphics::composer3::Composition>(
+                            it->second));
         }
     }
 }
@@ -312,12 +327,14 @@
     }
 }
 
-void Display::applyClientTargetRequests(const ClientTargetProperty& clientTargetProperty) {
+void Display::applyClientTargetRequests(const ClientTargetProperty& clientTargetProperty,
+                                        float brightness) {
     if (clientTargetProperty.dataspace == ui::Dataspace::UNKNOWN) {
         return;
     }
 
     editState().dataspace = clientTargetProperty.dataspace;
+    editState().clientTargetBrightness = brightness;
     getRenderSurface()->setBufferDataspace(clientTargetProperty.dataspace);
     getRenderSurface()->setBufferPixelFormat(clientTargetProperty.pixelFormat);
 }
@@ -364,8 +381,7 @@
     // 1) It is being handled by hardware composer, which may need this to
     //    keep its virtual display state machine in sync, or
     // 2) There is work to be done (the dirty region isn't empty)
-    if (GpuVirtualDisplayId::tryCast(mId) &&
-        getDirtyRegion(refreshArgs.repaintEverything).isEmpty()) {
+    if (GpuVirtualDisplayId::tryCast(mId) && getDirtyRegion().isEmpty()) {
         ALOGV("Skipping display composition");
         return;
     }
diff --git a/services/surfaceflinger/CompositionEngine/src/DumpHelpers.cpp b/services/surfaceflinger/CompositionEngine/src/DumpHelpers.cpp
index 5565396..01c368d 100644
--- a/services/surfaceflinger/CompositionEngine/src/DumpHelpers.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/DumpHelpers.cpp
@@ -63,6 +63,18 @@
     dumpVal(out, name, valueName.c_str(), value);
 }
 
+void dumpVal(std::string& out, const char* name, ui::LayerFilter filter) {
+    out.append(name);
+    out.append("={");
+    dumpVal(out, "layerStack", filter.layerStack.id);
+    dumpVal(out, "toInternalDisplay", filter.toInternalDisplay);
+    out.push_back('}');
+}
+
+void dumpVal(std::string& out, const char* name, ui::Size size) {
+    StringAppendF(&out, "%s=[%d %d] ", name, size.width, size.height);
+}
+
 void dumpVal(std::string& out, const char* name, const FloatRect& rect) {
     StringAppendF(&out, "%s=[%f %f %f %f] ", name, rect.left, rect.top, rect.right, rect.bottom);
 }
@@ -80,10 +92,6 @@
     out.append(" ");
 }
 
-void dumpVal(std::string& out, const char* name, const ui::Size& size) {
-    StringAppendF(&out, "%s=[%d %d] ", name, size.width, size.height);
-}
-
 void dumpVal(std::string& out, const char* name, const mat4& tr) {
     StringAppendF(&out,
                   "%s=["
diff --git a/services/surfaceflinger/CompositionEngine/src/Output.cpp b/services/surfaceflinger/CompositionEngine/src/Output.cpp
index 95ae5e5..4e67a63 100644
--- a/services/surfaceflinger/CompositionEngine/src/Output.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/Output.cpp
@@ -27,6 +27,7 @@
 #include <compositionengine/impl/OutputLayer.h>
 #include <compositionengine/impl/OutputLayerCompositionState.h>
 #include <compositionengine/impl/planner/Planner.h>
+#include <ftl/future.h>
 
 #include <thread>
 
@@ -49,6 +50,8 @@
 
 #include "TracedOrdinal.h"
 
+using aidl::android::hardware::graphics::composer3::Composition;
+
 namespace android::compositionengine {
 
 Output::~Output() = default;
@@ -156,65 +159,71 @@
                            const Rect& orientedDisplaySpaceRect) {
     auto& outputState = editState();
 
-    outputState.displaySpace.orientation = orientation;
-    LOG_FATAL_IF(outputState.displaySpace.bounds == Rect::INVALID_RECT,
+    outputState.displaySpace.setOrientation(orientation);
+    LOG_FATAL_IF(outputState.displaySpace.getBoundsAsRect() == Rect::INVALID_RECT,
                  "The display bounds are unknown.");
 
     // Compute orientedDisplaySpace
-    ui::Size orientedSize = outputState.displaySpace.bounds.getSize();
+    ui::Size orientedSize = outputState.displaySpace.getBounds();
     if (orientation == ui::ROTATION_90 || orientation == ui::ROTATION_270) {
         std::swap(orientedSize.width, orientedSize.height);
     }
-    outputState.orientedDisplaySpace.bounds = Rect(orientedSize);
-    outputState.orientedDisplaySpace.content = orientedDisplaySpaceRect;
+    outputState.orientedDisplaySpace.setBounds(orientedSize);
+    outputState.orientedDisplaySpace.setContent(orientedDisplaySpaceRect);
 
     // Compute displaySpace.content
     const uint32_t transformOrientationFlags = ui::Transform::toRotationFlags(orientation);
     ui::Transform rotation;
     if (transformOrientationFlags != ui::Transform::ROT_INVALID) {
-        const auto displaySize = outputState.displaySpace.bounds;
+        const auto displaySize = outputState.displaySpace.getBoundsAsRect();
         rotation.set(transformOrientationFlags, displaySize.width(), displaySize.height());
     }
-    outputState.displaySpace.content = rotation.transform(orientedDisplaySpaceRect);
+    outputState.displaySpace.setContent(rotation.transform(orientedDisplaySpaceRect));
 
     // Compute framebufferSpace
-    outputState.framebufferSpace.orientation = orientation;
-    LOG_FATAL_IF(outputState.framebufferSpace.bounds == Rect::INVALID_RECT,
+    outputState.framebufferSpace.setOrientation(orientation);
+    LOG_FATAL_IF(outputState.framebufferSpace.getBoundsAsRect() == Rect::INVALID_RECT,
                  "The framebuffer bounds are unknown.");
-    const auto scale =
-            getScale(outputState.displaySpace.bounds, outputState.framebufferSpace.bounds);
-    outputState.framebufferSpace.content = outputState.displaySpace.content.scale(scale.x, scale.y);
+    const auto scale = getScale(outputState.displaySpace.getBoundsAsRect(),
+                                outputState.framebufferSpace.getBoundsAsRect());
+    outputState.framebufferSpace.setContent(
+            outputState.displaySpace.getContent().scale(scale.x, scale.y));
 
     // Compute layerStackSpace
-    outputState.layerStackSpace.content = layerStackSpaceRect;
-    outputState.layerStackSpace.bounds = layerStackSpaceRect;
+    outputState.layerStackSpace.setContent(layerStackSpaceRect);
+    outputState.layerStackSpace.setBounds(
+            ui::Size(layerStackSpaceRect.getWidth(), layerStackSpaceRect.getHeight()));
 
     outputState.transform = outputState.layerStackSpace.getTransform(outputState.displaySpace);
     outputState.needsFiltering = outputState.transform.needsBilinearFiltering();
     dirtyEntireOutput();
 }
 
+void Output::setNextBrightness(float brightness) {
+    editState().displayBrightness = brightness;
+}
+
 void Output::setDisplaySize(const ui::Size& size) {
     mRenderSurface->setDisplaySize(size);
 
     auto& state = editState();
 
     // Update framebuffer space
-    const Rect newBounds(size);
-    state.framebufferSpace.bounds = newBounds;
+    const ui::Size newBounds(size);
+    state.framebufferSpace.setBounds(newBounds);
 
     // Update display space
-    state.displaySpace.bounds = newBounds;
+    state.displaySpace.setBounds(newBounds);
     state.transform = state.layerStackSpace.getTransform(state.displaySpace);
 
     // Update oriented display space
-    const auto orientation = state.displaySpace.orientation;
+    const auto orientation = state.displaySpace.getOrientation();
     ui::Size orientedSize = size;
     if (orientation == ui::ROTATION_90 || orientation == ui::ROTATION_270) {
         std::swap(orientedSize.width, orientedSize.height);
     }
-    const Rect newOrientedBounds(orientedSize);
-    state.orientedDisplaySpace.bounds = newOrientedBounds;
+    const ui::Size newOrientedBounds(orientedSize);
+    state.orientedDisplaySpace.setBounds(newOrientedBounds);
 
     if (mPlanner) {
         mPlanner->setDisplaySize(size);
@@ -227,11 +236,8 @@
     return static_cast<ui::Transform::RotationFlags>(getState().transform.getOrientation());
 }
 
-void Output::setLayerStackFilter(uint32_t layerStackId, bool isInternal) {
-    auto& outputState = editState();
-    outputState.layerStackId = layerStackId;
-    outputState.layerStackInternal = isInternal;
-
+void Output::setLayerFilter(ui::LayerFilter filter) {
+    editState().layerFilter = filter;
     dirtyEntireOutput();
 }
 
@@ -352,7 +358,7 @@
 void Output::setRenderSurface(std::unique_ptr<compositionengine::RenderSurface> surface) {
     mRenderSurface = std::move(surface);
     const auto size = mRenderSurface->getSize();
-    editState().framebufferSpace.bounds = Rect(size);
+    editState().framebufferSpace.setBounds(size);
     if (mPlanner) {
         mPlanner->setDisplaySize(size);
     }
@@ -371,26 +377,18 @@
     mRenderSurface = std::move(surface);
 }
 
-Region Output::getDirtyRegion(bool repaintEverything) const {
+Region Output::getDirtyRegion() const {
     const auto& outputState = getState();
-    Region dirty(outputState.layerStackSpace.content);
-    if (!repaintEverything) {
-        dirty.andSelf(outputState.dirtyRegion);
-    }
-    return dirty;
+    return outputState.dirtyRegion.intersect(outputState.layerStackSpace.getContent());
 }
 
-bool Output::belongsInOutput(std::optional<uint32_t> layerStackId, bool internalOnly) const {
-    // The layerStackId's must match, and also the layer must not be internal
-    // only when not on an internal output.
-    const auto& outputState = getState();
-    return layerStackId && (*layerStackId == outputState.layerStackId) &&
-            (!internalOnly || outputState.layerStackInternal);
+bool Output::includesLayer(ui::LayerFilter filter) const {
+    return getState().layerFilter.includes(filter);
 }
 
-bool Output::belongsInOutput(const sp<compositionengine::LayerFE>& layerFE) const {
+bool Output::includesLayer(const sp<LayerFE>& layerFE) const {
     const auto* layerFEState = layerFE->getCompositionState();
-    return layerFEState && belongsInOutput(layerFEState->layerStackId, layerFEState->internalOnly);
+    return layerFEState && includesLayer(layerFEState->outputFilter);
 }
 
 std::unique_ptr<compositionengine::OutputLayer> Output::createOutputLayer(
@@ -461,7 +459,7 @@
 
     // Compute the resulting coverage for this output, and store it for later
     const ui::Transform& tr = outputState.transform;
-    Region undefinedRegion{outputState.displaySpace.bounds};
+    Region undefinedRegion{outputState.displaySpace.getBoundsAsRect()};
     undefinedRegion.subtractSelf(tr.transform(coverage.aboveOpaqueLayers));
 
     outputState.undefinedRegion = undefinedRegion;
@@ -496,8 +494,8 @@
         layerFE->prepareCompositionState(compositionengine::LayerFE::StateSubset::BasicGeometry);
     }
 
-    // Only consider the layers on the given layer stack
-    if (!belongsInOutput(layerFE)) {
+    // Only consider the layers on this output
+    if (!includesLayer(layerFE)) {
         return;
     }
 
@@ -533,11 +531,18 @@
 
     /*
      * transparentRegion: area of a surface that is hinted to be completely
-     * transparent. This is only used to tell when the layer has no visible non-
-     * transparent regions and can be removed from the layer list. It does not
-     * affect the visibleRegion of this layer or any layers beneath it. The hint
-     * may not be correct if apps don't respect the SurfaceView restrictions
-     * (which, sadly, some don't).
+     * transparent.
+     * This is used to tell when the layer has no visible non-transparent
+     * regions and can be removed from the layer list. It does not affect the
+     * visibleRegion of this layer or any layers beneath it. The hint may not
+     * be correct if apps don't respect the SurfaceView restrictions (which,
+     * sadly, some don't).
+     *
+     * In addition, it is used on DISPLAY_DECORATION layers to specify the
+     * blockingRegion, allowing the DPU to skip it to save power. Once we have
+     * hardware that supports a blockingRegion on frames with AFBC, it may be
+     * useful to use this for other layers, too, so long as we can prevent
+     * regressions on b/7179570.
      */
     Region transparentRegion;
 
@@ -658,7 +663,7 @@
     // TODO(b/121291683): Why does this not use visibleRegion? (see outputSpaceVisibleRegion below)
     const auto& outputState = getState();
     Region drawRegion(outputState.transform.transform(visibleNonTransparentRegion));
-    drawRegion.andSelf(outputState.displaySpace.bounds);
+    drawRegion.andSelf(outputState.displaySpace.getBoundsAsRect());
     if (drawRegion.isEmpty()) {
         return;
     }
@@ -676,8 +681,13 @@
     outputLayerState.visibleNonTransparentRegion = visibleNonTransparentRegion;
     outputLayerState.coveredRegion = coveredRegion;
     outputLayerState.outputSpaceVisibleRegion = outputState.transform.transform(
-            visibleNonShadowRegion.intersect(outputState.layerStackSpace.content));
+            visibleNonShadowRegion.intersect(outputState.layerStackSpace.getContent()));
     outputLayerState.shadowRegion = shadowRegion;
+    outputLayerState.outputSpaceBlockingRegionHint =
+            layerFEState->compositionType == Composition::DISPLAY_DECORATION
+            ? outputState.transform.transform(
+                      transparentRegion.intersect(outputState.layerStackSpace.getContent()))
+            : Region();
 }
 
 void Output::setReleasedLayers(const compositionengine::CompositionRefreshArgs&) {
@@ -736,6 +746,7 @@
 
     editState().earliestPresentTime = refreshArgs.earliestPresentTime;
     editState().previousPresentFence = refreshArgs.previousPresentFence;
+    editState().expectedPresentTime = refreshArgs.expectedPresentTime;
 
     compositionengine::OutputLayer* peekThroughLayer = nullptr;
     sp<GraphicBuffer> previousOverride = nullptr;
@@ -911,7 +922,7 @@
 
 void Output::beginFrame() {
     auto& outputState = editState();
-    const bool dirty = !getDirtyRegion(false).isEmpty();
+    const bool dirty = !getDirtyRegion().isEmpty();
     const bool empty = getOutputLayerCount() == 0;
     const bool wasEmpty = !outputState.lastCompositionHadVisibleLayers;
 
@@ -963,14 +974,9 @@
     }
 
     if (getState().isEnabled) {
-        // transform the dirty region into this screen's coordinate space
-        const Region dirtyRegion = getDirtyRegion(refreshArgs.repaintEverything);
-        if (!dirtyRegion.isEmpty()) {
-            base::unique_fd readyFence;
-            // redraw the whole screen
+        if (const auto dirtyRegion = getDirtyRegion(); !dirtyRegion.isEmpty()) {
             static_cast<void>(composeSurfaces(dirtyRegion, refreshArgs));
-
-            mRenderSurface->queueBuffer(std::move(readyFence));
+            mRenderSurface->queueBuffer(base::unique_fd());
         }
     }
 
@@ -1049,43 +1055,43 @@
         }
     }
 
-    base::unique_fd readyFence;
     if (!hasClientComposition) {
         setExpensiveRenderingExpected(false);
-        return readyFence;
+        return base::unique_fd();
     }
 
     ALOGV("hasClientComposition");
 
     renderengine::DisplaySettings clientCompositionDisplay;
-    clientCompositionDisplay.physicalDisplay = outputState.framebufferSpace.content;
-    clientCompositionDisplay.clip = outputState.layerStackSpace.content;
+    clientCompositionDisplay.physicalDisplay = outputState.framebufferSpace.getContent();
+    clientCompositionDisplay.clip = outputState.layerStackSpace.getContent();
     clientCompositionDisplay.orientation =
-            ui::Transform::toRotationFlags(outputState.displaySpace.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.maxLuminance = outputState.displayBrightnessNits > 0.f
+    clientCompositionDisplay.currentLuminanceNits = outputState.displayBrightnessNits > 0.f
             ? outputState.displayBrightnessNits
             : mDisplayColorProfile->getHdrCapabilities().getDesiredMaxLuminance();
-    clientCompositionDisplay.sdrWhitePointNits = outputState.sdrWhitePointNits;
+    clientCompositionDisplay.maxLuminance =
+            mDisplayColorProfile->getHdrCapabilities().getDesiredMaxLuminance();
+    clientCompositionDisplay.targetLuminanceNits =
+            outputState.clientTargetBrightness * outputState.displayBrightnessNits;
 
     // Compute the global color transform matrix.
-    if (!outputState.usesDeviceComposition && !getSkipColorTransform()) {
-        clientCompositionDisplay.colorTransform = outputState.colorTransformMatrix;
-    }
-
-    // Note: Updated by generateClientCompositionRequests
-    clientCompositionDisplay.clearRegion = Region::INVALID_REGION;
+    clientCompositionDisplay.colorTransform = outputState.colorTransformMatrix;
+    clientCompositionDisplay.deviceHandlesColorTransform =
+            outputState.usesDeviceComposition || getSkipColorTransform();
 
     // Generate the client composition requests for the layers on this output.
+    std::vector<LayerFE*> clientCompositionLayersFE;
     std::vector<LayerFE::LayerSettings> clientCompositionLayers =
             generateClientCompositionRequests(supportsProtectedContent,
-                                              clientCompositionDisplay.clearRegion,
-                                              clientCompositionDisplay.outputDataspace);
+                                              clientCompositionDisplay.outputDataspace,
+                                              clientCompositionLayersFE);
     appendRegionFlashRequests(debugRegion, clientCompositionLayers);
 
     // Check if the client composition requests were rendered into the provided graphic buffer. If
@@ -1096,7 +1102,7 @@
                                                    clientCompositionLayers)) {
             outputCompositionState.reusedClientComposition = true;
             setExpensiveRenderingExpected(false);
-            return readyFence;
+            return base::unique_fd();
         }
         mClientCompositionRequestCache->add(tex->getBuffer()->getId(), clientCompositionDisplay,
                                             clientCompositionLayers);
@@ -1108,18 +1114,22 @@
     // because high frequency consumes extra battery.
     const bool expensiveBlurs =
             refreshArgs.blursAreExpensive && mLayerRequestingBackgroundBlur != nullptr;
-    const bool expensiveRenderingExpected =
-            clientCompositionDisplay.outputDataspace == ui::Dataspace::DISPLAY_P3 || expensiveBlurs;
+    const bool expensiveRenderingExpected = expensiveBlurs ||
+            std::any_of(clientCompositionLayers.begin(), clientCompositionLayers.end(),
+                        [outputDataspace =
+                                 clientCompositionDisplay.outputDataspace](const auto& layer) {
+                            return layer.sourceDataspace != outputDataspace;
+                        });
     if (expensiveRenderingExpected) {
         setExpensiveRenderingExpected(true);
     }
 
-    std::vector<const renderengine::LayerSettings*> clientCompositionLayerPointers;
-    clientCompositionLayerPointers.reserve(clientCompositionLayers.size());
+    std::vector<renderengine::LayerSettings> clientRenderEngineLayers;
+    clientRenderEngineLayers.reserve(clientCompositionLayers.size());
     std::transform(clientCompositionLayers.begin(), clientCompositionLayers.end(),
-                   std::back_inserter(clientCompositionLayerPointers),
-                   [](LayerFE::LayerSettings& settings) -> renderengine::LayerSettings* {
-                       return &settings;
+                   std::back_inserter(clientRenderEngineLayers),
+                   [](LayerFE::LayerSettings& settings) -> renderengine::LayerSettings {
+                       return settings;
                    });
 
     const nsecs_t renderEngineStart = systemTime();
@@ -1129,10 +1139,12 @@
     // bounds its framebuffer cache but Skia RenderEngine has no current policy. The best fix is
     // probably to encapsulate the output buffer into a structure that dispatches resource cleanup
     // over to RenderEngine, in which case this flag can be removed from the drawLayers interface.
-    const bool useFramebufferCache = outputState.layerStackInternal;
-    status_t status =
-            renderEngine.drawLayers(clientCompositionDisplay, clientCompositionLayerPointers, tex,
-                                    useFramebufferCache, std::move(fd), &readyFence);
+    const bool useFramebufferCache = outputState.layerFilter.toInternalDisplay;
+    auto [status, drawFence] =
+            renderEngine
+                    .drawLayers(clientCompositionDisplay, clientRenderEngineLayers, tex,
+                                useFramebufferCache, std::move(fd))
+                    .get();
 
     if (status != NO_ERROR && mClientCompositionRequestCache) {
         // If rendering was not successful, remove the request from the cache.
@@ -1140,27 +1152,32 @@
     }
 
     auto& timeStats = getCompositionEngine().getTimeStats();
-    if (readyFence.get() < 0) {
+    if (drawFence.get() < 0) {
         timeStats.recordRenderEngineDuration(renderEngineStart, systemTime());
     } else {
         timeStats.recordRenderEngineDuration(renderEngineStart,
                                              std::make_shared<FenceTime>(
-                                                     new Fence(dup(readyFence.get()))));
+                                                     new Fence(dup(drawFence.get()))));
     }
 
-    return readyFence;
+    if (clientCompositionLayersFE.size() > 0) {
+        sp<Fence> clientCompFence = new Fence(dup(drawFence.get()));
+        for (auto clientComposedLayer : clientCompositionLayersFE) {
+            clientComposedLayer->setWasClientComposed(clientCompFence);
+        }
+    }
+
+    return std::move(drawFence);
 }
 
 std::vector<LayerFE::LayerSettings> Output::generateClientCompositionRequests(
-        bool supportsProtectedContent, Region& clearRegion, ui::Dataspace outputDataspace) {
+      bool supportsProtectedContent, ui::Dataspace outputDataspace, std::vector<LayerFE*>& outLayerFEs) {
     std::vector<LayerFE::LayerSettings> clientCompositionLayers;
     ALOGV("Rendering client layers");
 
     const auto& outputState = getState();
-    const Region viewportRegion(outputState.layerStackSpace.content);
+    const Region viewportRegion(outputState.layerStackSpace.getContent());
     bool firstLayer = true;
-    // Used when a layer clears part of the buffer.
-    Region stubRegion;
 
     bool disableBlurs = false;
     sp<GraphicBuffer> previousOverrideBuffer = nullptr;
@@ -1222,18 +1239,19 @@
                                                outputState.needsFiltering,
                                        .isSecure = outputState.isSecure,
                                        .supportsProtectedContent = supportsProtectedContent,
-                                       .clearRegion = clientComposition ? clearRegion : stubRegion,
-                                       .viewport = outputState.layerStackSpace.content,
+                                       .viewport = outputState.layerStackSpace.getContent(),
                                        .dataspace = outputDataspace,
                                        .realContentIsVisible = realContentIsVisible,
                                        .clearContent = !clientComposition,
-                                       .blurSetting = blurSetting};
+                                       .blurSetting = blurSetting,
+                                       .whitePointNits = layerState.whitePointNits};
                 results = layerFE.prepareClientCompositionList(targetSettings);
                 if (realContentIsVisible && !results.empty()) {
                     layer->editState().clientCompositionTimestamp = systemTime();
                 }
             }
 
+            outLayerFEs.push_back(&layerFE);
             clientCompositionLayers.insert(clientCompositionLayers.end(),
                                            std::make_move_iterator(results.begin()),
                                            std::make_move_iterator(results.end()));
@@ -1305,8 +1323,10 @@
             releaseFence =
                     Fence::merge("LayerRelease", releaseFence, frame.clientTargetAcquireFence);
         }
-
-        layer->getLayerFE().onLayerDisplayed(releaseFence);
+        layer->getLayerFE().onLayerDisplayed(
+                ftl::yield<renderengine::RenderEngineResult>(
+                        {NO_ERROR, base::unique_fd(releaseFence->dup())})
+                        .share());
     }
 
     // We've got a list of layers needing fences, that are disjoint with
@@ -1314,7 +1334,9 @@
     // supply them with the present fence.
     for (auto& weakLayer : mReleasedLayers) {
         if (auto layer = weakLayer.promote(); layer != nullptr) {
-            layer->onLayerDisplayed(frame.presentFence);
+            layer->onLayerDisplayed(ftl::yield<renderengine::RenderEngineResult>(
+                                            {NO_ERROR, base::unique_fd(frame.presentFence->dup())})
+                                            .share());
         }
     }
 
@@ -1324,13 +1346,13 @@
 
 void Output::renderCachedSets(const CompositionRefreshArgs& refreshArgs) {
     if (mPlanner) {
-        mPlanner->renderCachedSets(getState(), refreshArgs.nextInvalidateTime);
+        mPlanner->renderCachedSets(getState(), refreshArgs.scheduledFrameTime);
     }
 }
 
 void Output::dirtyEntireOutput() {
     auto& outputState = editState();
-    outputState.dirtyRegion.set(outputState.displaySpace.bounds);
+    outputState.dirtyRegion.set(outputState.displaySpace.getBoundsAsRect());
 }
 
 void Output::chooseCompositionStrategy() {
diff --git a/services/surfaceflinger/CompositionEngine/src/OutputCompositionState.cpp b/services/surfaceflinger/CompositionEngine/src/OutputCompositionState.cpp
index ee30ad8..482250a 100644
--- a/services/surfaceflinger/CompositionEngine/src/OutputCompositionState.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/OutputCompositionState.cpp
@@ -28,9 +28,7 @@
     dumpVal(out, "usesDeviceComposition", usesDeviceComposition);
     dumpVal(out, "flipClientTarget", flipClientTarget);
     dumpVal(out, "reusedClientComposition", reusedClientComposition);
-
-    dumpVal(out, "layerStack", layerStackId);
-    dumpVal(out, "layerStackInternal", layerStackInternal);
+    dumpVal(out, "layerFilter", layerFilter);
 
     out.append("\n   ");
 
@@ -54,6 +52,10 @@
     dumpVal(out, "dataspace", toString(dataspace), dataspace);
     dumpVal(out, "colorTransformMatrix", colorTransformMatrix);
     dumpVal(out, "target dataspace", toString(targetDataspace), targetDataspace);
+    dumpVal(out, "displayBrightnessNits", displayBrightnessNits);
+    dumpVal(out, "sdrWhitePointNits", sdrWhitePointNits);
+    dumpVal(out, "clientTargetBrightness", clientTargetBrightness);
+    dumpVal(out, "displayBrightness", displayBrightness);
 
     out.append("\n");
 }
diff --git a/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp b/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp
index 56e9d27..723593d 100644
--- a/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp
@@ -24,6 +24,9 @@
 #include <compositionengine/impl/OutputLayer.h>
 #include <compositionengine/impl/OutputLayerCompositionState.h>
 #include <cstdint>
+#include "system/graphics-base-v1.0.h"
+
+#include <ui/DataspaceUtils.h>
 
 // TODO(b/129481165): remove the #pragma below and fix conversion issues
 #pragma clang diagnostic push
@@ -34,6 +37,8 @@
 // TODO(b/129481165): remove the #pragma below and fix conversion issues
 #pragma clang diagnostic pop // ignored "-Wconversion"
 
+using aidl::android::hardware::graphics::composer3::Composition;
+
 namespace android::compositionengine {
 
 OutputLayer::~OutputLayer() = default;
@@ -79,7 +84,7 @@
     FloatRect activeCropFloat =
             reduce(layerState.geomLayerBounds, layerState.transparentRegionHint);
 
-    const Rect& viewport = getOutput().getState().layerStackSpace.content;
+    const Rect& viewport = getOutput().getState().layerStackSpace.getContent();
     const ui::Transform& layerTransform = layerState.geomLayerTransform;
     const ui::Transform& inverseLayerTransform = layerState.geomInverseLayerTransform;
     // Transform to screen space.
@@ -136,7 +141,7 @@
          * buffer
          */
         uint32_t invTransformOrient =
-                ui::Transform::toRotationFlags(outputState.displaySpace.orientation);
+                ui::Transform::toRotationFlags(outputState.displaySpace.getOrientation());
         // calculate the inverse transform
         if (invTransformOrient & HAL_TRANSFORM_ROT_90) {
             invTransformOrient ^= HAL_TRANSFORM_FLIP_V | HAL_TRANSFORM_FLIP_H;
@@ -192,7 +197,7 @@
     Rect activeCrop = layerState.geomCrop;
     if (!activeCrop.isEmpty() && bufferSize.isValid()) {
         activeCrop = layerTransform.transform(activeCrop);
-        if (!activeCrop.intersect(outputState.layerStackSpace.content, &activeCrop)) {
+        if (!activeCrop.intersect(outputState.layerStackSpace.getContent(), &activeCrop)) {
             activeCrop.clear();
         }
         activeCrop = inverseLayerTransform.transform(activeCrop, true);
@@ -228,7 +233,7 @@
         geomLayerBounds.bottom += outset;
     }
     Rect frame{layerTransform.transform(reduce(geomLayerBounds, activeTransparentRegion))};
-    if (!frame.intersect(outputState.layerStackSpace.content, &frame)) {
+    if (!frame.intersect(outputState.layerStackSpace.getContent(), &frame)) {
         frame.clear();
     }
     const ui::Transform displayTransform{outputState.transform};
@@ -317,6 +322,20 @@
             ? outputState.targetDataspace
             : layerFEState->dataspace;
 
+    // For hdr content, treat the white point as the display brightness - HDR content should not be
+    // boosted or dimmed.
+    if (isHdrDataspace(state.dataspace) ||
+        getOutput().getState().displayBrightnessNits == getOutput().getState().sdrWhitePointNits ||
+        getOutput().getState().displayBrightnessNits == 0.f) {
+        state.dimmingRatio = 1.f;
+        state.whitePointNits = getOutput().getState().displayBrightnessNits;
+    } else {
+        state.dimmingRatio = std::clamp(getOutput().getState().sdrWhitePointNits /
+                                                getOutput().getState().displayBrightnessNits,
+                                        0.f, 1.f);
+        state.whitePointNits = getOutput().getState().sdrWhitePointNits;
+    }
+
     // These are evaluated every frame as they can potentially change at any
     // time.
     if (layerFEState->forceClientComposition || !profile.isDataspaceSupported(state.dataspace) ||
@@ -347,6 +366,10 @@
 
     auto requestedCompositionType = outputIndependentState->compositionType;
 
+    if (requestedCompositionType == Composition::SOLID_COLOR && state.overrideInfo.buffer) {
+        requestedCompositionType = Composition::DEVICE;
+    }
+
     // TODO(b/181172795): We now update geometry for all flattened layers. We should update it
     // only when the geometry actually changes
     const bool isOverridden =
@@ -359,20 +382,22 @@
     }
 
     writeOutputDependentPerFrameStateToHWC(hwcLayer.get());
-    writeOutputIndependentPerFrameStateToHWC(hwcLayer.get(), *outputIndependentState, skipLayer);
+    writeOutputIndependentPerFrameStateToHWC(hwcLayer.get(), *outputIndependentState,
+                                             requestedCompositionType, skipLayer);
 
     writeCompositionTypeToHWC(hwcLayer.get(), requestedCompositionType, isPeekingThrough,
                               skipLayer);
 
-    // Always set the layer color after setting the composition type.
-    writeSolidColorStateToHWC(hwcLayer.get(), *outputIndependentState);
+    if (requestedCompositionType == Composition::SOLID_COLOR) {
+        writeSolidColorStateToHWC(hwcLayer.get(), *outputIndependentState);
+    }
 
     editState().hwc->stateOverridden = isOverridden;
     editState().hwc->layerSkipped = skipLayer;
 }
 
 void OutputLayer::writeOutputDependentGeometryStateToHWC(HWC2::Layer* hwcLayer,
-                                                         hal::Composition requestedCompositionType,
+                                                         Composition requestedCompositionType,
                                                          uint32_t z) {
     const auto& outputDependentState = getState();
 
@@ -381,12 +406,7 @@
 
     if (outputDependentState.overrideInfo.buffer != nullptr) {
         displayFrame = outputDependentState.overrideInfo.displayFrame;
-        sourceCrop =
-                FloatRect(0.f, 0.f,
-                          static_cast<float>(outputDependentState.overrideInfo.buffer->getBuffer()
-                                                     ->getWidth()),
-                          static_cast<float>(outputDependentState.overrideInfo.buffer->getBuffer()
-                                                     ->getHeight()));
+        sourceCrop = displayFrame.toFloatRect();
     }
 
     ALOGV("Writing display frame [%d, %d, %d, %d]", displayFrame.left, displayFrame.top,
@@ -411,7 +431,7 @@
     }
 
     // Solid-color layers and overridden buffers should always use an identity transform.
-    const auto bufferTransform = (requestedCompositionType != hal::Composition::SOLID_COLOR &&
+    const auto bufferTransform = (requestedCompositionType != Composition::SOLID_COLOR &&
                                   getState().overrideInfo.buffer == nullptr)
             ? outputDependentState.bufferTransform
             : static_cast<hal::Transform>(0);
@@ -467,7 +487,15 @@
     if (auto error = hwcLayer->setVisibleRegion(visibleRegion); error != hal::Error::NONE) {
         ALOGE("[%s] Failed to set visible region: %s (%d)", getLayerFE().getDebugName(),
               to_string(error).c_str(), static_cast<int32_t>(error));
-        outputDependentState.outputSpaceVisibleRegion.dump(LOG_TAG);
+        visibleRegion.dump(LOG_TAG);
+    }
+
+    if (auto error =
+                hwcLayer->setBlockingRegion(outputDependentState.outputSpaceBlockingRegionHint);
+        error != hal::Error::NONE) {
+        ALOGE("[%s] Failed to set blocking region: %s (%d)", getLayerFE().getDebugName(),
+              to_string(error).c_str(), static_cast<int32_t>(error));
+        outputDependentState.outputSpaceBlockingRegionHint.dump(LOG_TAG);
     }
 
     const auto dataspace = outputDependentState.overrideInfo.buffer
@@ -478,11 +506,29 @@
         ALOGE("[%s] Failed to set dataspace %d: %s (%d)", getLayerFE().getDebugName(), dataspace,
               to_string(error).c_str(), static_cast<int32_t>(error));
     }
+
+    // Cached layers are not dimmed, which means that composer should attempt to dim.
+    // Note that if the dimming ratio is large, then this may cause the cached layer
+    // to kick back into GPU composition :(
+    // Also note that this assumes that there are no HDR layers that are able to be cached.
+    // Otherwise, this could cause HDR layers to be dimmed twice.
+    const auto dimmingRatio = outputDependentState.overrideInfo.buffer
+            ? (getOutput().getState().displayBrightnessNits != 0.f
+                       ? std::clamp(getOutput().getState().sdrWhitePointNits /
+                                            getOutput().getState().displayBrightnessNits,
+                                    0.f, 1.f)
+                       : 1.f)
+            : outputDependentState.dimmingRatio;
+
+    if (auto error = hwcLayer->setBrightness(dimmingRatio); error != hal::Error::NONE) {
+        ALOGE("[%s] Failed to set brightness %f: %s (%d)", getLayerFE().getDebugName(),
+              dimmingRatio, to_string(error).c_str(), static_cast<int32_t>(error));
+    }
 }
 
 void OutputLayer::writeOutputIndependentPerFrameStateToHWC(
         HWC2::Layer* hwcLayer, const LayerFECompositionState& outputIndependentState,
-        bool skipLayer) {
+        Composition compositionType, bool skipLayer) {
     switch (auto error = hwcLayer->setColorTransform(outputIndependentState.colorTransform)) {
         case hal::Error::NONE:
             break;
@@ -506,19 +552,20 @@
     }
 
     // Content-specific per-frame state
-    switch (outputIndependentState.compositionType) {
-        case hal::Composition::SOLID_COLOR:
+    switch (compositionType) {
+        case Composition::SOLID_COLOR:
             // For compatibility, should be written AFTER the composition type.
             break;
-        case hal::Composition::SIDEBAND:
+        case Composition::SIDEBAND:
             writeSidebandStateToHWC(hwcLayer, outputIndependentState);
             break;
-        case hal::Composition::CURSOR:
-        case hal::Composition::DEVICE:
+        case Composition::CURSOR:
+        case Composition::DEVICE:
+        case Composition::DISPLAY_DECORATION:
             writeBufferStateToHWC(hwcLayer, outputIndependentState, skipLayer);
             break;
-        case hal::Composition::INVALID:
-        case hal::Composition::CLIENT:
+        case Composition::INVALID:
+        case Composition::CLIENT:
             // Ignored
             break;
     }
@@ -526,14 +573,10 @@
 
 void OutputLayer::writeSolidColorStateToHWC(HWC2::Layer* hwcLayer,
                                             const LayerFECompositionState& outputIndependentState) {
-    if (outputIndependentState.compositionType != hal::Composition::SOLID_COLOR) {
-        return;
-    }
-
-    hal::Color color = {static_cast<uint8_t>(std::round(255.0f * outputIndependentState.color.r)),
-                        static_cast<uint8_t>(std::round(255.0f * outputIndependentState.color.g)),
-                        static_cast<uint8_t>(std::round(255.0f * outputIndependentState.color.b)),
-                        255};
+    aidl::android::hardware::graphics::composer3::Color color = {outputIndependentState.color.r,
+                                                                 outputIndependentState.color.g,
+                                                                 outputIndependentState.color.b,
+                                                                 1.0f};
 
     if (auto error = hwcLayer->setColor(color); error != hal::Error::NONE) {
         ALOGE("[%s] Failed to set color: %s (%d)", getLayerFE().getDebugName(),
@@ -588,13 +631,13 @@
 }
 
 void OutputLayer::writeCompositionTypeToHWC(HWC2::Layer* hwcLayer,
-                                            hal::Composition requestedCompositionType,
+                                            Composition requestedCompositionType,
                                             bool isPeekingThrough, bool skipLayer) {
     auto& outputDependentState = editState();
 
     if (isClientCompositionForced(isPeekingThrough)) {
         // If we are forcing client composition, we need to tell the HWC
-        requestedCompositionType = hal::Composition::CLIENT;
+        requestedCompositionType = Composition::CLIENT;
     }
 
     // Set the requested composition type with the HWC whenever it changes
@@ -607,7 +650,7 @@
         if (auto error = hwcLayer->setCompositionType(requestedCompositionType);
             error != hal::Error::NONE) {
             ALOGE("[%s] Failed to set composition type %s: %s (%d)", getLayerFE().getDebugName(),
-                  toString(requestedCompositionType).c_str(), to_string(error).c_str(),
+                  to_string(requestedCompositionType).c_str(), to_string(error).c_str(),
                   static_cast<int32_t>(error));
         }
     }
@@ -628,7 +671,7 @@
     const auto& outputState = getOutput().getState();
 
     Rect frame = layerFEState->cursorFrame;
-    frame.intersect(outputState.layerStackSpace.content, &frame);
+    frame.intersect(outputState.layerStackSpace.getContent(), &frame);
     Rect position = outputState.transform.transform(frame);
 
     if (auto error = hwcLayer->setCursorPosition(position.left, position.top);
@@ -646,38 +689,38 @@
 
 bool OutputLayer::requiresClientComposition() const {
     const auto& state = getState();
-    return !state.hwc || state.hwc->hwcCompositionType == hal::Composition::CLIENT;
+    return !state.hwc || state.hwc->hwcCompositionType == Composition::CLIENT;
 }
 
 bool OutputLayer::isHardwareCursor() const {
     const auto& state = getState();
-    return state.hwc && state.hwc->hwcCompositionType == hal::Composition::CURSOR;
+    return state.hwc && state.hwc->hwcCompositionType == Composition::CURSOR;
 }
 
-void OutputLayer::detectDisallowedCompositionTypeChange(hal::Composition from,
-                                                        hal::Composition to) const {
+void OutputLayer::detectDisallowedCompositionTypeChange(Composition from, Composition to) const {
     bool result = false;
     switch (from) {
-        case hal::Composition::INVALID:
-        case hal::Composition::CLIENT:
+        case Composition::INVALID:
+        case Composition::CLIENT:
             result = false;
             break;
 
-        case hal::Composition::DEVICE:
-        case hal::Composition::SOLID_COLOR:
-            result = (to == hal::Composition::CLIENT);
+        case Composition::DEVICE:
+        case Composition::SOLID_COLOR:
+            result = (to == Composition::CLIENT);
             break;
 
-        case hal::Composition::CURSOR:
-        case hal::Composition::SIDEBAND:
-            result = (to == hal::Composition::CLIENT || to == hal::Composition::DEVICE);
+        case Composition::CURSOR:
+        case Composition::SIDEBAND:
+        case Composition::DISPLAY_DECORATION:
+            result = (to == Composition::CLIENT || to == Composition::DEVICE);
             break;
     }
 
     if (!result) {
         ALOGE("[%s] Invalid device requested composition type change: %s (%d) --> %s (%d)",
-              getLayerFE().getDebugName(), toString(from).c_str(), static_cast<int>(from),
-              toString(to).c_str(), static_cast<int>(to));
+              getLayerFE().getDebugName(), to_string(from).c_str(), static_cast<int>(from),
+              to_string(to).c_str(), static_cast<int>(to));
     }
 }
 
@@ -686,7 +729,7 @@
             (!isPeekingThrough && getLayerFE().hasRoundedCorners());
 }
 
-void OutputLayer::applyDeviceCompositionTypeChange(hal::Composition compositionType) {
+void OutputLayer::applyDeviceCompositionTypeChange(Composition compositionType) {
     auto& state = editState();
     LOG_FATAL_IF(!state.hwc);
     auto& hwcState = *state.hwc;
@@ -737,7 +780,7 @@
     // framebuffer space of the override buffer to layer space.
     const ProjectionSpace& layerSpace = getOutput().getState().layerStackSpace;
     const ui::Transform transform = getState().overrideInfo.displaySpace.getTransform(layerSpace);
-    const Rect boundaries = transform.transform(getState().overrideInfo.displayFrame);
+    const Rect boundaries = transform.transform(getState().overrideInfo.displaySpace.getContent());
 
     LayerFE::LayerSettings settings;
     settings.geometry = renderengine::Geometry{
@@ -755,6 +798,7 @@
             }};
     settings.sourceDataspace = getState().overrideInfo.dataspace;
     settings.alpha = 1.0f;
+    settings.whitePointNits = getOutput().getState().sdrWhitePointNits;
 
     return {static_cast<LayerFE::LayerSettings>(settings)};
 }
diff --git a/services/surfaceflinger/CompositionEngine/src/OutputLayerCompositionState.cpp b/services/surfaceflinger/CompositionEngine/src/OutputLayerCompositionState.cpp
index cfa740e..da1f7e4 100644
--- a/services/surfaceflinger/CompositionEngine/src/OutputLayerCompositionState.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/OutputLayerCompositionState.cpp
@@ -61,12 +61,17 @@
     dumpVal(out, "shadowRegion", shadowRegion);
 
     out.append("      ");
+    dumpVal(out, "outputSpaceBlockingRegionHint", outputSpaceBlockingRegionHint);
+
+    out.append("      ");
     dumpVal(out, "forceClientComposition", forceClientComposition);
     dumpVal(out, "clearClientTarget", clearClientTarget);
     dumpVal(out, "displayFrame", displayFrame);
     dumpVal(out, "sourceCrop", sourceCrop);
     dumpVal(out, "bufferTransform", toString(bufferTransform), bufferTransform);
     dumpVal(out, "dataspace", toString(dataspace), dataspace);
+    dumpVal(out, "whitePointNits", whitePointNits);
+    dumpVal(out, "dimmingRatio", dimmingRatio);
     dumpVal(out, "override buffer", overrideInfo.buffer.get());
     dumpVal(out, "override acquire fence", overrideInfo.acquireFence.get());
     dumpVal(out, "override display frame", overrideInfo.displayFrame);
diff --git a/services/surfaceflinger/CompositionEngine/src/RenderSurface.cpp b/services/surfaceflinger/CompositionEngine/src/RenderSurface.cpp
index ef50870..12c2c8e 100644
--- a/services/surfaceflinger/CompositionEngine/src/RenderSurface.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/RenderSurface.cpp
@@ -28,6 +28,7 @@
 #include <log/log.h>
 #include <renderengine/ExternalTexture.h>
 #include <renderengine/RenderEngine.h>
+#include <renderengine/impl/ExternalTexture.h>
 #include <system/window.h>
 #include <ui/GraphicBuffer.h>
 #include <ui/Rect.h>
@@ -127,19 +128,18 @@
 }
 
 void RenderSurface::prepareFrame(bool usesClientComposition, bool usesDeviceComposition) {
-    DisplaySurface::CompositionType compositionType;
-    if (usesClientComposition && usesDeviceComposition) {
-        compositionType = DisplaySurface::COMPOSITION_MIXED;
-    } else if (usesClientComposition) {
-        compositionType = DisplaySurface::COMPOSITION_GPU;
-    } else if (usesDeviceComposition) {
-        compositionType = DisplaySurface::COMPOSITION_HWC;
-    } else {
+    const auto compositionType = [=] {
+        using CompositionType = DisplaySurface::CompositionType;
+
+        if (usesClientComposition && usesDeviceComposition) return CompositionType::Mixed;
+        if (usesClientComposition) return CompositionType::Gpu;
+        if (usesDeviceComposition) return CompositionType::Hwc;
+
         // Nothing to do -- when turning the screen off we get a frame like
         // this. Call it a HWC frame since we won't be doing any GPU work but
         // will do a prepare/set cycle.
-        compositionType = DisplaySurface::COMPOSITION_HWC;
-    }
+        return CompositionType::Hwc;
+    }();
 
     if (status_t result = mDisplaySurface->prepareFrame(compositionType); result != NO_ERROR) {
         ALOGE("updateCompositionType failed for %s: %d (%s)", mDisplay.getName().c_str(), result,
@@ -183,9 +183,10 @@
         mTexture = texture;
     } else {
         mTexture = std::make_shared<
-                renderengine::ExternalTexture>(GraphicBuffer::from(buffer),
-                                               mCompositionEngine.getRenderEngine(),
-                                               renderengine::ExternalTexture::Usage::WRITEABLE);
+                renderengine::impl::ExternalTexture>(GraphicBuffer::from(buffer),
+                                                     mCompositionEngine.getRenderEngine(),
+                                                     renderengine::impl::ExternalTexture::Usage::
+                                                             WRITEABLE);
     }
     mTextureCache.push_back(mTexture);
     if (mTextureCache.size() > mMaxTextureCacheSize) {
diff --git a/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp b/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp
index c1cd5ab..aaca4fe 100644
--- a/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp
@@ -20,11 +20,13 @@
 #define ATRACE_TAG ATRACE_TAG_GRAPHICS
 
 #include <android-base/properties.h>
+#include <android-base/stringprintf.h>
 #include <compositionengine/impl/OutputCompositionState.h>
 #include <compositionengine/impl/planner/CachedSet.h>
 #include <math/HashCombine.h>
 #include <renderengine/DisplaySettings.h>
 #include <renderengine/RenderEngine.h>
+#include <ui/DebugUtils.h>
 #include <utils/Trace.h>
 
 #include <utils/Trace.h>
@@ -159,30 +161,30 @@
 void CachedSet::render(renderengine::RenderEngine& renderEngine, TexturePool& texturePool,
                        const OutputCompositionState& outputState) {
     ATRACE_CALL();
-    const Rect& viewport = outputState.layerStackSpace.content;
+    const Rect& viewport = outputState.layerStackSpace.getContent();
     const ui::Dataspace& outputDataspace = outputState.dataspace;
     const ui::Transform::RotationFlags orientation =
-            ui::Transform::toRotationFlags(outputState.framebufferSpace.orientation);
+            ui::Transform::toRotationFlags(outputState.framebufferSpace.getOrientation());
 
     renderengine::DisplaySettings displaySettings{
-            .physicalDisplay = outputState.framebufferSpace.content,
+            .physicalDisplay = outputState.framebufferSpace.getContent(),
             .clip = viewport,
             .outputDataspace = outputDataspace,
             .orientation = orientation,
+            .targetLuminanceNits = outputState.displayBrightnessNits,
     };
 
-    Region clearRegion = Region::INVALID_REGION;
     LayerFE::ClientCompositionTargetSettings targetSettings{
             .clip = Region(viewport),
             .needsFiltering = false,
             .isSecure = outputState.isSecure,
             .supportsProtectedContent = false,
-            .clearRegion = clearRegion,
             .viewport = viewport,
             .dataspace = outputDataspace,
             .realContentIsVisible = true,
             .clearContent = false,
             .blurSetting = LayerFE::ClientCompositionTargetSettings::BlurSetting::Enabled,
+            .whitePointNits = outputState.displayBrightnessNits,
     };
 
     std::vector<renderengine::LayerSettings> layerSettings;
@@ -195,11 +197,6 @@
                              clientCompositionList.cend());
     }
 
-    std::vector<const renderengine::LayerSettings*> layerSettingsPointers;
-    std::transform(layerSettings.cbegin(), layerSettings.cend(),
-                   std::back_inserter(layerSettingsPointers),
-                   [](const renderengine::LayerSettings& settings) { return &settings; });
-
     renderengine::LayerSettings blurLayerSettings;
     if (mBlurLayer) {
         auto blurSettings = targetSettings;
@@ -214,15 +211,14 @@
         blurLayerSettings.name = std::string("blur layer");
         // Clear out the shadow settings
         blurLayerSettings.shadow = {};
-        layerSettingsPointers.push_back(&blurLayerSettings);
+        layerSettings.push_back(blurLayerSettings);
     }
 
     renderengine::LayerSettings holePunchSettings;
     renderengine::LayerSettings holePunchBackgroundSettings;
     if (mHolePunchLayer) {
-        auto clientCompositionList =
-                mHolePunchLayer->getOutputLayer()->getLayerFE().prepareClientCompositionList(
-                        targetSettings);
+        auto& layerFE = mHolePunchLayer->getOutputLayer()->getLayerFE();
+        auto clientCompositionList = layerFE.prepareClientCompositionList(targetSettings);
         // Assume that the final layer contains the buffer that we want to
         // replace with a hole punch.
         holePunchSettings = clientCompositionList.back();
@@ -231,8 +227,9 @@
         holePunchSettings.source.solidColor = half3(0.0f, 0.0f, 0.0f);
         holePunchSettings.disableBlending = true;
         holePunchSettings.alpha = 0.0f;
-        holePunchSettings.name = std::string("hole punch layer");
-        layerSettingsPointers.push_back(&holePunchSettings);
+        holePunchSettings.name =
+                android::base::StringPrintf("hole punch layer for %s", layerFE.getDebugName());
+        layerSettings.push_back(holePunchSettings);
 
         // Add a solid background as the first layer in case there is no opaque
         // buffer behind the punch hole
@@ -241,7 +238,7 @@
         holePunchBackgroundSettings.geometry.boundaries = holePunchSettings.geometry.boundaries;
         holePunchBackgroundSettings.geometry.positionTransform =
                 holePunchSettings.geometry.positionTransform;
-        layerSettingsPointers.insert(layerSettingsPointers.begin(), &holePunchBackgroundSettings);
+        layerSettings.emplace(layerSettings.begin(), holePunchBackgroundSettings);
     }
 
     if (sDebugHighlighLayers) {
@@ -259,7 +256,7 @@
                 .alpha = half(0.05f),
         };
 
-        layerSettingsPointers.emplace_back(&highlight);
+        layerSettings.emplace_back(highlight);
     }
 
     auto texture = texturePool.borrowTexture();
@@ -274,17 +271,17 @@
         bufferFence.reset(texture->getReadyFence()->dup());
     }
 
-    base::unique_fd drawFence;
-    status_t result =
-            renderEngine.drawLayers(displaySettings, layerSettingsPointers, texture->get(), false,
-                                    std::move(bufferFence), &drawFence);
+    auto [status, drawFence] = renderEngine
+                                       .drawLayers(displaySettings, layerSettings, texture->get(),
+                                                   false, std::move(bufferFence))
+                                       .get();
 
-    if (result == NO_ERROR) {
+    if (status == NO_ERROR) {
         mDrawFence = new Fence(drawFence.release());
         mOutputSpace = outputState.framebufferSpace;
         mTexture = texture;
         mTexture->setReadyFence(mDrawFence);
-        mOutputSpace.orientation = outputState.framebufferSpace.orientation;
+        mOutputSpace.setOrientation(outputState.framebufferSpace.getOrientation());
         mOutputDataspace = outputDataspace;
         mOrientation = orientation;
         mSkipCount = 0;
@@ -310,7 +307,12 @@
     }
 
     const auto& layerFE = mLayers[0].getState()->getOutputLayer()->getLayerFE();
-    if (layerFE.getCompositionState()->forceClientComposition) {
+    const auto* compositionState = layerFE.getCompositionState();
+    if (compositionState->forceClientComposition) {
+        return false;
+    }
+
+    if (compositionState->blendMode != hal::BlendMode::NONE) {
         return false;
     }
 
@@ -394,24 +396,36 @@
         const auto b = mTexture ? mTexture->get()->getBuffer().get() : nullptr;
         base::StringAppendF(&result, "    Override buffer: %p\n", b);
     }
-    base::StringAppendF(&result, "    HolePunchLayer: %p\n", mHolePunchLayer);
+    base::StringAppendF(&result, "    HolePunchLayer: %p\t%s\n", mHolePunchLayer,
+                        mHolePunchLayer
+                                ? mHolePunchLayer->getOutputLayer()->getLayerFE().getDebugName()
+                                : "");
 
     if (mLayers.size() == 1) {
         base::StringAppendF(&result, "    Layer [%s]\n", mLayers[0].getName().c_str());
-        base::StringAppendF(&result, "    Buffer %p", mLayers[0].getBuffer().get());
-        base::StringAppendF(&result, "    Protected [%s]",
+        if (auto* buffer = mLayers[0].getBuffer().get()) {
+            base::StringAppendF(&result, "    Buffer %p", buffer);
+            base::StringAppendF(&result, "    Format %s",
+                                decodePixelFormat(buffer->getPixelFormat()).c_str());
+        }
+        base::StringAppendF(&result, "    Protected [%s]\n",
                             mLayers[0].getState()->isProtected() ? "true" : "false");
     } else {
-        result.append("    Cached set of:");
+        result.append("    Cached set of:\n");
         for (const Layer& layer : mLayers) {
-            base::StringAppendF(&result, "\n      Layer [%s]", layer.getName().c_str());
-            base::StringAppendF(&result, "\n      Protected [%s]",
+            base::StringAppendF(&result, "      Layer [%s]\n", layer.getName().c_str());
+            if (auto* buffer = layer.getBuffer().get()) {
+                base::StringAppendF(&result, "       Buffer %p", buffer);
+                base::StringAppendF(&result, "    Format[%s]",
+                                    decodePixelFormat(buffer->getPixelFormat()).c_str());
+            }
+            base::StringAppendF(&result, "       Protected [%s]\n",
                                 layer.getState()->isProtected() ? "true" : "false");
         }
     }
 
-    base::StringAppendF(&result, "\n    Creation cost: %zd", getCreationCost());
-    base::StringAppendF(&result, "\n    Display cost: %zd\n", getDisplayCost());
+    base::StringAppendF(&result, "    Creation cost: %zd\n", getCreationCost());
+    base::StringAppendF(&result, "    Display cost: %zd\n", getDisplayCost());
 }
 
 } // namespace android::compositionengine::impl::planner
diff --git a/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp b/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp
index ad5e931..8d26747 100644
--- a/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp
@@ -211,7 +211,8 @@
         displayCost += static_cast<size_t>(layer->getDisplayFrame().width() *
                                            layer->getDisplayFrame().height());
 
-        hasClientComposition |= layer->getCompositionType() == hal::Composition::CLIENT;
+        hasClientComposition |= layer->getCompositionType() ==
+                aidl::android::hardware::graphics::composer3::Composition::CLIENT;
     }
 
     if (hasClientComposition) {
@@ -315,7 +316,7 @@
                         state.overrideInfo = {
                                 .buffer = mNewCachedSet->getBuffer(),
                                 .acquireFence = mNewCachedSet->getDrawFence(),
-                                .displayFrame = mNewCachedSet->getTextureBounds(),
+                                .displayFrame = mNewCachedSet->getBounds(),
                                 .dataspace = mNewCachedSet->getOutputDataspace(),
                                 .displaySpace = mNewCachedSet->getOutputSpace(),
                                 .damageRegion = Region::INVALID_REGION,
@@ -355,7 +356,7 @@
                 state.overrideInfo = {
                         .buffer = currentLayerIter->getBuffer(),
                         .acquireFence = currentLayerIter->getDrawFence(),
-                        .displayFrame = currentLayerIter->getTextureBounds(),
+                        .displayFrame = currentLayerIter->getBounds(),
                         .dataspace = currentLayerIter->getOutputDataspace(),
                         .displaySpace = currentLayerIter->getOutputSpace(),
                         .damageRegion = Region(),
@@ -400,6 +401,19 @@
     return true;
 }
 
+namespace {
+bool isDisplayDecoration(const CachedSet& cachedSet) {
+    return cachedSet.getLayerCount() == 1 &&
+            cachedSet.getFirstLayer()
+                    .getState()
+                    ->getOutputLayer()
+                    ->getLayerFE()
+                    .getCompositionState()
+                    ->compositionType ==
+            aidl::android::hardware::graphics::composer3::Composition::DISPLAY_DECORATION;
+}
+} // namespace
+
 std::vector<Flattener::Run> Flattener::findCandidateRuns(time_point now) const {
     ATRACE_CALL();
     std::vector<Run> runs;
@@ -409,25 +423,29 @@
     bool runHasFirstLayer = false;
 
     for (auto currentSet = mLayers.cbegin(); currentSet != mLayers.cend(); ++currentSet) {
-        const bool layerIsInactive =
-                now - currentSet->getLastUpdate() > mTunables.mActiveLayerTimeout;
+        bool layerIsInactive = now - currentSet->getLastUpdate() > mTunables.mActiveLayerTimeout;
         const bool layerHasBlur = currentSet->hasBlurBehind();
 
+        // Layers should also be considered inactive whenever their framerate is lower than 1fps.
+        if (!layerIsInactive && currentSet->getLayerCount() == kNumLayersFpsConsideration) {
+            auto layerFps = currentSet->getFirstLayer().getState()->getFps();
+            if (layerFps > 0 && layerFps <= kFpsActiveThreshold) {
+                ATRACE_FORMAT("layer is considered inactive due to low FPS [%s] %f",
+                              currentSet->getFirstLayer().getName().c_str(), layerFps);
+                layerIsInactive = true;
+            }
+        }
+
         if (layerIsInactive && (firstLayer || runHasFirstLayer || !layerHasBlur) &&
-            !currentSet->hasUnsupportedDataspace()) {
+            !currentSet->hasUnsupportedDataspace() && !isDisplayDecoration(*currentSet)) {
             if (isPartOfRun) {
-                builder.append(currentSet->getLayerCount());
+                builder.increment();
             } else {
-                // Runs can't start with a non-buffer layer
-                if (currentSet->getFirstLayer().getBuffer() == nullptr) {
-                    ALOGV("[%s] Skipping initial non-buffer layer", __func__);
-                } else {
-                    builder.init(currentSet);
-                    if (firstLayer) {
-                        runHasFirstLayer = true;
-                    }
-                    isPartOfRun = true;
+                builder.init(currentSet);
+                if (firstLayer) {
+                    runHasFirstLayer = true;
                 }
+                isPartOfRun = true;
             }
         } else if (isPartOfRun) {
             builder.setHolePunchCandidate(&(*currentSet));
diff --git a/services/surfaceflinger/CompositionEngine/src/planner/LayerState.cpp b/services/surfaceflinger/CompositionEngine/src/planner/LayerState.cpp
index 936dba3..c79ca0d 100644
--- a/services/surfaceflinger/CompositionEngine/src/planner/LayerState.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/planner/LayerState.cpp
@@ -93,11 +93,7 @@
 
 void LayerState::dump(std::string& result) const {
     for (const StateInterface* field : getNonUniqueFields()) {
-        if (auto viewOpt = flag_name(field->getField()); viewOpt) {
-            base::StringAppendF(&result, "  %16s: ", std::string(*viewOpt).c_str());
-        } else {
-            result.append("<UNKNOWN FIELD>:\n");
-        }
+        base::StringAppendF(&result, "  %16s: ", ftl::flag_string(field->getField()).c_str());
 
         bool first = true;
         for (const std::string& line : field->toStrings()) {
@@ -126,11 +122,7 @@
             continue;
         }
 
-        if (auto viewOpt = flag_name(thisField->getField()); viewOpt) {
-            base::StringAppendF(&result, "  %16s: ", std::string(*viewOpt).c_str());
-        } else {
-            result.append("<UNKNOWN FIELD>:\n");
-        }
+        base::StringAppendF(&result, "  %16s: ", ftl::flag_string(thisField->getField()).c_str());
 
         const auto& thisStrings = thisField->toStrings();
         const auto& otherStrings = otherField->toStrings();
@@ -168,7 +160,8 @@
             lhs.mColorTransform == rhs.mColorTransform &&
             lhs.mCompositionType == rhs.mCompositionType &&
             lhs.mSidebandStream == rhs.mSidebandStream && lhs.mBuffer == rhs.mBuffer &&
-            (lhs.mCompositionType.get() != hal::Composition::SOLID_COLOR ||
+            (lhs.mCompositionType.get() !=
+                     aidl::android::hardware::graphics::composer3::Composition::SOLID_COLOR ||
              lhs.mSolidColor == rhs.mSolidColor);
 }
 
diff --git a/services/surfaceflinger/CompositionEngine/src/planner/Planner.cpp b/services/surfaceflinger/CompositionEngine/src/planner/Planner.cpp
index f5b1cee..74d2701 100644
--- a/services/surfaceflinger/CompositionEngine/src/planner/Planner.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/planner/Planner.cpp
@@ -193,7 +193,7 @@
 
         finalPlan.addLayerType(
                 forcedOrRequestedClient
-                        ? hardware::graphics::composer::hal::Composition::CLIENT
+                        ? aidl::android::hardware::graphics::composer3::Composition::CLIENT
                         : layer->getLayerFE().getCompositionState()->compositionType);
     }
 
diff --git a/services/surfaceflinger/CompositionEngine/src/planner/Predictor.cpp b/services/surfaceflinger/CompositionEngine/src/planner/Predictor.cpp
index 8226ef7..2d53583 100644
--- a/services/surfaceflinger/CompositionEngine/src/planner/Predictor.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/planner/Predictor.cpp
@@ -39,8 +39,10 @@
 
         // Skip layers where both are client-composited, since that doesn't change the
         // composition plan
-        if (mLayers[i].getCompositionType() == hal::Composition::CLIENT &&
-            other[i]->getCompositionType() == hal::Composition::CLIENT) {
+        if (mLayers[i].getCompositionType() ==
+                    aidl::android::hardware::graphics::composer3::Composition::CLIENT &&
+            other[i]->getCompositionType() ==
+                    aidl::android::hardware::graphics::composer3::Composition::CLIENT) {
             continue;
         }
 
@@ -89,22 +91,32 @@
     for (char c : string) {
         switch (c) {
             case 'C':
-                plan.addLayerType(hal::Composition::CLIENT);
+                plan.addLayerType(
+                        aidl::android::hardware::graphics::composer3::Composition::CLIENT);
                 continue;
             case 'U':
-                plan.addLayerType(hal::Composition::CURSOR);
+                plan.addLayerType(
+                        aidl::android::hardware::graphics::composer3::Composition::CURSOR);
                 continue;
             case 'D':
-                plan.addLayerType(hal::Composition::DEVICE);
+                plan.addLayerType(
+                        aidl::android::hardware::graphics::composer3::Composition::DEVICE);
                 continue;
             case 'I':
-                plan.addLayerType(hal::Composition::INVALID);
+                plan.addLayerType(
+                        aidl::android::hardware::graphics::composer3::Composition::INVALID);
                 continue;
             case 'B':
-                plan.addLayerType(hal::Composition::SIDEBAND);
+                plan.addLayerType(
+                        aidl::android::hardware::graphics::composer3::Composition::SIDEBAND);
                 continue;
             case 'S':
-                plan.addLayerType(hal::Composition::SOLID_COLOR);
+                plan.addLayerType(
+                        aidl::android::hardware::graphics::composer3::Composition::SOLID_COLOR);
+                continue;
+            case 'A':
+                plan.addLayerType(aidl::android::hardware::graphics::composer3::Composition::
+                                          DISPLAY_DECORATION);
                 continue;
             default:
                 return std::nullopt;
@@ -117,24 +129,28 @@
     std::string result;
     for (auto type : plan.mLayerTypes) {
         switch (type) {
-            case hal::Composition::CLIENT:
+            case aidl::android::hardware::graphics::composer3::Composition::CLIENT:
                 result.append("C");
                 break;
-            case hal::Composition::CURSOR:
+            case aidl::android::hardware::graphics::composer3::Composition::CURSOR:
                 result.append("U");
                 break;
-            case hal::Composition::DEVICE:
+            case aidl::android::hardware::graphics::composer3::Composition::DEVICE:
                 result.append("D");
                 break;
-            case hal::Composition::INVALID:
+            case aidl::android::hardware::graphics::composer3::Composition::INVALID:
                 result.append("I");
                 break;
-            case hal::Composition::SIDEBAND:
+            case aidl::android::hardware::graphics::composer3::Composition::SIDEBAND:
                 result.append("B");
                 break;
-            case hal::Composition::SOLID_COLOR:
+            case aidl::android::hardware::graphics::composer3::Composition::SOLID_COLOR:
                 result.append("S");
                 break;
+            case aidl::android::hardware::graphics::composer3::Composition::DISPLAY_DECORATION:
+                // A for "Alpha", since the decoration is an alpha layer.
+                result.append("A");
+                break;
         }
     }
     return result;
diff --git a/services/surfaceflinger/CompositionEngine/src/planner/TexturePool.cpp b/services/surfaceflinger/CompositionEngine/src/planner/TexturePool.cpp
index 497c433..54ecb56 100644
--- a/services/surfaceflinger/CompositionEngine/src/planner/TexturePool.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/planner/TexturePool.cpp
@@ -20,6 +20,7 @@
 #define LOG_TAG "Planner"
 
 #include <compositionengine/impl/planner/TexturePool.h>
+#include <renderengine/impl/ExternalTexture.h>
 #include <utils/Log.h>
 
 namespace android::compositionengine::impl::planner {
@@ -82,16 +83,19 @@
 std::shared_ptr<renderengine::ExternalTexture> TexturePool::genTexture() {
     LOG_ALWAYS_FATAL_IF(!mSize.isValid(), "Attempted to generate texture with invalid size");
     return std::make_shared<
-            renderengine::ExternalTexture>(sp<GraphicBuffer>::
-                                                   make(mSize.getWidth(), mSize.getHeight(),
-                                                        HAL_PIXEL_FORMAT_RGBA_8888, 1,
-                                                        GraphicBuffer::USAGE_HW_RENDER |
-                                                                GraphicBuffer::USAGE_HW_COMPOSER |
-                                                                GraphicBuffer::USAGE_HW_TEXTURE,
-                                                        "Planner"),
-                                           mRenderEngine,
-                                           renderengine::ExternalTexture::Usage::READABLE |
-                                                   renderengine::ExternalTexture::Usage::WRITEABLE);
+            renderengine::impl::
+                    ExternalTexture>(sp<GraphicBuffer>::
+                                             make(static_cast<uint32_t>(mSize.getWidth()),
+                                                  static_cast<uint32_t>(mSize.getHeight()),
+                                                  HAL_PIXEL_FORMAT_RGBA_8888, 1U,
+                                                  static_cast<uint64_t>(
+                                                          GraphicBuffer::USAGE_HW_RENDER |
+                                                          GraphicBuffer::USAGE_HW_COMPOSER |
+                                                          GraphicBuffer::USAGE_HW_TEXTURE),
+                                                  "Planner"),
+                                     mRenderEngine,
+                                     renderengine::impl::ExternalTexture::Usage::READABLE |
+                                             renderengine::impl::ExternalTexture::Usage::WRITEABLE);
 }
 
 void TexturePool::setEnabled(bool enabled) {
diff --git a/services/surfaceflinger/CompositionEngine/tests/CompositionEngineTest.cpp b/services/surfaceflinger/CompositionEngine/tests/CompositionEngineTest.cpp
index 325361b..de9de01 100644
--- a/services/surfaceflinger/CompositionEngine/tests/CompositionEngineTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/CompositionEngineTest.cpp
@@ -203,9 +203,9 @@
  */
 
 struct CompositionTestPreComposition : public CompositionEngineTest {
-    sp<StrictMock<mock::LayerFE>> mLayer1FE{new StrictMock<mock::LayerFE>()};
-    sp<StrictMock<mock::LayerFE>> mLayer2FE{new StrictMock<mock::LayerFE>()};
-    sp<StrictMock<mock::LayerFE>> mLayer3FE{new StrictMock<mock::LayerFE>()};
+    sp<StrictMock<mock::LayerFE>> mLayer1FE = sp<StrictMock<mock::LayerFE>>::make();
+    sp<StrictMock<mock::LayerFE>> mLayer2FE = sp<StrictMock<mock::LayerFE>>::make();
+    sp<StrictMock<mock::LayerFE>> mLayer3FE = sp<StrictMock<mock::LayerFE>>::make();
 };
 
 TEST_F(CompositionTestPreComposition, preCompositionSetsFrameTimestamp) {
diff --git a/services/surfaceflinger/CompositionEngine/tests/DisplayTest.cpp b/services/surfaceflinger/CompositionEngine/tests/DisplayTest.cpp
index c037cc6..5cc0f97 100644
--- a/services/surfaceflinger/CompositionEngine/tests/DisplayTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/DisplayTest.cpp
@@ -37,6 +37,12 @@
 #include "MockHWC2.h"
 #include "MockHWComposer.h"
 #include "MockPowerAdvisor.h"
+#include "ftl/future.h"
+
+#include <aidl/android/hardware/graphics/composer3/Composition.h>
+
+using aidl::android::hardware::graphics::composer3::Capability;
+using aidl::android::hardware::graphics::composer3::Composition;
 
 namespace android::compositionengine {
 namespace {
@@ -44,6 +50,7 @@
 namespace hal = android::hardware::graphics::composer::hal;
 
 using testing::_;
+using testing::ByMove;
 using testing::DoAll;
 using testing::Eq;
 using testing::InSequence;
@@ -60,8 +67,7 @@
 constexpr HalVirtualDisplayId HAL_VIRTUAL_DISPLAY_ID{456u};
 constexpr GpuVirtualDisplayId GPU_VIRTUAL_DISPLAY_ID{789u};
 
-const ui::Size DEFAULT_RESOLUTION{1920, 1080};
-constexpr uint32_t DEFAULT_LAYER_STACK = 42;
+constexpr ui::Size DEFAULT_RESOLUTION{1920, 1080};
 
 struct Layer {
     Layer() {
@@ -69,7 +75,7 @@
         EXPECT_CALL(*outputLayer, getHwcLayer()).WillRepeatedly(Return(&hwc2Layer));
     }
 
-    sp<mock::LayerFE> layerFE = new StrictMock<mock::LayerFE>();
+    sp<StrictMock<mock::LayerFE>> layerFE = sp<StrictMock<mock::LayerFE>>::make();
     StrictMock<mock::OutputLayer>* outputLayer = new StrictMock<mock::OutputLayer>();
     StrictMock<HWC2::mock::Layer> hwc2Layer;
 };
@@ -80,7 +86,7 @@
         EXPECT_CALL(*outputLayer, getHwcLayer()).WillRepeatedly(Return(nullptr));
     }
 
-    sp<mock::LayerFE> layerFE = new StrictMock<mock::LayerFE>();
+    sp<StrictMock<mock::LayerFE>> layerFE = sp<StrictMock<mock::LayerFE>>::make();
     StrictMock<mock::OutputLayer>* outputLayer = new StrictMock<mock::OutputLayer>();
 };
 
@@ -161,13 +167,11 @@
         EXPECT_CALL(mRenderEngine, isProtected()).WillRepeatedly(Return(false));
     }
 
-    DisplayCreationArgs getDisplayCreationArgsForPhysicalHWCDisplay() {
+    DisplayCreationArgs getDisplayCreationArgsForPhysicalDisplay() {
         return DisplayCreationArgsBuilder()
                 .setId(DEFAULT_DISPLAY_ID)
-                .setConnectionType(ui::DisplayConnectionType::Internal)
                 .setPixels(DEFAULT_RESOLUTION)
                 .setIsSecure(true)
-                .setLayerStackId(DEFAULT_LAYER_STACK)
                 .setPowerAdvisor(&mPowerAdvisor)
                 .build();
     }
@@ -177,7 +181,6 @@
                 .setId(GPU_VIRTUAL_DISPLAY_ID)
                 .setPixels(DEFAULT_RESOLUTION)
                 .setIsSecure(false)
-                .setLayerStackId(DEFAULT_LAYER_STACK)
                 .setPowerAdvisor(&mPowerAdvisor)
                 .build();
     }
@@ -193,14 +196,13 @@
     using Display = DisplayTestCommon::PartialMockDisplay;
     std::shared_ptr<Display> mDisplay =
             createPartialMockDisplay<Display>(mCompositionEngine,
-                                              getDisplayCreationArgsForPhysicalHWCDisplay());
+                                              getDisplayCreationArgsForPhysicalDisplay());
 };
 
 struct FullDisplayImplTestCommon : public DisplayTestCommon {
     using Display = DisplayTestCommon::FullImplDisplay;
     std::shared_ptr<Display> mDisplay =
-            createDisplay<Display>(mCompositionEngine,
-                                   getDisplayCreationArgsForPhysicalHWCDisplay());
+            createDisplay<Display>(mCompositionEngine, getDisplayCreationArgsForPhysicalDisplay());
 };
 
 struct DisplayWithLayersTestCommon : public FullDisplayImplTestCommon {
@@ -218,8 +220,7 @@
     LayerNoHWC2Layer mLayer3;
     StrictMock<HWC2::mock::Layer> hwc2LayerUnknown;
     std::shared_ptr<Display> mDisplay =
-            createDisplay<Display>(mCompositionEngine,
-                                   getDisplayCreationArgsForPhysicalHWCDisplay());
+            createDisplay<Display>(mCompositionEngine, getDisplayCreationArgsForPhysicalDisplay());
 };
 
 /*
@@ -232,7 +233,7 @@
 
 TEST_F(DisplayCreationTest, createPhysicalInternalDisplay) {
     auto display =
-            impl::createDisplay(mCompositionEngine, getDisplayCreationArgsForPhysicalHWCDisplay());
+            impl::createDisplay(mCompositionEngine, getDisplayCreationArgsForPhysicalDisplay());
     EXPECT_TRUE(display->isSecure());
     EXPECT_FALSE(display->isVirtual());
     EXPECT_EQ(DEFAULT_DISPLAY_ID, display->getId());
@@ -252,13 +253,11 @@
 
 using DisplaySetConfigurationTest = PartialMockDisplayTestCommon;
 
-TEST_F(DisplaySetConfigurationTest, configuresInternalSecurePhysicalDisplay) {
+TEST_F(DisplaySetConfigurationTest, configuresPhysicalDisplay) {
     mDisplay->setConfiguration(DisplayCreationArgsBuilder()
                                        .setId(DEFAULT_DISPLAY_ID)
-                                       .setConnectionType(ui::DisplayConnectionType::Internal)
                                        .setPixels(DEFAULT_RESOLUTION)
                                        .setIsSecure(true)
-                                       .setLayerStackId(DEFAULT_LAYER_STACK)
                                        .setPowerAdvisor(&mPowerAdvisor)
                                        .setName(getDisplayNameFromCurrentTest())
                                        .build());
@@ -266,28 +265,11 @@
     EXPECT_EQ(DEFAULT_DISPLAY_ID, mDisplay->getId());
     EXPECT_TRUE(mDisplay->isSecure());
     EXPECT_FALSE(mDisplay->isVirtual());
-    EXPECT_EQ(DEFAULT_LAYER_STACK, mDisplay->getState().layerStackId);
-    EXPECT_TRUE(mDisplay->getState().layerStackInternal);
     EXPECT_FALSE(mDisplay->isValid());
-}
 
-TEST_F(DisplaySetConfigurationTest, configuresExternalInsecurePhysicalDisplay) {
-    mDisplay->setConfiguration(DisplayCreationArgsBuilder()
-                                       .setId(DEFAULT_DISPLAY_ID)
-                                       .setConnectionType(ui::DisplayConnectionType::External)
-                                       .setPixels(DEFAULT_RESOLUTION)
-                                       .setIsSecure(false)
-                                       .setLayerStackId(DEFAULT_LAYER_STACK)
-                                       .setPowerAdvisor(&mPowerAdvisor)
-                                       .setName(getDisplayNameFromCurrentTest())
-                                       .build());
-
-    EXPECT_EQ(DEFAULT_DISPLAY_ID, mDisplay->getId());
-    EXPECT_FALSE(mDisplay->isSecure());
-    EXPECT_FALSE(mDisplay->isVirtual());
-    EXPECT_EQ(DEFAULT_LAYER_STACK, mDisplay->getState().layerStackId);
-    EXPECT_FALSE(mDisplay->getState().layerStackInternal);
-    EXPECT_FALSE(mDisplay->isValid());
+    const auto& filter = mDisplay->getState().layerFilter;
+    EXPECT_EQ(ui::INVALID_LAYER_STACK, filter.layerStack);
+    EXPECT_FALSE(filter.toInternalDisplay);
 }
 
 TEST_F(DisplaySetConfigurationTest, configuresHalVirtualDisplay) {
@@ -295,7 +277,6 @@
                                        .setId(HAL_VIRTUAL_DISPLAY_ID)
                                        .setPixels(DEFAULT_RESOLUTION)
                                        .setIsSecure(false)
-                                       .setLayerStackId(DEFAULT_LAYER_STACK)
                                        .setPowerAdvisor(&mPowerAdvisor)
                                        .setName(getDisplayNameFromCurrentTest())
                                        .build());
@@ -303,9 +284,11 @@
     EXPECT_EQ(HAL_VIRTUAL_DISPLAY_ID, mDisplay->getId());
     EXPECT_FALSE(mDisplay->isSecure());
     EXPECT_TRUE(mDisplay->isVirtual());
-    EXPECT_EQ(DEFAULT_LAYER_STACK, mDisplay->getState().layerStackId);
-    EXPECT_FALSE(mDisplay->getState().layerStackInternal);
     EXPECT_FALSE(mDisplay->isValid());
+
+    const auto& filter = mDisplay->getState().layerFilter;
+    EXPECT_EQ(ui::INVALID_LAYER_STACK, filter.layerStack);
+    EXPECT_FALSE(filter.toInternalDisplay);
 }
 
 TEST_F(DisplaySetConfigurationTest, configuresGpuVirtualDisplay) {
@@ -313,7 +296,6 @@
                                        .setId(GPU_VIRTUAL_DISPLAY_ID)
                                        .setPixels(DEFAULT_RESOLUTION)
                                        .setIsSecure(false)
-                                       .setLayerStackId(DEFAULT_LAYER_STACK)
                                        .setPowerAdvisor(&mPowerAdvisor)
                                        .setName(getDisplayNameFromCurrentTest())
                                        .build());
@@ -321,9 +303,11 @@
     EXPECT_EQ(GPU_VIRTUAL_DISPLAY_ID, mDisplay->getId());
     EXPECT_FALSE(mDisplay->isSecure());
     EXPECT_TRUE(mDisplay->isVirtual());
-    EXPECT_EQ(DEFAULT_LAYER_STACK, mDisplay->getState().layerStackId);
-    EXPECT_FALSE(mDisplay->getState().layerStackInternal);
     EXPECT_FALSE(mDisplay->isValid());
+
+    const auto& filter = mDisplay->getState().layerFilter;
+    EXPECT_EQ(ui::INVALID_LAYER_STACK, filter.layerStack);
+    EXPECT_FALSE(filter.toInternalDisplay);
 }
 
 /*
@@ -485,7 +469,7 @@
 using DisplayCreateOutputLayerTest = FullDisplayImplTestCommon;
 
 TEST_F(DisplayCreateOutputLayerTest, setsHwcLayer) {
-    sp<mock::LayerFE> layerFE = new StrictMock<mock::LayerFE>();
+    sp<StrictMock<mock::LayerFE>> layerFE = sp<StrictMock<mock::LayerFE>>::make();
     auto hwcLayer = std::make_shared<StrictMock<HWC2::mock::Layer>>();
 
     EXPECT_CALL(mHwComposer, createLayer(HalDisplayId(DEFAULT_DISPLAY_ID)))
@@ -579,7 +563,7 @@
 TEST_F(DisplayChooseCompositionStrategyTest, takesEarlyOutOnHwcError) {
     EXPECT_CALL(*mDisplay, anyLayersRequireClientComposition()).WillOnce(Return(false));
     EXPECT_CALL(mHwComposer,
-                getDeviceCompositionChanges(HalDisplayId(DEFAULT_DISPLAY_ID), false, _, _, _))
+                getDeviceCompositionChanges(HalDisplayId(DEFAULT_DISPLAY_ID), false, _, _, _, _))
             .WillOnce(Return(INVALID_OPERATION));
 
     mDisplay->chooseCompositionStrategy();
@@ -602,7 +586,7 @@
             .WillOnce(Return(false));
 
     EXPECT_CALL(mHwComposer,
-                getDeviceCompositionChanges(HalDisplayId(DEFAULT_DISPLAY_ID), true, _, _, _))
+                getDeviceCompositionChanges(HalDisplayId(DEFAULT_DISPLAY_ID), true, _, _, _, _))
             .WillOnce(Return(NO_ERROR));
     EXPECT_CALL(*mDisplay, allLayersRequireClientComposition()).WillOnce(Return(false));
 
@@ -613,12 +597,45 @@
     EXPECT_TRUE(state.usesDeviceComposition);
 }
 
+TEST_F(DisplayChooseCompositionStrategyTest, normalOperationWithDisplayBrightness) {
+    // Since two calls are made to anyLayersRequireClientComposition with different return
+    // values, use a Sequence to control the matching so the values are returned in a known
+    // order.
+    constexpr float kDisplayBrightness = 0.5f;
+    Sequence s;
+    EXPECT_CALL(*mDisplay, anyLayersRequireClientComposition())
+            .InSequence(s)
+            .WillOnce(Return(true));
+    EXPECT_CALL(*mDisplay, anyLayersRequireClientComposition())
+            .InSequence(s)
+            .WillOnce(Return(false));
+    EXPECT_CALL(mHwComposer,
+                setDisplayBrightness(DEFAULT_DISPLAY_ID, kDisplayBrightness,
+                                     Hwc2::Composer::DisplayBrightnessOptions{.applyImmediately =
+                                                                                      false}))
+            .WillOnce(Return(ByMove(ftl::yield<status_t>(NO_ERROR))));
+
+    EXPECT_CALL(mHwComposer,
+                getDeviceCompositionChanges(HalDisplayId(DEFAULT_DISPLAY_ID), true, _, _, _, _))
+            .WillOnce(Return(NO_ERROR));
+    EXPECT_CALL(*mDisplay, allLayersRequireClientComposition()).WillOnce(Return(false));
+
+    mDisplay->setNextBrightness(kDisplayBrightness);
+    mDisplay->chooseCompositionStrategy();
+
+    auto& state = mDisplay->getState();
+    EXPECT_FALSE(state.usesClientComposition);
+    EXPECT_TRUE(state.usesDeviceComposition);
+    EXPECT_FALSE(state.displayBrightness.has_value());
+}
+
 TEST_F(DisplayChooseCompositionStrategyTest, normalOperationWithChanges) {
     android::HWComposer::DeviceRequestedChanges changes{
-            {{nullptr, hal::Composition::CLIENT}},
+            {{nullptr, Composition::CLIENT}},
             hal::DisplayRequest::FLIP_CLIENT_TARGET,
             {{nullptr, hal::LayerRequest::CLEAR_CLIENT_TARGET}},
             {hal::PixelFormat::RGBA_8888, hal::Dataspace::UNKNOWN},
+            -1.f,
     };
 
     // Since two calls are made to anyLayersRequireClientComposition with different return
@@ -633,8 +650,8 @@
             .WillOnce(Return(false));
 
     EXPECT_CALL(mHwComposer,
-                getDeviceCompositionChanges(HalDisplayId(DEFAULT_DISPLAY_ID), true, _, _, _))
-            .WillOnce(DoAll(SetArgPointee<4>(changes), Return(NO_ERROR)));
+                getDeviceCompositionChanges(HalDisplayId(DEFAULT_DISPLAY_ID), true, _, _, _, _))
+            .WillOnce(DoAll(SetArgPointee<5>(changes), Return(NO_ERROR)));
     EXPECT_CALL(*mDisplay, applyChangedTypesToLayers(changes.changedTypes)).Times(1);
     EXPECT_CALL(*mDisplay, applyDisplayRequests(changes.displayRequests)).Times(1);
     EXPECT_CALL(*mDisplay, applyLayerRequestsToLayers(changes.layerRequests)).Times(1);
@@ -652,9 +669,10 @@
  */
 
 using DisplayGetSkipColorTransformTest = DisplayWithLayersTestCommon;
+using aidl::android::hardware::graphics::composer3::DisplayCapability;
 
 TEST_F(DisplayGetSkipColorTransformTest, checksCapabilityIfGpuDisplay) {
-    EXPECT_CALL(mHwComposer, hasCapability(hal::Capability::SKIP_CLIENT_COLOR_TRANSFORM))
+    EXPECT_CALL(mHwComposer, hasCapability(Capability::SKIP_CLIENT_COLOR_TRANSFORM))
             .WillOnce(Return(true));
     auto args = getDisplayCreationArgsForGpuVirtualDisplay();
     auto gpuDisplay{impl::createDisplay(mCompositionEngine, args)};
@@ -664,7 +682,7 @@
 TEST_F(DisplayGetSkipColorTransformTest, checksDisplayCapability) {
     EXPECT_CALL(mHwComposer,
                 hasDisplayCapability(HalDisplayId(DEFAULT_DISPLAY_ID),
-                                     hal::DisplayCapability::SKIP_CLIENT_COLOR_TRANSFORM))
+                                     DisplayCapability::SKIP_CLIENT_COLOR_TRANSFORM))
             .WillOnce(Return(true));
     EXPECT_TRUE(mDisplay->getSkipColorTransform());
 }
@@ -722,17 +740,15 @@
 }
 
 TEST_F(DisplayApplyChangedTypesToLayersTest, appliesChanges) {
-    EXPECT_CALL(*mLayer1.outputLayer,
-                applyDeviceCompositionTypeChange(Hwc2::IComposerClient::Composition::CLIENT))
+    EXPECT_CALL(*mLayer1.outputLayer, applyDeviceCompositionTypeChange(Composition::CLIENT))
             .Times(1);
-    EXPECT_CALL(*mLayer2.outputLayer,
-                applyDeviceCompositionTypeChange(Hwc2::IComposerClient::Composition::DEVICE))
+    EXPECT_CALL(*mLayer2.outputLayer, applyDeviceCompositionTypeChange(Composition::DEVICE))
             .Times(1);
 
     mDisplay->applyChangedTypesToLayers(impl::Display::ChangedTypes{
-            {&mLayer1.hwc2Layer, hal::Composition::CLIENT},
-            {&mLayer2.hwc2Layer, hal::Composition::DEVICE},
-            {&hwc2LayerUnknown, hal::Composition::SOLID_COLOR},
+            {&mLayer1.hwc2Layer, Composition::CLIENT},
+            {&mLayer2.hwc2Layer, Composition::DEVICE},
+            {&hwc2LayerUnknown, Composition::SOLID_COLOR},
     });
 }
 
@@ -811,15 +827,18 @@
             .dataspace = hal::Dataspace::STANDARD_BT470M,
     };
 
+    static constexpr float kWhitePointNits = 800.f;
+
     mock::RenderSurface* renderSurface = new StrictMock<mock::RenderSurface>();
     mDisplay->setRenderSurfaceForTest(std::unique_ptr<RenderSurface>(renderSurface));
 
     EXPECT_CALL(*renderSurface, setBufferPixelFormat(clientTargetProperty.pixelFormat));
     EXPECT_CALL(*renderSurface, setBufferDataspace(clientTargetProperty.dataspace));
-    mDisplay->applyClientTargetRequests(clientTargetProperty);
+    mDisplay->applyClientTargetRequests(clientTargetProperty, kWhitePointNits);
 
     auto& state = mDisplay->getState();
     EXPECT_EQ(clientTargetProperty.dataspace, state.dataspace);
+    EXPECT_EQ(kWhitePointNits, state.clientTargetBrightness);
 }
 
 /*
@@ -899,13 +918,10 @@
 
     mDisplay->editState().isEnabled = true;
     mDisplay->editState().usesClientComposition = false;
-    mDisplay->editState().layerStackSpace.content = Rect(0, 0, 1, 1);
+    mDisplay->editState().layerStackSpace.setContent(Rect(0, 0, 1, 1));
     mDisplay->editState().dirtyRegion = Region::INVALID_REGION;
 
-    CompositionRefreshArgs refreshArgs;
-    refreshArgs.repaintEverything = false;
-
-    mDisplay->finishFrame(refreshArgs);
+    mDisplay->finishFrame({});
 }
 
 TEST_F(DisplayFinishFrameTest, skipsCompositionIfNotDirty) {
@@ -920,13 +936,10 @@
 
     gpuDisplay->editState().isEnabled = true;
     gpuDisplay->editState().usesClientComposition = false;
-    gpuDisplay->editState().layerStackSpace.content = Rect(0, 0, 1, 1);
+    gpuDisplay->editState().layerStackSpace.setContent(Rect(0, 0, 1, 1));
     gpuDisplay->editState().dirtyRegion = Region::INVALID_REGION;
 
-    CompositionRefreshArgs refreshArgs;
-    refreshArgs.repaintEverything = false;
-
-    gpuDisplay->finishFrame(refreshArgs);
+    gpuDisplay->finishFrame({});
 }
 
 TEST_F(DisplayFinishFrameTest, performsCompositionIfDirty) {
@@ -941,34 +954,9 @@
 
     gpuDisplay->editState().isEnabled = true;
     gpuDisplay->editState().usesClientComposition = false;
-    gpuDisplay->editState().layerStackSpace.content = Rect(0, 0, 1, 1);
+    gpuDisplay->editState().layerStackSpace.setContent(Rect(0, 0, 1, 1));
     gpuDisplay->editState().dirtyRegion = Region(Rect(0, 0, 1, 1));
-
-    CompositionRefreshArgs refreshArgs;
-    refreshArgs.repaintEverything = false;
-
-    gpuDisplay->finishFrame(refreshArgs);
-}
-
-TEST_F(DisplayFinishFrameTest, performsCompositionIfRepaintEverything) {
-    auto args = getDisplayCreationArgsForGpuVirtualDisplay();
-    std::shared_ptr<impl::Display> gpuDisplay = impl::createDisplay(mCompositionEngine, args);
-
-    mock::RenderSurface* renderSurface = new StrictMock<mock::RenderSurface>();
-    gpuDisplay->setRenderSurfaceForTest(std::unique_ptr<RenderSurface>(renderSurface));
-
-    // We expect a single call to queueBuffer when composition is not skipped.
-    EXPECT_CALL(*renderSurface, queueBuffer(_)).Times(1);
-
-    gpuDisplay->editState().isEnabled = true;
-    gpuDisplay->editState().usesClientComposition = false;
-    gpuDisplay->editState().layerStackSpace.content = Rect(0, 0, 1, 1);
-    gpuDisplay->editState().dirtyRegion = Region::INVALID_REGION;
-
-    CompositionRefreshArgs refreshArgs;
-    refreshArgs.repaintEverything = true;
-
-    gpuDisplay->finishFrame(refreshArgs);
+    gpuDisplay->finishFrame({});
 }
 
 /*
@@ -984,7 +972,8 @@
 
     DisplayFunctionalTest() {
         EXPECT_CALL(mCompositionEngine, getHwComposer()).WillRepeatedly(ReturnRef(mHwComposer));
-
+        mDisplay = createDisplay();
+        mRenderSurface = createRenderSurface();
         mDisplay->setRenderSurfaceForTest(std::unique_ptr<RenderSurface>(mRenderSurface));
     }
 
@@ -993,26 +982,29 @@
     NiceMock<mock::CompositionEngine> mCompositionEngine;
     sp<mock::NativeWindow> mNativeWindow = new NiceMock<mock::NativeWindow>();
     sp<mock::DisplaySurface> mDisplaySurface = new NiceMock<mock::DisplaySurface>();
+    std::shared_ptr<Display> mDisplay;
+    impl::RenderSurface* mRenderSurface;
 
-    std::shared_ptr<Display> mDisplay = impl::createDisplayTemplated<
-            Display>(mCompositionEngine,
-                     DisplayCreationArgsBuilder()
-                             .setId(DEFAULT_DISPLAY_ID)
-                             .setConnectionType(ui::DisplayConnectionType::Internal)
-                             .setPixels(DEFAULT_RESOLUTION)
-                             .setIsSecure(true)
-                             .setLayerStackId(DEFAULT_LAYER_STACK)
-                             .setPowerAdvisor(&mPowerAdvisor)
-                             .build());
+    std::shared_ptr<Display> createDisplay() {
+        return impl::createDisplayTemplated<Display>(mCompositionEngine,
+                                                     DisplayCreationArgsBuilder()
+                                                             .setId(DEFAULT_DISPLAY_ID)
+                                                             .setPixels(DEFAULT_RESOLUTION)
+                                                             .setIsSecure(true)
+                                                             .setPowerAdvisor(&mPowerAdvisor)
+                                                             .build());
+        ;
+    }
 
-    impl::RenderSurface* mRenderSurface =
-            new impl::RenderSurface{mCompositionEngine, *mDisplay,
-                                    RenderSurfaceCreationArgsBuilder()
-                                            .setDisplayWidth(DEFAULT_RESOLUTION.width)
-                                            .setDisplayHeight(DEFAULT_RESOLUTION.height)
-                                            .setNativeWindow(mNativeWindow)
-                                            .setDisplaySurface(mDisplaySurface)
-                                            .build()};
+    impl::RenderSurface* createRenderSurface() {
+        return new impl::RenderSurface{mCompositionEngine, *mDisplay,
+                                       RenderSurfaceCreationArgsBuilder()
+                                               .setDisplayWidth(DEFAULT_RESOLUTION.width)
+                                               .setDisplayHeight(DEFAULT_RESOLUTION.height)
+                                               .setNativeWindow(mNativeWindow)
+                                               .setDisplaySurface(mDisplaySurface)
+                                               .build()};
+    }
 };
 
 TEST_F(DisplayFunctionalTest, postFramebufferCriticalCallsAreOrdered) {
diff --git a/services/surfaceflinger/CompositionEngine/tests/MockHWC2.h b/services/surfaceflinger/CompositionEngine/tests/MockHWC2.h
index 9518659..d933b94 100644
--- a/services/surfaceflinger/CompositionEngine/tests/MockHWC2.h
+++ b/services/surfaceflinger/CompositionEngine/tests/MockHWC2.h
@@ -32,6 +32,8 @@
 #include <ui/GraphicTypes.h>
 #include "DisplayHardware/HWC2.h"
 
+#include <aidl/android/hardware/graphics/composer3/Composition.h>
+
 // TODO(b/129481165): remove the #pragma below and fix conversion issues
 #pragma clang diagnostic pop // ignored "-Wconversion -Wextra"
 
@@ -56,8 +58,9 @@
                        const android::sp<android::Fence>&));
     MOCK_METHOD1(setSurfaceDamage, Error(const android::Region&));
     MOCK_METHOD1(setBlendMode, Error(hal::BlendMode));
-    MOCK_METHOD1(setColor, Error(hal::Color));
-    MOCK_METHOD1(setCompositionType, Error(hal::Composition));
+    MOCK_METHOD1(setColor, Error(aidl::android::hardware::graphics::composer3::Color));
+    MOCK_METHOD1(setCompositionType,
+                 Error(aidl::android::hardware::graphics::composer3::Composition));
     MOCK_METHOD1(setDataspace, Error(android::ui::Dataspace));
     MOCK_METHOD2(setPerFrameMetadata, Error(const int32_t, const android::HdrMetadata&));
     MOCK_METHOD1(setDisplayFrame, Error(const android::Rect&));
@@ -71,6 +74,8 @@
     MOCK_METHOD1(setColorTransform, Error(const android::mat4&));
     MOCK_METHOD3(setLayerGenericMetadata,
                  Error(const std::string&, bool, const std::vector<uint8_t>&));
+    MOCK_METHOD1(setBrightness, Error(float));
+    MOCK_METHOD1(setBlockingRegion, Error(const android::Region&));
 };
 
 } // namespace mock
diff --git a/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h b/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h
index ada5adf..af013b0 100644
--- a/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h
+++ b/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h
@@ -39,20 +39,24 @@
     HWComposer();
     ~HWComposer() override;
 
-    MOCK_METHOD1(setCallback, void(HWC2::ComposerCallback*));
+    MOCK_METHOD1(setCallback, void(HWC2::ComposerCallback&));
     MOCK_CONST_METHOD3(getDisplayIdentificationData,
                        bool(hal::HWDisplayId, uint8_t*, DisplayIdentificationData*));
-    MOCK_CONST_METHOD1(hasCapability, bool(hal::Capability));
-    MOCK_CONST_METHOD2(hasDisplayCapability, bool(HalDisplayId, hal::DisplayCapability));
+    MOCK_CONST_METHOD1(hasCapability,
+                       bool(aidl::android::hardware::graphics::composer3::Capability));
+    MOCK_CONST_METHOD2(hasDisplayCapability,
+                       bool(HalDisplayId,
+                            aidl::android::hardware::graphics::composer3::DisplayCapability));
 
     MOCK_CONST_METHOD0(getMaxVirtualDisplayCount, size_t());
     MOCK_CONST_METHOD0(getMaxVirtualDisplayDimension, size_t());
     MOCK_METHOD3(allocateVirtualDisplay, bool(HalVirtualDisplayId, ui::Size, ui::PixelFormat*));
     MOCK_METHOD2(allocatePhysicalDisplay, void(hal::HWDisplayId, PhysicalDisplayId));
+
     MOCK_METHOD1(createLayer, std::shared_ptr<HWC2::Layer>(HalDisplayId));
-    MOCK_METHOD5(getDeviceCompositionChanges,
+    MOCK_METHOD6(getDeviceCompositionChanges,
                  status_t(HalDisplayId, bool, std::chrono::steady_clock::time_point,
-                          const std::shared_ptr<FenceTime>&,
+                          const std::shared_ptr<FenceTime>&, nsecs_t,
                           std::optional<android::HWComposer::DeviceRequestedChanges>*));
     MOCK_METHOD5(setClientTarget,
                  status_t(HalDisplayId, uint32_t, const sp<Fence>&, const sp<GraphicBuffer>&,
@@ -80,7 +84,9 @@
     MOCK_METHOD4(setDisplayContentSamplingEnabled, status_t(HalDisplayId, bool, uint8_t, uint64_t));
     MOCK_METHOD4(getDisplayedContentSample,
                  status_t(HalDisplayId, uint64_t, uint64_t, DisplayedFrameStats*));
-    MOCK_METHOD2(setDisplayBrightness, std::future<status_t>(PhysicalDisplayId, float));
+    MOCK_METHOD3(setDisplayBrightness,
+                 std::future<status_t>(PhysicalDisplayId, float,
+                                       const Hwc2::Composer::DisplayBrightnessOptions&));
     MOCK_METHOD2(getDisplayBrightnessSupport, status_t(PhysicalDisplayId, bool*));
 
     MOCK_METHOD2(onHotplug,
@@ -101,20 +107,36 @@
                  status_t(PhysicalDisplayId, hal::HWConfigId,
                           const hal::VsyncPeriodChangeConstraints&,
                           hal::VsyncPeriodChangeTimeline*));
+    MOCK_METHOD2(setBootDisplayMode, status_t(PhysicalDisplayId, hal::HWConfigId));
+    MOCK_METHOD1(clearBootDisplayMode, status_t(PhysicalDisplayId));
+    MOCK_METHOD1(getPreferredBootDisplayMode, std::optional<hal::HWConfigId>(PhysicalDisplayId));
+    MOCK_METHOD0(getBootDisplayModeSupport, bool());
     MOCK_METHOD2(setAutoLowLatencyMode, status_t(PhysicalDisplayId, bool));
-    MOCK_METHOD2(getSupportedContentTypes,
-                 status_t(PhysicalDisplayId, std::vector<hal::ContentType>*));
+    MOCK_METHOD(status_t, getSupportedContentTypes,
+                (PhysicalDisplayId, std::vector<hal::ContentType>*), (const, override));
     MOCK_METHOD2(setContentType, status_t(PhysicalDisplayId, hal::ContentType));
     MOCK_CONST_METHOD0(getSupportedLayerGenericMetadata,
                        const std::unordered_map<std::string, bool>&());
 
     MOCK_CONST_METHOD1(dump, void(std::string&));
     MOCK_CONST_METHOD0(getComposer, android::Hwc2::Composer*());
-    MOCK_CONST_METHOD1(getHwcDisplayId, std::optional<hal::HWDisplayId>(int32_t));
-    MOCK_CONST_METHOD0(getInternalHwcDisplayId, std::optional<hal::HWDisplayId>());
-    MOCK_CONST_METHOD0(getExternalHwcDisplayId, std::optional<hal::HWDisplayId>());
-    MOCK_CONST_METHOD1(toPhysicalDisplayId, std::optional<PhysicalDisplayId>(hal::HWDisplayId));
-    MOCK_CONST_METHOD1(fromPhysicalDisplayId, std::optional<hal::HWDisplayId>(PhysicalDisplayId));
+
+    MOCK_METHOD(hal::HWDisplayId, getPrimaryHwcDisplayId, (), (const, override));
+    MOCK_METHOD(PhysicalDisplayId, getPrimaryDisplayId, (), (const, override));
+    MOCK_METHOD(bool, isHeadless, (), (const, override));
+
+    MOCK_METHOD(std::optional<PhysicalDisplayId>, toPhysicalDisplayId, (hal::HWDisplayId),
+                (const, override));
+    MOCK_METHOD(std::optional<hal::HWDisplayId>, fromPhysicalDisplayId, (PhysicalDisplayId),
+                (const, override));
+    MOCK_METHOD2(getDisplayDecorationSupport,
+                 status_t(PhysicalDisplayId,
+                          std::optional<aidl::android::hardware::graphics::common::
+                                                DisplayDecorationSupport>* support));
+    MOCK_METHOD2(setIdleTimerEnabled, status_t(PhysicalDisplayId, std::chrono::milliseconds));
+    MOCK_METHOD(bool, hasDisplayIdleTimerCapability, (PhysicalDisplayId), (const, override));
+    MOCK_METHOD(Hwc2::AidlTransform, getPhysicalDisplayOrientation, (PhysicalDisplayId),
+                (const, override));
 };
 
 } // namespace mock
diff --git a/services/surfaceflinger/CompositionEngine/tests/MockPowerAdvisor.h b/services/surfaceflinger/CompositionEngine/tests/MockPowerAdvisor.h
index fc8cb50..50adcfb 100644
--- a/services/surfaceflinger/CompositionEngine/tests/MockPowerAdvisor.h
+++ b/services/surfaceflinger/CompositionEngine/tests/MockPowerAdvisor.h
@@ -29,11 +29,20 @@
     PowerAdvisor();
     ~PowerAdvisor() override;
 
-    MOCK_METHOD0(init, void());
-    MOCK_METHOD0(onBootFinished, void());
-    MOCK_METHOD2(setExpensiveRenderingExpected, void(DisplayId displayId, bool expected));
-    MOCK_METHOD0(isUsingExpensiveRendering, bool());
-    MOCK_METHOD0(notifyDisplayUpdateImminent, void());
+    MOCK_METHOD(void, init, (), (override));
+    MOCK_METHOD(void, onBootFinished, (), (override));
+    MOCK_METHOD(void, setExpensiveRenderingExpected, (DisplayId displayId, bool expected),
+                (override));
+    MOCK_METHOD(bool, isUsingExpensiveRendering, (), (override));
+    MOCK_METHOD(void, notifyDisplayUpdateImminent, (), (override));
+    MOCK_METHOD(bool, usePowerHintSession, (), (override));
+    MOCK_METHOD(bool, supportsPowerHintSession, (), (override));
+    MOCK_METHOD(bool, isPowerHintSessionRunning, (), (override));
+    MOCK_METHOD(void, setTargetWorkDuration, (int64_t targetDurationNanos), (override));
+    MOCK_METHOD(void, sendActualWorkDuration, (int64_t actualDurationNanos, nsecs_t timestamp),
+                (override));
+    MOCK_METHOD(void, enablePowerHint, (bool enabled), (override));
+    MOCK_METHOD(bool, startPowerHintSession, (const std::vector<int32_t>& threadIds), (override));
 };
 
 } // namespace mock
diff --git a/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp b/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp
index c8c6012..8eb1946 100644
--- a/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp
@@ -24,12 +24,17 @@
 #include <gtest/gtest.h>
 #include <log/log.h>
 
+#include <renderengine/impl/ExternalTexture.h>
 #include <renderengine/mock/RenderEngine.h>
 #include <ui/PixelFormat.h>
 #include "MockHWC2.h"
 #include "MockHWComposer.h"
 #include "RegionMatcher.h"
 
+#include <aidl/android/hardware/graphics/composer3/Composition.h>
+
+using aidl::android::hardware::graphics::composer3::Composition;
+
 namespace android::compositionengine {
 namespace {
 
@@ -104,8 +109,8 @@
     }
 
     compositionengine::mock::Output mOutput;
-    sp<compositionengine::mock::LayerFE> mLayerFE{
-            new StrictMock<compositionengine::mock::LayerFE>()};
+    sp<StrictMock<compositionengine::mock::LayerFE>> mLayerFE =
+            sp<StrictMock<compositionengine::mock::LayerFE>>::make();
     OutputLayer mOutputLayer{mOutput, mLayerFE};
 
     LayerFECompositionState mLayerFEState;
@@ -158,7 +163,7 @@
         mLayerFEState.geomBufferSize = Rect{0, 0, 1920, 1080};
         mLayerFEState.geomBufferTransform = TR_IDENT;
 
-        mOutputState.layerStackSpace.content = Rect{0, 0, 1920, 1080};
+        mOutputState.layerStackSpace.setContent(Rect{0, 0, 1920, 1080});
     }
 
     FloatRect calculateOutputSourceCrop() {
@@ -229,7 +234,7 @@
 
         mLayerFEState.geomBufferUsesDisplayInverseTransform = entry.bufferInvDisplay;
         mLayerFEState.geomBufferTransform = entry.buffer;
-        mOutputState.displaySpace.orientation = toRotation(entry.display);
+        mOutputState.displaySpace.setOrientation(toRotation(entry.display));
 
         EXPECT_THAT(calculateOutputSourceCrop(), entry.expected) << "entry " << i;
     }
@@ -243,7 +248,7 @@
 }
 
 TEST_F(OutputLayerSourceCropTest, viewportAffectsCrop) {
-    mOutputState.layerStackSpace.content = Rect{0, 0, 960, 540};
+    mOutputState.layerStackSpace.setContent(Rect{0, 0, 960, 540});
 
     const FloatRect expected{0.f, 0.f, 960.f, 540.f};
     EXPECT_THAT(calculateOutputSourceCrop(), expected);
@@ -265,7 +270,7 @@
         mLayerFEState.geomCrop = Rect{0, 0, 1920, 1080};
         mLayerFEState.geomLayerBounds = FloatRect{0.f, 0.f, 1920.f, 1080.f};
 
-        mOutputState.layerStackSpace.content = Rect{0, 0, 1920, 1080};
+        mOutputState.layerStackSpace.setContent(Rect{0, 0, 1920, 1080});
         mOutputState.transform = ui::Transform{TR_IDENT};
     }
 
@@ -313,7 +318,7 @@
 }
 
 TEST_F(OutputLayerDisplayFrameTest, viewportAffectsFrame) {
-    mOutputState.layerStackSpace.content = Rect{0, 0, 960, 540};
+    mOutputState.layerStackSpace.setContent(Rect{0, 0, 960, 540});
     const Rect expected{0, 0, 960, 540};
     EXPECT_THAT(calculateOutputDisplayFrame(), expected);
 }
@@ -399,7 +404,7 @@
 
         mLayerFEState.geomLayerTransform.set(entry.layer, 1920, 1080);
         mLayerFEState.geomBufferTransform = entry.buffer;
-        mOutputState.displaySpace.orientation = toRotation(entry.display);
+        mOutputState.displaySpace.setOrientation(toRotation(entry.display));
         mOutputState.transform = ui::Transform{entry.display};
 
         const auto actual = mOutputLayer.calculateOutputRelativeBufferTransform(entry.display);
@@ -511,7 +516,7 @@
 
         mLayerFEState.geomLayerTransform.set(entry.layer, 1920, 1080);
         mLayerFEState.geomBufferTransform = entry.buffer;
-        mOutputState.displaySpace.orientation = toRotation(entry.display);
+        mOutputState.displaySpace.setOrientation(toRotation(entry.display));
         mOutputState.transform = ui::Transform{entry.display};
 
         const auto actual = mOutputLayer.calculateOutputRelativeBufferTransform(entry.internal);
@@ -652,6 +657,25 @@
     EXPECT_EQ(ui::Dataspace::V0_SCRGB, mOutputLayer.getState().dataspace);
 }
 
+TEST_F(OutputLayerUpdateCompositionStateTest, setsWhitePointNitsAndDimmingRatioCorrectly) {
+    mOutputState.sdrWhitePointNits = 200.f;
+    mOutputState.displayBrightnessNits = 800.f;
+
+    mLayerFEState.dataspace = ui::Dataspace::DISPLAY_P3;
+    mLayerFEState.isColorspaceAgnostic = false;
+    mOutputLayer.updateCompositionState(false, false, ui::Transform::RotationFlags::ROT_0);
+    EXPECT_EQ(mOutputState.sdrWhitePointNits, mOutputLayer.getState().whitePointNits);
+    EXPECT_EQ(mOutputState.sdrWhitePointNits / mOutputState.displayBrightnessNits,
+              mOutputLayer.getState().dimmingRatio);
+
+    mLayerFEState.dataspace = ui::Dataspace::BT2020_ITU_PQ;
+    mLayerFEState.isColorspaceAgnostic = false;
+    mOutputLayer.updateCompositionState(false, false, ui::Transform::RotationFlags::ROT_0);
+
+    EXPECT_EQ(mOutputState.displayBrightnessNits, mOutputLayer.getState().whitePointNits);
+    EXPECT_EQ(1.f, mOutputLayer.getState().dimmingRatio);
+}
+
 TEST_F(OutputLayerUpdateCompositionStateTest, doesNotRecomputeGeometryIfNotRequested) {
     mOutputLayer.editState().forceClientComposition = false;
 
@@ -728,6 +752,11 @@
     static constexpr int kOverrideHwcSlot = impl::HwcBufferCache::FLATTENER_CACHING_SLOT;
     static constexpr bool kLayerGenericMetadata1Mandatory = true;
     static constexpr bool kLayerGenericMetadata2Mandatory = true;
+    static constexpr float kWhitePointNits = 200.f;
+    static constexpr float kSdrWhitePointNits = 100.f;
+    static constexpr float kDisplayBrightnessNits = 400.f;
+    static constexpr float kLayerBrightness = kWhitePointNits / kDisplayBrightnessNits;
+    static constexpr float kOverrideLayerBrightness = kSdrWhitePointNits / kDisplayBrightnessNits;
 
     static const half4 kColor;
     static const Rect kDisplayFrame;
@@ -758,6 +787,8 @@
         outputLayerState.bufferTransform = static_cast<Hwc2::Transform>(kBufferTransform);
         outputLayerState.outputSpaceVisibleRegion = kOutputSpaceVisibleRegion;
         outputLayerState.dataspace = kDataspace;
+        outputLayerState.whitePointNits = kWhitePointNits;
+        outputLayerState.dimmingRatio = kLayerBrightness;
 
         mLayerFEState.blendMode = kBlendMode;
         mLayerFEState.alpha = kAlpha;
@@ -770,6 +801,9 @@
         mLayerFEState.bufferSlot = BufferQueue::INVALID_BUFFER_SLOT;
         mLayerFEState.acquireFence = kFence;
 
+        mOutputState.displayBrightnessNits = kDisplayBrightnessNits;
+        mOutputState.sdrWhitePointNits = kSdrWhitePointNits;
+
         EXPECT_CALL(mOutput, getDisplayColorProfile())
                 .WillRepeatedly(Return(&mDisplayColorProfile));
         EXPECT_CALL(mDisplayColorProfile, getSupportedPerFrameMetadata())
@@ -790,10 +824,11 @@
         auto& overrideInfo = mOutputLayer.editState().overrideInfo;
 
         overrideInfo.buffer = std::make_shared<
-                renderengine::ExternalTexture>(kOverrideBuffer, mRenderEngine,
-                                               renderengine::ExternalTexture::Usage::READABLE |
-                                                       renderengine::ExternalTexture::Usage::
-                                                               WRITEABLE);
+                renderengine::impl::ExternalTexture>(kOverrideBuffer, mRenderEngine,
+                                                     renderengine::impl::ExternalTexture::Usage::
+                                                                     READABLE |
+                                                             renderengine::impl::ExternalTexture::
+                                                                     Usage::WRITEABLE);
         overrideInfo.acquireFence = kOverrideFence;
         overrideInfo.displayFrame = kOverrideDisplayFrame;
         overrideInfo.dataspace = kOverrideDataspace;
@@ -818,17 +853,22 @@
     void expectPerFrameCommonCalls(SimulateUnsupported unsupported = SimulateUnsupported::None,
                                    ui::Dataspace dataspace = kDataspace,
                                    const Region& visibleRegion = kOutputSpaceVisibleRegion,
-                                   const Region& surfaceDamage = kSurfaceDamage) {
+                                   const Region& surfaceDamage = kSurfaceDamage,
+                                   float brightness = kLayerBrightness,
+                                   const Region& blockingRegion = Region()) {
         EXPECT_CALL(*mHwcLayer, setVisibleRegion(RegionEq(visibleRegion))).WillOnce(Return(kError));
         EXPECT_CALL(*mHwcLayer, setDataspace(dataspace)).WillOnce(Return(kError));
+        EXPECT_CALL(*mHwcLayer, setBrightness(brightness)).WillOnce(Return(kError));
         EXPECT_CALL(*mHwcLayer, setColorTransform(kColorTransform))
                 .WillOnce(Return(unsupported == SimulateUnsupported::ColorTransform
                                          ? hal::Error::UNSUPPORTED
                                          : hal::Error::NONE));
         EXPECT_CALL(*mHwcLayer, setSurfaceDamage(RegionEq(surfaceDamage))).WillOnce(Return(kError));
+        EXPECT_CALL(*mHwcLayer, setBlockingRegion(RegionEq(blockingRegion)))
+                .WillOnce(Return(kError));
     }
 
-    void expectSetCompositionTypeCall(Hwc2::IComposerClient::Composition compositionType) {
+    void expectSetCompositionTypeCall(Composition compositionType) {
         EXPECT_CALL(*mHwcLayer, setCompositionType(compositionType)).WillOnce(Return(kError));
     }
 
@@ -837,9 +877,8 @@
     }
 
     void expectSetColorCall() {
-        const hal::Color color = {static_cast<uint8_t>(std::round(kColor.r * 255)),
-                                  static_cast<uint8_t>(std::round(kColor.g * 255)),
-                                  static_cast<uint8_t>(std::round(kColor.b * 255)), 255};
+        const aidl::android::hardware::graphics::composer3::Color color = {kColor.r, kColor.g,
+                                                                           kColor.b, 1.0f};
 
         EXPECT_CALL(*mHwcLayer, setColor(ColorEq(color))).WillOnce(Return(kError));
     }
@@ -876,7 +915,7 @@
                                                    84.f / 255.f};
 const Rect OutputLayerWriteStateToHWCTest::kDisplayFrame{1001, 1002, 1003, 10044};
 const Rect OutputLayerWriteStateToHWCTest::kOverrideDisplayFrame{1002, 1003, 1004, 20044};
-const FloatRect OutputLayerWriteStateToHWCTest::kOverrideSourceCrop{0.f, 0.f, 4.f, 5.f};
+const FloatRect OutputLayerWriteStateToHWCTest::kOverrideSourceCrop{1002, 1003, 1004, 20044};
 const Region OutputLayerWriteStateToHWCTest::kOutputSpaceVisibleRegion{
         Rect{1005, 1006, 1007, 1008}};
 const Region OutputLayerWriteStateToHWCTest::kOverrideVisibleRegion{Rect{1006, 1007, 1008, 1009}};
@@ -942,7 +981,7 @@
     // This test simulates a scenario where displayInstallOrientation is set to
     // ROT_90. This only has an effect on the transform; orientation stays 0 (see
     // DisplayDevice::setProjection).
-    mOutputState.displaySpace.orientation = ui::ROTATION_0;
+    mOutputState.displaySpace.setOrientation(ui::ROTATION_0);
     mOutputState.transform = ui::Transform{TR_ROT_90};
     // Buffers are pre-rotated based on the transform hint (ROT_90); their
     // geomBufferTransform is set to the inverse transform.
@@ -952,7 +991,7 @@
 }
 
 TEST_F(OutputLayerWriteStateToHWCTest, canSetPerFrameStateForSolidColor) {
-    mLayerFEState.compositionType = Hwc2::IComposerClient::Composition::SOLID_COLOR;
+    mLayerFEState.compositionType = Composition::SOLID_COLOR;
 
     expectPerFrameCommonCalls();
     EXPECT_CALL(*mLayerFE, hasRoundedCorners()).WillOnce(Return(false));
@@ -961,7 +1000,7 @@
     // check this in this test only by setting up an testing::InSeqeuence
     // instance before setting up the two expectations.
     InSequence s;
-    expectSetCompositionTypeCall(Hwc2::IComposerClient::Composition::SOLID_COLOR);
+    expectSetCompositionTypeCall(Composition::SOLID_COLOR);
     expectSetColorCall();
 
     mOutputLayer.writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false, 0,
@@ -969,11 +1008,11 @@
 }
 
 TEST_F(OutputLayerWriteStateToHWCTest, canSetPerFrameStateForSideband) {
-    mLayerFEState.compositionType = Hwc2::IComposerClient::Composition::SIDEBAND;
+    mLayerFEState.compositionType = Composition::SIDEBAND;
 
     expectPerFrameCommonCalls();
     expectSetSidebandHandleCall();
-    expectSetCompositionTypeCall(Hwc2::IComposerClient::Composition::SIDEBAND);
+    expectSetCompositionTypeCall(Composition::SIDEBAND);
 
     EXPECT_CALL(*mLayerFE, hasRoundedCorners()).WillOnce(Return(false));
 
@@ -982,11 +1021,11 @@
 }
 
 TEST_F(OutputLayerWriteStateToHWCTest, canSetPerFrameStateForCursor) {
-    mLayerFEState.compositionType = Hwc2::IComposerClient::Composition::CURSOR;
+    mLayerFEState.compositionType = Composition::CURSOR;
 
     expectPerFrameCommonCalls();
     expectSetHdrMetadataAndBufferCalls();
-    expectSetCompositionTypeCall(Hwc2::IComposerClient::Composition::CURSOR);
+    expectSetCompositionTypeCall(Composition::CURSOR);
 
     EXPECT_CALL(*mLayerFE, hasRoundedCorners()).WillOnce(Return(false));
 
@@ -995,11 +1034,11 @@
 }
 
 TEST_F(OutputLayerWriteStateToHWCTest, canSetPerFrameStateForDevice) {
-    mLayerFEState.compositionType = Hwc2::IComposerClient::Composition::DEVICE;
+    mLayerFEState.compositionType = Composition::DEVICE;
 
     expectPerFrameCommonCalls();
     expectSetHdrMetadataAndBufferCalls();
-    expectSetCompositionTypeCall(Hwc2::IComposerClient::Composition::DEVICE);
+    expectSetCompositionTypeCall(Composition::DEVICE);
 
     EXPECT_CALL(*mLayerFE, hasRoundedCorners()).WillOnce(Return(false));
 
@@ -1008,10 +1047,9 @@
 }
 
 TEST_F(OutputLayerWriteStateToHWCTest, compositionTypeIsNotSetIfUnchanged) {
-    (*mOutputLayer.editState().hwc).hwcCompositionType =
-            Hwc2::IComposerClient::Composition::SOLID_COLOR;
+    (*mOutputLayer.editState().hwc).hwcCompositionType = Composition::SOLID_COLOR;
 
-    mLayerFEState.compositionType = Hwc2::IComposerClient::Composition::SOLID_COLOR;
+    mLayerFEState.compositionType = Composition::SOLID_COLOR;
 
     expectPerFrameCommonCalls();
     expectSetColorCall();
@@ -1024,11 +1062,11 @@
 }
 
 TEST_F(OutputLayerWriteStateToHWCTest, compositionTypeIsSetToClientIfColorTransformNotSupported) {
-    mLayerFEState.compositionType = Hwc2::IComposerClient::Composition::SOLID_COLOR;
+    mLayerFEState.compositionType = Composition::SOLID_COLOR;
 
     expectPerFrameCommonCalls(SimulateUnsupported::ColorTransform);
     expectSetColorCall();
-    expectSetCompositionTypeCall(Hwc2::IComposerClient::Composition::CLIENT);
+    expectSetCompositionTypeCall(Composition::CLIENT);
 
     mOutputLayer.writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false, 0,
                                  /*zIsOverridden*/ false, /*isPeekingThrough*/ false);
@@ -1037,25 +1075,25 @@
 TEST_F(OutputLayerWriteStateToHWCTest, compositionTypeIsSetToClientIfClientCompositionForced) {
     mOutputLayer.editState().forceClientComposition = true;
 
-    mLayerFEState.compositionType = Hwc2::IComposerClient::Composition::SOLID_COLOR;
+    mLayerFEState.compositionType = Composition::SOLID_COLOR;
 
     expectPerFrameCommonCalls();
     expectSetColorCall();
-    expectSetCompositionTypeCall(Hwc2::IComposerClient::Composition::CLIENT);
+    expectSetCompositionTypeCall(Composition::CLIENT);
 
     mOutputLayer.writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false, 0,
                                  /*zIsOverridden*/ false, /*isPeekingThrough*/ false);
 }
 
 TEST_F(OutputLayerWriteStateToHWCTest, allStateIncludesMetadataIfPresent) {
-    mLayerFEState.compositionType = Hwc2::IComposerClient::Composition::DEVICE;
+    mLayerFEState.compositionType = Composition::DEVICE;
     includeGenericLayerMetadataInState();
 
     expectGeometryCommonCalls();
     expectPerFrameCommonCalls();
     expectSetHdrMetadataAndBufferCalls();
     expectGenericLayerMetadataCalls();
-    expectSetCompositionTypeCall(Hwc2::IComposerClient::Composition::DEVICE);
+    expectSetCompositionTypeCall(Composition::DEVICE);
 
     EXPECT_CALL(*mLayerFE, hasRoundedCorners()).WillOnce(Return(false));
 
@@ -1064,12 +1102,12 @@
 }
 
 TEST_F(OutputLayerWriteStateToHWCTest, perFrameStateDoesNotIncludeMetadataIfPresent) {
-    mLayerFEState.compositionType = Hwc2::IComposerClient::Composition::DEVICE;
+    mLayerFEState.compositionType = Composition::DEVICE;
     includeGenericLayerMetadataInState();
 
     expectPerFrameCommonCalls();
     expectSetHdrMetadataAndBufferCalls();
-    expectSetCompositionTypeCall(Hwc2::IComposerClient::Composition::DEVICE);
+    expectSetCompositionTypeCall(Composition::DEVICE);
 
     EXPECT_CALL(*mLayerFE, hasRoundedCorners()).WillOnce(Return(false));
 
@@ -1078,15 +1116,31 @@
 }
 
 TEST_F(OutputLayerWriteStateToHWCTest, overriddenSkipLayerDoesNotSendBuffer) {
-    mLayerFEState.compositionType = Hwc2::IComposerClient::Composition::DEVICE;
+    mLayerFEState.compositionType = Composition::DEVICE;
     includeOverrideInfo();
 
     expectGeometryCommonCalls(kOverrideDisplayFrame, kOverrideSourceCrop, kOverrideBufferTransform,
                               kOverrideBlendMode, kSkipAlpha);
     expectPerFrameCommonCalls(SimulateUnsupported::None, kOverrideDataspace, kOverrideVisibleRegion,
-                              kOverrideSurfaceDamage);
+                              kOverrideSurfaceDamage, kOverrideLayerBrightness);
     expectSetHdrMetadataAndBufferCalls();
-    expectSetCompositionTypeCall(Hwc2::IComposerClient::Composition::DEVICE);
+    expectSetCompositionTypeCall(Composition::DEVICE);
+    EXPECT_CALL(*mLayerFE, hasRoundedCorners()).WillRepeatedly(Return(false));
+
+    mOutputLayer.writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ true, 0,
+                                 /*zIsOverridden*/ false, /*isPeekingThrough*/ false);
+}
+
+TEST_F(OutputLayerWriteStateToHWCTest, overriddenSkipLayerForSolidColorDoesNotSendBuffer) {
+    mLayerFEState.compositionType = Composition::SOLID_COLOR;
+    includeOverrideInfo();
+
+    expectGeometryCommonCalls(kOverrideDisplayFrame, kOverrideSourceCrop, kOverrideBufferTransform,
+                              kOverrideBlendMode, kSkipAlpha);
+    expectPerFrameCommonCalls(SimulateUnsupported::None, kOverrideDataspace, kOverrideVisibleRegion,
+                              kOverrideSurfaceDamage, kOverrideLayerBrightness);
+    expectSetHdrMetadataAndBufferCalls();
+    expectSetCompositionTypeCall(Composition::DEVICE);
     EXPECT_CALL(*mLayerFE, hasRoundedCorners()).WillRepeatedly(Return(false));
 
     mOutputLayer.writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ true, 0,
@@ -1094,15 +1148,31 @@
 }
 
 TEST_F(OutputLayerWriteStateToHWCTest, includesOverrideInfoIfPresent) {
-    mLayerFEState.compositionType = Hwc2::IComposerClient::Composition::DEVICE;
+    mLayerFEState.compositionType = Composition::DEVICE;
     includeOverrideInfo();
 
     expectGeometryCommonCalls(kOverrideDisplayFrame, kOverrideSourceCrop, kOverrideBufferTransform,
                               kOverrideBlendMode, kOverrideAlpha);
     expectPerFrameCommonCalls(SimulateUnsupported::None, kOverrideDataspace, kOverrideVisibleRegion,
-                              kOverrideSurfaceDamage);
+                              kOverrideSurfaceDamage, kOverrideLayerBrightness);
     expectSetHdrMetadataAndBufferCalls(kOverrideHwcSlot, kOverrideBuffer, kOverrideFence);
-    expectSetCompositionTypeCall(Hwc2::IComposerClient::Composition::DEVICE);
+    expectSetCompositionTypeCall(Composition::DEVICE);
+    EXPECT_CALL(*mLayerFE, hasRoundedCorners()).WillRepeatedly(Return(false));
+
+    mOutputLayer.writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false, 0,
+                                 /*zIsOverridden*/ false, /*isPeekingThrough*/ false);
+}
+
+TEST_F(OutputLayerWriteStateToHWCTest, includesOverrideInfoForSolidColorIfPresent) {
+    mLayerFEState.compositionType = Composition::SOLID_COLOR;
+    includeOverrideInfo();
+
+    expectGeometryCommonCalls(kOverrideDisplayFrame, kOverrideSourceCrop, kOverrideBufferTransform,
+                              kOverrideBlendMode, kOverrideAlpha);
+    expectPerFrameCommonCalls(SimulateUnsupported::None, kOverrideDataspace, kOverrideVisibleRegion,
+                              kOverrideSurfaceDamage, kOverrideLayerBrightness);
+    expectSetHdrMetadataAndBufferCalls(kOverrideHwcSlot, kOverrideBuffer, kOverrideFence);
+    expectSetCompositionTypeCall(Composition::DEVICE);
     EXPECT_CALL(*mLayerFE, hasRoundedCorners()).WillRepeatedly(Return(false));
 
     mOutputLayer.writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false, 0,
@@ -1110,14 +1180,14 @@
 }
 
 TEST_F(OutputLayerWriteStateToHWCTest, previousOverriddenLayerSendsSurfaceDamage) {
-    mLayerFEState.compositionType = Hwc2::IComposerClient::Composition::DEVICE;
+    mLayerFEState.compositionType = Composition::DEVICE;
     mOutputLayer.editState().hwc->stateOverridden = true;
 
     expectGeometryCommonCalls();
     expectPerFrameCommonCalls(SimulateUnsupported::None, kDataspace, kOutputSpaceVisibleRegion,
                               Region::INVALID_REGION);
     expectSetHdrMetadataAndBufferCalls();
-    expectSetCompositionTypeCall(Hwc2::IComposerClient::Composition::DEVICE);
+    expectSetCompositionTypeCall(Composition::DEVICE);
     EXPECT_CALL(*mLayerFE, hasRoundedCorners()).WillRepeatedly(Return(false));
 
     mOutputLayer.writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false, 0,
@@ -1125,16 +1195,16 @@
 }
 
 TEST_F(OutputLayerWriteStateToHWCTest, previousSkipLayerSendsUpdatedDeviceCompositionInfo) {
-    mLayerFEState.compositionType = Hwc2::IComposerClient::Composition::DEVICE;
+    mLayerFEState.compositionType = Composition::DEVICE;
     mOutputLayer.editState().hwc->stateOverridden = true;
     mOutputLayer.editState().hwc->layerSkipped = true;
-    mOutputLayer.editState().hwc->hwcCompositionType = Hwc2::IComposerClient::Composition::DEVICE;
+    mOutputLayer.editState().hwc->hwcCompositionType = Composition::DEVICE;
 
     expectGeometryCommonCalls();
     expectPerFrameCommonCalls(SimulateUnsupported::None, kDataspace, kOutputSpaceVisibleRegion,
                               Region::INVALID_REGION);
     expectSetHdrMetadataAndBufferCalls();
-    expectSetCompositionTypeCall(Hwc2::IComposerClient::Composition::DEVICE);
+    expectSetCompositionTypeCall(Composition::DEVICE);
     EXPECT_CALL(*mLayerFE, hasRoundedCorners()).WillOnce(Return(false));
 
     mOutputLayer.writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false, 0,
@@ -1142,17 +1212,17 @@
 }
 
 TEST_F(OutputLayerWriteStateToHWCTest, previousSkipLayerSendsUpdatedClientCompositionInfo) {
-    mLayerFEState.compositionType = Hwc2::IComposerClient::Composition::DEVICE;
+    mLayerFEState.compositionType = Composition::DEVICE;
     mOutputLayer.editState().forceClientComposition = true;
     mOutputLayer.editState().hwc->stateOverridden = true;
     mOutputLayer.editState().hwc->layerSkipped = true;
-    mOutputLayer.editState().hwc->hwcCompositionType = Hwc2::IComposerClient::Composition::CLIENT;
+    mOutputLayer.editState().hwc->hwcCompositionType = Composition::CLIENT;
 
     expectGeometryCommonCalls();
     expectPerFrameCommonCalls(SimulateUnsupported::None, kDataspace, kOutputSpaceVisibleRegion,
                               Region::INVALID_REGION);
     expectSetHdrMetadataAndBufferCalls();
-    expectSetCompositionTypeCall(Hwc2::IComposerClient::Composition::CLIENT);
+    expectSetCompositionTypeCall(Composition::CLIENT);
     EXPECT_CALL(*mLayerFE, hasRoundedCorners()).WillRepeatedly(Return(false));
 
     mOutputLayer.writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false, 0,
@@ -1198,7 +1268,7 @@
     expectGeometryCommonCalls();
     expectPerFrameCommonCalls();
     EXPECT_CALL(*mLayerFE, hasRoundedCorners()).WillOnce(Return(true));
-    expectSetCompositionTypeCall(Hwc2::IComposerClient::Composition::CLIENT);
+    expectSetCompositionTypeCall(Composition::CLIENT);
 
     mOutputLayer.writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false, 0,
                                  /*zIsOverridden*/ false, /*isPeekingThrough*/
@@ -1210,14 +1280,30 @@
     expectPerFrameCommonCalls();
     expectSetHdrMetadataAndBufferCalls();
     EXPECT_CALL(*mLayerFE, hasRoundedCorners()).WillRepeatedly(Return(true));
-    expectSetCompositionTypeCall(Hwc2::IComposerClient::Composition::DEVICE);
+    expectSetCompositionTypeCall(Composition::DEVICE);
 
-    mLayerFEState.compositionType = Hwc2::IComposerClient::Composition::DEVICE;
+    mLayerFEState.compositionType = Composition::DEVICE;
     mOutputLayer.writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false, 0,
                                  /*zIsOverridden*/ false, /*isPeekingThrough*/
                                  true);
-    EXPECT_EQ(Hwc2::IComposerClient::Composition::DEVICE,
-              mOutputLayer.getState().hwc->hwcCompositionType);
+    EXPECT_EQ(Composition::DEVICE, mOutputLayer.getState().hwc->hwcCompositionType);
+}
+
+TEST_F(OutputLayerWriteStateToHWCTest, setBlockingRegion) {
+    mLayerFEState.compositionType = Composition::DISPLAY_DECORATION;
+    const auto blockingRegion = Region(Rect(0, 0, 1000, 1000));
+    mOutputLayer.editState().outputSpaceBlockingRegionHint = blockingRegion;
+
+    expectGeometryCommonCalls();
+    expectPerFrameCommonCalls(SimulateUnsupported::None, kDataspace, kOutputSpaceVisibleRegion,
+                              kSurfaceDamage, kLayerBrightness, blockingRegion);
+    expectSetHdrMetadataAndBufferCalls();
+    EXPECT_CALL(*mLayerFE, hasRoundedCorners()).WillRepeatedly(Return(false));
+    expectSetCompositionTypeCall(Composition::DISPLAY_DECORATION);
+
+    mOutputLayer.writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false, 0,
+                                 /*zIsOverridden*/ false, /*isPeekingThrough*/
+                                 false);
 }
 
 /*
@@ -1237,7 +1323,7 @@
 
         mLayerFEState.cursorFrame = kDefaultCursorFrame;
 
-        mOutputState.layerStackSpace.content = kDefaultDisplayViewport;
+        mOutputState.layerStackSpace.setContent(kDefaultDisplayViewport);
         mOutputState.transform = ui::Transform{kDefaultTransform};
     }
 
@@ -1316,14 +1402,14 @@
 
 TEST_F(OutputLayerTest, requiresClientCompositionReturnsTrueIfSetToClientComposition) {
     mOutputLayer.editState().hwc = impl::OutputLayerCompositionState::Hwc{nullptr};
-    mOutputLayer.editState().hwc->hwcCompositionType = Hwc2::IComposerClient::Composition::CLIENT;
+    mOutputLayer.editState().hwc->hwcCompositionType = Composition::CLIENT;
 
     EXPECT_TRUE(mOutputLayer.requiresClientComposition());
 }
 
 TEST_F(OutputLayerTest, requiresClientCompositionReturnsFalseIfSetToDeviceComposition) {
     mOutputLayer.editState().hwc = impl::OutputLayerCompositionState::Hwc{nullptr};
-    mOutputLayer.editState().hwc->hwcCompositionType = Hwc2::IComposerClient::Composition::DEVICE;
+    mOutputLayer.editState().hwc->hwcCompositionType = Composition::DEVICE;
 
     EXPECT_FALSE(mOutputLayer.requiresClientComposition());
 }
@@ -1340,14 +1426,14 @@
 
 TEST_F(OutputLayerTest, isHardwareCursorReturnsTrueIfSetToCursorComposition) {
     mOutputLayer.editState().hwc = impl::OutputLayerCompositionState::Hwc{nullptr};
-    mOutputLayer.editState().hwc->hwcCompositionType = Hwc2::IComposerClient::Composition::CURSOR;
+    mOutputLayer.editState().hwc->hwcCompositionType = Composition::CURSOR;
 
     EXPECT_TRUE(mOutputLayer.isHardwareCursor());
 }
 
 TEST_F(OutputLayerTest, isHardwareCursorReturnsFalseIfSetToDeviceComposition) {
     mOutputLayer.editState().hwc = impl::OutputLayerCompositionState::Hwc{nullptr};
-    mOutputLayer.editState().hwc->hwcCompositionType = Hwc2::IComposerClient::Composition::DEVICE;
+    mOutputLayer.editState().hwc->hwcCompositionType = Composition::DEVICE;
 
     EXPECT_FALSE(mOutputLayer.isHardwareCursor());
 }
@@ -1358,13 +1444,12 @@
 
 TEST_F(OutputLayerTest, applyDeviceCompositionTypeChangeSetsNewType) {
     mOutputLayer.editState().hwc = impl::OutputLayerCompositionState::Hwc{nullptr};
-    mOutputLayer.editState().hwc->hwcCompositionType = Hwc2::IComposerClient::Composition::DEVICE;
+    mOutputLayer.editState().hwc->hwcCompositionType = Composition::DEVICE;
 
-    mOutputLayer.applyDeviceCompositionTypeChange(Hwc2::IComposerClient::Composition::CLIENT);
+    mOutputLayer.applyDeviceCompositionTypeChange(Composition::CLIENT);
 
     ASSERT_TRUE(mOutputLayer.getState().hwc);
-    EXPECT_EQ(Hwc2::IComposerClient::Composition::CLIENT,
-              mOutputLayer.getState().hwc->hwcCompositionType);
+    EXPECT_EQ(Composition::CLIENT, mOutputLayer.getState().hwc->hwcCompositionType);
 }
 
 /*
diff --git a/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
index 09f5a5e..dd3858b 100644
--- a/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
@@ -24,7 +24,10 @@
 #include <compositionengine/mock/LayerFE.h>
 #include <compositionengine/mock/OutputLayer.h>
 #include <compositionengine/mock/RenderSurface.h>
+#include <ftl/future.h>
 #include <gtest/gtest.h>
+#include <renderengine/ExternalTexture.h>
+#include <renderengine/impl/ExternalTexture.h>
 #include <renderengine/mock/RenderEngine.h>
 #include <ui/Rect.h>
 #include <ui/Region.h>
@@ -35,7 +38,7 @@
 #include "CallOrderStateMachineHelper.h"
 #include "MockHWC2.h"
 #include "RegionMatcher.h"
-#include "renderengine/ExternalTexture.h"
+#include "TestUtils.h"
 
 namespace android::compositionengine {
 namespace {
@@ -103,7 +106,7 @@
     }
 
     mock::OutputLayer* outputLayer = {new StrictMock<mock::OutputLayer>};
-    sp<StrictMock<mock::LayerFE>> layerFE = new StrictMock<mock::LayerFE>();
+    sp<StrictMock<mock::LayerFE>> layerFE = sp<StrictMock<mock::LayerFE>>::make();
     LayerFECompositionState layerFEState;
     impl::OutputLayerCompositionState outputLayerState;
 };
@@ -120,7 +123,7 @@
     }
 
     mock::OutputLayer outputLayer;
-    sp<StrictMock<mock::LayerFE>> layerFE = new StrictMock<mock::LayerFE>();
+    sp<StrictMock<mock::LayerFE>> layerFE = sp<StrictMock<mock::LayerFE>>::make();
     LayerFECompositionState layerFEState;
     impl::OutputLayerCompositionState outputLayerState;
 };
@@ -142,7 +145,8 @@
                 std::unique_ptr<DisplayColorProfile>(mDisplayColorProfile));
         mOutput->setRenderSurfaceForTest(std::unique_ptr<RenderSurface>(mRenderSurface));
 
-        mOutput->editState().displaySpace.bounds = kDefaultDisplaySize;
+        mOutput->editState().displaySpace.setBounds(
+                ui::Size(kDefaultDisplaySize.getWidth(), kDefaultDisplaySize.getHeight()));
         EXPECT_CALL(mCompositionEngine, getRenderEngine()).WillRepeatedly(ReturnRef(mRenderEngine));
     }
 
@@ -270,9 +274,10 @@
     // Inject some layers
     InjectedLayer layer;
     layer.outputLayerState.overrideInfo.buffer = std::make_shared<
-            renderengine::ExternalTexture>(new GraphicBuffer(), renderEngine,
-                                           renderengine::ExternalTexture::Usage::READABLE |
-                                                   renderengine::ExternalTexture::Usage::WRITEABLE);
+            renderengine::impl::
+                    ExternalTexture>(new GraphicBuffer(), renderEngine,
+                                     renderengine::impl::ExternalTexture::Usage::READABLE |
+                                             renderengine::impl::ExternalTexture::Usage::WRITEABLE);
     injectOutputLayer(layer);
     // inject a null layer to check for null exceptions
     injectNullOutputLayer();
@@ -288,8 +293,10 @@
 
 TEST_F(OutputTest, setProjectionWorks) {
     const Rect displayRect{0, 0, 1000, 2000};
-    mOutput->editState().displaySpace.bounds = displayRect;
-    mOutput->editState().framebufferSpace.bounds = displayRect;
+    mOutput->editState().displaySpace.setBounds(
+            ui::Size(displayRect.getWidth(), displayRect.getHeight()));
+    mOutput->editState().framebufferSpace.setBounds(
+            ui::Size(displayRect.getWidth(), displayRect.getHeight()));
 
     const ui::Rotation orientation = ui::ROTATION_90;
     const Rect frame{50, 60, 100, 100};
@@ -297,28 +304,29 @@
 
     mOutput->setProjection(orientation, viewport, frame);
 
-    EXPECT_EQ(orientation, mOutput->getState().displaySpace.orientation);
-    EXPECT_EQ(frame, mOutput->getState().orientedDisplaySpace.content);
-    EXPECT_EQ(viewport, mOutput->getState().layerStackSpace.content);
+    EXPECT_EQ(orientation, mOutput->getState().displaySpace.getOrientation());
+    EXPECT_EQ(frame, mOutput->getState().orientedDisplaySpace.getContent());
+    EXPECT_EQ(viewport, mOutput->getState().layerStackSpace.getContent());
 
     const auto state = mOutput->getState();
-    EXPECT_EQ(ui::ROTATION_0, state.layerStackSpace.orientation);
-    EXPECT_EQ(viewport, state.layerStackSpace.content);
-    EXPECT_EQ(viewport, state.layerStackSpace.bounds);
+    EXPECT_EQ(ui::ROTATION_0, state.layerStackSpace.getOrientation());
+    EXPECT_EQ(viewport, state.layerStackSpace.getContent());
+    EXPECT_EQ(Rect(0, 0, 20, 20), state.layerStackSpace.getBoundsAsRect());
 
-    EXPECT_EQ(ui::ROTATION_0, state.orientedDisplaySpace.orientation);
-    EXPECT_EQ(frame, state.orientedDisplaySpace.content);
-    EXPECT_EQ(Rect(0, 0, 2000, 1000), state.orientedDisplaySpace.bounds);
+    EXPECT_EQ(ui::ROTATION_0, state.orientedDisplaySpace.getOrientation());
+    EXPECT_EQ(frame, state.orientedDisplaySpace.getContent());
+    EXPECT_EQ(Rect(0, 0, 2000, 1000), state.orientedDisplaySpace.getBoundsAsRect());
 
-    EXPECT_EQ(displayRect, state.displaySpace.bounds);
-    EXPECT_EQ(Rect(900, 50, 940, 100), state.displaySpace.content);
-    EXPECT_EQ(orientation, state.displaySpace.orientation);
+    EXPECT_EQ(displayRect, state.displaySpace.getBoundsAsRect());
+    EXPECT_EQ(Rect(900, 50, 940, 100), state.displaySpace.getContent());
+    EXPECT_EQ(orientation, state.displaySpace.getOrientation());
 
-    EXPECT_EQ(displayRect, state.framebufferSpace.bounds);
-    EXPECT_EQ(Rect(900, 50, 940, 100), state.framebufferSpace.content);
-    EXPECT_EQ(orientation, state.framebufferSpace.orientation);
+    EXPECT_EQ(displayRect, state.framebufferSpace.getBoundsAsRect());
+    EXPECT_EQ(Rect(900, 50, 940, 100), state.framebufferSpace.getContent());
+    EXPECT_EQ(orientation, state.framebufferSpace.getOrientation());
 
-    EXPECT_EQ(state.displaySpace.content, state.transform.transform(state.layerStackSpace.content));
+    EXPECT_EQ(state.displaySpace.getContent(),
+              state.transform.transform(state.layerStackSpace.getContent()));
 
     EXPECT_EQ(ui::Transform::ROT_90, mOutput->getTransformHint());
 }
@@ -326,8 +334,10 @@
 TEST_F(OutputTest, setProjectionWithSmallFramebufferWorks) {
     const Rect displayRect{0, 0, 1000, 2000};
     const Rect framebufferRect{0, 0, 500, 1000};
-    mOutput->editState().displaySpace.bounds = displayRect;
-    mOutput->editState().framebufferSpace.bounds = framebufferRect;
+    mOutput->editState().displaySpace.setBounds(
+            ui::Size(displayRect.getWidth(), displayRect.getHeight()));
+    mOutput->editState().framebufferSpace.setBounds(
+            ui::Size(framebufferRect.getWidth(), framebufferRect.getHeight()));
 
     const ui::Rotation orientation = ui::ROTATION_90;
     const Rect frame{50, 60, 100, 100};
@@ -335,28 +345,29 @@
 
     mOutput->setProjection(orientation, viewport, frame);
 
-    EXPECT_EQ(orientation, mOutput->getState().displaySpace.orientation);
-    EXPECT_EQ(frame, mOutput->getState().orientedDisplaySpace.content);
-    EXPECT_EQ(viewport, mOutput->getState().layerStackSpace.content);
+    EXPECT_EQ(orientation, mOutput->getState().displaySpace.getOrientation());
+    EXPECT_EQ(frame, mOutput->getState().orientedDisplaySpace.getContent());
+    EXPECT_EQ(viewport, mOutput->getState().layerStackSpace.getContent());
 
     const auto state = mOutput->getState();
-    EXPECT_EQ(ui::ROTATION_0, state.layerStackSpace.orientation);
-    EXPECT_EQ(viewport, state.layerStackSpace.content);
-    EXPECT_EQ(viewport, state.layerStackSpace.bounds);
+    EXPECT_EQ(ui::ROTATION_0, state.layerStackSpace.getOrientation());
+    EXPECT_EQ(viewport, state.layerStackSpace.getContent());
+    EXPECT_EQ(Rect(0, 0, 20, 20), state.layerStackSpace.getBoundsAsRect());
 
-    EXPECT_EQ(ui::ROTATION_0, state.orientedDisplaySpace.orientation);
-    EXPECT_EQ(frame, state.orientedDisplaySpace.content);
-    EXPECT_EQ(Rect(0, 0, 2000, 1000), state.orientedDisplaySpace.bounds);
+    EXPECT_EQ(ui::ROTATION_0, state.orientedDisplaySpace.getOrientation());
+    EXPECT_EQ(frame, state.orientedDisplaySpace.getContent());
+    EXPECT_EQ(Rect(0, 0, 2000, 1000), state.orientedDisplaySpace.getBoundsAsRect());
 
-    EXPECT_EQ(displayRect, state.displaySpace.bounds);
-    EXPECT_EQ(Rect(900, 50, 940, 100), state.displaySpace.content);
-    EXPECT_EQ(orientation, state.displaySpace.orientation);
+    EXPECT_EQ(displayRect, state.displaySpace.getBoundsAsRect());
+    EXPECT_EQ(Rect(900, 50, 940, 100), state.displaySpace.getContent());
+    EXPECT_EQ(orientation, state.displaySpace.getOrientation());
 
-    EXPECT_EQ(framebufferRect, state.framebufferSpace.bounds);
-    EXPECT_EQ(Rect(450, 25, 470, 50), state.framebufferSpace.content);
-    EXPECT_EQ(orientation, state.framebufferSpace.orientation);
+    EXPECT_EQ(framebufferRect, state.framebufferSpace.getBoundsAsRect());
+    EXPECT_EQ(Rect(450, 25, 470, 50), state.framebufferSpace.getContent());
+    EXPECT_EQ(orientation, state.framebufferSpace.getOrientation());
 
-    EXPECT_EQ(state.displaySpace.content, state.transform.transform(state.layerStackSpace.content));
+    EXPECT_EQ(state.displaySpace.getContent(),
+              state.transform.transform(state.layerStackSpace.getContent()));
 }
 
 /*
@@ -364,16 +375,16 @@
  */
 
 TEST_F(OutputTest, setDisplaySpaceSizeUpdatesOutputStateAndDirtiesEntireOutput) {
-    mOutput->editState().layerStackSpace.content = Rect(0, 0, 2000, 1000);
-    mOutput->editState().layerStackSpace.bounds = Rect(0, 0, 2000, 1000);
-    mOutput->editState().orientedDisplaySpace.content = Rect(0, 0, 1800, 900);
-    mOutput->editState().orientedDisplaySpace.bounds = Rect(0, 0, 2000, 1000);
-    mOutput->editState().framebufferSpace.content = Rect(0, 0, 900, 1800);
-    mOutput->editState().framebufferSpace.bounds = Rect(0, 0, 1000, 2000);
-    mOutput->editState().framebufferSpace.orientation = ui::ROTATION_90;
-    mOutput->editState().displaySpace.content = Rect(0, 0, 900, 1800);
-    mOutput->editState().displaySpace.bounds = Rect(0, 0, 1000, 2000);
-    mOutput->editState().displaySpace.orientation = ui::ROTATION_90;
+    mOutput->editState().layerStackSpace.setContent(Rect(0, 0, 2000, 1000));
+    mOutput->editState().layerStackSpace.setBounds(ui::Size(2000, 1000));
+    mOutput->editState().orientedDisplaySpace.setContent(Rect(0, 0, 1800, 900));
+    mOutput->editState().orientedDisplaySpace.setBounds(ui::Size(2000, 1000));
+    mOutput->editState().framebufferSpace.setContent(Rect(0, 0, 900, 1800));
+    mOutput->editState().framebufferSpace.setBounds(ui::Size(1000, 2000));
+    mOutput->editState().framebufferSpace.setOrientation(ui::ROTATION_90);
+    mOutput->editState().displaySpace.setContent(Rect(0, 0, 900, 1800));
+    mOutput->editState().displaySpace.setBounds(ui::Size(1000, 2000));
+    mOutput->editState().displaySpace.setOrientation(ui::ROTATION_90);
 
     const ui::Size newDisplaySize{500, 1000};
 
@@ -384,36 +395,38 @@
     const auto state = mOutput->getState();
 
     const Rect displayRect(newDisplaySize);
-    EXPECT_EQ(ui::ROTATION_0, state.layerStackSpace.orientation);
-    EXPECT_EQ(Rect(0, 0, 2000, 1000), state.layerStackSpace.content);
-    EXPECT_EQ(Rect(0, 0, 2000, 1000), state.layerStackSpace.bounds);
+    EXPECT_EQ(ui::ROTATION_0, state.layerStackSpace.getOrientation());
+    EXPECT_EQ(Rect(0, 0, 2000, 1000), state.layerStackSpace.getContent());
+    EXPECT_EQ(Rect(0, 0, 2000, 1000), state.layerStackSpace.getBoundsAsRect());
 
-    EXPECT_EQ(ui::ROTATION_0, state.orientedDisplaySpace.orientation);
-    EXPECT_EQ(Rect(0, 0, 1000, 500), state.orientedDisplaySpace.bounds);
+    EXPECT_EQ(ui::ROTATION_0, state.orientedDisplaySpace.getOrientation());
+    EXPECT_EQ(Rect(0, 0, 1000, 500), state.orientedDisplaySpace.getBoundsAsRect());
 
-    EXPECT_EQ(displayRect, state.displaySpace.bounds);
-    EXPECT_EQ(ui::ROTATION_90, state.displaySpace.orientation);
+    EXPECT_EQ(displayRect, state.displaySpace.getBoundsAsRect());
+    EXPECT_EQ(ui::ROTATION_90, state.displaySpace.getOrientation());
 
-    EXPECT_EQ(displayRect, state.framebufferSpace.bounds);
-    EXPECT_EQ(ui::ROTATION_90, state.framebufferSpace.orientation);
+    EXPECT_EQ(displayRect, state.framebufferSpace.getBoundsAsRect());
+    EXPECT_EQ(ui::ROTATION_90, state.framebufferSpace.getOrientation());
 
-    EXPECT_EQ(state.displaySpace.content, state.transform.transform(state.layerStackSpace.content));
+    EXPECT_EQ(state.displaySpace.getContent(),
+              state.transform.transform(state.layerStackSpace.getContent()));
 
     EXPECT_THAT(state.dirtyRegion, RegionEq(Region(displayRect)));
 }
 
 /*
- * Output::setLayerStackFilter()
+ * Output::setLayerFilter()
  */
 
-TEST_F(OutputTest, setLayerStackFilterSetsFilterAndDirtiesEntireOutput) {
-    const uint32_t layerStack = 123u;
-    mOutput->setLayerStackFilter(layerStack, true);
+TEST_F(OutputTest, setLayerFilterSetsFilterAndDirtiesEntireOutput) {
+    constexpr ui::LayerFilter kFilter{ui::LayerStack{123u}, true};
+    mOutput->setLayerFilter(kFilter);
 
-    EXPECT_TRUE(mOutput->getState().layerStackInternal);
-    EXPECT_EQ(layerStack, mOutput->getState().layerStackId);
+    const auto& state = mOutput->getState();
+    EXPECT_EQ(kFilter.layerStack, state.layerFilter.layerStack);
+    EXPECT_TRUE(state.layerFilter.toInternalDisplay);
 
-    EXPECT_THAT(mOutput->getState().dirtyRegion, RegionEq(Region(kDefaultDisplaySize)));
+    EXPECT_THAT(state.dirtyRegion, RegionEq(Region(kDefaultDisplaySize)));
 }
 
 /*
@@ -560,133 +573,118 @@
 
     mOutput->setRenderSurface(std::unique_ptr<RenderSurface>(renderSurface));
 
-    EXPECT_EQ(Rect(newDisplaySize), mOutput->getState().framebufferSpace.bounds);
+    EXPECT_EQ(Rect(newDisplaySize), mOutput->getState().framebufferSpace.getBoundsAsRect());
+}
+
+/**
+ * Output::setDisplayBrightness()
+ */
+
+TEST_F(OutputTest, setNextBrightness) {
+    constexpr float kDisplayBrightness = 0.5f;
+    mOutput->setNextBrightness(kDisplayBrightness);
+    ASSERT_TRUE(mOutput->getState().displayBrightness.has_value());
+    EXPECT_EQ(kDisplayBrightness, mOutput->getState().displayBrightness);
 }
 
 /*
  * Output::getDirtyRegion()
  */
 
-TEST_F(OutputTest, getDirtyRegionWithRepaintEverythingTrue) {
+TEST_F(OutputTest, getDirtyRegion) {
     const Rect viewport{100, 200};
-    mOutput->editState().layerStackSpace.content = viewport;
+    mOutput->editState().layerStackSpace.setContent(viewport);
     mOutput->editState().dirtyRegion.set(50, 300);
 
-    {
-        Region result = mOutput->getDirtyRegion(true);
-
-        EXPECT_THAT(result, RegionEq(Region(viewport)));
-    }
-}
-
-TEST_F(OutputTest, getDirtyRegionWithRepaintEverythingFalse) {
-    const Rect viewport{100, 200};
-    mOutput->editState().layerStackSpace.content = viewport;
-    mOutput->editState().dirtyRegion.set(50, 300);
-
-    {
-        Region result = mOutput->getDirtyRegion(false);
-
-        // The dirtyRegion should be clipped to the display bounds.
-        EXPECT_THAT(result, RegionEq(Region(Rect(50, 200))));
-    }
+    // The dirty region should be clipped to the display bounds.
+    EXPECT_THAT(mOutput->getDirtyRegion(), RegionEq(Region(Rect(50, 200))));
 }
 
 /*
- * Output::belongsInOutput()
+ * Output::includesLayer()
  */
 
-TEST_F(OutputTest, belongsInOutputFiltersAsExpected) {
-    const uint32_t layerStack1 = 123u;
-    const uint32_t layerStack2 = 456u;
+TEST_F(OutputTest, layerFiltering) {
+    const ui::LayerStack layerStack1{123u};
+    const ui::LayerStack layerStack2{456u};
 
-    // If the output accepts layerStack1 and internal-only layers....
-    mOutput->setLayerStackFilter(layerStack1, true);
+    // If the output is associated to layerStack1 and to an internal display...
+    mOutput->setLayerFilter({layerStack1, true});
 
-    // A layer with no layerStack does not belong to it, internal-only or not.
-    EXPECT_FALSE(mOutput->belongsInOutput(std::nullopt, false));
-    EXPECT_FALSE(mOutput->belongsInOutput(std::nullopt, true));
+    // It excludes layers with no layer stack, internal-only or not.
+    EXPECT_FALSE(mOutput->includesLayer({ui::INVALID_LAYER_STACK, false}));
+    EXPECT_FALSE(mOutput->includesLayer({ui::INVALID_LAYER_STACK, true}));
 
-    // Any layer with layerStack1 belongs to it, internal-only or not.
-    EXPECT_TRUE(mOutput->belongsInOutput(layerStack1, false));
-    EXPECT_TRUE(mOutput->belongsInOutput(layerStack1, true));
-    EXPECT_FALSE(mOutput->belongsInOutput(layerStack2, true));
-    EXPECT_FALSE(mOutput->belongsInOutput(layerStack2, false));
+    // It includes layers on layerStack1, internal-only or not.
+    EXPECT_TRUE(mOutput->includesLayer({layerStack1, false}));
+    EXPECT_TRUE(mOutput->includesLayer({layerStack1, true}));
+    EXPECT_FALSE(mOutput->includesLayer({layerStack2, true}));
+    EXPECT_FALSE(mOutput->includesLayer({layerStack2, false}));
 
-    // If the output accepts layerStack21 but not internal-only layers...
-    mOutput->setLayerStackFilter(layerStack1, false);
+    // If the output is associated to layerStack1 but not to an internal display...
+    mOutput->setLayerFilter({layerStack1, false});
 
-    // Only non-internal layers with layerStack1 belong to it.
-    EXPECT_TRUE(mOutput->belongsInOutput(layerStack1, false));
-    EXPECT_FALSE(mOutput->belongsInOutput(layerStack1, true));
-    EXPECT_FALSE(mOutput->belongsInOutput(layerStack2, true));
-    EXPECT_FALSE(mOutput->belongsInOutput(layerStack2, false));
+    // It includes layers on layerStack1, unless they are internal-only.
+    EXPECT_TRUE(mOutput->includesLayer({layerStack1, false}));
+    EXPECT_FALSE(mOutput->includesLayer({layerStack1, true}));
+    EXPECT_FALSE(mOutput->includesLayer({layerStack2, true}));
+    EXPECT_FALSE(mOutput->includesLayer({layerStack2, false}));
 }
 
-TEST_F(OutputTest, belongsInOutputHandlesLayerWithNoCompositionState) {
+TEST_F(OutputTest, layerFilteringWithoutCompositionState) {
     NonInjectedLayer layer;
     sp<LayerFE> layerFE(layer.layerFE);
 
-    // If the layer has no composition state, it does not belong to any output.
+    // Layers without composition state are excluded.
     EXPECT_CALL(*layer.layerFE, getCompositionState).WillOnce(Return(nullptr));
-    EXPECT_FALSE(mOutput->belongsInOutput(layerFE));
+    EXPECT_FALSE(mOutput->includesLayer(layerFE));
 }
 
-TEST_F(OutputTest, belongsInOutputFiltersLayersAsExpected) {
+TEST_F(OutputTest, layerFilteringWithCompositionState) {
     NonInjectedLayer layer;
     sp<LayerFE> layerFE(layer.layerFE);
 
-    const uint32_t layerStack1 = 123u;
-    const uint32_t layerStack2 = 456u;
+    const ui::LayerStack layerStack1{123u};
+    const ui::LayerStack layerStack2{456u};
 
-    // If the output accepts layerStack1 and internal-only layers....
-    mOutput->setLayerStackFilter(layerStack1, true);
+    // If the output is associated to layerStack1 and to an internal display...
+    mOutput->setLayerFilter({layerStack1, true});
 
-    // A layer with no layerStack does not belong to it, internal-only or not.
-    layer.layerFEState.layerStackId = std::nullopt;
-    layer.layerFEState.internalOnly = false;
-    EXPECT_FALSE(mOutput->belongsInOutput(layerFE));
+    // It excludes layers with no layer stack, internal-only or not.
+    layer.layerFEState.outputFilter = {ui::INVALID_LAYER_STACK, false};
+    EXPECT_FALSE(mOutput->includesLayer(layerFE));
 
-    layer.layerFEState.layerStackId = std::nullopt;
-    layer.layerFEState.internalOnly = true;
-    EXPECT_FALSE(mOutput->belongsInOutput(layerFE));
+    layer.layerFEState.outputFilter = {ui::INVALID_LAYER_STACK, true};
+    EXPECT_FALSE(mOutput->includesLayer(layerFE));
 
-    // Any layer with layerStack1 belongs to it, internal-only or not.
-    layer.layerFEState.layerStackId = layerStack1;
-    layer.layerFEState.internalOnly = false;
-    EXPECT_TRUE(mOutput->belongsInOutput(layerFE));
+    // It includes layers on layerStack1, internal-only or not.
+    layer.layerFEState.outputFilter = {layerStack1, false};
+    EXPECT_TRUE(mOutput->includesLayer(layerFE));
 
-    layer.layerFEState.layerStackId = layerStack1;
-    layer.layerFEState.internalOnly = true;
-    EXPECT_TRUE(mOutput->belongsInOutput(layerFE));
+    layer.layerFEState.outputFilter = {layerStack1, true};
+    EXPECT_TRUE(mOutput->includesLayer(layerFE));
 
-    layer.layerFEState.layerStackId = layerStack2;
-    layer.layerFEState.internalOnly = true;
-    EXPECT_FALSE(mOutput->belongsInOutput(layerFE));
+    layer.layerFEState.outputFilter = {layerStack2, true};
+    EXPECT_FALSE(mOutput->includesLayer(layerFE));
 
-    layer.layerFEState.layerStackId = layerStack2;
-    layer.layerFEState.internalOnly = false;
-    EXPECT_FALSE(mOutput->belongsInOutput(layerFE));
+    layer.layerFEState.outputFilter = {layerStack2, false};
+    EXPECT_FALSE(mOutput->includesLayer(layerFE));
 
-    // If the output accepts layerStack1 but not internal-only layers...
-    mOutput->setLayerStackFilter(layerStack1, false);
+    // If the output is associated to layerStack1 but not to an internal display...
+    mOutput->setLayerFilter({layerStack1, false});
 
-    // Only non-internal layers with layerStack1 belong to it.
-    layer.layerFEState.layerStackId = layerStack1;
-    layer.layerFEState.internalOnly = false;
-    EXPECT_TRUE(mOutput->belongsInOutput(layerFE));
+    // It includes layers on layerStack1, unless they are internal-only.
+    layer.layerFEState.outputFilter = {layerStack1, false};
+    EXPECT_TRUE(mOutput->includesLayer(layerFE));
 
-    layer.layerFEState.layerStackId = layerStack1;
-    layer.layerFEState.internalOnly = true;
-    EXPECT_FALSE(mOutput->belongsInOutput(layerFE));
+    layer.layerFEState.outputFilter = {layerStack1, true};
+    EXPECT_FALSE(mOutput->includesLayer(layerFE));
 
-    layer.layerFEState.layerStackId = layerStack2;
-    layer.layerFEState.internalOnly = true;
-    EXPECT_FALSE(mOutput->belongsInOutput(layerFE));
+    layer.layerFEState.outputFilter = {layerStack2, true};
+    EXPECT_FALSE(mOutput->includesLayer(layerFE));
 
-    layer.layerFEState.layerStackId = layerStack2;
-    layer.layerFEState.internalOnly = false;
-    EXPECT_FALSE(mOutput->belongsInOutput(layerFE));
+    layer.layerFEState.outputFilter = {layerStack2, false};
+    EXPECT_FALSE(mOutput->includesLayer(layerFE));
 }
 
 /*
@@ -724,9 +722,9 @@
 using OutputSetReleasedLayersTest = OutputTest;
 
 TEST_F(OutputSetReleasedLayersTest, setReleasedLayersTakesGivenLayers) {
-    sp<StrictMock<mock::LayerFE>> layer1FE{new StrictMock<mock::LayerFE>()};
-    sp<StrictMock<mock::LayerFE>> layer2FE{new StrictMock<mock::LayerFE>()};
-    sp<StrictMock<mock::LayerFE>> layer3FE{new StrictMock<mock::LayerFE>()};
+    sp<StrictMock<mock::LayerFE>> layer1FE = sp<StrictMock<mock::LayerFE>>::make();
+    sp<StrictMock<mock::LayerFE>> layer2FE = sp<StrictMock<mock::LayerFE>>::make();
+    sp<StrictMock<mock::LayerFE>> layer3FE = sp<StrictMock<mock::LayerFE>>::make();
 
     Output::ReleasedLayers layers;
     layers.push_back(layer1FE);
@@ -971,9 +969,10 @@
     mOutput->planComposition();
 
     std::shared_ptr<renderengine::ExternalTexture> buffer = std::make_shared<
-            renderengine::ExternalTexture>(new GraphicBuffer(), renderEngine,
-                                           renderengine::ExternalTexture::Usage::READABLE |
-                                                   renderengine::ExternalTexture::Usage::WRITEABLE);
+            renderengine::impl::
+                    ExternalTexture>(new GraphicBuffer(), renderEngine,
+                                     renderengine::impl::ExternalTexture::Usage::READABLE |
+                                             renderengine::impl::ExternalTexture::Usage::WRITEABLE);
     layer1.outputLayerState.overrideInfo.buffer = buffer;
     layer2.outputLayerState.overrideInfo.buffer = buffer;
     layer1.outputLayerState.overrideInfo.peekThroughLayer = layer3.outputLayer;
@@ -1079,7 +1078,8 @@
     OutputRebuildLayerStacksTest() {
         mOutput.mState.isEnabled = true;
         mOutput.mState.transform = kIdentityTransform;
-        mOutput.mState.displaySpace.bounds = kOutputBounds;
+        mOutput.mState.displaySpace.setBounds(
+                ui::Size(kOutputBounds.getWidth(), kOutputBounds.getHeight()));
 
         mRefreshArgs.updatingOutputGeometryThisFrame = true;
 
@@ -1209,7 +1209,7 @@
 
         StrictMock<mock::OutputLayer> outputLayer;
         impl::OutputLayerCompositionState outputLayerState;
-        sp<StrictMock<mock::LayerFE>> layerFE{new StrictMock<mock::LayerFE>()};
+        sp<StrictMock<mock::LayerFE>> layerFE = sp<StrictMock<mock::LayerFE>>::make();
     };
 
     OutputCollectVisibleLayersTest() {
@@ -1268,21 +1268,22 @@
     struct OutputPartialMock : public OutputPartialMockBase {
         // Sets up the helper functions called by the function under test to use
         // mock implementations.
-        MOCK_CONST_METHOD1(belongsInOutput, bool(const sp<compositionengine::LayerFE>&));
+        MOCK_METHOD(bool, includesLayer, (const sp<compositionengine::LayerFE>&),
+                    (const, override));
         MOCK_CONST_METHOD1(getOutputLayerOrderedByZByIndex, OutputLayer*(size_t));
         MOCK_METHOD2(ensureOutputLayer,
                      compositionengine::OutputLayer*(std::optional<size_t>, const sp<LayerFE>&));
     };
 
     OutputEnsureOutputLayerIfVisibleTest() {
-        EXPECT_CALL(mOutput, belongsInOutput(sp<LayerFE>(mLayer.layerFE)))
+        EXPECT_CALL(mOutput, includesLayer(sp<LayerFE>(mLayer.layerFE)))
                 .WillRepeatedly(Return(true));
         EXPECT_CALL(mOutput, getOutputLayerCount()).WillRepeatedly(Return(1u));
         EXPECT_CALL(mOutput, getOutputLayerOrderedByZByIndex(0u))
                 .WillRepeatedly(Return(&mLayer.outputLayer));
 
-        mOutput.mState.displaySpace.bounds = Rect(0, 0, 200, 300);
-        mOutput.mState.layerStackSpace.content = Rect(0, 0, 200, 300);
+        mOutput.mState.displaySpace.setBounds(ui::Size(200, 300));
+        mOutput.mState.layerStackSpace.setContent(Rect(0, 0, 200, 300));
         mOutput.mState.transform = ui::Transform(TR_IDENT, 200, 300);
 
         mLayer.layerFEState.isVisible = true;
@@ -1290,7 +1291,7 @@
         mLayer.layerFEState.contentDirty = true;
         mLayer.layerFEState.geomLayerBounds = FloatRect{0, 0, 100, 200};
         mLayer.layerFEState.geomLayerTransform = ui::Transform(TR_IDENT, 100, 200);
-        mLayer.layerFEState.transparentRegionHint = Region(Rect(0, 0, 100, 100));
+        mLayer.layerFEState.transparentRegionHint = kTransparentRegionHint;
 
         mLayer.outputLayerState.visibleRegion = Region(Rect(0, 0, 50, 200));
         mLayer.outputLayerState.coveredRegion = Region(Rect(50, 0, 100, 200));
@@ -1308,6 +1309,9 @@
     static const Region kRightHalfBoundsNoRotation;
     static const Region kLowerHalfBoundsNoRotation;
     static const Region kFullBounds90Rotation;
+    static const Region kTransparentRegionHint;
+    static const Region kTransparentRegionHintTwo;
+    static const Region kTransparentRegionHintTwo90Rotation;
 
     StrictMock<OutputPartialMock> mOutput;
     LayerFESet mGeomSnapshots;
@@ -1325,9 +1329,15 @@
         Region(Rect(50, 0, 100, 200));
 const Region OutputEnsureOutputLayerIfVisibleTest::kFullBounds90Rotation =
         Region(Rect(0, 0, 200, 100));
+const Region OutputEnsureOutputLayerIfVisibleTest::kTransparentRegionHint =
+        Region(Rect(0, 0, 100, 100));
+const Region OutputEnsureOutputLayerIfVisibleTest::kTransparentRegionHintTwo =
+        Region(Rect(25, 20, 50, 75));
+const Region OutputEnsureOutputLayerIfVisibleTest::kTransparentRegionHintTwo90Rotation =
+        Region(Rect(125, 25, 180, 50));
 
-TEST_F(OutputEnsureOutputLayerIfVisibleTest, performsGeomLatchBeforeCheckingIfLayerBelongs) {
-    EXPECT_CALL(mOutput, belongsInOutput(sp<LayerFE>(mLayer.layerFE))).WillOnce(Return(false));
+TEST_F(OutputEnsureOutputLayerIfVisibleTest, performsGeomLatchBeforeCheckingIfLayerIncluded) {
+    EXPECT_CALL(mOutput, includesLayer(sp<LayerFE>(mLayer.layerFE))).WillOnce(Return(false));
     EXPECT_CALL(*mLayer.layerFE,
                 prepareCompositionState(compositionengine::LayerFE::StateSubset::BasicGeometry));
 
@@ -1337,8 +1347,8 @@
 }
 
 TEST_F(OutputEnsureOutputLayerIfVisibleTest,
-       skipsLatchIfAlreadyLatchedBeforeCheckingIfLayerBelongs) {
-    EXPECT_CALL(mOutput, belongsInOutput(sp<LayerFE>(mLayer.layerFE))).WillOnce(Return(false));
+       skipsLatchIfAlreadyLatchedBeforeCheckingIfLayerIncluded) {
+    EXPECT_CALL(mOutput, includesLayer(sp<LayerFE>(mLayer.layerFE))).WillOnce(Return(false));
 
     ensureOutputLayerIfVisible();
 }
@@ -1362,7 +1372,7 @@
 }
 
 TEST_F(OutputEnsureOutputLayerIfVisibleTest, takesNotSoEarlyOutifDrawRegionEmpty) {
-    mOutput.mState.displaySpace.bounds = Rect(0, 0, 0, 0);
+    mOutput.mState.displaySpace.setBounds(ui::Size(0, 0));
 
     ensureOutputLayerIfVisible();
 }
@@ -1559,7 +1569,7 @@
     mLayer.layerFEState.contentDirty = true;
     mLayer.layerFEState.geomLayerTransform = ui::Transform(TR_IDENT, 100, 200);
 
-    mOutput.mState.layerStackSpace.content = Rect(0, 0, 300, 200);
+    mOutput.mState.layerStackSpace.setContent(Rect(0, 0, 300, 200));
     mOutput.mState.transform = ui::Transform(TR_ROT_90, 200, 300);
 
     EXPECT_CALL(mOutput, getOutputLayerCount()).WillOnce(Return(0u));
@@ -1585,7 +1595,7 @@
     mLayer.layerFEState.contentDirty = true;
     mLayer.layerFEState.geomLayerTransform = ui::Transform(TR_IDENT, 100, 200);
 
-    mOutput.mState.layerStackSpace.content = Rect(0, 0, 300, 200);
+    mOutput.mState.layerStackSpace.setContent(Rect(0, 0, 300, 200));
     mOutput.mState.transform = ui::Transform(TR_ROT_90, 200, 300);
 
     EXPECT_CALL(mOutput, ensureOutputLayer(Eq(0u), Eq(mLayer.layerFE)))
@@ -1748,6 +1758,52 @@
     ensureOutputLayerIfVisible();
 }
 
+TEST_F(OutputEnsureOutputLayerIfVisibleTest, displayDecorSetsBlockingFromTransparentRegion) {
+    mLayer.layerFEState.isOpaque = false;
+    mLayer.layerFEState.contentDirty = true;
+    mLayer.layerFEState.compositionType =
+            aidl::android::hardware::graphics::composer3::Composition::DISPLAY_DECORATION;
+
+    EXPECT_CALL(mOutput, getOutputLayerCount()).WillOnce(Return(0u));
+    EXPECT_CALL(mOutput, ensureOutputLayer(Eq(std::nullopt), Eq(mLayer.layerFE)))
+            .WillOnce(Return(&mLayer.outputLayer));
+    ensureOutputLayerIfVisible();
+
+    EXPECT_THAT(mLayer.outputLayerState.outputSpaceBlockingRegionHint,
+                RegionEq(kTransparentRegionHint));
+}
+
+TEST_F(OutputEnsureOutputLayerIfVisibleTest, normalLayersDoNotSetBlockingRegion) {
+    mLayer.layerFEState.isOpaque = false;
+    mLayer.layerFEState.contentDirty = true;
+
+    EXPECT_CALL(mOutput, getOutputLayerCount()).WillOnce(Return(0u));
+    EXPECT_CALL(mOutput, ensureOutputLayer(Eq(std::nullopt), Eq(mLayer.layerFE)))
+            .WillOnce(Return(&mLayer.outputLayer));
+    ensureOutputLayerIfVisible();
+
+    EXPECT_THAT(mLayer.outputLayerState.outputSpaceBlockingRegionHint, RegionEq(Region()));
+}
+
+TEST_F(OutputEnsureOutputLayerIfVisibleTest, blockingRegionIsInOutputSpace) {
+    mLayer.layerFEState.isOpaque = false;
+    mLayer.layerFEState.contentDirty = true;
+    mLayer.layerFEState.compositionType =
+            aidl::android::hardware::graphics::composer3::Composition::DISPLAY_DECORATION;
+    mLayer.layerFEState.transparentRegionHint = kTransparentRegionHintTwo;
+
+    mOutput.mState.layerStackSpace.setContent(Rect(0, 0, 300, 200));
+    mOutput.mState.transform = ui::Transform(TR_ROT_90, 200, 300);
+
+    EXPECT_CALL(mOutput, getOutputLayerCount()).WillOnce(Return(0u));
+    EXPECT_CALL(mOutput, ensureOutputLayer(Eq(std::nullopt), Eq(mLayer.layerFE)))
+            .WillOnce(Return(&mLayer.outputLayer));
+    ensureOutputLayerIfVisible();
+
+    EXPECT_THAT(mLayer.outputLayerState.outputSpaceBlockingRegionHint,
+                RegionEq(kTransparentRegionHintTwo90Rotation));
+}
+
 /*
  * Output::present()
  */
@@ -2530,7 +2586,7 @@
     struct OutputPartialMock : public OutputPartialMockBase {
         // Sets up the helper functions called by the function under test to use
         // mock implementations.
-        MOCK_CONST_METHOD1(getDirtyRegion, Region(bool));
+        MOCK_METHOD(Region, getDirtyRegion, (), (const));
     };
 
     OutputBeginFrameTest() {
@@ -2542,8 +2598,7 @@
     struct IfGetDirtyRegionExpectationState
           : public CallOrderStateMachineHelper<TestType, IfGetDirtyRegionExpectationState> {
         [[nodiscard]] auto ifGetDirtyRegionReturns(Region dirtyRegion) {
-            EXPECT_CALL(getInstance()->mOutput, getDirtyRegion(false))
-                    .WillOnce(Return(dirtyRegion));
+            EXPECT_CALL(getInstance()->mOutput, getDirtyRegion()).WillOnce(Return(dirtyRegion));
             return nextState<AndIfGetOutputLayerCountExpectationState>();
         }
     };
@@ -2683,7 +2738,7 @@
     struct OutputPartialMock : public OutputPartialMockBase {
         // Sets up the helper functions called by the function under test to use
         // mock implementations.
-        MOCK_CONST_METHOD1(getDirtyRegion, Region(bool));
+        MOCK_METHOD(Region, getDirtyRegion, (), (const));
         MOCK_METHOD2(composeSurfaces,
                      std::optional<base::unique_fd>(
                              const Region&, const compositionengine::CompositionRefreshArgs&));
@@ -2711,7 +2766,6 @@
 
 TEST_F(OutputDevOptRepaintFlashTest, doesNothingIfFlashDelayNotSet) {
     mRefreshArgs.devOptFlashDirtyRegionsDelay = {};
-    mRefreshArgs.repaintEverything = true;
     mOutput.mState.isEnabled = true;
 
     mOutput.devOptRepaintFlash(mRefreshArgs);
@@ -2719,7 +2773,6 @@
 
 TEST_F(OutputDevOptRepaintFlashTest, postsAndPreparesANewFrameIfNotEnabled) {
     mRefreshArgs.devOptFlashDirtyRegionsDelay = std::chrono::microseconds(1);
-    mRefreshArgs.repaintEverything = true;
     mOutput.mState.isEnabled = false;
 
     InSequence seq;
@@ -2729,13 +2782,12 @@
     mOutput.devOptRepaintFlash(mRefreshArgs);
 }
 
-TEST_F(OutputDevOptRepaintFlashTest, postsAndPreparesANewFrameIfNotDirty) {
+TEST_F(OutputDevOptRepaintFlashTest, postsAndPreparesANewFrameIfEnabled) {
     mRefreshArgs.devOptFlashDirtyRegionsDelay = std::chrono::microseconds(1);
-    mRefreshArgs.repaintEverything = true;
     mOutput.mState.isEnabled = true;
 
     InSequence seq;
-    EXPECT_CALL(mOutput, getDirtyRegion(true)).WillOnce(Return(kEmptyRegion));
+    EXPECT_CALL(mOutput, getDirtyRegion()).WillOnce(Return(kEmptyRegion));
     EXPECT_CALL(mOutput, postFramebuffer());
     EXPECT_CALL(mOutput, prepareFrame());
 
@@ -2744,11 +2796,10 @@
 
 TEST_F(OutputDevOptRepaintFlashTest, alsoComposesSurfacesAndQueuesABufferIfDirty) {
     mRefreshArgs.devOptFlashDirtyRegionsDelay = std::chrono::microseconds(1);
-    mRefreshArgs.repaintEverything = false;
     mOutput.mState.isEnabled = true;
 
     InSequence seq;
-    EXPECT_CALL(mOutput, getDirtyRegion(false)).WillOnce(Return(kNotEmptyRegion));
+    EXPECT_CALL(mOutput, getDirtyRegion()).WillOnce(Return(kNotEmptyRegion));
     EXPECT_CALL(mOutput, composeSurfaces(RegionEq(kNotEmptyRegion), Ref(mRefreshArgs)));
     EXPECT_CALL(*mRenderSurface, queueBuffer(_));
     EXPECT_CALL(mOutput, postFramebuffer());
@@ -2903,12 +2954,24 @@
     // are passed. This happens to work with the current implementation, but
     // would not survive certain calls like Fence::merge() which would return a
     // new instance.
-    EXPECT_CALL(*mLayer1.layerFE,
-                onLayerDisplayed(Property(&sp<Fence>::get, Eq(layer1Fence.get()))));
-    EXPECT_CALL(*mLayer2.layerFE,
-                onLayerDisplayed(Property(&sp<Fence>::get, Eq(layer2Fence.get()))));
-    EXPECT_CALL(*mLayer3.layerFE,
-                onLayerDisplayed(Property(&sp<Fence>::get, Eq(layer3Fence.get()))));
+    base::unique_fd layer1FD(layer1Fence->dup());
+    base::unique_fd layer2FD(layer2Fence->dup());
+    base::unique_fd layer3FD(layer3Fence->dup());
+    EXPECT_CALL(*mLayer1.layerFE, onLayerDisplayed(_))
+            .WillOnce([&layer1FD](std::shared_future<renderengine::RenderEngineResult>
+                                          futureRenderEngineResult) {
+                EXPECT_EQ(layer1FD, futureRenderEngineResult.get().drawFence);
+            });
+    EXPECT_CALL(*mLayer2.layerFE, onLayerDisplayed(_))
+            .WillOnce([&layer2FD](std::shared_future<renderengine::RenderEngineResult>
+                                          futureRenderEngineResult) {
+                EXPECT_EQ(layer2FD, futureRenderEngineResult.get().drawFence);
+            });
+    EXPECT_CALL(*mLayer3.layerFE, onLayerDisplayed(_))
+            .WillOnce([&layer3FD](std::shared_future<renderengine::RenderEngineResult>
+                                          futureRenderEngineResult) {
+                EXPECT_EQ(layer3FD, futureRenderEngineResult.get().drawFence);
+            });
 
     mOutput.postFramebuffer();
 }
@@ -2934,9 +2997,9 @@
     // Fence::merge is called, and since none of the fences are actually valid,
     // Fence::NO_FENCE is returned and passed to each onLayerDisplayed() call.
     // This is the best we can do without creating a real kernel fence object.
-    EXPECT_CALL(*mLayer1.layerFE, onLayerDisplayed(Fence::NO_FENCE));
-    EXPECT_CALL(*mLayer2.layerFE, onLayerDisplayed(Fence::NO_FENCE));
-    EXPECT_CALL(*mLayer3.layerFE, onLayerDisplayed(Fence::NO_FENCE));
+    EXPECT_CALL(*mLayer1.layerFE, onLayerDisplayed).WillOnce(Return());
+    EXPECT_CALL(*mLayer2.layerFE, onLayerDisplayed).WillOnce(Return());
+    EXPECT_CALL(*mLayer3.layerFE, onLayerDisplayed).WillOnce(Return());
 
     mOutput.postFramebuffer();
 }
@@ -2949,9 +3012,9 @@
     EXPECT_CALL(mOutput, getOutputLayerCount()).WillOnce(Return(0u));
 
     // Load up the released layers with some mock instances
-    sp<StrictMock<mock::LayerFE>> releasedLayer1{new StrictMock<mock::LayerFE>()};
-    sp<StrictMock<mock::LayerFE>> releasedLayer2{new StrictMock<mock::LayerFE>()};
-    sp<StrictMock<mock::LayerFE>> releasedLayer3{new StrictMock<mock::LayerFE>()};
+    sp<StrictMock<mock::LayerFE>> releasedLayer1 = sp<StrictMock<mock::LayerFE>>::make();
+    sp<StrictMock<mock::LayerFE>> releasedLayer2 = sp<StrictMock<mock::LayerFE>>::make();
+    sp<StrictMock<mock::LayerFE>> releasedLayer3 = sp<StrictMock<mock::LayerFE>>::make();
     Output::ReleasedLayers layers;
     layers.push_back(releasedLayer1);
     layers.push_back(releasedLayer2);
@@ -2968,12 +3031,22 @@
     EXPECT_CALL(*mRenderSurface, onPresentDisplayCompleted());
 
     // Each released layer should be given the presentFence.
-    EXPECT_CALL(*releasedLayer1,
-                onLayerDisplayed(Property(&sp<Fence>::get, Eq(presentFence.get()))));
-    EXPECT_CALL(*releasedLayer2,
-                onLayerDisplayed(Property(&sp<Fence>::get, Eq(presentFence.get()))));
-    EXPECT_CALL(*releasedLayer3,
-                onLayerDisplayed(Property(&sp<Fence>::get, Eq(presentFence.get()))));
+    base::unique_fd layerFD(presentFence.get()->dup());
+    EXPECT_CALL(*releasedLayer1, onLayerDisplayed(_))
+            .WillOnce([&layerFD](std::shared_future<renderengine::RenderEngineResult>
+                                         futureRenderEngineResult) {
+                EXPECT_EQ(layerFD, futureRenderEngineResult.get().drawFence);
+            });
+    EXPECT_CALL(*releasedLayer2, onLayerDisplayed(_))
+            .WillOnce([&layerFD](std::shared_future<renderengine::RenderEngineResult>
+                                         futureRenderEngineResult) {
+                EXPECT_EQ(layerFD, futureRenderEngineResult.get().drawFence);
+            });
+    EXPECT_CALL(*releasedLayer3, onLayerDisplayed(_))
+            .WillOnce([&layerFD](std::shared_future<renderengine::RenderEngineResult>
+                                         futureRenderEngineResult) {
+                EXPECT_EQ(layerFD, futureRenderEngineResult.get().drawFence);
+            });
 
     mOutput.postFramebuffer();
 
@@ -2993,7 +3066,7 @@
         // mock implementations.
         MOCK_CONST_METHOD0(getSkipColorTransform, bool());
         MOCK_METHOD3(generateClientCompositionRequests,
-                     std::vector<LayerFE::LayerSettings>(bool, Region&, ui::Dataspace));
+                     std::vector<LayerFE::LayerSettings>(bool, ui::Dataspace, std::vector<LayerFE*>&));
         MOCK_METHOD2(appendRegionFlashRequests,
                      void(const Region&, std::vector<LayerFE::LayerSettings>&));
         MOCK_METHOD1(setExpensiveRenderingExpected, void(bool));
@@ -3005,11 +3078,11 @@
         mOutput.setRenderSurfaceForTest(std::unique_ptr<RenderSurface>(mRenderSurface));
         mOutput.cacheClientCompositionRequests(MAX_CLIENT_COMPOSITION_CACHE_SIZE);
 
-        mOutput.mState.orientedDisplaySpace.content = kDefaultOutputFrame;
-        mOutput.mState.layerStackSpace.content = kDefaultOutputViewport;
-        mOutput.mState.framebufferSpace.content = kDefaultOutputDestinationClip;
-        mOutput.mState.displaySpace.content = kDefaultOutputDestinationClip;
-        mOutput.mState.displaySpace.orientation = kDefaultOutputOrientation;
+        mOutput.mState.orientedDisplaySpace.setContent(kDefaultOutputFrame);
+        mOutput.mState.layerStackSpace.setContent(kDefaultOutputViewport);
+        mOutput.mState.framebufferSpace.setContent(kDefaultOutputDestinationClip);
+        mOutput.mState.displaySpace.setContent(kDefaultOutputDestinationClip);
+        mOutput.mState.displaySpace.setOrientation(kDefaultOutputOrientation);
         mOutput.mState.transform = ui::Transform{kDefaultOutputOrientationFlags};
         mOutput.mState.dataspace = kDefaultOutputDataspace;
         mOutput.mState.colorTransformMatrix = kDefaultColorTransformMat;
@@ -3019,6 +3092,7 @@
         mOutput.mState.usesDeviceComposition = false;
         mOutput.mState.reusedClientComposition = false;
         mOutput.mState.flipClientTarget = false;
+        mOutput.mState.clientTargetBrightness = kClientTargetBrightness;
 
         EXPECT_CALL(mOutput, getCompositionEngine()).WillRepeatedly(ReturnRef(mCompositionEngine));
         EXPECT_CALL(mCompositionEngine, getRenderEngine()).WillRepeatedly(ReturnRef(mRenderEngine));
@@ -3053,6 +3127,10 @@
     static constexpr float kDefaultMaxLuminance = 0.9f;
     static constexpr float kDefaultAvgLuminance = 0.7f;
     static constexpr float kDefaultMinLuminance = 0.1f;
+    static constexpr float kUnknownLuminance = -1.f;
+    static constexpr float kDisplayLuminance = 400.f;
+    static constexpr float kClientTargetLuminanceNits = 200.f;
+    static constexpr float kClientTargetBrightness = 0.5f;
 
     static const Rect kDefaultOutputFrame;
     static const Rect kDefaultOutputViewport;
@@ -3071,9 +3149,10 @@
     mock::RenderSurface* mRenderSurface = new StrictMock<mock::RenderSurface>();
     StrictMock<OutputPartialMock> mOutput;
     std::shared_ptr<renderengine::ExternalTexture> mOutputBuffer = std::make_shared<
-            renderengine::ExternalTexture>(new GraphicBuffer(), mRenderEngine,
-                                           renderengine::ExternalTexture::Usage::READABLE |
-                                                   renderengine::ExternalTexture::Usage::WRITEABLE);
+            renderengine::impl::
+                    ExternalTexture>(new GraphicBuffer(), mRenderEngine,
+                                     renderengine::impl::ExternalTexture::Usage::READABLE |
+                                             renderengine::impl::ExternalTexture::Usage::WRITEABLE);
 
     std::optional<base::unique_fd> mReadyFence;
 };
@@ -3084,6 +3163,7 @@
 const mat4 OutputComposeSurfacesTest::kDefaultColorTransformMat{mat4() * 0.5f};
 const compositionengine::CompositionRefreshArgs OutputComposeSurfacesTest::kDefaultRefreshArgs;
 const Region OutputComposeSurfacesTest::kDebugRegion{Rect{100, 101, 102, 103}};
+
 const HdrCapabilities OutputComposeSurfacesTest::
         kHdrCapabilities{{},
                          OutputComposeSurfacesTest::kDefaultMaxLuminance,
@@ -3142,15 +3222,20 @@
     EXPECT_CALL(*mDisplayColorProfile, hasWideColorGamut()).WillRepeatedly(Return(true));
     EXPECT_CALL(mRenderEngine, supportsProtectedContent()).WillRepeatedly(Return(false));
     EXPECT_CALL(mRenderEngine, isProtected()).WillRepeatedly(Return(false));
-    EXPECT_CALL(mOutput, generateClientCompositionRequests(_, _, kDefaultOutputDataspace))
+    EXPECT_CALL(mOutput, generateClientCompositionRequests(_, kDefaultOutputDataspace, _))
             .WillRepeatedly(Return(std::vector<LayerFE::LayerSettings>{}));
     EXPECT_CALL(mOutput, appendRegionFlashRequests(RegionEq(kDebugRegion), _))
             .WillRepeatedly(Return());
 
     EXPECT_CALL(*mRenderSurface, dequeueBuffer(_)).WillRepeatedly(Return(mOutputBuffer));
-    EXPECT_CALL(mRenderEngine, drawLayers(_, IsEmpty(), _, false, _, _))
-            .WillRepeatedly(Return(NO_ERROR));
-
+    EXPECT_CALL(mRenderEngine, drawLayers(_, IsEmpty(), _, false, _))
+            .WillRepeatedly([&](const renderengine::DisplaySettings&,
+                                const std::vector<renderengine::LayerSettings>&,
+                                const std::shared_ptr<renderengine::ExternalTexture>&, const bool,
+                                base::unique_fd&&)
+                                    -> std::future<renderengine::RenderEngineResult> {
+                return futureOf<renderengine::RenderEngineResult>({NO_ERROR, base::unique_fd()});
+            });
     verify().execute().expectAFenceWasReturned();
 }
 
@@ -3165,7 +3250,7 @@
     EXPECT_CALL(*mDisplayColorProfile, hasWideColorGamut()).WillRepeatedly(Return(true));
     EXPECT_CALL(mRenderEngine, supportsProtectedContent()).WillRepeatedly(Return(false));
     EXPECT_CALL(mRenderEngine, isProtected()).WillRepeatedly(Return(false));
-    EXPECT_CALL(mOutput, generateClientCompositionRequests(_, _, kDefaultOutputDataspace))
+    EXPECT_CALL(mOutput, generateClientCompositionRequests(_, kDefaultOutputDataspace, _))
             .WillRepeatedly(Return(std::vector<LayerFE::LayerSettings>{r1}));
     EXPECT_CALL(mOutput, appendRegionFlashRequests(RegionEq(kDebugRegion), _))
             .WillRepeatedly(
@@ -3175,8 +3260,14 @@
                     }));
 
     EXPECT_CALL(*mRenderSurface, dequeueBuffer(_)).WillRepeatedly(Return(mOutputBuffer));
-    EXPECT_CALL(mRenderEngine, drawLayers(_, ElementsAre(Pointee(r1), Pointee(r2)), _, false, _, _))
-            .WillRepeatedly(Return(NO_ERROR));
+    EXPECT_CALL(mRenderEngine, drawLayers(_, ElementsAre(r1, r2), _, false, _))
+            .WillRepeatedly([&](const renderengine::DisplaySettings&,
+                                const std::vector<renderengine::LayerSettings>&,
+                                const std::shared_ptr<renderengine::ExternalTexture>&, const bool,
+                                base::unique_fd&&)
+                                    -> std::future<renderengine::RenderEngineResult> {
+                return futureOf<renderengine::RenderEngineResult>({NO_ERROR, base::unique_fd()});
+            });
 
     verify().execute().expectAFenceWasReturned();
 }
@@ -3188,14 +3279,13 @@
 
     r1.geometry.boundaries = FloatRect{1, 2, 3, 4};
     r2.geometry.boundaries = FloatRect{5, 6, 7, 8};
-    const constexpr uint32_t kInternalLayerStack = 1234;
-    mOutput.setLayerStackFilter(kInternalLayerStack, true);
+    mOutput.setLayerFilter({ui::LayerStack{1234u}, true});
 
     EXPECT_CALL(mOutput, getSkipColorTransform()).WillRepeatedly(Return(false));
     EXPECT_CALL(*mDisplayColorProfile, hasWideColorGamut()).WillRepeatedly(Return(true));
     EXPECT_CALL(mRenderEngine, supportsProtectedContent()).WillRepeatedly(Return(false));
     EXPECT_CALL(mRenderEngine, isProtected()).WillRepeatedly(Return(false));
-    EXPECT_CALL(mOutput, generateClientCompositionRequests(_, _, kDefaultOutputDataspace))
+    EXPECT_CALL(mOutput, generateClientCompositionRequests(_, kDefaultOutputDataspace, _))
             .WillRepeatedly(Return(std::vector<LayerFE::LayerSettings>{r1}));
     EXPECT_CALL(mOutput, appendRegionFlashRequests(RegionEq(kDebugRegion), _))
             .WillRepeatedly(
@@ -3205,8 +3295,14 @@
                     }));
 
     EXPECT_CALL(*mRenderSurface, dequeueBuffer(_)).WillRepeatedly(Return(mOutputBuffer));
-    EXPECT_CALL(mRenderEngine, drawLayers(_, ElementsAre(Pointee(r1), Pointee(r2)), _, true, _, _))
-            .WillRepeatedly(Return(NO_ERROR));
+    EXPECT_CALL(mRenderEngine, drawLayers(_, ElementsAre(r1, r2), _, true, _))
+            .WillRepeatedly([&](const renderengine::DisplaySettings&,
+                                const std::vector<renderengine::LayerSettings>&,
+                                const std::shared_ptr<renderengine::ExternalTexture>&, const bool,
+                                base::unique_fd&&)
+                                    -> std::future<renderengine::RenderEngineResult> {
+                return futureOf<renderengine::RenderEngineResult>({NO_ERROR, base::unique_fd()});
+            });
 
     verify().execute().expectAFenceWasReturned();
 }
@@ -3223,15 +3319,18 @@
     EXPECT_CALL(*mDisplayColorProfile, hasWideColorGamut()).WillRepeatedly(Return(true));
     EXPECT_CALL(mRenderEngine, supportsProtectedContent()).WillRepeatedly(Return(false));
     EXPECT_CALL(mRenderEngine, isProtected()).WillRepeatedly(Return(false));
-    EXPECT_CALL(mOutput, generateClientCompositionRequests(_, _, kDefaultOutputDataspace))
+    EXPECT_CALL(mOutput, generateClientCompositionRequests(_, kDefaultOutputDataspace, _))
             .WillRepeatedly(Return(std::vector<LayerFE::LayerSettings>{r1, r2}));
     EXPECT_CALL(mOutput, appendRegionFlashRequests(RegionEq(kDebugRegion), _))
             .WillRepeatedly(Return());
 
     EXPECT_CALL(*mRenderSurface, dequeueBuffer(_)).WillRepeatedly(Return(mOutputBuffer));
-    EXPECT_CALL(mRenderEngine, drawLayers(_, ElementsAre(Pointee(r1), Pointee(r2)), _, false, _, _))
+    EXPECT_CALL(mRenderEngine, drawLayers(_, ElementsAre(r1, r2), _, false, _))
             .Times(2)
-            .WillOnce(Return(NO_ERROR));
+            .WillOnce(Return(ByMove(
+                    futureOf<renderengine::RenderEngineResult>({NO_ERROR, base::unique_fd()}))))
+            .WillOnce(Return(ByMove(
+                    futureOf<renderengine::RenderEngineResult>({NO_ERROR, base::unique_fd()}))));
 
     verify().execute().expectAFenceWasReturned();
     EXPECT_FALSE(mOutput.mState.reusedClientComposition);
@@ -3252,14 +3351,15 @@
     EXPECT_CALL(*mDisplayColorProfile, hasWideColorGamut()).WillRepeatedly(Return(true));
     EXPECT_CALL(mRenderEngine, supportsProtectedContent()).WillRepeatedly(Return(false));
     EXPECT_CALL(mRenderEngine, isProtected()).WillRepeatedly(Return(false));
-    EXPECT_CALL(mOutput, generateClientCompositionRequests(_, _, kDefaultOutputDataspace))
+    EXPECT_CALL(mOutput, generateClientCompositionRequests(_, kDefaultOutputDataspace, _))
             .WillRepeatedly(Return(std::vector<LayerFE::LayerSettings>{r1, r2}));
     EXPECT_CALL(mOutput, appendRegionFlashRequests(RegionEq(kDebugRegion), _))
             .WillRepeatedly(Return());
 
     EXPECT_CALL(*mRenderSurface, dequeueBuffer(_)).WillRepeatedly(Return(mOutputBuffer));
-    EXPECT_CALL(mRenderEngine, drawLayers(_, ElementsAre(Pointee(r1), Pointee(r2)), _, false, _, _))
-            .WillOnce(Return(NO_ERROR));
+    EXPECT_CALL(mRenderEngine, drawLayers(_, ElementsAre(r1, r2), _, false, _))
+            .WillOnce(Return(ByMove(
+                    futureOf<renderengine::RenderEngineResult>({NO_ERROR, base::unique_fd()}))));
     EXPECT_CALL(mOutput, setExpensiveRenderingExpected(false));
 
     verify().execute().expectAFenceWasReturned();
@@ -3281,20 +3381,27 @@
     EXPECT_CALL(*mDisplayColorProfile, hasWideColorGamut()).WillRepeatedly(Return(true));
     EXPECT_CALL(mRenderEngine, supportsProtectedContent()).WillRepeatedly(Return(false));
     EXPECT_CALL(mRenderEngine, isProtected()).WillRepeatedly(Return(false));
-    EXPECT_CALL(mOutput, generateClientCompositionRequests(_, _, kDefaultOutputDataspace))
+    EXPECT_CALL(mOutput, generateClientCompositionRequests(_, kDefaultOutputDataspace, _))
             .WillRepeatedly(Return(std::vector<LayerFE::LayerSettings>{r1, r2}));
     EXPECT_CALL(mOutput, appendRegionFlashRequests(RegionEq(kDebugRegion), _))
             .WillRepeatedly(Return());
 
     const auto otherOutputBuffer = std::make_shared<
-            renderengine::ExternalTexture>(new GraphicBuffer(), mRenderEngine,
-                                           renderengine::ExternalTexture::Usage::READABLE |
-                                                   renderengine::ExternalTexture::Usage::WRITEABLE);
+            renderengine::impl::
+                    ExternalTexture>(new GraphicBuffer(), mRenderEngine,
+                                     renderengine::impl::ExternalTexture::Usage::READABLE |
+                                             renderengine::impl::ExternalTexture::Usage::WRITEABLE);
     EXPECT_CALL(*mRenderSurface, dequeueBuffer(_))
             .WillOnce(Return(mOutputBuffer))
             .WillOnce(Return(otherOutputBuffer));
-    EXPECT_CALL(mRenderEngine, drawLayers(_, ElementsAre(Pointee(r1), Pointee(r2)), _, false, _, _))
-            .WillRepeatedly(Return(NO_ERROR));
+    EXPECT_CALL(mRenderEngine, drawLayers(_, ElementsAre(r1, r2), _, false, _))
+            .WillRepeatedly([&](const renderengine::DisplaySettings&,
+                                const std::vector<renderengine::LayerSettings>&,
+                                const std::shared_ptr<renderengine::ExternalTexture>&, const bool,
+                                base::unique_fd&&)
+                                    -> std::future<renderengine::RenderEngineResult> {
+                return futureOf<renderengine::RenderEngineResult>({NO_ERROR, base::unique_fd()});
+            });
 
     verify().execute().expectAFenceWasReturned();
     EXPECT_FALSE(mOutput.mState.reusedClientComposition);
@@ -3316,17 +3423,19 @@
     EXPECT_CALL(*mDisplayColorProfile, hasWideColorGamut()).WillRepeatedly(Return(true));
     EXPECT_CALL(mRenderEngine, supportsProtectedContent()).WillRepeatedly(Return(false));
     EXPECT_CALL(mRenderEngine, isProtected()).WillRepeatedly(Return(false));
-    EXPECT_CALL(mOutput, generateClientCompositionRequests(_, _, kDefaultOutputDataspace))
+    EXPECT_CALL(mOutput, generateClientCompositionRequests(_, kDefaultOutputDataspace, _))
             .WillOnce(Return(std::vector<LayerFE::LayerSettings>{r1, r2}))
             .WillOnce(Return(std::vector<LayerFE::LayerSettings>{r1, r3}));
     EXPECT_CALL(mOutput, appendRegionFlashRequests(RegionEq(kDebugRegion), _))
             .WillRepeatedly(Return());
 
     EXPECT_CALL(*mRenderSurface, dequeueBuffer(_)).WillRepeatedly(Return(mOutputBuffer));
-    EXPECT_CALL(mRenderEngine, drawLayers(_, ElementsAre(Pointee(r1), Pointee(r2)), _, false, _, _))
-            .WillOnce(Return(NO_ERROR));
-    EXPECT_CALL(mRenderEngine, drawLayers(_, ElementsAre(Pointee(r1), Pointee(r3)), _, false, _, _))
-            .WillOnce(Return(NO_ERROR));
+    EXPECT_CALL(mRenderEngine, drawLayers(_, ElementsAre(r1, r2), _, false, _))
+            .WillOnce(Return(ByMove(
+                    futureOf<renderengine::RenderEngineResult>({NO_ERROR, base::unique_fd()}))));
+    EXPECT_CALL(mRenderEngine, drawLayers(_, ElementsAre(r1, r3), _, false, _))
+            .WillOnce(Return(ByMove(
+                    futureOf<renderengine::RenderEngineResult>({NO_ERROR, base::unique_fd()}))));
 
     verify().execute().expectAFenceWasReturned();
     EXPECT_FALSE(mOutput.mState.reusedClientComposition);
@@ -3339,7 +3448,7 @@
     OutputComposeSurfacesTest_UsesExpectedDisplaySettings() {
         EXPECT_CALL(mRenderEngine, supportsProtectedContent()).WillRepeatedly(Return(false));
         EXPECT_CALL(mRenderEngine, isProtected()).WillRepeatedly(Return(false));
-        EXPECT_CALL(mOutput, generateClientCompositionRequests(_, _, kDefaultOutputDataspace))
+        EXPECT_CALL(mOutput, generateClientCompositionRequests(_, kDefaultOutputDataspace, _))
                 .WillRepeatedly(Return(std::vector<LayerFE::LayerSettings>{}));
         EXPECT_CALL(mOutput, appendRegionFlashRequests(RegionEq(kDebugRegion), _))
                 .WillRepeatedly(Return());
@@ -3358,6 +3467,14 @@
         auto andIfUsesHdr(bool used) {
             EXPECT_CALL(*getInstance()->mDisplayColorProfile, hasWideColorGamut())
                     .WillOnce(Return(used));
+            return nextState<OutputWithDisplayBrightnessNits>();
+        }
+    };
+
+    struct OutputWithDisplayBrightnessNits
+          : public CallOrderStateMachineHelper<TestType, OutputWithDisplayBrightnessNits> {
+        auto withDisplayBrightnessNits(float nits) {
+            getInstance()->mOutput.mState.displayBrightnessNits = nits;
             return nextState<SkipColorTransformState>();
         }
     };
@@ -3375,8 +3492,9 @@
     struct ExpectDisplaySettingsState
           : public CallOrderStateMachineHelper<TestType, ExpectDisplaySettingsState> {
         auto thenExpectDisplaySettingsUsed(renderengine::DisplaySettings settings) {
-            EXPECT_CALL(getInstance()->mRenderEngine, drawLayers(settings, _, _, false, _, _))
-                    .WillOnce(Return(NO_ERROR));
+            EXPECT_CALL(getInstance()->mRenderEngine, drawLayers(settings, _, _, false, _))
+                    .WillOnce(Return(ByMove(futureOf<renderengine::RenderEngineResult>(
+                            {NO_ERROR, base::unique_fd()}))));
             return nextState<ExecuteState>();
         }
     };
@@ -3388,10 +3506,36 @@
 TEST_F(OutputComposeSurfacesTest_UsesExpectedDisplaySettings, forHdrMixedComposition) {
     verify().ifMixedCompositionIs(true)
             .andIfUsesHdr(true)
+            .withDisplayBrightnessNits(kUnknownLuminance)
             .andIfSkipColorTransform(false)
-            .thenExpectDisplaySettingsUsed({kDefaultOutputDestinationClip, kDefaultOutputViewport,
-                                            kDefaultMaxLuminance, kDefaultOutputDataspace, mat4(),
-                                            Region::INVALID_REGION, kDefaultOutputOrientationFlags})
+            .thenExpectDisplaySettingsUsed({.physicalDisplay = kDefaultOutputDestinationClip,
+                                            .clip = kDefaultOutputViewport,
+                                            .maxLuminance = kDefaultMaxLuminance,
+                                            .currentLuminanceNits = kDefaultMaxLuminance,
+                                            .outputDataspace = kDefaultOutputDataspace,
+                                            .colorTransform = kDefaultColorTransformMat,
+                                            .deviceHandlesColorTransform = true,
+                                            .orientation = kDefaultOutputOrientationFlags,
+                                            .targetLuminanceNits = kClientTargetLuminanceNits})
+            .execute()
+            .expectAFenceWasReturned();
+}
+
+TEST_F(OutputComposeSurfacesTest_UsesExpectedDisplaySettings,
+       forHdrMixedCompositionWithDisplayBrightness) {
+    verify().ifMixedCompositionIs(true)
+            .andIfUsesHdr(true)
+            .withDisplayBrightnessNits(kDisplayLuminance)
+            .andIfSkipColorTransform(false)
+            .thenExpectDisplaySettingsUsed({.physicalDisplay = kDefaultOutputDestinationClip,
+                                            .clip = kDefaultOutputViewport,
+                                            .maxLuminance = kDefaultMaxLuminance,
+                                            .currentLuminanceNits = kDisplayLuminance,
+                                            .outputDataspace = kDefaultOutputDataspace,
+                                            .colorTransform = kDefaultColorTransformMat,
+                                            .deviceHandlesColorTransform = true,
+                                            .orientation = kDefaultOutputOrientationFlags,
+                                            .targetLuminanceNits = kClientTargetLuminanceNits})
             .execute()
             .expectAFenceWasReturned();
 }
@@ -3399,10 +3543,17 @@
 TEST_F(OutputComposeSurfacesTest_UsesExpectedDisplaySettings, forNonHdrMixedComposition) {
     verify().ifMixedCompositionIs(true)
             .andIfUsesHdr(false)
+            .withDisplayBrightnessNits(kUnknownLuminance)
             .andIfSkipColorTransform(false)
-            .thenExpectDisplaySettingsUsed({kDefaultOutputDestinationClip, kDefaultOutputViewport,
-                                            kDefaultMaxLuminance, kDefaultOutputDataspace, mat4(),
-                                            Region::INVALID_REGION, kDefaultOutputOrientationFlags})
+            .thenExpectDisplaySettingsUsed({.physicalDisplay = kDefaultOutputDestinationClip,
+                                            .clip = kDefaultOutputViewport,
+                                            .maxLuminance = kDefaultMaxLuminance,
+                                            .currentLuminanceNits = kDefaultMaxLuminance,
+                                            .outputDataspace = kDefaultOutputDataspace,
+                                            .colorTransform = kDefaultColorTransformMat,
+                                            .deviceHandlesColorTransform = true,
+                                            .orientation = kDefaultOutputOrientationFlags,
+                                            .targetLuminanceNits = kClientTargetLuminanceNits})
             .execute()
             .expectAFenceWasReturned();
 }
@@ -3410,11 +3561,17 @@
 TEST_F(OutputComposeSurfacesTest_UsesExpectedDisplaySettings, forHdrOnlyClientComposition) {
     verify().ifMixedCompositionIs(false)
             .andIfUsesHdr(true)
+            .withDisplayBrightnessNits(kUnknownLuminance)
             .andIfSkipColorTransform(false)
-            .thenExpectDisplaySettingsUsed({kDefaultOutputDestinationClip, kDefaultOutputViewport,
-                                            kDefaultMaxLuminance, kDefaultOutputDataspace,
-                                            kDefaultColorTransformMat, Region::INVALID_REGION,
-                                            kDefaultOutputOrientationFlags})
+            .thenExpectDisplaySettingsUsed({.physicalDisplay = kDefaultOutputDestinationClip,
+                                            .clip = kDefaultOutputViewport,
+                                            .maxLuminance = kDefaultMaxLuminance,
+                                            .currentLuminanceNits = kDefaultMaxLuminance,
+                                            .outputDataspace = kDefaultOutputDataspace,
+                                            .colorTransform = kDefaultColorTransformMat,
+                                            .deviceHandlesColorTransform = false,
+                                            .orientation = kDefaultOutputOrientationFlags,
+                                            .targetLuminanceNits = kClientTargetLuminanceNits})
             .execute()
             .expectAFenceWasReturned();
 }
@@ -3422,11 +3579,17 @@
 TEST_F(OutputComposeSurfacesTest_UsesExpectedDisplaySettings, forNonHdrOnlyClientComposition) {
     verify().ifMixedCompositionIs(false)
             .andIfUsesHdr(false)
+            .withDisplayBrightnessNits(kUnknownLuminance)
             .andIfSkipColorTransform(false)
-            .thenExpectDisplaySettingsUsed({kDefaultOutputDestinationClip, kDefaultOutputViewport,
-                                            kDefaultMaxLuminance, kDefaultOutputDataspace,
-                                            kDefaultColorTransformMat, Region::INVALID_REGION,
-                                            kDefaultOutputOrientationFlags})
+            .thenExpectDisplaySettingsUsed({.physicalDisplay = kDefaultOutputDestinationClip,
+                                            .clip = kDefaultOutputViewport,
+                                            .maxLuminance = kDefaultMaxLuminance,
+                                            .currentLuminanceNits = kDefaultMaxLuminance,
+                                            .outputDataspace = kDefaultOutputDataspace,
+                                            .colorTransform = kDefaultColorTransformMat,
+                                            .deviceHandlesColorTransform = false,
+                                            .orientation = kDefaultOutputOrientationFlags,
+                                            .targetLuminanceNits = kClientTargetLuminanceNits})
             .execute()
             .expectAFenceWasReturned();
 }
@@ -3435,10 +3598,17 @@
        usesExpectedDisplaySettingsForHdrOnlyClientCompositionWithSkipClientTransform) {
     verify().ifMixedCompositionIs(false)
             .andIfUsesHdr(true)
+            .withDisplayBrightnessNits(kUnknownLuminance)
             .andIfSkipColorTransform(true)
-            .thenExpectDisplaySettingsUsed({kDefaultOutputDestinationClip, kDefaultOutputViewport,
-                                            kDefaultMaxLuminance, kDefaultOutputDataspace, mat4(),
-                                            Region::INVALID_REGION, kDefaultOutputOrientationFlags})
+            .thenExpectDisplaySettingsUsed({.physicalDisplay = kDefaultOutputDestinationClip,
+                                            .clip = kDefaultOutputViewport,
+                                            .maxLuminance = kDefaultMaxLuminance,
+                                            .currentLuminanceNits = kDefaultMaxLuminance,
+                                            .outputDataspace = kDefaultOutputDataspace,
+                                            .colorTransform = kDefaultColorTransformMat,
+                                            .deviceHandlesColorTransform = true,
+                                            .orientation = kDefaultOutputOrientationFlags,
+                                            .targetLuminanceNits = kClientTargetLuminanceNits})
             .execute()
             .expectAFenceWasReturned();
 }
@@ -3474,8 +3644,15 @@
         EXPECT_CALL(mOutput, appendRegionFlashRequests(RegionEq(kDebugRegion), _))
                 .WillRepeatedly(Return());
         EXPECT_CALL(*mRenderSurface, dequeueBuffer(_)).WillRepeatedly(Return(mOutputBuffer));
-        EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, false, _, _))
-                .WillRepeatedly(Return(NO_ERROR));
+        EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, false, _))
+                .WillRepeatedly(
+                        [&](const renderengine::DisplaySettings&,
+                            const std::vector<renderengine::LayerSettings>&,
+                            const std::shared_ptr<renderengine::ExternalTexture>&, const bool,
+                            base::unique_fd&&) -> std::future<renderengine::RenderEngineResult> {
+                            return futureOf<renderengine::RenderEngineResult>(
+                                    {NO_ERROR, base::unique_fd()});
+                        });
     }
 
     Layer mLayer1;
@@ -3527,7 +3704,9 @@
     EXPECT_CALL(*mRenderSurface, setProtected(true));
     // Must happen after setting the protected content state.
     EXPECT_CALL(*mRenderSurface, dequeueBuffer(_)).WillRepeatedly(Return(mOutputBuffer));
-    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, false, _, _)).WillOnce(Return(NO_ERROR));
+    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, false, _))
+            .WillOnce(Return(ByMove(
+                    futureOf<renderengine::RenderEngineResult>({NO_ERROR, base::unique_fd()}))));
 
     mOutput.composeSurfaces(kDebugRegion, kDefaultRefreshArgs);
 }
@@ -3590,14 +3769,17 @@
 TEST_F(OutputComposeSurfacesTest_SetsExpensiveRendering, IfExepensiveOutputDataspaceIsUsed) {
     mOutput.mState.dataspace = kExpensiveOutputDataspace;
 
-    EXPECT_CALL(mOutput, generateClientCompositionRequests(_, _, kExpensiveOutputDataspace))
-            .WillOnce(Return(std::vector<LayerFE::LayerSettings>{}));
+    LayerFE::LayerSettings layerSettings;
+    EXPECT_CALL(mOutput, generateClientCompositionRequests(_, kExpensiveOutputDataspace, _))
+            .WillOnce(Return(std::vector<LayerFE::LayerSettings>{layerSettings}));
 
     // For this test, we also check the call order of key functions.
     InSequence seq;
 
     EXPECT_CALL(mOutput, setExpensiveRenderingExpected(true));
-    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, false, _, _)).WillOnce(Return(NO_ERROR));
+    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, false, _))
+            .WillOnce(Return(ByMove(
+                    futureOf<renderengine::RenderEngineResult>({NO_ERROR, base::unique_fd()}))));
 
     mOutput.composeSurfaces(kDebugRegion, kDefaultRefreshArgs);
 }
@@ -3613,9 +3795,11 @@
         EXPECT_CALL(mLayer.outputLayer,
                     writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false, 0,
                                     /*zIsOverridden*/ false, /*isPeekingThrough*/ false));
-        EXPECT_CALL(mOutput, generateClientCompositionRequests(_, _, kDefaultOutputDataspace))
+        EXPECT_CALL(mOutput, generateClientCompositionRequests(_, kDefaultOutputDataspace, _))
                 .WillOnce(Return(std::vector<LayerFE::LayerSettings>{}));
-        EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, false, _, _)).WillOnce(Return(NO_ERROR));
+        EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, false, _))
+                .WillOnce(Return(ByMove(futureOf<renderengine::RenderEngineResult>(
+                        {NO_ERROR, base::unique_fd()}))));
         EXPECT_CALL(mOutput, getOutputLayerCount()).WillRepeatedly(Return(1u));
         EXPECT_CALL(mOutput, getOutputLayerOrderedByZByIndex(0u))
                 .WillRepeatedly(Return(&mLayer.outputLayer));
@@ -3652,11 +3836,11 @@
 struct GenerateClientCompositionRequestsTest : public testing::Test {
     struct OutputPartialMock : public OutputPartialMockBase {
         // compositionengine::Output overrides
-        std::vector<LayerFE::LayerSettings> generateClientCompositionRequests(
-                bool supportsProtectedContent, Region& clearRegion,
-                ui::Dataspace dataspace) override {
+        std::vector<LayerFE::LayerSettings> generateClientCompositionRequestsHelper(
+            bool supportsProtectedContent, ui::Dataspace dataspace) {
+            std::vector<LayerFE*> ignore;
             return impl::Output::generateClientCompositionRequests(supportsProtectedContent,
-                                                                   clearRegion, dataspace);
+                                                                   dataspace, ignore);
         }
     };
 
@@ -3683,6 +3867,8 @@
         mOutput.setRenderSurfaceForTest(std::unique_ptr<RenderSurface>(mRenderSurface));
     }
 
+    static constexpr float kLayerWhitePointNits = 200.f;
+
     mock::DisplayColorProfile* mDisplayColorProfile = new StrictMock<mock::DisplayColorProfile>();
     mock::RenderSurface* mRenderSurface = new StrictMock<mock::RenderSurface>();
     StrictMock<OutputPartialMock> mOutput;
@@ -3691,12 +3877,12 @@
 struct GenerateClientCompositionRequestsTest_ThreeLayers
       : public GenerateClientCompositionRequestsTest {
     GenerateClientCompositionRequestsTest_ThreeLayers() {
-        mOutput.mState.orientedDisplaySpace.content = kDisplayFrame;
-        mOutput.mState.layerStackSpace.content = kDisplayViewport;
-        mOutput.mState.displaySpace.content = kDisplayDestinationClip;
+        mOutput.mState.orientedDisplaySpace.setContent(kDisplayFrame);
+        mOutput.mState.layerStackSpace.setContent(kDisplayViewport);
+        mOutput.mState.displaySpace.setContent(kDisplayDestinationClip);
         mOutput.mState.transform =
                 ui::Transform{ui::Transform::toRotationFlags(kDisplayOrientation)};
-        mOutput.mState.displaySpace.orientation = kDisplayOrientation;
+        mOutput.mState.displaySpace.setOrientation(kDisplayOrientation);
         mOutput.mState.needsFiltering = false;
         mOutput.mState.isSecure = false;
 
@@ -3722,6 +3908,7 @@
 
     static constexpr ui::Rotation kDisplayOrientation = ui::ROTATION_0;
     static constexpr ui::Dataspace kDisplayDataspace = ui::Dataspace::UNKNOWN;
+    static constexpr float kLayerWhitePointNits = 200.f;
 
     static const Rect kDisplayFrame;
     static const Rect kDisplayViewport;
@@ -3740,11 +3927,9 @@
     EXPECT_CALL(mLayers[1].mOutputLayer, requiresClientComposition()).WillOnce(Return(false));
     EXPECT_CALL(mLayers[2].mOutputLayer, requiresClientComposition()).WillOnce(Return(false));
 
-    Region accumClearRegion(Rect(10, 11, 12, 13));
-    auto requests = mOutput.generateClientCompositionRequests(false /* supportsProtectedContent */,
-                                                              accumClearRegion, kDisplayDataspace);
+    auto requests = mOutput.generateClientCompositionRequestsHelper(false /* supportsProtectedContent */,
+                                                              kDisplayDataspace);
     EXPECT_EQ(0u, requests.size());
-    EXPECT_THAT(accumClearRegion, RegionEq(Region(Rect(10, 11, 12, 13))));
 }
 
 TEST_F(GenerateClientCompositionRequestsTest_ThreeLayers, requiresVisibleRegionAfterViewportClip) {
@@ -3752,11 +3937,9 @@
     mLayers[1].mOutputLayerState.visibleRegion = Region(Rect(4000, 0, 4010, 10));
     mLayers[2].mOutputLayerState.visibleRegion = Region(Rect(-10, -10, 0, 0));
 
-    Region accumClearRegion(Rect(10, 11, 12, 13));
-    auto requests = mOutput.generateClientCompositionRequests(false /* supportsProtectedContent */,
-                                                              accumClearRegion, kDisplayDataspace);
+    auto requests = mOutput.generateClientCompositionRequestsHelper(false /* supportsProtectedContent */,
+                                                              kDisplayDataspace);
     EXPECT_EQ(0u, requests.size());
-    EXPECT_THAT(accumClearRegion, RegionEq(Region(Rect(10, 11, 12, 13))));
 }
 
 TEST_F(GenerateClientCompositionRequestsTest_ThreeLayers, gathersClientCompositionRequests) {
@@ -3771,16 +3954,13 @@
             .WillOnce(Return(std::vector<LayerFE::LayerSettings>(
                     {mShadowSettings, mLayers[2].mLayerSettings})));
 
-    Region accumClearRegion(Rect(10, 11, 12, 13));
-    auto requests = mOutput.generateClientCompositionRequests(false /* supportsProtectedContent */,
-                                                              accumClearRegion, kDisplayDataspace);
+    auto requests = mOutput.generateClientCompositionRequestsHelper(false /* supportsProtectedContent */,
+                                                              kDisplayDataspace);
     ASSERT_EQ(3u, requests.size());
     EXPECT_EQ(mLayers[1].mLayerSettings, requests[0]);
     EXPECT_EQ(mShadowSettings, requests[1]);
     EXPECT_EQ(mLayers[2].mLayerSettings, requests[2]);
 
-    EXPECT_THAT(accumClearRegion, RegionEq(Region(Rect(10, 11, 12, 13))));
-
     // Check that a timestamp was set for the layers that generated requests
     EXPECT_TRUE(0 == mLayers[0].mOutputLayerState.clientCompositionTimestamp);
     EXPECT_TRUE(0 != mLayers[1].mOutputLayerState.clientCompositionTimestamp);
@@ -3811,16 +3991,13 @@
             .WillOnce(Return(std::vector<LayerFE::LayerSettings>(
                     {mShadowSettings, mLayers[2].mLayerSettings})));
 
-    Region accumClearRegion(Rect(10, 11, 12, 13));
-    auto requests = mOutput.generateClientCompositionRequests(false /* supportsProtectedContent */,
-                                                              accumClearRegion, kDisplayDataspace);
+    auto requests = mOutput.generateClientCompositionRequestsHelper(false /* supportsProtectedContent */,
+                                                              kDisplayDataspace);
     ASSERT_EQ(3u, requests.size());
     EXPECT_EQ(mLayers[1].mLayerSettings, requests[0]);
     EXPECT_EQ(mShadowSettings, requests[1]);
     EXPECT_EQ(mLayers[2].mLayerSettings, requests[2]);
 
-    EXPECT_THAT(accumClearRegion, RegionEq(Region(Rect(10, 11, 12, 13))));
-
     // Check that a timestamp was set for the layers that generated requests
     EXPECT_TRUE(0 == mLayers[0].mOutputLayerState.clientCompositionTimestamp);
     EXPECT_TRUE(0 != mLayers[1].mOutputLayerState.clientCompositionTimestamp);
@@ -3844,13 +4021,10 @@
     EXPECT_CALL(*mLayers[2].mLayerFE, prepareClientCompositionList(_))
             .WillOnce(Return(std::vector<LayerFE::LayerSettings>({mLayers[2].mLayerSettings})));
 
-    Region accumClearRegion(Rect(10, 11, 12, 13));
-    auto requests = mOutput.generateClientCompositionRequests(false /* supportsProtectedContent */,
-                                                              accumClearRegion, kDisplayDataspace);
+    auto requests = mOutput.generateClientCompositionRequestsHelper(false /* supportsProtectedContent */,
+                                                              kDisplayDataspace);
     ASSERT_EQ(1u, requests.size());
     EXPECT_EQ(mLayers[2].mLayerSettings, requests[0]);
-
-    EXPECT_THAT(accumClearRegion, RegionEq(Region(Rect(10, 11, 12, 13))));
 }
 
 TEST_F(GenerateClientCompositionRequestsTest_ThreeLayers,
@@ -3870,13 +4044,10 @@
     EXPECT_CALL(*mLayers[2].mLayerFE, prepareClientCompositionList(_))
             .WillOnce(Return(std::vector<LayerFE::LayerSettings>({mLayers[2].mLayerSettings})));
 
-    Region accumClearRegion(Rect(10, 11, 12, 13));
-    auto requests = mOutput.generateClientCompositionRequests(false /* supportsProtectedContent */,
-                                                              accumClearRegion, kDisplayDataspace);
+    auto requests = mOutput.generateClientCompositionRequestsHelper(false /* supportsProtectedContent */,
+                                                              kDisplayDataspace);
     ASSERT_EQ(1u, requests.size());
     EXPECT_EQ(mLayers[2].mLayerSettings, requests[0]);
-
-    EXPECT_THAT(accumClearRegion, RegionEq(Region(Rect(10, 11, 12, 13))));
 }
 
 TEST_F(GenerateClientCompositionRequestsTest_ThreeLayers, clearsHWCLayersIfOpaqueAndNotFirst) {
@@ -3897,32 +4068,30 @@
     mLayers[0].mLayerFEState.isOpaque = true;
     mLayers[1].mLayerFEState.isOpaque = true;
     mLayers[2].mLayerFEState.isOpaque = true;
-    Region accumClearRegion(Rect(10, 11, 12, 13));
-    Region stubRegion;
 
     compositionengine::LayerFE::ClientCompositionTargetSettings layer1TargetSettings{
             Region(kDisplayFrame),
-            false,      /* needs filtering */
-            false,      /* secure */
-            false,      /* supports protected content */
-            stubRegion, /* clear region */
+            false, /* needs filtering */
+            false, /* secure */
+            false, /* supports protected content */
             kDisplayViewport,
             kDisplayDataspace,
             false /* realContentIsVisible */,
             true /* clearContent */,
             compositionengine::LayerFE::ClientCompositionTargetSettings::BlurSetting::Enabled,
+            kLayerWhitePointNits,
     };
     compositionengine::LayerFE::ClientCompositionTargetSettings layer2TargetSettings{
             Region(kDisplayFrame),
             false, /* needs filtering */
             false, /* secure */
             false, /* supports protected content */
-            accumClearRegion,
             kDisplayViewport,
             kDisplayDataspace,
             true /* realContentIsVisible */,
             false /* clearContent */,
             compositionengine::LayerFE::ClientCompositionTargetSettings::BlurSetting::Enabled,
+            kLayerWhitePointNits,
     };
 
     LayerFE::LayerSettings mBlackoutSettings = mLayers[1].mLayerSettings;
@@ -3936,16 +4105,14 @@
     EXPECT_CALL(*mLayers[2].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer2TargetSettings))))
             .WillOnce(Return(std::vector<LayerFE::LayerSettings>({mLayers[2].mLayerSettings})));
 
-    auto requests = mOutput.generateClientCompositionRequests(false /* supportsProtectedContent */,
-                                                              accumClearRegion, kDisplayDataspace);
+    auto requests = mOutput.generateClientCompositionRequestsHelper(false /* supportsProtectedContent */,
+                                                              kDisplayDataspace);
     ASSERT_EQ(2u, requests.size());
 
     // The second layer is expected to be rendered as alpha=0 black with no blending
     EXPECT_EQ(mBlackoutSettings, requests[0]);
 
     EXPECT_EQ(mLayers[2].mLayerSettings, requests[1]);
-
-    EXPECT_THAT(accumClearRegion, RegionEq(Region(Rect(10, 11, 12, 13))));
 }
 
 TEST_F(GenerateClientCompositionRequestsTest_ThreeLayers,
@@ -3954,43 +4121,41 @@
     mLayers[1].mOutputLayerState.visibleRegion = Region(Rect(-10, -10, 30, 30));
     mLayers[2].mOutputLayerState.visibleRegion = Region(Rect(-10, 0, 40, 4000));
 
-    Region accumClearRegion(Rect(10, 11, 12, 13));
-
     compositionengine::LayerFE::ClientCompositionTargetSettings layer0TargetSettings{
             Region(Rect(10, 10, 20, 20)),
             false, /* needs filtering */
             false, /* secure */
             false, /* supports protected content */
-            accumClearRegion,
             kDisplayViewport,
             kDisplayDataspace,
             true /* realContentIsVisible */,
             false /* clearContent */,
             compositionengine::LayerFE::ClientCompositionTargetSettings::BlurSetting::Enabled,
+            kLayerWhitePointNits,
     };
     compositionengine::LayerFE::ClientCompositionTargetSettings layer1TargetSettings{
             Region(Rect(0, 0, 30, 30)),
             false, /* needs filtering */
             false, /* secure */
             false, /* supports protected content */
-            accumClearRegion,
             kDisplayViewport,
             kDisplayDataspace,
             true /* realContentIsVisible */,
             false /* clearContent */,
             compositionengine::LayerFE::ClientCompositionTargetSettings::BlurSetting::Enabled,
+            kLayerWhitePointNits,
     };
     compositionengine::LayerFE::ClientCompositionTargetSettings layer2TargetSettings{
             Region(Rect(0, 0, 40, 201)),
             false, /* needs filtering */
             false, /* secure */
             false, /* supports protected content */
-            accumClearRegion,
             kDisplayViewport,
             kDisplayDataspace,
             true /* realContentIsVisible */,
             false /* clearContent */,
             compositionengine::LayerFE::ClientCompositionTargetSettings::BlurSetting::Enabled,
+            kLayerWhitePointNits,
     };
 
     EXPECT_CALL(*mLayers[0].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer0TargetSettings))))
@@ -4001,8 +4166,8 @@
             .WillOnce(Return(std::vector<LayerFE::LayerSettings>()));
 
     static_cast<void>(
-            mOutput.generateClientCompositionRequests(false /* supportsProtectedContent */,
-                                                      accumClearRegion, kDisplayDataspace));
+            mOutput.generateClientCompositionRequestsHelper(false /* supportsProtectedContent */,
+                                                      kDisplayDataspace));
 }
 
 TEST_F(GenerateClientCompositionRequestsTest_ThreeLayers,
@@ -4010,43 +4175,41 @@
     mOutput.mState.needsFiltering = false;
     EXPECT_CALL(mLayers[0].mOutputLayer, needsFiltering()).WillRepeatedly(Return(true));
 
-    Region accumClearRegion(Rect(10, 11, 12, 13));
-
     compositionengine::LayerFE::ClientCompositionTargetSettings layer0TargetSettings{
             Region(kDisplayFrame),
             true,  /* needs filtering */
             false, /* secure */
             false, /* supports protected content */
-            accumClearRegion,
             kDisplayViewport,
             kDisplayDataspace,
             true /* realContentIsVisible */,
             false /* clearContent */,
             compositionengine::LayerFE::ClientCompositionTargetSettings::BlurSetting::Enabled,
+            kLayerWhitePointNits,
     };
     compositionengine::LayerFE::ClientCompositionTargetSettings layer1TargetSettings{
             Region(kDisplayFrame),
             false, /* needs filtering */
             false, /* secure */
             false, /* supports protected content */
-            accumClearRegion,
             kDisplayViewport,
             kDisplayDataspace,
             true /* realContentIsVisible */,
             false /* clearContent */,
             compositionengine::LayerFE::ClientCompositionTargetSettings::BlurSetting::Enabled,
+            kLayerWhitePointNits,
     };
     compositionengine::LayerFE::ClientCompositionTargetSettings layer2TargetSettings{
             Region(kDisplayFrame),
             false, /* needs filtering */
             false, /* secure */
             false, /* supports protected content */
-            accumClearRegion,
             kDisplayViewport,
             kDisplayDataspace,
             true /* realContentIsVisible */,
             false /* clearContent */,
             compositionengine::LayerFE::ClientCompositionTargetSettings::BlurSetting::Enabled,
+            kLayerWhitePointNits,
     };
 
     EXPECT_CALL(*mLayers[0].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer0TargetSettings))))
@@ -4057,8 +4220,8 @@
             .WillOnce(Return(std::vector<LayerFE::LayerSettings>()));
 
     static_cast<void>(
-            mOutput.generateClientCompositionRequests(false /* supportsProtectedContent */,
-                                                      accumClearRegion, kDisplayDataspace));
+            mOutput.generateClientCompositionRequestsHelper(false /* supportsProtectedContent */,
+                                                            kDisplayDataspace));
 }
 
 TEST_F(GenerateClientCompositionRequestsTest_ThreeLayers,
@@ -4066,44 +4229,41 @@
     mOutput.mState.needsFiltering = true;
     EXPECT_CALL(mLayers[0].mOutputLayer, needsFiltering()).WillRepeatedly(Return(true));
 
-    Region accumClearRegion(Rect(10, 11, 12, 13));
-
     compositionengine::LayerFE::ClientCompositionTargetSettings layer0TargetSettings{
             Region(kDisplayFrame),
             true,  /* needs filtering */
             false, /* secure */
             false, /* supports protected content */
-            accumClearRegion,
             kDisplayViewport,
             kDisplayDataspace,
             true /* realContentIsVisible */,
             false /* clearContent */,
             compositionengine::LayerFE::ClientCompositionTargetSettings::BlurSetting::Enabled,
-
+            kLayerWhitePointNits,
     };
     compositionengine::LayerFE::ClientCompositionTargetSettings layer1TargetSettings{
             Region(kDisplayFrame),
             true,  /* needs filtering */
             false, /* secure */
             false, /* supports protected content */
-            accumClearRegion,
             kDisplayViewport,
             kDisplayDataspace,
             true /* realContentIsVisible */,
             false /* clearContent */,
             compositionengine::LayerFE::ClientCompositionTargetSettings::BlurSetting::Enabled,
+            kLayerWhitePointNits,
     };
     compositionengine::LayerFE::ClientCompositionTargetSettings layer2TargetSettings{
             Region(kDisplayFrame),
             true,  /* needs filtering */
             false, /* secure */
             false, /* supports protected content */
-            accumClearRegion,
             kDisplayViewport,
             kDisplayDataspace,
             true /* realContentIsVisible */,
             false /* clearContent */,
             compositionengine::LayerFE::ClientCompositionTargetSettings::BlurSetting::Enabled,
+            kLayerWhitePointNits,
     };
 
     EXPECT_CALL(*mLayers[0].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer0TargetSettings))))
@@ -4114,51 +4274,49 @@
             .WillOnce(Return(std::vector<LayerFE::LayerSettings>()));
 
     static_cast<void>(
-            mOutput.generateClientCompositionRequests(false /* supportsProtectedContent */,
-                                                      accumClearRegion, kDisplayDataspace));
+            mOutput.generateClientCompositionRequestsHelper(false /* supportsProtectedContent */,
+                                                            kDisplayDataspace));
 }
 
 TEST_F(GenerateClientCompositionRequestsTest_ThreeLayers,
        wholeOutputSecurityUsedToGenerateRequests) {
     mOutput.mState.isSecure = true;
 
-    Region accumClearRegion(Rect(10, 11, 12, 13));
-
     compositionengine::LayerFE::ClientCompositionTargetSettings layer0TargetSettings{
             Region(kDisplayFrame),
             false, /* needs filtering */
             true,  /* secure */
             false, /* supports protected content */
-            accumClearRegion,
             kDisplayViewport,
             kDisplayDataspace,
             true /* realContentIsVisible */,
             false /* clearContent */,
             compositionengine::LayerFE::ClientCompositionTargetSettings::BlurSetting::Enabled,
+            kLayerWhitePointNits,
     };
     compositionengine::LayerFE::ClientCompositionTargetSettings layer1TargetSettings{
             Region(kDisplayFrame),
             false, /* needs filtering */
             true,  /* secure */
             false, /* supports protected content */
-            accumClearRegion,
             kDisplayViewport,
             kDisplayDataspace,
             true /* realContentIsVisible */,
             false /* clearContent */,
             compositionengine::LayerFE::ClientCompositionTargetSettings::BlurSetting::Enabled,
+            kLayerWhitePointNits,
     };
     compositionengine::LayerFE::ClientCompositionTargetSettings layer2TargetSettings{
             Region(kDisplayFrame),
             false, /* needs filtering */
             true,  /* secure */
             false, /* supports protected content */
-            accumClearRegion,
             kDisplayViewport,
             kDisplayDataspace,
             true /* realContentIsVisible */,
             false /* clearContent */,
             compositionengine::LayerFE::ClientCompositionTargetSettings::BlurSetting::Enabled,
+            kLayerWhitePointNits,
     };
 
     EXPECT_CALL(*mLayers[0].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer0TargetSettings))))
@@ -4169,49 +4327,47 @@
             .WillOnce(Return(std::vector<LayerFE::LayerSettings>()));
 
     static_cast<void>(
-            mOutput.generateClientCompositionRequests(false /* supportsProtectedContent */,
-                                                      accumClearRegion, kDisplayDataspace));
+            mOutput.generateClientCompositionRequestsHelper(false /* supportsProtectedContent */,
+                                                            kDisplayDataspace));
 }
 
 TEST_F(GenerateClientCompositionRequestsTest_ThreeLayers,
        protectedContentSupportUsedToGenerateRequests) {
-    Region accumClearRegion(Rect(10, 11, 12, 13));
-
     compositionengine::LayerFE::ClientCompositionTargetSettings layer0TargetSettings{
             Region(kDisplayFrame),
             false, /* needs filtering */
             false, /* secure */
             true,  /* supports protected content */
-            accumClearRegion,
             kDisplayViewport,
             kDisplayDataspace,
             true /* realContentIsVisible */,
             false /* clearContent */,
             compositionengine::LayerFE::ClientCompositionTargetSettings::BlurSetting::Enabled,
+            kLayerWhitePointNits,
     };
     compositionengine::LayerFE::ClientCompositionTargetSettings layer1TargetSettings{
             Region(kDisplayFrame),
             false, /* needs filtering */
             false, /* secure */
             true,  /* supports protected content */
-            accumClearRegion,
             kDisplayViewport,
             kDisplayDataspace,
             true /* realContentIsVisible */,
             false /* clearContent */,
             compositionengine::LayerFE::ClientCompositionTargetSettings::BlurSetting::Enabled,
+            kLayerWhitePointNits,
     };
     compositionengine::LayerFE::ClientCompositionTargetSettings layer2TargetSettings{
             Region(kDisplayFrame),
             false, /* needs filtering */
             false, /* secure */
             true,  /* supports protected content */
-            accumClearRegion,
             kDisplayViewport,
             kDisplayDataspace,
             true /* realContentIsVisible */,
             false /* clearContent */,
             compositionengine::LayerFE::ClientCompositionTargetSettings::BlurSetting::Enabled,
+            kLayerWhitePointNits,
     };
 
     EXPECT_CALL(*mLayers[0].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer0TargetSettings))))
@@ -4221,8 +4377,7 @@
     EXPECT_CALL(*mLayers[2].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer2TargetSettings))))
             .WillOnce(Return(std::vector<LayerFE::LayerSettings>()));
 
-    static_cast<void>(mOutput.generateClientCompositionRequests(true /* supportsProtectedContent */,
-                                                                accumClearRegion,
+    static_cast<void>(mOutput.generateClientCompositionRequestsHelper(true /* supportsProtectedContent */,
                                                                 kDisplayDataspace));
 }
 
@@ -4343,11 +4498,11 @@
     const ui::Rotation kPortraitOrientation = ui::ROTATION_90;
     constexpr ui::Dataspace kOutputDataspace = ui::Dataspace::DISPLAY_P3;
 
-    mOutput.mState.orientedDisplaySpace.content = kPortraitFrame;
-    mOutput.mState.layerStackSpace.content = kPortraitViewport;
-    mOutput.mState.displaySpace.content = kPortraitDestinationClip;
+    mOutput.mState.orientedDisplaySpace.setContent(kPortraitFrame);
+    mOutput.mState.layerStackSpace.setContent(kPortraitViewport);
+    mOutput.mState.displaySpace.setContent(kPortraitDestinationClip);
     mOutput.mState.transform = ui::Transform{ui::Transform::toRotationFlags(kPortraitOrientation)};
-    mOutput.mState.displaySpace.orientation = kPortraitOrientation;
+    mOutput.mState.displaySpace.setOrientation(kPortraitOrientation);
     mOutput.mState.needsFiltering = false;
     mOutput.mState.isSecure = true;
 
@@ -4370,19 +4525,17 @@
     EXPECT_CALL(mOutput, getOutputLayerOrderedByZByIndex(1u))
             .WillRepeatedly(Return(&rightLayer.mOutputLayer));
 
-    Region accumClearRegion(Rect(10, 11, 12, 13));
-
     compositionengine::LayerFE::ClientCompositionTargetSettings leftLayerSettings{
             Region(Rect(0, 0, 1000, 1000)),
             false, /* needs filtering */
             true,  /* secure */
             true,  /* supports protected content */
-            accumClearRegion,
             kPortraitViewport,
             kOutputDataspace,
             true /* realContentIsVisible */,
             false /* clearContent */,
             compositionengine::LayerFE::ClientCompositionTargetSettings::BlurSetting::Enabled,
+            kLayerWhitePointNits,
     };
 
     EXPECT_CALL(leftLayer.mOutputLayer, requiresClientComposition()).WillRepeatedly(Return(true));
@@ -4395,12 +4548,12 @@
             false, /* needs filtering */
             true,  /* secure */
             true,  /* supports protected content */
-            accumClearRegion,
             kPortraitViewport,
             kOutputDataspace,
             true /* realContentIsVisible */,
             false /* clearContent */,
             compositionengine::LayerFE::ClientCompositionTargetSettings::BlurSetting::Enabled,
+            kLayerWhitePointNits,
     };
 
     EXPECT_CALL(rightLayer.mOutputLayer, requiresClientComposition()).WillRepeatedly(Return(true));
@@ -4409,8 +4562,8 @@
             .WillOnce(Return(std::vector<LayerFE::LayerSettings>({rightLayer.mLayerSettings})));
 
     constexpr bool supportsProtectedContent = true;
-    auto requests = mOutput.generateClientCompositionRequests(supportsProtectedContent,
-                                                              accumClearRegion, kOutputDataspace);
+    auto requests =
+        mOutput.generateClientCompositionRequestsHelper(supportsProtectedContent, kOutputDataspace);
     ASSERT_EQ(2u, requests.size());
     EXPECT_EQ(leftLayer.mLayerSettings, requests[0]);
     EXPECT_EQ(rightLayer.mLayerSettings, requests[1]);
@@ -4423,18 +4576,17 @@
     const Region kShadowRegion = Region(kContentWithShadow).subtract(kContent);
     const Region kPartialShadowRegion = Region(kContentWithShadow).subtract(Rect(40, 40, 60, 80));
 
-    Region accumClearRegion(Rect(10, 11, 12, 13));
     compositionengine::LayerFE::ClientCompositionTargetSettings layer2Settings{
             Region(Rect(60, 40, 70, 80)).merge(Rect(40, 80, 70, 90)), /* visible region */
             false,                                                    /* needs filtering */
             false,                                                    /* secure */
             false, /* supports protected content */
-            accumClearRegion,
             kDisplayViewport,
             kDisplayDataspace,
             false /* realContentIsVisible */,
             false /* clearContent */,
             compositionengine::LayerFE::ClientCompositionTargetSettings::BlurSetting::Enabled,
+            kLayerWhitePointNits,
     };
 
     LayerFE::LayerSettings mShadowSettings;
@@ -4448,8 +4600,8 @@
     EXPECT_CALL(*mLayers[2].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer2Settings))))
             .WillOnce(Return(std::vector<LayerFE::LayerSettings>({mShadowSettings})));
 
-    auto requests = mOutput.generateClientCompositionRequests(false /* supportsProtectedContent */,
-                                                              accumClearRegion, kDisplayDataspace);
+    auto requests = mOutput.generateClientCompositionRequestsHelper(false /* supportsProtectedContent */,
+                                                              kDisplayDataspace);
     ASSERT_EQ(1u, requests.size());
 
     EXPECT_EQ(mShadowSettings, requests[0]);
@@ -4469,18 +4621,17 @@
     mLayers[2].mOutputLayerState.visibleRegion = kPartialContentWithPartialShadowRegion;
     mLayers[2].mOutputLayerState.shadowRegion = kShadowRegion;
 
-    Region accumClearRegion(Rect(10, 11, 12, 13));
     compositionengine::LayerFE::ClientCompositionTargetSettings layer2Settings{
             Region(Rect(50, 40, 70, 80)).merge(Rect(40, 80, 70, 90)), /* visible region */
             false,                                                    /* needs filtering */
             false,                                                    /* secure */
             false, /* supports protected content */
-            accumClearRegion,
             kDisplayViewport,
             kDisplayDataspace,
             true /* realContentIsVisible */,
             false /* clearContent */,
             compositionengine::LayerFE::ClientCompositionTargetSettings::BlurSetting::Enabled,
+            kLayerWhitePointNits,
     };
 
     EXPECT_CALL(mLayers[0].mOutputLayer, requiresClientComposition()).WillOnce(Return(false));
@@ -4489,8 +4640,8 @@
             .WillOnce(Return(std::vector<LayerFE::LayerSettings>(
                     {mShadowSettings, mLayers[2].mLayerSettings})));
 
-    auto requests = mOutput.generateClientCompositionRequests(false /* supportsProtectedContent */,
-                                                              accumClearRegion, kDisplayDataspace);
+    auto requests = mOutput.generateClientCompositionRequestsHelper(false /* supportsProtectedContent */,
+                                                              kDisplayDataspace);
     ASSERT_EQ(2u, requests.size());
 
     EXPECT_EQ(mShadowSettings, requests[0]);
diff --git a/services/surfaceflinger/CompositionEngine/tests/ProjectionSpaceTest.cpp b/services/surfaceflinger/CompositionEngine/tests/ProjectionSpaceTest.cpp
index 704f5a8..19b3928 100644
--- a/services/surfaceflinger/CompositionEngine/tests/ProjectionSpaceTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/ProjectionSpaceTest.cpp
@@ -53,40 +53,40 @@
 
 TEST(ProjectionSpaceTest, getTransformToSelfIsIdentity) {
     ProjectionSpace space;
-    space.content = Rect(100, 200);
-    space.bounds = Rect(100, 200);
+    space.setContent(Rect(100, 200));
+    space.setBounds(ui::Size(100, 200));
 
     const ui::Transform identity;
     for (int rotation = 0; rotation <= 3; rotation++) {
-        space.orientation = ui::Rotation(rotation);
+        space.setOrientation(ui::Rotation(rotation));
         EXPECT_EQ(space.getTransform(space), identity);
     }
 }
 
 TEST(ProjectionSpaceTest, getTransformWhenTranslationIsNeeded) {
     ProjectionSpace source;
-    source.content = Rect(10, 10, 20, 20);
-    source.bounds = Rect(100, 200);
+    source.setContent(Rect(10, 10, 20, 20));
+    source.setBounds(ui::Size(100, 200));
 
     ProjectionSpace dest;
-    dest.content = Rect(10, 20, 30, 20);
-    dest.bounds = source.bounds;
+    dest.setContent(Rect(10, 20, 30, 20));
+    dest.setBounds(source.getBounds());
 
     const auto transform = source.getTransform(dest);
-    EXPECT_EQ(transform.transform(source.content), dest.content);
+    EXPECT_EQ(transform.transform(source.getContent()), dest.getContent());
 }
 
 TEST(ProjectionSpaceTest, getTransformWhenScaleIsNeeded) {
     ProjectionSpace source;
-    source.content = Rect(0, 0, 20, 20);
-    source.bounds = Rect(100, 200);
+    source.setContent(Rect(0, 0, 20, 20));
+    source.setBounds(ui::Size(100, 200));
 
     ProjectionSpace dest;
-    dest.content = Rect(0, 0, 40, 30);
-    dest.bounds = source.bounds;
+    dest.setContent(Rect(0, 0, 40, 30));
+    dest.setBounds(source.getBounds());
 
     const auto transform = source.getTransform(dest);
-    EXPECT_EQ(transform.transform(source.content), dest.content);
+    EXPECT_EQ(transform.transform(source.getContent()), dest.getContent());
 }
 
 TEST(ProjectionSpaceTest, getSideStripTest) {
@@ -99,7 +99,7 @@
 
 void testTransform(const ProjectionSpace& source, const ProjectionSpace& dest) {
     const auto transform = source.getTransform(dest);
-    EXPECT_EQ(transform.transform(source.content), dest.content)
+    EXPECT_EQ(transform.transform(source.getContent()), dest.getContent())
             << "Source content doesn't map to dest content when projecting " << to_string(source)
             << " onto " << to_string(dest);
 
@@ -113,8 +113,8 @@
     //      |     |                |       *
     //      +-----+                +-------*
     // source(ROTATION_0)      dest (ROTATION_90)
-    const auto sourceStrip = getSideStrip(source.content, source.orientation);
-    const auto destStrip = getSideStrip(dest.content, dest.orientation);
+    const auto sourceStrip = getSideStrip(source.getContent(), source.getOrientation());
+    const auto destStrip = getSideStrip(dest.getContent(), dest.getOrientation());
     ASSERT_NE(sourceStrip, Rect::INVALID_RECT);
     ASSERT_NE(destStrip, Rect::INVALID_RECT);
     const auto mappedStrip = transform.transform(sourceStrip);
@@ -126,16 +126,16 @@
 
 TEST(ProjectionSpaceTest, getTransformWithOrienations) {
     ProjectionSpace source;
-    source.bounds = Rect(12, 13, 678, 789);
-    source.content = Rect(40, 50, 234, 343);
+    source.setBounds(ui::Size(666, 776));
+    source.setContent(Rect(40, 50, 234, 343));
     ProjectionSpace dest;
-    dest.bounds = Rect(17, 18, 879, 564);
-    dest.content = Rect(43, 52, 432, 213);
+    dest.setBounds(ui::Size(862, 546));
+    dest.setContent(Rect(43, 52, 432, 213));
 
     for (int sourceRot = 0; sourceRot <= 3; sourceRot++) {
-        source.orientation = ui::Rotation(sourceRot);
+        source.setOrientation(ui::Rotation(sourceRot));
         for (int destRot = 0; destRot <= 3; destRot++) {
-            dest.orientation = ui::Rotation(destRot);
+            dest.setOrientation(ui::Rotation(destRot));
             testTransform(source, dest);
         }
     }
diff --git a/services/surfaceflinger/CompositionEngine/tests/RenderSurfaceTest.cpp b/services/surfaceflinger/CompositionEngine/tests/RenderSurfaceTest.cpp
index 5090bb2..e5f9ebf 100644
--- a/services/surfaceflinger/CompositionEngine/tests/RenderSurfaceTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/RenderSurfaceTest.cpp
@@ -27,6 +27,7 @@
 #include <compositionengine/mock/OutputLayer.h>
 #include <gtest/gtest.h>
 #include <renderengine/ExternalTexture.h>
+#include <renderengine/impl/ExternalTexture.h>
 #include <renderengine/mock/RenderEngine.h>
 #include <ui/GraphicBuffer.h>
 
@@ -35,7 +36,7 @@
 
 constexpr int32_t DEFAULT_DISPLAY_WIDTH = 1920;
 constexpr int32_t DEFAULT_DISPLAY_HEIGHT = 1080;
-constexpr DisplayId DEFAULT_DISPLAY_ID = PhysicalDisplayId(123u);
+constexpr DisplayId DEFAULT_DISPLAY_ID = PhysicalDisplayId::fromPort(123u);
 const std::string DEFAULT_DISPLAY_NAME = "Mock Display";
 
 using testing::_;
@@ -201,28 +202,28 @@
  */
 
 TEST_F(RenderSurfaceTest, prepareFrameHandlesMixedComposition) {
-    EXPECT_CALL(*mDisplaySurface, prepareFrame(DisplaySurface::COMPOSITION_MIXED))
+    EXPECT_CALL(*mDisplaySurface, prepareFrame(DisplaySurface::CompositionType::Mixed))
             .WillOnce(Return(NO_ERROR));
 
     mSurface.prepareFrame(true, true);
 }
 
 TEST_F(RenderSurfaceTest, prepareFrameHandlesOnlyGpuComposition) {
-    EXPECT_CALL(*mDisplaySurface, prepareFrame(DisplaySurface::COMPOSITION_GPU))
+    EXPECT_CALL(*mDisplaySurface, prepareFrame(DisplaySurface::CompositionType::Gpu))
             .WillOnce(Return(NO_ERROR));
 
     mSurface.prepareFrame(true, false);
 }
 
 TEST_F(RenderSurfaceTest, prepareFrameHandlesOnlyHwcComposition) {
-    EXPECT_CALL(*mDisplaySurface, prepareFrame(DisplaySurface::COMPOSITION_HWC))
+    EXPECT_CALL(*mDisplaySurface, prepareFrame(DisplaySurface::CompositionType::Hwc))
             .WillOnce(Return(NO_ERROR));
 
     mSurface.prepareFrame(false, true);
 }
 
 TEST_F(RenderSurfaceTest, prepareFrameHandlesNoComposition) {
-    EXPECT_CALL(*mDisplaySurface, prepareFrame(DisplaySurface::COMPOSITION_HWC))
+    EXPECT_CALL(*mDisplaySurface, prepareFrame(DisplaySurface::CompositionType::Hwc))
             .WillOnce(Return(NO_ERROR));
 
     mSurface.prepareFrame(false, false);
@@ -251,9 +252,10 @@
 
 TEST_F(RenderSurfaceTest, queueBufferHandlesNoClientComposition) {
     const auto buffer = std::make_shared<
-            renderengine::ExternalTexture>(new GraphicBuffer(), mRenderEngine,
-                                           renderengine::ExternalTexture::Usage::READABLE |
-                                                   renderengine::ExternalTexture::Usage::WRITEABLE);
+            renderengine::impl::
+                    ExternalTexture>(new GraphicBuffer(), mRenderEngine,
+                                     renderengine::impl::ExternalTexture::Usage::READABLE |
+                                             renderengine::impl::ExternalTexture::Usage::WRITEABLE);
     mSurface.mutableTextureForTest() = buffer;
 
     impl::OutputCompositionState state;
@@ -269,8 +271,8 @@
 }
 
 TEST_F(RenderSurfaceTest, queueBufferHandlesClientComposition) {
-    const auto buffer = std::make_shared<renderengine::ExternalTexture>(new GraphicBuffer(),
-                                                                        mRenderEngine, false);
+    const auto buffer = std::make_shared<renderengine::impl::ExternalTexture>(new GraphicBuffer(),
+                                                                              mRenderEngine, false);
     mSurface.mutableTextureForTest() = buffer;
 
     impl::OutputCompositionState state;
@@ -288,8 +290,8 @@
 }
 
 TEST_F(RenderSurfaceTest, queueBufferHandlesFlipClientTargetRequest) {
-    const auto buffer = std::make_shared<renderengine::ExternalTexture>(new GraphicBuffer(),
-                                                                        mRenderEngine, false);
+    const auto buffer = std::make_shared<renderengine::impl::ExternalTexture>(new GraphicBuffer(),
+                                                                              mRenderEngine, false);
     mSurface.mutableTextureForTest() = buffer;
 
     impl::OutputCompositionState state;
@@ -327,8 +329,8 @@
 }
 
 TEST_F(RenderSurfaceTest, queueBufferHandlesNativeWindowQueueBufferFailureOnVirtualDisplay) {
-    const auto buffer = std::make_shared<renderengine::ExternalTexture>(new GraphicBuffer(),
-                                                                        mRenderEngine, false);
+    const auto buffer = std::make_shared<renderengine::impl::ExternalTexture>(new GraphicBuffer(),
+                                                                              mRenderEngine, false);
     mSurface.mutableTextureForTest() = buffer;
 
     impl::OutputCompositionState state;
diff --git a/services/surfaceflinger/CompositionEngine/tests/TestUtils.h b/services/surfaceflinger/CompositionEngine/tests/TestUtils.h
new file mode 100644
index 0000000..c80fde6
--- /dev/null
+++ b/services/surfaceflinger/CompositionEngine/tests/TestUtils.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#include <future>
+
+namespace android::compositionengine {
+namespace {
+
+template <class T>
+std::future<T> futureOf(T obj) {
+    std::promise<T> resultPromise;
+    std::future<T> resultFuture = resultPromise.get_future();
+    resultPromise.set_value(std::move(obj));
+    return resultFuture;
+}
+} // namespace
+} // namespace android::compositionengine
diff --git a/services/surfaceflinger/CompositionEngine/tests/planner/CachedSetTest.cpp b/services/surfaceflinger/CompositionEngine/tests/planner/CachedSetTest.cpp
index ec81322..8a99e4e 100644
--- a/services/surfaceflinger/CompositionEngine/tests/planner/CachedSetTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/planner/CachedSetTest.cpp
@@ -22,11 +22,14 @@
 #include <gmock/gmock-actions.h>
 #include <gtest/gtest.h>
 #include <renderengine/ExternalTexture.h>
+#include <renderengine/mock/FakeExternalTexture.h>
 #include <renderengine/mock/RenderEngine.h>
 #include <ui/GraphicTypes.h>
 #include <utils/Errors.h>
 #include <memory>
 
+#include "tests/TestUtils.h"
+
 namespace android::compositionengine {
 using namespace std::chrono_literals;
 
@@ -53,13 +56,21 @@
 }
 
 MATCHER_P(ClientCompositionTargetSettingsSecureEq, expectedSecureSetting, "") {
-    *result_listener << "ClientCompositionTargetSettings' SecureSettings aren't equal \n";
+    *result_listener << "ClientCompositionTargetSettings' isSecure bits aren't equal \n";
     *result_listener << "expected " << expectedSecureSetting << "\n";
     *result_listener << "actual " << arg.isSecure << "\n";
 
     return expectedSecureSetting == arg.isSecure;
 }
 
+MATCHER_P(ClientCompositionTargetSettingsWhitePointEq, expectedWhitePoint, "") {
+    *result_listener << "ClientCompositionTargetSettings' white points aren't equal \n";
+    *result_listener << "expected " << expectedWhitePoint << "\n";
+    *result_listener << "actual " << arg.whitePointNits << "\n";
+
+    return expectedWhitePoint == arg.whitePointNits;
+}
+
 static const ui::Size kOutputSize = ui::Size(1, 1);
 
 class CachedSetTest : public testing::Test {
@@ -121,7 +132,7 @@
         // set up minimium params needed for rendering
         mOutputState.dataspace = ui::Dataspace::SRGB;
         mOutputState.framebufferSpace = ProjectionSpace(ui::Size(10, 20), Rect(10, 5));
-        mOutputState.framebufferSpace.orientation = ui::ROTATION_90;
+        mOutputState.framebufferSpace.setOrientation(ui::ROTATION_90);
         mOutputState.layerStackSpace = ProjectionSpace(ui::Size(20, 10), Rect(5, 10));
     }
 }
@@ -342,19 +353,19 @@
     clientCompList2.push_back({});
     clientCompList2[0].alpha = 0.75f;
 
-    const auto drawLayers = [&](const renderengine::DisplaySettings& displaySettings,
-                                const std::vector<const renderengine::LayerSettings*>& layers,
-                                const std::shared_ptr<renderengine::ExternalTexture>&, const bool,
-                                base::unique_fd&&, base::unique_fd*) -> size_t {
-        EXPECT_EQ(mOutputState.framebufferSpace.content, displaySettings.physicalDisplay);
-        EXPECT_EQ(mOutputState.layerStackSpace.content, displaySettings.clip);
-        EXPECT_EQ(ui::Transform::toRotationFlags(mOutputState.framebufferSpace.orientation),
+    const auto drawLayers =
+            [&](const renderengine::DisplaySettings& displaySettings,
+                const std::vector<renderengine::LayerSettings>& layers,
+                const std::shared_ptr<renderengine::ExternalTexture>&, const bool,
+                base::unique_fd&&) -> std::future<renderengine::RenderEngineResult> {
+        EXPECT_EQ(mOutputState.framebufferSpace.getContent(), displaySettings.physicalDisplay);
+        EXPECT_EQ(mOutputState.layerStackSpace.getContent(), displaySettings.clip);
+        EXPECT_EQ(ui::Transform::toRotationFlags(mOutputState.framebufferSpace.getOrientation()),
                   displaySettings.orientation);
-        EXPECT_EQ(0.5f, layers[0]->alpha);
-        EXPECT_EQ(0.75f, layers[1]->alpha);
+        EXPECT_EQ(0.5f, layers[0].alpha);
+        EXPECT_EQ(0.75f, layers[1].alpha);
         EXPECT_EQ(ui::Dataspace::SRGB, displaySettings.outputDataspace);
-
-        return NO_ERROR;
+        return futureOf<renderengine::RenderEngineResult>({NO_ERROR, base::unique_fd()});
     };
 
     EXPECT_CALL(*layerFE1,
@@ -363,7 +374,7 @@
     EXPECT_CALL(*layerFE2,
                 prepareClientCompositionList(ClientCompositionTargetSettingsSecureEq(false)))
             .WillOnce(Return(clientCompList2));
-    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _, _)).WillOnce(Invoke(drawLayers));
+    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)).WillOnce(Invoke(drawLayers));
     mOutputState.isSecure = false;
     cachedSet.render(mRenderEngine, mTexturePool, mOutputState);
     expectReadyBuffer(cachedSet);
@@ -394,19 +405,20 @@
     clientCompList2.push_back({});
     clientCompList2[0].alpha = 0.75f;
 
-    const auto drawLayers = [&](const renderengine::DisplaySettings& displaySettings,
-                                const std::vector<const renderengine::LayerSettings*>& layers,
-                                const std::shared_ptr<renderengine::ExternalTexture>&, const bool,
-                                base::unique_fd&&, base::unique_fd*) -> size_t {
-        EXPECT_EQ(mOutputState.framebufferSpace.content, displaySettings.physicalDisplay);
-        EXPECT_EQ(mOutputState.layerStackSpace.content, displaySettings.clip);
-        EXPECT_EQ(ui::Transform::toRotationFlags(mOutputState.framebufferSpace.orientation),
+    const auto drawLayers =
+            [&](const renderengine::DisplaySettings& displaySettings,
+                const std::vector<renderengine::LayerSettings>& layers,
+                const std::shared_ptr<renderengine::ExternalTexture>&, const bool,
+                base::unique_fd&&) -> std::future<renderengine::RenderEngineResult> {
+        EXPECT_EQ(mOutputState.framebufferSpace.getContent(), displaySettings.physicalDisplay);
+        EXPECT_EQ(mOutputState.layerStackSpace.getContent(), displaySettings.clip);
+        EXPECT_EQ(ui::Transform::toRotationFlags(mOutputState.framebufferSpace.getOrientation()),
                   displaySettings.orientation);
-        EXPECT_EQ(0.5f, layers[0]->alpha);
-        EXPECT_EQ(0.75f, layers[1]->alpha);
+        EXPECT_EQ(0.5f, layers[0].alpha);
+        EXPECT_EQ(0.75f, layers[1].alpha);
         EXPECT_EQ(ui::Dataspace::SRGB, displaySettings.outputDataspace);
 
-        return NO_ERROR;
+        return futureOf<renderengine::RenderEngineResult>({NO_ERROR, base::unique_fd()});
     };
 
     EXPECT_CALL(*layerFE1,
@@ -415,7 +427,55 @@
     EXPECT_CALL(*layerFE2,
                 prepareClientCompositionList(ClientCompositionTargetSettingsSecureEq(true)))
             .WillOnce(Return(clientCompList2));
-    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _, _)).WillOnce(Invoke(drawLayers));
+    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)).WillOnce(Invoke(drawLayers));
+    mOutputState.isSecure = true;
+    cachedSet.render(mRenderEngine, mTexturePool, mOutputState);
+    expectReadyBuffer(cachedSet);
+
+    EXPECT_EQ(mOutputState.framebufferSpace, cachedSet.getOutputSpace());
+    EXPECT_EQ(Rect(kOutputSize.width, kOutputSize.height), cachedSet.getTextureBounds());
+
+    // Now check that appending a new cached set properly cleans up RenderEngine resources.
+    CachedSet::Layer& layer3 = *mTestLayers[2]->cachedSetLayer.get();
+    cachedSet.append(CachedSet(layer3));
+}
+
+TEST_F(CachedSetTest, renderWhitePoint) {
+    // Skip the 0th layer to ensure that the bounding box of the layers is offset from (0, 0)
+    CachedSet::Layer& layer1 = *mTestLayers[1]->cachedSetLayer.get();
+    sp<mock::LayerFE> layerFE1 = mTestLayers[1]->layerFE;
+    CachedSet::Layer& layer2 = *mTestLayers[2]->cachedSetLayer.get();
+    sp<mock::LayerFE> layerFE2 = mTestLayers[2]->layerFE;
+
+    CachedSet cachedSet(layer1);
+    cachedSet.append(CachedSet(layer2));
+
+    std::vector<compositionengine::LayerFE::LayerSettings> clientCompList1;
+    clientCompList1.push_back({});
+
+    std::vector<compositionengine::LayerFE::LayerSettings> clientCompList2;
+    clientCompList2.push_back({});
+
+    mOutputState.displayBrightnessNits = 400.f;
+
+    const auto drawLayers =
+            [&](const renderengine::DisplaySettings& displaySettings,
+                const std::vector<renderengine::LayerSettings>&,
+                const std::shared_ptr<renderengine::ExternalTexture>&, const bool,
+                base::unique_fd&&) -> std::future<renderengine::RenderEngineResult> {
+        EXPECT_EQ(mOutputState.displayBrightnessNits, displaySettings.targetLuminanceNits);
+        return futureOf<renderengine::RenderEngineResult>({NO_ERROR, base::unique_fd()});
+    };
+
+    EXPECT_CALL(*layerFE1,
+                prepareClientCompositionList(ClientCompositionTargetSettingsWhitePointEq(
+                        mOutputState.displayBrightnessNits)))
+            .WillOnce(Return(clientCompList1));
+    EXPECT_CALL(*layerFE2,
+                prepareClientCompositionList(ClientCompositionTargetSettingsWhitePointEq(
+                        mOutputState.displayBrightnessNits)))
+            .WillOnce(Return(clientCompList2));
+    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)).WillOnce(Invoke(drawLayers));
     mOutputState.isSecure = true;
     cachedSet.render(mRenderEngine, mTexturePool, mOutputState);
     expectReadyBuffer(cachedSet);
@@ -448,24 +508,25 @@
 
     mOutputState.framebufferSpace = ProjectionSpace(ui::Size(10, 20), Rect(2, 3, 10, 5));
 
-    const auto drawLayers = [&](const renderengine::DisplaySettings& displaySettings,
-                                const std::vector<const renderengine::LayerSettings*>& layers,
-                                const std::shared_ptr<renderengine::ExternalTexture>&, const bool,
-                                base::unique_fd&&, base::unique_fd*) -> size_t {
-        EXPECT_EQ(mOutputState.framebufferSpace.content, displaySettings.physicalDisplay);
-        EXPECT_EQ(mOutputState.layerStackSpace.content, displaySettings.clip);
-        EXPECT_EQ(ui::Transform::toRotationFlags(mOutputState.framebufferSpace.orientation),
+    const auto drawLayers =
+            [&](const renderengine::DisplaySettings& displaySettings,
+                const std::vector<renderengine::LayerSettings>& layers,
+                const std::shared_ptr<renderengine::ExternalTexture>&, const bool,
+                base::unique_fd&&) -> std::future<renderengine::RenderEngineResult> {
+        EXPECT_EQ(mOutputState.framebufferSpace.getContent(), displaySettings.physicalDisplay);
+        EXPECT_EQ(mOutputState.layerStackSpace.getContent(), displaySettings.clip);
+        EXPECT_EQ(ui::Transform::toRotationFlags(mOutputState.framebufferSpace.getOrientation()),
                   displaySettings.orientation);
-        EXPECT_EQ(0.5f, layers[0]->alpha);
-        EXPECT_EQ(0.75f, layers[1]->alpha);
+        EXPECT_EQ(0.5f, layers[0].alpha);
+        EXPECT_EQ(0.75f, layers[1].alpha);
         EXPECT_EQ(ui::Dataspace::SRGB, displaySettings.outputDataspace);
 
-        return NO_ERROR;
+        return futureOf<renderengine::RenderEngineResult>({NO_ERROR, base::unique_fd()});
     };
 
     EXPECT_CALL(*layerFE1, prepareClientCompositionList(_)).WillOnce(Return(clientCompList1));
     EXPECT_CALL(*layerFE2, prepareClientCompositionList(_)).WillOnce(Return(clientCompList2));
-    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _, _)).WillOnce(Invoke(drawLayers));
+    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)).WillOnce(Invoke(drawLayers));
     cachedSet.render(mRenderEngine, mTexturePool, mOutputState);
     expectReadyBuffer(cachedSet);
 
@@ -478,6 +539,8 @@
 
 TEST_F(CachedSetTest, holePunch_requiresBuffer) {
     CachedSet::Layer& layer1 = *mTestLayers[0]->cachedSetLayer.get();
+    auto& layerFECompositionState = mTestLayers[0]->layerFECompositionState;
+    layerFECompositionState.blendMode = hal::BlendMode::NONE;
     sp<mock::LayerFE> layerFE1 = mTestLayers[0]->layerFE;
 
     CachedSet cachedSet(layer1);
@@ -488,7 +551,9 @@
 
 TEST_F(CachedSetTest, holePunch_requiresRoundedCorners) {
     CachedSet::Layer& layer1 = *mTestLayers[0]->cachedSetLayer.get();
-    mTestLayers[0]->layerFECompositionState.buffer = sp<GraphicBuffer>::make();
+    auto& layerFECompositionState = mTestLayers[0]->layerFECompositionState;
+    layerFECompositionState.buffer = sp<GraphicBuffer>::make();
+    layerFECompositionState.blendMode = hal::BlendMode::NONE;
 
     CachedSet cachedSet(layer1);
 
@@ -497,7 +562,9 @@
 
 TEST_F(CachedSetTest, holePunch_requiresSingleLayer) {
     CachedSet::Layer& layer1 = *mTestLayers[0]->cachedSetLayer.get();
-    mTestLayers[0]->layerFECompositionState.buffer = sp<GraphicBuffer>::make();
+    auto& layerFECompositionState = mTestLayers[0]->layerFECompositionState;
+    layerFECompositionState.buffer = sp<GraphicBuffer>::make();
+    layerFECompositionState.blendMode = hal::BlendMode::NONE;
     sp<mock::LayerFE> layerFE = mTestLayers[0]->layerFE;
     EXPECT_CALL(*layerFE, hasRoundedCorners()).WillRepeatedly(Return(true));
 
@@ -514,7 +581,9 @@
     mTestLayers[0]->layerState->update(&mTestLayers[0]->outputLayer);
 
     CachedSet::Layer& layer = *mTestLayers[0]->cachedSetLayer.get();
-    mTestLayers[0]->layerFECompositionState.buffer = sp<GraphicBuffer>::make();
+    auto& layerFECompositionState = mTestLayers[0]->layerFECompositionState;
+    layerFECompositionState.buffer = sp<GraphicBuffer>::make();
+    layerFECompositionState.blendMode = hal::BlendMode::NONE;
     sp<mock::LayerFE> layerFE = mTestLayers[0]->layerFE;
 
     CachedSet cachedSet(layer);
@@ -528,7 +597,22 @@
     mTestLayers[0]->layerState->update(&mTestLayers[0]->outputLayer);
 
     CachedSet::Layer& layer = *mTestLayers[0]->cachedSetLayer.get();
-    mTestLayers[0]->layerFECompositionState.buffer = sp<GraphicBuffer>::make();
+    auto& layerFECompositionState = mTestLayers[0]->layerFECompositionState;
+    layerFECompositionState.buffer = sp<GraphicBuffer>::make();
+    layerFECompositionState.blendMode = hal::BlendMode::NONE;
+    sp<mock::LayerFE> layerFE = mTestLayers[0]->layerFE;
+
+    CachedSet cachedSet(layer);
+    EXPECT_CALL(*layerFE, hasRoundedCorners()).WillRepeatedly(Return(true));
+
+    EXPECT_FALSE(cachedSet.requiresHolePunch());
+}
+
+TEST_F(CachedSetTest, holePunch_requiresNoBlending) {
+    CachedSet::Layer& layer = *mTestLayers[0]->cachedSetLayer.get();
+    auto& layerFECompositionState = mTestLayers[0]->layerFECompositionState;
+    layerFECompositionState.buffer = sp<GraphicBuffer>::make();
+    layerFECompositionState.blendMode = hal::BlendMode::PREMULTIPLIED;
     sp<mock::LayerFE> layerFE = mTestLayers[0]->layerFE;
 
     CachedSet cachedSet(layer);
@@ -539,7 +623,9 @@
 
 TEST_F(CachedSetTest, requiresHolePunch) {
     CachedSet::Layer& layer = *mTestLayers[0]->cachedSetLayer.get();
-    mTestLayers[0]->layerFECompositionState.buffer = sp<GraphicBuffer>::make();
+    auto& layerFECompositionState = mTestLayers[0]->layerFECompositionState;
+    layerFECompositionState.buffer = sp<GraphicBuffer>::make();
+    layerFECompositionState.blendMode = hal::BlendMode::NONE;
     sp<mock::LayerFE> layerFE = mTestLayers[0]->layerFE;
 
     CachedSet cachedSet(layer);
@@ -553,6 +639,7 @@
     sp<mock::LayerFE> layerFE = mTestLayers[0]->layerFE;
     auto& layerFECompositionState = mTestLayers[0]->layerFECompositionState;
     layerFECompositionState.buffer = sp<GraphicBuffer>::make();
+    layerFECompositionState.blendMode = hal::BlendMode::NONE;
     layerFECompositionState.forceClientComposition = true;
 
     CachedSet cachedSet(layer);
@@ -642,41 +729,44 @@
     std::vector<compositionengine::LayerFE::LayerSettings> clientCompList3;
     clientCompList3.push_back({});
 
-    clientCompList3[0].source.buffer.buffer = std::make_shared<
-            renderengine::ExternalTexture>(sp<GraphicBuffer>::make(), mRenderEngine,
-                                           renderengine::ExternalTexture::READABLE);
+    clientCompList3[0].source.buffer.buffer =
+            std::make_shared<renderengine::mock::FakeExternalTexture>(1U /*width*/, 1U /*height*/,
+                                                                      1ULL /* bufferId */,
+                                                                      HAL_PIXEL_FORMAT_RGBA_8888,
+                                                                      0ULL /*usage*/);
 
     EXPECT_CALL(*layerFE1, prepareClientCompositionList(_)).WillOnce(Return(clientCompList1));
     EXPECT_CALL(*layerFE2, prepareClientCompositionList(_)).WillOnce(Return(clientCompList2));
     EXPECT_CALL(*layerFE3, prepareClientCompositionList(_)).WillOnce(Return(clientCompList3));
 
-    const auto drawLayers = [&](const renderengine::DisplaySettings&,
-                                const std::vector<const renderengine::LayerSettings*>& layers,
-                                const std::shared_ptr<renderengine::ExternalTexture>&, const bool,
-                                base::unique_fd&&, base::unique_fd*) -> size_t {
+    const auto drawLayers =
+            [&](const renderengine::DisplaySettings&,
+                const std::vector<renderengine::LayerSettings>& layers,
+                const std::shared_ptr<renderengine::ExternalTexture>&, const bool,
+                base::unique_fd&&) -> std::future<renderengine::RenderEngineResult> {
         // If the highlight layer is enabled, it will increase the size by 1.
         // We're interested in the third layer either way.
         EXPECT_GE(layers.size(), 4u);
         {
-            const auto* holePunchSettings = layers[3];
-            EXPECT_EQ(nullptr, holePunchSettings->source.buffer.buffer);
-            EXPECT_EQ(half3(0.0f, 0.0f, 0.0f), holePunchSettings->source.solidColor);
-            EXPECT_TRUE(holePunchSettings->disableBlending);
-            EXPECT_EQ(0.0f, holePunchSettings->alpha);
+            const auto holePunchSettings = layers[3];
+            EXPECT_EQ(nullptr, holePunchSettings.source.buffer.buffer);
+            EXPECT_EQ(half3(0.0f, 0.0f, 0.0f), holePunchSettings.source.solidColor);
+            EXPECT_TRUE(holePunchSettings.disableBlending);
+            EXPECT_EQ(0.0f, holePunchSettings.alpha);
         }
 
         {
-            const auto* holePunchBackgroundSettings = layers[0];
-            EXPECT_EQ(nullptr, holePunchBackgroundSettings->source.buffer.buffer);
-            EXPECT_EQ(half3(0.0f, 0.0f, 0.0f), holePunchBackgroundSettings->source.solidColor);
-            EXPECT_FALSE(holePunchBackgroundSettings->disableBlending);
-            EXPECT_EQ(1.0f, holePunchBackgroundSettings->alpha);
+            const auto holePunchBackgroundSettings = layers[0];
+            EXPECT_EQ(nullptr, holePunchBackgroundSettings.source.buffer.buffer);
+            EXPECT_EQ(half3(0.0f, 0.0f, 0.0f), holePunchBackgroundSettings.source.solidColor);
+            EXPECT_FALSE(holePunchBackgroundSettings.disableBlending);
+            EXPECT_EQ(1.0f, holePunchBackgroundSettings.alpha);
         }
 
-        return NO_ERROR;
+        return futureOf<renderengine::RenderEngineResult>({NO_ERROR, base::unique_fd()});
     };
 
-    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _, _)).WillOnce(Invoke(drawLayers));
+    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)).WillOnce(Invoke(drawLayers));
     cachedSet.render(mRenderEngine, mTexturePool, mOutputState);
 }
 
@@ -710,34 +800,35 @@
     EXPECT_CALL(*layerFE2, prepareClientCompositionList(_)).WillOnce(Return(clientCompList2));
     EXPECT_CALL(*layerFE3, prepareClientCompositionList(_)).WillOnce(Return(clientCompList3));
 
-    const auto drawLayers = [&](const renderengine::DisplaySettings&,
-                                const std::vector<const renderengine::LayerSettings*>& layers,
-                                const std::shared_ptr<renderengine::ExternalTexture>&, const bool,
-                                base::unique_fd&&, base::unique_fd*) -> size_t {
+    const auto drawLayers =
+            [&](const renderengine::DisplaySettings&,
+                const std::vector<renderengine::LayerSettings>& layers,
+                const std::shared_ptr<renderengine::ExternalTexture>&, const bool,
+                base::unique_fd&&) -> std::future<renderengine::RenderEngineResult> {
         // If the highlight layer is enabled, it will increase the size by 1.
         // We're interested in the third layer either way.
         EXPECT_GE(layers.size(), 4u);
 
         {
-            const auto* holePunchSettings = layers[3];
-            EXPECT_EQ(nullptr, holePunchSettings->source.buffer.buffer);
-            EXPECT_EQ(half3(0.0f, 0.0f, 0.0f), holePunchSettings->source.solidColor);
-            EXPECT_TRUE(holePunchSettings->disableBlending);
-            EXPECT_EQ(0.0f, holePunchSettings->alpha);
+            const auto holePunchSettings = layers[3];
+            EXPECT_EQ(nullptr, holePunchSettings.source.buffer.buffer);
+            EXPECT_EQ(half3(0.0f, 0.0f, 0.0f), holePunchSettings.source.solidColor);
+            EXPECT_TRUE(holePunchSettings.disableBlending);
+            EXPECT_EQ(0.0f, holePunchSettings.alpha);
         }
 
         {
-            const auto* holePunchBackgroundSettings = layers[0];
-            EXPECT_EQ(nullptr, holePunchBackgroundSettings->source.buffer.buffer);
-            EXPECT_EQ(half3(0.0f, 0.0f, 0.0f), holePunchBackgroundSettings->source.solidColor);
-            EXPECT_FALSE(holePunchBackgroundSettings->disableBlending);
-            EXPECT_EQ(1.0f, holePunchBackgroundSettings->alpha);
+            const auto holePunchBackgroundSettings = layers[0];
+            EXPECT_EQ(nullptr, holePunchBackgroundSettings.source.buffer.buffer);
+            EXPECT_EQ(half3(0.0f, 0.0f, 0.0f), holePunchBackgroundSettings.source.solidColor);
+            EXPECT_FALSE(holePunchBackgroundSettings.disableBlending);
+            EXPECT_EQ(1.0f, holePunchBackgroundSettings.alpha);
         }
 
-        return NO_ERROR;
+        return futureOf<renderengine::RenderEngineResult>({NO_ERROR, base::unique_fd()});
     };
 
-    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _, _)).WillOnce(Invoke(drawLayers));
+    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)).WillOnce(Invoke(drawLayers));
     cachedSet.render(mRenderEngine, mTexturePool, mOutputState);
 }
 
@@ -839,9 +930,11 @@
     std::vector<compositionengine::LayerFE::LayerSettings> clientCompList3;
     clientCompList3.push_back({});
 
-    clientCompList3[0].source.buffer.buffer = std::make_shared<
-            renderengine::ExternalTexture>(sp<GraphicBuffer>::make(), mRenderEngine,
-                                           renderengine::ExternalTexture::READABLE);
+    clientCompList3[0].source.buffer.buffer =
+            std::make_shared<renderengine::mock::FakeExternalTexture>(1U /*width*/, 1U /*height*/,
+                                                                      1ULL /* bufferId */,
+                                                                      HAL_PIXEL_FORMAT_RGBA_8888,
+                                                                      0ULL /*usage*/);
 
     EXPECT_CALL(*layerFE1,
                 prepareClientCompositionList(ClientCompositionTargetSettingsBlurSettingsEq(
@@ -859,22 +952,23 @@
                                 BackgroundBlurOnly)))
             .WillOnce(Return(clientCompList3));
 
-    const auto drawLayers = [&](const renderengine::DisplaySettings&,
-                                const std::vector<const renderengine::LayerSettings*>& layers,
-                                const std::shared_ptr<renderengine::ExternalTexture>&, const bool,
-                                base::unique_fd&&, base::unique_fd*) -> int32_t {
+    const auto drawLayers =
+            [&](const renderengine::DisplaySettings&,
+                const std::vector<renderengine::LayerSettings>& layers,
+                const std::shared_ptr<renderengine::ExternalTexture>&, const bool,
+                base::unique_fd&&) -> std::future<renderengine::RenderEngineResult> {
         // If the highlight layer is enabled, it will increase the size by 1.
         // We're interested in the third layer either way.
         EXPECT_GE(layers.size(), 3u);
-        const auto* blurSettings = layers[2];
-        EXPECT_TRUE(blurSettings->skipContentDraw);
-        EXPECT_EQ(half3(0.0f, 0.0f, 0.0f), blurSettings->source.solidColor);
-        EXPECT_EQ(0.0f, blurSettings->alpha);
+        const auto blurSettings = layers[2];
+        EXPECT_TRUE(blurSettings.skipContentDraw);
+        EXPECT_EQ(half3(0.0f, 0.0f, 0.0f), blurSettings.source.solidColor);
+        EXPECT_EQ(0.0f, blurSettings.alpha);
 
-        return NO_ERROR;
+        return futureOf<renderengine::RenderEngineResult>({NO_ERROR, base::unique_fd()});
     };
 
-    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _, _)).WillOnce(Invoke(drawLayers));
+    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)).WillOnce(Invoke(drawLayers));
     cachedSet.render(mRenderEngine, mTexturePool, mOutputState);
 }
 
diff --git a/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp b/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp
index a28fb2c..96c28c9 100644
--- a/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp
@@ -23,9 +23,12 @@
 #include <gtest/gtest.h>
 #include <renderengine/ExternalTexture.h>
 #include <renderengine/LayerSettings.h>
+#include <renderengine/impl/ExternalTexture.h>
 #include <renderengine/mock/RenderEngine.h>
 #include <chrono>
 
+#include "tests/TestUtils.h"
+
 namespace android::compositionengine {
 using namespace std::chrono_literals;
 using impl::planner::CachedSet;
@@ -141,7 +144,7 @@
         // set up minimium params needed for rendering
         mOutputState.dataspace = ui::Dataspace::SRGB;
         mOutputState.framebufferSpace = ProjectionSpace(ui::Size(10, 20), Rect(10, 5));
-        mOutputState.framebufferSpace.orientation = ui::ROTATION_90;
+        mOutputState.framebufferSpace.setOrientation(ui::ROTATION_90);
     }
 }
 
@@ -167,7 +170,9 @@
 
 void FlattenerTest::expectAllLayersFlattened(const std::vector<const LayerState*>& layers) {
     // layers would be flattened but the buffer would not be overridden
-    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _, _)).WillOnce(Return(NO_ERROR));
+    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _))
+            .WillOnce(Return(ByMove(
+                    futureOf<renderengine::RenderEngineResult>({NO_ERROR, base::unique_fd()}))));
 
     initializeOverrideBuffer(layers);
     EXPECT_EQ(getNonBufferHash(layers),
@@ -220,6 +225,22 @@
     mFlattener->renderCachedSets(mOutputState, std::nullopt);
 }
 
+TEST_F(FlattenerTest, flattenLayers_ActiveLayersWithLowFpsAreFlattened) {
+    mTestLayers[0]->layerFECompositionState.fps = Flattener::kFpsActiveThreshold / 2;
+    mTestLayers[1]->layerFECompositionState.fps = Flattener::kFpsActiveThreshold;
+
+    auto& layerState1 = mTestLayers[0]->layerState;
+    auto& layerState2 = mTestLayers[1]->layerState;
+
+    const std::vector<const LayerState*> layers = {
+            layerState1.get(),
+            layerState2.get(),
+    };
+
+    initializeFlattener(layers);
+    expectAllLayersFlattened(layers);
+}
+
 TEST_F(FlattenerTest, flattenLayers_basicFlatten) {
     auto& layerState1 = mTestLayers[0]->layerState;
     auto& layerState2 = mTestLayers[1]->layerState;
@@ -401,7 +422,9 @@
     // caleed for Layer2 and Layer3
     layerState1->resetFramesSinceBufferUpdate();
 
-    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _, _)).WillOnce(Return(NO_ERROR));
+    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _))
+            .WillOnce(Return(ByMove(
+                    futureOf<renderengine::RenderEngineResult>({NO_ERROR, base::unique_fd()}))));
     initializeOverrideBuffer(layers);
     EXPECT_EQ(getNonBufferHash(layers),
               mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
@@ -423,7 +446,9 @@
     layerState1->incrementFramesSinceBufferUpdate();
     mTime += 200ms;
 
-    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _, _)).WillOnce(Return(NO_ERROR));
+    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _))
+            .WillOnce(Return(ByMove(
+                    futureOf<renderengine::RenderEngineResult>({NO_ERROR, base::unique_fd()}))));
     initializeOverrideBuffer(layers);
     EXPECT_NE(getNonBufferHash(layers),
               mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
@@ -474,7 +499,9 @@
     // called for Layer1 and Layer2
     layerState3->resetFramesSinceBufferUpdate();
 
-    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _, _)).WillOnce(Return(NO_ERROR));
+    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _))
+            .WillOnce(Return(ByMove(
+                    futureOf<renderengine::RenderEngineResult>({NO_ERROR, base::unique_fd()}))));
     initializeOverrideBuffer(layers);
     EXPECT_EQ(getNonBufferHash(layers),
               mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
@@ -487,11 +514,13 @@
     EXPECT_EQ(nullptr, overrideBuffer5);
 
     // Layers 1 and 2 will be flattened a new drawFrame would be called for Layer4 and Layer5
-    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _, _)).WillOnce(Return(NO_ERROR));
+    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _))
+            .WillOnce(Return(ByMove(
+                    futureOf<renderengine::RenderEngineResult>({NO_ERROR, base::unique_fd()}))));
     initializeOverrideBuffer(layers);
     EXPECT_NE(getNonBufferHash(layers),
               mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
-    mOutputState.framebufferSpace.orientation = ui::ROTATION_90;
+    mOutputState.framebufferSpace.setOrientation(ui::ROTATION_90);
     mFlattener->renderCachedSets(mOutputState, std::nullopt);
 
     EXPECT_NE(nullptr, overrideBuffer1);
@@ -504,7 +533,7 @@
     initializeOverrideBuffer(layers);
     EXPECT_NE(getNonBufferHash(layers),
               mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
-    mOutputState.framebufferSpace.orientation = ui::ROTATION_180;
+    mOutputState.framebufferSpace.setOrientation(ui::ROTATION_180);
     mFlattener->renderCachedSets(mOutputState, std::nullopt);
 
     EXPECT_NE(nullptr, overrideBuffer1);
@@ -515,8 +544,9 @@
 
     layerState3->incrementFramesSinceBufferUpdate();
     mTime += 200ms;
-
-    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _, _)).WillOnce(Return(NO_ERROR));
+    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _))
+            .WillOnce(Return(ByMove(
+                    futureOf<renderengine::RenderEngineResult>({NO_ERROR, base::unique_fd()}))));
     initializeOverrideBuffer(layers);
     EXPECT_NE(getNonBufferHash(layers),
               mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
@@ -531,7 +561,7 @@
     initializeOverrideBuffer(layers);
     EXPECT_NE(getNonBufferHash(layers),
               mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
-    mOutputState.framebufferSpace.orientation = ui::ROTATION_270;
+    mOutputState.framebufferSpace.setOrientation(ui::ROTATION_270);
     mFlattener->renderCachedSets(mOutputState, std::nullopt);
 
     EXPECT_NE(nullptr, overrideBuffer1);
@@ -570,7 +600,9 @@
               mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
 
     // This will render a CachedSet.
-    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _, _)).WillOnce(Return(NO_ERROR));
+    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _))
+            .WillOnce(Return(ByMove(
+                    futureOf<renderengine::RenderEngineResult>({NO_ERROR, base::unique_fd()}))));
     mFlattener->renderCachedSets(mOutputState, std::nullopt);
 
     // We've rendered a CachedSet, but we haven't merged it in.
@@ -580,7 +612,7 @@
 
     // This time we merge the CachedSet in, so we have a new hash, and we should
     // only have two sets.
-    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _, _)).Times(0);
+    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)).Times(0);
     initializeOverrideBuffer(layers);
     EXPECT_NE(getNonBufferHash(layers),
               mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
@@ -601,6 +633,7 @@
 
     auto& layerState3 = mTestLayers[2]->layerState;
     const auto& overrideBuffer3 = layerState3->getOutputLayer()->getState().overrideInfo.buffer;
+    mTestLayers[2]->layerFECompositionState.blendMode = hal::BlendMode::NONE;
 
     EXPECT_CALL(*mTestLayers[2]->layerFE, hasRoundedCorners()).WillRepeatedly(Return(true));
 
@@ -608,9 +641,10 @@
             LayerFE::LayerSettings{},
     };
     clientCompositionList[0].source.buffer.buffer = std::make_shared<
-            renderengine::ExternalTexture>(mTestLayers[2]->layerFECompositionState.buffer,
-                                           mRenderEngine,
-                                           renderengine::ExternalTexture::Usage::READABLE);
+            renderengine::impl::ExternalTexture>(mTestLayers[2]->layerFECompositionState.buffer,
+                                                 mRenderEngine,
+                                                 renderengine::impl::ExternalTexture::Usage::
+                                                         READABLE);
     EXPECT_CALL(*mTestLayers[2]->layerFE, prepareClientCompositionList(_))
             .WillOnce(Return(clientCompositionList));
 
@@ -632,7 +666,9 @@
               mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
 
     // This will render a CachedSet.
-    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _, _)).WillOnce(Return(NO_ERROR));
+    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _))
+            .WillOnce(Return(ByMove(
+                    futureOf<renderengine::RenderEngineResult>({NO_ERROR, base::unique_fd()}))));
     mFlattener->renderCachedSets(mOutputState, std::nullopt);
 
     // We've rendered a CachedSet, but we haven't merged it in.
@@ -642,7 +678,7 @@
 
     // This time we merge the CachedSet in, so we have a new hash, and we should
     // only have two sets.
-    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _, _)).Times(0);
+    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)).Times(0);
     initializeOverrideBuffer(layers);
     EXPECT_NE(getNonBufferHash(layers),
               mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
@@ -660,6 +696,149 @@
     EXPECT_EQ(peekThroughLayer1, peekThroughLayer2);
 }
 
+// A test that verifies the hole puch optimization can be done on a single layer.
+TEST_F(FlattenerTest, flattenLayers_holePunchSingleLayer) {
+    mTestLayers[0]->outputLayerCompositionState.displayFrame = Rect(0, 0, 5, 5);
+
+    // An opaque static background
+    auto& layerState0 = mTestLayers[0]->layerState;
+    const auto& overrideBuffer0 = layerState0->getOutputLayer()->getState().overrideInfo.buffer;
+
+    // a rounded updating layer
+    auto& layerState1 = mTestLayers[1]->layerState;
+    const auto& overrideBuffer1 = layerState1->getOutputLayer()->getState().overrideInfo.buffer;
+    mTestLayers[1]->layerFECompositionState.blendMode = hal::BlendMode::NONE;
+
+    EXPECT_CALL(*mTestLayers[1]->layerFE, hasRoundedCorners()).WillRepeatedly(Return(true));
+
+    std::vector<LayerFE::LayerSettings> clientCompositionList = {
+            LayerFE::LayerSettings{},
+    };
+    clientCompositionList[0].source.buffer.buffer = std::make_shared<
+            renderengine::impl::ExternalTexture>(mTestLayers[1]->layerFECompositionState.buffer,
+                                                 mRenderEngine,
+                                                 renderengine::impl::ExternalTexture::Usage::
+                                                         READABLE);
+    EXPECT_CALL(*mTestLayers[1]->layerFE, prepareClientCompositionList(_))
+            .WillOnce(Return(clientCompositionList));
+
+    const std::vector<const LayerState*> layers = {
+            layerState0.get(),
+            layerState1.get(),
+    };
+
+    initializeFlattener(layers);
+
+    // layer 1 satisfies every condition in CachedSet::requiresHolePunch()
+    mTime += 200ms;
+    layerState1->resetFramesSinceBufferUpdate(); // it is updating
+
+    initializeOverrideBuffer(layers);
+    // Expect no cache invalidation the first time (there's no cache yet)
+    EXPECT_EQ(getNonBufferHash(layers),
+              mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
+
+    // This will render a CachedSet of layer 0. Though it is just one layer, it satisfies the
+    // exception that there would be a hole punch above it.
+    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _))
+            .WillOnce(Return(ByMove(
+                    futureOf<renderengine::RenderEngineResult>({NO_ERROR, base::unique_fd()}))));
+    mFlattener->renderCachedSets(mOutputState, std::nullopt);
+
+    // We've rendered a CachedSet, but we haven't merged it in.
+    EXPECT_EQ(nullptr, overrideBuffer0);
+
+    // This time we merge the CachedSet in and we should still have only two sets.
+    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)).Times(0);
+    initializeOverrideBuffer(layers);
+    EXPECT_EQ(getNonBufferHash(layers),
+              mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
+    mFlattener->renderCachedSets(mOutputState, std::nullopt);
+
+    EXPECT_NE(nullptr, overrideBuffer0); // got overridden
+    EXPECT_EQ(nullptr, overrideBuffer1); // did not
+
+    // expect 0's peek though layer to be 1's output layer
+    const auto* peekThroughLayer0 =
+            layerState0->getOutputLayer()->getState().overrideInfo.peekThroughLayer;
+    const auto* peekThroughLayer1 =
+            layerState1->getOutputLayer()->getState().overrideInfo.peekThroughLayer;
+    EXPECT_EQ(&mTestLayers[1]->outputLayer, peekThroughLayer0);
+    EXPECT_EQ(nullptr, peekThroughLayer1);
+}
+
+TEST_F(FlattenerTest, flattenLayers_holePunchSingleColorLayer) {
+    mTestLayers[0]->outputLayerCompositionState.displayFrame = Rect(0, 0, 5, 5);
+    mTestLayers[0]->layerFECompositionState.color = half4(255.f, 0.f, 0.f, 255.f);
+    mTestLayers[0]->layerFECompositionState.buffer = nullptr;
+
+    // An opaque static background
+    auto& layerState0 = mTestLayers[0]->layerState;
+    const auto& overrideBuffer0 = layerState0->getOutputLayer()->getState().overrideInfo.buffer;
+
+    // a rounded updating layer
+    auto& layerState1 = mTestLayers[1]->layerState;
+    const auto& overrideBuffer1 = layerState1->getOutputLayer()->getState().overrideInfo.buffer;
+    mTestLayers[1]->layerFECompositionState.blendMode = hal::BlendMode::NONE;
+
+    EXPECT_CALL(*mTestLayers[1]->layerFE, hasRoundedCorners()).WillRepeatedly(Return(true));
+
+    std::vector<LayerFE::LayerSettings> clientCompositionList = {
+            LayerFE::LayerSettings{},
+    };
+    clientCompositionList[0].source.buffer.buffer = std::make_shared<
+            renderengine::impl::ExternalTexture>(mTestLayers[1]->layerFECompositionState.buffer,
+                                                 mRenderEngine,
+                                                 renderengine::impl::ExternalTexture::Usage::
+                                                         READABLE);
+    EXPECT_CALL(*mTestLayers[1]->layerFE, prepareClientCompositionList(_))
+            .WillOnce(Return(clientCompositionList));
+
+    const std::vector<const LayerState*> layers = {
+            layerState0.get(),
+            layerState1.get(),
+    };
+
+    initializeFlattener(layers);
+
+    // layer 1 satisfies every condition in CachedSet::requiresHolePunch()
+    mTime += 200ms;
+    layerState1->resetFramesSinceBufferUpdate(); // it is updating
+
+    initializeOverrideBuffer(layers);
+    // Expect no cache invalidation the first time (there's no cache yet)
+    EXPECT_EQ(getNonBufferHash(layers),
+              mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
+
+    // This will render a CachedSet of layer 0. Though it is just one layer, it satisfies the
+    // exception that there would be a hole punch above it.
+    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _))
+            .WillOnce(Return(ByMove(
+                    futureOf<renderengine::RenderEngineResult>({NO_ERROR, base::unique_fd()}))));
+    mFlattener->renderCachedSets(mOutputState, std::nullopt);
+
+    // We've rendered a CachedSet, but we haven't merged it in.
+    EXPECT_EQ(nullptr, overrideBuffer0);
+
+    // This time we merge the CachedSet in and we should still have only two sets.
+    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)).Times(0);
+    initializeOverrideBuffer(layers);
+    EXPECT_EQ(getNonBufferHash(layers),
+              mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
+    mFlattener->renderCachedSets(mOutputState, std::nullopt);
+
+    EXPECT_NE(nullptr, overrideBuffer0); // got overridden
+    EXPECT_EQ(nullptr, overrideBuffer1); // did not
+
+    // expect 0's peek though layer to be 1's output layer
+    const auto* peekThroughLayer0 =
+            layerState0->getOutputLayer()->getState().overrideInfo.peekThroughLayer;
+    const auto* peekThroughLayer1 =
+            layerState1->getOutputLayer()->getState().overrideInfo.peekThroughLayer;
+    EXPECT_EQ(&mTestLayers[1]->outputLayer, peekThroughLayer0);
+    EXPECT_EQ(nullptr, peekThroughLayer1);
+}
+
 TEST_F(FlattenerTest, flattenLayers_flattensBlurBehindRunIfFirstRun) {
     auto& layerState1 = mTestLayers[0]->layerState;
 
@@ -685,7 +864,9 @@
     layerState3->resetFramesSinceBufferUpdate();
 
     // layers would be flattened but the buffer would not be overridden
-    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _, _)).WillOnce(Return(NO_ERROR));
+    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _))
+            .WillOnce(Return(ByMove(
+                    futureOf<renderengine::RenderEngineResult>({NO_ERROR, base::unique_fd()}))));
 
     initializeOverrideBuffer(layers);
     EXPECT_EQ(getNonBufferHash(layers),
@@ -729,7 +910,9 @@
     layerState1->resetFramesSinceBufferUpdate();
 
     // layers would be flattened but the buffer would not be overridden
-    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _, _)).WillRepeatedly(Return(NO_ERROR));
+    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _))
+            .WillRepeatedly(Return(ByMove(
+                    futureOf<renderengine::RenderEngineResult>({NO_ERROR, base::unique_fd()}))));
 
     initializeOverrideBuffer(layers);
     EXPECT_EQ(getNonBufferHash(layers),
@@ -781,7 +964,9 @@
     layerState1->resetFramesSinceBufferUpdate();
 
     // layers would be flattened but the buffer would not be overridden
-    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _, _)).WillOnce(Return(NO_ERROR));
+    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _))
+            .WillOnce(Return(ByMove(
+                    futureOf<renderengine::RenderEngineResult>({NO_ERROR, base::unique_fd()}))));
 
     initializeOverrideBuffer(layers);
     EXPECT_EQ(getNonBufferHash(layers),
@@ -828,7 +1013,9 @@
     layerStateWithBlurBehind->resetFramesSinceBufferUpdate();
 
     // layers would be flattened but the buffer would not be overridden
-    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _, _)).WillOnce(Return(NO_ERROR));
+    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _))
+            .WillOnce(Return(ByMove(
+                    futureOf<renderengine::RenderEngineResult>({NO_ERROR, base::unique_fd()}))));
 
     initializeOverrideBuffer(layers);
     EXPECT_EQ(getNonBufferHash(layers),
@@ -869,7 +1056,9 @@
     // Mark the layers inactive
     mTime += 200ms;
     // layers would be flattened but the buffer would not be overridden
-    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _, _)).WillOnce(Return(NO_ERROR));
+    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _))
+            .WillOnce(Return(ByMove(
+                    futureOf<renderengine::RenderEngineResult>({NO_ERROR, base::unique_fd()}))));
 
     initializeOverrideBuffer(layers);
     EXPECT_EQ(getNonBufferHash(layers),
@@ -881,7 +1070,7 @@
 
     // Simulate attempting to render prior to merging the new cached set with the layer stack.
     // Here we should not try to re-render.
-    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _, _)).Times(0);
+    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)).Times(0);
     mFlattener->renderCachedSets(mOutputState, std::nullopt);
 
     // We provide the override buffer now that it's rendered
@@ -928,13 +1117,15 @@
               mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
 
     for (size_t i = 0; i < kMaxDeferRenderAttempts; i++) {
-        EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _, _)).Times(0);
+        EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)).Times(0);
         mFlattener->renderCachedSets(mOutputState,
                                      std::chrono::steady_clock::now() -
                                              (kCachedSetRenderDuration + 10ms));
     }
 
-    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _, _)).WillOnce(Return(NO_ERROR));
+    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _))
+            .WillOnce(Return(ByMove(
+                    futureOf<renderengine::RenderEngineResult>({NO_ERROR, base::unique_fd()}))));
     mFlattener->renderCachedSets(mOutputState,
                                  std::chrono::steady_clock::now() -
                                          (kCachedSetRenderDuration + 10ms));
@@ -968,7 +1159,9 @@
               mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
 
     // This will render a CachedSet.
-    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _, _)).WillOnce(Return(NO_ERROR));
+    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _))
+            .WillOnce(Return(ByMove(
+                    futureOf<renderengine::RenderEngineResult>({NO_ERROR, base::unique_fd()}))));
     mFlattener->renderCachedSets(mOutputState, std::nullopt);
 
     // We've rendered a CachedSet, but we haven't merged it in.
@@ -978,7 +1171,7 @@
 
     // This time we merge the CachedSet in, so we have a new hash, and we should
     // only have two sets.
-    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _, _)).Times(0);
+    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)).Times(0);
     initializeOverrideBuffer(layers);
     EXPECT_NE(getNonBufferHash(layers),
               mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
@@ -1017,7 +1210,9 @@
               mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
 
     // This will render a CachedSet.
-    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _, _)).WillOnce(Return(NO_ERROR));
+    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _))
+            .WillOnce(Return(ByMove(
+                    futureOf<renderengine::RenderEngineResult>({NO_ERROR, base::unique_fd()}))));
     mFlattener->renderCachedSets(mOutputState, std::nullopt);
 
     // We've rendered a CachedSet, but we haven't merged it in.
@@ -1027,7 +1222,7 @@
 
     // This time we merge the CachedSet in, so we have a new hash, and we should
     // only have two sets.
-    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _, _)).Times(0);
+    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)).Times(0);
     initializeOverrideBuffer(layers);
     EXPECT_NE(getNonBufferHash(layers),
               mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
@@ -1066,7 +1261,9 @@
               mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
 
     // This will render a CachedSet.
-    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _, _)).WillOnce(Return(NO_ERROR));
+    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _))
+            .WillOnce(Return(ByMove(
+                    futureOf<renderengine::RenderEngineResult>({NO_ERROR, base::unique_fd()}))));
     mFlattener->renderCachedSets(mOutputState, std::nullopt);
 
     // We've rendered a CachedSet, but we haven't merged it in.
@@ -1076,7 +1273,114 @@
 
     // This time we merge the CachedSet in, so we have a new hash, and we should
     // only have two sets.
-    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _, _)).Times(0);
+    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)).Times(0);
+    initializeOverrideBuffer(layers);
+    EXPECT_NE(getNonBufferHash(layers),
+              mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
+    mFlattener->renderCachedSets(mOutputState, std::nullopt);
+
+    EXPECT_NE(nullptr, overrideBuffer1);
+    EXPECT_EQ(overrideBuffer1, overrideBuffer2);
+    EXPECT_EQ(nullptr, overrideBuffer3);
+}
+
+TEST_F(FlattenerTest, flattenLayers_skipsColorLayers) {
+    auto& layerState1 = mTestLayers[0]->layerState;
+    const auto& overrideBuffer1 = layerState1->getOutputLayer()->getState().overrideInfo.buffer;
+    auto& layerState2 = mTestLayers[1]->layerState;
+    const auto& overrideBuffer2 = layerState2->getOutputLayer()->getState().overrideInfo.buffer;
+    auto& layerState3 = mTestLayers[2]->layerState;
+    const auto& overrideBuffer3 = layerState3->getOutputLayer()->getState().overrideInfo.buffer;
+    auto& layerState4 = mTestLayers[3]->layerState;
+    const auto& overrideBuffer4 = layerState4->getOutputLayer()->getState().overrideInfo.buffer;
+
+    // Rewrite the first two layers to just be a solid color.
+    mTestLayers[0]->layerFECompositionState.color = half4(255.f, 0.f, 0.f, 255.f);
+    mTestLayers[0]->layerFECompositionState.buffer = nullptr;
+    mTestLayers[1]->layerFECompositionState.color = half4(0.f, 255.f, 0.f, 255.f);
+    mTestLayers[1]->layerFECompositionState.buffer = nullptr;
+
+    const std::vector<const LayerState*> layers = {
+            layerState1.get(),
+            layerState2.get(),
+            layerState3.get(),
+            layerState4.get(),
+    };
+
+    initializeFlattener(layers);
+
+    mTime += 200ms;
+    initializeOverrideBuffer(layers);
+    EXPECT_EQ(getNonBufferHash(layers),
+              mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
+
+    // This will render a CachedSet.
+    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _))
+            .WillOnce(Return(ByMove(
+                    futureOf<renderengine::RenderEngineResult>({NO_ERROR, base::unique_fd()}))));
+    mFlattener->renderCachedSets(mOutputState, std::nullopt);
+
+    // We've rendered a CachedSet, but we haven't merged it in.
+    EXPECT_EQ(nullptr, overrideBuffer1);
+    EXPECT_EQ(nullptr, overrideBuffer2);
+    EXPECT_EQ(nullptr, overrideBuffer3);
+    EXPECT_EQ(nullptr, overrideBuffer4);
+
+    // This time we merge the CachedSet in, so we have a new hash, and we should
+    // only have two sets.
+    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)).Times(0);
+    initializeOverrideBuffer(layers);
+    EXPECT_NE(getNonBufferHash(layers),
+              mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
+    mFlattener->renderCachedSets(mOutputState, std::nullopt);
+
+    EXPECT_EQ(nullptr, overrideBuffer1);
+    EXPECT_EQ(nullptr, overrideBuffer2);
+    EXPECT_EQ(overrideBuffer3, overrideBuffer4);
+    EXPECT_NE(nullptr, overrideBuffer4);
+}
+
+TEST_F(FlattenerTest, flattenLayers_skips_DISPLAY_DECORATION) {
+    auto& layerState1 = mTestLayers[0]->layerState;
+    const auto& overrideBuffer1 = layerState1->getOutputLayer()->getState().overrideInfo.buffer;
+
+    auto& layerState2 = mTestLayers[1]->layerState;
+    const auto& overrideBuffer2 = layerState2->getOutputLayer()->getState().overrideInfo.buffer;
+
+    // The third layer uses DISPLAY_DECORATION, which should never be cached.
+    auto& layerState3 = mTestLayers[2]->layerState;
+    const auto& overrideBuffer3 = layerState3->getOutputLayer()->getState().overrideInfo.buffer;
+    mTestLayers[2]->layerFECompositionState.compositionType =
+            aidl::android::hardware::graphics::composer3::Composition::DISPLAY_DECORATION;
+    mTestLayers[2]->layerState->update(&mTestLayers[2]->outputLayer);
+
+    const std::vector<const LayerState*> layers = {
+            layerState1.get(),
+            layerState2.get(),
+            layerState3.get(),
+    };
+
+    initializeFlattener(layers);
+
+    mTime += 200ms;
+    initializeOverrideBuffer(layers);
+    EXPECT_EQ(getNonBufferHash(layers),
+              mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
+
+    // This will render a CachedSet.
+    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _))
+            .WillOnce(Return(ByMove(
+                    futureOf<renderengine::RenderEngineResult>({NO_ERROR, base::unique_fd()}))));
+    mFlattener->renderCachedSets(mOutputState, std::nullopt);
+
+    // We've rendered a CachedSet, but we haven't merged it in.
+    EXPECT_EQ(nullptr, overrideBuffer1);
+    EXPECT_EQ(nullptr, overrideBuffer2);
+    EXPECT_EQ(nullptr, overrideBuffer3);
+
+    // This time we merge the CachedSet in, so we have a new hash, and we should
+    // only have two sets.
+    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)).Times(0);
     initializeOverrideBuffer(layers);
     EXPECT_NE(getNonBufferHash(layers),
               mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
diff --git a/services/surfaceflinger/CompositionEngine/tests/planner/LayerStateTest.cpp b/services/surfaceflinger/CompositionEngine/tests/planner/LayerStateTest.cpp
index 9ad3ab4..bd4ff13 100644
--- a/services/surfaceflinger/CompositionEngine/tests/planner/LayerStateTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/planner/LayerStateTest.cpp
@@ -17,6 +17,7 @@
 #undef LOG_TAG
 #define LOG_TAG "LayerStateTest"
 
+#include <aidl/android/hardware/graphics/common/BufferUsage.h>
 #include <compositionengine/impl/OutputLayer.h>
 #include <compositionengine/impl/planner/LayerState.h>
 #include <compositionengine/mock/LayerFE.h>
@@ -27,6 +28,11 @@
 #include "android/hardware_buffer.h"
 #include "compositionengine/LayerFECompositionState.h"
 
+#include <aidl/android/hardware/graphics/composer3/Composition.h>
+
+using ::aidl::android::hardware::graphics::common::BufferUsage;
+using ::aidl::android::hardware::graphics::composer3::Composition;
+
 namespace android::compositionengine::impl::planner {
 namespace {
 
@@ -103,7 +109,7 @@
         EXPECT_EQ(fields, rhs.getDifferingFields(lhs));
     }
 
-    mock::LayerFE mLayerFE;
+    sp<mock::LayerFE> mLayerFE = sp<mock::LayerFE>::make();
     mock::OutputLayer mOutputLayer;
     std::unique_ptr<LayerState> mLayerState;
 };
@@ -111,7 +117,7 @@
 TEST_F(LayerStateTest, getOutputLayer) {
     OutputLayerCompositionState outputLayerCompositionState;
     LayerFECompositionState layerFECompositionState;
-    setupMocksForLayer(mOutputLayer, mLayerFE, outputLayerCompositionState,
+    setupMocksForLayer(mOutputLayer, *mLayerFE, outputLayerCompositionState,
                        layerFECompositionState);
     mLayerState = std::make_unique<LayerState>(&mOutputLayer);
     EXPECT_EQ(&mOutputLayer, mLayerState->getOutputLayer());
@@ -120,14 +126,14 @@
 TEST_F(LayerStateTest, updateOutputLayer) {
     OutputLayerCompositionState outputLayerCompositionState;
     LayerFECompositionState layerFECompositionState;
-    setupMocksForLayer(mOutputLayer, mLayerFE, outputLayerCompositionState,
+    setupMocksForLayer(mOutputLayer, *mLayerFE, outputLayerCompositionState,
                        layerFECompositionState);
     mLayerState = std::make_unique<LayerState>(&mOutputLayer);
     EXPECT_EQ(&mOutputLayer, mLayerState->getOutputLayer());
 
     mock::OutputLayer newOutputLayer;
-    mock::LayerFE newLayerFE;
-    setupMocksForLayer(newOutputLayer, newLayerFE, outputLayerCompositionState,
+    sp<mock::LayerFE> newLayerFE = sp<mock::LayerFE>::make();
+    setupMocksForLayer(newOutputLayer, *newLayerFE, outputLayerCompositionState,
                        layerFECompositionState);
     mLayerState->update(&newOutputLayer);
     EXPECT_EQ(&newOutputLayer, mLayerState->getOutputLayer());
@@ -136,7 +142,7 @@
 TEST_F(LayerStateTest, getId) {
     OutputLayerCompositionState outputLayerCompositionState;
     LayerFECompositionState layerFECompositionState;
-    setupMocksForLayer(mOutputLayer, mLayerFE, outputLayerCompositionState,
+    setupMocksForLayer(mOutputLayer, *mLayerFE, outputLayerCompositionState,
                        layerFECompositionState);
     mLayerState = std::make_unique<LayerState>(&mOutputLayer);
     EXPECT_EQ(sSequenceId, mLayerState->getId());
@@ -145,13 +151,13 @@
 TEST_F(LayerStateTest, updateId) {
     OutputLayerCompositionState outputLayerCompositionState;
     LayerFECompositionState layerFECompositionState;
-    setupMocksForLayer(mOutputLayer, mLayerFE, outputLayerCompositionState,
+    setupMocksForLayer(mOutputLayer, *mLayerFE, outputLayerCompositionState,
                        layerFECompositionState);
     mLayerState = std::make_unique<LayerState>(&mOutputLayer);
 
     mock::OutputLayer newOutputLayer;
-    mock::LayerFE newLayerFE;
-    setupMocksForLayer(newOutputLayer, newLayerFE, outputLayerCompositionState,
+    sp<mock::LayerFE> newLayerFE = sp<mock::LayerFE>::make();
+    setupMocksForLayer(newOutputLayer, *newLayerFE, outputLayerCompositionState,
                        layerFECompositionState, sSequenceIdTwo);
     Flags<LayerStateField> updates = mLayerState->update(&newOutputLayer);
     EXPECT_EQ(sSequenceIdTwo, mLayerState->getId());
@@ -161,12 +167,12 @@
 TEST_F(LayerStateTest, compareId) {
     OutputLayerCompositionState outputLayerCompositionState;
     LayerFECompositionState layerFECompositionState;
-    setupMocksForLayer(mOutputLayer, mLayerFE, outputLayerCompositionState,
+    setupMocksForLayer(mOutputLayer, *mLayerFE, outputLayerCompositionState,
                        layerFECompositionState);
     mLayerState = std::make_unique<LayerState>(&mOutputLayer);
     mock::OutputLayer newOutputLayer;
-    mock::LayerFE newLayerFE;
-    setupMocksForLayer(newOutputLayer, newLayerFE, outputLayerCompositionState,
+    sp<mock::LayerFE> newLayerFE = sp<mock::LayerFE>::make();
+    setupMocksForLayer(newOutputLayer, *newLayerFE, outputLayerCompositionState,
                        layerFECompositionState, sSequenceIdTwo);
     auto otherLayerState = std::make_unique<LayerState>(&newOutputLayer);
 
@@ -181,7 +187,7 @@
 TEST_F(LayerStateTest, getName) {
     OutputLayerCompositionState outputLayerCompositionState;
     LayerFECompositionState layerFECompositionState;
-    setupMocksForLayer(mOutputLayer, mLayerFE, outputLayerCompositionState,
+    setupMocksForLayer(mOutputLayer, *mLayerFE, outputLayerCompositionState,
                        layerFECompositionState);
     mLayerState = std::make_unique<LayerState>(&mOutputLayer);
     EXPECT_EQ(sDebugName, mLayerState->getName());
@@ -190,13 +196,13 @@
 TEST_F(LayerStateTest, updateName) {
     OutputLayerCompositionState outputLayerCompositionState;
     LayerFECompositionState layerFECompositionState;
-    setupMocksForLayer(mOutputLayer, mLayerFE, outputLayerCompositionState,
+    setupMocksForLayer(mOutputLayer, *mLayerFE, outputLayerCompositionState,
                        layerFECompositionState);
     mLayerState = std::make_unique<LayerState>(&mOutputLayer);
 
     mock::OutputLayer newOutputLayer;
-    mock::LayerFE newLayerFE;
-    setupMocksForLayer(newOutputLayer, newLayerFE, outputLayerCompositionState,
+    sp<mock::LayerFE> newLayerFE = sp<mock::LayerFE>::make();
+    setupMocksForLayer(newOutputLayer, *newLayerFE, outputLayerCompositionState,
                        layerFECompositionState, sSequenceId, sDebugNameTwo);
     Flags<LayerStateField> updates = mLayerState->update(&newOutputLayer);
     EXPECT_EQ(sDebugNameTwo, mLayerState->getName());
@@ -206,12 +212,12 @@
 TEST_F(LayerStateTest, compareName) {
     OutputLayerCompositionState outputLayerCompositionState;
     LayerFECompositionState layerFECompositionState;
-    setupMocksForLayer(mOutputLayer, mLayerFE, outputLayerCompositionState,
+    setupMocksForLayer(mOutputLayer, *mLayerFE, outputLayerCompositionState,
                        layerFECompositionState);
     mLayerState = std::make_unique<LayerState>(&mOutputLayer);
     mock::OutputLayer newOutputLayer;
-    mock::LayerFE newLayerFE;
-    setupMocksForLayer(newOutputLayer, newLayerFE, outputLayerCompositionState,
+    sp<mock::LayerFE> newLayerFE = sp<mock::LayerFE>::make();
+    setupMocksForLayer(newOutputLayer, *newLayerFE, outputLayerCompositionState,
                        layerFECompositionState, sSequenceId, sDebugNameTwo);
     auto otherLayerState = std::make_unique<LayerState>(&newOutputLayer);
 
@@ -227,7 +233,7 @@
     OutputLayerCompositionState outputLayerCompositionState;
     outputLayerCompositionState.displayFrame = sRectOne;
     LayerFECompositionState layerFECompositionState;
-    setupMocksForLayer(mOutputLayer, mLayerFE, outputLayerCompositionState,
+    setupMocksForLayer(mOutputLayer, *mLayerFE, outputLayerCompositionState,
                        layerFECompositionState);
     mLayerState = std::make_unique<LayerState>(&mOutputLayer);
     EXPECT_EQ(sRectOne, mLayerState->getDisplayFrame());
@@ -237,15 +243,15 @@
     OutputLayerCompositionState outputLayerCompositionState;
     outputLayerCompositionState.displayFrame = sRectOne;
     LayerFECompositionState layerFECompositionState;
-    setupMocksForLayer(mOutputLayer, mLayerFE, outputLayerCompositionState,
+    setupMocksForLayer(mOutputLayer, *mLayerFE, outputLayerCompositionState,
                        layerFECompositionState);
     mLayerState = std::make_unique<LayerState>(&mOutputLayer);
 
     mock::OutputLayer newOutputLayer;
-    mock::LayerFE newLayerFE;
+    sp<mock::LayerFE> newLayerFE = sp<mock::LayerFE>::make();
     OutputLayerCompositionState outputLayerCompositionStateTwo;
     outputLayerCompositionStateTwo.displayFrame = sRectTwo;
-    setupMocksForLayer(newOutputLayer, newLayerFE, outputLayerCompositionStateTwo,
+    setupMocksForLayer(newOutputLayer, *newLayerFE, outputLayerCompositionStateTwo,
                        layerFECompositionState);
     Flags<LayerStateField> updates = mLayerState->update(&newOutputLayer);
     EXPECT_EQ(sRectTwo, mLayerState->getDisplayFrame());
@@ -256,14 +262,14 @@
     OutputLayerCompositionState outputLayerCompositionState;
     outputLayerCompositionState.displayFrame = sRectOne;
     LayerFECompositionState layerFECompositionState;
-    setupMocksForLayer(mOutputLayer, mLayerFE, outputLayerCompositionState,
+    setupMocksForLayer(mOutputLayer, *mLayerFE, outputLayerCompositionState,
                        layerFECompositionState);
     mLayerState = std::make_unique<LayerState>(&mOutputLayer);
     mock::OutputLayer newOutputLayer;
-    mock::LayerFE newLayerFE;
+    sp<mock::LayerFE> newLayerFE = sp<mock::LayerFE>::make();
     OutputLayerCompositionState outputLayerCompositionStateTwo;
     outputLayerCompositionStateTwo.displayFrame = sRectTwo;
-    setupMocksForLayer(newOutputLayer, newLayerFE, outputLayerCompositionStateTwo,
+    setupMocksForLayer(newOutputLayer, *newLayerFE, outputLayerCompositionStateTwo,
                        layerFECompositionState);
     auto otherLayerState = std::make_unique<LayerState>(&newOutputLayer);
 
@@ -277,64 +283,55 @@
 TEST_F(LayerStateTest, getCompositionType) {
     OutputLayerCompositionState outputLayerCompositionState;
     LayerFECompositionState layerFECompositionState;
-    layerFECompositionState.compositionType =
-            hardware::graphics::composer::hal::Composition::DEVICE;
-    setupMocksForLayer(mOutputLayer, mLayerFE, outputLayerCompositionState,
+    layerFECompositionState.compositionType = Composition::DEVICE;
+    setupMocksForLayer(mOutputLayer, *mLayerFE, outputLayerCompositionState,
                        layerFECompositionState);
     mLayerState = std::make_unique<LayerState>(&mOutputLayer);
-    EXPECT_EQ(hardware::graphics::composer::hal::Composition::DEVICE,
-              mLayerState->getCompositionType());
+    EXPECT_EQ(Composition::DEVICE, mLayerState->getCompositionType());
 }
 
 TEST_F(LayerStateTest, getCompositionType_forcedClient) {
     OutputLayerCompositionState outputLayerCompositionState;
     outputLayerCompositionState.forceClientComposition = true;
     LayerFECompositionState layerFECompositionState;
-    layerFECompositionState.compositionType =
-            hardware::graphics::composer::hal::Composition::DEVICE;
-    setupMocksForLayer(mOutputLayer, mLayerFE, outputLayerCompositionState,
+    layerFECompositionState.compositionType = Composition::DEVICE;
+    setupMocksForLayer(mOutputLayer, *mLayerFE, outputLayerCompositionState,
                        layerFECompositionState);
     mLayerState = std::make_unique<LayerState>(&mOutputLayer);
-    EXPECT_EQ(hardware::graphics::composer::hal::Composition::CLIENT,
-              mLayerState->getCompositionType());
+    EXPECT_EQ(Composition::CLIENT, mLayerState->getCompositionType());
 }
 
 TEST_F(LayerStateTest, updateCompositionType) {
     OutputLayerCompositionState outputLayerCompositionState;
     LayerFECompositionState layerFECompositionState;
-    layerFECompositionState.compositionType =
-            hardware::graphics::composer::hal::Composition::DEVICE;
-    setupMocksForLayer(mOutputLayer, mLayerFE, outputLayerCompositionState,
+    layerFECompositionState.compositionType = Composition::DEVICE;
+    setupMocksForLayer(mOutputLayer, *mLayerFE, outputLayerCompositionState,
                        layerFECompositionState);
     mLayerState = std::make_unique<LayerState>(&mOutputLayer);
 
     mock::OutputLayer newOutputLayer;
-    mock::LayerFE newLayerFE;
+    sp<mock::LayerFE> newLayerFE = sp<mock::LayerFE>::make();
     LayerFECompositionState layerFECompositionStateTwo;
-    layerFECompositionStateTwo.compositionType =
-            hardware::graphics::composer::hal::Composition::SOLID_COLOR;
-    setupMocksForLayer(newOutputLayer, newLayerFE, outputLayerCompositionState,
+    layerFECompositionStateTwo.compositionType = Composition::SOLID_COLOR;
+    setupMocksForLayer(newOutputLayer, *newLayerFE, outputLayerCompositionState,
                        layerFECompositionStateTwo);
     Flags<LayerStateField> updates = mLayerState->update(&newOutputLayer);
-    EXPECT_EQ(hardware::graphics::composer::hal::Composition::SOLID_COLOR,
-              mLayerState->getCompositionType());
+    EXPECT_EQ(Composition::SOLID_COLOR, mLayerState->getCompositionType());
     EXPECT_EQ(Flags<LayerStateField>(LayerStateField::CompositionType), updates);
 }
 
 TEST_F(LayerStateTest, compareCompositionType) {
     OutputLayerCompositionState outputLayerCompositionState;
     LayerFECompositionState layerFECompositionState;
-    layerFECompositionState.compositionType =
-            hardware::graphics::composer::hal::Composition::DEVICE;
-    setupMocksForLayer(mOutputLayer, mLayerFE, outputLayerCompositionState,
+    layerFECompositionState.compositionType = Composition::DEVICE;
+    setupMocksForLayer(mOutputLayer, *mLayerFE, outputLayerCompositionState,
                        layerFECompositionState);
     mLayerState = std::make_unique<LayerState>(&mOutputLayer);
     mock::OutputLayer newOutputLayer;
-    mock::LayerFE newLayerFE;
+    sp<mock::LayerFE> newLayerFE = sp<mock::LayerFE>::make();
     LayerFECompositionState layerFECompositionStateTwo;
-    layerFECompositionStateTwo.compositionType =
-            hardware::graphics::composer::hal::Composition::SOLID_COLOR;
-    setupMocksForLayer(newOutputLayer, newLayerFE, outputLayerCompositionState,
+    layerFECompositionStateTwo.compositionType = Composition::SOLID_COLOR;
+    setupMocksForLayer(newOutputLayer, *newLayerFE, outputLayerCompositionState,
                        layerFECompositionStateTwo);
     auto otherLayerState = std::make_unique<LayerState>(&newOutputLayer);
 
@@ -350,32 +347,80 @@
     OutputLayerCompositionState outputLayerCompositionState;
     LayerFECompositionState layerFECompositionState;
     layerFECompositionState.buffer = new GraphicBuffer();
-    setupMocksForLayer(mOutputLayer, mLayerFE, outputLayerCompositionState,
+    setupMocksForLayer(mOutputLayer, *mLayerFE, outputLayerCompositionState,
                        layerFECompositionState);
     mLayerState = std::make_unique<LayerState>(&mOutputLayer);
 
     mock::OutputLayer newOutputLayer;
-    mock::LayerFE newLayerFE;
+    sp<mock::LayerFE> newLayerFE = sp<mock::LayerFE>::make();
     LayerFECompositionState layerFECompositionStateTwo;
     layerFECompositionStateTwo.buffer = new GraphicBuffer();
-    setupMocksForLayer(newOutputLayer, newLayerFE, outputLayerCompositionState,
+    setupMocksForLayer(newOutputLayer, *newLayerFE, outputLayerCompositionState,
                        layerFECompositionStateTwo);
     Flags<LayerStateField> updates = mLayerState->update(&newOutputLayer);
     EXPECT_EQ(Flags<LayerStateField>(LayerStateField::Buffer), updates);
 }
 
+TEST_F(LayerStateTest, updateBufferSingleBufferedLegacy) {
+    OutputLayerCompositionState outputLayerCompositionState;
+    LayerFECompositionState layerFECompositionState;
+    layerFECompositionState.buffer = new GraphicBuffer();
+    setupMocksForLayer(mOutputLayer, *mLayerFE, outputLayerCompositionState,
+                       layerFECompositionState);
+    mLayerState = std::make_unique<LayerState>(&mOutputLayer);
+
+    mock::OutputLayer newOutputLayer;
+    sp<mock::LayerFE> newLayerFE = sp<mock::LayerFE>::make();
+    LayerFECompositionState layerFECompositionStateTwo;
+    layerFECompositionStateTwo.buffer = new GraphicBuffer();
+    setupMocksForLayer(newOutputLayer, *newLayerFE, outputLayerCompositionState,
+                       layerFECompositionStateTwo);
+
+    for (uint64_t i = 0; i < 10; i++) {
+        layerFECompositionStateTwo.frameNumber = i;
+        setupMocksForLayer(newOutputLayer, *newLayerFE, outputLayerCompositionState,
+                           layerFECompositionStateTwo);
+        Flags<LayerStateField> updates = mLayerState->update(&newOutputLayer);
+        EXPECT_EQ(Flags<LayerStateField>(LayerStateField::Buffer), updates);
+    }
+}
+
+TEST_F(LayerStateTest, updateBufferSingleBufferedUsage) {
+    OutputLayerCompositionState outputLayerCompositionState;
+    LayerFECompositionState layerFECompositionState;
+    layerFECompositionState.buffer = new GraphicBuffer();
+    setupMocksForLayer(mOutputLayer, *mLayerFE, outputLayerCompositionState,
+                       layerFECompositionState);
+    mLayerState = std::make_unique<LayerState>(&mOutputLayer);
+
+    mock::OutputLayer newOutputLayer;
+    sp<mock::LayerFE> newLayerFE = sp<mock::LayerFE>::make();
+    LayerFECompositionState layerFECompositionStateTwo;
+    layerFECompositionStateTwo.buffer = new GraphicBuffer();
+    layerFECompositionStateTwo.buffer->usage = static_cast<uint64_t>(BufferUsage::FRONT_BUFFER);
+    setupMocksForLayer(newOutputLayer, *newLayerFE, outputLayerCompositionState,
+                       layerFECompositionStateTwo);
+
+    for (uint64_t i = 0; i < 10; i++) {
+        setupMocksForLayer(newOutputLayer, *newLayerFE, outputLayerCompositionState,
+                           layerFECompositionStateTwo);
+        Flags<LayerStateField> updates = mLayerState->update(&newOutputLayer);
+        EXPECT_EQ(Flags<LayerStateField>(LayerStateField::Buffer), updates);
+    }
+}
+
 TEST_F(LayerStateTest, compareBuffer) {
     OutputLayerCompositionState outputLayerCompositionState;
     LayerFECompositionState layerFECompositionState;
     layerFECompositionState.buffer = new GraphicBuffer();
-    setupMocksForLayer(mOutputLayer, mLayerFE, outputLayerCompositionState,
+    setupMocksForLayer(mOutputLayer, *mLayerFE, outputLayerCompositionState,
                        layerFECompositionState);
     mLayerState = std::make_unique<LayerState>(&mOutputLayer);
     mock::OutputLayer newOutputLayer;
-    mock::LayerFE newLayerFE;
+    sp<mock::LayerFE> newLayerFE = sp<mock::LayerFE>::make();
     LayerFECompositionState layerFECompositionStateTwo;
     layerFECompositionStateTwo.buffer = new GraphicBuffer();
-    setupMocksForLayer(newOutputLayer, newLayerFE, outputLayerCompositionState,
+    setupMocksForLayer(newOutputLayer, *newLayerFE, outputLayerCompositionState,
                        layerFECompositionStateTwo);
     auto otherLayerState = std::make_unique<LayerState>(&newOutputLayer);
 
@@ -391,15 +436,15 @@
     OutputLayerCompositionState outputLayerCompositionState;
     outputLayerCompositionState.sourceCrop = sFloatRectOne;
     LayerFECompositionState layerFECompositionState;
-    setupMocksForLayer(mOutputLayer, mLayerFE, outputLayerCompositionState,
+    setupMocksForLayer(mOutputLayer, *mLayerFE, outputLayerCompositionState,
                        layerFECompositionState);
     mLayerState = std::make_unique<LayerState>(&mOutputLayer);
 
     mock::OutputLayer newOutputLayer;
-    mock::LayerFE newLayerFE;
+    sp<mock::LayerFE> newLayerFE = sp<mock::LayerFE>::make();
     OutputLayerCompositionState outputLayerCompositionStateTwo;
     outputLayerCompositionStateTwo.sourceCrop = sFloatRectTwo;
-    setupMocksForLayer(newOutputLayer, newLayerFE, outputLayerCompositionStateTwo,
+    setupMocksForLayer(newOutputLayer, *newLayerFE, outputLayerCompositionStateTwo,
                        layerFECompositionState);
     Flags<LayerStateField> updates = mLayerState->update(&newOutputLayer);
     EXPECT_EQ(Flags<LayerStateField>(LayerStateField::SourceCrop), updates);
@@ -409,14 +454,14 @@
     OutputLayerCompositionState outputLayerCompositionState;
     outputLayerCompositionState.sourceCrop = sFloatRectOne;
     LayerFECompositionState layerFECompositionState;
-    setupMocksForLayer(mOutputLayer, mLayerFE, outputLayerCompositionState,
+    setupMocksForLayer(mOutputLayer, *mLayerFE, outputLayerCompositionState,
                        layerFECompositionState);
     mLayerState = std::make_unique<LayerState>(&mOutputLayer);
     mock::OutputLayer newOutputLayer;
-    mock::LayerFE newLayerFE;
+    sp<mock::LayerFE> newLayerFE = sp<mock::LayerFE>::make();
     OutputLayerCompositionState outputLayerCompositionStateTwo;
     outputLayerCompositionStateTwo.sourceCrop = sFloatRectTwo;
-    setupMocksForLayer(newOutputLayer, newLayerFE, outputLayerCompositionStateTwo,
+    setupMocksForLayer(newOutputLayer, *newLayerFE, outputLayerCompositionStateTwo,
                        layerFECompositionState);
     auto otherLayerState = std::make_unique<LayerState>(&newOutputLayer);
 
@@ -430,15 +475,15 @@
     OutputLayerCompositionState outputLayerCompositionState;
     outputLayerCompositionState.bufferTransform = Hwc2::Transform::FLIP_H;
     LayerFECompositionState layerFECompositionState;
-    setupMocksForLayer(mOutputLayer, mLayerFE, outputLayerCompositionState,
+    setupMocksForLayer(mOutputLayer, *mLayerFE, outputLayerCompositionState,
                        layerFECompositionState);
     mLayerState = std::make_unique<LayerState>(&mOutputLayer);
 
     mock::OutputLayer newOutputLayer;
-    mock::LayerFE newLayerFE;
+    sp<mock::LayerFE> newLayerFE = sp<mock::LayerFE>::make();
     OutputLayerCompositionState outputLayerCompositionStateTwo;
     outputLayerCompositionStateTwo.bufferTransform = Hwc2::Transform::FLIP_V;
-    setupMocksForLayer(newOutputLayer, newLayerFE, outputLayerCompositionStateTwo,
+    setupMocksForLayer(newOutputLayer, *newLayerFE, outputLayerCompositionStateTwo,
                        layerFECompositionState);
     Flags<LayerStateField> updates = mLayerState->update(&newOutputLayer);
     EXPECT_EQ(Flags<LayerStateField>(LayerStateField::BufferTransform), updates);
@@ -448,14 +493,14 @@
     OutputLayerCompositionState outputLayerCompositionState;
     outputLayerCompositionState.bufferTransform = Hwc2::Transform::FLIP_H;
     LayerFECompositionState layerFECompositionState;
-    setupMocksForLayer(mOutputLayer, mLayerFE, outputLayerCompositionState,
+    setupMocksForLayer(mOutputLayer, *mLayerFE, outputLayerCompositionState,
                        layerFECompositionState);
     mLayerState = std::make_unique<LayerState>(&mOutputLayer);
     mock::OutputLayer newOutputLayer;
-    mock::LayerFE newLayerFE;
+    sp<mock::LayerFE> newLayerFE = sp<mock::LayerFE>::make();
     OutputLayerCompositionState outputLayerCompositionStateTwo;
     outputLayerCompositionStateTwo.bufferTransform = Hwc2::Transform::FLIP_V;
-    setupMocksForLayer(newOutputLayer, newLayerFE, outputLayerCompositionStateTwo,
+    setupMocksForLayer(newOutputLayer, *newLayerFE, outputLayerCompositionStateTwo,
                        layerFECompositionState);
     auto otherLayerState = std::make_unique<LayerState>(&newOutputLayer);
 
@@ -470,15 +515,15 @@
     OutputLayerCompositionState outputLayerCompositionState;
     LayerFECompositionState layerFECompositionState;
     layerFECompositionState.blendMode = hal::BlendMode::COVERAGE;
-    setupMocksForLayer(mOutputLayer, mLayerFE, outputLayerCompositionState,
+    setupMocksForLayer(mOutputLayer, *mLayerFE, outputLayerCompositionState,
                        layerFECompositionState);
     mLayerState = std::make_unique<LayerState>(&mOutputLayer);
 
     mock::OutputLayer newOutputLayer;
-    mock::LayerFE newLayerFE;
+    sp<mock::LayerFE> newLayerFE = sp<mock::LayerFE>::make();
     LayerFECompositionState layerFECompositionStateTwo;
     layerFECompositionStateTwo.blendMode = hal::BlendMode::PREMULTIPLIED;
-    setupMocksForLayer(newOutputLayer, newLayerFE, outputLayerCompositionState,
+    setupMocksForLayer(newOutputLayer, *newLayerFE, outputLayerCompositionState,
                        layerFECompositionStateTwo);
     Flags<LayerStateField> updates = mLayerState->update(&newOutputLayer);
     EXPECT_EQ(Flags<LayerStateField>(LayerStateField::BlendMode), updates);
@@ -488,14 +533,14 @@
     OutputLayerCompositionState outputLayerCompositionState;
     LayerFECompositionState layerFECompositionState;
     layerFECompositionState.blendMode = hal::BlendMode::COVERAGE;
-    setupMocksForLayer(mOutputLayer, mLayerFE, outputLayerCompositionState,
+    setupMocksForLayer(mOutputLayer, *mLayerFE, outputLayerCompositionState,
                        layerFECompositionState);
     mLayerState = std::make_unique<LayerState>(&mOutputLayer);
     mock::OutputLayer newOutputLayer;
-    mock::LayerFE newLayerFE;
+    sp<mock::LayerFE> newLayerFE = sp<mock::LayerFE>::make();
     LayerFECompositionState layerFECompositionStateTwo;
     layerFECompositionStateTwo.blendMode = hal::BlendMode::PREMULTIPLIED;
-    setupMocksForLayer(newOutputLayer, newLayerFE, outputLayerCompositionState,
+    setupMocksForLayer(newOutputLayer, *newLayerFE, outputLayerCompositionState,
                        layerFECompositionStateTwo);
     auto otherLayerState = std::make_unique<LayerState>(&newOutputLayer);
 
@@ -509,15 +554,15 @@
     OutputLayerCompositionState outputLayerCompositionState;
     LayerFECompositionState layerFECompositionState;
     layerFECompositionState.alpha = sAlphaOne;
-    setupMocksForLayer(mOutputLayer, mLayerFE, outputLayerCompositionState,
+    setupMocksForLayer(mOutputLayer, *mLayerFE, outputLayerCompositionState,
                        layerFECompositionState);
     mLayerState = std::make_unique<LayerState>(&mOutputLayer);
 
     mock::OutputLayer newOutputLayer;
-    mock::LayerFE newLayerFE;
+    sp<mock::LayerFE> newLayerFE = sp<mock::LayerFE>::make();
     LayerFECompositionState layerFECompositionStateTwo;
     layerFECompositionStateTwo.alpha = sAlphaTwo;
-    setupMocksForLayer(newOutputLayer, newLayerFE, outputLayerCompositionState,
+    setupMocksForLayer(newOutputLayer, *newLayerFE, outputLayerCompositionState,
                        layerFECompositionStateTwo);
     Flags<LayerStateField> updates = mLayerState->update(&newOutputLayer);
     EXPECT_EQ(Flags<LayerStateField>(LayerStateField::Alpha), updates);
@@ -527,14 +572,14 @@
     OutputLayerCompositionState outputLayerCompositionState;
     LayerFECompositionState layerFECompositionState;
     layerFECompositionState.alpha = sAlphaOne;
-    setupMocksForLayer(mOutputLayer, mLayerFE, outputLayerCompositionState,
+    setupMocksForLayer(mOutputLayer, *mLayerFE, outputLayerCompositionState,
                        layerFECompositionState);
     mLayerState = std::make_unique<LayerState>(&mOutputLayer);
     mock::OutputLayer newOutputLayer;
-    mock::LayerFE newLayerFE;
+    sp<mock::LayerFE> newLayerFE = sp<mock::LayerFE>::make();
     LayerFECompositionState layerFECompositionStateTwo;
     layerFECompositionStateTwo.alpha = sAlphaTwo;
-    setupMocksForLayer(newOutputLayer, newLayerFE, outputLayerCompositionState,
+    setupMocksForLayer(newOutputLayer, *newLayerFE, outputLayerCompositionState,
                        layerFECompositionStateTwo);
     auto otherLayerState = std::make_unique<LayerState>(&newOutputLayer);
 
@@ -548,15 +593,15 @@
     OutputLayerCompositionState outputLayerCompositionState;
     LayerFECompositionState layerFECompositionState;
     layerFECompositionState.metadata[sMetadataKeyOne] = sMetadataValueOne;
-    setupMocksForLayer(mOutputLayer, mLayerFE, outputLayerCompositionState,
+    setupMocksForLayer(mOutputLayer, *mLayerFE, outputLayerCompositionState,
                        layerFECompositionState);
     mLayerState = std::make_unique<LayerState>(&mOutputLayer);
 
     mock::OutputLayer newOutputLayer;
-    mock::LayerFE newLayerFE;
+    sp<mock::LayerFE> newLayerFE = sp<mock::LayerFE>::make();
     LayerFECompositionState layerFECompositionStateTwo;
     layerFECompositionStateTwo.metadata[sMetadataKeyTwo] = sMetadataValueTwo;
-    setupMocksForLayer(newOutputLayer, newLayerFE, outputLayerCompositionState,
+    setupMocksForLayer(newOutputLayer, *newLayerFE, outputLayerCompositionState,
                        layerFECompositionStateTwo);
     Flags<LayerStateField> updates = mLayerState->update(&newOutputLayer);
     EXPECT_EQ(Flags<LayerStateField>(LayerStateField::LayerMetadata), updates);
@@ -566,14 +611,14 @@
     OutputLayerCompositionState outputLayerCompositionState;
     LayerFECompositionState layerFECompositionState;
     layerFECompositionState.metadata[sMetadataKeyOne] = sMetadataValueOne;
-    setupMocksForLayer(mOutputLayer, mLayerFE, outputLayerCompositionState,
+    setupMocksForLayer(mOutputLayer, *mLayerFE, outputLayerCompositionState,
                        layerFECompositionState);
     mLayerState = std::make_unique<LayerState>(&mOutputLayer);
     mock::OutputLayer newOutputLayer;
-    mock::LayerFE newLayerFE;
+    sp<mock::LayerFE> newLayerFE = sp<mock::LayerFE>::make();
     LayerFECompositionState layerFECompositionStateTwo;
     layerFECompositionStateTwo.metadata[sMetadataKeyTwo] = sMetadataValueTwo;
-    setupMocksForLayer(newOutputLayer, newLayerFE, outputLayerCompositionState,
+    setupMocksForLayer(newOutputLayer, *newLayerFE, outputLayerCompositionState,
                        layerFECompositionStateTwo);
     auto otherLayerState = std::make_unique<LayerState>(&newOutputLayer);
 
@@ -587,7 +632,7 @@
     OutputLayerCompositionState outputLayerCompositionState;
     outputLayerCompositionState.visibleRegion = sRegionOne;
     LayerFECompositionState layerFECompositionState;
-    setupMocksForLayer(mOutputLayer, mLayerFE, outputLayerCompositionState,
+    setupMocksForLayer(mOutputLayer, *mLayerFE, outputLayerCompositionState,
                        layerFECompositionState);
     mLayerState = std::make_unique<LayerState>(&mOutputLayer);
     EXPECT_TRUE(mLayerState->getVisibleRegion().hasSameRects(sRegionOne));
@@ -597,15 +642,15 @@
     OutputLayerCompositionState outputLayerCompositionState;
     outputLayerCompositionState.visibleRegion = sRegionOne;
     LayerFECompositionState layerFECompositionState;
-    setupMocksForLayer(mOutputLayer, mLayerFE, outputLayerCompositionState,
+    setupMocksForLayer(mOutputLayer, *mLayerFE, outputLayerCompositionState,
                        layerFECompositionState);
     mLayerState = std::make_unique<LayerState>(&mOutputLayer);
 
     mock::OutputLayer newOutputLayer;
-    mock::LayerFE newLayerFE;
+    sp<mock::LayerFE> newLayerFE = sp<mock::LayerFE>::make();
     OutputLayerCompositionState outputLayerCompositionStateTwo;
     outputLayerCompositionStateTwo.visibleRegion = sRegionTwo;
-    setupMocksForLayer(newOutputLayer, newLayerFE, outputLayerCompositionStateTwo,
+    setupMocksForLayer(newOutputLayer, *newLayerFE, outputLayerCompositionStateTwo,
                        layerFECompositionState);
     Flags<LayerStateField> updates = mLayerState->update(&newOutputLayer);
     EXPECT_EQ(Flags<LayerStateField>(LayerStateField::VisibleRegion), updates);
@@ -615,14 +660,14 @@
     OutputLayerCompositionState outputLayerCompositionState;
     outputLayerCompositionState.visibleRegion = sRegionOne;
     LayerFECompositionState layerFECompositionState;
-    setupMocksForLayer(mOutputLayer, mLayerFE, outputLayerCompositionState,
+    setupMocksForLayer(mOutputLayer, *mLayerFE, outputLayerCompositionState,
                        layerFECompositionState);
     mLayerState = std::make_unique<LayerState>(&mOutputLayer);
     mock::OutputLayer newOutputLayer;
-    mock::LayerFE newLayerFE;
+    sp<mock::LayerFE> newLayerFE = sp<mock::LayerFE>::make();
     OutputLayerCompositionState outputLayerCompositionStateTwo;
     outputLayerCompositionStateTwo.visibleRegion = sRegionTwo;
-    setupMocksForLayer(newOutputLayer, newLayerFE, outputLayerCompositionStateTwo,
+    setupMocksForLayer(newOutputLayer, *newLayerFE, outputLayerCompositionStateTwo,
                        layerFECompositionState);
     auto otherLayerState = std::make_unique<LayerState>(&newOutputLayer);
 
@@ -636,15 +681,15 @@
     OutputLayerCompositionState outputLayerCompositionState;
     outputLayerCompositionState.dataspace = ui::Dataspace::SRGB;
     LayerFECompositionState layerFECompositionState;
-    setupMocksForLayer(mOutputLayer, mLayerFE, outputLayerCompositionState,
+    setupMocksForLayer(mOutputLayer, *mLayerFE, outputLayerCompositionState,
                        layerFECompositionState);
     mLayerState = std::make_unique<LayerState>(&mOutputLayer);
 
     mock::OutputLayer newOutputLayer;
-    mock::LayerFE newLayerFE;
+    sp<mock::LayerFE> newLayerFE = sp<mock::LayerFE>::make();
     OutputLayerCompositionState outputLayerCompositionStateTwo;
     outputLayerCompositionStateTwo.dataspace = ui::Dataspace::DISPLAY_P3;
-    setupMocksForLayer(newOutputLayer, newLayerFE, outputLayerCompositionStateTwo,
+    setupMocksForLayer(newOutputLayer, *newLayerFE, outputLayerCompositionStateTwo,
                        layerFECompositionState);
     Flags<LayerStateField> updates = mLayerState->update(&newOutputLayer);
     EXPECT_EQ(Flags<LayerStateField>(LayerStateField::Dataspace), updates);
@@ -654,14 +699,14 @@
     OutputLayerCompositionState outputLayerCompositionState;
     outputLayerCompositionState.dataspace = ui::Dataspace::SRGB;
     LayerFECompositionState layerFECompositionState;
-    setupMocksForLayer(mOutputLayer, mLayerFE, outputLayerCompositionState,
+    setupMocksForLayer(mOutputLayer, *mLayerFE, outputLayerCompositionState,
                        layerFECompositionState);
     mLayerState = std::make_unique<LayerState>(&mOutputLayer);
     mock::OutputLayer newOutputLayer;
-    mock::LayerFE newLayerFE;
+    sp<mock::LayerFE> newLayerFE = sp<mock::LayerFE>::make();
     OutputLayerCompositionState outputLayerCompositionStateTwo;
     outputLayerCompositionStateTwo.dataspace = ui::Dataspace::DISPLAY_P3;
-    setupMocksForLayer(newOutputLayer, newLayerFE, outputLayerCompositionStateTwo,
+    setupMocksForLayer(newOutputLayer, *newLayerFE, outputLayerCompositionStateTwo,
                        layerFECompositionState);
     auto otherLayerState = std::make_unique<LayerState>(&newOutputLayer);
 
@@ -679,19 +724,19 @@
                               AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN |
                                       AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN,
                               "buffer1");
-    setupMocksForLayer(mOutputLayer, mLayerFE, outputLayerCompositionState,
+    setupMocksForLayer(mOutputLayer, *mLayerFE, outputLayerCompositionState,
                        layerFECompositionState);
     mLayerState = std::make_unique<LayerState>(&mOutputLayer);
 
     mock::OutputLayer newOutputLayer;
-    mock::LayerFE newLayerFE;
+    sp<mock::LayerFE> newLayerFE = sp<mock::LayerFE>::make();
     LayerFECompositionState layerFECompositionStateTwo;
     layerFECompositionStateTwo.buffer =
             new GraphicBuffer(1, 1, PIXEL_FORMAT_RGBX_8888,
                               AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN |
                                       AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN,
                               "buffer2");
-    setupMocksForLayer(newOutputLayer, newLayerFE, outputLayerCompositionState,
+    setupMocksForLayer(newOutputLayer, *newLayerFE, outputLayerCompositionState,
                        layerFECompositionStateTwo);
     Flags<LayerStateField> updates = mLayerState->update(&newOutputLayer);
     EXPECT_EQ(Flags<LayerStateField>(LayerStateField::Buffer) |
@@ -707,18 +752,18 @@
                               AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN |
                                       AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN,
                               "buffer1");
-    setupMocksForLayer(mOutputLayer, mLayerFE, outputLayerCompositionState,
+    setupMocksForLayer(mOutputLayer, *mLayerFE, outputLayerCompositionState,
                        layerFECompositionState);
     mLayerState = std::make_unique<LayerState>(&mOutputLayer);
     mock::OutputLayer newOutputLayer;
-    mock::LayerFE newLayerFE;
+    sp<mock::LayerFE> newLayerFE = sp<mock::LayerFE>::make();
     LayerFECompositionState layerFECompositionStateTwo;
     layerFECompositionStateTwo.buffer =
             new GraphicBuffer(1, 1, PIXEL_FORMAT_RGBX_8888,
                               AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN |
                                       AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN,
                               "buffer2");
-    setupMocksForLayer(newOutputLayer, newLayerFE, outputLayerCompositionState,
+    setupMocksForLayer(newOutputLayer, *newLayerFE, outputLayerCompositionState,
                        layerFECompositionStateTwo);
     auto otherLayerState = std::make_unique<LayerState>(&newOutputLayer);
 
@@ -734,16 +779,16 @@
     LayerFECompositionState layerFECompositionState;
     layerFECompositionState.colorTransformIsIdentity = true;
     layerFECompositionState.colorTransform = mat4();
-    setupMocksForLayer(mOutputLayer, mLayerFE, outputLayerCompositionState,
+    setupMocksForLayer(mOutputLayer, *mLayerFE, outputLayerCompositionState,
                        layerFECompositionState);
     mLayerState = std::make_unique<LayerState>(&mOutputLayer);
 
     mock::OutputLayer newOutputLayer;
-    mock::LayerFE newLayerFE;
+    sp<mock::LayerFE> newLayerFE = sp<mock::LayerFE>::make();
     LayerFECompositionState layerFECompositionStateTwo;
     layerFECompositionStateTwo.colorTransformIsIdentity = false;
     layerFECompositionStateTwo.colorTransform = sMat4One;
-    setupMocksForLayer(newOutputLayer, newLayerFE, outputLayerCompositionState,
+    setupMocksForLayer(newOutputLayer, *newLayerFE, outputLayerCompositionState,
                        layerFECompositionStateTwo);
     Flags<LayerStateField> updates = mLayerState->update(&newOutputLayer);
     EXPECT_EQ(Flags<LayerStateField>(LayerStateField::ColorTransform), updates);
@@ -754,15 +799,15 @@
     LayerFECompositionState layerFECompositionState;
     layerFECompositionState.colorTransformIsIdentity = true;
     layerFECompositionState.colorTransform = mat4();
-    setupMocksForLayer(mOutputLayer, mLayerFE, outputLayerCompositionState,
+    setupMocksForLayer(mOutputLayer, *mLayerFE, outputLayerCompositionState,
                        layerFECompositionState);
     mLayerState = std::make_unique<LayerState>(&mOutputLayer);
     mock::OutputLayer newOutputLayer;
-    mock::LayerFE newLayerFE;
+    sp<mock::LayerFE> newLayerFE = sp<mock::LayerFE>::make();
     LayerFECompositionState layerFECompositionStateTwo;
     layerFECompositionStateTwo.colorTransformIsIdentity = false;
     layerFECompositionStateTwo.colorTransform = sMat4One;
-    setupMocksForLayer(newOutputLayer, newLayerFE, outputLayerCompositionState,
+    setupMocksForLayer(newOutputLayer, *newLayerFE, outputLayerCompositionState,
                        layerFECompositionStateTwo);
     auto otherLayerState = std::make_unique<LayerState>(&newOutputLayer);
 
@@ -776,15 +821,15 @@
     OutputLayerCompositionState outputLayerCompositionState;
     LayerFECompositionState layerFECompositionState;
     layerFECompositionState.sidebandStream = NativeHandle::create(sFakeSidebandStreamOne, false);
-    setupMocksForLayer(mOutputLayer, mLayerFE, outputLayerCompositionState,
+    setupMocksForLayer(mOutputLayer, *mLayerFE, outputLayerCompositionState,
                        layerFECompositionState);
     mLayerState = std::make_unique<LayerState>(&mOutputLayer);
 
     mock::OutputLayer newOutputLayer;
-    mock::LayerFE newLayerFE;
+    sp<mock::LayerFE> newLayerFE = sp<mock::LayerFE>::make();
     LayerFECompositionState layerFECompositionStateTwo;
     layerFECompositionStateTwo.sidebandStream = NativeHandle::create(sFakeSidebandStreamTwo, false);
-    setupMocksForLayer(newOutputLayer, newLayerFE, outputLayerCompositionState,
+    setupMocksForLayer(newOutputLayer, *newLayerFE, outputLayerCompositionState,
                        layerFECompositionStateTwo);
     Flags<LayerStateField> updates = mLayerState->update(&newOutputLayer);
     EXPECT_EQ(Flags<LayerStateField>(LayerStateField::SidebandStream), updates);
@@ -794,14 +839,14 @@
     OutputLayerCompositionState outputLayerCompositionState;
     LayerFECompositionState layerFECompositionState;
     layerFECompositionState.sidebandStream = NativeHandle::create(sFakeSidebandStreamOne, false);
-    setupMocksForLayer(mOutputLayer, mLayerFE, outputLayerCompositionState,
+    setupMocksForLayer(mOutputLayer, *mLayerFE, outputLayerCompositionState,
                        layerFECompositionState);
     mLayerState = std::make_unique<LayerState>(&mOutputLayer);
     mock::OutputLayer newOutputLayer;
-    mock::LayerFE newLayerFE;
+    sp<mock::LayerFE> newLayerFE = sp<mock::LayerFE>::make();
     LayerFECompositionState layerFECompositionStateTwo;
     layerFECompositionStateTwo.sidebandStream = NativeHandle::create(sFakeSidebandStreamTwo, false);
-    setupMocksForLayer(newOutputLayer, newLayerFE, outputLayerCompositionState,
+    setupMocksForLayer(newOutputLayer, *newLayerFE, outputLayerCompositionState,
                        layerFECompositionStateTwo);
     auto otherLayerState = std::make_unique<LayerState>(&newOutputLayer);
 
@@ -815,15 +860,15 @@
     OutputLayerCompositionState outputLayerCompositionState;
     LayerFECompositionState layerFECompositionState;
     layerFECompositionState.color = sHalf4One;
-    setupMocksForLayer(mOutputLayer, mLayerFE, outputLayerCompositionState,
+    setupMocksForLayer(mOutputLayer, *mLayerFE, outputLayerCompositionState,
                        layerFECompositionState);
     mLayerState = std::make_unique<LayerState>(&mOutputLayer);
 
     mock::OutputLayer newOutputLayer;
-    mock::LayerFE newLayerFE;
+    sp<mock::LayerFE> newLayerFE = sp<mock::LayerFE>::make();
     LayerFECompositionState layerFECompositionStateTwo;
     layerFECompositionStateTwo.color = sHalf4Two;
-    setupMocksForLayer(newOutputLayer, newLayerFE, outputLayerCompositionState,
+    setupMocksForLayer(newOutputLayer, *newLayerFE, outputLayerCompositionState,
                        layerFECompositionStateTwo);
     Flags<LayerStateField> updates = mLayerState->update(&newOutputLayer);
     EXPECT_EQ(Flags<LayerStateField>(LayerStateField::SolidColor), updates);
@@ -833,14 +878,14 @@
     OutputLayerCompositionState outputLayerCompositionState;
     LayerFECompositionState layerFECompositionState;
     layerFECompositionState.color = sHalf4One;
-    setupMocksForLayer(mOutputLayer, mLayerFE, outputLayerCompositionState,
+    setupMocksForLayer(mOutputLayer, *mLayerFE, outputLayerCompositionState,
                        layerFECompositionState);
     mLayerState = std::make_unique<LayerState>(&mOutputLayer);
     mock::OutputLayer newOutputLayer;
-    mock::LayerFE newLayerFE;
+    sp<mock::LayerFE> newLayerFE = sp<mock::LayerFE>::make();
     LayerFECompositionState layerFECompositionStateTwo;
     layerFECompositionStateTwo.color = sHalf4Two;
-    setupMocksForLayer(newOutputLayer, newLayerFE, outputLayerCompositionState,
+    setupMocksForLayer(newOutputLayer, *newLayerFE, outputLayerCompositionState,
                        layerFECompositionStateTwo);
     auto otherLayerState = std::make_unique<LayerState>(&newOutputLayer);
 
@@ -854,15 +899,15 @@
     OutputLayerCompositionState outputLayerCompositionState;
     LayerFECompositionState layerFECompositionState;
     layerFECompositionState.backgroundBlurRadius = sBgBlurRadiusOne;
-    setupMocksForLayer(mOutputLayer, mLayerFE, outputLayerCompositionState,
+    setupMocksForLayer(mOutputLayer, *mLayerFE, outputLayerCompositionState,
                        layerFECompositionState);
     mLayerState = std::make_unique<LayerState>(&mOutputLayer);
 
     mock::OutputLayer newOutputLayer;
-    mock::LayerFE newLayerFE;
+    sp<mock::LayerFE> newLayerFE = sp<mock::LayerFE>::make();
     LayerFECompositionState layerFECompositionStateTwo;
     layerFECompositionStateTwo.backgroundBlurRadius = sBgBlurRadiusTwo;
-    setupMocksForLayer(newOutputLayer, newLayerFE, outputLayerCompositionState,
+    setupMocksForLayer(newOutputLayer, *newLayerFE, outputLayerCompositionState,
                        layerFECompositionStateTwo);
     Flags<LayerStateField> updates = mLayerState->update(&newOutputLayer);
     EXPECT_EQ(Flags<LayerStateField>(LayerStateField::BackgroundBlurRadius), updates);
@@ -872,14 +917,14 @@
     OutputLayerCompositionState outputLayerCompositionState;
     LayerFECompositionState layerFECompositionState;
     layerFECompositionState.backgroundBlurRadius = sBgBlurRadiusOne;
-    setupMocksForLayer(mOutputLayer, mLayerFE, outputLayerCompositionState,
+    setupMocksForLayer(mOutputLayer, *mLayerFE, outputLayerCompositionState,
                        layerFECompositionState);
     mLayerState = std::make_unique<LayerState>(&mOutputLayer);
     mock::OutputLayer newOutputLayer;
-    mock::LayerFE newLayerFE;
+    sp<mock::LayerFE> newLayerFE = sp<mock::LayerFE>::make();
     LayerFECompositionState layerFECompositionStateTwo;
     layerFECompositionStateTwo.backgroundBlurRadius = sBgBlurRadiusTwo;
-    setupMocksForLayer(newOutputLayer, newLayerFE, outputLayerCompositionState,
+    setupMocksForLayer(newOutputLayer, *newLayerFE, outputLayerCompositionState,
                        layerFECompositionStateTwo);
     auto otherLayerState = std::make_unique<LayerState>(&newOutputLayer);
 
@@ -894,15 +939,15 @@
     OutputLayerCompositionState outputLayerCompositionState;
     LayerFECompositionState layerFECompositionState;
     layerFECompositionState.blurRegions.push_back(sBlurRegionOne);
-    setupMocksForLayer(mOutputLayer, mLayerFE, outputLayerCompositionState,
+    setupMocksForLayer(mOutputLayer, *mLayerFE, outputLayerCompositionState,
                        layerFECompositionState);
     mLayerState = std::make_unique<LayerState>(&mOutputLayer);
 
     mock::OutputLayer newOutputLayer;
-    mock::LayerFE newLayerFE;
+    sp<mock::LayerFE> newLayerFE = sp<mock::LayerFE>::make();
     LayerFECompositionState layerFECompositionStateTwo;
     layerFECompositionStateTwo.blurRegions.push_back(sBlurRegionTwo);
-    setupMocksForLayer(newOutputLayer, newLayerFE, outputLayerCompositionState,
+    setupMocksForLayer(newOutputLayer, *newLayerFE, outputLayerCompositionState,
                        layerFECompositionStateTwo);
     Flags<LayerStateField> updates = mLayerState->update(&newOutputLayer);
     EXPECT_EQ(Flags<LayerStateField>(LayerStateField::BlurRegions), updates);
@@ -912,14 +957,14 @@
     OutputLayerCompositionState outputLayerCompositionState;
     LayerFECompositionState layerFECompositionState;
     layerFECompositionState.blurRegions.push_back(sBlurRegionOne);
-    setupMocksForLayer(mOutputLayer, mLayerFE, outputLayerCompositionState,
+    setupMocksForLayer(mOutputLayer, *mLayerFE, outputLayerCompositionState,
                        layerFECompositionState);
     mLayerState = std::make_unique<LayerState>(&mOutputLayer);
     mock::OutputLayer newOutputLayer;
-    mock::LayerFE newLayerFE;
+    sp<mock::LayerFE> newLayerFE = sp<mock::LayerFE>::make();
     LayerFECompositionState layerFECompositionStateTwo;
     layerFECompositionStateTwo.blurRegions.push_back(sBlurRegionTwo);
-    setupMocksForLayer(newOutputLayer, newLayerFE, outputLayerCompositionState,
+    setupMocksForLayer(newOutputLayer, *newLayerFE, outputLayerCompositionState,
                        layerFECompositionStateTwo);
     auto otherLayerState = std::make_unique<LayerState>(&newOutputLayer);
 
@@ -932,7 +977,7 @@
 TEST_F(LayerStateTest, hasBlurBehind_noBlur_returnsFalse) {
     OutputLayerCompositionState outputLayerCompositionState;
     LayerFECompositionState layerFECompositionState;
-    setupMocksForLayer(mOutputLayer, mLayerFE, outputLayerCompositionState,
+    setupMocksForLayer(mOutputLayer, *mLayerFE, outputLayerCompositionState,
                        layerFECompositionState);
     mLayerState = std::make_unique<LayerState>(&mOutputLayer);
     EXPECT_FALSE(mLayerState->hasBlurBehind());
@@ -942,7 +987,7 @@
     OutputLayerCompositionState outputLayerCompositionState;
     LayerFECompositionState layerFECompositionState;
     layerFECompositionState.backgroundBlurRadius = sBgBlurRadiusOne;
-    setupMocksForLayer(mOutputLayer, mLayerFE, outputLayerCompositionState,
+    setupMocksForLayer(mOutputLayer, *mLayerFE, outputLayerCompositionState,
                        layerFECompositionState);
     mLayerState = std::make_unique<LayerState>(&mOutputLayer);
     EXPECT_TRUE(mLayerState->hasBlurBehind());
@@ -952,7 +997,7 @@
     OutputLayerCompositionState outputLayerCompositionState;
     LayerFECompositionState layerFECompositionState;
     layerFECompositionState.blurRegions.push_back(sBlurRegionOne);
-    setupMocksForLayer(mOutputLayer, mLayerFE, outputLayerCompositionState,
+    setupMocksForLayer(mOutputLayer, *mLayerFE, outputLayerCompositionState,
                        layerFECompositionState);
     mLayerState = std::make_unique<LayerState>(&mOutputLayer);
     EXPECT_TRUE(mLayerState->hasBlurBehind());
@@ -961,7 +1006,7 @@
 TEST_F(LayerStateTest, dumpDoesNotCrash) {
     OutputLayerCompositionState outputLayerCompositionState;
     LayerFECompositionState layerFECompositionState;
-    setupMocksForLayer(mOutputLayer, mLayerFE, outputLayerCompositionState,
+    setupMocksForLayer(mOutputLayer, *mLayerFE, outputLayerCompositionState,
                        layerFECompositionState);
     mLayerState = std::make_unique<LayerState>(&mOutputLayer);
     std::string dump;
@@ -972,7 +1017,7 @@
 TEST_F(LayerStateTest, framesSinceBufferUpdate) {
     OutputLayerCompositionState outputLayerCompositionState;
     LayerFECompositionState layerFECompositionState;
-    setupMocksForLayer(mOutputLayer, mLayerFE, outputLayerCompositionState,
+    setupMocksForLayer(mOutputLayer, *mLayerFE, outputLayerCompositionState,
                        layerFECompositionState);
     mLayerState = std::make_unique<LayerState>(&mOutputLayer);
 
@@ -987,14 +1032,14 @@
     OutputLayerCompositionState outputLayerCompositionState;
     outputLayerCompositionState.displayFrame = sRectOne;
     LayerFECompositionState layerFECompositionState;
-    setupMocksForLayer(mOutputLayer, mLayerFE, outputLayerCompositionState,
+    setupMocksForLayer(mOutputLayer, *mLayerFE, outputLayerCompositionState,
                        layerFECompositionState);
     mLayerState = std::make_unique<LayerState>(&mOutputLayer);
     mock::OutputLayer newOutputLayer;
-    mock::LayerFE newLayerFE;
+    sp<mock::LayerFE> newLayerFE = sp<mock::LayerFE>::make();
     OutputLayerCompositionState outputLayerCompositionStateTwo;
     outputLayerCompositionStateTwo.displayFrame = sRectTwo;
-    setupMocksForLayer(newOutputLayer, newLayerFE, outputLayerCompositionStateTwo,
+    setupMocksForLayer(newOutputLayer, *newLayerFE, outputLayerCompositionStateTwo,
                        layerFECompositionState);
     auto otherLayerState = std::make_unique<LayerState>(&newOutputLayer);
 
@@ -1006,14 +1051,14 @@
     OutputLayerCompositionState outputLayerCompositionState;
     outputLayerCompositionState.displayFrame = sRectOne;
     LayerFECompositionState layerFECompositionState;
-    setupMocksForLayer(mOutputLayer, mLayerFE, outputLayerCompositionState,
+    setupMocksForLayer(mOutputLayer, *mLayerFE, outputLayerCompositionState,
                        layerFECompositionState);
     mLayerState = std::make_unique<LayerState>(&mOutputLayer);
     mock::OutputLayer newOutputLayer;
-    mock::LayerFE newLayerFE;
+    sp<mock::LayerFE> newLayerFE = sp<mock::LayerFE>::make();
     OutputLayerCompositionState outputLayerCompositionStateTwo;
     outputLayerCompositionStateTwo.displayFrame = sRectTwo;
-    setupMocksForLayer(newOutputLayer, newLayerFE, outputLayerCompositionStateTwo,
+    setupMocksForLayer(newOutputLayer, *newLayerFE, outputLayerCompositionStateTwo,
                        layerFECompositionState);
     auto otherLayerState = std::make_unique<LayerState>(&newOutputLayer);
 
@@ -1025,15 +1070,15 @@
     OutputLayerCompositionState outputLayerCompositionState;
     LayerFECompositionState layerFECompositionState;
     layerFECompositionState.buffer = new GraphicBuffer();
-    setupMocksForLayer(mOutputLayer, mLayerFE, outputLayerCompositionState,
+    setupMocksForLayer(mOutputLayer, *mLayerFE, outputLayerCompositionState,
                        layerFECompositionState);
     mLayerState = std::make_unique<LayerState>(&mOutputLayer);
 
     mock::OutputLayer newOutputLayer;
-    mock::LayerFE newLayerFE;
+    sp<mock::LayerFE> newLayerFE = sp<mock::LayerFE>::make();
     LayerFECompositionState layerFECompositionStateTwo;
     layerFECompositionStateTwo.buffer = new GraphicBuffer();
-    setupMocksForLayer(newOutputLayer, newLayerFE, outputLayerCompositionState,
+    setupMocksForLayer(newOutputLayer, *newLayerFE, outputLayerCompositionState,
                        layerFECompositionStateTwo);
     auto otherLayerState = std::make_unique<LayerState>(&newOutputLayer);
 
diff --git a/services/surfaceflinger/CompositionEngine/tests/planner/PredictorTest.cpp b/services/surfaceflinger/CompositionEngine/tests/planner/PredictorTest.cpp
index 1492707..68c72e0 100644
--- a/services/surfaceflinger/CompositionEngine/tests/planner/PredictorTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/planner/PredictorTest.cpp
@@ -24,6 +24,10 @@
 #include <gtest/gtest.h>
 #include <log/log.h>
 
+#include <aidl/android/hardware/graphics/composer3/Composition.h>
+
+using aidl::android::hardware::graphics::composer3::Composition;
+
 namespace android::compositionengine::impl::planner {
 namespace {
 
@@ -69,26 +73,26 @@
 
 TEST_F(LayerStackTest, getApproximateMatch_doesNotMatchSizeDifferences) {
     mock::OutputLayer outputLayerOne;
-    mock::LayerFE layerFEOne;
+    sp<mock::LayerFE> layerFEOne = sp<mock::LayerFE>::make();
     OutputLayerCompositionState outputLayerCompositionStateOne;
     LayerFECompositionState layerFECompositionStateOne;
-    setupMocksForLayer(outputLayerOne, layerFEOne, outputLayerCompositionStateOne,
+    setupMocksForLayer(outputLayerOne, *layerFEOne, outputLayerCompositionStateOne,
                        layerFECompositionStateOne);
     LayerState layerStateOne(&outputLayerOne);
 
     mock::OutputLayer outputLayerTwo;
-    mock::LayerFE layerFETwo;
+    sp<mock::LayerFE> layerFETwo = sp<mock::LayerFE>::make();
     OutputLayerCompositionState outputLayerCompositionStateTwo;
     LayerFECompositionState layerFECompositionStateTwo;
-    setupMocksForLayer(outputLayerTwo, layerFETwo, outputLayerCompositionStateTwo,
+    setupMocksForLayer(outputLayerTwo, *layerFETwo, outputLayerCompositionStateTwo,
                        layerFECompositionStateTwo);
     LayerState layerStateTwo(&outputLayerTwo);
 
     mock::OutputLayer outputLayerThree;
-    mock::LayerFE layerFEThree;
+    sp<mock::LayerFE> layerFEThree = sp<mock::LayerFE>::make();
     OutputLayerCompositionState outputLayerCompositionStateThree;
     LayerFECompositionState layerFECompositionStateThree;
-    setupMocksForLayer(outputLayerThree, layerFEThree, outputLayerCompositionStateThree,
+    setupMocksForLayer(outputLayerThree, *layerFEThree, outputLayerCompositionStateThree,
                        layerFECompositionStateThree);
     LayerState layerStateThree(&outputLayerThree);
 
@@ -100,20 +104,20 @@
 
 TEST_F(LayerStackTest, getApproximateMatch_doesNotMatchDifferentCompositionTypes) {
     mock::OutputLayer outputLayerOne;
-    mock::LayerFE layerFEOne;
+    sp<mock::LayerFE> layerFEOne = sp<mock::LayerFE>::make();
     OutputLayerCompositionState outputLayerCompositionStateOne;
     LayerFECompositionState layerFECompositionStateOne;
-    layerFECompositionStateOne.compositionType = hal::Composition::DEVICE;
-    setupMocksForLayer(outputLayerOne, layerFEOne, outputLayerCompositionStateOne,
+    layerFECompositionStateOne.compositionType = Composition::DEVICE;
+    setupMocksForLayer(outputLayerOne, *layerFEOne, outputLayerCompositionStateOne,
                        layerFECompositionStateOne);
     LayerState layerStateOne(&outputLayerOne);
 
     mock::OutputLayer outputLayerTwo;
-    mock::LayerFE layerFETwo;
+    sp<mock::LayerFE> layerFETwo = sp<mock::LayerFE>::make();
     OutputLayerCompositionState outputLayerCompositionStateTwo;
     LayerFECompositionState layerFECompositionStateTwo;
-    layerFECompositionStateTwo.compositionType = hal::Composition::SOLID_COLOR;
-    setupMocksForLayer(outputLayerTwo, layerFETwo, outputLayerCompositionStateTwo,
+    layerFECompositionStateTwo.compositionType = Composition::SOLID_COLOR;
+    setupMocksForLayer(outputLayerTwo, *layerFETwo, outputLayerCompositionStateTwo,
                        layerFECompositionStateTwo);
     LayerState layerStateTwo(&outputLayerTwo);
 
@@ -124,22 +128,22 @@
 
 TEST_F(LayerStackTest, getApproximateMatch_matchesSingleDifferenceInSingleLayer) {
     mock::OutputLayer outputLayerOne;
-    mock::LayerFE layerFEOne;
+    sp<mock::LayerFE> layerFEOne = sp<mock::LayerFE>::make();
     OutputLayerCompositionState outputLayerCompositionStateOne{
             .sourceCrop = sFloatRectOne,
     };
     LayerFECompositionState layerFECompositionStateOne;
-    setupMocksForLayer(outputLayerOne, layerFEOne, outputLayerCompositionStateOne,
+    setupMocksForLayer(outputLayerOne, *layerFEOne, outputLayerCompositionStateOne,
                        layerFECompositionStateOne);
     LayerState layerStateOne(&outputLayerOne);
 
     mock::OutputLayer outputLayerTwo;
-    mock::LayerFE layerFETwo;
+    sp<mock::LayerFE> layerFETwo = sp<mock::LayerFE>::make();
     OutputLayerCompositionState outputLayerCompositionStateTwo{
             .sourceCrop = sFloatRectTwo,
     };
     LayerFECompositionState layerFECompositionStateTwo;
-    setupMocksForLayer(outputLayerTwo, layerFETwo, outputLayerCompositionStateTwo,
+    setupMocksForLayer(outputLayerTwo, *layerFETwo, outputLayerCompositionStateTwo,
                        layerFECompositionStateTwo);
     LayerState layerStateTwo(&outputLayerTwo);
 
@@ -155,22 +159,22 @@
 
 TEST_F(LayerStackTest, getApproximateMatch_matchesSingleDifferenceInMultiLayerStack) {
     mock::OutputLayer outputLayerOne;
-    mock::LayerFE layerFEOne;
+    sp<mock::LayerFE> layerFEOne = sp<mock::LayerFE>::make();
     OutputLayerCompositionState outputLayerCompositionStateOne{
             .sourceCrop = sFloatRectOne,
     };
     LayerFECompositionState layerFECompositionStateOne;
-    setupMocksForLayer(outputLayerOne, layerFEOne, outputLayerCompositionStateOne,
+    setupMocksForLayer(outputLayerOne, *layerFEOne, outputLayerCompositionStateOne,
                        layerFECompositionStateOne);
     LayerState layerStateOne(&outputLayerOne);
 
     mock::OutputLayer outputLayerTwo;
-    mock::LayerFE layerFETwo;
+    sp<mock::LayerFE> layerFETwo = sp<mock::LayerFE>::make();
     OutputLayerCompositionState outputLayerCompositionStateTwo{
             .sourceCrop = sFloatRectTwo,
     };
     LayerFECompositionState layerFECompositionStateTwo;
-    setupMocksForLayer(outputLayerTwo, layerFETwo, outputLayerCompositionStateTwo,
+    setupMocksForLayer(outputLayerTwo, *layerFETwo, outputLayerCompositionStateTwo,
                        layerFECompositionStateTwo);
     LayerState layerStateTwo(&outputLayerTwo);
 
@@ -186,7 +190,7 @@
 
 TEST_F(LayerStackTest, getApproximateMatch_doesNotMatchManyDifferences) {
     mock::OutputLayer outputLayerOne;
-    mock::LayerFE layerFEOne;
+    sp<mock::LayerFE> layerFEOne = sp<mock::LayerFE>::make();
     OutputLayerCompositionState outputLayerCompositionStateOne{
             .visibleRegion = sRegionOne,
             .displayFrame = sRectOne,
@@ -197,12 +201,12 @@
     layerFECompositionStateOne.alpha = sAlphaOne;
     layerFECompositionStateOne.colorTransformIsIdentity = true;
     layerFECompositionStateOne.blendMode = hal::BlendMode::NONE;
-    setupMocksForLayer(outputLayerOne, layerFEOne, outputLayerCompositionStateOne,
+    setupMocksForLayer(outputLayerOne, *layerFEOne, outputLayerCompositionStateOne,
                        layerFECompositionStateOne);
     LayerState layerStateOne(&outputLayerOne);
 
     mock::OutputLayer outputLayerTwo;
-    mock::LayerFE layerFETwo;
+    sp<mock::LayerFE> layerFETwo = sp<mock::LayerFE>::make();
     OutputLayerCompositionState outputLayerCompositionStateTwo{
             .visibleRegion = sRegionTwo,
             .displayFrame = sRectTwo,
@@ -214,7 +218,7 @@
     layerFECompositionStateTwo.colorTransformIsIdentity = false;
     layerFECompositionStateTwo.colorTransform = sMat4One;
     layerFECompositionStateTwo.blendMode = hal::BlendMode::PREMULTIPLIED;
-    setupMocksForLayer(outputLayerTwo, layerFETwo, outputLayerCompositionStateTwo,
+    setupMocksForLayer(outputLayerTwo, *layerFETwo, outputLayerCompositionStateTwo,
                        layerFECompositionStateTwo);
     LayerState layerStateTwo(&outputLayerTwo);
 
@@ -226,20 +230,20 @@
 TEST_F(LayerStackTest, getApproximateMatch_exactMatchesSameBuffer) {
     sp<GraphicBuffer> buffer = new GraphicBuffer();
     mock::OutputLayer outputLayerOne;
-    mock::LayerFE layerFEOne;
+    sp<mock::LayerFE> layerFEOne = sp<mock::LayerFE>::make();
     OutputLayerCompositionState outputLayerCompositionStateOne;
     LayerFECompositionState layerFECompositionStateOne;
     layerFECompositionStateOne.buffer = buffer;
-    setupMocksForLayer(outputLayerOne, layerFEOne, outputLayerCompositionStateOne,
+    setupMocksForLayer(outputLayerOne, *layerFEOne, outputLayerCompositionStateOne,
                        layerFECompositionStateOne);
     LayerState layerStateOne(&outputLayerOne);
 
     mock::OutputLayer outputLayerTwo;
-    mock::LayerFE layerFETwo;
+    sp<mock::LayerFE> layerFETwo = sp<mock::LayerFE>::make();
     OutputLayerCompositionState outputLayerCompositionStateTwo;
     LayerFECompositionState layerFECompositionStateTwo;
     layerFECompositionStateTwo.buffer = buffer;
-    setupMocksForLayer(outputLayerTwo, layerFETwo, outputLayerCompositionStateTwo,
+    setupMocksForLayer(outputLayerTwo, *layerFETwo, outputLayerCompositionStateTwo,
                        layerFECompositionStateTwo);
     LayerState layerStateTwo(&outputLayerTwo);
 
@@ -255,7 +259,7 @@
 
 TEST_F(LayerStackTest, getApproximateMatch_alwaysMatchesClientComposition) {
     mock::OutputLayer outputLayerOne;
-    mock::LayerFE layerFEOne;
+    sp<mock::LayerFE> layerFEOne = sp<mock::LayerFE>::make();
     OutputLayerCompositionState outputLayerCompositionStateOne{
             .visibleRegion = sRegionOne,
             .forceClientComposition = true,
@@ -267,12 +271,12 @@
     layerFECompositionStateOne.buffer = new GraphicBuffer();
     layerFECompositionStateOne.alpha = sAlphaOne;
     layerFECompositionStateOne.colorTransformIsIdentity = true;
-    setupMocksForLayer(outputLayerOne, layerFEOne, outputLayerCompositionStateOne,
+    setupMocksForLayer(outputLayerOne, *layerFEOne, outputLayerCompositionStateOne,
                        layerFECompositionStateOne);
     LayerState layerStateOne(&outputLayerOne);
 
     mock::OutputLayer outputLayerTwo;
-    mock::LayerFE layerFETwo;
+    sp<mock::LayerFE> layerFETwo = sp<mock::LayerFE>::make();
     OutputLayerCompositionState outputLayerCompositionStateTwo{
             .visibleRegion = sRegionTwo,
             .forceClientComposition = true,
@@ -285,7 +289,7 @@
     layerFECompositionStateTwo.alpha = sAlphaTwo;
     layerFECompositionStateTwo.colorTransformIsIdentity = false;
     layerFECompositionStateTwo.colorTransform = sMat4One;
-    setupMocksForLayer(outputLayerTwo, layerFETwo, outputLayerCompositionStateTwo,
+    setupMocksForLayer(outputLayerTwo, *layerFETwo, outputLayerCompositionStateTwo,
                        layerFECompositionStateTwo);
     LayerState layerStateTwo(&outputLayerTwo);
 
@@ -301,24 +305,24 @@
 
 TEST_F(LayerStackTest, getApproximateMatch_doesNotMatchMultipleApproximations) {
     mock::OutputLayer outputLayerOne;
-    mock::LayerFE layerFEOne;
+    sp<mock::LayerFE> layerFEOne = sp<mock::LayerFE>::make();
     OutputLayerCompositionState outputLayerCompositionStateOne{
             .sourceCrop = sFloatRectOne,
     };
     LayerFECompositionState layerFECompositionStateOne;
     layerFECompositionStateOne.buffer = new GraphicBuffer();
-    setupMocksForLayer(outputLayerOne, layerFEOne, outputLayerCompositionStateOne,
+    setupMocksForLayer(outputLayerOne, *layerFEOne, outputLayerCompositionStateOne,
                        layerFECompositionStateOne);
     LayerState layerStateOne(&outputLayerOne);
 
     mock::OutputLayer outputLayerTwo;
-    mock::LayerFE layerFETwo;
+    sp<mock::LayerFE> layerFETwo = sp<mock::LayerFE>::make();
     OutputLayerCompositionState outputLayerCompositionStateTwo{
             .sourceCrop = sFloatRectTwo,
     };
     LayerFECompositionState layerFECompositionStateTwo;
     layerFECompositionStateTwo.buffer = new GraphicBuffer();
-    setupMocksForLayer(outputLayerTwo, layerFETwo, outputLayerCompositionStateTwo,
+    setupMocksForLayer(outputLayerTwo, *layerFETwo, outputLayerCompositionStateTwo,
                        layerFECompositionStateTwo);
     LayerState layerStateTwo(&outputLayerTwo);
 
@@ -344,22 +348,22 @@
 
 TEST_F(LayerStackTest, reorderingChangesNonBufferHash) {
     mock::OutputLayer outputLayerOne;
-    mock::LayerFE layerFEOne;
+    sp<mock::LayerFE> layerFEOne = sp<mock::LayerFE>::make();
     OutputLayerCompositionState outputLayerCompositionStateOne{
             .sourceCrop = sFloatRectOne,
     };
     LayerFECompositionState layerFECompositionStateOne;
-    setupMocksForLayer(outputLayerOne, layerFEOne, outputLayerCompositionStateOne,
+    setupMocksForLayer(outputLayerOne, *layerFEOne, outputLayerCompositionStateOne,
                        layerFECompositionStateOne);
     LayerState layerStateOne(&outputLayerOne);
 
     mock::OutputLayer outputLayerTwo;
-    mock::LayerFE layerFETwo;
+    sp<mock::LayerFE> layerFETwo = sp<mock::LayerFE>::make();
     OutputLayerCompositionState outputLayerCompositionStateTwo{
             .sourceCrop = sFloatRectTwo,
     };
     LayerFECompositionState layerFECompositionStateTwo;
-    setupMocksForLayer(outputLayerTwo, layerFETwo, outputLayerCompositionStateTwo,
+    setupMocksForLayer(outputLayerTwo, *layerFETwo, outputLayerCompositionStateTwo,
                        layerFECompositionStateTwo);
     LayerState layerStateTwo(&outputLayerTwo);
 
@@ -370,7 +374,7 @@
 
 TEST_F(PredictionTest, constructPrediction) {
     Plan plan;
-    plan.addLayerType(hal::Composition::DEVICE);
+    plan.addLayerType(Composition::DEVICE);
 
     Prediction prediction({}, plan);
 
@@ -439,16 +443,16 @@
 
 TEST_F(PredictorTest, getPredictedPlan_recordCandidateAndRetrieveExactMatch) {
     mock::OutputLayer outputLayerOne;
-    mock::LayerFE layerFEOne;
+    sp<mock::LayerFE> layerFEOne = sp<mock::LayerFE>::make();
     OutputLayerCompositionState outputLayerCompositionStateOne;
     LayerFECompositionState layerFECompositionStateOne;
-    layerFECompositionStateOne.compositionType = hal::Composition::DEVICE;
-    setupMocksForLayer(outputLayerOne, layerFEOne, outputLayerCompositionStateOne,
+    layerFECompositionStateOne.compositionType = Composition::DEVICE;
+    setupMocksForLayer(outputLayerOne, *layerFEOne, outputLayerCompositionStateOne,
                        layerFECompositionStateOne);
     LayerState layerStateOne(&outputLayerOne);
 
     Plan plan;
-    plan.addLayerType(hal::Composition::DEVICE);
+    plan.addLayerType(Composition::DEVICE);
 
     Predictor predictor;
 
@@ -464,27 +468,27 @@
 
 TEST_F(PredictorTest, getPredictedPlan_recordCandidateAndRetrieveApproximateMatch) {
     mock::OutputLayer outputLayerOne;
-    mock::LayerFE layerFEOne;
+    sp<mock::LayerFE> layerFEOne = sp<mock::LayerFE>::make();
     OutputLayerCompositionState outputLayerCompositionStateOne{
             .sourceCrop = sFloatRectOne,
     };
     LayerFECompositionState layerFECompositionStateOne;
-    setupMocksForLayer(outputLayerOne, layerFEOne, outputLayerCompositionStateOne,
+    setupMocksForLayer(outputLayerOne, *layerFEOne, outputLayerCompositionStateOne,
                        layerFECompositionStateOne);
     LayerState layerStateOne(&outputLayerOne);
 
     mock::OutputLayer outputLayerTwo;
-    mock::LayerFE layerFETwo;
+    sp<mock::LayerFE> layerFETwo = sp<mock::LayerFE>::make();
     OutputLayerCompositionState outputLayerCompositionStateTwo{
             .sourceCrop = sFloatRectTwo,
     };
     LayerFECompositionState layerFECompositionStateTwo;
-    setupMocksForLayer(outputLayerTwo, layerFETwo, outputLayerCompositionStateTwo,
+    setupMocksForLayer(outputLayerTwo, *layerFETwo, outputLayerCompositionStateTwo,
                        layerFECompositionStateTwo);
     LayerState layerStateTwo(&outputLayerTwo);
 
     Plan plan;
-    plan.addLayerType(hal::Composition::DEVICE);
+    plan.addLayerType(Composition::DEVICE);
 
     Predictor predictor;
 
@@ -501,27 +505,27 @@
 
 TEST_F(PredictorTest, recordMissedPlan_skipsApproximateMatch) {
     mock::OutputLayer outputLayerOne;
-    mock::LayerFE layerFEOne;
+    sp<mock::LayerFE> layerFEOne = sp<mock::LayerFE>::make();
     OutputLayerCompositionState outputLayerCompositionStateOne{
             .sourceCrop = sFloatRectOne,
     };
     LayerFECompositionState layerFECompositionStateOne;
-    setupMocksForLayer(outputLayerOne, layerFEOne, outputLayerCompositionStateOne,
+    setupMocksForLayer(outputLayerOne, *layerFEOne, outputLayerCompositionStateOne,
                        layerFECompositionStateOne);
     LayerState layerStateOne(&outputLayerOne);
 
     mock::OutputLayer outputLayerTwo;
-    mock::LayerFE layerFETwo;
+    sp<mock::LayerFE> layerFETwo = sp<mock::LayerFE>::make();
     OutputLayerCompositionState outputLayerCompositionStateTwo{
             .sourceCrop = sFloatRectTwo,
     };
     LayerFECompositionState layerFECompositionStateTwo;
-    setupMocksForLayer(outputLayerTwo, layerFETwo, outputLayerCompositionStateTwo,
+    setupMocksForLayer(outputLayerTwo, *layerFETwo, outputLayerCompositionStateTwo,
                        layerFECompositionStateTwo);
     LayerState layerStateTwo(&outputLayerTwo);
 
     Plan plan;
-    plan.addLayerType(hal::Composition::DEVICE);
+    plan.addLayerType(Composition::DEVICE);
 
     Predictor predictor;
 
@@ -535,7 +539,7 @@
     EXPECT_EQ(Prediction::Type::Approximate, predictedPlan->type);
 
     Plan planTwo;
-    planTwo.addLayerType(hal::Composition::CLIENT);
+    planTwo.addLayerType(Composition::CLIENT);
     predictor.recordResult(predictedPlan, hashTwo, {&layerStateTwo}, false, planTwo);
     // Now trying to retrieve the predicted plan again returns a nullopt instead.
     // TODO(b/158790260): Even though this is enforced in this test, we might want to reassess this.
diff --git a/services/surfaceflinger/ContainerLayer.cpp b/services/surfaceflinger/ContainerLayer.cpp
index 841e79f..3ccc229 100644
--- a/services/surfaceflinger/ContainerLayer.cpp
+++ b/services/surfaceflinger/ContainerLayer.cpp
@@ -36,8 +36,7 @@
 
 sp<Layer> ContainerLayer::createClone() {
     sp<ContainerLayer> layer = mFlinger->getFactory().createContainerLayer(
-            LayerCreationArgs(mFlinger.get(), nullptr, mName + " (Mirror)", 0, 0, 0,
-                              LayerMetadata()));
+            LayerCreationArgs(mFlinger.get(), nullptr, mName + " (Mirror)", 0, LayerMetadata()));
     layer->setInitialValuesForClone(this);
     return layer;
 }
diff --git a/services/surfaceflinger/DisplayDevice.cpp b/services/surfaceflinger/DisplayDevice.cpp
index 4445eea..f5a4b3d 100644
--- a/services/surfaceflinger/DisplayDevice.cpp
+++ b/services/surfaceflinger/DisplayDevice.cpp
@@ -92,10 +92,12 @@
     }
 
     mCompositionDisplay->createDisplayColorProfile(
-            compositionengine::DisplayColorProfileCreationArgs{args.hasWideColorGamut,
-                                                               std::move(args.hdrCapabilities),
-                                                               args.supportedPerFrameMetadata,
-                                                               args.hwcColorModes});
+            compositionengine::DisplayColorProfileCreationArgsBuilder()
+                    .setHasWideColorGamut(args.hasWideColorGamut)
+                    .setHdrCapabilities(std::move(args.hdrCapabilities))
+                    .setSupportedPerFrameMetadata(args.supportedPerFrameMetadata)
+                    .setHwcColorModes(std::move(args.hwcColorModes))
+                    .Build());
 
     if (!mCompositionDisplay->isValid()) {
         ALOGE("Composition Display did not validate!");
@@ -116,11 +118,11 @@
 }
 
 int DisplayDevice::getWidth() const {
-    return mCompositionDisplay->getState().displaySpace.bounds.getWidth();
+    return mCompositionDisplay->getState().displaySpace.getBounds().width;
 }
 
 int DisplayDevice::getHeight() const {
-    return mCompositionDisplay->getState().displaySpace.bounds.getHeight();
+    return mCompositionDisplay->getState().displaySpace.getBounds().height;
 }
 
 void DisplayDevice::setDisplayName(const std::string& displayName) {
@@ -139,7 +141,38 @@
     return mCompositionDisplay->getRenderSurface()->getPageFlipCount();
 }
 
-// ----------------------------------------------------------------------------
+auto DisplayDevice::getInputInfo() const -> InputInfo {
+    gui::DisplayInfo info;
+    info.displayId = getLayerStack().id;
+
+    // The physical orientation is set when the orientation of the display panel is
+    // different than the default orientation of the device. Other services like
+    // InputFlinger do not know about this, so we do not need to expose the physical
+    // orientation of the panel outside of SurfaceFlinger.
+    const ui::Rotation inversePhysicalOrientation = ui::ROTATION_0 - mPhysicalOrientation;
+    auto width = getWidth();
+    auto height = getHeight();
+    if (inversePhysicalOrientation == ui::ROTATION_90 ||
+        inversePhysicalOrientation == ui::ROTATION_270) {
+        std::swap(width, height);
+    }
+    const ui::Transform undoPhysicalOrientation(ui::Transform::toRotationFlags(
+                                                        inversePhysicalOrientation),
+                                                width, height);
+    const auto& displayTransform = undoPhysicalOrientation * getTransform();
+    // Send the inverse display transform to input so it can convert display coordinates to
+    // logical display.
+    info.transform = displayTransform.inverse();
+
+    info.logicalWidth = getLayerStackSpaceRect().width();
+    info.logicalHeight = getLayerStackSpaceRect().height();
+
+    return {.info = info,
+            .transform = displayTransform,
+            .receivesInput = receivesInput(),
+            .isSecure = isSecure()};
+}
+
 void DisplayDevice::setPowerMode(hal::PowerMode mode) {
     mPowerMode = mode;
     getCompositionDisplay()->setCompositionEnabled(mPowerMode != hal::PowerMode::OFF);
@@ -163,7 +196,7 @@
     ATRACE_INT(mActiveModeFPSTrace.c_str(), mode->getFps().getIntValue());
     mActiveMode = mode;
     if (mRefreshRateConfigs) {
-        mRefreshRateConfigs->setCurrentModeId(mActiveMode->getId());
+        mRefreshRateConfigs->setActiveModeId(mActiveMode->getId());
     }
     if (mRefreshRateOverlay) {
         mRefreshRateOverlay->changeRefreshRate(mActiveMode->getFps());
@@ -194,12 +227,18 @@
 }
 
 DisplayModePtr DisplayDevice::getMode(DisplayModeId modeId) const {
-    const auto it = std::find_if(mSupportedModes.begin(), mSupportedModes.end(),
-                                 [&](DisplayModePtr mode) { return mode->getId() == modeId; });
+    const DisplayModePtr nullMode;
+    return mSupportedModes.get(modeId).value_or(std::cref(nullMode));
+}
+
+std::optional<DisplayModeId> DisplayDevice::translateModeId(hal::HWConfigId hwcId) const {
+    const auto it =
+            std::find_if(mSupportedModes.begin(), mSupportedModes.end(),
+                         [hwcId](const auto& pair) { return pair.second->getHwcId() == hwcId; });
     if (it != mSupportedModes.end()) {
-        return *it;
+        return it->second->getId();
     }
-    return nullptr;
+    return {};
 }
 
 nsecs_t DisplayDevice::getVsyncPeriodFromHWC() const {
@@ -232,7 +271,7 @@
 }
 
 void DisplayDevice::setLayerStack(ui::LayerStack stack) {
-    mCompositionDisplay->setLayerStackFilter(stack, isInternal());
+    mCompositionDisplay->setLayerFilter({stack, isInternal()});
     if (mRefreshRateOverlay) {
         mRefreshRateOverlay->setLayerStack(stack);
     }
@@ -262,13 +301,15 @@
     if (!orientedDisplaySpaceRect.isValid()) {
         // The destination frame can be invalid if it has never been set,
         // in that case we assume the whole display size.
-        orientedDisplaySpaceRect = getCompositionDisplay()->getState().displaySpace.bounds;
+        orientedDisplaySpaceRect =
+                getCompositionDisplay()->getState().displaySpace.getBoundsAsRect();
     }
 
     if (layerStackSpaceRect.isEmpty()) {
         // The layerStackSpaceRect can be invalid if it has never been set, in that case
         // we assume the whole framebuffer size.
-        layerStackSpaceRect = getCompositionDisplay()->getState().framebufferSpace.bounds;
+        layerStackSpaceRect =
+                getCompositionDisplay()->getState().framebufferSpace.getBoundsAsRect();
         if (orientation == ui::ROTATION_90 || orientation == ui::ROTATION_270) {
             std::swap(layerStackSpaceRect.right, layerStackSpaceRect.bottom);
         }
@@ -281,6 +322,22 @@
                                            orientedDisplaySpaceRect);
 }
 
+void DisplayDevice::stageBrightness(float brightness) {
+    mStagedBrightness = brightness;
+}
+
+void DisplayDevice::persistBrightness(bool needsComposite) {
+    if (needsComposite && mStagedBrightness && mBrightness != *mStagedBrightness) {
+        getCompositionDisplay()->setNextBrightness(*mStagedBrightness);
+        mBrightness = *mStagedBrightness;
+    }
+    mStagedBrightness = std::nullopt;
+}
+
+std::optional<float> DisplayDevice::getStagedBrightness() const {
+    return mStagedBrightness;
+}
+
 ui::Transform::RotationFlags DisplayDevice::getPrimaryDisplayRotationFlags() {
     return sPrimaryDisplayRotationFlags;
 }
@@ -304,12 +361,12 @@
                   activeMode ? to_string(*activeMode).c_str() : "none");
 
     result.append("   supportedModes=\n");
-
-    for (const auto& mode : mSupportedModes) {
-        result.append("     ");
+    for (const auto& [id, mode] : mSupportedModes) {
+        result.append("      ");
         result.append(to_string(*mode));
-        result.append("\n");
+        result.push_back('\n');
     }
+
     StringAppendF(&result, "   deviceProductInfo=");
     if (mDeviceProductInfo) {
         mDeviceProductInfo->dump(result);
@@ -336,8 +393,8 @@
     return mCompositionDisplay->isSecure();
 }
 
-const Rect& DisplayDevice::getBounds() const {
-    return mCompositionDisplay->getState().displaySpace.bounds;
+const Rect DisplayDevice::getBounds() const {
+    return mCompositionDisplay->getState().displaySpace.getBoundsAsRect();
 }
 
 const Region& DisplayDevice::getUndefinedRegion() const {
@@ -349,7 +406,7 @@
 }
 
 ui::LayerStack DisplayDevice::getLayerStack() const {
-    return mCompositionDisplay->getState().layerStackId;
+    return mCompositionDisplay->getState().layerFilter.layerStack;
 }
 
 ui::Transform::RotationFlags DisplayDevice::getTransformHint() const {
@@ -361,11 +418,11 @@
 }
 
 const Rect& DisplayDevice::getLayerStackSpaceRect() const {
-    return mCompositionDisplay->getState().layerStackSpace.content;
+    return mCompositionDisplay->getState().layerStackSpace.getContent();
 }
 
 const Rect& DisplayDevice::getOrientedDisplaySpaceRect() const {
-    return mCompositionDisplay->getState().orientedDisplaySpace.content;
+    return mCompositionDisplay->getState().orientedDisplaySpace.getContent();
 }
 
 bool DisplayDevice::hasWideColorGamut() const {
@@ -436,9 +493,9 @@
     return false;
 }
 
-void DisplayDevice::onInvalidate() {
+void DisplayDevice::animateRefreshRateOverlay() {
     if (mRefreshRateOverlay) {
-        mRefreshRateOverlay->onInvalidate();
+        mRefreshRateOverlay->animate();
     }
 }
 
@@ -453,7 +510,7 @@
     std::scoped_lock lock(mActiveModeLock);
     if (mDesiredActiveModeChanged) {
         // If a mode change is pending, just cache the latest request in mDesiredActiveMode
-        const Scheduler::ModeEvent prevConfig = mDesiredActiveMode.event;
+        const auto prevConfig = mDesiredActiveMode.event;
         mDesiredActiveMode = info;
         mDesiredActiveMode.event = mDesiredActiveMode.event | prevConfig;
         return false;
@@ -478,7 +535,7 @@
 
 void DisplayDevice::clearDesiredActiveModeState() {
     std::scoped_lock lock(mActiveModeLock);
-    mDesiredActiveMode.event = Scheduler::ModeEvent::None;
+    mDesiredActiveMode.event = scheduler::DisplayModeEvent::None;
     mDesiredActiveModeChanged = false;
 }
 
diff --git a/services/surfaceflinger/DisplayDevice.h b/services/surfaceflinger/DisplayDevice.h
index 4d435c7..2c4a300 100644
--- a/services/surfaceflinger/DisplayDevice.h
+++ b/services/surfaceflinger/DisplayDevice.h
@@ -41,7 +41,7 @@
 
 #include "MainThreadGuard.h"
 
-#include "DisplayHardware/DisplayIdentification.h"
+#include <ui/DisplayIdentification.h>
 #include "DisplayHardware/DisplayMode.h"
 #include "DisplayHardware/Hal.h"
 #include "DisplayHardware/PowerAdvisor.h"
@@ -86,9 +86,7 @@
 
     bool isVirtual() const { return !mConnectionType; }
     bool isPrimary() const { return mIsPrimary; }
-    bool isInternal() const {
-        return !isVirtual() && mConnectionType == ui::DisplayConnectionType::Internal;
-    }
+    bool isInternal() const { return mConnectionType == ui::DisplayConnectionType::Internal; }
 
     // isSecure indicates whether this display can be trusted to display
     // secure surfaces.
@@ -101,6 +99,9 @@
     void setLayerStack(ui::LayerStack);
     void setDisplaySize(int width, int height);
     void setProjection(ui::Rotation orientation, Rect viewport, Rect frame);
+    void stageBrightness(float brightness) REQUIRES(SF_MAIN_THREAD);
+    void persistBrightness(bool needsComposite) REQUIRES(SF_MAIN_THREAD);
+    bool isBrightnessStale() const REQUIRES(SF_MAIN_THREAD);
     void setFlags(uint32_t flags);
 
     ui::Rotation getPhysicalOrientation() const { return mPhysicalOrientation; }
@@ -108,6 +109,7 @@
 
     static ui::Transform::RotationFlags getPrimaryDisplayRotationFlags();
 
+    std::optional<float> getStagedBrightness() const REQUIRES(SF_MAIN_THREAD);
     ui::Transform::RotationFlags getTransformHint() const;
     const ui::Transform& getTransform() const;
     const Rect& getLayerStackSpaceRect() const;
@@ -158,8 +160,8 @@
     // Return true if intent is supported by the display.
     bool hasRenderIntent(ui::RenderIntent intent) const;
 
-    const Rect& getBounds() const;
-    const Rect& bounds() const { return getBounds(); }
+    const Rect getBounds() const;
+    const Rect bounds() const { return getBounds(); }
 
     void setDisplayName(const std::string& displayName);
     const std::string& getDisplayName() const { return mDisplayName; }
@@ -169,6 +171,15 @@
         return mDeviceProductInfo;
     }
 
+    struct InputInfo {
+        gui::DisplayInfo info;
+        ui::Transform transform;
+        bool receivesInput;
+        bool isSecure;
+    };
+
+    InputInfo getInputInfo() const;
+
     /* ------------------------------------------------------------------------
      * Display power mode management.
      */
@@ -188,7 +199,7 @@
 
     struct ActiveModeInfo {
         DisplayModePtr mode;
-        scheduler::RefreshRateConfigEvent event = scheduler::RefreshRateConfigEvent::None;
+        scheduler::DisplayModeEvent event = scheduler::DisplayModeEvent::None;
 
         bool operator!=(const ActiveModeInfo& other) const {
             return mode != other.mode || event != other.event;
@@ -218,6 +229,8 @@
     // set-top boxes after a hotplug reconnect.
     DisplayModePtr getMode(DisplayModeId) const;
 
+    std::optional<DisplayModeId> translateModeId(hal::HWConfigId) const;
+
     // Returns the refresh rate configs for this display.
     scheduler::RefreshRateConfigs& refreshRateConfigs() const { return *mRefreshRateConfigs; }
 
@@ -232,7 +245,7 @@
     void enableRefreshRateOverlay(bool enable, bool showSpinner);
     bool isRefreshRateOverlayEnabled() const { return mRefreshRateOverlay != nullptr; }
     bool onKernelTimerChanged(std::optional<DisplayModeId>, bool timerExpired);
-    void onInvalidate();
+    void animateRefreshRateOverlay();
 
     void onVsync(nsecs_t timestamp);
     nsecs_t getVsyncPeriodFromHWC() const;
@@ -269,11 +282,13 @@
     hardware::graphics::composer::hal::PowerMode mPowerMode =
             hardware::graphics::composer::hal::PowerMode::OFF;
     DisplayModePtr mActiveMode;
+    std::optional<float> mStagedBrightness = std::nullopt;
+    float mBrightness = -1.f;
     const DisplayModes mSupportedModes;
 
     std::atomic<nsecs_t> mLastHwVsync = 0;
 
-    // TODO(b/74619554): Remove special cases for primary display.
+    // TODO(b/182939859): Remove special cases for primary display.
     const bool mIsPrimary;
 
     uint32_t mFlags = 0;
@@ -311,7 +326,7 @@
     int32_t sequenceId = sNextSequenceId++;
     std::optional<Physical> physical;
     sp<IGraphicBufferProducer> surface;
-    ui::LayerStack layerStack = ui::NO_LAYER_STACK;
+    ui::LayerStack layerStack;
     uint32_t flags = 0;
     Rect layerStackSpaceRect;
     Rect orientedDisplaySpaceRect;
@@ -354,16 +369,4 @@
     DisplayModeId activeModeId;
 };
 
-// Predicates for display lookup.
-
-struct WithLayerStack {
-    explicit WithLayerStack(ui::LayerStack layerStack) : layerStack(layerStack) {}
-
-    bool operator()(const DisplayDevice& display) const {
-        return display.getLayerStack() == layerStack;
-    }
-
-    ui::LayerStack layerStack;
-};
-
 } // namespace android
diff --git a/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp b/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp
new file mode 100644
index 0000000..297a776
--- /dev/null
+++ b/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp
@@ -0,0 +1,1142 @@
+/*
+ * 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.
+ */
+
+#undef LOG_TAG
+#define LOG_TAG "HwcComposer"
+#define ATRACE_TAG ATRACE_TAG_GRAPHICS
+
+#include "AidlComposerHal.h"
+
+#include <android-base/file.h>
+#include <android/binder_ibinder_platform.h>
+#include <android/binder_manager.h>
+#include <log/log.h>
+#include <utils/Trace.h>
+
+#include <aidl/android/hardware/graphics/composer3/BnComposerCallback.h>
+
+#include <algorithm>
+#include <cinttypes>
+
+#include "HWC2.h"
+
+namespace android {
+
+using hardware::hidl_handle;
+using hardware::hidl_vec;
+using hardware::Return;
+
+using aidl::android::hardware::graphics::composer3::BnComposerCallback;
+using aidl::android::hardware::graphics::composer3::Capability;
+using aidl::android::hardware::graphics::composer3::PowerMode;
+using aidl::android::hardware::graphics::composer3::VirtualDisplay;
+
+using aidl::android::hardware::graphics::composer3::CommandResultPayload;
+
+using AidlColorMode = aidl::android::hardware::graphics::composer3::ColorMode;
+using AidlContentType = aidl::android::hardware::graphics::composer3::ContentType;
+using AidlDisplayIdentification =
+        aidl::android::hardware::graphics::composer3::DisplayIdentification;
+using AidlDisplayContentSample = aidl::android::hardware::graphics::composer3::DisplayContentSample;
+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 AidlPerFrameMetadata = aidl::android::hardware::graphics::composer3::PerFrameMetadata;
+using AidlPerFrameMetadataKey = aidl::android::hardware::graphics::composer3::PerFrameMetadataKey;
+using AidlPerFrameMetadataBlob = aidl::android::hardware::graphics::composer3::PerFrameMetadataBlob;
+using AidlRenderIntent = aidl::android::hardware::graphics::composer3::RenderIntent;
+using AidlVsyncPeriodChangeConstraints =
+        aidl::android::hardware::graphics::composer3::VsyncPeriodChangeConstraints;
+using AidlVsyncPeriodChangeTimeline =
+        aidl::android::hardware::graphics::composer3::VsyncPeriodChangeTimeline;
+using AidlDisplayContentSamplingAttributes =
+        aidl::android::hardware::graphics::composer3::DisplayContentSamplingAttributes;
+using AidlFormatColorComponent = aidl::android::hardware::graphics::composer3::FormatColorComponent;
+using AidlDisplayConnectionType =
+        aidl::android::hardware::graphics::composer3::DisplayConnectionType;
+
+using AidlColorTransform = aidl::android::hardware::graphics::common::ColorTransform;
+using AidlDataspace = aidl::android::hardware::graphics::common::Dataspace;
+using AidlFRect = aidl::android::hardware::graphics::common::FRect;
+using AidlRect = aidl::android::hardware::graphics::common::Rect;
+using AidlTransform = aidl::android::hardware::graphics::common::Transform;
+
+namespace Hwc2 {
+
+namespace {
+
+template <typename To, typename From>
+To translate(From x) {
+    return static_cast<To>(x);
+}
+
+template <typename To, typename From>
+std::vector<To> translate(const std::vector<From>& in) {
+    std::vector<To> out;
+    out.reserve(in.size());
+    std::transform(in.begin(), in.end(), std::back_inserter(out),
+                   [](From x) { return translate<To>(x); });
+    return out;
+}
+
+template <>
+AidlRect translate(IComposerClient::Rect x) {
+    return AidlRect{
+            .left = x.left,
+            .top = x.top,
+            .right = x.right,
+            .bottom = x.bottom,
+    };
+}
+
+template <>
+AidlFRect translate(IComposerClient::FRect x) {
+    return AidlFRect{
+            .left = x.left,
+            .top = x.top,
+            .right = x.right,
+            .bottom = x.bottom,
+    };
+}
+
+template <>
+AidlPerFrameMetadataBlob translate(IComposerClient::PerFrameMetadataBlob x) {
+    AidlPerFrameMetadataBlob blob;
+    blob.key = translate<AidlPerFrameMetadataKey>(x.key),
+    std::copy(x.blob.begin(), x.blob.end(), std::inserter(blob.blob, blob.blob.end()));
+    return blob;
+}
+
+template <>
+AidlPerFrameMetadata translate(IComposerClient::PerFrameMetadata x) {
+    return AidlPerFrameMetadata{
+            .key = translate<AidlPerFrameMetadataKey>(x.key),
+            .value = x.value,
+    };
+}
+
+template <>
+DisplayedFrameStats translate(AidlDisplayContentSample x) {
+    return DisplayedFrameStats{
+            .numFrames = static_cast<uint64_t>(x.frameCount),
+            .component_0_sample = translate<uint64_t>(x.sampleComponent0),
+            .component_1_sample = translate<uint64_t>(x.sampleComponent1),
+            .component_2_sample = translate<uint64_t>(x.sampleComponent2),
+            .component_3_sample = translate<uint64_t>(x.sampleComponent3),
+    };
+}
+
+template <>
+AidlVsyncPeriodChangeConstraints translate(IComposerClient::VsyncPeriodChangeConstraints x) {
+    return AidlVsyncPeriodChangeConstraints{
+            .desiredTimeNanos = x.desiredTimeNanos,
+            .seamlessRequired = x.seamlessRequired,
+    };
+}
+
+template <>
+VsyncPeriodChangeTimeline translate(AidlVsyncPeriodChangeTimeline x) {
+    return VsyncPeriodChangeTimeline{
+            .newVsyncAppliedTimeNanos = x.newVsyncAppliedTimeNanos,
+            .refreshRequired = x.refreshRequired,
+            .refreshTimeNanos = x.refreshTimeNanos,
+    };
+}
+
+template <>
+IComposerClient::ClientTargetProperty translate(ClientTargetProperty x) {
+    return IComposerClient::ClientTargetProperty{
+            .pixelFormat = translate<PixelFormat>(x.pixelFormat),
+            .dataspace = translate<Dataspace>(x.dataspace),
+    };
+}
+
+mat4 makeMat4(std::vector<float> in) {
+    return mat4(static_cast<const float*>(in.data()));
+}
+
+} // namespace
+
+class AidlIComposerCallbackWrapper : public BnComposerCallback {
+public:
+    AidlIComposerCallbackWrapper(HWC2::ComposerCallback& callback) : mCallback(callback) {}
+
+    ::ndk::ScopedAStatus onHotplug(int64_t in_display, bool in_connected) override {
+        const auto connection = in_connected ? V2_4::IComposerCallback::Connection::CONNECTED
+                                             : V2_4::IComposerCallback::Connection::DISCONNECTED;
+        mCallback.onComposerHalHotplug(translate<Display>(in_display), connection);
+        return ::ndk::ScopedAStatus::ok();
+    }
+
+    ::ndk::ScopedAStatus onRefresh(int64_t in_display) override {
+        mCallback.onComposerHalRefresh(translate<Display>(in_display));
+        return ::ndk::ScopedAStatus::ok();
+    }
+
+    ::ndk::ScopedAStatus onSeamlessPossible(int64_t in_display) override {
+        mCallback.onComposerHalSeamlessPossible(translate<Display>(in_display));
+        return ::ndk::ScopedAStatus::ok();
+    }
+
+    ::ndk::ScopedAStatus onVsync(int64_t in_display, int64_t in_timestamp,
+                                 int32_t in_vsyncPeriodNanos) override {
+        mCallback.onComposerHalVsync(translate<Display>(in_display), in_timestamp,
+                                     static_cast<uint32_t>(in_vsyncPeriodNanos));
+        return ::ndk::ScopedAStatus::ok();
+    }
+
+    ::ndk::ScopedAStatus onVsyncPeriodTimingChanged(
+            int64_t in_display, const AidlVsyncPeriodChangeTimeline& in_updatedTimeline) override {
+        mCallback.onComposerHalVsyncPeriodTimingChanged(translate<Display>(in_display),
+                                                        translate<V2_4::VsyncPeriodChangeTimeline>(
+                                                                in_updatedTimeline));
+        return ::ndk::ScopedAStatus::ok();
+    }
+
+    ::ndk::ScopedAStatus onVsyncIdle(int64_t in_display) override {
+        mCallback.onComposerHalVsyncIdle(translate<Display>(in_display));
+        return ::ndk::ScopedAStatus::ok();
+    }
+
+private:
+    HWC2::ComposerCallback& mCallback;
+};
+
+std::string AidlComposer::instance(const std::string& serviceName) {
+    return std::string(AidlIComposer::descriptor) + "/" + serviceName;
+}
+
+bool AidlComposer::isDeclared(const std::string& serviceName) {
+    return AServiceManager_isDeclared(instance(serviceName).c_str());
+}
+
+AidlComposer::AidlComposer(const std::string& serviceName) {
+    // This only waits if the service is actually declared
+    mAidlComposer = AidlIComposer::fromBinder(
+            ndk::SpAIBinder(AServiceManager_waitForService(instance(serviceName).c_str())));
+    if (!mAidlComposer) {
+        LOG_ALWAYS_FATAL("Failed to get AIDL composer service");
+        return;
+    }
+
+    if (!mAidlComposer->createClient(&mAidlComposerClient).isOk()) {
+        LOG_ALWAYS_FATAL("Can't create AidlComposerClient, fallback to HIDL");
+        return;
+    }
+
+    ALOGI("Loaded AIDL composer3 HAL service");
+}
+
+AidlComposer::~AidlComposer() = default;
+
+bool AidlComposer::isSupported(OptionalFeature feature) const {
+    switch (feature) {
+        case OptionalFeature::RefreshRateSwitching:
+        case OptionalFeature::ExpectedPresentTime:
+        case OptionalFeature::DisplayBrightnessCommand:
+        case OptionalFeature::KernelIdleTimer:
+        case OptionalFeature::PhysicalDisplayOrientation:
+            return true;
+    }
+}
+
+std::vector<Capability> AidlComposer::getCapabilities() {
+    std::vector<Capability> capabilities;
+    const auto status = mAidlComposer->getCapabilities(&capabilities);
+    if (!status.isOk()) {
+        ALOGE("getCapabilities failed %s", status.getDescription().c_str());
+        return {};
+    }
+    return capabilities;
+}
+
+std::string AidlComposer::dumpDebugInfo() {
+    int pipefds[2];
+    int result = pipe(pipefds);
+    if (result < 0) {
+        ALOGE("dumpDebugInfo: pipe failed: %s", strerror(errno));
+        return {};
+    }
+
+    std::string str;
+    const auto status = mAidlComposer->dump(pipefds[1], /*args*/ nullptr, /*numArgs*/ 0);
+    // Close the write-end of the pipe to make sure that when reading from the
+    // read-end we will get eof instead of blocking forever
+    close(pipefds[1]);
+
+    if (status == STATUS_OK) {
+        base::ReadFdToString(pipefds[0], &str);
+    } else {
+        ALOGE("dumpDebugInfo: dump failed: %d", status);
+    }
+
+    close(pipefds[0]);
+    return str;
+}
+
+void AidlComposer::registerCallback(HWC2::ComposerCallback& callback) {
+    if (mAidlComposerCallback) {
+        ALOGE("Callback already registered");
+    }
+
+    mAidlComposerCallback = ndk::SharedRefBase::make<AidlIComposerCallbackWrapper>(callback);
+    AIBinder_setMinSchedulerPolicy(mAidlComposerCallback->asBinder().get(), SCHED_FIFO, 2);
+
+    const auto status = mAidlComposerClient->registerCallback(mAidlComposerCallback);
+    if (!status.isOk()) {
+        ALOGE("registerCallback failed %s", status.getDescription().c_str());
+    }
+}
+
+void AidlComposer::resetCommands() {
+    mWriter.reset();
+}
+
+Error AidlComposer::executeCommands() {
+    return execute();
+}
+
+uint32_t AidlComposer::getMaxVirtualDisplayCount() {
+    int32_t count = 0;
+    const auto status = mAidlComposerClient->getMaxVirtualDisplayCount(&count);
+    if (!status.isOk()) {
+        ALOGE("getMaxVirtualDisplayCount failed %s", status.getDescription().c_str());
+        return 0;
+    }
+    return static_cast<uint32_t>(count);
+}
+
+Error AidlComposer::createVirtualDisplay(uint32_t width, uint32_t height, PixelFormat* format,
+                                         Display* outDisplay) {
+    using AidlPixelFormat = aidl::android::hardware::graphics::common::PixelFormat;
+    const int32_t bufferSlotCount = 1;
+    VirtualDisplay virtualDisplay;
+    const auto status =
+            mAidlComposerClient->createVirtualDisplay(static_cast<int32_t>(width),
+                                                      static_cast<int32_t>(height),
+                                                      static_cast<AidlPixelFormat>(*format),
+                                                      bufferSlotCount, &virtualDisplay);
+
+    if (!status.isOk()) {
+        ALOGE("createVirtualDisplay failed %s", status.getDescription().c_str());
+        return static_cast<Error>(status.getServiceSpecificError());
+    }
+
+    *outDisplay = translate<Display>(virtualDisplay.display);
+    *format = static_cast<PixelFormat>(virtualDisplay.format);
+    return Error::NONE;
+}
+
+Error AidlComposer::destroyVirtualDisplay(Display display) {
+    const auto status = mAidlComposerClient->destroyVirtualDisplay(translate<int64_t>(display));
+    if (!status.isOk()) {
+        ALOGE("destroyVirtualDisplay failed %s", status.getDescription().c_str());
+        return static_cast<Error>(status.getServiceSpecificError());
+    }
+    return Error::NONE;
+}
+
+Error AidlComposer::acceptDisplayChanges(Display display) {
+    mWriter.acceptDisplayChanges(translate<int64_t>(display));
+    return Error::NONE;
+}
+
+Error AidlComposer::createLayer(Display display, Layer* outLayer) {
+    int64_t layer;
+    const auto status = mAidlComposerClient->createLayer(translate<int64_t>(display),
+                                                         kMaxLayerBufferCount, &layer);
+    if (!status.isOk()) {
+        ALOGE("createLayer failed %s", status.getDescription().c_str());
+        return static_cast<Error>(status.getServiceSpecificError());
+    }
+
+    *outLayer = translate<Layer>(layer);
+    return Error::NONE;
+}
+
+Error AidlComposer::destroyLayer(Display display, Layer layer) {
+    const auto status = mAidlComposerClient->destroyLayer(translate<int64_t>(display),
+                                                          translate<int64_t>(layer));
+    if (!status.isOk()) {
+        ALOGE("destroyLayer failed %s", status.getDescription().c_str());
+        return static_cast<Error>(status.getServiceSpecificError());
+    }
+    return Error::NONE;
+}
+
+Error AidlComposer::getActiveConfig(Display display, Config* outConfig) {
+    int32_t config;
+    const auto status = mAidlComposerClient->getActiveConfig(translate<int64_t>(display), &config);
+    if (!status.isOk()) {
+        ALOGE("getActiveConfig failed %s", status.getDescription().c_str());
+        return static_cast<Error>(status.getServiceSpecificError());
+    }
+    *outConfig = translate<Config>(config);
+    return Error::NONE;
+}
+
+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));
+    outLayers->reserve(changedLayers.size());
+    outTypes->reserve(changedLayers.size());
+
+    for (const auto& layer : changedLayers) {
+        outLayers->emplace_back(translate<Layer>(layer.layer));
+        outTypes->emplace_back(layer.composition);
+    }
+    return Error::NONE;
+}
+
+Error AidlComposer::getColorModes(Display display, std::vector<ColorMode>* outModes) {
+    std::vector<AidlColorMode> modes;
+    const auto status = mAidlComposerClient->getColorModes(translate<int64_t>(display), &modes);
+    if (!status.isOk()) {
+        ALOGE("getColorModes failed %s", status.getDescription().c_str());
+        return static_cast<Error>(status.getServiceSpecificError());
+    }
+    *outModes = translate<ColorMode>(modes);
+    return Error::NONE;
+}
+
+Error AidlComposer::getDisplayAttribute(Display display, Config config,
+                                        IComposerClient::Attribute attribute, int32_t* outValue) {
+    const auto status =
+            mAidlComposerClient->getDisplayAttribute(translate<int64_t>(display),
+                                                     translate<int32_t>(config),
+                                                     static_cast<AidlDisplayAttribute>(attribute),
+                                                     outValue);
+    if (!status.isOk()) {
+        ALOGE("getDisplayAttribute failed %s", status.getDescription().c_str());
+        return static_cast<Error>(status.getServiceSpecificError());
+    }
+    return Error::NONE;
+}
+
+Error AidlComposer::getDisplayConfigs(Display display, std::vector<Config>* outConfigs) {
+    std::vector<int32_t> configs;
+    const auto status =
+            mAidlComposerClient->getDisplayConfigs(translate<int64_t>(display), &configs);
+    if (!status.isOk()) {
+        ALOGE("getDisplayConfigs failed %s", status.getDescription().c_str());
+        return static_cast<Error>(status.getServiceSpecificError());
+    }
+    *outConfigs = translate<Config>(configs);
+    return Error::NONE;
+}
+
+Error AidlComposer::getDisplayName(Display display, std::string* outName) {
+    const auto status = mAidlComposerClient->getDisplayName(translate<int64_t>(display), outName);
+    if (!status.isOk()) {
+        ALOGE("getDisplayName failed %s", status.getDescription().c_str());
+        return static_cast<Error>(status.getServiceSpecificError());
+    }
+    return Error::NONE;
+}
+
+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));
+    *outDisplayRequestMask = translate<uint32_t>(displayRequests.mask);
+    outLayers->reserve(displayRequests.layerRequests.size());
+    outLayerRequestMasks->reserve(displayRequests.layerRequests.size());
+
+    for (const auto& layer : displayRequests.layerRequests) {
+        outLayers->emplace_back(translate<Layer>(layer.layer));
+        outLayerRequestMasks->emplace_back(translate<uint32_t>(layer.mask));
+    }
+    return Error::NONE;
+}
+
+Error AidlComposer::getDozeSupport(Display display, bool* outSupport) {
+    std::vector<AidlDisplayCapability> capabilities;
+    const auto status =
+            mAidlComposerClient->getDisplayCapabilities(translate<int64_t>(display), &capabilities);
+    if (!status.isOk()) {
+        ALOGE("getDisplayCapabilities failed %s", status.getDescription().c_str());
+        return static_cast<Error>(status.getServiceSpecificError());
+    }
+    *outSupport = std::find(capabilities.begin(), capabilities.end(),
+                            AidlDisplayCapability::DOZE) != capabilities.end();
+    return Error::NONE;
+}
+
+Error AidlComposer::hasDisplayIdleTimerCapability(Display display, bool* outSupport) {
+    std::vector<AidlDisplayCapability> capabilities;
+    const auto status =
+            mAidlComposerClient->getDisplayCapabilities(translate<int64_t>(display), &capabilities);
+    if (!status.isOk()) {
+        ALOGE("getDisplayCapabilities failed %s", status.getDescription().c_str());
+        return static_cast<Error>(status.getServiceSpecificError());
+    }
+    *outSupport = std::find(capabilities.begin(), capabilities.end(),
+                            AidlDisplayCapability::DISPLAY_IDLE_TIMER) != capabilities.end();
+    return Error::NONE;
+}
+
+Error AidlComposer::getHdrCapabilities(Display display, std::vector<Hdr>* outTypes,
+                                       float* outMaxLuminance, float* outMaxAverageLuminance,
+                                       float* outMinLuminance) {
+    AidlHdrCapabilities capabilities;
+    const auto status =
+            mAidlComposerClient->getHdrCapabilities(translate<int64_t>(display), &capabilities);
+    if (!status.isOk()) {
+        ALOGE("getHdrCapabilities failed %s", status.getDescription().c_str());
+        return static_cast<Error>(status.getServiceSpecificError());
+    }
+
+    *outTypes = translate<Hdr>(capabilities.types);
+    *outMaxLuminance = capabilities.maxLuminance;
+    *outMaxAverageLuminance = capabilities.maxAverageLuminance;
+    *outMinLuminance = capabilities.minLuminance;
+    return Error::NONE;
+}
+
+Error AidlComposer::getReleaseFences(Display display, std::vector<Layer>* outLayers,
+                                     std::vector<int>* outReleaseFences) {
+    auto fences = mReader.takeReleaseFences(translate<int64_t>(display));
+    outLayers->reserve(fences.size());
+    outReleaseFences->reserve(fences.size());
+
+    for (auto& fence : fences) {
+        outLayers->emplace_back(translate<Layer>(fence.layer));
+        // take ownership
+        const int fenceOwner = fence.fence.get();
+        *fence.fence.getR() = -1;
+        outReleaseFences->emplace_back(fenceOwner);
+    }
+    return Error::NONE;
+}
+
+Error AidlComposer::presentDisplay(Display display, int* outPresentFence) {
+    ATRACE_NAME("HwcPresentDisplay");
+    mWriter.presentDisplay(translate<int64_t>(display));
+
+    Error error = execute();
+    if (error != Error::NONE) {
+        return error;
+    }
+
+    auto fence = mReader.takePresentFence(translate<int64_t>(display));
+    // take ownership
+    *outPresentFence = fence.get();
+    *fence.getR() = -1;
+    return Error::NONE;
+}
+
+Error AidlComposer::setActiveConfig(Display display, Config config) {
+    const auto status = mAidlComposerClient->setActiveConfig(translate<int64_t>(display),
+                                                             translate<int32_t>(config));
+    if (!status.isOk()) {
+        ALOGE("setActiveConfig failed %s", status.getDescription().c_str());
+        return static_cast<Error>(status.getServiceSpecificError());
+    }
+    return Error::NONE;
+}
+
+Error AidlComposer::setClientTarget(Display display, uint32_t slot, const sp<GraphicBuffer>& target,
+                                    int acquireFence, Dataspace dataspace,
+                                    const std::vector<IComposerClient::Rect>& damage) {
+    const native_handle_t* handle = nullptr;
+    if (target.get()) {
+        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 AidlComposer::setColorMode(Display display, ColorMode mode, RenderIntent renderIntent) {
+    const auto status =
+            mAidlComposerClient->setColorMode(translate<int64_t>(display),
+                                              translate<AidlColorMode>(mode),
+                                              translate<AidlRenderIntent>(renderIntent));
+    if (!status.isOk()) {
+        ALOGE("setColorMode failed %s", status.getDescription().c_str());
+        return static_cast<Error>(status.getServiceSpecificError());
+    }
+    return Error::NONE;
+}
+
+Error AidlComposer::setColorTransform(Display display, const float* matrix) {
+    mWriter.setColorTransform(translate<int64_t>(display), matrix);
+    return Error::NONE;
+}
+
+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;
+}
+
+Error AidlComposer::setPowerMode(Display display, IComposerClient::PowerMode mode) {
+    const auto status = mAidlComposerClient->setPowerMode(translate<int64_t>(display),
+                                                          translate<PowerMode>(mode));
+    if (!status.isOk()) {
+        ALOGE("setPowerMode failed %s", status.getDescription().c_str());
+        return static_cast<Error>(status.getServiceSpecificError());
+    }
+    return Error::NONE;
+}
+
+Error AidlComposer::setVsyncEnabled(Display display, IComposerClient::Vsync enabled) {
+    const bool enableVsync = enabled == IComposerClient::Vsync::ENABLE;
+    const auto status =
+            mAidlComposerClient->setVsyncEnabled(translate<int64_t>(display), enableVsync);
+    if (!status.isOk()) {
+        ALOGE("setVsyncEnabled failed %s", status.getDescription().c_str());
+        return static_cast<Error>(status.getServiceSpecificError());
+    }
+    return Error::NONE;
+}
+
+Error AidlComposer::setClientTargetSlotCount(Display display) {
+    const int32_t bufferSlotCount = BufferQueue::NUM_BUFFER_SLOTS;
+    const auto status = mAidlComposerClient->setClientTargetSlotCount(translate<int64_t>(display),
+                                                                      bufferSlotCount);
+    if (!status.isOk()) {
+        ALOGE("setClientTargetSlotCount failed %s", status.getDescription().c_str());
+        return static_cast<Error>(status.getServiceSpecificError());
+    }
+    return Error::NONE;
+}
+
+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});
+
+    Error error = execute();
+    if (error != Error::NONE) {
+        return error;
+    }
+
+    mReader.hasChanges(translate<int64_t>(display), outNumTypes, outNumRequests);
+
+    return Error::NONE;
+}
+
+Error AidlComposer::presentOrValidateDisplay(Display display, nsecs_t expectedPresentTime,
+                                             uint32_t* outNumTypes, uint32_t* outNumRequests,
+                                             int* outPresentFence, uint32_t* state) {
+    ATRACE_NAME("HwcPresentOrValidateDisplay");
+    mWriter.presentOrvalidateDisplay(translate<int64_t>(display),
+                                     ClockMonotonicTimestamp{expectedPresentTime});
+
+    Error error = execute();
+    if (error != Error::NONE) {
+        return error;
+    }
+
+    const auto result = mReader.takePresentOrValidateStage(translate<int64_t>(display));
+    if (!result.has_value()) {
+        *state = translate<uint32_t>(-1);
+        return Error::NO_RESOURCES;
+    }
+
+    *state = translate<uint32_t>(*result);
+
+    if (*result == PresentOrValidate::Result::Presented) {
+        auto fence = mReader.takePresentFence(translate<int64_t>(display));
+        // take ownership
+        *outPresentFence = fence.get();
+        *fence.getR() = -1;
+    }
+
+    if (*result == PresentOrValidate::Result::Validated) {
+        mReader.hasChanges(translate<int64_t>(display), outNumTypes, outNumRequests);
+    }
+
+    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 AidlComposer::setLayerBuffer(Display display, Layer layer, uint32_t slot,
+                                   const sp<GraphicBuffer>& buffer, int acquireFence) {
+    const native_handle_t* handle = nullptr;
+    if (buffer.get()) {
+        handle = buffer->getNativeBuffer()->handle;
+    }
+
+    mWriter.setLayerBuffer(translate<int64_t>(display), translate<int64_t>(layer), slot, handle,
+                           acquireFence);
+    return Error::NONE;
+}
+
+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 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 AidlComposer::setLayerColor(Display display, Layer layer, const Color& color) {
+    mWriter.setLayerColor(translate<int64_t>(display), translate<int64_t>(layer), color);
+    return Error::NONE;
+}
+
+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 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 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 AidlComposer::setLayerPlaneAlpha(Display display, Layer layer, float alpha) {
+    mWriter.setLayerPlaneAlpha(translate<int64_t>(display), translate<int64_t>(layer), alpha);
+    return Error::NONE;
+}
+
+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 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 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 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 AidlComposer::setLayerZOrder(Display display, Layer layer, uint32_t z) {
+    mWriter.setLayerZOrder(translate<int64_t>(display), translate<int64_t>(layer), z);
+    return Error::NONE;
+}
+
+Error AidlComposer::execute() {
+    const auto& commands = mWriter.getPendingCommands();
+    if (commands.empty()) {
+        mWriter.reset();
+        return Error::NONE;
+    }
+
+    { // scope for results
+        std::vector<CommandResultPayload> results;
+        auto status = mAidlComposerClient->executeCommands(commands, &results);
+        if (!status.isOk()) {
+            ALOGE("executeCommands failed %s", status.getDescription().c_str());
+            return static_cast<Error>(status.getServiceSpecificError());
+        }
+
+        mReader.parse(std::move(results));
+    }
+    const auto commandErrors = mReader.takeErrors();
+    Error error = Error::NONE;
+    for (const auto& cmdErr : commandErrors) {
+        const auto index = static_cast<size_t>(cmdErr.commandIndex);
+        if (index < 0 || index >= commands.size()) {
+            ALOGE("invalid command index %zu", index);
+            return Error::BAD_PARAMETER;
+        }
+
+        const auto& command = commands[index];
+        if (command.validateDisplay || command.presentDisplay || command.presentOrValidateDisplay) {
+            error = translate<Error>(cmdErr.errorCode);
+        } else {
+            ALOGW("command '%s' generated error %" PRId32, command.toString().c_str(),
+                  cmdErr.errorCode);
+        }
+    }
+
+    mWriter.reset();
+
+    return error;
+}
+
+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;
+}
+
+std::vector<IComposerClient::PerFrameMetadataKey> AidlComposer::getPerFrameMetadataKeys(
+        Display display) {
+    std::vector<AidlPerFrameMetadataKey> keys;
+    const auto status =
+            mAidlComposerClient->getPerFrameMetadataKeys(translate<int64_t>(display), &keys);
+    if (!status.isOk()) {
+        ALOGE("getPerFrameMetadataKeys failed %s", status.getDescription().c_str());
+        return {};
+    }
+    return translate<IComposerClient::PerFrameMetadataKey>(keys);
+}
+
+Error AidlComposer::getRenderIntents(Display display, ColorMode colorMode,
+                                     std::vector<RenderIntent>* outRenderIntents) {
+    std::vector<AidlRenderIntent> renderIntents;
+    const auto status = mAidlComposerClient->getRenderIntents(translate<int64_t>(display),
+                                                              translate<AidlColorMode>(colorMode),
+                                                              &renderIntents);
+    if (!status.isOk()) {
+        ALOGE("getRenderIntents failed %s", status.getDescription().c_str());
+        return static_cast<Error>(status.getServiceSpecificError());
+    }
+    *outRenderIntents = translate<RenderIntent>(renderIntents);
+    return Error::NONE;
+}
+
+Error AidlComposer::getDataspaceSaturationMatrix(Dataspace dataspace, mat4* outMatrix) {
+    std::vector<float> matrix;
+    const auto status =
+            mAidlComposerClient->getDataspaceSaturationMatrix(translate<AidlDataspace>(dataspace),
+                                                              &matrix);
+    if (!status.isOk()) {
+        ALOGE("getDataspaceSaturationMatrix failed %s", status.getDescription().c_str());
+        return static_cast<Error>(status.getServiceSpecificError());
+    }
+    *outMatrix = makeMat4(matrix);
+    return Error::NONE;
+}
+
+Error AidlComposer::getDisplayIdentificationData(Display display, uint8_t* outPort,
+                                                 std::vector<uint8_t>* outData) {
+    AidlDisplayIdentification displayIdentification;
+    const auto status =
+            mAidlComposerClient->getDisplayIdentificationData(translate<int64_t>(display),
+                                                              &displayIdentification);
+    if (!status.isOk()) {
+        ALOGE("getDisplayIdentificationData failed %s", status.getDescription().c_str());
+        return static_cast<Error>(status.getServiceSpecificError());
+    }
+
+    *outPort = static_cast<uint8_t>(displayIdentification.port);
+    *outData = displayIdentification.data;
+
+    return Error::NONE;
+}
+
+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 AidlComposer::getDisplayedContentSamplingAttributes(Display display, PixelFormat* outFormat,
+                                                          Dataspace* outDataspace,
+                                                          uint8_t* outComponentMask) {
+    if (!outFormat || !outDataspace || !outComponentMask) {
+        return Error::BAD_PARAMETER;
+    }
+
+    AidlDisplayContentSamplingAttributes attributes;
+    const auto status =
+            mAidlComposerClient->getDisplayedContentSamplingAttributes(translate<int64_t>(display),
+                                                                       &attributes);
+    if (!status.isOk()) {
+        ALOGE("getDisplayedContentSamplingAttributes failed %s", status.getDescription().c_str());
+        return static_cast<Error>(status.getServiceSpecificError());
+    }
+
+    *outFormat = translate<PixelFormat>(attributes.format);
+    *outDataspace = translate<Dataspace>(attributes.dataspace);
+    *outComponentMask = static_cast<uint8_t>(attributes.componentMask);
+    return Error::NONE;
+}
+
+Error AidlComposer::setDisplayContentSamplingEnabled(Display display, bool enabled,
+                                                     uint8_t componentMask, uint64_t maxFrames) {
+    const auto status =
+            mAidlComposerClient
+                    ->setDisplayedContentSamplingEnabled(translate<int64_t>(display), enabled,
+                                                         static_cast<AidlFormatColorComponent>(
+                                                                 componentMask),
+                                                         static_cast<int64_t>(maxFrames));
+    if (!status.isOk()) {
+        ALOGE("setDisplayedContentSamplingEnabled failed %s", status.getDescription().c_str());
+        return static_cast<Error>(status.getServiceSpecificError());
+    }
+    return Error::NONE;
+}
+
+Error AidlComposer::getDisplayedContentSample(Display display, uint64_t maxFrames,
+                                              uint64_t timestamp, DisplayedFrameStats* outStats) {
+    if (!outStats) {
+        return Error::BAD_PARAMETER;
+    }
+
+    AidlDisplayContentSample sample;
+    const auto status =
+            mAidlComposerClient->getDisplayedContentSample(translate<int64_t>(display),
+                                                           static_cast<int64_t>(maxFrames),
+                                                           static_cast<int64_t>(timestamp),
+                                                           &sample);
+    if (!status.isOk()) {
+        ALOGE("getDisplayedContentSample failed %s", status.getDescription().c_str());
+        return static_cast<Error>(status.getServiceSpecificError());
+    }
+    *outStats = translate<DisplayedFrameStats>(sample);
+    return Error::NONE;
+}
+
+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 AidlComposer::setDisplayBrightness(Display display, float brightness,
+                                         const DisplayBrightnessOptions& options) {
+    mWriter.setDisplayBrightness(translate<int64_t>(display), brightness);
+
+    if (options.applyImmediately) {
+        return execute();
+    }
+
+    return Error::NONE;
+}
+
+Error AidlComposer::getDisplayCapabilities(Display display,
+                                           std::vector<AidlDisplayCapability>* outCapabilities) {
+    const auto status = mAidlComposerClient->getDisplayCapabilities(translate<int64_t>(display),
+                                                                    outCapabilities);
+    if (!status.isOk()) {
+        ALOGE("getDisplayCapabilities failed %s", status.getDescription().c_str());
+        outCapabilities->clear();
+        return static_cast<Error>(status.getServiceSpecificError());
+    }
+    return Error::NONE;
+}
+
+V2_4::Error AidlComposer::getDisplayConnectionType(
+        Display display, IComposerClient::DisplayConnectionType* outType) {
+    AidlDisplayConnectionType type;
+    const auto status =
+            mAidlComposerClient->getDisplayConnectionType(translate<int64_t>(display), &type);
+    if (!status.isOk()) {
+        ALOGE("getDisplayConnectionType failed %s", status.getDescription().c_str());
+        return static_cast<V2_4::Error>(status.getServiceSpecificError());
+    }
+    *outType = translate<IComposerClient::DisplayConnectionType>(type);
+    return V2_4::Error::NONE;
+}
+
+V2_4::Error AidlComposer::getDisplayVsyncPeriod(Display display, VsyncPeriodNanos* outVsyncPeriod) {
+    int32_t vsyncPeriod;
+    const auto status =
+            mAidlComposerClient->getDisplayVsyncPeriod(translate<int64_t>(display), &vsyncPeriod);
+    if (!status.isOk()) {
+        ALOGE("getDisplayVsyncPeriod failed %s", status.getDescription().c_str());
+        return static_cast<V2_4::Error>(status.getServiceSpecificError());
+    }
+    *outVsyncPeriod = translate<VsyncPeriodNanos>(vsyncPeriod);
+    return V2_4::Error::NONE;
+}
+
+V2_4::Error AidlComposer::setActiveConfigWithConstraints(
+        Display display, Config config,
+        const IComposerClient::VsyncPeriodChangeConstraints& vsyncPeriodChangeConstraints,
+        VsyncPeriodChangeTimeline* outTimeline) {
+    AidlVsyncPeriodChangeTimeline timeline;
+    const auto status =
+            mAidlComposerClient
+                    ->setActiveConfigWithConstraints(translate<int64_t>(display),
+                                                     translate<int32_t>(config),
+                                                     translate<AidlVsyncPeriodChangeConstraints>(
+                                                             vsyncPeriodChangeConstraints),
+                                                     &timeline);
+    if (!status.isOk()) {
+        ALOGE("setActiveConfigWithConstraints failed %s", status.getDescription().c_str());
+        return static_cast<V2_4::Error>(status.getServiceSpecificError());
+    }
+    *outTimeline = translate<VsyncPeriodChangeTimeline>(timeline);
+    return V2_4::Error::NONE;
+}
+
+V2_4::Error AidlComposer::setAutoLowLatencyMode(Display display, bool on) {
+    const auto status = mAidlComposerClient->setAutoLowLatencyMode(translate<int64_t>(display), on);
+    if (!status.isOk()) {
+        ALOGE("setAutoLowLatencyMode failed %s", status.getDescription().c_str());
+        return static_cast<V2_4::Error>(status.getServiceSpecificError());
+    }
+    return V2_4::Error::NONE;
+}
+
+V2_4::Error AidlComposer::getSupportedContentTypes(
+        Display displayId, std::vector<IComposerClient::ContentType>* outSupportedContentTypes) {
+    std::vector<AidlContentType> types;
+    const auto status =
+            mAidlComposerClient->getSupportedContentTypes(translate<int64_t>(displayId), &types);
+    if (!status.isOk()) {
+        ALOGE("getSupportedContentTypes failed %s", status.getDescription().c_str());
+        return static_cast<V2_4::Error>(status.getServiceSpecificError());
+    }
+    *outSupportedContentTypes = translate<IComposerClient::ContentType>(types);
+    return V2_4::Error::NONE;
+}
+
+V2_4::Error AidlComposer::setContentType(Display display,
+                                         IComposerClient::ContentType contentType) {
+    const auto status =
+            mAidlComposerClient->setContentType(translate<int64_t>(display),
+                                                translate<AidlContentType>(contentType));
+    if (!status.isOk()) {
+        ALOGE("setContentType failed %s", status.getDescription().c_str());
+        return static_cast<V2_4::Error>(status.getServiceSpecificError());
+    }
+    return V2_4::Error::NONE;
+}
+
+V2_4::Error AidlComposer::setLayerGenericMetadata(Display, Layer, const std::string&, bool,
+                                                  const std::vector<uint8_t>&) {
+    // There are no users for this API. See b/209691612.
+    return V2_4::Error::UNSUPPORTED;
+}
+
+V2_4::Error AidlComposer::getLayerGenericMetadataKeys(
+        std::vector<IComposerClient::LayerGenericMetadataKey>*) {
+    // There are no users for this API. See b/209691612.
+    return V2_4::Error::UNSUPPORTED;
+}
+
+Error AidlComposer::setBootDisplayConfig(Display display, Config config) {
+    const auto status = mAidlComposerClient->setBootDisplayConfig(translate<int64_t>(display),
+                                                                  translate<int32_t>(config));
+    if (!status.isOk()) {
+        ALOGE("setBootDisplayConfig failed %s", status.getDescription().c_str());
+        return static_cast<Error>(status.getServiceSpecificError());
+    }
+    return Error::NONE;
+}
+
+Error AidlComposer::clearBootDisplayConfig(Display display) {
+    const auto status = mAidlComposerClient->clearBootDisplayConfig(translate<int64_t>(display));
+    if (!status.isOk()) {
+        ALOGE("clearBootDisplayConfig failed %s", status.getDescription().c_str());
+        return static_cast<Error>(status.getServiceSpecificError());
+    }
+    return Error::NONE;
+}
+
+Error AidlComposer::getPreferredBootDisplayConfig(Display display, Config* config) {
+    int32_t displayConfig;
+    const auto status =
+            mAidlComposerClient->getPreferredBootDisplayConfig(translate<int64_t>(display),
+                                                               &displayConfig);
+    if (!status.isOk()) {
+        ALOGE("getPreferredBootDisplayConfig failed %s", status.getDescription().c_str());
+        return static_cast<Error>(status.getServiceSpecificError());
+    }
+    *config = translate<uint32_t>(displayConfig);
+    return Error::NONE;
+}
+
+Error AidlComposer::getClientTargetProperty(
+        Display display, IComposerClient::ClientTargetProperty* outClientTargetProperty,
+        float* outBrightness) {
+    const auto property = mReader.takeClientTargetProperty(translate<int64_t>(display));
+    *outClientTargetProperty =
+            translate<IComposerClient::ClientTargetProperty>(property.clientTargetProperty);
+    *outBrightness = property.brightness;
+    return Error::NONE;
+}
+
+Error AidlComposer::setLayerBrightness(Display display, Layer layer, float brightness) {
+    mWriter.setLayerBrightness(translate<int64_t>(display), translate<int64_t>(layer), brightness);
+    return Error::NONE;
+}
+
+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 AidlComposer::getDisplayDecorationSupport(Display display,
+                                                std::optional<DisplayDecorationSupport>* support) {
+    const auto status =
+            mAidlComposerClient->getDisplayDecorationSupport(translate<int64_t>(display), support);
+    if (!status.isOk()) {
+        ALOGE("getDisplayDecorationSupport failed %s", status.getDescription().c_str());
+        support->reset();
+        return static_cast<Error>(status.getServiceSpecificError());
+    }
+    return Error::NONE;
+}
+
+Error AidlComposer::setIdleTimerEnabled(Display displayId, std::chrono::milliseconds timeout) {
+    const auto status =
+            mAidlComposerClient->setIdleTimerEnabled(translate<int64_t>(displayId),
+                                                     translate<int32_t>(timeout.count()));
+    if (!status.isOk()) {
+        ALOGE("setIdleTimerEnabled failed %s", status.getDescription().c_str());
+        return static_cast<Error>(status.getServiceSpecificError());
+    }
+    return Error::NONE;
+}
+
+Error AidlComposer::getPhysicalDisplayOrientation(Display displayId,
+                                                  AidlTransform* outDisplayOrientation) {
+    const auto status =
+            mAidlComposerClient->getDisplayPhysicalOrientation(translate<int64_t>(displayId),
+                                                               outDisplayOrientation);
+    if (!status.isOk()) {
+        ALOGE("getPhysicalDisplayOrientation failed %s", status.getDescription().c_str());
+        return static_cast<Error>(status.getServiceSpecificError());
+    }
+    return Error::NONE;
+}
+
+} // namespace Hwc2
+} // namespace android
diff --git a/services/surfaceflinger/DisplayHardware/AidlComposerHal.h b/services/surfaceflinger/DisplayHardware/AidlComposerHal.h
new file mode 100644
index 0000000..28ff167
--- /dev/null
+++ b/services/surfaceflinger/DisplayHardware/AidlComposerHal.h
@@ -0,0 +1,256 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include "ComposerHal.h"
+
+#include <optional>
+#include <string>
+#include <unordered_map>
+#include <utility>
+#include <vector>
+
+// TODO(b/129481165): remove the #pragma below and fix conversion issues
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wconversion"
+#pragma clang diagnostic ignored "-Wextra"
+
+#include <android/hardware/graphics/composer/2.4/IComposer.h>
+#include <android/hardware/graphics/composer/2.4/IComposerClient.h>
+
+#include <aidl/android/hardware/graphics/composer3/IComposer.h>
+#include <aidl/android/hardware/graphics/composer3/IComposerClient.h>
+#include <android/hardware/graphics/composer3/ComposerClientReader.h>
+#include <android/hardware/graphics/composer3/ComposerClientWriter.h>
+
+#include <aidl/android/hardware/graphics/composer3/Composition.h>
+#include <aidl/android/hardware/graphics/composer3/DisplayCapability.h>
+
+// TODO(b/129481165): remove the #pragma below and fix conversion issues
+#pragma clang diagnostic pop // ignored "-Wconversion -Wextra"
+
+namespace android::Hwc2 {
+
+using aidl::android::hardware::graphics::common::DisplayDecorationSupport;
+using aidl::android::hardware::graphics::composer3::ComposerClientReader;
+using aidl::android::hardware::graphics::composer3::ComposerClientWriter;
+
+class AidlIComposerCallbackWrapper;
+
+// Composer is a wrapper to IComposer, a proxy to server-side composer.
+class AidlComposer final : public Hwc2::Composer {
+public:
+    static bool isDeclared(const std::string& serviceName);
+
+    explicit AidlComposer(const std::string& serviceName);
+    ~AidlComposer() override;
+
+    bool isSupported(OptionalFeature) const;
+
+    std::vector<aidl::android::hardware::graphics::composer3::Capability> getCapabilities()
+            override;
+    std::string dumpDebugInfo() override;
+
+    void registerCallback(HWC2::ComposerCallback& callback) override;
+
+    // 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;
+
+    // Explicitly flush all pending commands in the command buffer.
+    Error executeCommands() override;
+
+    uint32_t getMaxVirtualDisplayCount() override;
+    Error createVirtualDisplay(uint32_t width, uint32_t height, PixelFormat* format,
+                               Display* outDisplay) override;
+    Error destroyVirtualDisplay(Display display) override;
+
+    Error acceptDisplayChanges(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 getChangedCompositionTypes(
+            Display display, std::vector<Layer>* outLayers,
+            std::vector<aidl::android::hardware::graphics::composer3::Composition>* outTypes)
+            override;
+    Error getColorModes(Display display, std::vector<ColorMode>* outModes) override;
+    Error getDisplayAttribute(Display display, Config config, IComposerClient::Attribute attribute,
+                              int32_t* outValue) override;
+    Error getDisplayConfigs(Display display, std::vector<Config>* outConfigs);
+    Error getDisplayName(Display display, std::string* outName) override;
+
+    Error getDisplayRequests(Display display, uint32_t* outDisplayRequestMask,
+                             std::vector<Layer>* outLayers,
+                             std::vector<uint32_t>* outLayerRequestMasks) override;
+
+    Error getDozeSupport(Display display, bool* outSupport) override;
+    Error hasDisplayIdleTimerCapability(Display display, bool* outSupport) override;
+    Error getHdrCapabilities(Display display, std::vector<Hdr>* outTypes, float* outMaxLuminance,
+                             float* outMaxAverageLuminance, float* outMinLuminance) override;
+
+    Error getReleaseFences(Display display, std::vector<Layer>* outLayers,
+                           std::vector<int>* outReleaseFences) override;
+
+    Error presentDisplay(Display display, int* outPresentFence) override;
+
+    Error setActiveConfig(Display display, Config config) override;
+
+    /*
+     * The composer caches client targets internally.  When target is nullptr,
+     * the composer uses slot to look up the client target from its cache.
+     * When target is not nullptr, the cache is updated with the new target.
+     */
+    Error setClientTarget(Display display, uint32_t slot, const sp<GraphicBuffer>& target,
+                          int acquireFence, Dataspace dataspace,
+                          const std::vector<IComposerClient::Rect>& damage) override;
+    Error setColorMode(Display display, ColorMode mode, RenderIntent renderIntent) override;
+    Error setColorTransform(Display display, const float* matrix) override;
+    Error setOutputBuffer(Display display, const native_handle_t* buffer,
+                          int releaseFence) override;
+    Error setPowerMode(Display display, IComposerClient::PowerMode mode) override;
+    Error setVsyncEnabled(Display display, IComposerClient::Vsync enabled) override;
+
+    Error setClientTargetSlotCount(Display display) override;
+
+    Error validateDisplay(Display display, nsecs_t expectedPresentTime, uint32_t* outNumTypes,
+                          uint32_t* outNumRequests) override;
+
+    Error presentOrValidateDisplay(Display display, nsecs_t expectedPresentTime,
+                                   uint32_t* outNumTypes, uint32_t* outNumRequests,
+                                   int* outPresentFence, uint32_t* state) override;
+
+    Error setCursorPosition(Display display, Layer layer, int32_t x, int32_t y) override;
+    /* see setClientTarget for the purpose of slot */
+    Error setLayerBuffer(Display display, Layer layer, uint32_t slot,
+                         const sp<GraphicBuffer>& buffer, int acquireFence) override;
+    Error setLayerSurfaceDamage(Display display, Layer layer,
+                                const std::vector<IComposerClient::Rect>& damage) override;
+    Error setLayerBlendMode(Display display, Layer layer, IComposerClient::BlendMode mode) override;
+    Error setLayerColor(Display display, Layer layer, const Color& color) override;
+    Error setLayerCompositionType(
+            Display display, Layer layer,
+            aidl::android::hardware::graphics::composer3::Composition type) override;
+    Error setLayerDataspace(Display display, Layer layer, Dataspace dataspace) override;
+    Error setLayerDisplayFrame(Display display, Layer layer,
+                               const IComposerClient::Rect& frame) override;
+    Error setLayerPlaneAlpha(Display display, Layer layer, float alpha) override;
+    Error setLayerSidebandStream(Display display, Layer layer,
+                                 const native_handle_t* stream) override;
+    Error setLayerSourceCrop(Display display, Layer layer,
+                             const IComposerClient::FRect& crop) override;
+    Error setLayerTransform(Display display, Layer layer, Transform transform) override;
+    Error setLayerVisibleRegion(Display display, Layer layer,
+                                const std::vector<IComposerClient::Rect>& visible) override;
+    Error setLayerZOrder(Display display, Layer layer, uint32_t z) override;
+
+    // Composer HAL 2.2
+    Error setLayerPerFrameMetadata(
+            Display display, Layer layer,
+            const std::vector<IComposerClient::PerFrameMetadata>& perFrameMetadatas) override;
+    std::vector<IComposerClient::PerFrameMetadataKey> getPerFrameMetadataKeys(
+            Display display) override;
+    Error getRenderIntents(Display display, ColorMode colorMode,
+                           std::vector<RenderIntent>* outRenderIntents) override;
+    Error getDataspaceSaturationMatrix(Dataspace dataspace, mat4* outMatrix) override;
+
+    // Composer HAL 2.3
+    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* outFormat,
+                                                Dataspace* outDataspace,
+                                                uint8_t* outComponentMask) override;
+    Error setDisplayContentSamplingEnabled(Display display, bool enabled, uint8_t componentMask,
+                                           uint64_t maxFrames) override;
+    Error getDisplayedContentSample(Display display, uint64_t maxFrames, uint64_t timestamp,
+                                    DisplayedFrameStats* outStats) override;
+    Error setLayerPerFrameMetadataBlobs(
+            Display display, Layer layer,
+            const std::vector<IComposerClient::PerFrameMetadataBlob>& metadata) override;
+    Error setDisplayBrightness(Display display, float brightness,
+                               const DisplayBrightnessOptions& options) override;
+
+    // Composer HAL 2.4
+    Error getDisplayCapabilities(
+            Display display,
+            std::vector<aidl::android::hardware::graphics::composer3::DisplayCapability>*
+                    outCapabilities) override;
+    V2_4::Error getDisplayConnectionType(Display display,
+                                         IComposerClient::DisplayConnectionType* outType) override;
+    V2_4::Error getDisplayVsyncPeriod(Display display, VsyncPeriodNanos* outVsyncPeriod) override;
+    V2_4::Error setActiveConfigWithConstraints(
+            Display display, Config config,
+            const IComposerClient::VsyncPeriodChangeConstraints& vsyncPeriodChangeConstraints,
+            VsyncPeriodChangeTimeline* outTimeline) override;
+    V2_4::Error setAutoLowLatencyMode(Display displayId, bool on) override;
+    V2_4::Error getSupportedContentTypes(
+            Display displayId,
+            std::vector<IComposerClient::ContentType>* outSupportedContentTypes) override;
+    V2_4::Error setContentType(Display displayId,
+                               IComposerClient::ContentType contentType) override;
+    V2_4::Error setLayerGenericMetadata(Display display, Layer layer, const std::string& key,
+                                        bool mandatory, const std::vector<uint8_t>& value) override;
+    V2_4::Error getLayerGenericMetadataKeys(
+            std::vector<IComposerClient::LayerGenericMetadataKey>* outKeys) override;
+    Error getClientTargetProperty(Display display,
+                                  IComposerClient::ClientTargetProperty* outClientTargetProperty,
+                                  float* outBrightness) override;
+
+    // AIDL Composer HAL
+    Error setLayerBrightness(Display display, Layer layer, float brightness) override;
+    Error setLayerBlockingRegion(Display display, Layer layer,
+                                 const std::vector<IComposerClient::Rect>& blocking) override;
+    Error setBootDisplayConfig(Display displayId, Config) override;
+    Error clearBootDisplayConfig(Display displayId) override;
+    Error getPreferredBootDisplayConfig(Display displayId, Config*) override;
+    Error getDisplayDecorationSupport(Display display,
+                                      std::optional<DisplayDecorationSupport>* support) override;
+    Error setIdleTimerEnabled(Display displayId, std::chrono::milliseconds timeout) override;
+
+    Error getPhysicalDisplayOrientation(Display displayId,
+                                        AidlTransform* outDisplayOrientation) 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();
+
+    // returns the default instance name for the given service
+    static std::string instance(const std::string& serviceName);
+
+    // 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
+    // We obtain this number by:
+    // 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;
+
+    // Aidl interface
+    using AidlIComposer = aidl::android::hardware::graphics::composer3::IComposer;
+    using AidlIComposerClient = aidl::android::hardware::graphics::composer3::IComposerClient;
+    std::shared_ptr<AidlIComposer> mAidlComposer;
+    std::shared_ptr<AidlIComposerClient> mAidlComposerClient;
+    std::shared_ptr<AidlIComposerCallbackWrapper> mAidlComposerCallback;
+};
+
+} // namespace android::Hwc2
diff --git a/services/surfaceflinger/DisplayHardware/ComposerHal.cpp b/services/surfaceflinger/DisplayHardware/ComposerHal.cpp
index 09734c2..d69a923 100644
--- a/services/surfaceflinger/DisplayHardware/ComposerHal.cpp
+++ b/services/surfaceflinger/DisplayHardware/ComposerHal.cpp
@@ -14,1562 +14,23 @@
  * limitations under the License.
  */
 
-// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wconversion"
-
 #undef LOG_TAG
 #define LOG_TAG "HwcComposer"
 #define ATRACE_TAG ATRACE_TAG_GRAPHICS
 
-#include "ComposerHal.h"
+#include "AidlComposerHal.h"
+#include "HidlComposerHal.h"
 
-#include <composer-command-buffer/2.2/ComposerCommandBuffer.h>
-#include <hidl/HidlTransportSupport.h>
-#include <hidl/HidlTransportUtils.h>
-#include <log/log.h>
-#include <utils/Trace.h>
-
-#include <algorithm>
-#include <cinttypes>
-
-namespace android {
-
-using hardware::Return;
-using hardware::hidl_vec;
-using hardware::hidl_handle;
-
-namespace Hwc2 {
+namespace android::Hwc2 {
 
 Composer::~Composer() = default;
 
-namespace {
-
-class BufferHandle {
-public:
-    explicit BufferHandle(const native_handle_t* buffer) {
-        // nullptr is not a valid handle to HIDL
-        mHandle = (buffer) ? buffer : native_handle_init(mStorage, 0, 0);
+std::unique_ptr<Composer> Composer::create(const std::string& serviceName) {
+    if (AidlComposer::isDeclared(serviceName)) {
+        return std::make_unique<AidlComposer>(serviceName);
     }
 
-    operator const hidl_handle&() const // NOLINT(google-explicit-constructor)
-    {
-        return mHandle;
-    }
-
-private:
-    NATIVE_HANDLE_DECLARE_STORAGE(mStorage, 0, 0);
-    hidl_handle mHandle;
-};
-
-class FenceHandle
-{
-public:
-    FenceHandle(int fd, bool owned)
-        : mOwned(owned)
-    {
-        native_handle_t* handle;
-        if (fd >= 0) {
-            handle = native_handle_init(mStorage, 1, 0);
-            handle->data[0] = fd;
-        } else {
-            // nullptr is not a valid handle to HIDL
-            handle = native_handle_init(mStorage, 0, 0);
-        }
-        mHandle = handle;
-    }
-
-    ~FenceHandle()
-    {
-        if (mOwned) {
-            native_handle_close(mHandle);
-        }
-    }
-
-    operator const hidl_handle&() const // NOLINT(google-explicit-constructor)
-    {
-        return mHandle;
-    }
-
-private:
-    bool mOwned;
-    NATIVE_HANDLE_DECLARE_STORAGE(mStorage, 1, 0);
-    hidl_handle mHandle;
-};
-
-// assume NO_RESOURCES when Status::isOk returns false
-constexpr Error kDefaultError = Error::NO_RESOURCES;
-constexpr V2_4::Error kDefaultError_2_4 = static_cast<V2_4::Error>(kDefaultError);
-
-template<typename T, typename U>
-T unwrapRet(Return<T>& ret, const U& default_val)
-{
-    return (ret.isOk()) ? static_cast<T>(ret) :
-        static_cast<T>(default_val);
+    return std::make_unique<HidlComposer>(serviceName);
 }
 
-Error unwrapRet(Return<Error>& ret)
-{
-    return unwrapRet(ret, kDefaultError);
-}
-
-} // anonymous namespace
-
-namespace impl {
-
-Composer::Composer(const std::string& serviceName) : mWriter(kWriterInitialSize) {
-    mComposer = V2_1::IComposer::getService(serviceName);
-
-    if (mComposer == nullptr) {
-        LOG_ALWAYS_FATAL("failed to get hwcomposer service");
-    }
-
-    if (sp<IComposer> composer_2_4 = IComposer::castFrom(mComposer)) {
-        composer_2_4->createClient_2_4([&](const auto& tmpError, const auto& tmpClient) {
-            if (tmpError == V2_4::Error::NONE) {
-                mClient = tmpClient;
-                mClient_2_2 = tmpClient;
-                mClient_2_3 = tmpClient;
-                mClient_2_4 = tmpClient;
-            }
-        });
-    } else if (sp<V2_3::IComposer> composer_2_3 = V2_3::IComposer::castFrom(mComposer)) {
-        composer_2_3->createClient_2_3([&](const auto& tmpError, const auto& tmpClient) {
-            if (tmpError == Error::NONE) {
-                mClient = tmpClient;
-                mClient_2_2 = tmpClient;
-                mClient_2_3 = tmpClient;
-            }
-        });
-    } else {
-        mComposer->createClient([&](const auto& tmpError, const auto& tmpClient) {
-            if (tmpError != Error::NONE) {
-                return;
-            }
-
-            mClient = tmpClient;
-            if (sp<V2_2::IComposer> composer_2_2 = V2_2::IComposer::castFrom(mComposer)) {
-                mClient_2_2 = V2_2::IComposerClient::castFrom(mClient);
-                LOG_ALWAYS_FATAL_IF(mClient_2_2 == nullptr,
-                                    "IComposer 2.2 did not return IComposerClient 2.2");
-            }
-        });
-    }
-
-    if (mClient == nullptr) {
-        LOG_ALWAYS_FATAL("failed to create composer client");
-    }
-}
-
-Composer::~Composer() = default;
-
-std::vector<IComposer::Capability> Composer::getCapabilities()
-{
-    std::vector<IComposer::Capability> capabilities;
-    mComposer->getCapabilities(
-            [&](const auto& tmpCapabilities) {
-                capabilities = tmpCapabilities;
-            });
-    return capabilities;
-}
-
-std::string Composer::dumpDebugInfo()
-{
-    std::string info;
-    mComposer->dumpDebugInfo([&](const auto& tmpInfo) {
-        info = tmpInfo.c_str();
-    });
-
-    return info;
-}
-
-void Composer::registerCallback(const sp<IComposerCallback>& callback)
-{
-    android::hardware::setMinSchedulerPolicy(callback, SCHED_FIFO, 2);
-    auto ret = [&]() {
-        if (mClient_2_4) {
-            return mClient_2_4->registerCallback_2_4(callback);
-        }
-        return mClient->registerCallback(callback);
-    }();
-    if (!ret.isOk()) {
-        ALOGE("failed to register IComposerCallback");
-    }
-}
-
-void Composer::resetCommands() {
-    mWriter.reset();
-}
-
-Error Composer::executeCommands() {
-    return execute();
-}
-
-uint32_t Composer::getMaxVirtualDisplayCount()
-{
-    auto ret = mClient->getMaxVirtualDisplayCount();
-    return unwrapRet(ret, 0);
-}
-
-Error Composer::createVirtualDisplay(uint32_t width, uint32_t height, PixelFormat* format,
-                                     Display* outDisplay) {
-    const uint32_t bufferSlotCount = 1;
-    Error error = kDefaultError;
-    if (mClient_2_2) {
-        mClient_2_2->createVirtualDisplay_2_2(width, height,
-                                              static_cast<types::V1_1::PixelFormat>(*format),
-                                              bufferSlotCount,
-                                              [&](const auto& tmpError, const auto& tmpDisplay,
-                                                  const auto& tmpFormat) {
-                                                  error = tmpError;
-                                                  if (error != Error::NONE) {
-                                                      return;
-                                                  }
-
-                                                  *outDisplay = tmpDisplay;
-                                                  *format = static_cast<types::V1_2::PixelFormat>(
-                                                          tmpFormat);
-                                              });
-    } else {
-        mClient->createVirtualDisplay(width, height,
-                static_cast<types::V1_0::PixelFormat>(*format), bufferSlotCount,
-                [&](const auto& tmpError, const auto& tmpDisplay,
-                    const auto& tmpFormat) {
-                    error = tmpError;
-                    if (error != Error::NONE) {
-                        return;
-                    }
-
-                    *outDisplay = tmpDisplay;
-                    *format = static_cast<PixelFormat>(tmpFormat);
-            });
-    }
-
-    return error;
-}
-
-Error Composer::destroyVirtualDisplay(Display display)
-{
-    auto ret = mClient->destroyVirtualDisplay(display);
-    return unwrapRet(ret);
-}
-
-Error Composer::acceptDisplayChanges(Display display)
-{
-    mWriter.selectDisplay(display);
-    mWriter.acceptDisplayChanges();
-    return Error::NONE;
-}
-
-Error Composer::createLayer(Display display, Layer* outLayer)
-{
-    Error error = kDefaultError;
-    mClient->createLayer(display, kMaxLayerBufferCount,
-                         [&](const auto& tmpError, const auto& tmpLayer) {
-                             error = tmpError;
-                             if (error != Error::NONE) {
-                                 return;
-                             }
-
-                             *outLayer = tmpLayer;
-                         });
-
-    return error;
-}
-
-Error Composer::destroyLayer(Display display, Layer layer)
-{
-    auto ret = mClient->destroyLayer(display, layer);
-    return unwrapRet(ret);
-}
-
-Error Composer::getActiveConfig(Display display, Config* outConfig)
-{
-    Error error = kDefaultError;
-    mClient->getActiveConfig(display,
-            [&](const auto& tmpError, const auto& tmpConfig) {
-                error = tmpError;
-                if (error != Error::NONE) {
-                    return;
-                }
-
-                *outConfig = tmpConfig;
-            });
-
-    return error;
-}
-
-Error Composer::getChangedCompositionTypes(Display display,
-        std::vector<Layer>* outLayers,
-        std::vector<IComposerClient::Composition>* outTypes)
-{
-    mReader.takeChangedCompositionTypes(display, outLayers, outTypes);
-    return Error::NONE;
-}
-
-Error Composer::getColorModes(Display display,
-        std::vector<ColorMode>* outModes)
-{
-    Error error = kDefaultError;
-
-    if (mClient_2_3) {
-        mClient_2_3->getColorModes_2_3(display, [&](const auto& tmpError, const auto& tmpModes) {
-            error = tmpError;
-            if (error != Error::NONE) {
-                return;
-            }
-
-            *outModes = tmpModes;
-        });
-    } else if (mClient_2_2) {
-        mClient_2_2->getColorModes_2_2(display, [&](const auto& tmpError, const auto& tmpModes) {
-            error = tmpError;
-            if (error != Error::NONE) {
-                return;
-            }
-
-            for (types::V1_1::ColorMode colorMode : tmpModes) {
-                outModes->push_back(static_cast<ColorMode>(colorMode));
-            }
-        });
-    } else {
-        mClient->getColorModes(display,
-                [&](const auto& tmpError, const auto& tmpModes) {
-                    error = tmpError;
-                    if (error != Error::NONE) {
-                        return;
-                    }
-                    for (types::V1_0::ColorMode colorMode : tmpModes) {
-                        outModes->push_back(static_cast<ColorMode>(colorMode));
-                    }
-                });
-    }
-
-    return error;
-}
-
-Error Composer::getDisplayAttribute(Display display, Config config,
-        IComposerClient::Attribute attribute, int32_t* outValue)
-{
-    Error error = kDefaultError;
-    if (mClient_2_4) {
-        mClient_2_4->getDisplayAttribute_2_4(display, config, attribute,
-                                             [&](const auto& tmpError, const auto& tmpValue) {
-                                                 error = static_cast<Error>(tmpError);
-                                                 if (error != Error::NONE) {
-                                                     return;
-                                                 }
-
-                                                 *outValue = tmpValue;
-                                             });
-    } else {
-        mClient->getDisplayAttribute(display, config,
-                                     static_cast<V2_1::IComposerClient::Attribute>(attribute),
-                                     [&](const auto& tmpError, const auto& tmpValue) {
-                                         error = tmpError;
-                                         if (error != Error::NONE) {
-                                             return;
-                                         }
-
-                                         *outValue = tmpValue;
-                                     });
-    }
-
-    return error;
-}
-
-Error Composer::getDisplayConfigs(Display display,
-        std::vector<Config>* outConfigs)
-{
-    Error error = kDefaultError;
-    mClient->getDisplayConfigs(display,
-            [&](const auto& tmpError, const auto& tmpConfigs) {
-                error = tmpError;
-                if (error != Error::NONE) {
-                    return;
-                }
-
-                *outConfigs = tmpConfigs;
-            });
-
-    return error;
-}
-
-Error Composer::getDisplayName(Display display, std::string* outName)
-{
-    Error error = kDefaultError;
-    mClient->getDisplayName(display,
-            [&](const auto& tmpError, const auto& tmpName) {
-                error = tmpError;
-                if (error != Error::NONE) {
-                    return;
-                }
-
-                *outName = tmpName.c_str();
-            });
-
-    return error;
-}
-
-Error Composer::getDisplayRequests(Display display,
-        uint32_t* outDisplayRequestMask, std::vector<Layer>* outLayers,
-        std::vector<uint32_t>* outLayerRequestMasks)
-{
-    mReader.takeDisplayRequests(display, outDisplayRequestMask,
-            outLayers, outLayerRequestMasks);
-    return Error::NONE;
-}
-
-Error Composer::getDozeSupport(Display display, bool* outSupport)
-{
-    Error error = kDefaultError;
-    mClient->getDozeSupport(display,
-            [&](const auto& tmpError, const auto& tmpSupport) {
-                error = tmpError;
-                if (error != Error::NONE) {
-                    return;
-                }
-
-                *outSupport = tmpSupport;
-            });
-
-    return error;
-}
-
-Error Composer::getHdrCapabilities(Display display,
-        std::vector<Hdr>* outTypes, 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& tmpMaxLuminance,
-                                                const auto& tmpMaxAverageLuminance,
-                                                const auto& tmpMinLuminance) {
-                                                error = tmpError;
-                                                if (error != Error::NONE) {
-                                                    return;
-                                                }
-
-                                                *outTypes = tmpTypes;
-                                                *outMaxLuminance = tmpMaxLuminance;
-                                                *outMaxAverageLuminance = tmpMaxAverageLuminance;
-                                                *outMinLuminance = tmpMinLuminance;
-                                            });
-    } else {
-        mClient->getHdrCapabilities(display,
-                                    [&](const auto& tmpError, const auto& tmpTypes,
-                                        const auto& tmpMaxLuminance,
-                                        const auto& tmpMaxAverageLuminance,
-                                        const auto& tmpMinLuminance) {
-                                        error = tmpError;
-                                        if (error != Error::NONE) {
-                                            return;
-                                        }
-
-                                        outTypes->clear();
-                                        for (auto type : tmpTypes) {
-                                            outTypes->push_back(static_cast<Hdr>(type));
-                                        }
-
-                                        *outMaxLuminance = tmpMaxLuminance;
-                                        *outMaxAverageLuminance = tmpMaxAverageLuminance;
-                                        *outMinLuminance = tmpMinLuminance;
-                                    });
-    }
-
-    return error;
-}
-
-Error Composer::getReleaseFences(Display display,
-        std::vector<Layer>* outLayers, std::vector<int>* outReleaseFences)
-{
-    mReader.takeReleaseFences(display, outLayers, outReleaseFences);
-    return Error::NONE;
-}
-
-Error Composer::presentDisplay(Display display, int* outPresentFence)
-{
-    ATRACE_NAME("HwcPresentDisplay");
-    mWriter.selectDisplay(display);
-    mWriter.presentDisplay();
-
-    Error error = execute();
-    if (error != Error::NONE) {
-        return error;
-    }
-
-    mReader.takePresentFence(display, outPresentFence);
-
-    return Error::NONE;
-}
-
-Error Composer::setActiveConfig(Display display, Config config)
-{
-    auto ret = mClient->setActiveConfig(display, config);
-    return unwrapRet(ret);
-}
-
-Error Composer::setClientTarget(Display display, uint32_t slot,
-        const sp<GraphicBuffer>& target,
-        int acquireFence, Dataspace dataspace,
-        const std::vector<IComposerClient::Rect>& damage)
-{
-    mWriter.selectDisplay(display);
-
-    const native_handle_t* handle = nullptr;
-    if (target.get()) {
-        handle = target->getNativeBuffer()->handle;
-    }
-
-    mWriter.setClientTarget(slot, handle, acquireFence, dataspace, damage);
-    return Error::NONE;
-}
-
-Error Composer::setColorMode(Display display, ColorMode mode,
-        RenderIntent renderIntent)
-{
-    hardware::Return<Error> ret(kDefaultError);
-    if (mClient_2_3) {
-        ret = mClient_2_3->setColorMode_2_3(display, mode, renderIntent);
-    } else if (mClient_2_2) {
-        ret = mClient_2_2->setColorMode_2_2(display, static_cast<types::V1_1::ColorMode>(mode),
-                                            renderIntent);
-    } else {
-        ret = mClient->setColorMode(display,
-                static_cast<types::V1_0::ColorMode>(mode));
-    }
-    return unwrapRet(ret);
-}
-
-Error Composer::setColorTransform(Display display, const float* matrix,
-        ColorTransform hint)
-{
-    mWriter.selectDisplay(display);
-    mWriter.setColorTransform(matrix, hint);
-    return Error::NONE;
-}
-
-Error Composer::setOutputBuffer(Display display, const native_handle_t* buffer,
-        int releaseFence)
-{
-    mWriter.selectDisplay(display);
-    mWriter.setOutputBuffer(0, buffer, dup(releaseFence));
-    return Error::NONE;
-}
-
-Error Composer::setPowerMode(Display display, IComposerClient::PowerMode mode) {
-    Return<Error> ret(Error::UNSUPPORTED);
-    if (mClient_2_2) {
-        ret = mClient_2_2->setPowerMode_2_2(display, mode);
-    } else if (mode != IComposerClient::PowerMode::ON_SUSPEND) {
-        ret = mClient->setPowerMode(display, static_cast<V2_1::IComposerClient::PowerMode>(mode));
-    }
-
-    return unwrapRet(ret);
-}
-
-Error Composer::setVsyncEnabled(Display display, IComposerClient::Vsync enabled)
-{
-    auto ret = mClient->setVsyncEnabled(display, enabled);
-    return unwrapRet(ret);
-}
-
-Error Composer::setClientTargetSlotCount(Display display)
-{
-    const uint32_t bufferSlotCount = BufferQueue::NUM_BUFFER_SLOTS;
-    auto ret = mClient->setClientTargetSlotCount(display, bufferSlotCount);
-    return unwrapRet(ret);
-}
-
-Error Composer::validateDisplay(Display display, uint32_t* outNumTypes,
-        uint32_t* outNumRequests)
-{
-    ATRACE_NAME("HwcValidateDisplay");
-    mWriter.selectDisplay(display);
-    mWriter.validateDisplay();
-
-    Error error = execute();
-    if (error != Error::NONE) {
-        return error;
-    }
-
-    mReader.hasChanges(display, outNumTypes, outNumRequests);
-
-    return Error::NONE;
-}
-
-Error Composer::presentOrValidateDisplay(Display display, uint32_t* outNumTypes,
-                               uint32_t* outNumRequests, int* outPresentFence, uint32_t* state) {
-    ATRACE_NAME("HwcPresentOrValidateDisplay");
-    mWriter.selectDisplay(display);
-    mWriter.presentOrvalidateDisplay();
-
-    Error error = execute();
-    if (error != Error::NONE) {
-        return error;
-    }
-
-   mReader.takePresentOrValidateStage(display, state);
-
-   if (*state == 1) { // Present succeeded
-       mReader.takePresentFence(display, outPresentFence);
-   }
-
-   if (*state == 0) { // Validate succeeded.
-       mReader.hasChanges(display, outNumTypes, outNumRequests);
-   }
-
-   return Error::NONE;
-}
-
-Error Composer::setCursorPosition(Display display, Layer layer,
-        int32_t x, int32_t y)
-{
-    mWriter.selectDisplay(display);
-    mWriter.selectLayer(layer);
-    mWriter.setLayerCursorPosition(x, y);
-    return Error::NONE;
-}
-
-Error Composer::setLayerBuffer(Display display, Layer layer,
-        uint32_t slot, const sp<GraphicBuffer>& buffer, int acquireFence)
-{
-    mWriter.selectDisplay(display);
-    mWriter.selectLayer(layer);
-
-    const native_handle_t* handle = nullptr;
-    if (buffer.get()) {
-        handle = buffer->getNativeBuffer()->handle;
-    }
-
-    mWriter.setLayerBuffer(slot, handle, acquireFence);
-    return Error::NONE;
-}
-
-Error Composer::setLayerSurfaceDamage(Display display, Layer layer,
-        const std::vector<IComposerClient::Rect>& damage)
-{
-    mWriter.selectDisplay(display);
-    mWriter.selectLayer(layer);
-    mWriter.setLayerSurfaceDamage(damage);
-    return Error::NONE;
-}
-
-Error Composer::setLayerBlendMode(Display display, Layer layer,
-        IComposerClient::BlendMode mode)
-{
-    mWriter.selectDisplay(display);
-    mWriter.selectLayer(layer);
-    mWriter.setLayerBlendMode(mode);
-    return Error::NONE;
-}
-
-Error Composer::setLayerColor(Display display, Layer layer,
-        const IComposerClient::Color& color)
-{
-    mWriter.selectDisplay(display);
-    mWriter.selectLayer(layer);
-    mWriter.setLayerColor(color);
-    return Error::NONE;
-}
-
-Error Composer::setLayerCompositionType(Display display, Layer layer,
-        IComposerClient::Composition type)
-{
-    mWriter.selectDisplay(display);
-    mWriter.selectLayer(layer);
-    mWriter.setLayerCompositionType(type);
-    return Error::NONE;
-}
-
-Error Composer::setLayerDataspace(Display display, Layer layer,
-        Dataspace dataspace)
-{
-    mWriter.selectDisplay(display);
-    mWriter.selectLayer(layer);
-    mWriter.setLayerDataspace(dataspace);
-    return Error::NONE;
-}
-
-Error Composer::setLayerDisplayFrame(Display display, Layer layer,
-        const IComposerClient::Rect& frame)
-{
-    mWriter.selectDisplay(display);
-    mWriter.selectLayer(layer);
-    mWriter.setLayerDisplayFrame(frame);
-    return Error::NONE;
-}
-
-Error Composer::setLayerPlaneAlpha(Display display, Layer layer,
-        float alpha)
-{
-    mWriter.selectDisplay(display);
-    mWriter.selectLayer(layer);
-    mWriter.setLayerPlaneAlpha(alpha);
-    return Error::NONE;
-}
-
-Error Composer::setLayerSidebandStream(Display display, Layer layer,
-        const native_handle_t* stream)
-{
-    mWriter.selectDisplay(display);
-    mWriter.selectLayer(layer);
-    mWriter.setLayerSidebandStream(stream);
-    return Error::NONE;
-}
-
-Error Composer::setLayerSourceCrop(Display display, Layer layer,
-        const IComposerClient::FRect& crop)
-{
-    mWriter.selectDisplay(display);
-    mWriter.selectLayer(layer);
-    mWriter.setLayerSourceCrop(crop);
-    return Error::NONE;
-}
-
-Error Composer::setLayerTransform(Display display, Layer layer,
-        Transform transform)
-{
-    mWriter.selectDisplay(display);
-    mWriter.selectLayer(layer);
-    mWriter.setLayerTransform(transform);
-    return Error::NONE;
-}
-
-Error Composer::setLayerVisibleRegion(Display display, Layer layer,
-        const std::vector<IComposerClient::Rect>& visible)
-{
-    mWriter.selectDisplay(display);
-    mWriter.selectLayer(layer);
-    mWriter.setLayerVisibleRegion(visible);
-    return Error::NONE;
-}
-
-Error Composer::setLayerZOrder(Display display, Layer layer, uint32_t z)
-{
-    mWriter.selectDisplay(display);
-    mWriter.selectLayer(layer);
-    mWriter.setLayerZOrder(z);
-    return Error::NONE;
-}
-
-Error Composer::execute()
-{
-    // prepare input command queue
-    bool queueChanged = false;
-    uint32_t commandLength = 0;
-    hidl_vec<hidl_handle> commandHandles;
-    if (!mWriter.writeQueue(&queueChanged, &commandLength, &commandHandles)) {
-        mWriter.reset();
-        return Error::NO_RESOURCES;
-    }
-
-    // set up new input command queue if necessary
-    if (queueChanged) {
-        auto ret = mClient->setInputCommandQueue(*mWriter.getMQDescriptor());
-        auto error = unwrapRet(ret);
-        if (error != Error::NONE) {
-            mWriter.reset();
-            return error;
-        }
-    }
-
-    if (commandLength == 0) {
-        mWriter.reset();
-        return Error::NONE;
-    }
-
-    Error error = kDefaultError;
-    hardware::Return<void> ret;
-    auto hidl_callback = [&](const auto& tmpError, const auto& tmpOutChanged,
-                             const auto& tmpOutLength, const auto& tmpOutHandles)
-                         {
-                             error = tmpError;
-
-                             // set up new output command queue if necessary
-                             if (error == Error::NONE && tmpOutChanged) {
-                                 error = kDefaultError;
-                                 mClient->getOutputCommandQueue(
-                                     [&](const auto& tmpError,
-                                         const auto& tmpDescriptor)
-                                     {
-                                         error = tmpError;
-                                         if (error != Error::NONE) {
-                                             return;
-                                     }
-
-                                     mReader.setMQDescriptor(tmpDescriptor);
-                                 });
-                             }
-
-                             if (error != Error::NONE) {
-                                 return;
-                             }
-
-                             if (mReader.readQueue(tmpOutLength, tmpOutHandles)) {
-                                 error = mReader.parse();
-                                 mReader.reset();
-                             } else {
-                                 error = Error::NO_RESOURCES;
-                             }
-                         };
-    if (mClient_2_2) {
-        ret = mClient_2_2->executeCommands_2_2(commandLength, commandHandles, hidl_callback);
-    } else {
-        ret = mClient->executeCommands(commandLength, commandHandles, hidl_callback);
-    }
-    // executeCommands can fail because of out-of-fd and we do not want to
-    // abort() in that case
-    if (!ret.isOk()) {
-        ALOGE("executeCommands failed because of %s", ret.description().c_str());
-    }
-
-    if (error == Error::NONE) {
-        std::vector<CommandReader::CommandError> commandErrors =
-            mReader.takeErrors();
-
-        for (const auto& cmdErr : commandErrors) {
-            auto command =
-                    static_cast<IComposerClient::Command>(mWriter.getCommand(cmdErr.location));
-
-            if (command == IComposerClient::Command::VALIDATE_DISPLAY ||
-                command == IComposerClient::Command::PRESENT_DISPLAY ||
-                command == IComposerClient::Command::PRESENT_OR_VALIDATE_DISPLAY) {
-                error = cmdErr.error;
-            } else {
-                ALOGW("command 0x%x generated error %d",
-                        command, cmdErr.error);
-            }
-        }
-    }
-
-    mWriter.reset();
-
-    return error;
-}
-
-// Composer HAL 2.2
-
-Error Composer::setLayerPerFrameMetadata(Display display, Layer layer,
-        const std::vector<IComposerClient::PerFrameMetadata>& perFrameMetadatas) {
-    if (!mClient_2_2) {
-        return Error::UNSUPPORTED;
-    }
-
-    mWriter.selectDisplay(display);
-    mWriter.selectLayer(layer);
-    mWriter.setLayerPerFrameMetadata(perFrameMetadatas);
-    return Error::NONE;
-}
-
-std::vector<IComposerClient::PerFrameMetadataKey> Composer::getPerFrameMetadataKeys(
-        Display display) {
-    std::vector<IComposerClient::PerFrameMetadataKey>  keys;
-    if (!mClient_2_2) {
-        return keys;
-    }
-
-    Error error = kDefaultError;
-    if (mClient_2_3) {
-        mClient_2_3->getPerFrameMetadataKeys_2_3(display,
-                                                 [&](const auto& tmpError, const auto& tmpKeys) {
-                                                     error = tmpError;
-                                                     if (error != Error::NONE) {
-                                                         ALOGW("getPerFrameMetadataKeys failed "
-                                                               "with %d",
-                                                               tmpError);
-                                                         return;
-                                                     }
-                                                     keys = tmpKeys;
-                                                 });
-    } else {
-        mClient_2_2
-                ->getPerFrameMetadataKeys(display, [&](const auto& tmpError, const auto& tmpKeys) {
-                    error = tmpError;
-                    if (error != Error::NONE) {
-                        ALOGW("getPerFrameMetadataKeys failed with %d", tmpError);
-                        return;
-                    }
-
-                    keys.clear();
-                    for (auto key : tmpKeys) {
-                        keys.push_back(static_cast<IComposerClient::PerFrameMetadataKey>(key));
-                    }
-                });
-    }
-
-    return keys;
-}
-
-Error Composer::getRenderIntents(Display display, ColorMode colorMode,
-        std::vector<RenderIntent>* outRenderIntents) {
-    if (!mClient_2_2) {
-        outRenderIntents->push_back(RenderIntent::COLORIMETRIC);
-        return Error::NONE;
-    }
-
-    Error error = kDefaultError;
-
-    auto getRenderIntentsLambda = [&](const auto& tmpError, const auto& tmpKeys) {
-        error = tmpError;
-        if (error != Error::NONE) {
-            return;
-        }
-
-        *outRenderIntents = tmpKeys;
-    };
-
-    if (mClient_2_3) {
-        mClient_2_3->getRenderIntents_2_3(display, colorMode, getRenderIntentsLambda);
-    } else {
-        mClient_2_2->getRenderIntents(display, static_cast<types::V1_1::ColorMode>(colorMode),
-                                      getRenderIntentsLambda);
-    }
-
-    return error;
-}
-
-Error Composer::getDataspaceSaturationMatrix(Dataspace dataspace, mat4* outMatrix)
-{
-    if (!mClient_2_2) {
-        *outMatrix = mat4();
-        return Error::NONE;
-    }
-
-    Error error = kDefaultError;
-    mClient_2_2->getDataspaceSaturationMatrix(static_cast<types::V1_1::Dataspace>(dataspace),
-                                              [&](const auto& tmpError, const auto& tmpMatrix) {
-                                                  error = tmpError;
-                                                  if (error != Error::NONE) {
-                                                      return;
-                                                  }
-                                                  *outMatrix = mat4(tmpMatrix.data());
-                                              });
-
-    return error;
-}
-
-// Composer HAL 2.3
-
-Error Composer::getDisplayIdentificationData(Display display, uint8_t* outPort,
-                                             std::vector<uint8_t>* outData) {
-    if (!mClient_2_3) {
-        return Error::UNSUPPORTED;
-    }
-
-    Error error = kDefaultError;
-    mClient_2_3->getDisplayIdentificationData(display,
-                                              [&](const auto& tmpError, const auto& tmpPort,
-                                                  const auto& tmpData) {
-                                                  error = tmpError;
-                                                  if (error != Error::NONE) {
-                                                      return;
-                                                  }
-
-                                                  *outPort = tmpPort;
-                                                  *outData = tmpData;
-                                              });
-
-    return error;
-}
-
-Error Composer::setLayerColorTransform(Display display, Layer layer, const float* matrix)
-{
-    if (!mClient_2_3) {
-        return Error::UNSUPPORTED;
-    }
-
-    mWriter.selectDisplay(display);
-    mWriter.selectLayer(layer);
-    mWriter.setLayerColorTransform(matrix);
-    return Error::NONE;
-}
-
-Error Composer::getDisplayedContentSamplingAttributes(Display display, PixelFormat* outFormat,
-                                                      Dataspace* outDataspace,
-                                                      uint8_t* outComponentMask) {
-    if (!outFormat || !outDataspace || !outComponentMask) {
-        return Error::BAD_PARAMETER;
-    }
-    if (!mClient_2_3) {
-        return Error::UNSUPPORTED;
-    }
-    Error error = kDefaultError;
-    mClient_2_3->getDisplayedContentSamplingAttributes(display,
-                                                       [&](const auto tmpError,
-                                                           const auto& tmpFormat,
-                                                           const auto& tmpDataspace,
-                                                           const auto& tmpComponentMask) {
-                                                           error = tmpError;
-                                                           if (error == Error::NONE) {
-                                                               *outFormat = tmpFormat;
-                                                               *outDataspace = tmpDataspace;
-                                                               *outComponentMask =
-                                                                       static_cast<uint8_t>(
-                                                                               tmpComponentMask);
-                                                           }
-                                                       });
-    return error;
-}
-
-Error Composer::setDisplayContentSamplingEnabled(Display display, bool enabled,
-                                                 uint8_t componentMask, uint64_t maxFrames) {
-    if (!mClient_2_3) {
-        return Error::UNSUPPORTED;
-    }
-
-    auto enable = enabled ? V2_3::IComposerClient::DisplayedContentSampling::ENABLE
-                          : V2_3::IComposerClient::DisplayedContentSampling::DISABLE;
-    return mClient_2_3->setDisplayedContentSamplingEnabled(display, enable, componentMask,
-                                                           maxFrames);
-}
-
-Error Composer::getDisplayedContentSample(Display display, uint64_t maxFrames, uint64_t timestamp,
-                                          DisplayedFrameStats* outStats) {
-    if (!outStats) {
-        return Error::BAD_PARAMETER;
-    }
-    if (!mClient_2_3) {
-        return Error::UNSUPPORTED;
-    }
-    Error error = kDefaultError;
-    mClient_2_3->getDisplayedContentSample(display, maxFrames, timestamp,
-                                           [&](const auto tmpError, auto tmpNumFrames,
-                                               const auto& tmpSamples0, const auto& tmpSamples1,
-                                               const auto& tmpSamples2, const auto& tmpSamples3) {
-                                               error = tmpError;
-                                               if (error == Error::NONE) {
-                                                   outStats->numFrames = tmpNumFrames;
-                                                   outStats->component_0_sample = tmpSamples0;
-                                                   outStats->component_1_sample = tmpSamples1;
-                                                   outStats->component_2_sample = tmpSamples2;
-                                                   outStats->component_3_sample = tmpSamples3;
-                                               }
-                                           });
-    return error;
-}
-
-Error Composer::setLayerPerFrameMetadataBlobs(
-        Display display, Layer layer,
-        const std::vector<IComposerClient::PerFrameMetadataBlob>& metadata) {
-    if (!mClient_2_3) {
-        return Error::UNSUPPORTED;
-    }
-
-    mWriter.selectDisplay(display);
-    mWriter.selectLayer(layer);
-    mWriter.setLayerPerFrameMetadataBlobs(metadata);
-    return Error::NONE;
-}
-
-Error Composer::setDisplayBrightness(Display display, float brightness) {
-    if (!mClient_2_3) {
-        return Error::UNSUPPORTED;
-    }
-    return mClient_2_3->setDisplayBrightness(display, brightness);
-}
-
-// Composer HAL 2.4
-
-Error Composer::getDisplayCapabilities(Display display,
-                                       std::vector<DisplayCapability>* outCapabilities) {
-    if (!mClient_2_3) {
-        return Error::UNSUPPORTED;
-    }
-
-    V2_4::Error error = kDefaultError_2_4;
-    if (mClient_2_4) {
-        mClient_2_4->getDisplayCapabilities_2_4(display,
-                                                [&](const auto& tmpError, const auto& tmpCaps) {
-                                                    error = tmpError;
-                                                    if (error != V2_4::Error::NONE) {
-                                                        return;
-                                                    }
-                                                    *outCapabilities = tmpCaps;
-                                                });
-    } else {
-        mClient_2_3
-                ->getDisplayCapabilities(display, [&](const auto& tmpError, const auto& tmpCaps) {
-                    error = static_cast<V2_4::Error>(tmpError);
-                    if (error != V2_4::Error::NONE) {
-                        return;
-                    }
-
-                    outCapabilities->resize(tmpCaps.size());
-                    std::transform(tmpCaps.begin(), tmpCaps.end(), outCapabilities->begin(),
-                                   [](auto cap) { return static_cast<DisplayCapability>(cap); });
-                });
-    }
-
-    return static_cast<Error>(error);
-}
-
-V2_4::Error Composer::getDisplayConnectionType(Display display,
-                                               IComposerClient::DisplayConnectionType* outType) {
-    using Error = V2_4::Error;
-    if (!mClient_2_4) {
-        return Error::UNSUPPORTED;
-    }
-
-    Error error = kDefaultError_2_4;
-    mClient_2_4->getDisplayConnectionType(display, [&](const auto& tmpError, const auto& tmpType) {
-        error = tmpError;
-        if (error != V2_4::Error::NONE) {
-            return;
-        }
-
-        *outType = tmpType;
-    });
-
-    return error;
-}
-
-V2_4::Error Composer::getDisplayVsyncPeriod(Display display, VsyncPeriodNanos* outVsyncPeriod) {
-    using Error = V2_4::Error;
-    if (!mClient_2_4) {
-        return Error::UNSUPPORTED;
-    }
-
-    Error error = kDefaultError_2_4;
-    mClient_2_4->getDisplayVsyncPeriod(display,
-                                       [&](const auto& tmpError, const auto& tmpVsyncPeriod) {
-                                           error = tmpError;
-                                           if (error != Error::NONE) {
-                                               return;
-                                           }
-
-                                           *outVsyncPeriod = tmpVsyncPeriod;
-                                       });
-
-    return error;
-}
-
-V2_4::Error Composer::setActiveConfigWithConstraints(
-        Display display, Config config,
-        const IComposerClient::VsyncPeriodChangeConstraints& vsyncPeriodChangeConstraints,
-        VsyncPeriodChangeTimeline* outTimeline) {
-    using Error = V2_4::Error;
-    if (!mClient_2_4) {
-        return Error::UNSUPPORTED;
-    }
-
-    Error error = kDefaultError_2_4;
-    mClient_2_4->setActiveConfigWithConstraints(display, config, vsyncPeriodChangeConstraints,
-                                                [&](const auto& tmpError, const auto& tmpTimeline) {
-                                                    error = tmpError;
-                                                    if (error != Error::NONE) {
-                                                        return;
-                                                    }
-
-                                                    *outTimeline = tmpTimeline;
-                                                });
-
-    return error;
-}
-
-V2_4::Error Composer::setAutoLowLatencyMode(Display display, bool on) {
-    using Error = V2_4::Error;
-    if (!mClient_2_4) {
-        return Error::UNSUPPORTED;
-    }
-
-    return mClient_2_4->setAutoLowLatencyMode(display, on);
-}
-
-V2_4::Error Composer::getSupportedContentTypes(
-        Display displayId, std::vector<IComposerClient::ContentType>* outSupportedContentTypes) {
-    using Error = V2_4::Error;
-    if (!mClient_2_4) {
-        return Error::UNSUPPORTED;
-    }
-
-    Error error = kDefaultError_2_4;
-    mClient_2_4->getSupportedContentTypes(displayId,
-                                          [&](const auto& tmpError,
-                                              const auto& tmpSupportedContentTypes) {
-                                              error = tmpError;
-                                              if (error != Error::NONE) {
-                                                  return;
-                                              }
-
-                                              *outSupportedContentTypes = tmpSupportedContentTypes;
-                                          });
-    return error;
-}
-
-V2_4::Error Composer::setContentType(Display display, IComposerClient::ContentType contentType) {
-    using Error = V2_4::Error;
-    if (!mClient_2_4) {
-        return Error::UNSUPPORTED;
-    }
-
-    return mClient_2_4->setContentType(display, contentType);
-}
-
-V2_4::Error Composer::setLayerGenericMetadata(Display display, Layer layer, const std::string& key,
-                                              bool mandatory, const std::vector<uint8_t>& value) {
-    using Error = V2_4::Error;
-    if (!mClient_2_4) {
-        return Error::UNSUPPORTED;
-    }
-    mWriter.selectDisplay(display);
-    mWriter.selectLayer(layer);
-    mWriter.setLayerGenericMetadata(key, mandatory, value);
-    return Error::NONE;
-}
-
-V2_4::Error Composer::getLayerGenericMetadataKeys(
-        std::vector<IComposerClient::LayerGenericMetadataKey>* outKeys) {
-    using Error = V2_4::Error;
-    if (!mClient_2_4) {
-        return Error::UNSUPPORTED;
-    }
-    Error error = kDefaultError_2_4;
-    mClient_2_4->getLayerGenericMetadataKeys([&](const auto& tmpError, const auto& tmpKeys) {
-        error = tmpError;
-        if (error != Error::NONE) {
-            return;
-        }
-
-        *outKeys = tmpKeys;
-    });
-    return error;
-}
-
-Error Composer::getClientTargetProperty(
-        Display display, IComposerClient::ClientTargetProperty* outClientTargetProperty) {
-    mReader.takeClientTargetProperty(display, outClientTargetProperty);
-    return Error::NONE;
-}
-
-CommandReader::~CommandReader()
-{
-    resetData();
-}
-
-Error CommandReader::parse()
-{
-    resetData();
-
-    IComposerClient::Command command;
-    uint16_t length = 0;
-
-    while (!isEmpty()) {
-        if (!beginCommand(&command, &length)) {
-            break;
-        }
-
-        bool parsed = false;
-        switch (command) {
-        case IComposerClient::Command::SELECT_DISPLAY:
-            parsed = parseSelectDisplay(length);
-            break;
-        case IComposerClient::Command::SET_ERROR:
-            parsed = parseSetError(length);
-            break;
-        case IComposerClient::Command::SET_CHANGED_COMPOSITION_TYPES:
-            parsed = parseSetChangedCompositionTypes(length);
-            break;
-        case IComposerClient::Command::SET_DISPLAY_REQUESTS:
-            parsed = parseSetDisplayRequests(length);
-            break;
-        case IComposerClient::Command::SET_PRESENT_FENCE:
-            parsed = parseSetPresentFence(length);
-            break;
-        case IComposerClient::Command::SET_RELEASE_FENCES:
-            parsed = parseSetReleaseFences(length);
-            break;
-        case IComposerClient::Command ::SET_PRESENT_OR_VALIDATE_DISPLAY_RESULT:
-            parsed = parseSetPresentOrValidateDisplayResult(length);
-            break;
-        case IComposerClient::Command::SET_CLIENT_TARGET_PROPERTY:
-            parsed = parseSetClientTargetProperty(length);
-            break;
-        default:
-            parsed = false;
-            break;
-        }
-
-        endCommand();
-
-        if (!parsed) {
-            ALOGE("failed to parse command 0x%x length %" PRIu16,
-                    command, length);
-            break;
-        }
-    }
-
-    return isEmpty() ? Error::NONE : Error::NO_RESOURCES;
-}
-
-bool CommandReader::parseSelectDisplay(uint16_t length)
-{
-    if (length != CommandWriterBase::kSelectDisplayLength) {
-        return false;
-    }
-
-    mCurrentReturnData = &mReturnData[read64()];
-
-    return true;
-}
-
-bool CommandReader::parseSetError(uint16_t length)
-{
-    if (length != CommandWriterBase::kSetErrorLength) {
-        return false;
-    }
-
-    auto location = read();
-    auto error = static_cast<Error>(readSigned());
-
-    mErrors.emplace_back(CommandError{location, error});
-
-    return true;
-}
-
-bool CommandReader::parseSetChangedCompositionTypes(uint16_t length)
-{
-    // (layer id, composition type) pairs
-    if (length % 3 != 0 || !mCurrentReturnData) {
-        return false;
-    }
-
-    uint32_t count = length / 3;
-    mCurrentReturnData->changedLayers.reserve(count);
-    mCurrentReturnData->compositionTypes.reserve(count);
-    while (count > 0) {
-        auto layer = read64();
-        auto type = static_cast<IComposerClient::Composition>(readSigned());
-
-        mCurrentReturnData->changedLayers.push_back(layer);
-        mCurrentReturnData->compositionTypes.push_back(type);
-
-        count--;
-    }
-
-    return true;
-}
-
-bool CommandReader::parseSetDisplayRequests(uint16_t length)
-{
-    // display requests followed by (layer id, layer requests) pairs
-    if (length % 3 != 1 || !mCurrentReturnData) {
-        return false;
-    }
-
-    mCurrentReturnData->displayRequests = read();
-
-    uint32_t count = (length - 1) / 3;
-    mCurrentReturnData->requestedLayers.reserve(count);
-    mCurrentReturnData->requestMasks.reserve(count);
-    while (count > 0) {
-        auto layer = read64();
-        auto layerRequestMask = read();
-
-        mCurrentReturnData->requestedLayers.push_back(layer);
-        mCurrentReturnData->requestMasks.push_back(layerRequestMask);
-
-        count--;
-    }
-
-    return true;
-}
-
-bool CommandReader::parseSetPresentFence(uint16_t length)
-{
-    if (length != CommandWriterBase::kSetPresentFenceLength ||
-            !mCurrentReturnData) {
-        return false;
-    }
-
-    if (mCurrentReturnData->presentFence >= 0) {
-        close(mCurrentReturnData->presentFence);
-    }
-    mCurrentReturnData->presentFence = readFence();
-
-    return true;
-}
-
-bool CommandReader::parseSetReleaseFences(uint16_t length)
-{
-    // (layer id, release fence index) pairs
-    if (length % 3 != 0 || !mCurrentReturnData) {
-        return false;
-    }
-
-    uint32_t count = length / 3;
-    mCurrentReturnData->releasedLayers.reserve(count);
-    mCurrentReturnData->releaseFences.reserve(count);
-    while (count > 0) {
-        auto layer = read64();
-        auto fence = readFence();
-
-        mCurrentReturnData->releasedLayers.push_back(layer);
-        mCurrentReturnData->releaseFences.push_back(fence);
-
-        count--;
-    }
-
-    return true;
-}
-
-bool CommandReader::parseSetPresentOrValidateDisplayResult(uint16_t length)
-{
-    if (length != CommandWriterBase::kPresentOrValidateDisplayResultLength || !mCurrentReturnData) {
-        return false;
-    }
-    mCurrentReturnData->presentOrValidateState = read();
-    return true;
-}
-
-bool CommandReader::parseSetClientTargetProperty(uint16_t length) {
-    if (length != CommandWriterBase::kSetClientTargetPropertyLength || !mCurrentReturnData) {
-        return false;
-    }
-    mCurrentReturnData->clientTargetProperty.pixelFormat = static_cast<PixelFormat>(readSigned());
-    mCurrentReturnData->clientTargetProperty.dataspace = static_cast<Dataspace>(readSigned());
-    return true;
-}
-
-void CommandReader::resetData()
-{
-    mErrors.clear();
-
-    for (auto& data : mReturnData) {
-        if (data.second.presentFence >= 0) {
-            close(data.second.presentFence);
-        }
-        for (auto fence : data.second.releaseFences) {
-            if (fence >= 0) {
-                close(fence);
-            }
-        }
-    }
-
-    mReturnData.clear();
-    mCurrentReturnData = nullptr;
-}
-
-std::vector<CommandReader::CommandError> CommandReader::takeErrors()
-{
-    return std::move(mErrors);
-}
-
-bool CommandReader::hasChanges(Display display,
-        uint32_t* outNumChangedCompositionTypes,
-        uint32_t* outNumLayerRequestMasks) const
-{
-    auto found = mReturnData.find(display);
-    if (found == mReturnData.end()) {
-        *outNumChangedCompositionTypes = 0;
-        *outNumLayerRequestMasks = 0;
-        return false;
-    }
-
-    const ReturnData& data = found->second;
-
-    *outNumChangedCompositionTypes = data.compositionTypes.size();
-    *outNumLayerRequestMasks = data.requestMasks.size();
-
-    return !(data.compositionTypes.empty() && data.requestMasks.empty());
-}
-
-void CommandReader::takeChangedCompositionTypes(Display display,
-        std::vector<Layer>* outLayers,
-        std::vector<IComposerClient::Composition>* outTypes)
-{
-    auto found = mReturnData.find(display);
-    if (found == mReturnData.end()) {
-        outLayers->clear();
-        outTypes->clear();
-        return;
-    }
-
-    ReturnData& data = found->second;
-
-    *outLayers = std::move(data.changedLayers);
-    *outTypes = std::move(data.compositionTypes);
-}
-
-void CommandReader::takeDisplayRequests(Display display,
-        uint32_t* outDisplayRequestMask, std::vector<Layer>* outLayers,
-        std::vector<uint32_t>* outLayerRequestMasks)
-{
-    auto found = mReturnData.find(display);
-    if (found == mReturnData.end()) {
-        *outDisplayRequestMask = 0;
-        outLayers->clear();
-        outLayerRequestMasks->clear();
-        return;
-    }
-
-    ReturnData& data = found->second;
-
-    *outDisplayRequestMask = data.displayRequests;
-    *outLayers = std::move(data.requestedLayers);
-    *outLayerRequestMasks = std::move(data.requestMasks);
-}
-
-void CommandReader::takeReleaseFences(Display display,
-        std::vector<Layer>* outLayers, std::vector<int>* outReleaseFences)
-{
-    auto found = mReturnData.find(display);
-    if (found == mReturnData.end()) {
-        outLayers->clear();
-        outReleaseFences->clear();
-        return;
-    }
-
-    ReturnData& data = found->second;
-
-    *outLayers = std::move(data.releasedLayers);
-    *outReleaseFences = std::move(data.releaseFences);
-}
-
-void CommandReader::takePresentFence(Display display, int* outPresentFence)
-{
-    auto found = mReturnData.find(display);
-    if (found == mReturnData.end()) {
-        *outPresentFence = -1;
-        return;
-    }
-
-    ReturnData& data = found->second;
-
-    *outPresentFence = data.presentFence;
-    data.presentFence = -1;
-}
-
-void CommandReader::takePresentOrValidateStage(Display display, uint32_t* state) {
-    auto found = mReturnData.find(display);
-    if (found == mReturnData.end()) {
-        *state= -1;
-        return;
-    }
-    ReturnData& data = found->second;
-    *state = data.presentOrValidateState;
-}
-
-void CommandReader::takeClientTargetProperty(
-        Display display, IComposerClient::ClientTargetProperty* outClientTargetProperty) {
-    auto found = mReturnData.find(display);
-
-    // If not found, return the default values.
-    if (found == mReturnData.end()) {
-        outClientTargetProperty->pixelFormat = PixelFormat::RGBA_8888;
-        outClientTargetProperty->dataspace = Dataspace::UNKNOWN;
-        return;
-    }
-
-    ReturnData& data = found->second;
-    *outClientTargetProperty = data.clientTargetProperty;
-}
-
-} // namespace impl
-} // namespace Hwc2
-} // namespace android
-
-// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic pop // ignored "-Wconversion"
+} // namespace android::Hwc2
diff --git a/services/surfaceflinger/DisplayHardware/ComposerHal.h b/services/surfaceflinger/DisplayHardware/ComposerHal.h
index fe114b9..2dc0830 100644
--- a/services/surfaceflinger/DisplayHardware/ComposerHal.h
+++ b/services/surfaceflinger/DisplayHardware/ComposerHal.h
@@ -14,24 +14,15 @@
  * limitations under the License.
  */
 
-#ifndef ANDROID_SF_COMPOSER_HAL_H
-#define ANDROID_SF_COMPOSER_HAL_H
+#pragma once
 
 #include <memory>
-#include <optional>
-#include <string>
-#include <unordered_map>
-#include <utility>
-#include <vector>
 
 // TODO(b/129481165): remove the #pragma below and fix conversion issues
 #pragma clang diagnostic push
 #pragma clang diagnostic ignored "-Wconversion"
 #pragma clang diagnostic ignored "-Wextra"
 
-#include <android/hardware/graphics/common/1.1/types.h>
-#include <android/hardware/graphics/composer/2.4/IComposer.h>
-#include <android/hardware/graphics/composer/2.4/IComposerClient.h>
 #include <composer-command-buffer/2.4/ComposerCommandBuffer.h>
 #include <gui/BufferQueue.h>
 #include <gui/HdrMetadata.h>
@@ -40,10 +31,23 @@
 #include <ui/GraphicBuffer.h>
 #include <utils/StrongPointer.h>
 
+#include <aidl/android/hardware/graphics/common/DisplayDecorationSupport.h>
+#include <aidl/android/hardware/graphics/composer3/Capability.h>
+#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/IComposerCallback.h>
+
+#include <aidl/android/hardware/graphics/common/Transform.h>
+#include <optional>
+
 // TODO(b/129481165): remove the #pragma below and fix conversion issues
 #pragma clang diagnostic pop // ignored "-Wconversion -Wextra"
 
 namespace android {
+namespace HWC2 {
+struct ComposerCallback;
+} // namespace HWC2
 
 namespace Hwc2 {
 
@@ -53,6 +57,7 @@
 namespace V2_2 = hardware::graphics::composer::V2_2;
 namespace V2_3 = hardware::graphics::composer::V2_3;
 namespace V2_4 = hardware::graphics::composer::V2_4;
+namespace V3_0 = ::aidl::android::hardware::graphics::composer3;
 
 using types::V1_0::ColorTransform;
 using types::V1_0::Transform;
@@ -73,19 +78,33 @@
 using V2_4::IComposerClient;
 using V2_4::VsyncPeriodChangeTimeline;
 using V2_4::VsyncPeriodNanos;
-using DisplayCapability = IComposerClient::DisplayCapability;
 using PerFrameMetadata = IComposerClient::PerFrameMetadata;
 using PerFrameMetadataKey = IComposerClient::PerFrameMetadataKey;
 using PerFrameMetadataBlob = IComposerClient::PerFrameMetadataBlob;
+using AidlTransform = ::aidl::android::hardware::graphics::common::Transform;
 
 class Composer {
 public:
+    static std::unique_ptr<Composer> create(const std::string& serviceName);
+
     virtual ~Composer() = 0;
 
-    virtual std::vector<IComposer::Capability> getCapabilities() = 0;
+    enum class OptionalFeature {
+        RefreshRateSwitching,
+        ExpectedPresentTime,
+        // Whether setDisplayBrightness is able to be applied as part of a display command.
+        DisplayBrightnessCommand,
+        KernelIdleTimer,
+        PhysicalDisplayOrientation,
+    };
+
+    virtual bool isSupported(OptionalFeature) const = 0;
+
+    virtual std::vector<aidl::android::hardware::graphics::composer3::Capability>
+    getCapabilities() = 0;
     virtual std::string dumpDebugInfo() = 0;
 
-    virtual void registerCallback(const sp<IComposerCallback>& callback) = 0;
+    virtual void registerCallback(HWC2::ComposerCallback& callback) = 0;
 
     // Reset all pending commands in the command buffer. Useful if you want to
     // skip a frame but have already queued some commands.
@@ -105,9 +124,8 @@
     virtual Error destroyLayer(Display display, Layer layer) = 0;
 
     virtual Error getActiveConfig(Display display, Config* outConfig) = 0;
-    virtual Error getChangedCompositionTypes(
-            Display display, std::vector<Layer>* outLayers,
-            std::vector<IComposerClient::Composition>* outTypes) = 0;
+    virtual Error getChangedCompositionTypes(Display display, std::vector<Layer>* outLayers,
+                                             std::vector<V3_0::Composition>* outTypes) = 0;
     virtual Error getColorModes(Display display, std::vector<ColorMode>* outModes) = 0;
     virtual Error getDisplayAttribute(Display display, Config config,
                                       IComposerClient::Attribute attribute, int32_t* outValue) = 0;
@@ -119,6 +137,7 @@
                                      std::vector<uint32_t>* outLayerRequestMasks) = 0;
 
     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,
                                      float* outMaxLuminance, float* outMaxAverageLuminance,
                                      float* outMinLuminance) = 0;
@@ -139,7 +158,7 @@
                                   int acquireFence, Dataspace dataspace,
                                   const std::vector<IComposerClient::Rect>& damage) = 0;
     virtual Error setColorMode(Display display, ColorMode mode, RenderIntent renderIntent) = 0;
-    virtual Error setColorTransform(Display display, const float* matrix, ColorTransform hint) = 0;
+    virtual Error setColorTransform(Display display, const float* matrix) = 0;
     virtual Error setOutputBuffer(Display display, const native_handle_t* buffer,
                                   int releaseFence) = 0;
     virtual Error setPowerMode(Display display, IComposerClient::PowerMode mode) = 0;
@@ -147,12 +166,12 @@
 
     virtual Error setClientTargetSlotCount(Display display) = 0;
 
-    virtual Error validateDisplay(Display display, uint32_t* outNumTypes,
-                                  uint32_t* outNumRequests) = 0;
+    virtual Error validateDisplay(Display display, nsecs_t expectedPresentTime,
+                                  uint32_t* outNumTypes, uint32_t* outNumRequests) = 0;
 
-    virtual Error presentOrValidateDisplay(Display display, uint32_t* outNumTypes,
-                                           uint32_t* outNumRequests, int* outPresentFence,
-                                           uint32_t* state) = 0;
+    virtual Error presentOrValidateDisplay(Display display, nsecs_t expectedPresentTime,
+                                           uint32_t* outNumTypes, uint32_t* outNumRequests,
+                                           int* outPresentFence, uint32_t* state) = 0;
 
     virtual Error setCursorPosition(Display display, Layer layer, int32_t x, int32_t y) = 0;
     /* see setClientTarget for the purpose of slot */
@@ -162,10 +181,12 @@
                                         const std::vector<IComposerClient::Rect>& damage) = 0;
     virtual Error setLayerBlendMode(Display display, Layer layer,
                                     IComposerClient::BlendMode mode) = 0;
-    virtual Error setLayerColor(Display display, Layer layer,
-                                const IComposerClient::Color& color) = 0;
-    virtual Error setLayerCompositionType(Display display, Layer layer,
-                                          IComposerClient::Composition type) = 0;
+    virtual Error setLayerColor(
+            Display display, Layer layer,
+            const aidl::android::hardware::graphics::composer3::Color& color) = 0;
+    virtual Error setLayerCompositionType(
+            Display display, Layer layer,
+            aidl::android::hardware::graphics::composer3::Composition type) = 0;
     virtual Error setLayerDataspace(Display display, Layer layer, Dataspace dataspace) = 0;
     virtual Error setLayerDisplayFrame(Display display, Layer layer,
                                        const IComposerClient::Rect& frame) = 0;
@@ -203,12 +224,24 @@
                                             DisplayedFrameStats* outStats) = 0;
     virtual Error setLayerPerFrameMetadataBlobs(
             Display display, Layer layer, const std::vector<PerFrameMetadataBlob>& metadata) = 0;
-    virtual Error setDisplayBrightness(Display display, float brightness) = 0;
+    // Options for setting the display brightness
+    struct DisplayBrightnessOptions {
+        // If true, then immediately submits a brightness change request to composer. Otherwise,
+        // submission of the brightness change may be deferred until presenting the next frame.
+        // applyImmediately should only be false if OptionalFeature::DisplayBrightnessCommand is
+        // supported.
+        bool applyImmediately = true;
+
+        bool operator==(const DisplayBrightnessOptions& other) const {
+            return applyImmediately == other.applyImmediately;
+        }
+    };
+    virtual Error setDisplayBrightness(Display display, float brightness,
+                                       const DisplayBrightnessOptions& options) = 0;
 
     // Composer HAL 2.4
-    virtual bool isVsyncPeriodSwitchSupported() = 0;
     virtual Error getDisplayCapabilities(Display display,
-                                         std::vector<DisplayCapability>* outCapabilities) = 0;
+                                         std::vector<V3_0::DisplayCapability>* outCapabilities) = 0;
     virtual V2_4::Error getDisplayConnectionType(
             Display display, IComposerClient::DisplayConnectionType* outType) = 0;
     virtual V2_4::Error getDisplayVsyncPeriod(Display display,
@@ -229,283 +262,26 @@
                                                 const std::vector<uint8_t>& value) = 0;
     virtual V2_4::Error getLayerGenericMetadataKeys(
             std::vector<IComposerClient::LayerGenericMetadataKey>* outKeys) = 0;
+
     virtual Error getClientTargetProperty(
-            Display display, IComposerClient::ClientTargetProperty* outClientTargetProperty) = 0;
-};
+            Display display, IComposerClient::ClientTargetProperty* outClientTargetProperty,
+            float* outBrightness) = 0;
 
-namespace impl {
-
-class CommandReader : public CommandReaderBase {
-public:
-    ~CommandReader();
-
-    // Parse and execute commands from the command queue.  The commands are
-    // actually return values from the server and will be saved in ReturnData.
-    Error parse();
-
-    // Get and clear saved errors.
-    struct CommandError {
-        uint32_t location;
-        Error error;
-    };
-    std::vector<CommandError> takeErrors();
-
-    bool hasChanges(Display display, uint32_t* outNumChangedCompositionTypes,
-            uint32_t* outNumLayerRequestMasks) const;
-
-    // Get and clear saved changed composition types.
-    void takeChangedCompositionTypes(Display display,
-            std::vector<Layer>* outLayers,
-            std::vector<IComposerClient::Composition>* outTypes);
-
-    // Get and clear saved display requests.
-    void takeDisplayRequests(Display display,
-        uint32_t* outDisplayRequestMask, std::vector<Layer>* outLayers,
-        std::vector<uint32_t>* outLayerRequestMasks);
-
-    // Get and clear saved release fences.
-    void takeReleaseFences(Display display, std::vector<Layer>* outLayers,
-            std::vector<int>* outReleaseFences);
-
-    // Get and clear saved present fence.
-    void takePresentFence(Display display, int* outPresentFence);
-
-    // Get what stage succeeded during PresentOrValidate: Present or Validate
-    void takePresentOrValidateStage(Display display, uint32_t * state);
-
-    // Get the client target properties requested by hardware composer.
-    void takeClientTargetProperty(Display display,
-                                  IComposerClient::ClientTargetProperty* outClientTargetProperty);
-
-private:
-    void resetData();
-
-    bool parseSelectDisplay(uint16_t length);
-    bool parseSetError(uint16_t length);
-    bool parseSetChangedCompositionTypes(uint16_t length);
-    bool parseSetDisplayRequests(uint16_t length);
-    bool parseSetPresentFence(uint16_t length);
-    bool parseSetReleaseFences(uint16_t length);
-    bool parseSetPresentOrValidateDisplayResult(uint16_t length);
-    bool parseSetClientTargetProperty(uint16_t length);
-
-    struct ReturnData {
-        uint32_t displayRequests = 0;
-
-        std::vector<Layer> changedLayers;
-        std::vector<IComposerClient::Composition> compositionTypes;
-
-        std::vector<Layer> requestedLayers;
-        std::vector<uint32_t> requestMasks;
-
-        int presentFence = -1;
-
-        std::vector<Layer> releasedLayers;
-        std::vector<int> releaseFences;
-
-        uint32_t presentOrValidateState;
-
-        // Composer 2.4 implementation can return a client target property
-        // structure to indicate the client target properties that hardware
-        // composer requests. The composer client must change the client target
-        // properties to match this request.
-        IComposerClient::ClientTargetProperty clientTargetProperty{PixelFormat::RGBA_8888,
-                                                                   Dataspace::UNKNOWN};
-    };
-
-    std::vector<CommandError> mErrors;
-    std::unordered_map<Display, ReturnData> mReturnData;
-
-    // When SELECT_DISPLAY is parsed, this is updated to point to the
-    // display's return data in mReturnData.  We use it to avoid repeated
-    // map lookups.
-    ReturnData* mCurrentReturnData;
-};
-
-// Composer is a wrapper to IComposer, a proxy to server-side composer.
-class Composer final : public Hwc2::Composer {
-public:
-    explicit Composer(const std::string& serviceName);
-    ~Composer() override;
-
-    std::vector<IComposer::Capability> getCapabilities() override;
-    std::string dumpDebugInfo() override;
-
-    void registerCallback(const sp<IComposerCallback>& callback) override;
-
-    // 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;
-
-    // Explicitly flush all pending commands in the command buffer.
-    Error executeCommands() override;
-
-    uint32_t getMaxVirtualDisplayCount() override;
-    Error createVirtualDisplay(uint32_t width, uint32_t height, PixelFormat* format,
-                               Display* outDisplay) override;
-    Error destroyVirtualDisplay(Display display) override;
-
-    Error acceptDisplayChanges(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 getChangedCompositionTypes(Display display, std::vector<Layer>* outLayers,
-                                     std::vector<IComposerClient::Composition>* outTypes) override;
-    Error getColorModes(Display display, std::vector<ColorMode>* outModes) override;
-    Error getDisplayAttribute(Display display, Config config, IComposerClient::Attribute attribute,
-                              int32_t* outValue) override;
-    Error getDisplayConfigs(Display display, std::vector<Config>* outConfigs);
-    Error getDisplayName(Display display, std::string* outName) override;
-
-    Error getDisplayRequests(Display display, uint32_t* outDisplayRequestMask,
-                             std::vector<Layer>* outLayers,
-                             std::vector<uint32_t>* outLayerRequestMasks) override;
-
-    Error getDozeSupport(Display display, bool* outSupport) override;
-    Error getHdrCapabilities(Display display, std::vector<Hdr>* outTypes, float* outMaxLuminance,
-                             float* outMaxAverageLuminance, float* outMinLuminance) override;
-
-    Error getReleaseFences(Display display, std::vector<Layer>* outLayers,
-                           std::vector<int>* outReleaseFences) override;
-
-    Error presentDisplay(Display display, int* outPresentFence) override;
-
-    Error setActiveConfig(Display display, Config config) override;
-
-    /*
-     * The composer caches client targets internally.  When target is nullptr,
-     * the composer uses slot to look up the client target from its cache.
-     * When target is not nullptr, the cache is updated with the new target.
-     */
-    Error setClientTarget(Display display, uint32_t slot, const sp<GraphicBuffer>& target,
-                          int acquireFence, Dataspace dataspace,
-                          const std::vector<IComposerClient::Rect>& damage) override;
-    Error setColorMode(Display display, ColorMode mode, RenderIntent renderIntent) override;
-    Error setColorTransform(Display display, const float* matrix, ColorTransform hint) override;
-    Error setOutputBuffer(Display display, const native_handle_t* buffer,
-                          int releaseFence) override;
-    Error setPowerMode(Display display, IComposerClient::PowerMode mode) override;
-    Error setVsyncEnabled(Display display, IComposerClient::Vsync enabled) override;
-
-    Error setClientTargetSlotCount(Display display) override;
-
-    Error validateDisplay(Display display, uint32_t* outNumTypes,
-                          uint32_t* outNumRequests) override;
-
-    Error presentOrValidateDisplay(Display display, uint32_t* outNumTypes, uint32_t* outNumRequests,
-                                   int* outPresentFence, uint32_t* state) override;
-
-    Error setCursorPosition(Display display, Layer layer, int32_t x, int32_t y) override;
-    /* see setClientTarget for the purpose of slot */
-    Error setLayerBuffer(Display display, Layer layer, uint32_t slot,
-                         const sp<GraphicBuffer>& buffer, int acquireFence) override;
-    Error setLayerSurfaceDamage(Display display, Layer layer,
-                                const std::vector<IComposerClient::Rect>& damage) override;
-    Error setLayerBlendMode(Display display, Layer layer, IComposerClient::BlendMode mode) override;
-    Error setLayerColor(Display display, Layer layer, const IComposerClient::Color& color) override;
-    Error setLayerCompositionType(Display display, Layer layer,
-                                  IComposerClient::Composition type) override;
-    Error setLayerDataspace(Display display, Layer layer, Dataspace dataspace) override;
-    Error setLayerDisplayFrame(Display display, Layer layer,
-                               const IComposerClient::Rect& frame) override;
-    Error setLayerPlaneAlpha(Display display, Layer layer, float alpha) override;
-    Error setLayerSidebandStream(Display display, Layer layer,
-                                 const native_handle_t* stream) override;
-    Error setLayerSourceCrop(Display display, Layer layer,
-                             const IComposerClient::FRect& crop) override;
-    Error setLayerTransform(Display display, Layer layer, Transform transform) override;
-    Error setLayerVisibleRegion(Display display, Layer layer,
-                                const std::vector<IComposerClient::Rect>& visible) override;
-    Error setLayerZOrder(Display display, Layer layer, uint32_t z) override;
-
-    // Composer HAL 2.2
-    Error setLayerPerFrameMetadata(
-            Display display, Layer layer,
-            const std::vector<IComposerClient::PerFrameMetadata>& perFrameMetadatas) override;
-    std::vector<IComposerClient::PerFrameMetadataKey> getPerFrameMetadataKeys(
-            Display display) override;
-    Error getRenderIntents(Display display, ColorMode colorMode,
-            std::vector<RenderIntent>* outRenderIntents) override;
-    Error getDataspaceSaturationMatrix(Dataspace dataspace, mat4* outMatrix) override;
-
-    // Composer HAL 2.3
-    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* outFormat,
-                                                Dataspace* outDataspace,
-                                                uint8_t* outComponentMask) override;
-    Error setDisplayContentSamplingEnabled(Display display, bool enabled, uint8_t componentMask,
-                                           uint64_t maxFrames) override;
-    Error getDisplayedContentSample(Display display, uint64_t maxFrames, uint64_t timestamp,
-                                    DisplayedFrameStats* outStats) override;
-    Error setLayerPerFrameMetadataBlobs(
-            Display display, Layer layer,
-            const std::vector<IComposerClient::PerFrameMetadataBlob>& metadata) override;
-    Error setDisplayBrightness(Display display, float brightness) override;
-
-    // Composer HAL 2.4
-    bool isVsyncPeriodSwitchSupported() override { return mClient_2_4 != nullptr; }
-    Error getDisplayCapabilities(Display display,
-                                 std::vector<DisplayCapability>* outCapabilities) override;
-    V2_4::Error getDisplayConnectionType(Display display,
-                                         IComposerClient::DisplayConnectionType* outType) override;
-    V2_4::Error getDisplayVsyncPeriod(Display display, VsyncPeriodNanos* outVsyncPeriod) override;
-    V2_4::Error setActiveConfigWithConstraints(
-            Display display, Config config,
-            const IComposerClient::VsyncPeriodChangeConstraints& vsyncPeriodChangeConstraints,
-            VsyncPeriodChangeTimeline* outTimeline) override;
-    V2_4::Error setAutoLowLatencyMode(Display displayId, bool on) override;
-    V2_4::Error getSupportedContentTypes(
-            Display displayId,
-            std::vector<IComposerClient::ContentType>* outSupportedContentTypes) override;
-    V2_4::Error setContentType(Display displayId,
-                               IComposerClient::ContentType contentType) override;
-    V2_4::Error setLayerGenericMetadata(Display display, Layer layer, const std::string& key,
-                                        bool mandatory, const std::vector<uint8_t>& value) override;
-    V2_4::Error getLayerGenericMetadataKeys(
-            std::vector<IComposerClient::LayerGenericMetadataKey>* outKeys) override;
-    Error getClientTargetProperty(
+    // AIDL Composer
+    virtual Error setLayerBrightness(Display display, Layer layer, float brightness) = 0;
+    virtual Error setLayerBlockingRegion(Display display, Layer layer,
+                                         const std::vector<IComposerClient::Rect>& blocking) = 0;
+    virtual Error setBootDisplayConfig(Display displayId, Config) = 0;
+    virtual Error clearBootDisplayConfig(Display displayId) = 0;
+    virtual Error getPreferredBootDisplayConfig(Display displayId, Config*) = 0;
+    virtual Error getDisplayDecorationSupport(
             Display display,
-            IComposerClient::ClientTargetProperty* outClientTargetProperty) override;
-
-private:
-    class CommandWriter : public CommandWriterBase {
-    public:
-        explicit CommandWriter(uint32_t initialMaxSize) : CommandWriterBase(initialMaxSize) {}
-        ~CommandWriter() override {}
-    };
-
-    // 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();
-
-    sp<V2_1::IComposer> mComposer;
-
-    sp<V2_1::IComposerClient> mClient;
-    sp<V2_2::IComposerClient> mClient_2_2;
-    sp<V2_3::IComposerClient> mClient_2_3;
-    sp<IComposerClient> mClient_2_4;
-
-    // 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
-    // We obtain this number by:
-    // 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;
-    CommandWriter mWriter;
-    CommandReader mReader;
+            std::optional<::aidl::android::hardware::graphics::common::DisplayDecorationSupport>*
+                    support) = 0;
+    virtual Error setIdleTimerEnabled(Display displayId, std::chrono::milliseconds timeout) = 0;
+    virtual Error getPhysicalDisplayOrientation(Display displayId,
+                                                AidlTransform* outDisplayOrientation) = 0;
 };
 
-} // namespace impl
-
 } // namespace Hwc2
-
 } // namespace android
-
-#endif // ANDROID_SF_COMPOSER_HAL_H
diff --git a/services/surfaceflinger/DisplayHardware/DisplayMode.h b/services/surfaceflinger/DisplayHardware/DisplayMode.h
index 5de622b..61a9a08 100644
--- a/services/surfaceflinger/DisplayHardware/DisplayMode.h
+++ b/services/surfaceflinger/DisplayHardware/DisplayMode.h
@@ -16,20 +16,21 @@
 
 #pragma once
 
-#include "DisplayHardware/Hal.h"
-#include "Fps.h"
-#include "Scheduler/StrongTyping.h"
+#include <cstddef>
+#include <memory>
 
 #include <android-base/stringprintf.h>
 #include <android/configuration.h>
+#include <ftl/small_map.h>
 #include <ui/DisplayId.h>
 #include <ui/DisplayMode.h>
 #include <ui/Size.h>
 #include <utils/Timers.h>
 
-#include <cstddef>
-#include <memory>
-#include <vector>
+#include <scheduler/Fps.h>
+
+#include "DisplayHardware/Hal.h"
+#include "Scheduler/StrongTyping.h"
 
 namespace android {
 
@@ -37,8 +38,17 @@
 
 class DisplayMode;
 using DisplayModePtr = std::shared_ptr<const DisplayMode>;
-using DisplayModes = std::vector<DisplayModePtr>;
-using DisplayModeId = StrongTyping<ui::DisplayModeId, struct DisplayModeIdTag, Compare, Hash>;
+
+// Prevent confusion with fps_approx_ops on the underlying Fps.
+bool operator<(const DisplayModePtr&, const DisplayModePtr&) = delete;
+bool operator>(const DisplayModePtr&, const DisplayModePtr&) = delete;
+bool operator<=(const DisplayModePtr&, const DisplayModePtr&) = delete;
+bool operator>=(const DisplayModePtr&, const DisplayModePtr&) = delete;
+
+using DisplayModeId = StrongTyping<ui::DisplayModeId, struct DisplayModeIdTag, Compare>;
+
+using DisplayModes = ftl::SmallMap<DisplayModeId, DisplayModePtr, 3>;
+using DisplayModeIterator = DisplayModes::const_iterator;
 
 class DisplayMode {
 public:
@@ -60,35 +70,30 @@
             return *this;
         }
 
-        Builder& setWidth(int32_t width) {
-            mDisplayMode->mWidth = width;
+        Builder& setResolution(ui::Size resolution) {
+            mDisplayMode->mResolution = resolution;
             return *this;
         }
 
-        Builder& setHeight(int32_t height) {
-            mDisplayMode->mHeight = height;
-            return *this;
-        }
-
-        Builder& setVsyncPeriod(int32_t vsyncPeriod) {
+        Builder& setVsyncPeriod(nsecs_t vsyncPeriod) {
             mDisplayMode->mFps = Fps::fromPeriodNsecs(vsyncPeriod);
             return *this;
         }
 
         Builder& setDpiX(int32_t dpiX) {
             if (dpiX == -1) {
-                mDisplayMode->mDpiX = getDefaultDensity();
+                mDisplayMode->mDpi.x = getDefaultDensity();
             } else {
-                mDisplayMode->mDpiX = dpiX / 1000.0f;
+                mDisplayMode->mDpi.x = dpiX / 1000.f;
             }
             return *this;
         }
 
         Builder& setDpiY(int32_t dpiY) {
             if (dpiY == -1) {
-                mDisplayMode->mDpiY = getDefaultDensity();
+                mDisplayMode->mDpi.y = getDefaultDensity();
             } else {
-                mDisplayMode->mDpiY = dpiY / 1000.0f;
+                mDisplayMode->mDpi.y = dpiY / 1000.f;
             }
             return *this;
         }
@@ -106,59 +111,76 @@
             // information to begin with. This is also used for virtual displays and
             // older HWC implementations, so be careful about orientation.
 
-            auto longDimension = std::max(mDisplayMode->mWidth, mDisplayMode->mHeight);
-            if (longDimension >= 1080) {
+            if (std::max(mDisplayMode->getWidth(), mDisplayMode->getHeight()) >= 1080) {
                 return ACONFIGURATION_DENSITY_XHIGH;
             } else {
                 return ACONFIGURATION_DENSITY_TV;
             }
         }
+
         std::shared_ptr<DisplayMode> mDisplayMode;
     };
 
     DisplayModeId getId() const { return mId; }
+
     hal::HWConfigId getHwcId() const { return mHwcId; }
     PhysicalDisplayId getPhysicalDisplayId() const { return mPhysicalDisplayId; }
 
-    int32_t getWidth() const { return mWidth; }
-    int32_t getHeight() const { return mHeight; }
-    ui::Size getSize() const { return {mWidth, mHeight}; }
+    ui::Size getResolution() const { return mResolution; }
+    int32_t getWidth() const { return mResolution.getWidth(); }
+    int32_t getHeight() const { return mResolution.getHeight(); }
+
     Fps getFps() const { return mFps; }
     nsecs_t getVsyncPeriod() const { return mFps.getPeriodNsecs(); }
-    float getDpiX() const { return mDpiX; }
-    float getDpiY() const { return mDpiY; }
+
+    struct Dpi {
+        float x = -1;
+        float y = -1;
+
+        bool operator==(Dpi other) const { return x == other.x && y == other.y; }
+    };
+
+    Dpi getDpi() const { return mDpi; }
 
     // Switches between modes in the same group are seamless, i.e.
     // without visual interruptions such as a black screen.
     int32_t getGroup() const { return mGroup; }
 
-    bool equalsExceptDisplayModeId(const DisplayModePtr& other) const {
-        return mHwcId == other->mHwcId && mWidth == other->mWidth && mHeight == other->mHeight &&
-                getVsyncPeriod() == other->getVsyncPeriod() && mDpiX == other->mDpiX &&
-                mDpiY == other->mDpiY && mGroup == other->mGroup;
-    }
-
 private:
     explicit DisplayMode(hal::HWConfigId id) : mHwcId(id) {}
 
-    hal::HWConfigId mHwcId;
+    const hal::HWConfigId mHwcId;
     DisplayModeId mId;
+
     PhysicalDisplayId mPhysicalDisplayId;
 
-    int32_t mWidth = -1;
-    int32_t mHeight = -1;
+    ui::Size mResolution;
     Fps mFps;
-    float mDpiX = -1;
-    float mDpiY = -1;
+    Dpi mDpi;
     int32_t mGroup = -1;
 };
 
-inline std::string to_string(const DisplayMode& mode) {
-    return base::StringPrintf("{id=%d, hwcId=%d, width=%d, height=%d, refreshRate=%s, "
-                              "dpiX=%.2f, dpiY=%.2f, group=%d}",
-                              mode.getId().value(), mode.getHwcId(), mode.getWidth(),
-                              mode.getHeight(), to_string(mode.getFps()).c_str(), mode.getDpiX(),
-                              mode.getDpiY(), mode.getGroup());
+inline bool equalsExceptDisplayModeId(const DisplayMode& lhs, const DisplayMode& rhs) {
+    return lhs.getHwcId() == rhs.getHwcId() && lhs.getResolution() == rhs.getResolution() &&
+            lhs.getVsyncPeriod() == rhs.getVsyncPeriod() && lhs.getDpi() == rhs.getDpi() &&
+            lhs.getGroup() == rhs.getGroup();
 }
 
-} // namespace android
\ No newline at end of file
+inline std::string to_string(const DisplayMode& mode) {
+    return base::StringPrintf("{id=%d, hwcId=%d, resolution=%dx%d, refreshRate=%s, "
+                              "dpi=%.2fx%.2f, group=%d}",
+                              mode.getId().value(), mode.getHwcId(), mode.getWidth(),
+                              mode.getHeight(), to_string(mode.getFps()).c_str(), mode.getDpi().x,
+                              mode.getDpi().y, mode.getGroup());
+}
+
+template <typename... DisplayModePtrs>
+inline DisplayModes makeModes(const DisplayModePtrs&... modePtrs) {
+    DisplayModes modes;
+    // Note: The omission of std::move(modePtrs) is intentional, because order of evaluation for
+    // arguments is unspecified.
+    (modes.try_emplace(modePtrs->getId(), modePtrs), ...);
+    return modes;
+}
+
+} // namespace android
diff --git a/services/surfaceflinger/DisplayHardware/FramebufferSurface.h b/services/surfaceflinger/DisplayHardware/FramebufferSurface.h
index 3123351..d41a856 100644
--- a/services/surfaceflinger/DisplayHardware/FramebufferSurface.h
+++ b/services/surfaceflinger/DisplayHardware/FramebufferSurface.h
@@ -26,7 +26,7 @@
 #include <ui/DisplayId.h>
 #include <ui/Size.h>
 
-#include "DisplayIdentification.h"
+#include <ui/DisplayIdentification.h>
 
 // ---------------------------------------------------------------------------
 namespace android {
diff --git a/services/surfaceflinger/DisplayHardware/HWC2.cpp b/services/surfaceflinger/DisplayHardware/HWC2.cpp
index 27146ab..c0432bf 100644
--- a/services/surfaceflinger/DisplayHardware/HWC2.cpp
+++ b/services/surfaceflinger/DisplayHardware/HWC2.cpp
@@ -37,7 +37,10 @@
 #include <iterator>
 #include <set>
 
-#include "ComposerHal.h"
+using aidl::android::hardware::graphics::composer3::Color;
+using aidl::android::hardware::graphics::composer3::Composition;
+using AidlCapability = aidl::android::hardware::graphics::composer3::Capability;
+using aidl::android::hardware::graphics::composer3::DisplayCapability;
 
 namespace android {
 
@@ -71,7 +74,7 @@
 namespace impl {
 
 Display::Display(android::Hwc2::Composer& composer,
-                 const std::unordered_set<Capability>& capabilities, HWDisplayId id,
+                 const std::unordered_set<AidlCapability>& capabilities, HWDisplayId id,
                  DisplayType type)
       : mComposer(composer), mCapabilities(capabilities), mId(id), mType(type) {
     ALOGV("Created display %" PRIu64, id);
@@ -142,12 +145,23 @@
 bool Display::isVsyncPeriodSwitchSupported() const {
     ALOGV("[%" PRIu64 "] isVsyncPeriodSwitchSupported()", mId);
 
-    return mComposer.isVsyncPeriodSwitchSupported();
+    return mComposer.isSupported(android::Hwc2::Composer::OptionalFeature::RefreshRateSwitching);
+}
+
+bool Display::hasDisplayIdleTimerCapability() const {
+    bool isCapabilitySupported = false;
+    return mComposer.hasDisplayIdleTimerCapability(mId, &isCapabilitySupported) == Error::NONE &&
+            isCapabilitySupported;
+}
+
+Error Display::getPhysicalDisplayOrientation(Hwc2::AidlTransform* outTransform) const {
+    auto error = mComposer.getPhysicalDisplayOrientation(mId, outTransform);
+    return static_cast<Error>(error);
 }
 
 Error Display::getChangedCompositionTypes(std::unordered_map<HWC2::Layer*, Composition>* outTypes) {
     std::vector<Hwc2::Layer> layerIds;
-    std::vector<Hwc2::IComposerClient::Composition> types;
+    std::vector<Composition> types;
     auto intError = mComposer.getChangedCompositionTypes(
             mId, &layerIds, &types);
     uint32_t numElements = layerIds.size();
@@ -283,8 +297,21 @@
     return Error::NONE;
 }
 
+bool Display::hasCapability(DisplayCapability capability) const {
+    std::scoped_lock lock(mDisplayCapabilitiesMutex);
+    if (mDisplayCapabilities) {
+        return mDisplayCapabilities->count(capability) > 0;
+    }
+
+    ALOGW("Can't query capability %s."
+          " Display Capabilities were not queried from HWC yet",
+          to_string(capability).c_str());
+
+    return false;
+}
+
 Error Display::supportsDoze(bool* outSupport) const {
-    *outSupport = mDisplayCapabilities.count(DisplayCapability::DOZE) > 0;
+    *outSupport = hasCapability(DisplayCapability::DOZE);
     return Error::NONE;
 }
 
@@ -421,8 +448,8 @@
     return static_cast<Error>(intError);
 }
 
-Error Display::setColorTransform(const android::mat4& matrix, ColorTransform hint) {
-    auto intError = mComposer.setColorTransform(mId, matrix.asArray(), hint);
+Error Display::setColorTransform(const android::mat4& matrix) {
+    auto intError = mComposer.setColorTransform(mId, matrix.asArray());
     return static_cast<Error>(intError);
 }
 
@@ -443,21 +470,25 @@
 
     if (mode == PowerMode::ON) {
         std::call_once(mDisplayCapabilityQueryFlag, [this]() {
-            std::vector<Hwc2::DisplayCapability> tmpCapabilities;
+            std::vector<DisplayCapability> tmpCapabilities;
             auto error =
                     static_cast<Error>(mComposer.getDisplayCapabilities(mId, &tmpCapabilities));
             if (error == Error::NONE) {
+                std::scoped_lock lock(mDisplayCapabilitiesMutex);
+                mDisplayCapabilities.emplace();
                 for (auto capability : tmpCapabilities) {
-                    mDisplayCapabilities.emplace(static_cast<DisplayCapability>(capability));
+                    mDisplayCapabilities->emplace(capability);
                 }
             } else if (error == Error::UNSUPPORTED) {
-                if (mCapabilities.count(Capability::SKIP_CLIENT_COLOR_TRANSFORM)) {
-                    mDisplayCapabilities.emplace(DisplayCapability::SKIP_CLIENT_COLOR_TRANSFORM);
+                std::scoped_lock lock(mDisplayCapabilitiesMutex);
+                mDisplayCapabilities.emplace();
+                if (mCapabilities.count(AidlCapability::SKIP_CLIENT_COLOR_TRANSFORM)) {
+                    mDisplayCapabilities->emplace(DisplayCapability::SKIP_CLIENT_COLOR_TRANSFORM);
                 }
                 bool dozeSupport = false;
                 error = static_cast<Error>(mComposer.getDozeSupport(mId, &dozeSupport));
                 if (error == Error::NONE && dozeSupport) {
-                    mDisplayCapabilities.emplace(DisplayCapability::DOZE);
+                    mDisplayCapabilities->emplace(DisplayCapability::DOZE);
                 }
             }
         });
@@ -473,11 +504,11 @@
     return static_cast<Error>(intError);
 }
 
-Error Display::validate(uint32_t* outNumTypes, uint32_t* outNumRequests)
-{
+Error Display::validate(nsecs_t expectedPresentTime, uint32_t* outNumTypes,
+                        uint32_t* outNumRequests) {
     uint32_t numTypes = 0;
     uint32_t numRequests = 0;
-    auto intError = mComposer.validateDisplay(mId, &numTypes, &numRequests);
+    auto intError = mComposer.validateDisplay(mId, expectedPresentTime, &numTypes, &numRequests);
     auto error = static_cast<Error>(intError);
     if (error != Error::NONE && !hasChangesError(error)) {
         return error;
@@ -488,14 +519,14 @@
     return error;
 }
 
-Error Display::presentOrValidate(uint32_t* outNumTypes, uint32_t* outNumRequests,
-                                 sp<android::Fence>* outPresentFence, uint32_t* state) {
-
+Error Display::presentOrValidate(nsecs_t expectedPresentTime, uint32_t* outNumTypes,
+                                 uint32_t* outNumRequests, sp<android::Fence>* outPresentFence,
+                                 uint32_t* state) {
     uint32_t numTypes = 0;
     uint32_t numRequests = 0;
     int32_t presentFenceFd = -1;
-    auto intError = mComposer.presentOrValidateDisplay(
-            mId, &numTypes, &numRequests, &presentFenceFd, state);
+    auto intError = mComposer.presentOrValidateDisplay(mId, expectedPresentTime, &numTypes,
+                                                       &numRequests, &presentFenceFd, state);
     auto error = static_cast<Error>(intError);
     if (error != Error::NONE && !hasChangesError(error)) {
         return error;
@@ -512,13 +543,29 @@
     return error;
 }
 
-std::future<Error> Display::setDisplayBrightness(float brightness) {
-    return ftl::defer([composer = &mComposer, id = mId, brightness] {
-        const auto intError = composer->setDisplayBrightness(id, brightness);
+std::future<Error> Display::setDisplayBrightness(
+        float brightness, const Hwc2::Composer::DisplayBrightnessOptions& options) {
+    return ftl::defer([composer = &mComposer, id = mId, brightness, options] {
+        const auto intError = composer->setDisplayBrightness(id, brightness, options);
         return static_cast<Error>(intError);
     });
 }
 
+Error Display::setBootDisplayConfig(hal::HWConfigId configId) {
+    auto intError = mComposer.setBootDisplayConfig(mId, configId);
+    return static_cast<Error>(intError);
+}
+
+Error Display::clearBootDisplayConfig() {
+    auto intError = mComposer.clearBootDisplayConfig(mId);
+    return static_cast<Error>(intError);
+}
+
+Error Display::getPreferredBootDisplayConfig(hal::HWConfigId* configId) const {
+    auto intError = mComposer.getPreferredBootDisplayConfig(mId, configId);
+    return static_cast<Error>(intError);
+}
+
 Error Display::setAutoLowLatencyMode(bool on) {
     auto intError = mComposer.setAutoLowLatencyMode(mId, on);
     return static_cast<Error>(intError);
@@ -538,8 +585,22 @@
     return static_cast<Error>(intError);
 }
 
-Error Display::getClientTargetProperty(ClientTargetProperty* outClientTargetProperty) {
-    const auto error = mComposer.getClientTargetProperty(mId, outClientTargetProperty);
+Error Display::getClientTargetProperty(ClientTargetProperty* outClientTargetProperty,
+                                       float* outWhitePointNits) {
+    const auto error =
+            mComposer.getClientTargetProperty(mId, outClientTargetProperty, outWhitePointNits);
+    return static_cast<Error>(error);
+}
+
+Error Display::getDisplayDecorationSupport(
+        std::optional<aidl::android::hardware::graphics::common::DisplayDecorationSupport>*
+                support) {
+    const auto error = mComposer.getDisplayDecorationSupport(mId, support);
+    return static_cast<Error>(error);
+}
+
+Error Display::setIdleTimerEnabled(std::chrono::milliseconds timeout) {
+    const auto error = mComposer.setIdleTimerEnabled(mId, timeout);
     return static_cast<Error>(error);
 }
 
@@ -562,12 +623,28 @@
 
 // Layer methods
 
+namespace {
+std::vector<Hwc2::IComposerClient::Rect> convertRegionToHwcRects(const Region& region) {
+    size_t rectCount = 0;
+    Rect const* rectArray = region.getArray(&rectCount);
+
+    std::vector<Hwc2::IComposerClient::Rect> hwcRects;
+    hwcRects.reserve(rectCount);
+    for (size_t rect = 0; rect < rectCount; ++rect) {
+        hwcRects.push_back({rectArray[rect].left, rectArray[rect].top, rectArray[rect].right,
+                            rectArray[rect].bottom});
+    }
+    return hwcRects;
+}
+} // namespace
+
 Layer::~Layer() = default;
 
 namespace impl {
 
-Layer::Layer(android::Hwc2::Composer& composer, const std::unordered_set<Capability>& capabilities,
-             HWC2::Display& display, HWLayerId layerId)
+Layer::Layer(android::Hwc2::Composer& composer,
+             const std::unordered_set<AidlCapability>& capabilities, HWC2::Display& display,
+             HWLayerId layerId)
       : mComposer(composer),
         mCapabilities(capabilities),
         mDisplay(&display),
@@ -652,15 +729,7 @@
         intError = mComposer.setLayerSurfaceDamage(mDisplay->getId(), mId,
                                                    std::vector<Hwc2::IComposerClient::Rect>());
     } else {
-        size_t rectCount = 0;
-        auto rectArray = damage.getArray(&rectCount);
-
-        std::vector<Hwc2::IComposerClient::Rect> hwcRects;
-        for (size_t rect = 0; rect < rectCount; ++rect) {
-            hwcRects.push_back({rectArray[rect].left, rectArray[rect].top,
-                    rectArray[rect].right, rectArray[rect].bottom});
-        }
-
+        const auto hwcRects = convertRegionToHwcRects(damage);
         intError = mComposer.setLayerSurfaceDamage(mDisplay->getId(), mId, hwcRects);
     }
 
@@ -805,7 +874,7 @@
         return Error::BAD_DISPLAY;
     }
 
-    if (mCapabilities.count(Capability::SIDEBAND_STREAM) == 0) {
+    if (mCapabilities.count(AidlCapability::SIDEBAND_STREAM) == 0) {
         ALOGE("Attempted to call setSidebandStream without checking that the "
                 "device supports sideband streams");
         return Error::UNSUPPORTED;
@@ -848,16 +917,7 @@
         return Error::NONE;
     }
     mVisibleRegion = region;
-
-    size_t rectCount = 0;
-    auto rectArray = region.getArray(&rectCount);
-
-    std::vector<Hwc2::IComposerClient::Rect> hwcRects;
-    for (size_t rect = 0; rect < rectCount; ++rect) {
-        hwcRects.push_back({rectArray[rect].left, rectArray[rect].top,
-                rectArray[rect].right, rectArray[rect].bottom});
-    }
-
+    const auto hwcRects = convertRegionToHwcRects(region);
     auto intError = mComposer.setLayerVisibleRegion(mDisplay->getId(), mId, hwcRects);
     return static_cast<Error>(intError);
 }
@@ -902,6 +962,31 @@
     return static_cast<Error>(intError);
 }
 
+// AIDL HAL
+Error Layer::setBrightness(float brightness) {
+    if (CC_UNLIKELY(!mDisplay)) {
+        return Error::BAD_DISPLAY;
+    }
+
+    auto intError = mComposer.setLayerBrightness(mDisplay->getId(), mId, brightness);
+    return static_cast<Error>(intError);
+}
+
+Error Layer::setBlockingRegion(const Region& region) {
+    if (CC_UNLIKELY(!mDisplay)) {
+        return Error::BAD_DISPLAY;
+    }
+
+    if (region.isRect() && mBlockingRegion.isRect() &&
+        (region.getBounds() == mBlockingRegion.getBounds())) {
+        return Error::NONE;
+    }
+    mBlockingRegion = region;
+    const auto hwcRects = convertRegionToHwcRects(region);
+    const auto intError = mComposer.setLayerBlockingRegion(mDisplay->getId(), mId, hwcRects);
+    return static_cast<Error>(intError);
+}
+
 } // namespace impl
 } // namespace HWC2
 } // namespace android
diff --git a/services/surfaceflinger/DisplayHardware/HWC2.h b/services/surfaceflinger/DisplayHardware/HWC2.h
index 871465d..a805566 100644
--- a/services/surfaceflinger/DisplayHardware/HWC2.h
+++ b/services/surfaceflinger/DisplayHardware/HWC2.h
@@ -17,6 +17,7 @@
 #pragma once
 
 #include <android-base/expected.h>
+#include <android-base/thread_annotations.h>
 #include <gui/HdrMetadata.h>
 #include <math/mat4.h>
 #include <ui/HdrCapabilities.h>
@@ -33,8 +34,15 @@
 #include <unordered_set>
 #include <vector>
 
+#include "ComposerHal.h"
 #include "Hal.h"
 
+#include <aidl/android/hardware/graphics/common/DisplayDecorationSupport.h>
+#include <aidl/android/hardware/graphics/composer3/Capability.h>
+#include <aidl/android/hardware/graphics/composer3/Color.h>
+#include <aidl/android/hardware/graphics/composer3/Composition.h>
+#include <aidl/android/hardware/graphics/composer3/DisplayCapability.h>
+
 namespace android {
 
 class Fence;
@@ -66,6 +74,7 @@
     virtual void onComposerHalVsyncPeriodTimingChanged(hal::HWDisplayId,
                                                        const hal::VsyncPeriodChangeTimeline&) = 0;
     virtual void onComposerHalSeamlessPossible(hal::HWDisplayId) = 0;
+    virtual void onComposerHalVsyncIdle(hal::HWDisplayId) = 0;
 
 protected:
     ~ComposerCallback() = default;
@@ -79,73 +88,85 @@
     virtual hal::HWDisplayId getId() const = 0;
     virtual bool isConnected() const = 0;
     virtual void setConnected(bool connected) = 0; // For use by Device only
-    virtual const std::unordered_set<hal::DisplayCapability>& getCapabilities() const = 0;
+    virtual bool hasCapability(
+            aidl::android::hardware::graphics::composer3::DisplayCapability) const = 0;
     virtual bool isVsyncPeriodSwitchSupported() const = 0;
+    virtual bool hasDisplayIdleTimerCapability() const = 0;
     virtual void onLayerDestroyed(hal::HWLayerId layerId) = 0;
 
-    [[clang::warn_unused_result]] virtual hal::Error acceptChanges() = 0;
-    [[clang::warn_unused_result]] virtual base::expected<std::shared_ptr<HWC2::Layer>, hal::Error>
+    [[nodiscard]] virtual hal::Error acceptChanges() = 0;
+    [[nodiscard]] virtual base::expected<std::shared_ptr<HWC2::Layer>, hal::Error>
     createLayer() = 0;
-    [[clang::warn_unused_result]] virtual hal::Error getChangedCompositionTypes(
-            std::unordered_map<Layer*, hal::Composition>* outTypes) = 0;
-    [[clang::warn_unused_result]] virtual hal::Error getColorModes(
-            std::vector<hal::ColorMode>* outModes) const = 0;
+    [[nodiscard]] virtual hal::Error getChangedCompositionTypes(
+            std::unordered_map<Layer*, aidl::android::hardware::graphics::composer3::Composition>*
+                    outTypes) = 0;
+    [[nodiscard]] virtual hal::Error getColorModes(std::vector<hal::ColorMode>* outModes) const = 0;
     // Returns a bitmask which contains HdrMetadata::Type::*.
-    [[clang::warn_unused_result]] virtual int32_t getSupportedPerFrameMetadata() const = 0;
-    [[clang::warn_unused_result]] virtual hal::Error getRenderIntents(
+    [[nodiscard]] virtual int32_t getSupportedPerFrameMetadata() const = 0;
+    [[nodiscard]] virtual hal::Error getRenderIntents(
             hal::ColorMode colorMode, std::vector<hal::RenderIntent>* outRenderIntents) const = 0;
-    [[clang::warn_unused_result]] virtual hal::Error getDataspaceSaturationMatrix(
-            hal::Dataspace dataspace, android::mat4* outMatrix) = 0;
+    [[nodiscard]] virtual hal::Error getDataspaceSaturationMatrix(hal::Dataspace dataspace,
+                                                                  android::mat4* outMatrix) = 0;
 
-    [[clang::warn_unused_result]] virtual hal::Error getName(std::string* outName) const = 0;
-    [[clang::warn_unused_result]] virtual hal::Error getRequests(
+    [[nodiscard]] virtual hal::Error getName(std::string* outName) const = 0;
+    [[nodiscard]] virtual hal::Error getRequests(
             hal::DisplayRequest* outDisplayRequests,
             std::unordered_map<Layer*, hal::LayerRequest>* outLayerRequests) = 0;
-    [[clang::warn_unused_result]] virtual hal::Error getConnectionType(
-            ui::DisplayConnectionType*) const = 0;
-    [[clang::warn_unused_result]] virtual hal::Error supportsDoze(bool* outSupport) const = 0;
-    [[clang::warn_unused_result]] virtual hal::Error getHdrCapabilities(
+    [[nodiscard]] virtual hal::Error getConnectionType(ui::DisplayConnectionType*) const = 0;
+    [[nodiscard]] virtual hal::Error supportsDoze(bool* outSupport) const = 0;
+    [[nodiscard]] virtual hal::Error getHdrCapabilities(
             android::HdrCapabilities* outCapabilities) const = 0;
-    [[clang::warn_unused_result]] virtual hal::Error getDisplayedContentSamplingAttributes(
+    [[nodiscard]] virtual hal::Error getDisplayedContentSamplingAttributes(
             hal::PixelFormat* outFormat, hal::Dataspace* outDataspace,
             uint8_t* outComponentMask) const = 0;
-    [[clang::warn_unused_result]] virtual hal::Error setDisplayContentSamplingEnabled(
-            bool enabled, uint8_t componentMask, uint64_t maxFrames) const = 0;
-    [[clang::warn_unused_result]] virtual hal::Error getDisplayedContentSample(
+    [[nodiscard]] virtual hal::Error setDisplayContentSamplingEnabled(bool enabled,
+                                                                      uint8_t componentMask,
+                                                                      uint64_t maxFrames) const = 0;
+    [[nodiscard]] virtual hal::Error getDisplayedContentSample(
             uint64_t maxFrames, uint64_t timestamp,
             android::DisplayedFrameStats* outStats) const = 0;
-    [[clang::warn_unused_result]] virtual hal::Error getReleaseFences(
+    [[nodiscard]] virtual hal::Error getReleaseFences(
             std::unordered_map<Layer*, android::sp<android::Fence>>* outFences) const = 0;
-    [[clang::warn_unused_result]] virtual hal::Error present(
-            android::sp<android::Fence>* outPresentFence) = 0;
-    [[clang::warn_unused_result]] virtual hal::Error setClientTarget(
+    [[nodiscard]] virtual hal::Error present(android::sp<android::Fence>* outPresentFence) = 0;
+    [[nodiscard]] virtual hal::Error setClientTarget(
             uint32_t slot, const android::sp<android::GraphicBuffer>& target,
             const android::sp<android::Fence>& acquireFence, hal::Dataspace dataspace) = 0;
-    [[clang::warn_unused_result]] virtual hal::Error setColorMode(
-            hal::ColorMode mode, hal::RenderIntent renderIntent) = 0;
-    [[clang::warn_unused_result]] virtual hal::Error setColorTransform(
-            const android::mat4& matrix, hal::ColorTransform hint) = 0;
-    [[clang::warn_unused_result]] virtual hal::Error setOutputBuffer(
+    [[nodiscard]] virtual hal::Error setColorMode(hal::ColorMode mode,
+                                                  hal::RenderIntent renderIntent) = 0;
+    [[nodiscard]] virtual hal::Error setColorTransform(const android::mat4& matrix) = 0;
+    [[nodiscard]] virtual hal::Error setOutputBuffer(
             const android::sp<android::GraphicBuffer>& buffer,
             const android::sp<android::Fence>& releaseFence) = 0;
-    [[clang::warn_unused_result]] virtual hal::Error setPowerMode(hal::PowerMode mode) = 0;
-    [[clang::warn_unused_result]] virtual hal::Error setVsyncEnabled(hal::Vsync enabled) = 0;
-    [[clang::warn_unused_result]] virtual hal::Error validate(uint32_t* outNumTypes,
-                                                              uint32_t* outNumRequests) = 0;
-    [[clang::warn_unused_result]] virtual hal::Error presentOrValidate(
-            uint32_t* outNumTypes, uint32_t* outNumRequests,
-            android::sp<android::Fence>* outPresentFence, uint32_t* state) = 0;
-    [[clang::warn_unused_result]] virtual std::future<hal::Error> setDisplayBrightness(
-            float brightness) = 0;
-    [[clang::warn_unused_result]] virtual hal::Error setActiveConfigWithConstraints(
+    [[nodiscard]] virtual hal::Error setPowerMode(hal::PowerMode mode) = 0;
+    [[nodiscard]] virtual hal::Error setVsyncEnabled(hal::Vsync enabled) = 0;
+    [[nodiscard]] virtual hal::Error validate(nsecs_t expectedPresentTime, uint32_t* outNumTypes,
+                                              uint32_t* outNumRequests) = 0;
+    [[nodiscard]] virtual hal::Error presentOrValidate(nsecs_t expectedPresentTime,
+                                                       uint32_t* outNumTypes,
+                                                       uint32_t* outNumRequests,
+                                                       android::sp<android::Fence>* outPresentFence,
+                                                       uint32_t* state) = 0;
+    [[nodiscard]] virtual std::future<hal::Error> setDisplayBrightness(
+            float brightness, const Hwc2::Composer::DisplayBrightnessOptions& options) = 0;
+    [[nodiscard]] virtual hal::Error setActiveConfigWithConstraints(
             hal::HWConfigId configId, const hal::VsyncPeriodChangeConstraints& constraints,
             hal::VsyncPeriodChangeTimeline* outTimeline) = 0;
-    [[clang::warn_unused_result]] virtual hal::Error setAutoLowLatencyMode(bool on) = 0;
-    [[clang::warn_unused_result]] virtual hal::Error getSupportedContentTypes(
+    [[nodiscard]] virtual hal::Error setBootDisplayConfig(hal::HWConfigId configId) = 0;
+    [[nodiscard]] virtual hal::Error clearBootDisplayConfig() = 0;
+    [[nodiscard]] virtual hal::Error getPreferredBootDisplayConfig(
+            hal::HWConfigId* configId) const = 0;
+    [[nodiscard]] virtual hal::Error setAutoLowLatencyMode(bool on) = 0;
+    [[nodiscard]] virtual hal::Error getSupportedContentTypes(
             std::vector<hal::ContentType>*) const = 0;
-    [[clang::warn_unused_result]] virtual hal::Error setContentType(hal::ContentType) = 0;
-    [[clang::warn_unused_result]] virtual hal::Error getClientTargetProperty(
-            hal::ClientTargetProperty* outClientTargetProperty) = 0;
+    [[nodiscard]] virtual hal::Error setContentType(hal::ContentType) = 0;
+    [[nodiscard]] virtual hal::Error getClientTargetProperty(
+            hal::ClientTargetProperty* outClientTargetProperty, float* outWhitePointNits) = 0;
+    [[nodiscard]] virtual hal::Error getDisplayDecorationSupport(
+            std::optional<aidl::android::hardware::graphics::common::DisplayDecorationSupport>*
+                    support) = 0;
+    [[nodiscard]] virtual hal::Error setIdleTimerEnabled(std::chrono::milliseconds timeout) = 0;
+    [[nodiscard]] virtual hal::Error getPhysicalDisplayOrientation(
+            Hwc2::AidlTransform* outTransform) const = 0;
 };
 
 namespace impl {
@@ -154,15 +175,18 @@
 
 class Display : public HWC2::Display {
 public:
-    Display(android::Hwc2::Composer&, const std::unordered_set<hal::Capability>&, hal::HWDisplayId,
-            hal::DisplayType);
+    Display(android::Hwc2::Composer&,
+            const std::unordered_set<aidl::android::hardware::graphics::composer3::Capability>&,
+            hal::HWDisplayId, hal::DisplayType);
     ~Display() override;
 
     // Required by HWC2
     hal::Error acceptChanges() override;
     base::expected<std::shared_ptr<HWC2::Layer>, hal::Error> createLayer() override;
     hal::Error getChangedCompositionTypes(
-            std::unordered_map<HWC2::Layer*, hal::Composition>* outTypes) override;
+            std::unordered_map<HWC2::Layer*,
+                               aidl::android::hardware::graphics::composer3::Composition>* outTypes)
+            override;
     hal::Error getColorModes(std::vector<hal::ColorMode>* outModes) const override;
     // Returns a bitmask which contains HdrMetadata::Type::*.
     int32_t getSupportedPerFrameMetadata() const override;
@@ -175,7 +199,7 @@
             hal::DisplayRequest* outDisplayRequests,
             std::unordered_map<HWC2::Layer*, hal::LayerRequest>* outLayerRequests) override;
     hal::Error getConnectionType(ui::DisplayConnectionType*) const override;
-    hal::Error supportsDoze(bool* outSupport) const override;
+    hal::Error supportsDoze(bool* outSupport) const override EXCLUDES(mDisplayCapabilitiesMutex);
     hal::Error getHdrCapabilities(android::HdrCapabilities* outCapabilities) const override;
     hal::Error getDisplayedContentSamplingAttributes(hal::PixelFormat* outFormat,
                                                      hal::Dataspace* outDataspace,
@@ -191,34 +215,46 @@
                                const android::sp<android::Fence>& acquireFence,
                                hal::Dataspace dataspace) override;
     hal::Error setColorMode(hal::ColorMode, hal::RenderIntent) override;
-    hal::Error setColorTransform(const android::mat4& matrix, hal::ColorTransform hint) override;
+    hal::Error setColorTransform(const android::mat4& matrix) override;
     hal::Error setOutputBuffer(const android::sp<android::GraphicBuffer>&,
                                const android::sp<android::Fence>& releaseFence) override;
     hal::Error setPowerMode(hal::PowerMode) override;
     hal::Error setVsyncEnabled(hal::Vsync enabled) override;
-    hal::Error validate(uint32_t* outNumTypes, uint32_t* outNumRequests) override;
-    hal::Error presentOrValidate(uint32_t* outNumTypes, uint32_t* outNumRequests,
+    hal::Error validate(nsecs_t expectedPresentTime, uint32_t* outNumTypes,
+                        uint32_t* outNumRequests) override;
+    hal::Error presentOrValidate(nsecs_t expectedPresentTime, uint32_t* outNumTypes,
+                                 uint32_t* outNumRequests,
                                  android::sp<android::Fence>* outPresentFence,
                                  uint32_t* state) override;
-    std::future<hal::Error> setDisplayBrightness(float brightness) override;
+    std::future<hal::Error> setDisplayBrightness(
+            float brightness, const Hwc2::Composer::DisplayBrightnessOptions& options) override;
     hal::Error setActiveConfigWithConstraints(hal::HWConfigId configId,
                                               const hal::VsyncPeriodChangeConstraints& constraints,
                                               hal::VsyncPeriodChangeTimeline* outTimeline) override;
+    hal::Error setBootDisplayConfig(hal::HWConfigId configId) override;
+    hal::Error clearBootDisplayConfig() override;
+    hal::Error getPreferredBootDisplayConfig(hal::HWConfigId* configId) const override;
     hal::Error setAutoLowLatencyMode(bool on) override;
     hal::Error getSupportedContentTypes(
             std::vector<hal::ContentType>* outSupportedContentTypes) const override;
     hal::Error setContentType(hal::ContentType) override;
-    hal::Error getClientTargetProperty(hal::ClientTargetProperty* outClientTargetProperty) override;
+    hal::Error getClientTargetProperty(hal::ClientTargetProperty* outClientTargetProperty,
+                                       float* outWhitePointNits) override;
+    hal::Error getDisplayDecorationSupport(
+            std::optional<aidl::android::hardware::graphics::common::DisplayDecorationSupport>*
+                    support) override;
+    hal::Error setIdleTimerEnabled(std::chrono::milliseconds timeout) override;
 
     // Other Display methods
     hal::HWDisplayId getId() const override { return mId; }
     bool isConnected() const override { return mIsConnected; }
     void setConnected(bool connected) override; // For use by Device only
-    const std::unordered_set<hal::DisplayCapability>& getCapabilities() const override {
-        return mDisplayCapabilities;
-    };
+    bool hasCapability(aidl::android::hardware::graphics::composer3::DisplayCapability)
+            const override EXCLUDES(mDisplayCapabilitiesMutex);
     bool isVsyncPeriodSwitchSupported() const override;
+    bool hasDisplayIdleTimerCapability() const override;
     void onLayerDestroyed(hal::HWLayerId layerId) override;
+    hal::Error getPhysicalDisplayOrientation(Hwc2::AidlTransform* outTransform) const override;
 
 private:
 
@@ -234,7 +270,8 @@
     // this HWC2::Display, so these references are guaranteed to be valid for
     // the lifetime of this object.
     android::Hwc2::Composer& mComposer;
-    const std::unordered_set<hal::Capability>& mCapabilities;
+    const std::unordered_set<aidl::android::hardware::graphics::composer3::Capability>&
+            mCapabilities;
 
     const hal::HWDisplayId mId;
     hal::DisplayType mType;
@@ -243,8 +280,11 @@
     using Layers = std::unordered_map<hal::HWLayerId, std::weak_ptr<HWC2::impl::Layer>>;
     Layers mLayers;
 
+    mutable std::mutex mDisplayCapabilitiesMutex;
     std::once_flag mDisplayCapabilityQueryFlag;
-    std::unordered_set<hal::DisplayCapability> mDisplayCapabilities;
+    std::optional<
+            std::unordered_set<aidl::android::hardware::graphics::composer3::DisplayCapability>>
+            mDisplayCapabilities GUARDED_BY(mDisplayCapabilitiesMutex);
 };
 
 } // namespace impl
@@ -255,38 +295,39 @@
 
     virtual hal::HWLayerId getId() const = 0;
 
-    [[clang::warn_unused_result]] virtual hal::Error setCursorPosition(int32_t x, int32_t y) = 0;
-    [[clang::warn_unused_result]] virtual hal::Error setBuffer(
-            uint32_t slot, const android::sp<android::GraphicBuffer>& buffer,
-            const android::sp<android::Fence>& acquireFence) = 0;
-    [[clang::warn_unused_result]] virtual hal::Error setSurfaceDamage(
-            const android::Region& damage) = 0;
+    [[nodiscard]] virtual hal::Error setCursorPosition(int32_t x, int32_t y) = 0;
+    [[nodiscard]] virtual hal::Error setBuffer(uint32_t slot,
+                                               const android::sp<android::GraphicBuffer>& buffer,
+                                               const android::sp<android::Fence>& acquireFence) = 0;
+    [[nodiscard]] virtual hal::Error setSurfaceDamage(const android::Region& damage) = 0;
 
-    [[clang::warn_unused_result]] virtual hal::Error setBlendMode(hal::BlendMode mode) = 0;
-    [[clang::warn_unused_result]] virtual hal::Error setColor(hal::Color color) = 0;
-    [[clang::warn_unused_result]] virtual hal::Error setCompositionType(hal::Composition type) = 0;
-    [[clang::warn_unused_result]] virtual hal::Error setDataspace(hal::Dataspace dataspace) = 0;
-    [[clang::warn_unused_result]] virtual hal::Error setPerFrameMetadata(
-            const int32_t supportedPerFrameMetadata, const android::HdrMetadata& metadata) = 0;
-    [[clang::warn_unused_result]] virtual hal::Error setDisplayFrame(
-            const android::Rect& frame) = 0;
-    [[clang::warn_unused_result]] virtual hal::Error setPlaneAlpha(float alpha) = 0;
-    [[clang::warn_unused_result]] virtual hal::Error setSidebandStream(
-            const native_handle_t* stream) = 0;
-    [[clang::warn_unused_result]] virtual hal::Error setSourceCrop(
-            const android::FloatRect& crop) = 0;
-    [[clang::warn_unused_result]] virtual hal::Error setTransform(hal::Transform transform) = 0;
-    [[clang::warn_unused_result]] virtual hal::Error setVisibleRegion(
-            const android::Region& region) = 0;
-    [[clang::warn_unused_result]] virtual hal::Error setZOrder(uint32_t z) = 0;
+    [[nodiscard]] virtual hal::Error setBlendMode(hal::BlendMode mode) = 0;
+    [[nodiscard]] virtual hal::Error setColor(
+            aidl::android::hardware::graphics::composer3::Color color) = 0;
+    [[nodiscard]] virtual hal::Error setCompositionType(
+            aidl::android::hardware::graphics::composer3::Composition type) = 0;
+    [[nodiscard]] virtual hal::Error setDataspace(hal::Dataspace dataspace) = 0;
+    [[nodiscard]] virtual hal::Error setPerFrameMetadata(const int32_t supportedPerFrameMetadata,
+                                                         const android::HdrMetadata& metadata) = 0;
+    [[nodiscard]] virtual hal::Error setDisplayFrame(const android::Rect& frame) = 0;
+    [[nodiscard]] virtual hal::Error setPlaneAlpha(float alpha) = 0;
+    [[nodiscard]] virtual hal::Error setSidebandStream(const native_handle_t* stream) = 0;
+    [[nodiscard]] virtual hal::Error setSourceCrop(const android::FloatRect& crop) = 0;
+    [[nodiscard]] virtual hal::Error setTransform(hal::Transform transform) = 0;
+    [[nodiscard]] virtual hal::Error setVisibleRegion(const android::Region& region) = 0;
+    [[nodiscard]] virtual hal::Error setZOrder(uint32_t z) = 0;
 
     // Composer HAL 2.3
-    [[clang::warn_unused_result]] virtual hal::Error setColorTransform(
-            const android::mat4& matrix) = 0;
+    [[nodiscard]] virtual hal::Error setColorTransform(const android::mat4& matrix) = 0;
 
     // Composer HAL 2.4
-    [[clang::warn_unused_result]] virtual hal::Error setLayerGenericMetadata(
-            const std::string& name, bool mandatory, const std::vector<uint8_t>& value) = 0;
+    [[nodiscard]] virtual hal::Error setLayerGenericMetadata(const std::string& name,
+                                                             bool mandatory,
+                                                             const std::vector<uint8_t>& value) = 0;
+
+    // AIDL HAL
+    [[nodiscard]] virtual hal::Error setBrightness(float brightness) = 0;
+    [[nodiscard]] virtual hal::Error setBlockingRegion(const android::Region& region) = 0;
 };
 
 namespace impl {
@@ -296,8 +337,9 @@
 class Layer : public HWC2::Layer {
 public:
     Layer(android::Hwc2::Composer& composer,
-          const std::unordered_set<hal::Capability>& capabilities, HWC2::Display& display,
-          hal::HWLayerId layerId);
+          const std::unordered_set<aidl::android::hardware::graphics::composer3::Capability>&
+                  capabilities,
+          HWC2::Display& display, hal::HWLayerId layerId);
     ~Layer() override;
 
     void onOwningDisplayDestroyed();
@@ -310,8 +352,9 @@
     hal::Error setSurfaceDamage(const android::Region& damage) override;
 
     hal::Error setBlendMode(hal::BlendMode mode) override;
-    hal::Error setColor(hal::Color color) override;
-    hal::Error setCompositionType(hal::Composition type) override;
+    hal::Error setColor(aidl::android::hardware::graphics::composer3::Color color) override;
+    hal::Error setCompositionType(
+            aidl::android::hardware::graphics::composer3::Composition type) override;
     hal::Error setDataspace(hal::Dataspace dataspace) override;
     hal::Error setPerFrameMetadata(const int32_t supportedPerFrameMetadata,
                                    const android::HdrMetadata& metadata) override;
@@ -330,12 +373,17 @@
     hal::Error setLayerGenericMetadata(const std::string& name, bool mandatory,
                                        const std::vector<uint8_t>& value) override;
 
+    // AIDL HAL
+    hal::Error setBrightness(float brightness) override;
+    hal::Error setBlockingRegion(const android::Region& region) override;
+
 private:
     // These are references to data owned by HWC2::Device, which will outlive
     // this HWC2::Layer, so these references are guaranteed to be valid for
     // the lifetime of this object.
     android::Hwc2::Composer& mComposer;
-    const std::unordered_set<hal::Capability>& mCapabilities;
+    const std::unordered_set<aidl::android::hardware::graphics::composer3::Capability>&
+            mCapabilities;
 
     HWC2::Display* mDisplay;
     hal::HWLayerId mId;
@@ -344,6 +392,7 @@
     // multiple times.
     android::Region mVisibleRegion = android::Region::INVALID_REGION;
     android::Region mDamageRegion = android::Region::INVALID_REGION;
+    android::Region mBlockingRegion = android::Region::INVALID_REGION;
     hal::Dataspace mDataSpace = hal::Dataspace::UNKNOWN;
     android::HdrMetadata mHdrMetadata;
     android::mat4 mColorMatrix;
diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.cpp b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
index a790b4c..459291a 100644
--- a/services/surfaceflinger/DisplayHardware/HWComposer.cpp
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
@@ -71,66 +71,11 @@
 #define RETURN_IF_HWC_ERROR(error, displayId, ...) \
     RETURN_IF_HWC_ERROR_FOR(__FUNCTION__, error, displayId, __VA_ARGS__)
 
+using aidl::android::hardware::graphics::composer3::Capability;
+using aidl::android::hardware::graphics::composer3::DisplayCapability;
 namespace hal = android::hardware::graphics::composer::hal;
 
 namespace android {
-namespace {
-
-using android::hardware::Return;
-using android::hardware::Void;
-using android::HWC2::ComposerCallback;
-
-class ComposerCallbackBridge : public hal::IComposerCallback {
-public:
-    ComposerCallbackBridge(ComposerCallback* callback, bool vsyncSwitchingSupported)
-          : mCallback(callback), mVsyncSwitchingSupported(vsyncSwitchingSupported) {}
-
-    Return<void> onHotplug(hal::HWDisplayId display, hal::Connection connection) override {
-        mCallback->onComposerHalHotplug(display, connection);
-        return Void();
-    }
-
-    Return<void> onRefresh(hal::HWDisplayId display) override {
-        mCallback->onComposerHalRefresh(display);
-        return Void();
-    }
-
-    Return<void> onVsync(hal::HWDisplayId display, int64_t timestamp) override {
-        if (!mVsyncSwitchingSupported) {
-            mCallback->onComposerHalVsync(display, timestamp, std::nullopt);
-        } else {
-            ALOGW("Unexpected onVsync callback on composer >= 2.4, ignoring.");
-        }
-        return Void();
-    }
-
-    Return<void> onVsync_2_4(hal::HWDisplayId display, int64_t timestamp,
-                             hal::VsyncPeriodNanos vsyncPeriodNanos) override {
-        if (mVsyncSwitchingSupported) {
-            mCallback->onComposerHalVsync(display, timestamp, vsyncPeriodNanos);
-        } else {
-            ALOGW("Unexpected onVsync_2_4 callback on composer <= 2.3, ignoring.");
-        }
-        return Void();
-    }
-
-    Return<void> onVsyncPeriodTimingChanged(
-            hal::HWDisplayId display, const hal::VsyncPeriodChangeTimeline& timeline) override {
-        mCallback->onComposerHalVsyncPeriodTimingChanged(display, timeline);
-        return Void();
-    }
-
-    Return<void> onSeamlessPossible(hal::HWDisplayId display) override {
-        mCallback->onComposerHalSeamlessPossible(display);
-        return Void();
-    }
-
-private:
-    ComposerCallback* const mCallback;
-    const bool mVsyncSwitchingSupported;
-};
-
-} // namespace
 
 HWComposer::~HWComposer() = default;
 
@@ -143,13 +88,13 @@
                 sysprop::update_device_product_info_on_hotplug_reconnect(false)) {}
 
 HWComposer::HWComposer(const std::string& composerServiceName)
-      : HWComposer(std::make_unique<Hwc2::impl::Composer>(composerServiceName)) {}
+      : HWComposer(Hwc2::Composer::create(composerServiceName)) {}
 
 HWComposer::~HWComposer() {
     mDisplayData.clear();
 }
 
-void HWComposer::setCallback(HWC2::ComposerCallback* callback) {
+void HWComposer::setCallback(HWC2::ComposerCallback& callback) {
     loadCapabilities();
     loadLayerMetadataSupport();
 
@@ -159,8 +104,7 @@
     }
     mRegisteredCallback = true;
 
-    mComposer->registerCallback(
-            sp<ComposerCallbackBridge>::make(callback, mComposer->isVsyncPeriodSwitchSupported()));
+    mComposer->registerCallback(callback);
 }
 
 bool HWComposer::getDisplayIdentificationData(hal::HWDisplayId hwcDisplayId, uint8_t* outPort,
@@ -176,14 +120,13 @@
     return true;
 }
 
-bool HWComposer::hasCapability(hal::Capability capability) const {
+bool HWComposer::hasCapability(Capability capability) const {
     return mCapabilities.count(capability) > 0;
 }
 
-bool HWComposer::hasDisplayCapability(HalDisplayId displayId,
-                                      hal::DisplayCapability capability) const {
+bool HWComposer::hasDisplayCapability(HalDisplayId displayId, DisplayCapability capability) const {
     RETURN_IF_INVALID_DISPLAY(displayId, false);
-    return mDisplayData.at(displayId).hwcDisplay->getCapabilities().count(capability) > 0;
+    return mDisplayData.at(displayId).hwcDisplay->hasCapability(capability);
 }
 
 std::optional<DisplayIdentificationInfo> HWComposer::onHotplug(hal::HWDisplayId hwcDisplayId,
@@ -212,8 +155,6 @@
     RETURN_IF_INVALID_DISPLAY(*displayId, false);
 
     auto& displayData = mDisplayData[*displayId];
-    LOG_FATAL_IF(displayData.isVirtual, "%s: Invalid operation on virtual display with ID %s",
-                 __FUNCTION__, to_string(*displayId).c_str());
 
     {
         // There have been reports of HWCs that signal several vsync events
@@ -271,7 +212,6 @@
     display->setConnected(true);
     auto& displayData = mDisplayData[displayId];
     displayData.hwcDisplay = std::move(display);
-    displayData.isVirtual = true;
     return true;
 }
 
@@ -279,10 +219,8 @@
                                          PhysicalDisplayId displayId) {
     mPhysicalDisplayIdMap[hwcDisplayId] = displayId;
 
-    if (!mInternalHwcDisplayId) {
-        mInternalHwcDisplayId = hwcDisplayId;
-    } else if (mInternalHwcDisplayId != hwcDisplayId && !mExternalHwcDisplayId) {
-        mExternalHwcDisplayId = hwcDisplayId;
+    if (!mPrimaryHwcDisplayId) {
+        mPrimaryHwcDisplayId = hwcDisplayId;
     }
 
     auto& displayData = mDisplayData[displayId];
@@ -372,7 +310,7 @@
     ui::DisplayConnectionType type;
     const auto error = hwcDisplay->getConnectionType(&type);
 
-    const auto FALLBACK_TYPE = hwcDisplay->getId() == mInternalHwcDisplayId
+    const auto FALLBACK_TYPE = hwcDisplay->getId() == mPrimaryHwcDisplayId
             ? ui::DisplayConnectionType::Internal
             : ui::DisplayConnectionType::External;
 
@@ -428,9 +366,6 @@
     RETURN_IF_INVALID_DISPLAY(displayId);
     auto& displayData = mDisplayData[displayId];
 
-    LOG_FATAL_IF(displayData.isVirtual, "%s: Invalid operation on virtual display with ID %s",
-                 __FUNCTION__, to_string(displayId).c_str());
-
     // NOTE: we use our own internal lock here because we have to call
     // into the HWC with the lock held, and we want to make sure
     // that even if HWC blocks (which it shouldn't), it won't
@@ -465,7 +400,7 @@
 status_t HWComposer::getDeviceCompositionChanges(
         HalDisplayId displayId, bool frameUsesClientComposition,
         std::chrono::steady_clock::time_point earliestPresentTime,
-        const std::shared_ptr<FenceTime>& previousPresentFence,
+        const std::shared_ptr<FenceTime>& previousPresentFence, nsecs_t expectedPresentTime,
         std::optional<android::HWComposer::DeviceRequestedChanges>* outChanges) {
     ATRACE_CALL();
 
@@ -487,16 +422,30 @@
     // earliest time to present. Otherwise, we may present a frame too early.
     // 2. There is no client composition. Otherwise, we first need to render the
     // client target buffer.
-    const bool prevFencePending =
-            previousPresentFence->getSignalTime() == Fence::SIGNAL_TIME_PENDING;
-    const bool canPresentEarly =
-            !prevFencePending && std::chrono::steady_clock::now() < earliestPresentTime;
-    const bool canSkipValidate = !canPresentEarly && !frameUsesClientComposition;
+    const bool canSkipValidate = [&] {
+        // We must call validate if we have client composition
+        if (frameUsesClientComposition) {
+            return false;
+        }
+
+        // If composer supports getting the expected present time, we can skip
+        // as composer will make sure to prevent early presentation
+        if (mComposer->isSupported(Hwc2::Composer::OptionalFeature::ExpectedPresentTime)) {
+            return true;
+        }
+
+        // composer doesn't support getting the expected present time. We can only
+        // skip validate if we know that we are not going to present early.
+        return std::chrono::steady_clock::now() >= earliestPresentTime ||
+                previousPresentFence->getSignalTime() == Fence::SIGNAL_TIME_PENDING;
+    }();
+
     displayData.validateWasSkipped = false;
     if (canSkipValidate) {
         sp<Fence> outPresentFence;
         uint32_t state = UINT32_MAX;
-        error = hwcDisplay->presentOrValidate(&numTypes, &numRequests, &outPresentFence , &state);
+        error = hwcDisplay->presentOrValidate(expectedPresentTime, &numTypes, &numRequests,
+                                              &outPresentFence, &state);
         if (!hasChangesError(error)) {
             RETURN_IF_HWC_ERROR_FOR("presentOrValidate", error, displayId, UNKNOWN_ERROR);
         }
@@ -511,7 +460,7 @@
         }
         // Present failed but Validate ran.
     } else {
-        error = hwcDisplay->validate(&numTypes, &numRequests);
+        error = hwcDisplay->validate(expectedPresentTime, &numTypes, &numRequests);
     }
     ALOGV("SkipValidate failed, Falling back to SLOW validate/present");
     if (!hasChangesError(error)) {
@@ -530,11 +479,12 @@
     RETURN_IF_HWC_ERROR_FOR("getRequests", error, displayId, BAD_INDEX);
 
     DeviceRequestedChanges::ClientTargetProperty clientTargetProperty;
-    error = hwcDisplay->getClientTargetProperty(&clientTargetProperty);
+    float brightness = 1.f;
+    error = hwcDisplay->getClientTargetProperty(&clientTargetProperty, &brightness);
 
     outChanges->emplace(DeviceRequestedChanges{std::move(changedTypes), std::move(displayRequests),
                                                std::move(layerRequests),
-                                               std::move(clientTargetProperty)});
+                                               std::move(clientTargetProperty), brightness});
     error = hwcDisplay->acceptChanges();
     RETURN_IF_HWC_ERROR_FOR("acceptChanges", error, displayId, BAD_INDEX);
 
@@ -575,9 +525,11 @@
         return NO_ERROR;
     }
 
-    const bool previousFramePending =
-            previousPresentFence->getSignalTime() == Fence::SIGNAL_TIME_PENDING;
-    if (!previousFramePending) {
+    const bool waitForEarliestPresent =
+            !mComposer->isSupported(Hwc2::Composer::OptionalFeature::ExpectedPresentTime) &&
+            previousPresentFence->getSignalTime() != Fence::SIGNAL_TIME_PENDING;
+
+    if (waitForEarliestPresent) {
         ATRACE_NAME("wait for earliest present time");
         std::this_thread::sleep_until(earliestPresentTime);
     }
@@ -597,14 +549,11 @@
 status_t HWComposer::setPowerMode(PhysicalDisplayId displayId, hal::PowerMode mode) {
     RETURN_IF_INVALID_DISPLAY(displayId, BAD_INDEX);
 
-    const auto& displayData = mDisplayData[displayId];
-    LOG_FATAL_IF(displayData.isVirtual, "%s: Invalid operation on virtual display with ID %s",
-                 __FUNCTION__, to_string(displayId).c_str());
-
     if (mode == hal::PowerMode::OFF) {
         setVsyncEnabled(displayId, hal::Vsync::DISABLE);
     }
 
+    const auto& displayData = mDisplayData[displayId];
     auto& hwcDisplay = displayData.hwcDisplay;
     switch (mode) {
         case hal::PowerMode::OFF:
@@ -664,11 +613,7 @@
     RETURN_IF_INVALID_DISPLAY(displayId, BAD_INDEX);
 
     auto& displayData = mDisplayData[displayId];
-    bool isIdentity = transform == mat4();
-    auto error = displayData.hwcDisplay
-                         ->setColorTransform(transform,
-                                             isIdentity ? hal::ColorTransform::IDENTITY
-                                                        : hal::ColorTransform::ARBITRARY_MATRIX);
+    auto error = displayData.hwcDisplay->setColorTransform(transform);
     RETURN_IF_HWC_ERROR(error, displayId, UNKNOWN_ERROR);
     return NO_ERROR;
 }
@@ -678,14 +623,15 @@
     auto& displayData = mDisplayData[displayId];
     const auto hwcDisplayId = displayData.hwcDisplay->getId();
 
-    // TODO(b/74619554): Select internal/external display from remaining displays.
-    if (hwcDisplayId == mInternalHwcDisplayId) {
-        mInternalHwcDisplayId.reset();
-    } else if (hwcDisplayId == mExternalHwcDisplayId) {
-        mExternalHwcDisplayId.reset();
-    }
     mPhysicalDisplayIdMap.erase(hwcDisplayId);
     mDisplayData.erase(displayId);
+
+    // Reset the primary display ID if we're disconnecting it.
+    // This way isHeadless() will return false, which is necessary
+    // because getPrimaryDisplayId() will crash.
+    if (mPrimaryHwcDisplayId == hwcDisplayId) {
+        mPrimaryHwcDisplayId.reset();
+    }
 }
 
 status_t HWComposer::setOutputBuffer(HalVirtualDisplayId displayId, const sp<Fence>& acquireFence,
@@ -693,9 +639,6 @@
     RETURN_IF_INVALID_DISPLAY(displayId, BAD_INDEX);
     const auto& displayData = mDisplayData[displayId];
 
-    LOG_FATAL_IF(!displayData.isVirtual, "%s: Invalid operation on physical display with ID %s",
-                 __FUNCTION__, to_string(displayId).c_str());
-
     auto error = displayData.hwcDisplay->setOutputBuffer(buffer, acquireFence);
     RETURN_IF_HWC_ERROR(error, displayId, UNKNOWN_ERROR);
     return NO_ERROR;
@@ -778,12 +721,13 @@
     return NO_ERROR;
 }
 
-std::future<status_t> HWComposer::setDisplayBrightness(PhysicalDisplayId displayId,
-                                                       float brightness) {
+std::future<status_t> HWComposer::setDisplayBrightness(
+        PhysicalDisplayId displayId, float brightness,
+        const Hwc2::Composer::DisplayBrightnessOptions& options) {
     RETURN_IF_INVALID_DISPLAY(displayId, ftl::yield<status_t>(BAD_INDEX));
     auto& display = mDisplayData[displayId].hwcDisplay;
 
-    return ftl::chain(display->setDisplayBrightness(brightness))
+    return ftl::chain(display->setDisplayBrightness(brightness, options))
             .then([displayId](hal::Error error) -> status_t {
                 if (error == hal::Error::UNSUPPORTED) {
                     RETURN_IF_HWC_ERROR(error, displayId, INVALID_OPERATION);
@@ -796,6 +740,62 @@
             });
 }
 
+status_t HWComposer::setBootDisplayMode(PhysicalDisplayId displayId,
+                                        hal::HWConfigId displayModeId) {
+    RETURN_IF_INVALID_DISPLAY(displayId, BAD_INDEX);
+    const auto error = mDisplayData[displayId].hwcDisplay->setBootDisplayConfig(displayModeId);
+    if (error == hal::Error::UNSUPPORTED) {
+        RETURN_IF_HWC_ERROR(error, displayId, INVALID_OPERATION);
+    }
+    if (error == hal::Error::BAD_PARAMETER) {
+        RETURN_IF_HWC_ERROR(error, displayId, BAD_VALUE);
+    }
+    RETURN_IF_HWC_ERROR(error, displayId, UNKNOWN_ERROR);
+    return NO_ERROR;
+}
+
+status_t HWComposer::clearBootDisplayMode(PhysicalDisplayId displayId) {
+    RETURN_IF_INVALID_DISPLAY(displayId, BAD_INDEX);
+    const auto error = mDisplayData[displayId].hwcDisplay->clearBootDisplayConfig();
+    if (error == hal::Error::UNSUPPORTED) {
+        RETURN_IF_HWC_ERROR(error, displayId, INVALID_OPERATION);
+    }
+    if (error == hal::Error::BAD_PARAMETER) {
+        RETURN_IF_HWC_ERROR(error, displayId, BAD_VALUE);
+    }
+    RETURN_IF_HWC_ERROR(error, displayId, UNKNOWN_ERROR);
+    return NO_ERROR;
+}
+
+std::optional<hal::HWConfigId> HWComposer::getPreferredBootDisplayMode(
+        PhysicalDisplayId displayId) {
+    RETURN_IF_INVALID_DISPLAY(displayId, std::nullopt);
+    hal::HWConfigId displayModeId;
+    const auto error =
+            mDisplayData[displayId].hwcDisplay->getPreferredBootDisplayConfig(&displayModeId);
+    if (error != hal::Error::NONE) {
+        LOG_DISPLAY_ERROR(displayId, to_string(error).c_str());
+        return std::nullopt;
+    }
+    return displayModeId;
+}
+
+status_t HWComposer::getDisplayDecorationSupport(
+        PhysicalDisplayId displayId,
+        std::optional<aidl::android::hardware::graphics::common::DisplayDecorationSupport>*
+                support) {
+    RETURN_IF_INVALID_DISPLAY(displayId, BAD_INDEX);
+    const auto error = mDisplayData[displayId].hwcDisplay->getDisplayDecorationSupport(support);
+    if (error == hal::Error::UNSUPPORTED) {
+        RETURN_IF_HWC_ERROR(error, displayId, INVALID_OPERATION);
+    }
+    if (error == hal::Error::BAD_PARAMETER) {
+        RETURN_IF_HWC_ERROR(error, displayId, BAD_VALUE);
+    }
+    RETURN_IF_HWC_ERROR(error, displayId, UNKNOWN_ERROR);
+    return NO_ERROR;
+}
+
 status_t HWComposer::setAutoLowLatencyMode(PhysicalDisplayId displayId, bool on) {
     RETURN_IF_INVALID_DISPLAY(displayId, BAD_INDEX);
     const auto error = mDisplayData[displayId].hwcDisplay->setAutoLowLatencyMode(on);
@@ -810,10 +810,11 @@
 }
 
 status_t HWComposer::getSupportedContentTypes(
-        PhysicalDisplayId displayId, std::vector<hal::ContentType>* outSupportedContentTypes) {
+        PhysicalDisplayId displayId,
+        std::vector<hal::ContentType>* outSupportedContentTypes) const {
     RETURN_IF_INVALID_DISPLAY(displayId, BAD_INDEX);
-    const auto error =
-            mDisplayData[displayId].hwcDisplay->getSupportedContentTypes(outSupportedContentTypes);
+    const auto error = mDisplayData.at(displayId).hwcDisplay->getSupportedContentTypes(
+            outSupportedContentTypes);
 
     RETURN_IF_HWC_ERROR(error, displayId, UNKNOWN_ERROR);
 
@@ -853,8 +854,7 @@
 
 std::optional<hal::HWDisplayId> HWComposer::fromPhysicalDisplayId(
         PhysicalDisplayId displayId) const {
-    if (const auto it = mDisplayData.find(displayId);
-        it != mDisplayData.end() && !it->second.isVirtual) {
+    if (const auto it = mDisplayData.find(displayId); it != mDisplayData.end()) {
         return it->second.hwcDisplay->getId();
     }
     return {};
@@ -868,7 +868,8 @@
         return true;
     }
 
-    if (!mHasMultiDisplaySupport && mInternalHwcDisplayId && mExternalHwcDisplayId) {
+    // Legacy mode only supports IDs LEGACY_DISPLAY_TYPE_PRIMARY and LEGACY_DISPLAY_TYPE_EXTERNAL.
+    if (!mHasMultiDisplaySupport && mPhysicalDisplayIdMap.size() == 2) {
         ALOGE("Ignoring connection of tertiary display %" PRIu64, hwcDisplayId);
         return true;
     }
@@ -909,7 +910,7 @@
         }
 
         info = [this, hwcDisplayId, &port, &data, hasDisplayIdentificationData] {
-            const bool isPrimary = !mInternalHwcDisplayId;
+            const bool isPrimary = !mPrimaryHwcDisplayId;
             if (mHasMultiDisplaySupport) {
                 if (const auto info = parseDisplayIdentificationData(port, data)) {
                     return *info;
@@ -922,8 +923,8 @@
             }
 
             return DisplayIdentificationInfo{.id = PhysicalDisplayId::fromPort(port),
-                                             .name = isPrimary ? "Internal display"
-                                                               : "External display",
+                                             .name = isPrimary ? "Primary display"
+                                                               : "Secondary display",
                                              .deviceProductInfo = std::nullopt};
         }();
     }
@@ -936,9 +937,12 @@
 
 std::optional<DisplayIdentificationInfo> HWComposer::onHotplugDisconnect(
         hal::HWDisplayId hwcDisplayId) {
+    LOG_ALWAYS_FATAL_IF(hwcDisplayId == mPrimaryHwcDisplayId,
+                        "Primary display cannot be disconnected.");
+
     const auto displayId = toPhysicalDisplayId(hwcDisplayId);
     if (!displayId) {
-        ALOGE("Ignoring disconnection of invalid HWC display %" PRIu64, hwcDisplayId);
+        LOG_HWC_DISPLAY_ERROR(hwcDisplayId, "Invalid HWC display");
         return {};
     }
 
@@ -947,7 +951,7 @@
     if (isConnected(*displayId)) {
         mDisplayData[*displayId].hwcDisplay->setConnected(false);
     } else {
-        ALOGW("Attempted to disconnect unknown display %" PRIu64, hwcDisplayId);
+        LOG_HWC_DISPLAY_ERROR(hwcDisplayId, "Already disconnected");
     }
     // The cleanup of Disconnect is handled through HWComposer::disconnectDisplay
     // via SurfaceFlinger's onHotplugReceived callback handling
@@ -960,10 +964,40 @@
     static_assert(sizeof(hal::Capability) == sizeof(int32_t), "Capability size has changed");
     auto capabilities = mComposer->getCapabilities();
     for (auto capability : capabilities) {
-        mCapabilities.emplace(static_cast<hal::Capability>(capability));
+        mCapabilities.emplace(capability);
     }
 }
 
+status_t HWComposer::setIdleTimerEnabled(PhysicalDisplayId displayId,
+                                         std::chrono::milliseconds timeout) {
+    ATRACE_CALL();
+    RETURN_IF_INVALID_DISPLAY(displayId, BAD_INDEX);
+    const auto error = mDisplayData[displayId].hwcDisplay->setIdleTimerEnabled(timeout);
+    if (error == hal::Error::UNSUPPORTED) {
+        RETURN_IF_HWC_ERROR(error, displayId, INVALID_OPERATION);
+    }
+    if (error == hal::Error::BAD_PARAMETER) {
+        RETURN_IF_HWC_ERROR(error, displayId, BAD_VALUE);
+    }
+    RETURN_IF_HWC_ERROR(error, displayId, UNKNOWN_ERROR);
+    return NO_ERROR;
+}
+
+bool HWComposer::hasDisplayIdleTimerCapability(PhysicalDisplayId displayId) const {
+    RETURN_IF_INVALID_DISPLAY(displayId, false);
+    return mDisplayData.at(displayId).hwcDisplay->hasDisplayIdleTimerCapability();
+}
+
+Hwc2::AidlTransform HWComposer::getPhysicalDisplayOrientation(PhysicalDisplayId displayId) const {
+    ATRACE_CALL();
+    RETURN_IF_INVALID_DISPLAY(displayId, Hwc2::AidlTransform::NONE);
+    Hwc2::AidlTransform outTransform;
+    const auto& hwcDisplay = mDisplayData.at(displayId).hwcDisplay;
+    const auto error = hwcDisplay->getPhysicalDisplayOrientation(&outTransform);
+    RETURN_IF_HWC_ERROR(error, displayId, Hwc2::AidlTransform::NONE);
+    return outTransform;
+}
+
 void HWComposer::loadLayerMetadataSupport() {
     mSupportedLayerGenericMetadata.clear();
 
diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.h b/services/surfaceflinger/DisplayHardware/HWComposer.h
index 49f96d9..0e15a7c 100644
--- a/services/surfaceflinger/DisplayHardware/HWComposer.h
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.h
@@ -14,8 +14,7 @@
  * limitations under the License.
  */
 
-#ifndef ANDROID_SF_HWCOMPOSER_H
-#define ANDROID_SF_HWCOMPOSER_H
+#pragma once
 
 #include <cstdint>
 #include <future>
@@ -27,6 +26,7 @@
 #include <vector>
 
 #include <android-base/thread_annotations.h>
+#include <ui/DisplayIdentification.h>
 #include <ui/FenceTime.h>
 
 // TODO(b/129481165): remove the #pragma below and fix conversion issues
@@ -39,11 +39,15 @@
 #include <utils/StrongPointer.h>
 #include <utils/Timers.h>
 
-#include "DisplayIdentification.h"
 #include "DisplayMode.h"
 #include "HWC2.h"
 #include "Hal.h"
 
+#include <aidl/android/hardware/graphics/common/DisplayDecorationSupport.h>
+#include <aidl/android/hardware/graphics/composer3/Capability.h>
+#include <aidl/android/hardware/graphics/composer3/Composition.h>
+#include <aidl/android/hardware/graphics/composer3/DisplayCapability.h>
+
 namespace android {
 
 namespace hal = hardware::graphics::composer::hal;
@@ -71,7 +75,9 @@
 class HWComposer {
 public:
     struct DeviceRequestedChanges {
-        using ChangedTypes = std::unordered_map<HWC2::Layer*, hal::Composition>;
+        using ChangedTypes =
+                std::unordered_map<HWC2::Layer*,
+                                   aidl::android::hardware::graphics::composer3::Composition>;
         using ClientTargetProperty = hal::ClientTargetProperty;
         using DisplayRequests = hal::DisplayRequest;
         using LayerRequests = std::unordered_map<HWC2::Layer*, hal::LayerRequest>;
@@ -80,6 +86,7 @@
         DisplayRequests displayRequests;
         LayerRequests layerRequests;
         ClientTargetProperty clientTargetProperty;
+        float clientTargetBrightness;
     };
 
     struct HWCDisplayMode {
@@ -100,13 +107,15 @@
 
     virtual ~HWComposer();
 
-    virtual void setCallback(HWC2::ComposerCallback*) = 0;
+    virtual void setCallback(HWC2::ComposerCallback&) = 0;
 
     virtual bool getDisplayIdentificationData(hal::HWDisplayId, uint8_t* outPort,
                                               DisplayIdentificationData* outData) const = 0;
 
-    virtual bool hasCapability(hal::Capability) const = 0;
-    virtual bool hasDisplayCapability(HalDisplayId, hal::DisplayCapability) const = 0;
+    virtual bool hasCapability(aidl::android::hardware::graphics::composer3::Capability) const = 0;
+    virtual bool hasDisplayCapability(
+            HalDisplayId,
+            aidl::android::hardware::graphics::composer3::DisplayCapability) const = 0;
 
     virtual size_t getMaxVirtualDisplayCount() const = 0;
     virtual size_t getMaxVirtualDisplayDimension() const = 0;
@@ -130,7 +139,7 @@
     virtual status_t getDeviceCompositionChanges(
             HalDisplayId, bool frameUsesClientComposition,
             std::chrono::steady_clock::time_point earliestPresentTime,
-            const std::shared_ptr<FenceTime>& previousPresentFence,
+            const std::shared_ptr<FenceTime>& previousPresentFence, nsecs_t expectedPresentTime,
             std::optional<DeviceRequestedChanges>* outChanges) = 0;
 
     virtual status_t setClientTarget(HalDisplayId, uint32_t slot, const sp<Fence>& acquireFence,
@@ -185,7 +194,9 @@
                                                DisplayedFrameStats* outStats) = 0;
 
     // Sets the brightness of a display.
-    virtual std::future<status_t> setDisplayBrightness(PhysicalDisplayId, float brightness) = 0;
+    virtual std::future<status_t> setDisplayBrightness(
+            PhysicalDisplayId, float brightness,
+            const Hwc2::Composer::DisplayBrightnessOptions&) = 0;
 
     // Events handling ---------------------------------------------------------
 
@@ -223,22 +234,49 @@
                                                   hal::VsyncPeriodChangeTimeline* outTimeline) = 0;
     virtual status_t setAutoLowLatencyMode(PhysicalDisplayId, bool on) = 0;
     virtual status_t getSupportedContentTypes(
-            PhysicalDisplayId, std::vector<hal::ContentType>* outSupportedContentTypes) = 0;
+            PhysicalDisplayId, std::vector<hal::ContentType>* outSupportedContentTypes) const = 0;
+
+    bool supportsContentType(PhysicalDisplayId displayId, hal::ContentType type) const {
+        std::vector<hal::ContentType> types;
+        return getSupportedContentTypes(displayId, &types) == NO_ERROR &&
+                std::find(types.begin(), types.end(), type) != types.end();
+    }
+
     virtual status_t setContentType(PhysicalDisplayId, hal::ContentType) = 0;
+
     virtual const std::unordered_map<std::string, bool>& getSupportedLayerGenericMetadata()
             const = 0;
 
-    // for debugging ----------------------------------------------------------
     virtual void dump(std::string& out) const = 0;
 
     virtual Hwc2::Composer* getComposer() const = 0;
 
-    // TODO(b/74619554): Remove special cases for internal/external display.
-    virtual std::optional<hal::HWDisplayId> getInternalHwcDisplayId() const = 0;
-    virtual std::optional<hal::HWDisplayId> getExternalHwcDisplayId() const = 0;
+    // Returns the first display connected at boot. Its connection via HWComposer::onHotplug,
+    // which in practice is immediately after HWComposer construction, must occur before any
+    // call to this function.
+    // The primary display can be temporarily disconnected from the perspective
+    // of this class. Callers must not call getPrimaryHwcDisplayId() or getPrimaryDisplayId()
+    // if isHeadless().
+    //
+    // TODO(b/182939859): Remove special cases for primary display.
+    virtual hal::HWDisplayId getPrimaryHwcDisplayId() const = 0;
+    virtual PhysicalDisplayId getPrimaryDisplayId() const = 0;
+    virtual bool isHeadless() const = 0;
 
     virtual std::optional<PhysicalDisplayId> toPhysicalDisplayId(hal::HWDisplayId) const = 0;
     virtual std::optional<hal::HWDisplayId> fromPhysicalDisplayId(PhysicalDisplayId) const = 0;
+
+    // Composer 3.0
+    virtual status_t setBootDisplayMode(PhysicalDisplayId, hal::HWConfigId) = 0;
+    virtual status_t clearBootDisplayMode(PhysicalDisplayId) = 0;
+    virtual std::optional<hal::HWConfigId> getPreferredBootDisplayMode(PhysicalDisplayId) = 0;
+    virtual status_t getDisplayDecorationSupport(
+            PhysicalDisplayId,
+            std::optional<aidl::android::hardware::graphics::common::DisplayDecorationSupport>*
+                    support) = 0;
+    virtual status_t setIdleTimerEnabled(PhysicalDisplayId, std::chrono::milliseconds timeout) = 0;
+    virtual bool hasDisplayIdleTimerCapability(PhysicalDisplayId) const = 0;
+    virtual Hwc2::AidlTransform getPhysicalDisplayOrientation(PhysicalDisplayId) const = 0;
 };
 
 namespace impl {
@@ -250,13 +288,15 @@
 
     ~HWComposer() override;
 
-    void setCallback(HWC2::ComposerCallback*) override;
+    void setCallback(HWC2::ComposerCallback&) override;
 
     bool getDisplayIdentificationData(hal::HWDisplayId, uint8_t* outPort,
                                       DisplayIdentificationData* outData) const override;
 
-    bool hasCapability(hal::Capability) const override;
-    bool hasDisplayCapability(HalDisplayId, hal::DisplayCapability) const override;
+    bool hasCapability(aidl::android::hardware::graphics::composer3::Capability) const override;
+    bool hasDisplayCapability(
+            HalDisplayId,
+            aidl::android::hardware::graphics::composer3::DisplayCapability) const override;
 
     size_t getMaxVirtualDisplayCount() const override;
     size_t getMaxVirtualDisplayDimension() const override;
@@ -272,7 +312,7 @@
     status_t getDeviceCompositionChanges(
             HalDisplayId, bool frameUsesClientComposition,
             std::chrono::steady_clock::time_point earliestPresentTime,
-            const std::shared_ptr<FenceTime>& previousPresentFence,
+            const std::shared_ptr<FenceTime>& previousPresentFence, nsecs_t expectedPresentTime,
             std::optional<DeviceRequestedChanges>* outChanges) override;
 
     status_t setClientTarget(HalDisplayId, uint32_t slot, const sp<Fence>& acquireFence,
@@ -324,7 +364,9 @@
                                               uint64_t maxFrames) override;
     status_t getDisplayedContentSample(HalDisplayId, uint64_t maxFrames, uint64_t timestamp,
                                        DisplayedFrameStats* outStats) override;
-    std::future<status_t> setDisplayBrightness(PhysicalDisplayId, float brightness) override;
+    std::future<status_t> setDisplayBrightness(
+            PhysicalDisplayId, float brightness,
+            const Hwc2::Composer::DisplayBrightnessOptions&) override;
 
     // Events handling ---------------------------------------------------------
 
@@ -356,24 +398,42 @@
                                           const hal::VsyncPeriodChangeConstraints&,
                                           hal::VsyncPeriodChangeTimeline* outTimeline) override;
     status_t setAutoLowLatencyMode(PhysicalDisplayId, bool) override;
-    status_t getSupportedContentTypes(PhysicalDisplayId, std::vector<hal::ContentType>*) override;
+    status_t getSupportedContentTypes(PhysicalDisplayId,
+                                      std::vector<hal::ContentType>*) const override;
     status_t setContentType(PhysicalDisplayId, hal::ContentType) override;
 
     const std::unordered_map<std::string, bool>& getSupportedLayerGenericMetadata() const override;
 
+    // Composer 3.0
+    status_t setBootDisplayMode(PhysicalDisplayId, hal::HWConfigId) override;
+    status_t clearBootDisplayMode(PhysicalDisplayId) override;
+    std::optional<hal::HWConfigId> getPreferredBootDisplayMode(PhysicalDisplayId) override;
+    status_t getDisplayDecorationSupport(
+            PhysicalDisplayId,
+            std::optional<aidl::android::hardware::graphics::common::DisplayDecorationSupport>*
+                    support) override;
+    status_t setIdleTimerEnabled(PhysicalDisplayId, std::chrono::milliseconds timeout) override;
+    bool hasDisplayIdleTimerCapability(PhysicalDisplayId) const override;
+    Hwc2::AidlTransform getPhysicalDisplayOrientation(PhysicalDisplayId) const override;
+
     // for debugging ----------------------------------------------------------
     void dump(std::string& out) const override;
 
     Hwc2::Composer* getComposer() const override { return mComposer.get(); }
 
-    // TODO(b/74619554): Remove special cases for internal/external display.
-    std::optional<hal::HWDisplayId> getInternalHwcDisplayId() const override {
-        return mInternalHwcDisplayId;
+    hal::HWDisplayId getPrimaryHwcDisplayId() const override {
+        LOG_ALWAYS_FATAL_IF(!mPrimaryHwcDisplayId, "Missing HWC primary display");
+        return *mPrimaryHwcDisplayId;
     }
-    std::optional<hal::HWDisplayId> getExternalHwcDisplayId() const override {
-        return mExternalHwcDisplayId;
+
+    PhysicalDisplayId getPrimaryDisplayId() const override {
+        const auto id = toPhysicalDisplayId(getPrimaryHwcDisplayId());
+        LOG_ALWAYS_FATAL_IF(!id, "Missing primary display");
+        return *id;
     }
 
+    virtual bool isHeadless() const override { return !mPrimaryHwcDisplayId; }
+
     std::optional<PhysicalDisplayId> toPhysicalDisplayId(hal::HWDisplayId) const override;
     std::optional<hal::HWDisplayId> fromPhysicalDisplayId(PhysicalDisplayId) const override;
 
@@ -382,12 +442,9 @@
     friend TestableSurfaceFlinger;
 
     struct DisplayData {
-        bool isVirtual = false;
         std::unique_ptr<HWC2::Display> hwcDisplay;
         sp<Fence> lastPresentFence = Fence::NO_FENCE; // signals when the last set op retires
         std::unordered_map<HWC2::Layer*, sp<Fence>> releaseFences;
-        buffer_handle_t outbufHandle = nullptr;
-        sp<Fence> outbufAcquireFence = Fence::NO_FENCE;
 
         bool validateWasSkipped;
         hal::Error presentError;
@@ -413,13 +470,12 @@
     std::unordered_map<HalDisplayId, DisplayData> mDisplayData;
 
     std::unique_ptr<android::Hwc2::Composer> mComposer;
-    std::unordered_set<hal::Capability> mCapabilities;
+    std::unordered_set<aidl::android::hardware::graphics::composer3::Capability> mCapabilities;
     std::unordered_map<std::string, bool> mSupportedLayerGenericMetadata;
     bool mRegisteredCallback = false;
 
     std::unordered_map<hal::HWDisplayId, PhysicalDisplayId> mPhysicalDisplayIdMap;
-    std::optional<hal::HWDisplayId> mInternalHwcDisplayId;
-    std::optional<hal::HWDisplayId> mExternalHwcDisplayId;
+    std::optional<hal::HWDisplayId> mPrimaryHwcDisplayId;
     bool mHasMultiDisplaySupport = false;
 
     const size_t mMaxVirtualDisplayDimension;
@@ -428,5 +484,3 @@
 
 } // namespace impl
 } // namespace android
-
-#endif // ANDROID_SF_HWCOMPOSER_H
diff --git a/services/surfaceflinger/DisplayHardware/Hal.h b/services/surfaceflinger/DisplayHardware/Hal.h
index 02d0658..4737034 100644
--- a/services/surfaceflinger/DisplayHardware/Hal.h
+++ b/services/surfaceflinger/DisplayHardware/Hal.h
@@ -20,6 +20,9 @@
 #include <android/hardware/graphics/composer/2.4/IComposer.h>
 #include <android/hardware/graphics/composer/2.4/IComposerClient.h>
 
+#include <aidl/android/hardware/graphics/composer3/Composition.h>
+#include <aidl/android/hardware/graphics/composer3/DisplayCapability.h>
+
 #define ERROR_HAS_CHANGES 5
 
 namespace android {
@@ -48,13 +51,10 @@
 
 using Attribute = IComposerClient::Attribute;
 using BlendMode = IComposerClient::BlendMode;
-using Color = IComposerClient::Color;
-using Composition = IComposerClient::Composition;
 using Connection = IComposerCallback::Connection;
 using ContentType = IComposerClient::ContentType;
 using Capability = IComposer::Capability;
 using ClientTargetProperty = IComposerClient::ClientTargetProperty;
-using DisplayCapability = IComposerClient::DisplayCapability;
 using DisplayRequest = IComposerClient::DisplayRequest;
 using DisplayType = IComposerClient::DisplayType;
 using HWConfigId = V2_1::Config;
@@ -95,20 +95,48 @@
     }
 }
 
-inline std::string to_string(hardware::graphics::composer::hal::Composition composition) {
+inline std::string to_string(
+        aidl::android::hardware::graphics::composer3::Composition composition) {
     switch (composition) {
-        case hardware::graphics::composer::hal::Composition::INVALID:
+        case aidl::android::hardware::graphics::composer3::Composition::INVALID:
             return "Invalid";
-        case hardware::graphics::composer::hal::Composition::CLIENT:
+        case aidl::android::hardware::graphics::composer3::Composition::CLIENT:
             return "Client";
-        case hardware::graphics::composer::hal::Composition::DEVICE:
+        case aidl::android::hardware::graphics::composer3::Composition::DEVICE:
             return "Device";
-        case hardware::graphics::composer::hal::Composition::SOLID_COLOR:
+        case aidl::android::hardware::graphics::composer3::Composition::SOLID_COLOR:
             return "SolidColor";
-        case hardware::graphics::composer::hal::Composition::CURSOR:
+        case aidl::android::hardware::graphics::composer3::Composition::CURSOR:
             return "Cursor";
-        case hardware::graphics::composer::hal::Composition::SIDEBAND:
+        case aidl::android::hardware::graphics::composer3::Composition::SIDEBAND:
             return "Sideband";
+        case aidl::android::hardware::graphics::composer3::Composition::DISPLAY_DECORATION:
+            return "DisplayDecoration";
+        default:
+            return "Unknown";
+    }
+}
+
+inline std::string to_string(
+        aidl::android::hardware::graphics::composer3::DisplayCapability displayCapability) {
+    switch (displayCapability) {
+        case aidl::android::hardware::graphics::composer3::DisplayCapability::INVALID:
+            return "Invalid";
+        case aidl::android::hardware::graphics::composer3::DisplayCapability::
+                SKIP_CLIENT_COLOR_TRANSFORM:
+            return "SkipColorTransform";
+        case aidl::android::hardware::graphics::composer3::DisplayCapability::DOZE:
+            return "Doze";
+        case aidl::android::hardware::graphics::composer3::DisplayCapability::BRIGHTNESS:
+            return "Brightness";
+        case aidl::android::hardware::graphics::composer3::DisplayCapability::PROTECTED_CONTENTS:
+            return "ProtectedContents";
+        case aidl::android::hardware::graphics::composer3::DisplayCapability::AUTO_LOW_LATENCY_MODE:
+            return "AutoLowLatencyMode";
+        case aidl::android::hardware::graphics::composer3::DisplayCapability::SUSPEND:
+            return "Suspend";
+        case aidl::android::hardware::graphics::composer3::DisplayCapability::DISPLAY_IDLE_TIMER:
+            return "DisplayIdleTimer";
         default:
             return "Unknown";
     }
diff --git a/services/surfaceflinger/DisplayHardware/Hash.cpp b/services/surfaceflinger/DisplayHardware/Hash.cpp
deleted file mode 100644
index 6056c8d..0000000
--- a/services/surfaceflinger/DisplayHardware/Hash.cpp
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * 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.
- */
-
-#undef LOG_TAG
-#define LOG_TAG "DisplayIdentification"
-
-#include <cstring>
-#include <type_traits>
-
-#include <log/log.h>
-
-#include "Hash.h"
-
-namespace android {
-namespace {
-
-template <class T>
-inline T load(const void* p) {
-    static_assert(std::is_integral<T>::value, "T must be integral");
-
-    T r;
-    std::memcpy(&r, p, sizeof(r));
-    return r;
-}
-
-uint64_t rotateByAtLeast1(uint64_t val, uint8_t shift) {
-    return (val >> shift) | (val << (64 - shift));
-}
-
-uint64_t shiftMix(uint64_t val) {
-    return val ^ (val >> 47);
-}
-
-uint64_t hash64Len16(uint64_t u, uint64_t v) {
-    constexpr uint64_t kMul = 0x9ddfea08eb382d69;
-    uint64_t a = (u ^ v) * kMul;
-    a ^= (a >> 47);
-    uint64_t b = (v ^ a) * kMul;
-    b ^= (b >> 47);
-    b *= kMul;
-    return b;
-}
-
-uint64_t hash64Len0To16(const char* s, uint64_t len) {
-    constexpr uint64_t k2 = 0x9ae16a3b2f90404f;
-    constexpr uint64_t k3 = 0xc949d7c7509e6557;
-
-    if (len > 8) {
-        const uint64_t a = load<uint64_t>(s);
-        const uint64_t b = load<uint64_t>(s + len - 8);
-        return hash64Len16(a, rotateByAtLeast1(b + len, static_cast<uint8_t>(len))) ^ b;
-    }
-    if (len >= 4) {
-        const uint32_t a = load<uint32_t>(s);
-        const uint32_t b = load<uint32_t>(s + len - 4);
-        return hash64Len16(len + (a << 3), b);
-    }
-    if (len > 0) {
-        const unsigned char a = static_cast<unsigned char>(s[0]);
-        const unsigned char b = static_cast<unsigned char>(s[len >> 1]);
-        const unsigned char c = static_cast<unsigned char>(s[len - 1]);
-        const uint32_t y = static_cast<uint32_t>(a) + (static_cast<uint32_t>(b) << 8);
-        const uint32_t z = static_cast<uint32_t>(len) + (static_cast<uint32_t>(c) << 2);
-        return shiftMix(y * k2 ^ z * k3) * k2;
-    }
-    return k2;
-}
-
-} // namespace
-
-uint64_t cityHash64Len0To16(std::string_view sv) {
-    auto len = sv.length();
-    if (len > 16) {
-        ALOGE("%s called with length %zu. Only hashing the first 16 chars", __FUNCTION__, len);
-        len = 16;
-    }
-    return hash64Len0To16(sv.data(), len);
-}
-
-} // namespace android
\ No newline at end of file
diff --git a/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp b/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp
new file mode 100644
index 0000000..d9af553
--- /dev/null
+++ b/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp
@@ -0,0 +1,1655 @@
+/*
+ * 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.
+ */
+
+// TODO(b/129481165): remove the #pragma below and fix conversion issues
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wconversion"
+
+#undef LOG_TAG
+#define LOG_TAG "HwcComposer"
+#define ATRACE_TAG ATRACE_TAG_GRAPHICS
+
+#include "HidlComposerHal.h"
+
+#include <android/binder_manager.h>
+#include <composer-command-buffer/2.2/ComposerCommandBuffer.h>
+#include <hidl/HidlTransportSupport.h>
+#include <hidl/HidlTransportUtils.h>
+#include <log/log.h>
+#include <utils/Trace.h>
+#include "HWC2.h"
+#include "Hal.h"
+
+#include <algorithm>
+#include <cinttypes>
+
+using aidl::android::hardware::graphics::composer3::Capability;
+using aidl::android::hardware::graphics::composer3::DisplayCapability;
+
+namespace android {
+
+using hardware::hidl_handle;
+using hardware::hidl_vec;
+using hardware::Return;
+
+namespace Hwc2 {
+namespace {
+
+using android::hardware::Return;
+using android::hardware::Void;
+using android::HWC2::ComposerCallback;
+
+class ComposerCallbackBridge : public IComposerCallback {
+public:
+    ComposerCallbackBridge(ComposerCallback& callback, bool vsyncSwitchingSupported)
+          : mCallback(callback), mVsyncSwitchingSupported(vsyncSwitchingSupported) {}
+
+    Return<void> onHotplug(Display display, Connection connection) override {
+        mCallback.onComposerHalHotplug(display, connection);
+        return Void();
+    }
+
+    Return<void> onRefresh(Display display) override {
+        mCallback.onComposerHalRefresh(display);
+        return Void();
+    }
+
+    Return<void> onVsync(Display display, int64_t timestamp) override {
+        if (!mVsyncSwitchingSupported) {
+            mCallback.onComposerHalVsync(display, timestamp, std::nullopt);
+        } else {
+            ALOGW("Unexpected onVsync callback on composer >= 2.4, ignoring.");
+        }
+        return Void();
+    }
+
+    Return<void> onVsync_2_4(Display display, int64_t timestamp,
+                             VsyncPeriodNanos vsyncPeriodNanos) override {
+        if (mVsyncSwitchingSupported) {
+            mCallback.onComposerHalVsync(display, timestamp, vsyncPeriodNanos);
+        } else {
+            ALOGW("Unexpected onVsync_2_4 callback on composer <= 2.3, ignoring.");
+        }
+        return Void();
+    }
+
+    Return<void> onVsyncPeriodTimingChanged(Display display,
+                                            const VsyncPeriodChangeTimeline& timeline) override {
+        mCallback.onComposerHalVsyncPeriodTimingChanged(display, timeline);
+        return Void();
+    }
+
+    Return<void> onSeamlessPossible(Display display) override {
+        mCallback.onComposerHalSeamlessPossible(display);
+        return Void();
+    }
+
+private:
+    ComposerCallback& mCallback;
+    const bool mVsyncSwitchingSupported;
+};
+
+} // namespace
+
+HidlComposer::~HidlComposer() = default;
+
+namespace {
+
+class BufferHandle {
+public:
+    explicit BufferHandle(const native_handle_t* buffer) {
+        // nullptr is not a valid handle to HIDL
+        mHandle = (buffer) ? buffer : native_handle_init(mStorage, 0, 0);
+    }
+
+    operator const hidl_handle&() const // NOLINT(google-explicit-constructor)
+    {
+        return mHandle;
+    }
+
+private:
+    NATIVE_HANDLE_DECLARE_STORAGE(mStorage, 0, 0);
+    hidl_handle mHandle;
+};
+
+class FenceHandle {
+public:
+    FenceHandle(int fd, bool owned) : mOwned(owned) {
+        native_handle_t* handle;
+        if (fd >= 0) {
+            handle = native_handle_init(mStorage, 1, 0);
+            handle->data[0] = fd;
+        } else {
+            // nullptr is not a valid handle to HIDL
+            handle = native_handle_init(mStorage, 0, 0);
+        }
+        mHandle = handle;
+    }
+
+    ~FenceHandle() {
+        if (mOwned) {
+            native_handle_close(mHandle);
+        }
+    }
+
+    operator const hidl_handle&() const // NOLINT(google-explicit-constructor)
+    {
+        return mHandle;
+    }
+
+private:
+    bool mOwned;
+    NATIVE_HANDLE_DECLARE_STORAGE(mStorage, 1, 0);
+    hidl_handle mHandle;
+};
+
+// assume NO_RESOURCES when Status::isOk returns false
+constexpr Error kDefaultError = Error::NO_RESOURCES;
+constexpr V2_4::Error kDefaultError_2_4 = static_cast<V2_4::Error>(kDefaultError);
+
+template <typename T, typename U>
+T unwrapRet(Return<T>& ret, const U& default_val) {
+    return (ret.isOk()) ? static_cast<T>(ret) : static_cast<T>(default_val);
+}
+
+Error unwrapRet(Return<Error>& ret) {
+    return unwrapRet(ret, kDefaultError);
+}
+
+template <typename To, typename From>
+To translate(From x) {
+    return static_cast<To>(x);
+}
+
+template <typename To, typename From>
+std::vector<To> translate(const hidl_vec<From>& in) {
+    std::vector<To> out;
+    out.reserve(in.size());
+    std::transform(in.begin(), in.end(), std::back_inserter(out),
+                   [](From x) { return translate<To>(x); });
+    return out;
+}
+
+} // anonymous namespace
+
+HidlComposer::HidlComposer(const std::string& serviceName) : mWriter(kWriterInitialSize) {
+    mComposer = V2_1::IComposer::getService(serviceName);
+
+    if (mComposer == nullptr) {
+        LOG_ALWAYS_FATAL("failed to get hwcomposer service");
+    }
+
+    if (sp<IComposer> composer_2_4 = IComposer::castFrom(mComposer)) {
+        composer_2_4->createClient_2_4([&](const auto& tmpError, const auto& tmpClient) {
+            if (tmpError == V2_4::Error::NONE) {
+                mClient = tmpClient;
+                mClient_2_2 = tmpClient;
+                mClient_2_3 = tmpClient;
+                mClient_2_4 = tmpClient;
+            }
+        });
+    } else if (sp<V2_3::IComposer> composer_2_3 = V2_3::IComposer::castFrom(mComposer)) {
+        composer_2_3->createClient_2_3([&](const auto& tmpError, const auto& tmpClient) {
+            if (tmpError == Error::NONE) {
+                mClient = tmpClient;
+                mClient_2_2 = tmpClient;
+                mClient_2_3 = tmpClient;
+            }
+        });
+    } else {
+        mComposer->createClient([&](const auto& tmpError, const auto& tmpClient) {
+            if (tmpError != Error::NONE) {
+                return;
+            }
+
+            mClient = tmpClient;
+            if (sp<V2_2::IComposer> composer_2_2 = V2_2::IComposer::castFrom(mComposer)) {
+                mClient_2_2 = V2_2::IComposerClient::castFrom(mClient);
+                LOG_ALWAYS_FATAL_IF(mClient_2_2 == nullptr,
+                                    "IComposer 2.2 did not return IComposerClient 2.2");
+            }
+        });
+    }
+
+    if (mClient == nullptr) {
+        LOG_ALWAYS_FATAL("failed to create composer client");
+    }
+}
+
+bool HidlComposer::isSupported(OptionalFeature feature) const {
+    switch (feature) {
+        case OptionalFeature::RefreshRateSwitching:
+            return mClient_2_4 != nullptr;
+        case OptionalFeature::ExpectedPresentTime:
+        case OptionalFeature::DisplayBrightnessCommand:
+        case OptionalFeature::KernelIdleTimer:
+        case OptionalFeature::PhysicalDisplayOrientation:
+            return false;
+    }
+}
+
+std::vector<Capability> HidlComposer::getCapabilities() {
+    std::vector<Capability> capabilities;
+    mComposer->getCapabilities([&](const auto& tmpCapabilities) {
+        capabilities = translate<Capability>(tmpCapabilities);
+    });
+    return capabilities;
+}
+
+std::string HidlComposer::dumpDebugInfo() {
+    std::string info;
+    mComposer->dumpDebugInfo([&](const auto& tmpInfo) { info = tmpInfo.c_str(); });
+
+    return info;
+}
+
+void HidlComposer::registerCallback(const sp<IComposerCallback>& callback) {
+    android::hardware::setMinSchedulerPolicy(callback, SCHED_FIFO, 2);
+
+    auto ret = [&]() {
+        if (mClient_2_4) {
+            return mClient_2_4->registerCallback_2_4(callback);
+        }
+        return mClient->registerCallback(callback);
+    }();
+    if (!ret.isOk()) {
+        ALOGE("failed to register IComposerCallback");
+    }
+}
+
+void HidlComposer::resetCommands() {
+    mWriter.reset();
+}
+
+Error HidlComposer::executeCommands() {
+    return execute();
+}
+
+uint32_t HidlComposer::getMaxVirtualDisplayCount() {
+    auto ret = mClient->getMaxVirtualDisplayCount();
+    return unwrapRet(ret, 0);
+}
+
+Error HidlComposer::createVirtualDisplay(uint32_t width, uint32_t height, PixelFormat* format,
+                                         Display* outDisplay) {
+    const uint32_t bufferSlotCount = 1;
+    Error error = kDefaultError;
+    if (mClient_2_2) {
+        mClient_2_2->createVirtualDisplay_2_2(width, height,
+                                              static_cast<types::V1_1::PixelFormat>(*format),
+                                              bufferSlotCount,
+                                              [&](const auto& tmpError, const auto& tmpDisplay,
+                                                  const auto& tmpFormat) {
+                                                  error = tmpError;
+                                                  if (error != Error::NONE) {
+                                                      return;
+                                                  }
+
+                                                  *outDisplay = tmpDisplay;
+                                                  *format = static_cast<types::V1_2::PixelFormat>(
+                                                          tmpFormat);
+                                              });
+    } else {
+        mClient->createVirtualDisplay(width, height, static_cast<types::V1_0::PixelFormat>(*format),
+                                      bufferSlotCount,
+                                      [&](const auto& tmpError, const auto& tmpDisplay,
+                                          const auto& tmpFormat) {
+                                          error = tmpError;
+                                          if (error != Error::NONE) {
+                                              return;
+                                          }
+
+                                          *outDisplay = tmpDisplay;
+                                          *format = static_cast<PixelFormat>(tmpFormat);
+                                      });
+    }
+
+    return error;
+}
+
+Error HidlComposer::destroyVirtualDisplay(Display display) {
+    auto ret = mClient->destroyVirtualDisplay(display);
+    return unwrapRet(ret);
+}
+
+Error HidlComposer::acceptDisplayChanges(Display display) {
+    mWriter.selectDisplay(display);
+    mWriter.acceptDisplayChanges();
+    return Error::NONE;
+}
+
+Error HidlComposer::createLayer(Display display, Layer* outLayer) {
+    Error error = kDefaultError;
+    mClient->createLayer(display, kMaxLayerBufferCount,
+                         [&](const auto& tmpError, const auto& tmpLayer) {
+                             error = tmpError;
+                             if (error != Error::NONE) {
+                                 return;
+                             }
+
+                             *outLayer = tmpLayer;
+                         });
+
+    return error;
+}
+
+Error HidlComposer::destroyLayer(Display display, Layer layer) {
+    auto ret = mClient->destroyLayer(display, layer);
+    return unwrapRet(ret);
+}
+
+Error HidlComposer::getActiveConfig(Display display, Config* outConfig) {
+    Error error = kDefaultError;
+    mClient->getActiveConfig(display, [&](const auto& tmpError, const auto& tmpConfig) {
+        error = tmpError;
+        if (error != Error::NONE) {
+            return;
+        }
+
+        *outConfig = tmpConfig;
+    });
+
+    return error;
+}
+
+Error HidlComposer::getChangedCompositionTypes(
+        Display display, std::vector<Layer>* outLayers,
+        std::vector<aidl::android::hardware::graphics::composer3::Composition>* outTypes) {
+    mReader.takeChangedCompositionTypes(display, outLayers, outTypes);
+    return Error::NONE;
+}
+
+Error HidlComposer::getColorModes(Display display, std::vector<ColorMode>* outModes) {
+    Error error = kDefaultError;
+
+    if (mClient_2_3) {
+        mClient_2_3->getColorModes_2_3(display, [&](const auto& tmpError, const auto& tmpModes) {
+            error = tmpError;
+            if (error != Error::NONE) {
+                return;
+            }
+
+            *outModes = tmpModes;
+        });
+    } else if (mClient_2_2) {
+        mClient_2_2->getColorModes_2_2(display, [&](const auto& tmpError, const auto& tmpModes) {
+            error = tmpError;
+            if (error != Error::NONE) {
+                return;
+            }
+
+            for (types::V1_1::ColorMode colorMode : tmpModes) {
+                outModes->push_back(static_cast<ColorMode>(colorMode));
+            }
+        });
+    } else {
+        mClient->getColorModes(display, [&](const auto& tmpError, const auto& tmpModes) {
+            error = tmpError;
+            if (error != Error::NONE) {
+                return;
+            }
+            for (types::V1_0::ColorMode colorMode : tmpModes) {
+                outModes->push_back(static_cast<ColorMode>(colorMode));
+            }
+        });
+    }
+
+    return error;
+}
+
+Error HidlComposer::getDisplayAttribute(Display display, Config config,
+                                        IComposerClient::Attribute attribute, int32_t* outValue) {
+    Error error = kDefaultError;
+    if (mClient_2_4) {
+        mClient_2_4->getDisplayAttribute_2_4(display, config, attribute,
+                                             [&](const auto& tmpError, const auto& tmpValue) {
+                                                 error = static_cast<Error>(tmpError);
+                                                 if (error != Error::NONE) {
+                                                     return;
+                                                 }
+
+                                                 *outValue = tmpValue;
+                                             });
+    } else {
+        mClient->getDisplayAttribute(display, config,
+                                     static_cast<V2_1::IComposerClient::Attribute>(attribute),
+                                     [&](const auto& tmpError, const auto& tmpValue) {
+                                         error = tmpError;
+                                         if (error != Error::NONE) {
+                                             return;
+                                         }
+
+                                         *outValue = tmpValue;
+                                     });
+    }
+
+    return error;
+}
+
+Error HidlComposer::getDisplayConfigs(Display display, std::vector<Config>* outConfigs) {
+    Error error = kDefaultError;
+    mClient->getDisplayConfigs(display, [&](const auto& tmpError, const auto& tmpConfigs) {
+        error = tmpError;
+        if (error != Error::NONE) {
+            return;
+        }
+
+        *outConfigs = tmpConfigs;
+    });
+
+    return error;
+}
+
+Error HidlComposer::getDisplayName(Display display, std::string* outName) {
+    Error error = kDefaultError;
+    mClient->getDisplayName(display, [&](const auto& tmpError, const auto& tmpName) {
+        error = tmpError;
+        if (error != Error::NONE) {
+            return;
+        }
+
+        *outName = tmpName.c_str();
+    });
+
+    return error;
+}
+
+Error HidlComposer::getDisplayRequests(Display display, uint32_t* outDisplayRequestMask,
+                                       std::vector<Layer>* outLayers,
+                                       std::vector<uint32_t>* outLayerRequestMasks) {
+    mReader.takeDisplayRequests(display, outDisplayRequestMask, outLayers, outLayerRequestMasks);
+    return Error::NONE;
+}
+
+Error HidlComposer::getDozeSupport(Display display, bool* outSupport) {
+    Error error = kDefaultError;
+    mClient->getDozeSupport(display, [&](const auto& tmpError, const auto& tmpSupport) {
+        error = tmpError;
+        if (error != Error::NONE) {
+            return;
+        }
+
+        *outSupport = tmpSupport;
+    });
+
+    return error;
+}
+
+Error HidlComposer::hasDisplayIdleTimerCapability(Display, bool*) {
+    LOG_ALWAYS_FATAL("hasDisplayIdleTimerCapability should have never been called on this as "
+                     "OptionalFeature::KernelIdleTimer is not supported on HIDL");
+}
+
+Error HidlComposer::getHdrCapabilities(Display display, std::vector<Hdr>* outTypes,
+                                       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& tmpMaxLuminance,
+                                                const auto& tmpMaxAverageLuminance,
+                                                const auto& tmpMinLuminance) {
+                                                error = tmpError;
+                                                if (error != Error::NONE) {
+                                                    return;
+                                                }
+
+                                                *outTypes = tmpTypes;
+                                                *outMaxLuminance = tmpMaxLuminance;
+                                                *outMaxAverageLuminance = tmpMaxAverageLuminance;
+                                                *outMinLuminance = tmpMinLuminance;
+                                            });
+    } else {
+        mClient->getHdrCapabilities(display,
+                                    [&](const auto& tmpError, const auto& tmpTypes,
+                                        const auto& tmpMaxLuminance,
+                                        const auto& tmpMaxAverageLuminance,
+                                        const auto& tmpMinLuminance) {
+                                        error = tmpError;
+                                        if (error != Error::NONE) {
+                                            return;
+                                        }
+
+                                        outTypes->clear();
+                                        for (auto type : tmpTypes) {
+                                            outTypes->push_back(static_cast<Hdr>(type));
+                                        }
+
+                                        *outMaxLuminance = tmpMaxLuminance;
+                                        *outMaxAverageLuminance = tmpMaxAverageLuminance;
+                                        *outMinLuminance = tmpMinLuminance;
+                                    });
+    }
+
+    return error;
+}
+
+Error HidlComposer::getReleaseFences(Display display, std::vector<Layer>* outLayers,
+                                     std::vector<int>* outReleaseFences) {
+    mReader.takeReleaseFences(display, outLayers, outReleaseFences);
+    return Error::NONE;
+}
+
+Error HidlComposer::presentDisplay(Display display, int* outPresentFence) {
+    ATRACE_NAME("HwcPresentDisplay");
+    mWriter.selectDisplay(display);
+    mWriter.presentDisplay();
+
+    Error error = execute();
+    if (error != Error::NONE) {
+        return error;
+    }
+
+    mReader.takePresentFence(display, outPresentFence);
+
+    return Error::NONE;
+}
+
+Error HidlComposer::setActiveConfig(Display display, Config config) {
+    auto ret = mClient->setActiveConfig(display, config);
+    return unwrapRet(ret);
+}
+
+Error HidlComposer::setClientTarget(Display display, uint32_t slot, const sp<GraphicBuffer>& target,
+                                    int acquireFence, Dataspace dataspace,
+                                    const std::vector<IComposerClient::Rect>& damage) {
+    mWriter.selectDisplay(display);
+
+    const native_handle_t* handle = nullptr;
+    if (target.get()) {
+        handle = target->getNativeBuffer()->handle;
+    }
+
+    mWriter.setClientTarget(slot, handle, acquireFence, dataspace, damage);
+    return Error::NONE;
+}
+
+Error HidlComposer::setColorMode(Display display, ColorMode mode, RenderIntent renderIntent) {
+    hardware::Return<Error> ret(kDefaultError);
+    if (mClient_2_3) {
+        ret = mClient_2_3->setColorMode_2_3(display, mode, renderIntent);
+    } else if (mClient_2_2) {
+        ret = mClient_2_2->setColorMode_2_2(display, static_cast<types::V1_1::ColorMode>(mode),
+                                            renderIntent);
+    } else {
+        ret = mClient->setColorMode(display, static_cast<types::V1_0::ColorMode>(mode));
+    }
+    return unwrapRet(ret);
+}
+
+Error HidlComposer::setColorTransform(Display display, const float* matrix) {
+    mWriter.selectDisplay(display);
+    const bool isIdentity = (mat4(matrix) == mat4());
+    mWriter.setColorTransform(matrix,
+                              isIdentity ? ColorTransform::IDENTITY
+                                         : ColorTransform::ARBITRARY_MATRIX);
+    return Error::NONE;
+}
+
+Error HidlComposer::setOutputBuffer(Display display, const native_handle_t* buffer,
+                                    int releaseFence) {
+    mWriter.selectDisplay(display);
+    mWriter.setOutputBuffer(0, buffer, dup(releaseFence));
+    return Error::NONE;
+}
+
+Error HidlComposer::setPowerMode(Display display, IComposerClient::PowerMode mode) {
+    Return<Error> ret(Error::UNSUPPORTED);
+    if (mClient_2_2) {
+        ret = mClient_2_2->setPowerMode_2_2(display, mode);
+    } else if (mode != IComposerClient::PowerMode::ON_SUSPEND) {
+        ret = mClient->setPowerMode(display, static_cast<V2_1::IComposerClient::PowerMode>(mode));
+    }
+
+    return unwrapRet(ret);
+}
+
+Error HidlComposer::setVsyncEnabled(Display display, IComposerClient::Vsync enabled) {
+    auto ret = mClient->setVsyncEnabled(display, enabled);
+    return unwrapRet(ret);
+}
+
+Error HidlComposer::setClientTargetSlotCount(Display display) {
+    const uint32_t bufferSlotCount = BufferQueue::NUM_BUFFER_SLOTS;
+    auto ret = mClient->setClientTargetSlotCount(display, bufferSlotCount);
+    return unwrapRet(ret);
+}
+
+Error HidlComposer::validateDisplay(Display display, nsecs_t /*expectedPresentTime*/,
+                                    uint32_t* outNumTypes, uint32_t* outNumRequests) {
+    ATRACE_NAME("HwcValidateDisplay");
+    mWriter.selectDisplay(display);
+    mWriter.validateDisplay();
+
+    Error error = execute();
+    if (error != Error::NONE) {
+        return error;
+    }
+
+    mReader.hasChanges(display, outNumTypes, outNumRequests);
+
+    return Error::NONE;
+}
+
+Error HidlComposer::presentOrValidateDisplay(Display display, nsecs_t /*expectedPresentTime*/,
+                                             uint32_t* outNumTypes, uint32_t* outNumRequests,
+                                             int* outPresentFence, uint32_t* state) {
+    ATRACE_NAME("HwcPresentOrValidateDisplay");
+    mWriter.selectDisplay(display);
+    mWriter.presentOrvalidateDisplay();
+
+    Error error = execute();
+    if (error != Error::NONE) {
+        return error;
+    }
+
+    mReader.takePresentOrValidateStage(display, state);
+
+    if (*state == 1) { // Present succeeded
+        mReader.takePresentFence(display, outPresentFence);
+    }
+
+    if (*state == 0) { // Validate succeeded.
+        mReader.hasChanges(display, outNumTypes, outNumRequests);
+    }
+
+    return Error::NONE;
+}
+
+Error HidlComposer::setCursorPosition(Display display, Layer layer, int32_t x, int32_t y) {
+    mWriter.selectDisplay(display);
+    mWriter.selectLayer(layer);
+    mWriter.setLayerCursorPosition(x, y);
+    return Error::NONE;
+}
+
+Error HidlComposer::setLayerBuffer(Display display, Layer layer, uint32_t slot,
+                                   const sp<GraphicBuffer>& buffer, int acquireFence) {
+    mWriter.selectDisplay(display);
+    mWriter.selectLayer(layer);
+
+    const native_handle_t* handle = nullptr;
+    if (buffer.get()) {
+        handle = buffer->getNativeBuffer()->handle;
+    }
+
+    mWriter.setLayerBuffer(slot, handle, acquireFence);
+    return Error::NONE;
+}
+
+Error HidlComposer::setLayerSurfaceDamage(Display display, Layer layer,
+                                          const std::vector<IComposerClient::Rect>& damage) {
+    mWriter.selectDisplay(display);
+    mWriter.selectLayer(layer);
+    mWriter.setLayerSurfaceDamage(damage);
+    return Error::NONE;
+}
+
+Error HidlComposer::setLayerBlendMode(Display display, Layer layer,
+                                      IComposerClient::BlendMode mode) {
+    mWriter.selectDisplay(display);
+    mWriter.selectLayer(layer);
+    mWriter.setLayerBlendMode(mode);
+    return Error::NONE;
+}
+
+static IComposerClient::Color to_hidl_type(
+        aidl::android::hardware::graphics::composer3::Color color) {
+    const auto floatColorToUint8Clamped = [](float val) -> uint8_t {
+        const auto intVal = static_cast<uint64_t>(std::round(255.0f * val));
+        const auto minVal = static_cast<uint64_t>(0);
+        const auto maxVal = static_cast<uint64_t>(255);
+        return std::clamp(intVal, minVal, maxVal);
+    };
+
+    return IComposerClient::Color{
+            floatColorToUint8Clamped(color.r),
+            floatColorToUint8Clamped(color.g),
+            floatColorToUint8Clamped(color.b),
+            floatColorToUint8Clamped(color.a),
+    };
+}
+
+Error HidlComposer::setLayerColor(
+        Display display, Layer layer,
+        const aidl::android::hardware::graphics::composer3::Color& color) {
+    mWriter.selectDisplay(display);
+    mWriter.selectLayer(layer);
+    mWriter.setLayerColor(to_hidl_type(color));
+    return Error::NONE;
+}
+
+static IComposerClient::Composition to_hidl_type(
+        aidl::android::hardware::graphics::composer3::Composition type) {
+    LOG_ALWAYS_FATAL_IF(static_cast<int32_t>(type) >
+                                static_cast<int32_t>(IComposerClient::Composition::SIDEBAND),
+                        "Trying to use %s, which is not supported by HidlComposer!",
+                        android::to_string(type).c_str());
+
+    return static_cast<IComposerClient::Composition>(type);
+}
+
+Error HidlComposer::setLayerCompositionType(
+        Display display, Layer layer,
+        aidl::android::hardware::graphics::composer3::Composition type) {
+    mWriter.selectDisplay(display);
+    mWriter.selectLayer(layer);
+    mWriter.setLayerCompositionType(to_hidl_type(type));
+    return Error::NONE;
+}
+
+Error HidlComposer::setLayerDataspace(Display display, Layer layer, Dataspace dataspace) {
+    mWriter.selectDisplay(display);
+    mWriter.selectLayer(layer);
+    mWriter.setLayerDataspace(dataspace);
+    return Error::NONE;
+}
+
+Error HidlComposer::setLayerDisplayFrame(Display display, Layer layer,
+                                         const IComposerClient::Rect& frame) {
+    mWriter.selectDisplay(display);
+    mWriter.selectLayer(layer);
+    mWriter.setLayerDisplayFrame(frame);
+    return Error::NONE;
+}
+
+Error HidlComposer::setLayerPlaneAlpha(Display display, Layer layer, float alpha) {
+    mWriter.selectDisplay(display);
+    mWriter.selectLayer(layer);
+    mWriter.setLayerPlaneAlpha(alpha);
+    return Error::NONE;
+}
+
+Error HidlComposer::setLayerSidebandStream(Display display, Layer layer,
+                                           const native_handle_t* stream) {
+    mWriter.selectDisplay(display);
+    mWriter.selectLayer(layer);
+    mWriter.setLayerSidebandStream(stream);
+    return Error::NONE;
+}
+
+Error HidlComposer::setLayerSourceCrop(Display display, Layer layer,
+                                       const IComposerClient::FRect& crop) {
+    mWriter.selectDisplay(display);
+    mWriter.selectLayer(layer);
+    mWriter.setLayerSourceCrop(crop);
+    return Error::NONE;
+}
+
+Error HidlComposer::setLayerTransform(Display display, Layer layer, Transform transform) {
+    mWriter.selectDisplay(display);
+    mWriter.selectLayer(layer);
+    mWriter.setLayerTransform(transform);
+    return Error::NONE;
+}
+
+Error HidlComposer::setLayerVisibleRegion(Display display, Layer layer,
+                                          const std::vector<IComposerClient::Rect>& visible) {
+    mWriter.selectDisplay(display);
+    mWriter.selectLayer(layer);
+    mWriter.setLayerVisibleRegion(visible);
+    return Error::NONE;
+}
+
+Error HidlComposer::setLayerZOrder(Display display, Layer layer, uint32_t z) {
+    mWriter.selectDisplay(display);
+    mWriter.selectLayer(layer);
+    mWriter.setLayerZOrder(z);
+    return Error::NONE;
+}
+
+Error HidlComposer::execute() {
+    // prepare input command queue
+    bool queueChanged = false;
+    uint32_t commandLength = 0;
+    hidl_vec<hidl_handle> commandHandles;
+    if (!mWriter.writeQueue(&queueChanged, &commandLength, &commandHandles)) {
+        mWriter.reset();
+        return Error::NO_RESOURCES;
+    }
+
+    // set up new input command queue if necessary
+    if (queueChanged) {
+        auto ret = mClient->setInputCommandQueue(*mWriter.getMQDescriptor());
+        auto error = unwrapRet(ret);
+        if (error != Error::NONE) {
+            mWriter.reset();
+            return error;
+        }
+    }
+
+    if (commandLength == 0) {
+        mWriter.reset();
+        return Error::NONE;
+    }
+
+    Error error = kDefaultError;
+    hardware::Return<void> ret;
+    auto hidl_callback = [&](const auto& tmpError, const auto& tmpOutChanged,
+                             const auto& tmpOutLength, const auto& tmpOutHandles) {
+        error = tmpError;
+
+        // set up new output command queue if necessary
+        if (error == Error::NONE && tmpOutChanged) {
+            error = kDefaultError;
+            mClient->getOutputCommandQueue([&](const auto& tmpError, const auto& tmpDescriptor) {
+                error = tmpError;
+                if (error != Error::NONE) {
+                    return;
+                }
+
+                mReader.setMQDescriptor(tmpDescriptor);
+            });
+        }
+
+        if (error != Error::NONE) {
+            return;
+        }
+
+        if (mReader.readQueue(tmpOutLength, tmpOutHandles)) {
+            error = mReader.parse();
+            mReader.reset();
+        } else {
+            error = Error::NO_RESOURCES;
+        }
+    };
+    if (mClient_2_2) {
+        ret = mClient_2_2->executeCommands_2_2(commandLength, commandHandles, hidl_callback);
+    } else {
+        ret = mClient->executeCommands(commandLength, commandHandles, hidl_callback);
+    }
+    // executeCommands can fail because of out-of-fd and we do not want to
+    // abort() in that case
+    if (!ret.isOk()) {
+        ALOGE("executeCommands failed because of %s", ret.description().c_str());
+    }
+
+    if (error == Error::NONE) {
+        std::vector<CommandReader::CommandError> commandErrors = mReader.takeErrors();
+
+        for (const auto& cmdErr : commandErrors) {
+            auto command =
+                    static_cast<IComposerClient::Command>(mWriter.getCommand(cmdErr.location));
+
+            if (command == IComposerClient::Command::VALIDATE_DISPLAY ||
+                command == IComposerClient::Command::PRESENT_DISPLAY ||
+                command == IComposerClient::Command::PRESENT_OR_VALIDATE_DISPLAY) {
+                error = cmdErr.error;
+            } else {
+                ALOGW("command 0x%x generated error %d", command, cmdErr.error);
+            }
+        }
+    }
+
+    mWriter.reset();
+
+    return error;
+}
+
+// Composer HAL 2.2
+
+Error HidlComposer::setLayerPerFrameMetadata(
+        Display display, Layer layer,
+        const std::vector<IComposerClient::PerFrameMetadata>& perFrameMetadatas) {
+    if (!mClient_2_2) {
+        return Error::UNSUPPORTED;
+    }
+
+    mWriter.selectDisplay(display);
+    mWriter.selectLayer(layer);
+    mWriter.setLayerPerFrameMetadata(perFrameMetadatas);
+    return Error::NONE;
+}
+
+std::vector<IComposerClient::PerFrameMetadataKey> HidlComposer::getPerFrameMetadataKeys(
+        Display display) {
+    std::vector<IComposerClient::PerFrameMetadataKey> keys;
+    if (!mClient_2_2) {
+        return keys;
+    }
+
+    Error error = kDefaultError;
+    if (mClient_2_3) {
+        mClient_2_3->getPerFrameMetadataKeys_2_3(display,
+                                                 [&](const auto& tmpError, const auto& tmpKeys) {
+                                                     error = tmpError;
+                                                     if (error != Error::NONE) {
+                                                         ALOGW("getPerFrameMetadataKeys failed "
+                                                               "with %d",
+                                                               tmpError);
+                                                         return;
+                                                     }
+                                                     keys = tmpKeys;
+                                                 });
+    } else {
+        mClient_2_2
+                ->getPerFrameMetadataKeys(display, [&](const auto& tmpError, const auto& tmpKeys) {
+                    error = tmpError;
+                    if (error != Error::NONE) {
+                        ALOGW("getPerFrameMetadataKeys failed with %d", tmpError);
+                        return;
+                    }
+
+                    keys.clear();
+                    for (auto key : tmpKeys) {
+                        keys.push_back(static_cast<IComposerClient::PerFrameMetadataKey>(key));
+                    }
+                });
+    }
+
+    return keys;
+}
+
+Error HidlComposer::getRenderIntents(Display display, ColorMode colorMode,
+                                     std::vector<RenderIntent>* outRenderIntents) {
+    if (!mClient_2_2) {
+        outRenderIntents->push_back(RenderIntent::COLORIMETRIC);
+        return Error::NONE;
+    }
+
+    Error error = kDefaultError;
+
+    auto getRenderIntentsLambda = [&](const auto& tmpError, const auto& tmpKeys) {
+        error = tmpError;
+        if (error != Error::NONE) {
+            return;
+        }
+
+        *outRenderIntents = tmpKeys;
+    };
+
+    if (mClient_2_3) {
+        mClient_2_3->getRenderIntents_2_3(display, colorMode, getRenderIntentsLambda);
+    } else {
+        mClient_2_2->getRenderIntents(display, static_cast<types::V1_1::ColorMode>(colorMode),
+                                      getRenderIntentsLambda);
+    }
+
+    return error;
+}
+
+Error HidlComposer::getDataspaceSaturationMatrix(Dataspace dataspace, mat4* outMatrix) {
+    if (!mClient_2_2) {
+        *outMatrix = mat4();
+        return Error::NONE;
+    }
+
+    Error error = kDefaultError;
+    mClient_2_2->getDataspaceSaturationMatrix(static_cast<types::V1_1::Dataspace>(dataspace),
+                                              [&](const auto& tmpError, const auto& tmpMatrix) {
+                                                  error = tmpError;
+                                                  if (error != Error::NONE) {
+                                                      return;
+                                                  }
+                                                  *outMatrix = mat4(tmpMatrix.data());
+                                              });
+
+    return error;
+}
+
+// Composer HAL 2.3
+
+Error HidlComposer::getDisplayIdentificationData(Display display, uint8_t* outPort,
+                                                 std::vector<uint8_t>* outData) {
+    if (!mClient_2_3) {
+        return Error::UNSUPPORTED;
+    }
+
+    Error error = kDefaultError;
+    mClient_2_3->getDisplayIdentificationData(display,
+                                              [&](const auto& tmpError, const auto& tmpPort,
+                                                  const auto& tmpData) {
+                                                  error = tmpError;
+                                                  if (error != Error::NONE) {
+                                                      return;
+                                                  }
+
+                                                  *outPort = tmpPort;
+                                                  *outData = tmpData;
+                                              });
+
+    return error;
+}
+
+Error HidlComposer::setLayerColorTransform(Display display, Layer layer, const float* matrix) {
+    if (!mClient_2_3) {
+        return Error::UNSUPPORTED;
+    }
+
+    mWriter.selectDisplay(display);
+    mWriter.selectLayer(layer);
+    mWriter.setLayerColorTransform(matrix);
+    return Error::NONE;
+}
+
+Error HidlComposer::getDisplayedContentSamplingAttributes(Display display, PixelFormat* outFormat,
+                                                          Dataspace* outDataspace,
+                                                          uint8_t* outComponentMask) {
+    if (!outFormat || !outDataspace || !outComponentMask) {
+        return Error::BAD_PARAMETER;
+    }
+    if (!mClient_2_3) {
+        return Error::UNSUPPORTED;
+    }
+    Error error = kDefaultError;
+    mClient_2_3->getDisplayedContentSamplingAttributes(display,
+                                                       [&](const auto tmpError,
+                                                           const auto& tmpFormat,
+                                                           const auto& tmpDataspace,
+                                                           const auto& tmpComponentMask) {
+                                                           error = tmpError;
+                                                           if (error == Error::NONE) {
+                                                               *outFormat = tmpFormat;
+                                                               *outDataspace = tmpDataspace;
+                                                               *outComponentMask =
+                                                                       static_cast<uint8_t>(
+                                                                               tmpComponentMask);
+                                                           }
+                                                       });
+    return error;
+}
+
+Error HidlComposer::setDisplayContentSamplingEnabled(Display display, bool enabled,
+                                                     uint8_t componentMask, uint64_t maxFrames) {
+    if (!mClient_2_3) {
+        return Error::UNSUPPORTED;
+    }
+
+    auto enable = enabled ? V2_3::IComposerClient::DisplayedContentSampling::ENABLE
+                          : V2_3::IComposerClient::DisplayedContentSampling::DISABLE;
+    return mClient_2_3->setDisplayedContentSamplingEnabled(display, enable, componentMask,
+                                                           maxFrames);
+}
+
+Error HidlComposer::getDisplayedContentSample(Display display, uint64_t maxFrames,
+                                              uint64_t timestamp, DisplayedFrameStats* outStats) {
+    if (!outStats) {
+        return Error::BAD_PARAMETER;
+    }
+    if (!mClient_2_3) {
+        return Error::UNSUPPORTED;
+    }
+    Error error = kDefaultError;
+    mClient_2_3->getDisplayedContentSample(display, maxFrames, timestamp,
+                                           [&](const auto tmpError, auto tmpNumFrames,
+                                               const auto& tmpSamples0, const auto& tmpSamples1,
+                                               const auto& tmpSamples2, const auto& tmpSamples3) {
+                                               error = tmpError;
+                                               if (error == Error::NONE) {
+                                                   outStats->numFrames = tmpNumFrames;
+                                                   outStats->component_0_sample = tmpSamples0;
+                                                   outStats->component_1_sample = tmpSamples1;
+                                                   outStats->component_2_sample = tmpSamples2;
+                                                   outStats->component_3_sample = tmpSamples3;
+                                               }
+                                           });
+    return error;
+}
+
+Error HidlComposer::setLayerPerFrameMetadataBlobs(
+        Display display, Layer layer,
+        const std::vector<IComposerClient::PerFrameMetadataBlob>& metadata) {
+    if (!mClient_2_3) {
+        return Error::UNSUPPORTED;
+    }
+
+    mWriter.selectDisplay(display);
+    mWriter.selectLayer(layer);
+    mWriter.setLayerPerFrameMetadataBlobs(metadata);
+    return Error::NONE;
+}
+
+Error HidlComposer::setDisplayBrightness(Display display, float brightness,
+                                         const DisplayBrightnessOptions&) {
+    if (!mClient_2_3) {
+        return Error::UNSUPPORTED;
+    }
+    return mClient_2_3->setDisplayBrightness(display, brightness);
+}
+
+// Composer HAL 2.4
+
+Error HidlComposer::getDisplayCapabilities(Display display,
+                                           std::vector<DisplayCapability>* outCapabilities) {
+    if (!mClient_2_3) {
+        return Error::UNSUPPORTED;
+    }
+
+    V2_4::Error error = kDefaultError_2_4;
+    if (mClient_2_4) {
+        mClient_2_4->getDisplayCapabilities_2_4(display,
+                                                [&](const auto& tmpError, const auto& tmpCaps) {
+                                                    error = tmpError;
+                                                    if (error != V2_4::Error::NONE) {
+                                                        return;
+                                                    }
+                                                    *outCapabilities =
+                                                            translate<DisplayCapability>(tmpCaps);
+                                                });
+    } else {
+        mClient_2_3
+                ->getDisplayCapabilities(display, [&](const auto& tmpError, const auto& tmpCaps) {
+                    error = static_cast<V2_4::Error>(tmpError);
+                    if (error != V2_4::Error::NONE) {
+                        return;
+                    }
+
+                    *outCapabilities = translate<DisplayCapability>(tmpCaps);
+                });
+    }
+
+    return static_cast<Error>(error);
+}
+
+V2_4::Error HidlComposer::getDisplayConnectionType(
+        Display display, IComposerClient::DisplayConnectionType* outType) {
+    using Error = V2_4::Error;
+    if (!mClient_2_4) {
+        return Error::UNSUPPORTED;
+    }
+
+    Error error = kDefaultError_2_4;
+    mClient_2_4->getDisplayConnectionType(display, [&](const auto& tmpError, const auto& tmpType) {
+        error = tmpError;
+        if (error != V2_4::Error::NONE) {
+            return;
+        }
+
+        *outType = tmpType;
+    });
+
+    return error;
+}
+
+V2_4::Error HidlComposer::getDisplayVsyncPeriod(Display display, VsyncPeriodNanos* outVsyncPeriod) {
+    using Error = V2_4::Error;
+    if (!mClient_2_4) {
+        return Error::UNSUPPORTED;
+    }
+
+    Error error = kDefaultError_2_4;
+    mClient_2_4->getDisplayVsyncPeriod(display,
+                                       [&](const auto& tmpError, const auto& tmpVsyncPeriod) {
+                                           error = tmpError;
+                                           if (error != Error::NONE) {
+                                               return;
+                                           }
+
+                                           *outVsyncPeriod = tmpVsyncPeriod;
+                                       });
+
+    return error;
+}
+
+V2_4::Error HidlComposer::setActiveConfigWithConstraints(
+        Display display, Config config,
+        const IComposerClient::VsyncPeriodChangeConstraints& vsyncPeriodChangeConstraints,
+        VsyncPeriodChangeTimeline* outTimeline) {
+    using Error = V2_4::Error;
+    if (!mClient_2_4) {
+        return Error::UNSUPPORTED;
+    }
+
+    Error error = kDefaultError_2_4;
+    mClient_2_4->setActiveConfigWithConstraints(display, config, vsyncPeriodChangeConstraints,
+                                                [&](const auto& tmpError, const auto& tmpTimeline) {
+                                                    error = tmpError;
+                                                    if (error != Error::NONE) {
+                                                        return;
+                                                    }
+
+                                                    *outTimeline = tmpTimeline;
+                                                });
+
+    return error;
+}
+
+V2_4::Error HidlComposer::setAutoLowLatencyMode(Display display, bool on) {
+    using Error = V2_4::Error;
+    if (!mClient_2_4) {
+        return Error::UNSUPPORTED;
+    }
+
+    return mClient_2_4->setAutoLowLatencyMode(display, on);
+}
+
+V2_4::Error HidlComposer::getSupportedContentTypes(
+        Display displayId, std::vector<IComposerClient::ContentType>* outSupportedContentTypes) {
+    using Error = V2_4::Error;
+    if (!mClient_2_4) {
+        return Error::UNSUPPORTED;
+    }
+
+    Error error = kDefaultError_2_4;
+    mClient_2_4->getSupportedContentTypes(displayId,
+                                          [&](const auto& tmpError,
+                                              const auto& tmpSupportedContentTypes) {
+                                              error = tmpError;
+                                              if (error != Error::NONE) {
+                                                  return;
+                                              }
+
+                                              *outSupportedContentTypes = tmpSupportedContentTypes;
+                                          });
+    return error;
+}
+
+V2_4::Error HidlComposer::setContentType(Display display,
+                                         IComposerClient::ContentType contentType) {
+    using Error = V2_4::Error;
+    if (!mClient_2_4) {
+        return Error::UNSUPPORTED;
+    }
+
+    return mClient_2_4->setContentType(display, contentType);
+}
+
+V2_4::Error HidlComposer::setLayerGenericMetadata(Display display, Layer layer,
+                                                  const std::string& key, bool mandatory,
+                                                  const std::vector<uint8_t>& value) {
+    using Error = V2_4::Error;
+    if (!mClient_2_4) {
+        return Error::UNSUPPORTED;
+    }
+    mWriter.selectDisplay(display);
+    mWriter.selectLayer(layer);
+    mWriter.setLayerGenericMetadata(key, mandatory, value);
+    return Error::NONE;
+}
+
+V2_4::Error HidlComposer::getLayerGenericMetadataKeys(
+        std::vector<IComposerClient::LayerGenericMetadataKey>* outKeys) {
+    using Error = V2_4::Error;
+    if (!mClient_2_4) {
+        return Error::UNSUPPORTED;
+    }
+    Error error = kDefaultError_2_4;
+    mClient_2_4->getLayerGenericMetadataKeys([&](const auto& tmpError, const auto& tmpKeys) {
+        error = tmpError;
+        if (error != Error::NONE) {
+            return;
+        }
+
+        *outKeys = tmpKeys;
+    });
+    return error;
+}
+
+Error HidlComposer::setBootDisplayConfig(Display /*displayId*/, Config) {
+    return Error::UNSUPPORTED;
+}
+
+Error HidlComposer::clearBootDisplayConfig(Display /*displayId*/) {
+    return Error::UNSUPPORTED;
+}
+
+Error HidlComposer::getPreferredBootDisplayConfig(Display /*displayId*/, Config*) {
+    return Error::UNSUPPORTED;
+}
+
+Error HidlComposer::getClientTargetProperty(
+        Display display, IComposerClient::ClientTargetProperty* outClientTargetProperty,
+        float* outBrightness) {
+    mReader.takeClientTargetProperty(display, outClientTargetProperty);
+    *outBrightness = 1.f;
+    return Error::NONE;
+}
+
+Error HidlComposer::setLayerBrightness(Display, Layer, float) {
+    return Error::NONE;
+}
+
+Error HidlComposer::setLayerBlockingRegion(Display, Layer,
+                                           const std::vector<IComposerClient::Rect>&) {
+    return Error::NONE;
+}
+
+Error HidlComposer::getDisplayDecorationSupport(
+        Display,
+        std::optional<aidl::android::hardware::graphics::common::DisplayDecorationSupport>*
+                support) {
+    support->reset();
+    return Error::UNSUPPORTED;
+}
+
+Error HidlComposer::setIdleTimerEnabled(Display, std::chrono::milliseconds) {
+    LOG_ALWAYS_FATAL("setIdleTimerEnabled should have never been called on this as "
+                     "OptionalFeature::KernelIdleTimer is not supported on HIDL");
+}
+
+Error HidlComposer::getPhysicalDisplayOrientation(Display, AidlTransform*) {
+    LOG_ALWAYS_FATAL("getPhysicalDisplayOrientation should have never been called on this as "
+                     "OptionalFeature::PhysicalDisplayOrientation is not supported on HIDL");
+}
+
+void HidlComposer::registerCallback(ComposerCallback& callback) {
+    const bool vsyncSwitchingSupported =
+            isSupported(Hwc2::Composer::OptionalFeature::RefreshRateSwitching);
+
+    registerCallback(sp<ComposerCallbackBridge>::make(callback, vsyncSwitchingSupported));
+}
+
+CommandReader::~CommandReader() {
+    resetData();
+}
+
+Error CommandReader::parse() {
+    resetData();
+
+    IComposerClient::Command command;
+    uint16_t length = 0;
+
+    while (!isEmpty()) {
+        if (!beginCommand(&command, &length)) {
+            break;
+        }
+
+        bool parsed = false;
+        switch (command) {
+            case IComposerClient::Command::SELECT_DISPLAY:
+                parsed = parseSelectDisplay(length);
+                break;
+            case IComposerClient::Command::SET_ERROR:
+                parsed = parseSetError(length);
+                break;
+            case IComposerClient::Command::SET_CHANGED_COMPOSITION_TYPES:
+                parsed = parseSetChangedCompositionTypes(length);
+                break;
+            case IComposerClient::Command::SET_DISPLAY_REQUESTS:
+                parsed = parseSetDisplayRequests(length);
+                break;
+            case IComposerClient::Command::SET_PRESENT_FENCE:
+                parsed = parseSetPresentFence(length);
+                break;
+            case IComposerClient::Command::SET_RELEASE_FENCES:
+                parsed = parseSetReleaseFences(length);
+                break;
+            case IComposerClient::Command ::SET_PRESENT_OR_VALIDATE_DISPLAY_RESULT:
+                parsed = parseSetPresentOrValidateDisplayResult(length);
+                break;
+            case IComposerClient::Command::SET_CLIENT_TARGET_PROPERTY:
+                parsed = parseSetClientTargetProperty(length);
+                break;
+            default:
+                parsed = false;
+                break;
+        }
+
+        endCommand();
+
+        if (!parsed) {
+            ALOGE("failed to parse command 0x%x length %" PRIu16, command, length);
+            break;
+        }
+    }
+
+    return isEmpty() ? Error::NONE : Error::NO_RESOURCES;
+}
+
+bool CommandReader::parseSelectDisplay(uint16_t length) {
+    if (length != CommandWriterBase::kSelectDisplayLength) {
+        return false;
+    }
+
+    mCurrentReturnData = &mReturnData[read64()];
+
+    return true;
+}
+
+bool CommandReader::parseSetError(uint16_t length) {
+    if (length != CommandWriterBase::kSetErrorLength) {
+        return false;
+    }
+
+    auto location = read();
+    auto error = static_cast<Error>(readSigned());
+
+    mErrors.emplace_back(CommandError{location, error});
+
+    return true;
+}
+
+bool CommandReader::parseSetChangedCompositionTypes(uint16_t length) {
+    // (layer id, composition type) pairs
+    if (length % 3 != 0 || !mCurrentReturnData) {
+        return false;
+    }
+
+    uint32_t count = length / 3;
+    mCurrentReturnData->changedLayers.reserve(count);
+    mCurrentReturnData->compositionTypes.reserve(count);
+    while (count > 0) {
+        auto layer = read64();
+        auto type = static_cast<aidl::android::hardware::graphics::composer3::Composition>(
+                readSigned());
+
+        mCurrentReturnData->changedLayers.push_back(layer);
+        mCurrentReturnData->compositionTypes.push_back(type);
+
+        count--;
+    }
+
+    return true;
+}
+
+bool CommandReader::parseSetDisplayRequests(uint16_t length) {
+    // display requests followed by (layer id, layer requests) pairs
+    if (length % 3 != 1 || !mCurrentReturnData) {
+        return false;
+    }
+
+    mCurrentReturnData->displayRequests = read();
+
+    uint32_t count = (length - 1) / 3;
+    mCurrentReturnData->requestedLayers.reserve(count);
+    mCurrentReturnData->requestMasks.reserve(count);
+    while (count > 0) {
+        auto layer = read64();
+        auto layerRequestMask = read();
+
+        mCurrentReturnData->requestedLayers.push_back(layer);
+        mCurrentReturnData->requestMasks.push_back(layerRequestMask);
+
+        count--;
+    }
+
+    return true;
+}
+
+bool CommandReader::parseSetPresentFence(uint16_t length) {
+    if (length != CommandWriterBase::kSetPresentFenceLength || !mCurrentReturnData) {
+        return false;
+    }
+
+    if (mCurrentReturnData->presentFence >= 0) {
+        close(mCurrentReturnData->presentFence);
+    }
+    mCurrentReturnData->presentFence = readFence();
+
+    return true;
+}
+
+bool CommandReader::parseSetReleaseFences(uint16_t length) {
+    // (layer id, release fence index) pairs
+    if (length % 3 != 0 || !mCurrentReturnData) {
+        return false;
+    }
+
+    uint32_t count = length / 3;
+    mCurrentReturnData->releasedLayers.reserve(count);
+    mCurrentReturnData->releaseFences.reserve(count);
+    while (count > 0) {
+        auto layer = read64();
+        auto fence = readFence();
+
+        mCurrentReturnData->releasedLayers.push_back(layer);
+        mCurrentReturnData->releaseFences.push_back(fence);
+
+        count--;
+    }
+
+    return true;
+}
+
+bool CommandReader::parseSetPresentOrValidateDisplayResult(uint16_t length) {
+    if (length != CommandWriterBase::kPresentOrValidateDisplayResultLength || !mCurrentReturnData) {
+        return false;
+    }
+    mCurrentReturnData->presentOrValidateState = read();
+    return true;
+}
+
+bool CommandReader::parseSetClientTargetProperty(uint16_t length) {
+    if (length != CommandWriterBase::kSetClientTargetPropertyLength || !mCurrentReturnData) {
+        return false;
+    }
+    mCurrentReturnData->clientTargetProperty.pixelFormat = static_cast<PixelFormat>(readSigned());
+    mCurrentReturnData->clientTargetProperty.dataspace = static_cast<Dataspace>(readSigned());
+    return true;
+}
+
+void CommandReader::resetData() {
+    mErrors.clear();
+
+    for (auto& data : mReturnData) {
+        if (data.second.presentFence >= 0) {
+            close(data.second.presentFence);
+        }
+        for (auto fence : data.second.releaseFences) {
+            if (fence >= 0) {
+                close(fence);
+            }
+        }
+    }
+
+    mReturnData.clear();
+    mCurrentReturnData = nullptr;
+}
+
+std::vector<CommandReader::CommandError> CommandReader::takeErrors() {
+    return std::move(mErrors);
+}
+
+bool CommandReader::hasChanges(Display display, uint32_t* outNumChangedCompositionTypes,
+                               uint32_t* outNumLayerRequestMasks) const {
+    auto found = mReturnData.find(display);
+    if (found == mReturnData.end()) {
+        *outNumChangedCompositionTypes = 0;
+        *outNumLayerRequestMasks = 0;
+        return false;
+    }
+
+    const ReturnData& data = found->second;
+
+    *outNumChangedCompositionTypes = data.compositionTypes.size();
+    *outNumLayerRequestMasks = data.requestMasks.size();
+
+    return !(data.compositionTypes.empty() && data.requestMasks.empty());
+}
+
+void CommandReader::takeChangedCompositionTypes(
+        Display display, std::vector<Layer>* outLayers,
+        std::vector<aidl::android::hardware::graphics::composer3::Composition>* outTypes) {
+    auto found = mReturnData.find(display);
+    if (found == mReturnData.end()) {
+        outLayers->clear();
+        outTypes->clear();
+        return;
+    }
+
+    ReturnData& data = found->second;
+
+    *outLayers = std::move(data.changedLayers);
+    *outTypes = std::move(data.compositionTypes);
+}
+
+void CommandReader::takeDisplayRequests(Display display, uint32_t* outDisplayRequestMask,
+                                        std::vector<Layer>* outLayers,
+                                        std::vector<uint32_t>* outLayerRequestMasks) {
+    auto found = mReturnData.find(display);
+    if (found == mReturnData.end()) {
+        *outDisplayRequestMask = 0;
+        outLayers->clear();
+        outLayerRequestMasks->clear();
+        return;
+    }
+
+    ReturnData& data = found->second;
+
+    *outDisplayRequestMask = data.displayRequests;
+    *outLayers = std::move(data.requestedLayers);
+    *outLayerRequestMasks = std::move(data.requestMasks);
+}
+
+void CommandReader::takeReleaseFences(Display display, std::vector<Layer>* outLayers,
+                                      std::vector<int>* outReleaseFences) {
+    auto found = mReturnData.find(display);
+    if (found == mReturnData.end()) {
+        outLayers->clear();
+        outReleaseFences->clear();
+        return;
+    }
+
+    ReturnData& data = found->second;
+
+    *outLayers = std::move(data.releasedLayers);
+    *outReleaseFences = std::move(data.releaseFences);
+}
+
+void CommandReader::takePresentFence(Display display, int* outPresentFence) {
+    auto found = mReturnData.find(display);
+    if (found == mReturnData.end()) {
+        *outPresentFence = -1;
+        return;
+    }
+
+    ReturnData& data = found->second;
+
+    *outPresentFence = data.presentFence;
+    data.presentFence = -1;
+}
+
+void CommandReader::takePresentOrValidateStage(Display display, uint32_t* state) {
+    auto found = mReturnData.find(display);
+    if (found == mReturnData.end()) {
+        *state = -1;
+        return;
+    }
+    ReturnData& data = found->second;
+    *state = data.presentOrValidateState;
+}
+
+void CommandReader::takeClientTargetProperty(
+        Display display, IComposerClient::ClientTargetProperty* outClientTargetProperty) {
+    auto found = mReturnData.find(display);
+
+    // If not found, return the default values.
+    if (found == mReturnData.end()) {
+        outClientTargetProperty->pixelFormat = PixelFormat::RGBA_8888;
+        outClientTargetProperty->dataspace = Dataspace::UNKNOWN;
+        return;
+    }
+
+    ReturnData& data = found->second;
+    *outClientTargetProperty = data.clientTargetProperty;
+}
+
+} // namespace Hwc2
+} // namespace android
+
+// TODO(b/129481165): remove the #pragma below and fix conversion issues
+#pragma clang diagnostic pop // ignored "-Wconversion"
diff --git a/services/surfaceflinger/DisplayHardware/HidlComposerHal.h b/services/surfaceflinger/DisplayHardware/HidlComposerHal.h
new file mode 100644
index 0000000..5869ae5
--- /dev/null
+++ b/services/surfaceflinger/DisplayHardware/HidlComposerHal.h
@@ -0,0 +1,372 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include "ComposerHal.h"
+
+#include <optional>
+#include <string>
+#include <unordered_map>
+#include <utility>
+#include <vector>
+
+// TODO(b/129481165): remove the #pragma below and fix conversion issues
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wconversion"
+#pragma clang diagnostic ignored "-Wextra"
+
+#include <composer-command-buffer/2.4/ComposerCommandBuffer.h>
+#include <gui/BufferQueue.h>
+#include <gui/HdrMetadata.h>
+#include <math/mat4.h>
+#include <ui/DisplayedFrameStats.h>
+#include <ui/GraphicBuffer.h>
+#include <utils/StrongPointer.h>
+
+#include <aidl/android/hardware/graphics/composer3/Composition.h>
+
+// TODO(b/129481165): remove the #pragma below and fix conversion issues
+#pragma clang diagnostic pop // ignored "-Wconversion -Wextra"
+
+namespace android::Hwc2 {
+
+namespace types = hardware::graphics::common;
+
+namespace V2_1 = hardware::graphics::composer::V2_1;
+namespace V2_2 = hardware::graphics::composer::V2_2;
+namespace V2_3 = hardware::graphics::composer::V2_3;
+namespace V2_4 = hardware::graphics::composer::V2_4;
+
+using types::V1_0::ColorTransform;
+using types::V1_0::Transform;
+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;
+using V2_1::Display;
+using V2_1::Error;
+using V2_1::Layer;
+using V2_4::CommandReaderBase;
+using V2_4::CommandWriterBase;
+using V2_4::IComposer;
+using V2_4::IComposerCallback;
+using V2_4::IComposerClient;
+using V2_4::VsyncPeriodChangeTimeline;
+using V2_4::VsyncPeriodNanos;
+using PerFrameMetadata = IComposerClient::PerFrameMetadata;
+using PerFrameMetadataKey = IComposerClient::PerFrameMetadataKey;
+using PerFrameMetadataBlob = IComposerClient::PerFrameMetadataBlob;
+
+class CommandReader : public CommandReaderBase {
+public:
+    ~CommandReader();
+
+    // Parse and execute commands from the command queue.  The commands are
+    // actually return values from the server and will be saved in ReturnData.
+    Error parse();
+
+    // Get and clear saved errors.
+    struct CommandError {
+        uint32_t location;
+        Error error;
+    };
+    std::vector<CommandError> takeErrors();
+
+    bool hasChanges(Display display, uint32_t* outNumChangedCompositionTypes,
+                    uint32_t* outNumLayerRequestMasks) const;
+
+    // Get and clear saved changed composition types.
+    void takeChangedCompositionTypes(
+            Display display, std::vector<Layer>* outLayers,
+            std::vector<aidl::android::hardware::graphics::composer3::Composition>* outTypes);
+
+    // Get and clear saved display requests.
+    void takeDisplayRequests(Display display, uint32_t* outDisplayRequestMask,
+                             std::vector<Layer>* outLayers,
+                             std::vector<uint32_t>* outLayerRequestMasks);
+
+    // Get and clear saved release fences.
+    void takeReleaseFences(Display display, std::vector<Layer>* outLayers,
+                           std::vector<int>* outReleaseFences);
+
+    // Get and clear saved present fence.
+    void takePresentFence(Display display, int* outPresentFence);
+
+    // Get what stage succeeded during PresentOrValidate: Present or Validate
+    void takePresentOrValidateStage(Display display, uint32_t* state);
+
+    // Get the client target properties requested by hardware composer.
+    void takeClientTargetProperty(Display display,
+                                  IComposerClient::ClientTargetProperty* outClientTargetProperty);
+
+private:
+    void resetData();
+
+    bool parseSelectDisplay(uint16_t length);
+    bool parseSetError(uint16_t length);
+    bool parseSetChangedCompositionTypes(uint16_t length);
+    bool parseSetDisplayRequests(uint16_t length);
+    bool parseSetPresentFence(uint16_t length);
+    bool parseSetReleaseFences(uint16_t length);
+    bool parseSetPresentOrValidateDisplayResult(uint16_t length);
+    bool parseSetClientTargetProperty(uint16_t length);
+
+    struct ReturnData {
+        uint32_t displayRequests = 0;
+
+        std::vector<Layer> changedLayers;
+        std::vector<aidl::android::hardware::graphics::composer3::Composition> compositionTypes;
+
+        std::vector<Layer> requestedLayers;
+        std::vector<uint32_t> requestMasks;
+
+        int presentFence = -1;
+
+        std::vector<Layer> releasedLayers;
+        std::vector<int> releaseFences;
+
+        uint32_t presentOrValidateState;
+
+        // Composer 2.4 implementation can return a client target property
+        // structure to indicate the client target properties that hardware
+        // composer requests. The composer client must change the client target
+        // properties to match this request.
+        IComposerClient::ClientTargetProperty clientTargetProperty{PixelFormat::RGBA_8888,
+                                                                   Dataspace::UNKNOWN};
+    };
+
+    std::vector<CommandError> mErrors;
+    std::unordered_map<Display, ReturnData> mReturnData;
+
+    // When SELECT_DISPLAY is parsed, this is updated to point to the
+    // display's return data in mReturnData.  We use it to avoid repeated
+    // map lookups.
+    ReturnData* mCurrentReturnData;
+};
+
+// Composer is a wrapper to IComposer, a proxy to server-side composer.
+class HidlComposer final : public Composer {
+public:
+    explicit HidlComposer(const std::string& serviceName);
+    ~HidlComposer() override;
+
+    bool isSupported(OptionalFeature) const;
+
+    std::vector<aidl::android::hardware::graphics::composer3::Capability> getCapabilities()
+            override;
+    std::string dumpDebugInfo() override;
+
+    void registerCallback(HWC2::ComposerCallback& callback) override;
+
+    // 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;
+
+    // Explicitly flush all pending commands in the command buffer.
+    Error executeCommands() override;
+
+    uint32_t getMaxVirtualDisplayCount() override;
+    Error createVirtualDisplay(uint32_t width, uint32_t height, PixelFormat* format,
+                               Display* outDisplay) override;
+    Error destroyVirtualDisplay(Display display) override;
+
+    Error acceptDisplayChanges(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 getChangedCompositionTypes(
+            Display display, std::vector<Layer>* outLayers,
+            std::vector<aidl::android::hardware::graphics::composer3::Composition>* outTypes)
+            override;
+    Error getColorModes(Display display, std::vector<ColorMode>* outModes) override;
+    Error getDisplayAttribute(Display display, Config config, IComposerClient::Attribute attribute,
+                              int32_t* outValue) override;
+    Error getDisplayConfigs(Display display, std::vector<Config>* outConfigs);
+    Error getDisplayName(Display display, std::string* outName) override;
+
+    Error getDisplayRequests(Display display, uint32_t* outDisplayRequestMask,
+                             std::vector<Layer>* outLayers,
+                             std::vector<uint32_t>* outLayerRequestMasks) override;
+
+    Error getDozeSupport(Display display, bool* outSupport) override;
+    Error hasDisplayIdleTimerCapability(Display display, bool* outSupport) override;
+    Error getHdrCapabilities(Display display, std::vector<Hdr>* outTypes, float* outMaxLuminance,
+                             float* outMaxAverageLuminance, float* outMinLuminance) override;
+
+    Error getReleaseFences(Display display, std::vector<Layer>* outLayers,
+                           std::vector<int>* outReleaseFences) override;
+
+    Error presentDisplay(Display display, int* outPresentFence) override;
+
+    Error setActiveConfig(Display display, Config config) override;
+
+    /*
+     * The composer caches client targets internally.  When target is nullptr,
+     * the composer uses slot to look up the client target from its cache.
+     * When target is not nullptr, the cache is updated with the new target.
+     */
+    Error setClientTarget(Display display, uint32_t slot, const sp<GraphicBuffer>& target,
+                          int acquireFence, Dataspace dataspace,
+                          const std::vector<IComposerClient::Rect>& damage) override;
+    Error setColorMode(Display display, ColorMode mode, RenderIntent renderIntent) override;
+    Error setColorTransform(Display display, const float* matrix) override;
+    Error setOutputBuffer(Display display, const native_handle_t* buffer,
+                          int releaseFence) override;
+    Error setPowerMode(Display display, IComposerClient::PowerMode mode) override;
+    Error setVsyncEnabled(Display display, IComposerClient::Vsync enabled) override;
+
+    Error setClientTargetSlotCount(Display display) override;
+
+    Error validateDisplay(Display display, nsecs_t expectedPresentTime, uint32_t* outNumTypes,
+                          uint32_t* outNumRequests) override;
+
+    Error presentOrValidateDisplay(Display display, nsecs_t expectedPresentTime,
+                                   uint32_t* outNumTypes, uint32_t* outNumRequests,
+                                   int* outPresentFence, uint32_t* state) override;
+
+    Error setCursorPosition(Display display, Layer layer, int32_t x, int32_t y) override;
+    /* see setClientTarget for the purpose of slot */
+    Error setLayerBuffer(Display display, Layer layer, uint32_t slot,
+                         const sp<GraphicBuffer>& buffer, int acquireFence) override;
+    Error setLayerSurfaceDamage(Display display, Layer layer,
+                                const std::vector<IComposerClient::Rect>& damage) override;
+    Error setLayerBlendMode(Display display, Layer layer, IComposerClient::BlendMode mode) override;
+    Error setLayerColor(Display display, Layer layer,
+                        const aidl::android::hardware::graphics::composer3::Color& color) override;
+    Error setLayerCompositionType(
+            Display display, Layer layer,
+            aidl::android::hardware::graphics::composer3::Composition type) override;
+    Error setLayerDataspace(Display display, Layer layer, Dataspace dataspace) override;
+    Error setLayerDisplayFrame(Display display, Layer layer,
+                               const IComposerClient::Rect& frame) override;
+    Error setLayerPlaneAlpha(Display display, Layer layer, float alpha) override;
+    Error setLayerSidebandStream(Display display, Layer layer,
+                                 const native_handle_t* stream) override;
+    Error setLayerSourceCrop(Display display, Layer layer,
+                             const IComposerClient::FRect& crop) override;
+    Error setLayerTransform(Display display, Layer layer, Transform transform) override;
+    Error setLayerVisibleRegion(Display display, Layer layer,
+                                const std::vector<IComposerClient::Rect>& visible) override;
+    Error setLayerZOrder(Display display, Layer layer, uint32_t z) override;
+
+    // Composer HAL 2.2
+    Error setLayerPerFrameMetadata(
+            Display display, Layer layer,
+            const std::vector<IComposerClient::PerFrameMetadata>& perFrameMetadatas) override;
+    std::vector<IComposerClient::PerFrameMetadataKey> getPerFrameMetadataKeys(
+            Display display) override;
+    Error getRenderIntents(Display display, ColorMode colorMode,
+                           std::vector<RenderIntent>* outRenderIntents) override;
+    Error getDataspaceSaturationMatrix(Dataspace dataspace, mat4* outMatrix) override;
+
+    // Composer HAL 2.3
+    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* outFormat,
+                                                Dataspace* outDataspace,
+                                                uint8_t* outComponentMask) override;
+    Error setDisplayContentSamplingEnabled(Display display, bool enabled, uint8_t componentMask,
+                                           uint64_t maxFrames) override;
+    Error getDisplayedContentSample(Display display, uint64_t maxFrames, uint64_t timestamp,
+                                    DisplayedFrameStats* outStats) override;
+    Error setLayerPerFrameMetadataBlobs(
+            Display display, Layer layer,
+            const std::vector<IComposerClient::PerFrameMetadataBlob>& metadata) override;
+    Error setDisplayBrightness(Display display, float brightness,
+                               const DisplayBrightnessOptions& options) override;
+
+    // Composer HAL 2.4
+    Error getDisplayCapabilities(
+            Display display,
+            std::vector<aidl::android::hardware::graphics::composer3::DisplayCapability>*
+                    outCapabilities) override;
+    V2_4::Error getDisplayConnectionType(Display display,
+                                         IComposerClient::DisplayConnectionType* outType) override;
+    V2_4::Error getDisplayVsyncPeriod(Display display, VsyncPeriodNanos* outVsyncPeriod) override;
+    V2_4::Error setActiveConfigWithConstraints(
+            Display display, Config config,
+            const IComposerClient::VsyncPeriodChangeConstraints& vsyncPeriodChangeConstraints,
+            VsyncPeriodChangeTimeline* outTimeline) override;
+    V2_4::Error setAutoLowLatencyMode(Display displayId, bool on) override;
+    V2_4::Error getSupportedContentTypes(
+            Display displayId,
+            std::vector<IComposerClient::ContentType>* outSupportedContentTypes) override;
+    V2_4::Error setContentType(Display displayId,
+                               IComposerClient::ContentType contentType) override;
+    V2_4::Error setLayerGenericMetadata(Display display, Layer layer, const std::string& key,
+                                        bool mandatory, const std::vector<uint8_t>& value) override;
+    V2_4::Error getLayerGenericMetadataKeys(
+            std::vector<IComposerClient::LayerGenericMetadataKey>* outKeys) override;
+    Error getClientTargetProperty(Display display,
+                                  IComposerClient::ClientTargetProperty* outClientTargetProperty,
+                                  float* outBrightness) override;
+
+    // AIDL Composer HAL
+    Error setLayerBrightness(Display display, Layer layer, float brightness) override;
+    Error setLayerBlockingRegion(Display display, Layer layer,
+                                 const std::vector<IComposerClient::Rect>& blocking) override;
+    Error setBootDisplayConfig(Display displayId, Config) override;
+    Error clearBootDisplayConfig(Display displayId) override;
+    Error getPreferredBootDisplayConfig(Display displayId, Config*) override;
+    Error getDisplayDecorationSupport(
+            Display display,
+            std::optional<aidl::android::hardware::graphics::common::DisplayDecorationSupport>*
+                    support) override;
+    Error setIdleTimerEnabled(Display displayId, std::chrono::milliseconds timeout) override;
+
+    Error getPhysicalDisplayOrientation(Display displayId,
+                                        AidlTransform* outDisplayOrientation) override;
+
+private:
+    class CommandWriter : public CommandWriterBase {
+    public:
+        explicit CommandWriter(uint32_t initialMaxSize) : CommandWriterBase(initialMaxSize) {}
+        ~CommandWriter() override {}
+    };
+
+    void registerCallback(const sp<IComposerCallback>& callback);
+
+    // 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();
+
+    sp<V2_1::IComposer> mComposer;
+
+    sp<V2_1::IComposerClient> mClient;
+    sp<V2_2::IComposerClient> mClient_2_2;
+    sp<V2_3::IComposerClient> mClient_2_3;
+    sp<IComposerClient> mClient_2_4;
+
+    // 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
+    // We obtain this number by:
+    // 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;
+    CommandWriter mWriter;
+    CommandReader mReader;
+};
+
+} // namespace android::Hwc2
diff --git a/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp b/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp
index 1765caf..44c086d 100644
--- a/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp
+++ b/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp
@@ -16,17 +16,26 @@
 
 //#define LOG_NDEBUG 0
 
+#define ATRACE_TAG ATRACE_TAG_GRAPHICS
+
 #undef LOG_TAG
 #define LOG_TAG "PowerAdvisor"
 
+#include <unistd.h>
 #include <cinttypes>
+#include <cstdint>
+#include <optional>
 
 #include <android-base/properties.h>
 #include <utils/Log.h>
 #include <utils/Mutex.h>
+#include <utils/Trace.h>
 
 #include <android/hardware/power/1.3/IPower.h>
 #include <android/hardware/power/IPower.h>
+#include <android/hardware/power/IPowerHintSession.h>
+#include <android/hardware/power/WorkDuration.h>
+
 #include <binder/IServiceManager.h>
 
 #include "../SurfaceFlingerProperties.h"
@@ -47,10 +56,14 @@
 
 using android::hardware::power::Boost;
 using android::hardware::power::IPower;
+using android::hardware::power::IPowerHintSession;
 using android::hardware::power::Mode;
-using base::GetIntProperty;
+using android::hardware::power::WorkDuration;
+
 using scheduler::OneShotTimer;
 
+class AidlPowerHalWrapper;
+
 PowerAdvisor::~PowerAdvisor() = default;
 
 namespace {
@@ -60,6 +73,14 @@
     return timeout;
 }
 
+void traceExpensiveRendering(bool enabled) {
+    if (enabled) {
+        ATRACE_ASYNC_BEGIN("ExpensiveRendering", 0);
+    } else {
+        ATRACE_ASYNC_END("ExpensiveRendering", 0);
+    }
+}
+
 } // namespace
 
 PowerAdvisor::PowerAdvisor(SurfaceFlinger& flinger)
@@ -136,6 +157,74 @@
     }
 }
 
+// checks both if it supports and if it's enabled
+bool PowerAdvisor::usePowerHintSession() {
+    // uses cached value since the underlying support and flag are unlikely to change at runtime
+    return mPowerHintEnabled.value_or(false) && supportsPowerHintSession();
+}
+
+bool PowerAdvisor::supportsPowerHintSession() {
+    // cache to avoid needing lock every time
+    if (!mSupportsPowerHint.has_value()) {
+        std::lock_guard lock(mPowerHalMutex);
+        HalWrapper* const halWrapper = getPowerHal();
+        mSupportsPowerHint = halWrapper->supportsPowerHintSession();
+    }
+    return *mSupportsPowerHint;
+}
+
+bool PowerAdvisor::isPowerHintSessionRunning() {
+    return mPowerHintSessionRunning;
+}
+
+void PowerAdvisor::setTargetWorkDuration(int64_t targetDurationNanos) {
+    if (!usePowerHintSession()) {
+        ALOGV("Power hint session target duration cannot be set, skipping");
+        return;
+    }
+    {
+        std::lock_guard lock(mPowerHalMutex);
+        HalWrapper* const halWrapper = getPowerHal();
+        if (halWrapper != nullptr) {
+            halWrapper->setTargetWorkDuration(targetDurationNanos - kTargetSafetyMargin.count());
+        }
+    }
+}
+
+void PowerAdvisor::sendActualWorkDuration(int64_t actualDurationNanos, nsecs_t timeStampNanos) {
+    if (!mBootFinished || !usePowerHintSession()) {
+        ALOGV("Actual work duration power hint cannot be sent, skipping");
+        return;
+    }
+    {
+        std::lock_guard lock(mPowerHalMutex);
+        HalWrapper* const halWrapper = getPowerHal();
+        if (halWrapper != nullptr) {
+            halWrapper->sendActualWorkDuration(actualDurationNanos, timeStampNanos);
+        }
+    }
+}
+
+// needs to be set after the flag is known but before PowerAdvisor enters onBootFinished
+void PowerAdvisor::enablePowerHint(bool enabled) {
+    mPowerHintEnabled = enabled;
+}
+
+bool PowerAdvisor::startPowerHintSession(const std::vector<int32_t>& threadIds) {
+    if (!usePowerHintSession()) {
+        ALOGI("Power hint session cannot be started, skipping");
+    }
+    {
+        std::lock_guard lock(mPowerHalMutex);
+        HalWrapper* halWrapper = getPowerHal();
+        if (halWrapper != nullptr && usePowerHintSession()) {
+            halWrapper->setPowerHintSessionThreadIds(threadIds);
+            mPowerHintSessionRunning = halWrapper->startPowerHintSession();
+        }
+    }
+    return mPowerHintSessionRunning;
+}
+
 class HidlPowerHalWrapper : public PowerAdvisor::HalWrapper {
 public:
     HidlPowerHalWrapper(sp<V1_3::IPower> powerHal) : mPowerHal(std::move(powerHal)) {}
@@ -169,6 +258,9 @@
     bool setExpensiveRendering(bool enabled) override {
         ALOGV("HIDL setExpensiveRendering %s", enabled ? "T" : "F");
         auto ret = mPowerHal->powerHintAsync_1_3(PowerHint::EXPENSIVE_RENDERING, enabled);
+        if (ret.isOk()) {
+            traceExpensiveRendering(enabled);
+        }
         return ret.isOk();
     }
 
@@ -178,6 +270,26 @@
         return true;
     }
 
+    bool supportsPowerHintSession() override { return false; }
+
+    bool isPowerHintSessionRunning() override { return false; }
+
+    void restartPowerHintSession() override {}
+
+    void setPowerHintSessionThreadIds(const std::vector<int32_t>&) override {}
+
+    bool startPowerHintSession() override { return false; }
+
+    void setTargetWorkDuration(int64_t) override {}
+
+    void sendActualWorkDuration(int64_t, nsecs_t) override {}
+
+    bool shouldReconnectHAL() override { return false; }
+
+    std::vector<int32_t> getPowerHintSessionThreadIds() override { return std::vector<int32_t>{}; }
+
+    std::optional<int64_t> getTargetWorkDuration() override { return std::nullopt; }
+
 private:
     const sp<V1_3::IPower> mPowerHal = nullptr;
 };
@@ -195,9 +307,16 @@
         if (!ret.isOk()) {
             mHasDisplayUpdateImminent = false;
         }
+
+        mSupportsPowerHint = checkPowerHintSessionSupported();
     }
 
-    ~AidlPowerHalWrapper() override = default;
+    ~AidlPowerHalWrapper() override {
+        if (mPowerHintSession != nullptr) {
+            mPowerHintSession->close();
+            mPowerHintSession = nullptr;
+        }
+    };
 
     static std::unique_ptr<HalWrapper> connect() {
         // This only waits if the service is actually declared
@@ -218,6 +337,9 @@
         }
 
         auto ret = mPowerHal->setMode(Mode::EXPENSIVE_RENDERING, enabled);
+        if (ret.isOk()) {
+            traceExpensiveRendering(enabled);
+        }
         return ret.isOk();
     }
 
@@ -232,12 +354,205 @@
         return ret.isOk();
     }
 
+    // only version 2+ of the aidl supports power hint sessions, hidl has no support
+    bool supportsPowerHintSession() override { return mSupportsPowerHint; }
+
+    bool checkPowerHintSessionSupported() {
+        int64_t unused;
+        // Try to get preferred rate to determine if hint sessions are supported
+        // We check for isOk not EX_UNSUPPORTED_OPERATION to lump other errors
+        return mPowerHal->getHintSessionPreferredRate(&unused).isOk();
+    }
+
+    bool isPowerHintSessionRunning() override { return mPowerHintSession != nullptr; }
+
+    void closePowerHintSession() {
+        if (mPowerHintSession != nullptr) {
+            mPowerHintSession->close();
+            mPowerHintSession = nullptr;
+        }
+    }
+
+    void restartPowerHintSession() {
+        closePowerHintSession();
+        startPowerHintSession();
+    }
+
+    void setPowerHintSessionThreadIds(const std::vector<int32_t>& threadIds) override {
+        if (threadIds != mPowerHintThreadIds) {
+            mPowerHintThreadIds = threadIds;
+            if (isPowerHintSessionRunning()) {
+                restartPowerHintSession();
+            }
+        }
+    }
+
+    bool startPowerHintSession() override {
+        if (mPowerHintSession != nullptr || mPowerHintThreadIds.empty()) {
+            ALOGV("Cannot start power hint session, skipping");
+            return false;
+        }
+        auto ret = mPowerHal->createHintSession(getpid(), static_cast<int32_t>(getuid()),
+                                                mPowerHintThreadIds, mTargetDuration,
+                                                &mPowerHintSession);
+        if (!ret.isOk()) {
+            ALOGW("Failed to start power hint session with error: %s",
+                  ret.exceptionToString(ret.exceptionCode()).c_str());
+        } else {
+            mLastTargetDurationSent = mTargetDuration;
+        }
+        return isPowerHintSessionRunning();
+    }
+
+    bool shouldSetTargetDuration(int64_t targetDurationNanos) {
+        // report if the change in target from our last submission to now exceeds the threshold
+        return abs(1.0 -
+                   static_cast<double>(mLastTargetDurationSent) /
+                           static_cast<double>(targetDurationNanos)) >=
+                kAllowedTargetDeviationPercent;
+    }
+
+    void setTargetWorkDuration(int64_t targetDurationNanos) override {
+        ATRACE_CALL();
+        mTargetDuration = targetDurationNanos;
+        if (sTraceHintSessionData) ATRACE_INT64("Time target", targetDurationNanos);
+        if (!sNormalizeTarget && shouldSetTargetDuration(targetDurationNanos) &&
+            isPowerHintSessionRunning()) {
+            if (mLastActualDurationSent.has_value()) {
+                // update the error term here since we are actually sending an update to powerhal
+                if (sTraceHintSessionData)
+                    ATRACE_INT64("Target error term",
+                                 targetDurationNanos - *mLastActualDurationSent);
+            }
+            ALOGV("Sending target time: %lld ns", static_cast<long long>(targetDurationNanos));
+            mLastTargetDurationSent = targetDurationNanos;
+            auto ret = mPowerHintSession->updateTargetWorkDuration(targetDurationNanos);
+            if (!ret.isOk()) {
+                ALOGW("Failed to set power hint target work duration with error: %s",
+                      ret.exceptionMessage().c_str());
+                mShouldReconnectHal = true;
+            }
+        }
+    }
+
+    bool shouldReportActualDurationsNow() {
+        // report if we have never reported before or are approaching a stale session
+        if (!mLastActualDurationSent.has_value() ||
+            (systemTime() - mLastActualReportTimestamp) > kStaleTimeout.count()) {
+            return true;
+        }
+
+        if (!mActualDuration.has_value()) {
+            return false;
+        }
+
+        // duration of most recent timing
+        const double mostRecentActualDuration = static_cast<double>(*mActualDuration);
+        // duration of the last timing actually reported to the powerhal
+        const double lastReportedActualDuration = static_cast<double>(*mLastActualDurationSent);
+
+        // report if the change in duration from then to now exceeds the threshold
+        return abs(1.0 - mostRecentActualDuration / lastReportedActualDuration) >=
+                kAllowedActualDeviationPercent;
+    }
+
+    void sendActualWorkDuration(int64_t actualDurationNanos, nsecs_t timeStampNanos) override {
+        ATRACE_CALL();
+
+        if (actualDurationNanos < 0 || !isPowerHintSessionRunning()) {
+            ALOGV("Failed to send actual work duration, skipping");
+            return;
+        }
+
+        WorkDuration duration;
+        duration.durationNanos = actualDurationNanos;
+        mActualDuration = actualDurationNanos;
+
+        // normalize the sent values to a pre-set target
+        if (sNormalizeTarget) {
+            duration.durationNanos += mLastTargetDurationSent - mTargetDuration;
+        }
+        duration.timeStampNanos = timeStampNanos;
+        mPowerHintQueue.push_back(duration);
+
+        long long targetNsec = mTargetDuration;
+        long long durationNsec = actualDurationNanos;
+
+        if (sTraceHintSessionData) {
+            ATRACE_INT64("Measured duration", durationNsec);
+            ATRACE_INT64("Target error term", targetNsec - durationNsec);
+        }
+
+        ALOGV("Sending actual work duration of: %lld on target: %lld with error: %lld",
+              durationNsec, targetNsec, targetNsec - durationNsec);
+
+        // This rate limiter queues similar duration reports to the powerhal into
+        // batches to avoid excessive binder calls. The criteria to send a given batch
+        // are outlined in shouldReportActualDurationsNow()
+        if (shouldReportActualDurationsNow()) {
+            ALOGV("Sending hint update batch");
+            mLastActualReportTimestamp = systemTime();
+            auto ret = mPowerHintSession->reportActualWorkDuration(mPowerHintQueue);
+            if (!ret.isOk()) {
+                ALOGW("Failed to report actual work durations with error: %s",
+                      ret.exceptionMessage().c_str());
+                mShouldReconnectHal = true;
+            }
+            mPowerHintQueue.clear();
+            // we save the non-normalized value here to detect % changes
+            mLastActualDurationSent = actualDurationNanos;
+        }
+    }
+
+    bool shouldReconnectHAL() override { return mShouldReconnectHal; }
+
+    std::vector<int32_t> getPowerHintSessionThreadIds() override { return mPowerHintThreadIds; }
+
+    std::optional<int64_t> getTargetWorkDuration() override { return mTargetDuration; }
+
 private:
     const sp<IPower> mPowerHal = nullptr;
     bool mHasExpensiveRendering = false;
     bool mHasDisplayUpdateImminent = false;
+    // Used to indicate an error state and need for reconstruction
+    bool mShouldReconnectHal = false;
+    // This is not thread safe, but is currently protected by mPowerHalMutex so it needs no lock
+    sp<IPowerHintSession> mPowerHintSession = nullptr;
+    // Queue of actual durations saved to report
+    std::vector<WorkDuration> mPowerHintQueue;
+    // The latest un-normalized values we have received for target and actual
+    int64_t mTargetDuration = kDefaultTarget.count();
+    std::optional<int64_t> mActualDuration;
+    // The list of thread ids, stored so we can restart the session from this class if needed
+    std::vector<int32_t> mPowerHintThreadIds;
+    bool mSupportsPowerHint;
+    // Keep track of the last messages sent for rate limiter change detection
+    std::optional<int64_t> mLastActualDurationSent;
+    // timestamp of the last report we sent, used to avoid stale sessions
+    int64_t mLastActualReportTimestamp = 0;
+    int64_t mLastTargetDurationSent = kDefaultTarget.count();
+    // Whether to normalize all the actual values as error terms relative to a constant target
+    // This saves a binder call by not setting the target, and should not affect the pid values
+    static const bool sNormalizeTarget;
+    // Whether we should emit ATRACE_INT data for hint sessions
+    static const bool sTraceHintSessionData;
+    // Max percent the actual duration can vary without causing a report (eg: 0.1 = 10%)
+    static constexpr double kAllowedActualDeviationPercent = 0.1;
+    // Max percent the target duration can vary without causing a report (eg: 0.05 = 5%)
+    static constexpr double kAllowedTargetDeviationPercent = 0.05;
+    // Target used for init and normalization, the actual value does not really matter
+    static constexpr const std::chrono::nanoseconds kDefaultTarget = 50ms;
+    // Amount of time after the last message was sent before the session goes stale
+    // actually 100ms but we use 80 here to ideally avoid going stale
+    static constexpr const std::chrono::nanoseconds kStaleTimeout = 80ms;
 };
 
+const bool AidlPowerHalWrapper::sTraceHintSessionData =
+        base::GetBoolProperty(std::string("debug.sf.trace_hint_sessions"), false);
+
+const bool AidlPowerHalWrapper::sNormalizeTarget =
+        base::GetBoolProperty(std::string("debug.sf.normalize_hint_session_durations"), false);
+
 PowerAdvisor::HalWrapper* PowerAdvisor::getPowerHal() {
     static std::unique_ptr<HalWrapper> sHalWrapper = nullptr;
     static bool sHasHal = true;
@@ -246,6 +561,15 @@
         return nullptr;
     }
 
+    // grab old hint session values before we destroy any existing wrapper
+    std::vector<int32_t> oldPowerHintSessionThreadIds;
+    std::optional<int64_t> oldTargetWorkDuration;
+
+    if (sHalWrapper != nullptr) {
+        oldPowerHintSessionThreadIds = sHalWrapper->getPowerHintSessionThreadIds();
+        oldTargetWorkDuration = sHalWrapper->getTargetWorkDuration();
+    }
+
     // If we used to have a HAL, but it stopped responding, attempt to reconnect
     if (mReconnectPowerHal) {
         sHalWrapper = nullptr;
@@ -253,15 +577,34 @@
     }
 
     if (sHalWrapper != nullptr) {
-        return sHalWrapper.get();
+        auto wrapper = sHalWrapper.get();
+        // if the wrapper is fine, return it, but if it indicates a reconnect, remake it
+        if (!wrapper->shouldReconnectHAL()) {
+            return wrapper;
+        }
+        sHalWrapper = nullptr;
     }
 
+    // at this point, we know for sure there is no running session
+    mPowerHintSessionRunning = false;
+
     // First attempt to connect to the AIDL Power HAL
     sHalWrapper = AidlPowerHalWrapper::connect();
 
     // If that didn't succeed, attempt to connect to the HIDL Power HAL
     if (sHalWrapper == nullptr) {
         sHalWrapper = HidlPowerHalWrapper::connect();
+    } else { // if AIDL, pass on any existing hint session values
+        // thread ids always safe to set
+        sHalWrapper->setPowerHintSessionThreadIds(oldPowerHintSessionThreadIds);
+        // only set duration and start if duration is defined
+        if (oldTargetWorkDuration.has_value()) {
+            sHalWrapper->setTargetWorkDuration(*oldTargetWorkDuration);
+            // only start if possible to run and both threadids and duration are defined
+            if (usePowerHintSession() && !oldPowerHintSessionThreadIds.empty()) {
+                mPowerHintSessionRunning = sHalWrapper->startPowerHintSession();
+            }
+        }
     }
 
     // If we make it to this point and still don't have a HAL, it's unlikely we
diff --git a/services/surfaceflinger/DisplayHardware/PowerAdvisor.h b/services/surfaceflinger/DisplayHardware/PowerAdvisor.h
index f2d0766..0db56aa 100644
--- a/services/surfaceflinger/DisplayHardware/PowerAdvisor.h
+++ b/services/surfaceflinger/DisplayHardware/PowerAdvisor.h
@@ -17,12 +17,15 @@
 #pragma once
 
 #include <atomic>
+#include <chrono>
 #include <unordered_set>
 
 #include <utils/Mutex.h>
 
+#include <ui/DisplayIdentification.h>
 #include "../Scheduler/OneShotTimer.h"
-#include "DisplayIdentification.h"
+
+using namespace std::chrono_literals;
 
 namespace android {
 
@@ -40,6 +43,13 @@
     virtual void setExpensiveRenderingExpected(DisplayId displayId, bool expected) = 0;
     virtual bool isUsingExpensiveRendering() = 0;
     virtual void notifyDisplayUpdateImminent() = 0;
+    virtual bool usePowerHintSession() = 0;
+    virtual bool supportsPowerHintSession() = 0;
+    virtual bool isPowerHintSessionRunning() = 0;
+    virtual void setTargetWorkDuration(int64_t targetDurationNanos) = 0;
+    virtual void sendActualWorkDuration(int64_t actualDurationNanos, nsecs_t timestamp) = 0;
+    virtual void enablePowerHint(bool enabled) = 0;
+    virtual bool startPowerHintSession(const std::vector<int32_t>& threadIds) = 0;
 };
 
 namespace impl {
@@ -54,6 +64,17 @@
 
         virtual bool setExpensiveRendering(bool enabled) = 0;
         virtual bool notifyDisplayUpdateImminent() = 0;
+        virtual bool supportsPowerHintSession() = 0;
+        virtual bool isPowerHintSessionRunning() = 0;
+        virtual void restartPowerHintSession() = 0;
+        virtual void setPowerHintSessionThreadIds(const std::vector<int32_t>& threadIds) = 0;
+        virtual bool startPowerHintSession() = 0;
+        virtual void setTargetWorkDuration(int64_t targetDurationNanos) = 0;
+        virtual void sendActualWorkDuration(int64_t actualDurationNanos,
+                                            nsecs_t timeStampNanos) = 0;
+        virtual bool shouldReconnectHAL() = 0;
+        virtual std::vector<int32_t> getPowerHintSessionThreadIds() = 0;
+        virtual std::optional<int64_t> getTargetWorkDuration() = 0;
     };
 
     PowerAdvisor(SurfaceFlinger& flinger);
@@ -62,8 +83,15 @@
     void init() override;
     void onBootFinished() override;
     void setExpensiveRenderingExpected(DisplayId displayId, bool expected) override;
-    bool isUsingExpensiveRendering() override { return mNotifiedExpensiveRendering; }
+    bool isUsingExpensiveRendering() override { return mNotifiedExpensiveRendering; };
     void notifyDisplayUpdateImminent() override;
+    bool usePowerHintSession() override;
+    bool supportsPowerHintSession() override;
+    bool isPowerHintSessionRunning() override;
+    void setTargetWorkDuration(int64_t targetDurationNanos) override;
+    void sendActualWorkDuration(int64_t actualDurationNanos, nsecs_t timestamp) override;
+    void enablePowerHint(bool enabled) override;
+    bool startPowerHintSession(const std::vector<int32_t>& threadIds) override;
 
 private:
     HalWrapper* getPowerHal() REQUIRES(mPowerHalMutex);
@@ -71,6 +99,15 @@
     std::mutex mPowerHalMutex;
 
     std::atomic_bool mBootFinished = false;
+    std::optional<bool> mPowerHintEnabled;
+    std::optional<bool> mSupportsPowerHint;
+    bool mPowerHintSessionRunning = false;
+
+    // An adjustable safety margin which moves the "target" earlier to allow flinger to
+    // go a bit over without dropping a frame, especially since we can't measure
+    // the exact time HWC finishes composition so "actual" durations are measured
+    // from the end of present() instead, which is a bit later.
+    static constexpr const std::chrono::nanoseconds kTargetSafetyMargin = 2ms;
 
     std::unordered_set<DisplayId> mExpensiveDisplays;
     bool mNotifiedExpensiveRendering = false;
diff --git a/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.cpp b/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.cpp
index 82a9ae2..b4fb51f 100644
--- a/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.cpp
+++ b/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.cpp
@@ -21,20 +21,18 @@
 // #define LOG_NDEBUG 0
 #include "VirtualDisplaySurface.h"
 
-#include <inttypes.h>
+#include <cinttypes>
 
 #include "HWComposer.h"
 #include "SurfaceFlinger.h"
 
+#include <ftl/Flags.h>
+#include <ftl/enum.h>
 #include <gui/BufferItem.h>
 #include <gui/BufferQueue.h>
 #include <gui/IProducerListener.h>
 #include <system/window.h>
 
-// ---------------------------------------------------------------------------
-namespace android {
-// ---------------------------------------------------------------------------
-
 #define VDS_LOGE(msg, ...) ALOGE("[%s] " msg, \
         mDisplayName.c_str(), ##__VA_ARGS__)
 #define VDS_LOGW_IF(cond, msg, ...) ALOGW_IF(cond, "[%s] " msg, \
@@ -42,20 +40,11 @@
 #define VDS_LOGV(msg, ...) ALOGV("[%s] " msg, \
         mDisplayName.c_str(), ##__VA_ARGS__)
 
-static const char* dbgCompositionTypeStr(compositionengine::DisplaySurface::CompositionType type) {
-    switch (type) {
-        case compositionengine::DisplaySurface::COMPOSITION_UNKNOWN:
-            return "UNKNOWN";
-        case compositionengine::DisplaySurface::COMPOSITION_GPU:
-            return "GPU";
-        case compositionengine::DisplaySurface::COMPOSITION_HWC:
-            return "HWC";
-        case compositionengine::DisplaySurface::COMPOSITION_MIXED:
-            return "MIXED";
-        default:
-            return "<INVALID>";
-    }
-}
+#define UNSUPPORTED()                                               \
+    VDS_LOGE("%s: Invalid operation on virtual display", __func__); \
+    return INVALID_OPERATION
+
+namespace android {
 
 VirtualDisplaySurface::VirtualDisplaySurface(HWComposer& hwc, VirtualDisplayId displayId,
                                              const sp<IGraphicBufferProducer>& sink,
@@ -76,14 +65,10 @@
         mQueueBufferOutput(),
         mSinkBufferWidth(0),
         mSinkBufferHeight(0),
-        mCompositionType(COMPOSITION_UNKNOWN),
         mFbFence(Fence::NO_FENCE),
         mOutputFence(Fence::NO_FENCE),
         mFbProducerSlot(BufferQueue::INVALID_BUFFER_SLOT),
         mOutputProducerSlot(BufferQueue::INVALID_BUFFER_SLOT),
-        mDbgState(DBG_STATE_IDLE),
-        mDbgLastCompositionType(COMPOSITION_UNKNOWN),
-        mMustRecompose(false),
         mForceHwcCopy(SurfaceFlinger::useHwcForRgbToYuv) {
     mSource[SOURCE_SINK] = sink;
     mSource[SOURCE_SCRATCH] = bqProducer;
@@ -131,9 +116,9 @@
 
     mMustRecompose = mustRecompose;
 
-    VDS_LOGW_IF(mDbgState != DBG_STATE_IDLE,
-            "Unexpected beginFrame() in %s state", dbgStateStr());
-    mDbgState = DBG_STATE_BEGUN;
+    VDS_LOGW_IF(mDebugState != DebugState::Idle, "Unexpected %s in %s state", __func__,
+                ftl::enum_string(mDebugState).c_str());
+    mDebugState = DebugState::Begun;
 
     return refreshOutputBuffer();
 }
@@ -143,12 +128,12 @@
         return NO_ERROR;
     }
 
-    VDS_LOGW_IF(mDbgState != DBG_STATE_BEGUN,
-            "Unexpected prepareFrame() in %s state", dbgStateStr());
-    mDbgState = DBG_STATE_PREPARED;
+    VDS_LOGW_IF(mDebugState != DebugState::Begun, "Unexpected %s in %s state", __func__,
+                ftl::enum_string(mDebugState).c_str());
+    mDebugState = DebugState::Prepared;
 
     mCompositionType = compositionType;
-    if (mForceHwcCopy && mCompositionType == COMPOSITION_GPU) {
+    if (mForceHwcCopy && mCompositionType == CompositionType::Gpu) {
         // Some hardware can do RGB->YUV conversion more efficiently in hardware
         // controlled by HWC than in hardware controlled by the video encoder.
         // Forcing GPU-composed frames to go through an extra copy by the HWC
@@ -157,16 +142,16 @@
         //
         // On the other hand, when the consumer prefers RGB or can consume RGB
         // inexpensively, this forces an unnecessary copy.
-        mCompositionType = COMPOSITION_MIXED;
+        mCompositionType = CompositionType::Mixed;
     }
 
-    if (mCompositionType != mDbgLastCompositionType) {
-        VDS_LOGV("prepareFrame: composition type changed to %s",
-                dbgCompositionTypeStr(mCompositionType));
-        mDbgLastCompositionType = mCompositionType;
+    if (mCompositionType != mDebugLastCompositionType) {
+        VDS_LOGV("%s: composition type changed to %s", __func__,
+                 toString(mCompositionType).c_str());
+        mDebugLastCompositionType = mCompositionType;
     }
 
-    if (mCompositionType != COMPOSITION_GPU &&
+    if (mCompositionType != CompositionType::Gpu &&
         (mOutputFormat != mDefaultOutputFormat || mOutputUsage != GRALLOC_USAGE_HW_COMPOSER)) {
         // We must have just switched from GPU-only to MIXED or HWC
         // composition. Stop using the format and usage requested by the GPU
@@ -191,33 +176,32 @@
         return NO_ERROR;
     }
 
-    if (mCompositionType == COMPOSITION_HWC) {
-        VDS_LOGW_IF(mDbgState != DBG_STATE_PREPARED,
-                "Unexpected advanceFrame() in %s state on HWC frame",
-                dbgStateStr());
+    if (mCompositionType == CompositionType::Hwc) {
+        VDS_LOGW_IF(mDebugState != DebugState::Prepared, "Unexpected %s in %s state on HWC frame",
+                    __func__, ftl::enum_string(mDebugState).c_str());
     } else {
-        VDS_LOGW_IF(mDbgState != DBG_STATE_GPU_DONE,
-                    "Unexpected advanceFrame() in %s state on GPU/MIXED frame", dbgStateStr());
+        VDS_LOGW_IF(mDebugState != DebugState::GpuDone,
+                    "Unexpected %s in %s state on GPU/MIXED frame", __func__,
+                    ftl::enum_string(mDebugState).c_str());
     }
-    mDbgState = DBG_STATE_HWC;
+    mDebugState = DebugState::Hwc;
 
     if (mOutputProducerSlot < 0 ||
-            (mCompositionType != COMPOSITION_HWC && mFbProducerSlot < 0)) {
+        (mCompositionType != CompositionType::Hwc && mFbProducerSlot < 0)) {
         // Last chance bailout if something bad happened earlier. For example,
         // in a graphics API configuration, if the sink disappears then dequeueBuffer
         // will fail, the GPU driver won't queue a buffer, but SurfaceFlinger
         // will soldier on. So we end up here without a buffer. There should
         // be lots of scary messages in the log just before this.
-        VDS_LOGE("advanceFrame: no buffer, bailing out");
+        VDS_LOGE("%s: no buffer, bailing out", __func__);
         return NO_MEMORY;
     }
 
     sp<GraphicBuffer> fbBuffer = mFbProducerSlot >= 0 ?
             mProducerBuffers[mFbProducerSlot] : sp<GraphicBuffer>(nullptr);
     sp<GraphicBuffer> outBuffer = mProducerBuffers[mOutputProducerSlot];
-    VDS_LOGV("advanceFrame: fb=%d(%p) out=%d(%p)",
-            mFbProducerSlot, fbBuffer.get(),
-            mOutputProducerSlot, outBuffer.get());
+    VDS_LOGV("%s: fb=%d(%p) out=%d(%p)", __func__, mFbProducerSlot, fbBuffer.get(),
+             mOutputProducerSlot, outBuffer.get());
 
     const auto halDisplayId = HalVirtualDisplayId::tryCast(mDisplayId);
     LOG_FATAL_IF(!halDisplayId);
@@ -245,16 +229,16 @@
         return;
     }
 
-    VDS_LOGW_IF(mDbgState != DBG_STATE_HWC,
-            "Unexpected onFrameCommitted() in %s state", dbgStateStr());
-    mDbgState = DBG_STATE_IDLE;
+    VDS_LOGW_IF(mDebugState != DebugState::Hwc, "Unexpected %s in %s state", __func__,
+                ftl::enum_string(mDebugState).c_str());
+    mDebugState = DebugState::Idle;
 
     sp<Fence> retireFence = mHwc.getPresentFence(*halDisplayId);
-    if (mCompositionType == COMPOSITION_MIXED && mFbProducerSlot >= 0) {
+    if (mCompositionType == CompositionType::Mixed && mFbProducerSlot >= 0) {
         // release the scratch buffer back to the pool
         Mutex::Autolock lock(mMutex);
         int sslot = mapProducer2SourceSlot(SOURCE_SCRATCH, mFbProducerSlot);
-        VDS_LOGV("onFrameCommitted: release scratch sslot=%d", sslot);
+        VDS_LOGV("%s: release scratch sslot=%d", __func__, sslot);
         addReleaseFenceLocked(sslot, mProducerBuffers[mFbProducerSlot],
                 retireFence);
         releaseBufferLocked(sslot, mProducerBuffers[mFbProducerSlot]);
@@ -263,7 +247,7 @@
     if (mOutputProducerSlot >= 0) {
         int sslot = mapProducer2SourceSlot(SOURCE_SINK, mOutputProducerSlot);
         QueueBufferOutput qbo;
-        VDS_LOGV("onFrameCommitted: queue sink sslot=%d", sslot);
+        VDS_LOGV("%s: queue sink sslot=%d", __func__, sslot);
         if (mMustRecompose) {
             status_t result = mSource[SOURCE_SINK]->queueBuffer(sslot,
                     QueueBufferInput(
@@ -308,8 +292,8 @@
         return mSource[SOURCE_SINK]->requestBuffer(pslot, outBuf);
     }
 
-    VDS_LOGW_IF(mDbgState != DBG_STATE_GPU, "Unexpected requestBuffer pslot=%d in %s state", pslot,
-                dbgStateStr());
+    VDS_LOGW_IF(mDebugState != DebugState::Gpu, "Unexpected %s pslot=%d in %s state", __func__,
+                pslot, ftl::enum_string(mDebugState).c_str());
 
     *outBuf = mProducerBuffers[pslot];
     return NO_ERROR;
@@ -334,8 +318,8 @@
     if (result < 0)
         return result;
     int pslot = mapSource2ProducerSlot(source, *sslot);
-    VDS_LOGV("dequeueBuffer(%s): sslot=%d pslot=%d result=%d",
-            dbgSourceStr(source), *sslot, pslot, result);
+    VDS_LOGV("%s(%s): sslot=%d pslot=%d result=%d", __func__, ftl::enum_string(source).c_str(),
+             *sslot, pslot, result);
     uint64_t sourceBit = static_cast<uint64_t>(source) << pslot;
 
     // reset producer slot reallocation flag
@@ -363,10 +347,9 @@
             mSource[source]->cancelBuffer(*sslot, *fence);
             return result;
         }
-        VDS_LOGV("dequeueBuffer(%s): buffers[%d]=%p fmt=%d usage=%#" PRIx64,
-                dbgSourceStr(source), pslot, mProducerBuffers[pslot].get(),
-                mProducerBuffers[pslot]->getPixelFormat(),
-                mProducerBuffers[pslot]->getUsage());
+        VDS_LOGV("%s(%s): buffers[%d]=%p fmt=%d usage=%#" PRIx64, __func__,
+                 ftl::enum_string(source).c_str(), pslot, mProducerBuffers[pslot].get(),
+                 mProducerBuffers[pslot]->getPixelFormat(), mProducerBuffers[pslot]->getUsage());
 
         // propagate reallocation to VDS consumer
         mProducerSlotNeedReallocation |= 1ULL << pslot;
@@ -384,11 +367,11 @@
                                                    outTimestamps);
     }
 
-    VDS_LOGW_IF(mDbgState != DBG_STATE_PREPARED,
-            "Unexpected dequeueBuffer() in %s state", dbgStateStr());
-    mDbgState = DBG_STATE_GPU;
+    VDS_LOGW_IF(mDebugState != DebugState::Prepared, "Unexpected %s in %s state", __func__,
+                ftl::enum_string(mDebugState).c_str());
+    mDebugState = DebugState::Gpu;
 
-    VDS_LOGV("dequeueBuffer %dx%d fmt=%d usage=%#" PRIx64, w, h, format, usage);
+    VDS_LOGV("%s %dx%d fmt=%d usage=%#" PRIx64, __func__, w, h, format, usage);
 
     status_t result = NO_ERROR;
     Source source = fbSourceForCompositionType(mCompositionType);
@@ -401,7 +384,7 @@
             // will fail, the GPU driver won't queue a buffer, but SurfaceFlinger
             // will soldier on. So we end up here without a buffer. There should
             // be lots of scary messages in the log just before this.
-            VDS_LOGE("dequeueBuffer: no buffer, bailing out");
+            VDS_LOGE("%s: no buffer, bailing out", __func__);
             return NO_MEMORY;
         }
 
@@ -417,12 +400,11 @@
                 (format != 0 && format != buf->getPixelFormat()) ||
                 (w != 0 && w != mSinkBufferWidth) ||
                 (h != 0 && h != mSinkBufferHeight)) {
-            VDS_LOGV("dequeueBuffer: dequeueing new output buffer: "
-                    "want %dx%d fmt=%d use=%#" PRIx64 ", "
-                    "have %dx%d fmt=%d use=%#" PRIx64,
-                    w, h, format, usage,
-                    mSinkBufferWidth, mSinkBufferHeight,
-                    buf->getPixelFormat(), buf->getUsage());
+            VDS_LOGV("%s: dequeueing new output buffer: "
+                     "want %dx%d fmt=%d use=%#" PRIx64 ", "
+                     "have %dx%d fmt=%d use=%#" PRIx64,
+                     __func__, w, h, format, usage, mSinkBufferWidth, mSinkBufferHeight,
+                     buf->getPixelFormat(), buf->getUsage());
             mOutputFormat = format;
             mOutputUsage = usage;
             result = refreshOutputBuffer();
@@ -452,21 +434,16 @@
     return result;
 }
 
-status_t VirtualDisplaySurface::detachBuffer(int /* slot */) {
-    VDS_LOGE("detachBuffer is not available for VirtualDisplaySurface");
-    return INVALID_OPERATION;
+status_t VirtualDisplaySurface::detachBuffer(int) {
+    UNSUPPORTED();
 }
 
-status_t VirtualDisplaySurface::detachNextBuffer(
-        sp<GraphicBuffer>* /* outBuffer */, sp<Fence>* /* outFence */) {
-    VDS_LOGE("detachNextBuffer is not available for VirtualDisplaySurface");
-    return INVALID_OPERATION;
+status_t VirtualDisplaySurface::detachNextBuffer(sp<GraphicBuffer>*, sp<Fence>*) {
+    UNSUPPORTED();
 }
 
-status_t VirtualDisplaySurface::attachBuffer(int* /* outSlot */,
-        const sp<GraphicBuffer>& /* buffer */) {
-    VDS_LOGE("attachBuffer is not available for VirtualDisplaySurface");
-    return INVALID_OPERATION;
+status_t VirtualDisplaySurface::attachBuffer(int*, const sp<GraphicBuffer>&) {
+    UNSUPPORTED();
 }
 
 status_t VirtualDisplaySurface::queueBuffer(int pslot,
@@ -475,14 +452,14 @@
         return mSource[SOURCE_SINK]->queueBuffer(pslot, input, output);
     }
 
-    VDS_LOGW_IF(mDbgState != DBG_STATE_GPU, "Unexpected queueBuffer(pslot=%d) in %s state", pslot,
-                dbgStateStr());
-    mDbgState = DBG_STATE_GPU_DONE;
+    VDS_LOGW_IF(mDebugState != DebugState::Gpu, "Unexpected %s(pslot=%d) in %s state", __func__,
+                pslot, ftl::enum_string(mDebugState).c_str());
+    mDebugState = DebugState::GpuDone;
 
-    VDS_LOGV("queueBuffer pslot=%d", pslot);
+    VDS_LOGV("%s pslot=%d", __func__, pslot);
 
     status_t result;
-    if (mCompositionType == COMPOSITION_MIXED) {
+    if (mCompositionType == CompositionType::Mixed) {
         // Queue the buffer back into the scratch pool
         QueueBufferOutput scratchQBO;
         int sslot = mapProducer2SourceSlot(SOURCE_SCRATCH, pslot);
@@ -498,15 +475,15 @@
         if (result != NO_ERROR)
             return result;
         VDS_LOGW_IF(item.mSlot != sslot,
-                "queueBuffer: acquired sslot %d from SCRATCH after queueing sslot %d",
-                item.mSlot, sslot);
+                    "%s: acquired sslot %d from SCRATCH after queueing sslot %d", __func__,
+                    item.mSlot, sslot);
         mFbProducerSlot = mapSource2ProducerSlot(SOURCE_SCRATCH, item.mSlot);
         mFbFence = mSlots[item.mSlot].mFence;
 
     } else {
-        LOG_FATAL_IF(mCompositionType != COMPOSITION_GPU,
-                     "Unexpected queueBuffer in state %s for compositionType %s", dbgStateStr(),
-                     dbgCompositionTypeStr(mCompositionType));
+        LOG_FATAL_IF(mCompositionType != CompositionType::Gpu,
+                     "Unexpected %s in state %s for composition type %s", __func__,
+                     ftl::enum_string(mDebugState).c_str(), toString(mCompositionType).c_str());
 
         // Extract the GPU release fence for HWC to acquire
         int64_t timestamp;
@@ -533,9 +510,9 @@
         return mSource[SOURCE_SINK]->cancelBuffer(mapProducer2SourceSlot(SOURCE_SINK, pslot), fence);
     }
 
-    VDS_LOGW_IF(mDbgState != DBG_STATE_GPU, "Unexpected cancelBuffer(pslot=%d) in %s state", pslot,
-                dbgStateStr());
-    VDS_LOGV("cancelBuffer pslot=%d", pslot);
+    VDS_LOGW_IF(mDebugState != DebugState::Gpu, "Unexpected %s(pslot=%d) in %s state", __func__,
+                pslot, ftl::enum_string(mDebugState).c_str());
+    VDS_LOGV("%s pslot=%d", __func__, pslot);
     Source source = fbSourceForCompositionType(mCompositionType);
     return mSource[source]->cancelBuffer(
             mapProducer2SourceSlot(source, pslot), fence);
@@ -573,8 +550,8 @@
     return mSource[SOURCE_SINK]->disconnect(api, mode);
 }
 
-status_t VirtualDisplaySurface::setSidebandStream(const sp<NativeHandle>& /*stream*/) {
-    return INVALID_OPERATION;
+status_t VirtualDisplaySurface::setSidebandStream(const sp<NativeHandle>&) {
+    UNSUPPORTED();
 }
 
 void VirtualDisplaySurface::allocateBuffers(uint32_t /* width */,
@@ -586,40 +563,32 @@
     return INVALID_OPERATION;
 }
 
-status_t VirtualDisplaySurface::setGenerationNumber(uint32_t /* generation */) {
-    ALOGE("setGenerationNumber not supported on VirtualDisplaySurface");
-    return INVALID_OPERATION;
+status_t VirtualDisplaySurface::setGenerationNumber(uint32_t) {
+    UNSUPPORTED();
 }
 
 String8 VirtualDisplaySurface::getConsumerName() const {
     return String8("VirtualDisplaySurface");
 }
 
-status_t VirtualDisplaySurface::setSharedBufferMode(bool /*sharedBufferMode*/) {
-    ALOGE("setSharedBufferMode not supported on VirtualDisplaySurface");
-    return INVALID_OPERATION;
+status_t VirtualDisplaySurface::setSharedBufferMode(bool) {
+    UNSUPPORTED();
 }
 
-status_t VirtualDisplaySurface::setAutoRefresh(bool /*autoRefresh*/) {
-    ALOGE("setAutoRefresh not supported on VirtualDisplaySurface");
-    return INVALID_OPERATION;
+status_t VirtualDisplaySurface::setAutoRefresh(bool) {
+    UNSUPPORTED();
 }
 
-status_t VirtualDisplaySurface::setDequeueTimeout(nsecs_t /* timeout */) {
-    ALOGE("setDequeueTimeout not supported on VirtualDisplaySurface");
-    return INVALID_OPERATION;
+status_t VirtualDisplaySurface::setDequeueTimeout(nsecs_t) {
+    UNSUPPORTED();
 }
 
-status_t VirtualDisplaySurface::getLastQueuedBuffer(
-        sp<GraphicBuffer>* /*outBuffer*/, sp<Fence>* /*outFence*/,
-        float[16] /* outTransformMatrix*/) {
-    ALOGE("getLastQueuedBuffer not supported on VirtualDisplaySurface");
-    return INVALID_OPERATION;
+status_t VirtualDisplaySurface::getLastQueuedBuffer(sp<GraphicBuffer>*, sp<Fence>*, float[16]) {
+    UNSUPPORTED();
 }
 
-status_t VirtualDisplaySurface::getUniqueId(uint64_t* /*outId*/) const {
-    ALOGE("getUniqueId not supported on VirtualDisplaySurface");
-    return INVALID_OPERATION;
+status_t VirtualDisplaySurface::getUniqueId(uint64_t*) const {
+    UNSUPPORTED();
 }
 
 status_t VirtualDisplaySurface::getConsumerUsage(uint64_t* outUsage) const {
@@ -633,7 +602,7 @@
 }
 
 void VirtualDisplaySurface::resetPerFrameState() {
-    mCompositionType = COMPOSITION_UNKNOWN;
+    mCompositionType = CompositionType::Unknown;
     mFbFence = Fence::NO_FENCE;
     mOutputFence = Fence::NO_FENCE;
     mOutputProducerSlot = -1;
@@ -682,39 +651,16 @@
     return mapSource2ProducerSlot(source, pslot);
 }
 
-VirtualDisplaySurface::Source
-VirtualDisplaySurface::fbSourceForCompositionType(CompositionType type) {
-    return type == COMPOSITION_MIXED ? SOURCE_SCRATCH : SOURCE_SINK;
+auto VirtualDisplaySurface::fbSourceForCompositionType(CompositionType type) -> Source {
+    return type == CompositionType::Mixed ? SOURCE_SCRATCH : SOURCE_SINK;
 }
 
-const char* VirtualDisplaySurface::dbgStateStr() const {
-    switch (mDbgState) {
-        case DBG_STATE_IDLE:
-            return "IDLE";
-        case DBG_STATE_PREPARED:
-            return "PREPARED";
-        case DBG_STATE_GPU:
-            return "GPU";
-        case DBG_STATE_GPU_DONE:
-            return "GPU_DONE";
-        case DBG_STATE_HWC:
-            return "HWC";
-        default:
-            return "INVALID";
-    }
+std::string VirtualDisplaySurface::toString(CompositionType type) {
+    using namespace std::literals;
+    return type == CompositionType::Unknown ? "Unknown"s : Flags(type).string();
 }
 
-const char* VirtualDisplaySurface::dbgSourceStr(Source s) {
-    switch (s) {
-        case SOURCE_SINK:    return "SINK";
-        case SOURCE_SCRATCH: return "SCRATCH";
-        default:             return "INVALID";
-    }
-}
-
-// ---------------------------------------------------------------------------
 } // namespace android
-// ---------------------------------------------------------------------------
 
 // TODO(b/129481165): remove the #pragma below and fix conversion issues
 #pragma clang diagnostic pop // ignored "-Wconversion"
diff --git a/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.h b/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.h
index bbb6306..307da41 100644
--- a/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.h
+++ b/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.h
@@ -14,8 +14,7 @@
  * limitations under the License.
  */
 
-#ifndef ANDROID_SF_VIRTUAL_DISPLAY_SURFACE_H
-#define ANDROID_SF_VIRTUAL_DISPLAY_SURFACE_H
+#pragma once
 
 #include <optional>
 #include <string>
@@ -26,11 +25,9 @@
 #include <gui/IGraphicBufferProducer.h>
 #include <ui/DisplayId.h>
 
-#include "DisplayIdentification.h"
+#include <ui/DisplayIdentification.h>
 
-// ---------------------------------------------------------------------------
 namespace android {
-// ---------------------------------------------------------------------------
 
 class HWComposer;
 class IProducerListener;
@@ -94,7 +91,13 @@
     virtual const sp<Fence>& getClientTargetAcquireFence() const override;
 
 private:
-    enum Source {SOURCE_SINK = 0, SOURCE_SCRATCH = 1};
+    enum Source : size_t {
+        SOURCE_SINK = 0,
+        SOURCE_SCRATCH = 1,
+
+        ftl_first = SOURCE_SINK,
+        ftl_last = SOURCE_SCRATCH,
+    };
 
     virtual ~VirtualDisplaySurface();
 
@@ -133,6 +136,8 @@
     // Utility methods
     //
     static Source fbSourceForCompositionType(CompositionType);
+    static std::string toString(CompositionType);
+
     status_t dequeueBuffer(Source, PixelFormat, uint64_t usage, int* sslot, sp<Fence>*);
     void updateQueueBufferOutput(QueueBufferOutput&&);
     void resetPerFrameState();
@@ -197,7 +202,7 @@
 
     // Composition type and graphics buffer source for the current frame.
     // Valid after prepareFrame(), cleared in onFrameCommitted.
-    CompositionType mCompositionType;
+    CompositionType mCompositionType = CompositionType::Unknown;
 
     // mFbFence is the fence HWC should wait for before reading the framebuffer
     // target buffer.
@@ -219,47 +224,42 @@
     // +-----------+-------------------+-------------+
     // | State     | Event             || Next State |
     // +-----------+-------------------+-------------+
-    // | IDLE      | beginFrame        || BEGUN      |
-    // | BEGUN     | prepareFrame      || PREPARED   |
-    // | PREPARED  | dequeueBuffer [1] || GPU        |
-    // | PREPARED  | advanceFrame [2]  || HWC        |
-    // | GPU       | queueBuffer       || GPU_DONE   |
-    // | GPU_DONE  | advanceFrame      || HWC        |
-    // | HWC       | onFrameCommitted  || IDLE       |
+    // | Idle      | beginFrame        || Begun      |
+    // | Begun     | prepareFrame      || Prepared   |
+    // | Prepared  | dequeueBuffer [1] || Gpu        |
+    // | Prepared  | advanceFrame [2]  || Hwc        |
+    // | Gpu       | queueBuffer       || GpuDone    |
+    // | GpuDone   | advanceFrame      || Hwc        |
+    // | Hwc       | onFrameCommitted  || Idle       |
     // +-----------+-------------------++------------+
-    // [1] COMPOSITION_GPU and COMPOSITION_MIXED frames.
-    // [2] COMPOSITION_HWC frames.
+    // [1] CompositionType::Gpu and CompositionType::Mixed frames.
+    // [2] CompositionType::Hwc frames.
     //
-    enum DbgState {
+    enum class DebugState {
         // no buffer dequeued, don't know anything about the next frame
-        DBG_STATE_IDLE,
+        Idle,
         // output buffer dequeued, framebuffer source not yet known
-        DBG_STATE_BEGUN,
+        Begun,
         // output buffer dequeued, framebuffer source known but not provided
         // to GPU yet.
-        DBG_STATE_PREPARED,
+        Prepared,
         // GPU driver has a buffer dequeued
-        DBG_STATE_GPU,
+        Gpu,
         // GPU driver has queued the buffer, we haven't sent it to HWC yet
-        DBG_STATE_GPU_DONE,
+        GpuDone,
         // HWC has the buffer for this frame
-        DBG_STATE_HWC,
+        Hwc,
+
+        ftl_last = Hwc
     };
-    DbgState mDbgState;
-    CompositionType mDbgLastCompositionType;
+    DebugState mDebugState = DebugState::Idle;
+    CompositionType mDebugLastCompositionType = CompositionType::Unknown;
 
-    const char* dbgStateStr() const;
-    static const char* dbgSourceStr(Source s);
-
-    bool mMustRecompose;
+    bool mMustRecompose = false;
 
     compositionengine::impl::HwcBufferCache mHwcBufferCache;
 
     bool mForceHwcCopy;
 };
 
-// ---------------------------------------------------------------------------
 } // namespace android
-// ---------------------------------------------------------------------------
-
-#endif // ANDROID_SF_VIRTUAL_DISPLAY_SURFACE_H
diff --git a/services/surfaceflinger/EffectLayer.cpp b/services/surfaceflinger/EffectLayer.cpp
index 86c6b21..cc85352 100644
--- a/services/surfaceflinger/EffectLayer.cpp
+++ b/services/surfaceflinger/EffectLayer.cpp
@@ -109,7 +109,8 @@
 
     auto* compositionState = editCompositionState();
     compositionState->color = getColor();
-    compositionState->compositionType = Hwc2::IComposerClient::Composition::SOLID_COLOR;
+    compositionState->compositionType =
+            aidl::android::hardware::graphics::composer3::Composition::SOLID_COLOR;
 }
 
 sp<compositionengine::LayerFE> EffectLayer::getCompositionEngineLayerFE() const {
@@ -136,8 +137,7 @@
 
 sp<Layer> EffectLayer::createClone() {
     sp<EffectLayer> layer = mFlinger->getFactory().createEffectLayer(
-            LayerCreationArgs(mFlinger.get(), nullptr, mName + " (Mirror)", 0, 0, 0,
-                              LayerMetadata()));
+            LayerCreationArgs(mFlinger.get(), nullptr, mName + " (Mirror)", 0, LayerMetadata()));
     layer->setInitialValuesForClone(this);
     return layer;
 }
diff --git a/services/surfaceflinger/FlagManager.cpp b/services/surfaceflinger/FlagManager.cpp
new file mode 100644
index 0000000..bd3cf74
--- /dev/null
+++ b/services/surfaceflinger/FlagManager.cpp
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "FlagManager.h"
+
+#include <SurfaceFlingerProperties.sysprop.h>
+#include <android-base/parsebool.h>
+#include <android-base/parseint.h>
+#include <android-base/properties.h>
+#include <android-base/stringprintf.h>
+#include <log/log.h>
+#include <renderengine/RenderEngine.h>
+#include <server_configurable_flags/get_flags.h>
+#include <cinttypes>
+
+namespace android {
+static constexpr const char* kExperimentNamespace = "surface_flinger_native_boot";
+static constexpr const int64_t kDemoFlag = -1;
+
+FlagManager::~FlagManager() = default;
+
+void FlagManager::dump(std::string& result) const {
+    base::StringAppendF(&result, "FlagManager values: \n");
+    base::StringAppendF(&result, "demo_flag: %" PRId64 "\n", demo_flag());
+    base::StringAppendF(&result, "use_adpf_cpu_hint: %s\n", use_adpf_cpu_hint() ? "true" : "false");
+    base::StringAppendF(&result, "use_skia_tracing: %s\n", use_skia_tracing() ? "true" : "false");
+}
+
+namespace {
+template <typename T>
+std::optional<T> doParse(const char* str);
+
+template <>
+[[maybe_unused]] std::optional<int32_t> doParse(const char* str) {
+    int32_t ret;
+    return base::ParseInt(str, &ret) ? std::make_optional(ret) : std::nullopt;
+}
+
+template <>
+[[maybe_unused]] std::optional<int64_t> doParse(const char* str) {
+    int64_t ret;
+    return base::ParseInt(str, &ret) ? std::make_optional(ret) : std::nullopt;
+}
+
+template <>
+[[maybe_unused]] std::optional<bool> doParse(const char* str) {
+    base::ParseBoolResult parseResult = base::ParseBool(str);
+    switch (parseResult) {
+        case base::ParseBoolResult::kTrue:
+            return std::make_optional(true);
+        case base::ParseBoolResult::kFalse:
+            return std::make_optional(false);
+        case base::ParseBoolResult::kError:
+            return std::nullopt;
+    }
+}
+} // namespace
+
+std::string FlagManager::getServerConfigurableFlag(const std::string& experimentFlagName) const {
+    return server_configurable_flags::GetServerConfigurableFlag(kExperimentNamespace,
+                                                                experimentFlagName, "");
+}
+
+template int32_t FlagManager::getValue<int32_t>(const std::string&, std::optional<int32_t>,
+                                                int32_t) const;
+template int64_t FlagManager::getValue<int64_t>(const std::string&, std::optional<int64_t>,
+                                                int64_t) const;
+template bool FlagManager::getValue<bool>(const std::string&, std::optional<bool>, bool) const;
+template <typename T>
+T FlagManager::getValue(const std::string& experimentFlagName, std::optional<T> systemPropertyOpt,
+                        T defaultValue) const {
+    // System property takes precedence over the experiment config server value.
+    if (systemPropertyOpt.has_value()) {
+        return *systemPropertyOpt;
+    }
+    std::string str = getServerConfigurableFlag(experimentFlagName);
+    return str.empty() ? defaultValue : doParse<T>(str.c_str()).value_or(defaultValue);
+}
+
+int64_t FlagManager::demo_flag() const {
+    std::optional<int64_t> sysPropVal = std::nullopt;
+    return getValue("DemoFeature__demo_flag", sysPropVal, kDemoFlag);
+}
+
+bool FlagManager::use_adpf_cpu_hint() const {
+    std::optional<bool> sysPropVal = std::nullopt;
+    return getValue("AdpfFeature__adpf_cpu_hint", sysPropVal, false);
+}
+
+bool FlagManager::use_skia_tracing() const {
+    std::optional<bool> sysPropVal =
+            doParse<bool>(base::GetProperty(PROPERTY_SKIA_ATRACE_ENABLED, "").c_str());
+    return getValue("SkiaTracingFeature__use_skia_tracing", sysPropVal, false);
+}
+
+} // namespace android
diff --git a/services/surfaceflinger/FlagManager.h b/services/surfaceflinger/FlagManager.h
new file mode 100644
index 0000000..e834142
--- /dev/null
+++ b/services/surfaceflinger/FlagManager.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <cstdint>
+#include <optional>
+#include <string>
+
+namespace android {
+// Manages flags for SurfaceFlinger, including default values, system properties, and Mendel
+// experiment configuration values.
+class FlagManager {
+public:
+    FlagManager() = default;
+    virtual ~FlagManager();
+    void dump(std::string& result) const;
+
+    int64_t demo_flag() const;
+
+    bool use_adpf_cpu_hint() const;
+
+    bool use_skia_tracing() const;
+
+private:
+    friend class FlagManagerTest;
+
+    // Wrapper for mocking in test.
+    virtual std::string getServerConfigurableFlag(const std::string& experimentFlagName) const;
+
+    template <typename T>
+    T getValue(const std::string& experimentFlagName, std::optional<T> systemPropertyOpt,
+               T defaultValue) const;
+};
+} // namespace android
diff --git a/services/surfaceflinger/Fps.h b/services/surfaceflinger/Fps.h
deleted file mode 100644
index e9f06e5..0000000
--- a/services/surfaceflinger/Fps.h
+++ /dev/null
@@ -1,112 +0,0 @@
-/*
- * Copyright (C) 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.
- */
-
-#pragma once
-
-#include <cmath>
-#include <ostream>
-#include <string>
-
-#include <android-base/stringprintf.h>
-#include <utils/Timers.h>
-
-namespace android {
-
-// Value which represents "frames per second". This class is a wrapper around
-// float, providing some useful utilities, such as comparisons with tolerance
-// and converting between period duration and frequency.
-class Fps {
-public:
-    static constexpr Fps fromPeriodNsecs(nsecs_t period) { return Fps(1e9f / period, period); }
-
-    Fps() = default;
-    explicit constexpr Fps(float fps)
-          : fps(fps), period(fps == 0.0f ? 0 : static_cast<nsecs_t>(1e9f / fps)) {}
-
-    constexpr float getValue() const { return fps; }
-
-    constexpr nsecs_t getPeriodNsecs() const { return period; }
-
-    bool equalsWithMargin(const Fps& other) const { return std::abs(fps - other.fps) < kMargin; }
-
-    // DO NOT use for std::sort. Instead use comparesLess().
-    bool lessThanWithMargin(const Fps& other) const { return fps + kMargin < other.fps; }
-
-    bool greaterThanWithMargin(const Fps& other) const { return fps > other.fps + kMargin; }
-
-    bool lessThanOrEqualWithMargin(const Fps& other) const { return !greaterThanWithMargin(other); }
-
-    bool greaterThanOrEqualWithMargin(const Fps& other) const { return !lessThanWithMargin(other); }
-
-    bool isValid() const { return fps > 0.0f; }
-
-    int getIntValue() const { return static_cast<int>(std::round(fps)); }
-
-    // Use this comparator for sorting. Using a comparator with margins can
-    // cause std::sort to crash.
-    inline static bool comparesLess(const Fps& left, const Fps& right) {
-        return left.fps < right.fps;
-    }
-
-    // Compares two FPS with margin.
-    // Transitivity is not guaranteed, i.e. a==b and b==c doesn't imply a==c.
-    // DO NOT use with hash maps. Instead use EqualsInBuckets.
-    struct EqualsWithMargin {
-        bool operator()(const Fps& left, const Fps& right) const {
-            return left.equalsWithMargin(right);
-        }
-    };
-
-    // Equals comparator which can be used with hash maps.
-    // It's guaranteed that if two elements are equal, then their hashes are equal.
-    struct EqualsInBuckets {
-        bool operator()(const Fps& left, const Fps& right) const {
-            return left.getBucket() == right.getBucket();
-        }
-    };
-
-    inline friend std::string to_string(const Fps& fps) {
-        return base::StringPrintf("%.2ffps", fps.fps);
-    }
-
-    inline friend std::ostream& operator<<(std::ostream& os, const Fps& fps) {
-        return os << to_string(fps);
-    }
-
-private:
-    friend std::hash<android::Fps>;
-
-    constexpr Fps(float fps, nsecs_t period) : fps(fps), period(period) {}
-
-    float getBucket() const { return std::round(fps / kMargin); }
-
-    static constexpr float kMargin = 0.001f;
-    float fps = 0;
-    nsecs_t period = 0;
-};
-
-static_assert(std::is_trivially_copyable_v<Fps>);
-
-} // namespace android
-
-namespace std {
-template <>
-struct hash<android::Fps> {
-    std::size_t operator()(const android::Fps& fps) const {
-        return std::hash<float>()(fps.getBucket());
-    }
-};
-} // namespace std
\ No newline at end of file
diff --git a/services/surfaceflinger/FpsReporter.h b/services/surfaceflinger/FpsReporter.h
index bd7b9a5..438b1aa 100644
--- a/services/surfaceflinger/FpsReporter.h
+++ b/services/surfaceflinger/FpsReporter.h
@@ -24,6 +24,7 @@
 
 #include "Clock.h"
 #include "FrameTimeline/FrameTimeline.h"
+#include "WpHash.h"
 
 namespace android {
 
@@ -50,11 +51,6 @@
 
 private:
     mutable std::mutex mMutex;
-    struct WpHash {
-        size_t operator()(const wp<IBinder>& p) const {
-            return std::hash<IBinder*>()(p.unsafe_get());
-        }
-    };
 
     struct TrackedListener {
         sp<gui::IFpsListener> listener;
diff --git a/services/surfaceflinger/FrameTimeline/Android.bp b/services/surfaceflinger/FrameTimeline/Android.bp
index 10a5833..2d4ec04 100644
--- a/services/surfaceflinger/FrameTimeline/Android.bp
+++ b/services/surfaceflinger/FrameTimeline/Android.bp
@@ -13,6 +13,9 @@
     srcs: [
         "FrameTimeline.cpp",
     ],
+    header_libs: [
+        "libscheduler_headers",
+    ],
     shared_libs: [
         "android.hardware.graphics.composer@2.4",
         "libbase",
diff --git a/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp b/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp
index c294ff2..66beff2 100644
--- a/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp
+++ b/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp
@@ -304,7 +304,7 @@
                            frametimeline::TimelineItem&& predictions,
                            std::shared_ptr<TimeStats> timeStats,
                            JankClassificationThresholds thresholds,
-                           TraceCookieCounter* traceCookieCounter, bool isBuffer, int32_t gameMode)
+                           TraceCookieCounter* traceCookieCounter, bool isBuffer, GameMode gameMode)
       : mToken(frameTimelineInfo.vsyncId),
         mInputEventId(frameTimelineInfo.inputEventId),
         mOwnerPid(ownerPid),
@@ -492,17 +492,22 @@
 
 void SurfaceFrame::classifyJankLocked(int32_t displayFrameJankType, const Fps& refreshRate,
                                       nsecs_t& deadlineDelta) {
-    if (mPredictionState == PredictionState::Expired ||
-        mActuals.presentTime == Fence::SIGNAL_TIME_INVALID) {
+    if (mActuals.presentTime == Fence::SIGNAL_TIME_INVALID) {
         // Cannot do any classification for invalid present time.
-        // For prediction expired case, we do not know what happened here to classify this
-        // correctly. This could potentially be AppDeadlineMissed but that's assuming no app will
-        // request frames 120ms apart.
         mJankType = JankType::Unknown;
         deadlineDelta = -1;
         return;
     }
 
+    if (mPredictionState == PredictionState::Expired) {
+        // We classify prediction expired as AppDeadlineMissed as the
+        // TokenManager::kMaxTokens we store is large enough to account for a
+        // reasonable app, so prediction expire would mean a huge scheduling delay.
+        mJankType = JankType::AppDeadlineMissed;
+        deadlineDelta = -1;
+        return;
+    }
+
     if (mPredictionState == PredictionState::None) {
         // Cannot do jank classification on frames that don't have a token.
         return;
@@ -613,15 +618,15 @@
     }
 }
 
-void SurfaceFrame::tracePredictions(int64_t displayFrameToken) const {
+void SurfaceFrame::tracePredictions(int64_t displayFrameToken, nsecs_t monoBootOffset) const {
     int64_t expectedTimelineCookie = mTraceCookieCounter.getCookieForTracing();
 
     // Expected timeline start
     FrameTimelineDataSource::Trace([&](FrameTimelineDataSource::TraceContext ctx) {
         std::scoped_lock lock(mMutex);
         auto packet = ctx.NewTracePacket();
-        packet->set_timestamp_clock_id(perfetto::protos::pbzero::BUILTIN_CLOCK_MONOTONIC);
-        packet->set_timestamp(static_cast<uint64_t>(mPredictions.startTime));
+        packet->set_timestamp_clock_id(perfetto::protos::pbzero::BUILTIN_CLOCK_BOOTTIME);
+        packet->set_timestamp(static_cast<uint64_t>(mPredictions.startTime + monoBootOffset));
 
         auto* event = packet->set_frame_timeline_event();
         auto* expectedSurfaceFrameStartEvent = event->set_expected_surface_frame_start();
@@ -639,8 +644,8 @@
     FrameTimelineDataSource::Trace([&](FrameTimelineDataSource::TraceContext ctx) {
         std::scoped_lock lock(mMutex);
         auto packet = ctx.NewTracePacket();
-        packet->set_timestamp_clock_id(perfetto::protos::pbzero::BUILTIN_CLOCK_MONOTONIC);
-        packet->set_timestamp(static_cast<uint64_t>(mPredictions.endTime));
+        packet->set_timestamp_clock_id(perfetto::protos::pbzero::BUILTIN_CLOCK_BOOTTIME);
+        packet->set_timestamp(static_cast<uint64_t>(mPredictions.endTime + monoBootOffset));
 
         auto* event = packet->set_frame_timeline_event();
         auto* expectedSurfaceFrameEndEvent = event->set_frame_end();
@@ -649,14 +654,14 @@
     });
 }
 
-void SurfaceFrame::traceActuals(int64_t displayFrameToken) const {
+void SurfaceFrame::traceActuals(int64_t displayFrameToken, nsecs_t monoBootOffset) const {
     int64_t actualTimelineCookie = mTraceCookieCounter.getCookieForTracing();
 
     // Actual timeline start
     FrameTimelineDataSource::Trace([&](FrameTimelineDataSource::TraceContext ctx) {
         std::scoped_lock lock(mMutex);
         auto packet = ctx.NewTracePacket();
-        packet->set_timestamp_clock_id(perfetto::protos::pbzero::BUILTIN_CLOCK_MONOTONIC);
+        packet->set_timestamp_clock_id(perfetto::protos::pbzero::BUILTIN_CLOCK_BOOTTIME);
         // Actual start time is not yet available, so use expected start instead
         if (mPredictionState == PredictionState::Expired) {
             // If prediction is expired, we can't use the predicted start time. Instead, just use a
@@ -664,10 +669,12 @@
             // frame in the trace.
             nsecs_t endTime =
                     (mPresentState == PresentState::Dropped ? mDropTime : mActuals.endTime);
-            packet->set_timestamp(
-                    static_cast<uint64_t>(endTime - kPredictionExpiredStartTimeDelta));
+            const auto timestamp = endTime - kPredictionExpiredStartTimeDelta;
+            packet->set_timestamp(static_cast<uint64_t>(timestamp + monoBootOffset));
         } else {
-            packet->set_timestamp(static_cast<uint64_t>(mPredictions.startTime));
+            const auto timestamp =
+                    mActuals.startTime == 0 ? mPredictions.startTime : mActuals.startTime;
+            packet->set_timestamp(static_cast<uint64_t>(timestamp + monoBootOffset));
         }
 
         auto* event = packet->set_frame_timeline_event();
@@ -700,11 +707,11 @@
     FrameTimelineDataSource::Trace([&](FrameTimelineDataSource::TraceContext ctx) {
         std::scoped_lock lock(mMutex);
         auto packet = ctx.NewTracePacket();
-        packet->set_timestamp_clock_id(perfetto::protos::pbzero::BUILTIN_CLOCK_MONOTONIC);
+        packet->set_timestamp_clock_id(perfetto::protos::pbzero::BUILTIN_CLOCK_BOOTTIME);
         if (mPresentState == PresentState::Dropped) {
-            packet->set_timestamp(static_cast<uint64_t>(mDropTime));
+            packet->set_timestamp(static_cast<uint64_t>(mDropTime + monoBootOffset));
         } else {
-            packet->set_timestamp(static_cast<uint64_t>(mActuals.endTime));
+            packet->set_timestamp(static_cast<uint64_t>(mActuals.endTime + monoBootOffset));
         }
 
         auto* event = packet->set_frame_timeline_event();
@@ -717,7 +724,7 @@
 /**
  * TODO(b/178637512): add inputEventId to the perfetto trace.
  */
-void SurfaceFrame::trace(int64_t displayFrameToken) const {
+void SurfaceFrame::trace(int64_t displayFrameToken, nsecs_t monoBootOffset) const {
     if (mToken == FrameTimelineInfo::INVALID_VSYNC_ID ||
         displayFrameToken == FrameTimelineInfo::INVALID_VSYNC_ID) {
         // No packets can be traced with a missing token.
@@ -726,9 +733,9 @@
     if (getPredictionState() != PredictionState::Expired) {
         // Expired predictions have zeroed timestamps. This cannot be used in any meaningful way in
         // a trace.
-        tracePredictions(displayFrameToken);
+        tracePredictions(displayFrameToken, monoBootOffset);
     }
-    traceActuals(displayFrameToken);
+    traceActuals(displayFrameToken, monoBootOffset);
 }
 
 namespace impl {
@@ -754,8 +761,9 @@
 }
 
 FrameTimeline::FrameTimeline(std::shared_ptr<TimeStats> timeStats, pid_t surfaceFlingerPid,
-                             JankClassificationThresholds thresholds)
-      : mMaxDisplayFrames(kDefaultMaxDisplayFrames),
+                             JankClassificationThresholds thresholds, bool useBootTimeClock)
+      : mUseBootTimeClock(useBootTimeClock),
+        mMaxDisplayFrames(kDefaultMaxDisplayFrames),
         mTimeStats(std::move(timeStats)),
         mSurfaceFlingerPid(surfaceFlingerPid),
         mJankClassificationThresholds(thresholds) {
@@ -778,7 +786,7 @@
 
 std::shared_ptr<SurfaceFrame> FrameTimeline::createSurfaceFrameForToken(
         const FrameTimelineInfo& frameTimelineInfo, pid_t ownerPid, uid_t ownerUid, int32_t layerId,
-        std::string layerName, std::string debugName, bool isBuffer, int32_t gameMode) {
+        std::string layerName, std::string debugName, bool isBuffer, GameMode gameMode) {
     ATRACE_CALL();
     if (frameTimelineInfo.vsyncId == FrameTimelineInfo::INVALID_VSYNC_ID) {
         return std::make_shared<SurfaceFrame>(frameTimelineInfo, ownerPid, ownerUid, layerId,
@@ -1010,14 +1018,16 @@
     }
 }
 
-void FrameTimeline::DisplayFrame::tracePredictions(pid_t surfaceFlingerPid) const {
+void FrameTimeline::DisplayFrame::tracePredictions(pid_t surfaceFlingerPid,
+                                                   nsecs_t monoBootOffset) const {
     int64_t expectedTimelineCookie = mTraceCookieCounter.getCookieForTracing();
 
     // Expected timeline start
     FrameTimelineDataSource::Trace([&](FrameTimelineDataSource::TraceContext ctx) {
         auto packet = ctx.NewTracePacket();
-        packet->set_timestamp_clock_id(perfetto::protos::pbzero::BUILTIN_CLOCK_MONOTONIC);
-        packet->set_timestamp(static_cast<uint64_t>(mSurfaceFlingerPredictions.startTime));
+        packet->set_timestamp_clock_id(perfetto::protos::pbzero::BUILTIN_CLOCK_BOOTTIME);
+        packet->set_timestamp(
+                static_cast<uint64_t>(mSurfaceFlingerPredictions.startTime + monoBootOffset));
 
         auto* event = packet->set_frame_timeline_event();
         auto* expectedDisplayFrameStartEvent = event->set_expected_display_frame_start();
@@ -1031,8 +1041,9 @@
     // Expected timeline end
     FrameTimelineDataSource::Trace([&](FrameTimelineDataSource::TraceContext ctx) {
         auto packet = ctx.NewTracePacket();
-        packet->set_timestamp_clock_id(perfetto::protos::pbzero::BUILTIN_CLOCK_MONOTONIC);
-        packet->set_timestamp(static_cast<uint64_t>(mSurfaceFlingerPredictions.endTime));
+        packet->set_timestamp_clock_id(perfetto::protos::pbzero::BUILTIN_CLOCK_BOOTTIME);
+        packet->set_timestamp(
+                static_cast<uint64_t>(mSurfaceFlingerPredictions.endTime + monoBootOffset));
 
         auto* event = packet->set_frame_timeline_event();
         auto* expectedDisplayFrameEndEvent = event->set_frame_end();
@@ -1041,14 +1052,16 @@
     });
 }
 
-void FrameTimeline::DisplayFrame::traceActuals(pid_t surfaceFlingerPid) const {
+void FrameTimeline::DisplayFrame::traceActuals(pid_t surfaceFlingerPid,
+                                               nsecs_t monoBootOffset) const {
     int64_t actualTimelineCookie = mTraceCookieCounter.getCookieForTracing();
 
     // Actual timeline start
     FrameTimelineDataSource::Trace([&](FrameTimelineDataSource::TraceContext ctx) {
         auto packet = ctx.NewTracePacket();
-        packet->set_timestamp_clock_id(perfetto::protos::pbzero::BUILTIN_CLOCK_MONOTONIC);
-        packet->set_timestamp(static_cast<uint64_t>(mSurfaceFlingerActuals.startTime));
+        packet->set_timestamp_clock_id(perfetto::protos::pbzero::BUILTIN_CLOCK_BOOTTIME);
+        packet->set_timestamp(
+                static_cast<uint64_t>(mSurfaceFlingerActuals.startTime + monoBootOffset));
 
         auto* event = packet->set_frame_timeline_event();
         auto* actualDisplayFrameStartEvent = event->set_actual_display_frame_start();
@@ -1069,8 +1082,9 @@
     // Actual timeline end
     FrameTimelineDataSource::Trace([&](FrameTimelineDataSource::TraceContext ctx) {
         auto packet = ctx.NewTracePacket();
-        packet->set_timestamp_clock_id(perfetto::protos::pbzero::BUILTIN_CLOCK_MONOTONIC);
-        packet->set_timestamp(static_cast<uint64_t>(mSurfaceFlingerActuals.presentTime));
+        packet->set_timestamp_clock_id(perfetto::protos::pbzero::BUILTIN_CLOCK_BOOTTIME);
+        packet->set_timestamp(
+                static_cast<uint64_t>(mSurfaceFlingerActuals.presentTime + monoBootOffset));
 
         auto* event = packet->set_frame_timeline_event();
         auto* actualDisplayFrameEndEvent = event->set_frame_end();
@@ -1079,7 +1093,7 @@
     });
 }
 
-void FrameTimeline::DisplayFrame::trace(pid_t surfaceFlingerPid) const {
+void FrameTimeline::DisplayFrame::trace(pid_t surfaceFlingerPid, nsecs_t monoBootOffset) const {
     if (mToken == FrameTimelineInfo::INVALID_VSYNC_ID) {
         // DisplayFrame should not have an invalid token.
         ALOGE("Cannot trace DisplayFrame with invalid token");
@@ -1089,12 +1103,12 @@
     if (mPredictionState == PredictionState::Valid) {
         // Expired and unknown predictions have zeroed timestamps. This cannot be used in any
         // meaningful way in a trace.
-        tracePredictions(surfaceFlingerPid);
+        tracePredictions(surfaceFlingerPid, monoBootOffset);
     }
-    traceActuals(surfaceFlingerPid);
+    traceActuals(surfaceFlingerPid, monoBootOffset);
 
     for (auto& surfaceFrame : mSurfaceFrames) {
-        surfaceFrame->trace(mToken);
+        surfaceFrame->trace(mToken, monoBootOffset);
     }
 }
 
@@ -1158,6 +1172,12 @@
 }
 
 void FrameTimeline::flushPendingPresentFences() {
+    // Perfetto is using boottime clock to void drifts when the device goes
+    // to suspend.
+    const auto monoBootOffset = mUseBootTimeClock
+            ? (systemTime(SYSTEM_TIME_BOOTTIME) - systemTime(SYSTEM_TIME_MONOTONIC))
+            : 0;
+
     for (size_t i = 0; i < mPendingPresentFences.size(); i++) {
         const auto& pendingPresentFence = mPendingPresentFences[i];
         nsecs_t signalTime = Fence::SIGNAL_TIME_INVALID;
@@ -1169,7 +1189,7 @@
         }
         auto& displayFrame = pendingPresentFence.second;
         displayFrame->onPresent(signalTime, mPreviousPresentTime);
-        displayFrame->trace(mSurfaceFlingerPid);
+        displayFrame->trace(mSurfaceFlingerPid, monoBootOffset);
         mPreviousPresentTime = signalTime;
 
         mPendingPresentFences.erase(mPendingPresentFences.begin() + static_cast<int>(i));
diff --git a/services/surfaceflinger/FrameTimeline/FrameTimeline.h b/services/surfaceflinger/FrameTimeline/FrameTimeline.h
index 139f91f..a2305af 100644
--- a/services/surfaceflinger/FrameTimeline/FrameTimeline.h
+++ b/services/surfaceflinger/FrameTimeline/FrameTimeline.h
@@ -16,10 +16,17 @@
 
 #pragma once
 
-#include <../Fps.h>
-#include <../TimeStats/TimeStats.h>
+#include <atomic>
+#include <chrono>
+#include <deque>
+#include <memory>
+#include <mutex>
+#include <optional>
+#include <string>
+
 #include <gui/ISurfaceComposer.h>
 #include <gui/JankInfo.h>
+#include <gui/LayerMetadata.h>
 #include <perfetto/trace/android/frame_timeline_event.pbzero.h>
 #include <perfetto/tracing.h>
 #include <ui/FenceTime.h>
@@ -28,8 +35,9 @@
 #include <utils/Timers.h>
 #include <utils/Vector.h>
 
-#include <deque>
-#include <mutex>
+#include <scheduler/Fps.h>
+
+#include "../TimeStats/TimeStats.h"
 
 namespace android::frametimeline {
 
@@ -154,7 +162,7 @@
                  int32_t layerId, std::string layerName, std::string debugName,
                  PredictionState predictionState, TimelineItem&& predictions,
                  std::shared_ptr<TimeStats> timeStats, JankClassificationThresholds thresholds,
-                 TraceCookieCounter* traceCookieCounter, bool isBuffer, int32_t gameMode);
+                 TraceCookieCounter* traceCookieCounter, bool isBuffer, GameMode);
     ~SurfaceFrame() = default;
 
     // Returns std::nullopt if the frame hasn't been classified yet.
@@ -196,8 +204,9 @@
     std::string miniDump() const;
     // Emits a packet for perfetto tracing. The function body will be executed only if tracing is
     // enabled. The displayFrameToken is needed to link the SurfaceFrame to the corresponding
-    // DisplayFrame at the trace processor side.
-    void trace(int64_t displayFrameToken) const;
+    // DisplayFrame at the trace processor side. monoBootOffset is the difference
+    // between SYSTEM_TIME_BOOTTIME and SYSTEM_TIME_MONOTONIC.
+    void trace(int64_t displayFrameToken, nsecs_t monoBootOffset) const;
 
     // Getter functions used only by FrameTimelineTests and SurfaceFrame internally
     TimelineItem getActuals() const;
@@ -217,8 +226,8 @@
             std::chrono::duration_cast<std::chrono::nanoseconds>(2ms).count();
 
 private:
-    void tracePredictions(int64_t displayFrameToken) const;
-    void traceActuals(int64_t displayFrameToken) const;
+    void tracePredictions(int64_t displayFrameToken, nsecs_t monoBootOffset) const;
+    void traceActuals(int64_t displayFrameToken, nsecs_t monoBootOffset) const;
     void classifyJankLocked(int32_t displayFrameJankType, const Fps& refreshRate,
                             nsecs_t& deadlineDelta) REQUIRES(mMutex);
 
@@ -260,7 +269,7 @@
     // buffer(animations)
     bool mIsBuffer;
     // GameMode from the layer. Used in metrics.
-    int32_t mGameMode = 0;
+    GameMode mGameMode = GameMode::Unsupported;
 };
 
 /*
@@ -281,7 +290,7 @@
     virtual std::shared_ptr<SurfaceFrame> createSurfaceFrameForToken(
             const FrameTimelineInfo& frameTimelineInfo, pid_t ownerPid, uid_t ownerUid,
             int32_t layerId, std::string layerName, std::string debugName, bool isBuffer,
-            int32_t gameMode) = 0;
+            GameMode) = 0;
 
     // Adds a new SurfaceFrame to the current DisplayFrame. Frames from multiple layers can be
     // composited into one display frame.
@@ -361,8 +370,9 @@
         // Dumpsys interface - dumps all data irrespective of jank
         void dumpAll(std::string& result, nsecs_t baseTime) const;
         // Emits a packet for perfetto tracing. The function body will be executed only if tracing
-        // is enabled.
-        void trace(pid_t surfaceFlingerPid) const;
+        // is enabled. monoBootOffset is the difference between SYSTEM_TIME_BOOTTIME
+        // and SYSTEM_TIME_MONOTONIC.
+        void trace(pid_t surfaceFlingerPid, nsecs_t monoBootOffset) const;
         // Sets the token, vsyncPeriod, predictions and SF start time.
         void onSfWakeUp(int64_t token, Fps refreshRate, std::optional<TimelineItem> predictions,
                         nsecs_t wakeUpTime);
@@ -393,8 +403,8 @@
 
     private:
         void dump(std::string& result, nsecs_t baseTime) const;
-        void tracePredictions(pid_t surfaceFlingerPid) const;
-        void traceActuals(pid_t surfaceFlingerPid) const;
+        void tracePredictions(pid_t surfaceFlingerPid, nsecs_t monoBootOffset) const;
+        void traceActuals(pid_t surfaceFlingerPid, nsecs_t monoBootOffset) const;
         void classifyJank(nsecs_t& deadlineDelta, nsecs_t& deltaToVsync,
                           nsecs_t previousPresentTime);
 
@@ -434,14 +444,14 @@
     };
 
     FrameTimeline(std::shared_ptr<TimeStats> timeStats, pid_t surfaceFlingerPid,
-                  JankClassificationThresholds thresholds = {});
+                  JankClassificationThresholds thresholds = {}, bool useBootTimeClock = true);
     ~FrameTimeline() = default;
 
     frametimeline::TokenManager* getTokenManager() override { return &mTokenManager; }
     std::shared_ptr<SurfaceFrame> createSurfaceFrameForToken(
             const FrameTimelineInfo& frameTimelineInfo, pid_t ownerPid, uid_t ownerUid,
             int32_t layerId, std::string layerName, std::string debugName, bool isBuffer,
-            int32_t gameMode) override;
+            GameMode) override;
     void addSurfaceFrame(std::shared_ptr<frametimeline::SurfaceFrame> surfaceFrame) override;
     void setSfWakeUp(int64_t token, nsecs_t wakeupTime, Fps refreshRate) override;
     void setSfPresent(nsecs_t sfPresentTime, const std::shared_ptr<FenceTime>& presentFence,
@@ -476,6 +486,7 @@
     TokenManager mTokenManager;
     TraceCookieCounter mTraceCookieCounter;
     mutable std::mutex mMutex;
+    const bool mUseBootTimeClock;
     uint32_t mMaxDisplayFrames;
     std::shared_ptr<TimeStats> mTimeStats;
     const pid_t mSurfaceFlingerPid;
diff --git a/services/surfaceflinger/HdrLayerInfoReporter.h b/services/surfaceflinger/HdrLayerInfoReporter.h
index 671395f..4ada2b6 100644
--- a/services/surfaceflinger/HdrLayerInfoReporter.h
+++ b/services/surfaceflinger/HdrLayerInfoReporter.h
@@ -22,6 +22,8 @@
 
 #include <unordered_map>
 
+#include "WpHash.h"
+
 namespace android {
 
 class HdrLayerInfoReporter final : public IBinder::DeathRecipient {
@@ -63,11 +65,6 @@
 
 private:
     mutable std::mutex mMutex;
-    struct WpHash {
-        size_t operator()(const wp<IBinder>& p) const {
-            return std::hash<IBinder*>()(p.unsafe_get());
-        }
-    };
 
     struct TrackedListener {
         sp<gui::IHdrLayerInfoListener> listener;
diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp
index da7ff71..fa5a1c8 100644
--- a/services/surfaceflinger/Layer.cpp
+++ b/services/surfaceflinger/Layer.cpp
@@ -36,6 +36,7 @@
 #include <cutils/compiler.h>
 #include <cutils/native_handle.h>
 #include <cutils/properties.h>
+#include <ftl/enum.h>
 #include <gui/BufferItem.h>
 #include <gui/LayerDebugInfo.h>
 #include <gui/Surface.h>
@@ -45,6 +46,7 @@
 #include <stdint.h>
 #include <stdlib.h>
 #include <sys/types.h>
+#include <ui/DataspaceUtils.h>
 #include <ui/DebugUtils.h>
 #include <ui/GraphicBuffer.h>
 #include <ui/PixelFormat.h>
@@ -87,27 +89,28 @@
 std::atomic<int32_t> Layer::sSequence{1};
 
 Layer::Layer(const LayerCreationArgs& args)
-      : mFlinger(args.flinger),
-        mName(args.name),
+      : sequence(args.sequence.value_or(sSequence++)),
+        mFlinger(args.flinger),
+        mName(base::StringPrintf("%s#%d", args.name.c_str(), sequence)),
         mClientRef(args.client),
-        mWindowType(
-                static_cast<WindowInfo::Type>(args.metadata.getInt32(METADATA_WINDOW_TYPE, 0))) {
+        mWindowType(static_cast<WindowInfo::Type>(args.metadata.getInt32(METADATA_WINDOW_TYPE, 0))),
+        mLayerCreationFlags(args.flags) {
     uint32_t layerFlags = 0;
     if (args.flags & ISurfaceComposerClient::eHidden) layerFlags |= layer_state_t::eLayerHidden;
     if (args.flags & ISurfaceComposerClient::eOpaque) layerFlags |= layer_state_t::eLayerOpaque;
     if (args.flags & ISurfaceComposerClient::eSecure) layerFlags |= layer_state_t::eLayerSecure;
     if (args.flags & ISurfaceComposerClient::eSkipScreenshot)
         layerFlags |= layer_state_t::eLayerSkipScreenshot;
-
-    mDrawingState.active_legacy.w = args.w;
-    mDrawingState.active_legacy.h = args.h;
+    if (args.sequence) {
+        sSequence = *args.sequence + 1;
+    }
     mDrawingState.flags = layerFlags;
     mDrawingState.active_legacy.transform.set(0, 0);
     mDrawingState.crop.makeInvalid();
     mDrawingState.requestedCrop = mDrawingState.crop;
     mDrawingState.z = 0;
     mDrawingState.color.a = 1.0f;
-    mDrawingState.layerStack = 0;
+    mDrawingState.layerStack = ui::DEFAULT_LAYER_STACK;
     mDrawingState.sequence = 0;
     mDrawingState.requested_legacy = mDrawingState.active_legacy;
     mDrawingState.width = UINT32_MAX;
@@ -185,12 +188,10 @@
 }
 
 LayerCreationArgs::LayerCreationArgs(SurfaceFlinger* flinger, sp<Client> client, std::string name,
-                                     uint32_t w, uint32_t h, uint32_t flags, LayerMetadata metadata)
+                                     uint32_t flags, LayerMetadata metadata)
       : flinger(flinger),
         client(std::move(client)),
         name(std::move(name)),
-        w(w),
-        h(h),
         flags(flags),
         metadata(std::move(metadata)) {
     IPCThreadState* ipc = IPCThreadState::self();
@@ -207,7 +208,8 @@
  * Layer.  So, the implementation is done in BufferLayer.  When called on a
  * EffectLayer object, it's essentially a NOP.
  */
-void Layer::onLayerDisplayed(const sp<Fence>& /*releaseFence*/) {}
+void Layer::onLayerDisplayed(
+        std::shared_future<renderengine::RenderEngineResult> /*futureRenderEngineResult*/) {}
 
 void Layer::removeRelativeZ(const std::vector<Layer*>& layersInTree) {
     if (mDrawingState.zOrderRelativeOf == nullptr) {
@@ -336,6 +338,12 @@
     // Calculate effective layer transform
     mEffectiveTransform = parentTransform * getActiveTransform(s);
 
+    if (CC_UNLIKELY(!isTransformValid())) {
+        ALOGW("Stop computing bounds for %s because it has invalid transformation.",
+              getDebugName());
+        return;
+    }
+
     // Transform parent bounds to layer space
     parentBounds = getActiveTransform(s).inverse().transform(parentBounds);
 
@@ -393,7 +401,6 @@
 
 void Layer::prepareBasicGeometryCompositionState() {
     const auto& drawingState{getDrawingState()};
-    const uint32_t layerStack = getLayerStack();
     const auto alpha = static_cast<float>(getAlpha());
     const bool opaque = isOpaque(drawingState);
     const bool usesRoundedCorners = getRoundedCornerState().radius != 0.f;
@@ -405,9 +412,7 @@
     }
 
     auto* compositionState = editCompositionState();
-    compositionState->layerStackId =
-            (layerStack != ~0u) ? std::make_optional(layerStack) : std::nullopt;
-    compositionState->internalOnly = getPrimaryDisplayOnly();
+    compositionState->outputFilter = getOutputFilter();
     compositionState->isVisible = isVisible();
     compositionState->isOpaque = opaque && !usesRoundedCorners && alpha == 1.f;
     compositionState->shadowRadius = mEffectiveShadowRadius;
@@ -422,25 +427,13 @@
 
     compositionState->blendMode = static_cast<Hwc2::IComposerClient::BlendMode>(blendMode);
     compositionState->alpha = alpha;
+    compositionState->backgroundBlurRadius = drawingState.backgroundBlurRadius;
+    compositionState->blurRegions = drawingState.blurRegions;
     compositionState->stretchEffect = getStretchEffect();
 }
 
 void Layer::prepareGeometryCompositionState() {
     const auto& drawingState{getDrawingState()};
-
-    int type = drawingState.metadata.getInt32(METADATA_WINDOW_TYPE, 0);
-    int appId = drawingState.metadata.getInt32(METADATA_OWNER_UID, 0);
-    sp<Layer> parent = mDrawingParent.promote();
-    if (parent.get()) {
-        auto& parentState = parent->getDrawingState();
-        const int parentType = parentState.metadata.getInt32(METADATA_WINDOW_TYPE, 0);
-        const int parentAppId = parentState.metadata.getInt32(METADATA_OWNER_UID, 0);
-        if (parentType > 0 && parentAppId > 0) {
-            type = parentType;
-            appId = parentAppId;
-        }
-    }
-
     auto* compositionState = editCompositionState();
 
     compositionState->geomBufferSize = getBufferSize(drawingState);
@@ -500,6 +493,11 @@
     // If there are no visible region changes, we still need to update blur parameters.
     compositionState->blurRegions = drawingState.blurRegions;
     compositionState->backgroundBlurRadius = drawingState.backgroundBlurRadius;
+
+    // Layer framerate is used in caching decisions.
+    // Retrieve it from the scheduler which maintains an instance of LayerHistory, and store it in
+    // LayerFECompositionState where it would be visible to Flattener.
+    compositionState->fps = mFlinger->getLayerFramerate(systemTime(), getSequence());
 }
 
 void Layer::prepareCursorCompositionState() {
@@ -596,6 +594,8 @@
 
     layerSettings.alpha = alpha;
     layerSettings.sourceDataspace = getDataSpace();
+
+    layerSettings.whitePointNits = targetSettings.whitePointNits;
     switch (targetSettings.blurSetting) {
         case LayerFE::ClientCompositionTargetSettings::BlurSetting::Enabled:
             layerSettings.backgroundBlurRadius = getBackgroundBlurRadius();
@@ -658,15 +658,16 @@
     return {*layerSettings};
 }
 
-Hwc2::IComposerClient::Composition Layer::getCompositionType(const DisplayDevice& display) const {
+aidl::android::hardware::graphics::composer3::Composition Layer::getCompositionType(
+        const DisplayDevice& display) const {
     const auto outputLayer = findOutputLayerForDisplay(&display);
     if (outputLayer == nullptr) {
-        return Hwc2::IComposerClient::Composition::INVALID;
+        return aidl::android::hardware::graphics::composer3::Composition::INVALID;
     }
     if (outputLayer->getState().hwc) {
         return (*outputLayer->getState().hwc).hwcCompositionType;
     } else {
-        return Hwc2::IComposerClient::Composition::CLIENT;
+        return aidl::android::hardware::graphics::composer3::Composition::CLIENT;
     }
 }
 
@@ -731,14 +732,14 @@
     mDrawingState.bufferlessSurfaceFramesTX.clear();
 }
 
-uint32_t Layer::getTransactionFlags(uint32_t flags) {
-    auto ret = mTransactionFlags & flags;
-    mTransactionFlags &= ~flags;
-    return ret;
+uint32_t Layer::clearTransactionFlags(uint32_t mask) {
+    const auto flags = mTransactionFlags & mask;
+    mTransactionFlags &= ~mask;
+    return flags;
 }
 
-uint32_t Layer::setTransactionFlags(uint32_t flags) {
-    return mTransactionFlags |= flags;
+void Layer::setTransactionFlags(uint32_t mask) {
+    mTransactionFlags |= mask;
 }
 
 bool Layer::setPosition(float x, float y) {
@@ -832,6 +833,14 @@
         return false;
     }
 
+    if (CC_UNLIKELY(relative->usingRelativeZ(LayerVector::StateSet::Drawing)) &&
+        (relative->mDrawingState.zOrderRelativeOf == this)) {
+        ALOGE("Detected relative layer loop between %s and %s",
+              mName.c_str(), relative->mName.c_str());
+        ALOGE("Ignoring new call to set relative layer");
+        return false;
+    }
+
     mFlinger->mSomeChildrenChanged = true;
 
     mDrawingState.sequence++;
@@ -903,7 +912,7 @@
         uint32_t flags = ISurfaceComposerClient::eFXSurfaceEffect;
         std::string name = mName + "BackgroundColorLayer";
         mDrawingState.bgColorLayer = mFlinger->getFactory().createEffectLayer(
-                LayerCreationArgs(mFlinger.get(), nullptr, std::move(name), 0, 0, flags,
+                LayerCreationArgs(mFlinger.get(), nullptr, std::move(name), flags,
                                   LayerMetadata()));
 
         // add to child list
@@ -950,17 +959,10 @@
     setTransactionFlags(eTransactionNeeded);
     return true;
 }
-
-bool Layer::setMatrix(const layer_state_t::matrix22_t& matrix,
-        bool allowNonRectPreservingTransforms) {
+bool Layer::setMatrix(const layer_state_t::matrix22_t& matrix) {
     ui::Transform t;
     t.set(matrix.dsdx, matrix.dtdy, matrix.dtdx, matrix.dsdy);
 
-    if (!allowNonRectPreservingTransforms && !t.preserveRects()) {
-        ALOGW("Attempt to set rotation matrix without permission ACCESS_SURFACE_FLINGER nor "
-              "ROTATE_SURFACE_FLINGER ignored");
-        return false;
-    }
     mDrawingState.sequence++;
     mDrawingState.transform.set(matrix.dsdx, matrix.dtdy, matrix.dtdx, matrix.dsdy);
     mDrawingState.modified = true;
@@ -1016,7 +1018,7 @@
     return true;
 }
 
-bool Layer::setLayerStack(uint32_t layerStack) {
+bool Layer::setLayerStack(ui::LayerStack layerStack) {
     if (mDrawingState.layerStack == layerStack) return false;
     mDrawingState.sequence++;
     mDrawingState.layerStack = layerStack;
@@ -1063,12 +1065,11 @@
     return priority == PRIORITY_FOCUSED_WITH_MODE || priority == PRIORITY_FOCUSED_WITHOUT_MODE;
 };
 
-uint32_t Layer::getLayerStack() const {
-    auto p = mDrawingParent.promote();
-    if (p == nullptr) {
-        return getDrawingState().layerStack;
+ui::LayerStack Layer::getLayerStack() const {
+    if (const auto parent = mDrawingParent.promote()) {
+        return parent->getLayerStack();
     }
-    return p->getLayerStack();
+    return getDrawingState().layerStack;
 }
 
 bool Layer::setShadowRadius(float shadowRadius) {
@@ -1149,7 +1150,7 @@
     if (!frameRate.rate.isValid() && frameRate.type != FrameRateCompatibility::NoVote &&
         childrenHaveFrameRate) {
         *transactionNeeded |=
-                setFrameRateForLayerTree(FrameRate(Fps(0.0f), FrameRateCompatibility::NoVote));
+                setFrameRateForLayerTree(FrameRate(Fps(), FrameRateCompatibility::NoVote));
     }
 
     // We return whether this layer ot its children has a vote. We ignore ExactOrMultiple votes for
@@ -1183,9 +1184,6 @@
 }
 
 bool Layer::setFrameRate(FrameRate frameRate) {
-    if (!mFlinger->useFrameRateApi) {
-        return false;
-    }
     if (mDrawingState.frameRate == frameRate) {
         return false;
     }
@@ -1275,6 +1273,7 @@
                                                                  getSequence(), mName,
                                                                  mTransactionName,
                                                                  /*isBuffer*/ false, getGameMode());
+    surfaceFrame->setActualStartTime(info.startTimeNanos);
     // For Transactions, the post time is considered to be both queue and acquire fence time.
     surfaceFrame->setActualQueueTime(postTime);
     surfaceFrame->setAcquireFenceTime(postTime);
@@ -1292,6 +1291,7 @@
             mFlinger->mFrameTimeline->createSurfaceFrameForToken(info, mOwnerPid, mOwnerUid,
                                                                  getSequence(), mName, debugName,
                                                                  /*isBuffer*/ true, getGameMode());
+    surfaceFrame->setActualStartTime(info.startTimeNanos);
     // For buffers, acquire fence time will set during latch.
     surfaceFrame->setActualQueueTime(queueTime);
     const auto fps = mFlinger->mScheduler->getFrameRateOverride(getOwnerUid());
@@ -1316,8 +1316,8 @@
     mDrawingState.modified = true;
     setTransactionFlags(eTransactionNeeded);
 
-    mFlinger->mScheduler->recordLayerHistory(this, systemTime(),
-                                             LayerHistory::LayerUpdateType::SetFrameRate);
+    using LayerUpdateType = scheduler::LayerHistory::LayerUpdateType;
+    mFlinger->mScheduler->recordLayerHistory(this, systemTime(), LayerUpdateType::SetFrameRate);
 
     return true;
 }
@@ -1340,6 +1340,10 @@
             }
         }
     }
+    if (CC_UNLIKELY(!isTransformValid())) {
+        ALOGW("Hide layer %s because it has invalid transformation.", getDebugName());
+        return true;
+    }
     return s.flags & layer_state_t::eLayerHidden;
 }
 
@@ -1382,7 +1386,7 @@
 
     info.mVisibleRegion = getVisibleRegion(display);
     info.mSurfaceDamageRegion = surfaceDamageRegion;
-    info.mLayerStack = getLayerStack();
+    info.mLayerStack = getLayerStack().id;
     info.mX = ds.transform.tx();
     info.mY = ds.transform.ty();
     info.mZ = ds.z;
@@ -1412,7 +1416,6 @@
         }
     }
     info.mNumQueuedFrames = getQueuedFrameCount();
-    info.mRefreshPending = isBufferLatched();
     info.mIsOpaque = isOpaque(ds);
     info.mContentDirty = contentDirty;
     info.mStretchEffect = getStretchEffect();
@@ -1434,19 +1437,6 @@
     result.append("\n");
 }
 
-std::string Layer::frameRateCompatibilityString(Layer::FrameRateCompatibility compatibility) {
-    switch (compatibility) {
-        case FrameRateCompatibility::Default:
-            return "Default";
-        case FrameRateCompatibility::ExactOrMultiple:
-            return "ExactOrMultiple";
-        case FrameRateCompatibility::NoVote:
-            return "NoVote";
-        case FrameRateCompatibility::Exact:
-            return "Exact";
-    }
-}
-
 void Layer::miniDump(std::string& result, const DisplayDevice& display) const {
     const auto outputLayer = findOutputLayerForDisplay(&display);
     if (!outputLayer) {
@@ -1485,8 +1475,8 @@
     const auto frameRate = getFrameRateForLayerTree();
     if (frameRate.rate.isValid() || frameRate.type != FrameRateCompatibility::Default) {
         StringAppendF(&result, "%s %15s %17s", to_string(frameRate.rate).c_str(),
-                      frameRateCompatibilityString(frameRate.type).c_str(),
-                      toString(frameRate.seamlessness).c_str());
+                      ftl::enum_string(frameRate.type).c_str(),
+                      ftl::enum_string(frameRate.seamlessness).c_str());
     } else {
         result.append(41, ' ');
     }
@@ -1569,11 +1559,10 @@
     return count;
 }
 
-void Layer::setGameModeForTree(int parentGameMode) {
-    int gameMode = parentGameMode;
-    auto& currentState = getDrawingState();
+void Layer::setGameModeForTree(GameMode gameMode) {
+    const auto& currentState = getDrawingState();
     if (currentState.metadata.has(METADATA_GAME_MODE)) {
-        gameMode = currentState.metadata.getInt32(METADATA_GAME_MODE, 0);
+        gameMode = static_cast<GameMode>(currentState.metadata.getInt32(METADATA_GAME_MODE, 0));
     }
     setGameMode(gameMode);
     for (const sp<Layer>& child : mCurrentChildren) {
@@ -1599,7 +1588,7 @@
     const auto removeResult = mCurrentChildren.remove(layer);
 
     updateTreeHasFrameRateVote();
-    layer->setGameModeForTree(0);
+    layer->setGameModeForTree(GameMode::Unsupported);
     layer->updateTreeHasFrameRateVote();
 
     return removeResult;
@@ -1887,6 +1876,11 @@
     return mEffectiveTransform;
 }
 
+bool Layer::isTransformValid() const {
+    float transformDet = getTransform().det();
+    return transformDet != 0 && !isinf(transformDet) && !isnan(transformDet);
+}
+
 half Layer::getAlpha() const {
     const auto& p = mDrawingParent.promote();
 
@@ -2004,6 +1998,18 @@
     }
 }
 
+bool Layer::findInHierarchy(const sp<Layer>& l) {
+    if (l == this) {
+        return true;
+    }
+    for (auto& child : mDrawingChildren) {
+      if (child->findInHierarchy(l)) {
+          return true;
+      }
+    }
+    return false;
+}
+
 void Layer::commitChildList() {
     for (size_t i = 0; i < mCurrentChildren.size(); i++) {
         const auto& child = mCurrentChildren[i];
@@ -2011,6 +2017,17 @@
     }
     mDrawingChildren = mCurrentChildren;
     mDrawingParent = mCurrentParent;
+    if (CC_UNLIKELY(usingRelativeZ(LayerVector::StateSet::Drawing))) {
+        auto zOrderRelativeOf = mDrawingState.zOrderRelativeOf.promote();
+        if (zOrderRelativeOf == nullptr) return;
+        if (findInHierarchy(zOrderRelativeOf)) {
+            ALOGE("Detected Z ordering loop between %s and %s", mName.c_str(),
+                  zOrderRelativeOf->mName.c_str());
+            ALOGE("Severing rel Z loop, potentially dangerous");
+            mDrawingState.isRelativeOf = false;
+            zOrderRelativeOf->removeZOrderRelative(this);
+        }
+    }
 }
 
 
@@ -2022,67 +2039,59 @@
     setTransactionFlags(eTransactionNeeded);
 }
 
-LayerProto* Layer::writeToProto(LayersProto& layersProto, uint32_t traceFlags,
-                                const DisplayDevice* display) {
+LayerProto* Layer::writeToProto(LayersProto& layersProto, uint32_t traceFlags) {
     LayerProto* layerProto = layersProto.add_layers();
-    writeToProtoDrawingState(layerProto, traceFlags, display);
+    writeToProtoDrawingState(layerProto);
     writeToProtoCommonState(layerProto, LayerVector::StateSet::Drawing, traceFlags);
 
-    if (traceFlags & SurfaceTracing::TRACE_COMPOSITION) {
+    if (traceFlags & LayerTracing::TRACE_COMPOSITION) {
         // Only populate for the primary display.
+        UnnecessaryLock assumeLocked(mFlinger->mStateLock); // called from the main thread.
+        const auto display = mFlinger->getDefaultDisplayDeviceLocked();
         if (display) {
-            const Hwc2::IComposerClient::Composition compositionType = getCompositionType(*display);
+            const auto compositionType = getCompositionType(*display);
             layerProto->set_hwc_composition_type(static_cast<HwcCompositionType>(compositionType));
+            LayerProtoHelper::writeToProto(getVisibleRegion(display.get()),
+                                           [&]() { return layerProto->mutable_visible_region(); });
         }
     }
 
     for (const sp<Layer>& layer : mDrawingChildren) {
-        layer->writeToProto(layersProto, traceFlags, display);
+        layer->writeToProto(layersProto, traceFlags);
     }
 
     return layerProto;
 }
 
-void Layer::writeToProtoDrawingState(LayerProto* layerInfo, uint32_t traceFlags,
-                                     const DisplayDevice* display) {
+void Layer::writeToProtoDrawingState(LayerProto* layerInfo) {
     const ui::Transform transform = getTransform();
+    auto buffer = getExternalTexture();
+    if (buffer != nullptr) {
+        LayerProtoHelper::writeToProto(*buffer,
+                                       [&]() { return layerInfo->mutable_active_buffer(); });
+        LayerProtoHelper::writeToProtoDeprecated(ui::Transform(getBufferTransform()),
+                                                 layerInfo->mutable_buffer_transform());
+    }
+    layerInfo->set_invalidate(contentDirty);
+    layerInfo->set_is_protected(isProtected());
+    layerInfo->set_dataspace(dataspaceDetails(static_cast<android_dataspace>(getDataSpace())));
+    layerInfo->set_queued_frames(getQueuedFrameCount());
+    layerInfo->set_curr_frame(mCurrentFrameNumber);
+    layerInfo->set_effective_scaling_mode(getEffectiveScalingMode());
 
-    if (traceFlags & SurfaceTracing::TRACE_CRITICAL) {
+    layerInfo->set_requested_corner_radius(getDrawingState().cornerRadius);
+    layerInfo->set_corner_radius(getRoundedCornerState().radius);
+    layerInfo->set_background_blur_radius(getBackgroundBlurRadius());
+    layerInfo->set_is_trusted_overlay(isTrustedOverlay());
+    LayerProtoHelper::writeToProtoDeprecated(transform, layerInfo->mutable_transform());
+    LayerProtoHelper::writePositionToProto(transform.tx(), transform.ty(),
+                                           [&]() { return layerInfo->mutable_position(); });
+    LayerProtoHelper::writeToProto(mBounds, [&]() { return layerInfo->mutable_bounds(); });
+    LayerProtoHelper::writeToProto(surfaceDamageRegion,
+                                   [&]() { return layerInfo->mutable_damage_region(); });
 
-        auto buffer = getBuffer();
-        if (buffer != nullptr) {
-            LayerProtoHelper::writeToProto(buffer,
-                                           [&]() { return layerInfo->mutable_active_buffer(); });
-            LayerProtoHelper::writeToProtoDeprecated(ui::Transform(getBufferTransform()),
-                                                     layerInfo->mutable_buffer_transform());
-        }
-        layerInfo->set_invalidate(contentDirty);
-        layerInfo->set_is_protected(isProtected());
-        layerInfo->set_dataspace(dataspaceDetails(static_cast<android_dataspace>(getDataSpace())));
-        layerInfo->set_queued_frames(getQueuedFrameCount());
-        layerInfo->set_refresh_pending(isBufferLatched());
-        layerInfo->set_curr_frame(mCurrentFrameNumber);
-        layerInfo->set_effective_scaling_mode(getEffectiveScalingMode());
-
-        layerInfo->set_requested_corner_radius(getDrawingState().cornerRadius);
-        layerInfo->set_corner_radius(getRoundedCornerState().radius);
-        layerInfo->set_background_blur_radius(getBackgroundBlurRadius());
-        layerInfo->set_is_trusted_overlay(isTrustedOverlay());
-        LayerProtoHelper::writeToProtoDeprecated(transform, layerInfo->mutable_transform());
-        LayerProtoHelper::writePositionToProto(transform.tx(), transform.ty(),
-                                               [&]() { return layerInfo->mutable_position(); });
-        LayerProtoHelper::writeToProto(mBounds, [&]() { return layerInfo->mutable_bounds(); });
-        if (traceFlags & SurfaceTracing::TRACE_COMPOSITION) {
-            LayerProtoHelper::writeToProto(getVisibleRegion(display),
-                                           [&]() { return layerInfo->mutable_visible_region(); });
-        }
-        LayerProtoHelper::writeToProto(surfaceDamageRegion,
-                                       [&]() { return layerInfo->mutable_damage_region(); });
-
-        if (hasColorTransform()) {
-            LayerProtoHelper::writeToProto(getColorTransform(),
-                                           layerInfo->mutable_color_transform());
-        }
+    if (hasColorTransform()) {
+        LayerProtoHelper::writeToProto(getColorTransform(), layerInfo->mutable_color_transform());
     }
 
     LayerProtoHelper::writeToProto(mSourceBounds,
@@ -2102,73 +2111,69 @@
 
     ui::Transform requestedTransform = state.transform;
 
-    if (traceFlags & SurfaceTracing::TRACE_CRITICAL) {
-        layerInfo->set_id(sequence);
-        layerInfo->set_name(getName().c_str());
-        layerInfo->set_type(getType());
+    layerInfo->set_id(sequence);
+    layerInfo->set_name(getName().c_str());
+    layerInfo->set_type(getType());
 
-        for (const auto& child : children) {
-            layerInfo->add_children(child->sequence);
-        }
-
-        for (const wp<Layer>& weakRelative : state.zOrderRelatives) {
-            sp<Layer> strongRelative = weakRelative.promote();
-            if (strongRelative != nullptr) {
-                layerInfo->add_relatives(strongRelative->sequence);
-            }
-        }
-
-        LayerProtoHelper::writeToProto(state.activeTransparentRegion_legacy,
-                                       [&]() { return layerInfo->mutable_transparent_region(); });
-
-        layerInfo->set_layer_stack(getLayerStack());
-        layerInfo->set_z(state.z);
-
-        LayerProtoHelper::writePositionToProto(requestedTransform.tx(), requestedTransform.ty(),
-                                               [&]() {
-                                                   return layerInfo->mutable_requested_position();
-                                               });
-
-        LayerProtoHelper::writeSizeToProto(state.width, state.height,
-                                           [&]() { return layerInfo->mutable_size(); });
-
-        LayerProtoHelper::writeToProto(state.crop, [&]() { return layerInfo->mutable_crop(); });
-
-        layerInfo->set_is_opaque(isOpaque(state));
-
-
-        layerInfo->set_pixel_format(decodePixelFormat(getPixelFormat()));
-        LayerProtoHelper::writeToProto(getColor(), [&]() { return layerInfo->mutable_color(); });
-        LayerProtoHelper::writeToProto(state.color,
-                                       [&]() { return layerInfo->mutable_requested_color(); });
-        layerInfo->set_flags(state.flags);
-
-        LayerProtoHelper::writeToProtoDeprecated(requestedTransform,
-                                                 layerInfo->mutable_requested_transform());
-
-        auto parent = useDrawing ? mDrawingParent.promote() : mCurrentParent.promote();
-        if (parent != nullptr) {
-            layerInfo->set_parent(parent->sequence);
-        } else {
-            layerInfo->set_parent(-1);
-        }
-
-        auto zOrderRelativeOf = state.zOrderRelativeOf.promote();
-        if (zOrderRelativeOf != nullptr) {
-            layerInfo->set_z_order_relative_of(zOrderRelativeOf->sequence);
-        } else {
-            layerInfo->set_z_order_relative_of(-1);
-        }
-
-        layerInfo->set_is_relative_of(state.isRelativeOf);
-
-        layerInfo->set_owner_uid(mOwnerUid);
+    for (const auto& child : children) {
+        layerInfo->add_children(child->sequence);
     }
 
-    if (traceFlags & SurfaceTracing::TRACE_INPUT) {
+    for (const wp<Layer>& weakRelative : state.zOrderRelatives) {
+        sp<Layer> strongRelative = weakRelative.promote();
+        if (strongRelative != nullptr) {
+            layerInfo->add_relatives(strongRelative->sequence);
+        }
+    }
+
+    LayerProtoHelper::writeToProto(state.activeTransparentRegion_legacy,
+                                   [&]() { return layerInfo->mutable_transparent_region(); });
+
+    layerInfo->set_layer_stack(getLayerStack().id);
+    layerInfo->set_z(state.z);
+
+    LayerProtoHelper::writePositionToProto(requestedTransform.tx(), requestedTransform.ty(), [&]() {
+        return layerInfo->mutable_requested_position();
+    });
+
+    LayerProtoHelper::writeSizeToProto(state.width, state.height,
+                                       [&]() { return layerInfo->mutable_size(); });
+
+    LayerProtoHelper::writeToProto(state.crop, [&]() { return layerInfo->mutable_crop(); });
+
+    layerInfo->set_is_opaque(isOpaque(state));
+
+    layerInfo->set_pixel_format(decodePixelFormat(getPixelFormat()));
+    LayerProtoHelper::writeToProto(getColor(), [&]() { return layerInfo->mutable_color(); });
+    LayerProtoHelper::writeToProto(state.color,
+                                   [&]() { return layerInfo->mutable_requested_color(); });
+    layerInfo->set_flags(state.flags);
+
+    LayerProtoHelper::writeToProtoDeprecated(requestedTransform,
+                                             layerInfo->mutable_requested_transform());
+
+    auto parent = useDrawing ? mDrawingParent.promote() : mCurrentParent.promote();
+    if (parent != nullptr) {
+        layerInfo->set_parent(parent->sequence);
+    } else {
+        layerInfo->set_parent(-1);
+    }
+
+    auto zOrderRelativeOf = state.zOrderRelativeOf.promote();
+    if (zOrderRelativeOf != nullptr) {
+        layerInfo->set_z_order_relative_of(zOrderRelativeOf->sequence);
+    } else {
+        layerInfo->set_z_order_relative_of(-1);
+    }
+
+    layerInfo->set_is_relative_of(state.isRelativeOf);
+
+    layerInfo->set_owner_uid(mOwnerUid);
+
+    if (traceFlags & LayerTracing::TRACE_INPUT) {
         WindowInfo info;
         if (useDrawing) {
-            info = fillInputInfo({nullptr});
+            info = fillInputInfo(ui::Transform(), /* displayIsSecure */ true);
         } else {
             info = state.inputInfo;
         }
@@ -2177,12 +2182,15 @@
                                        [&]() { return layerInfo->mutable_input_window_info(); });
     }
 
-    if (traceFlags & SurfaceTracing::TRACE_EXTRA) {
+    if (traceFlags & LayerTracing::TRACE_EXTRA) {
         auto protoMap = layerInfo->mutable_metadata();
         for (const auto& entry : state.metadata.mMap) {
             (*protoMap)[entry.first] = std::string(entry.second.cbegin(), entry.second.cend());
         }
     }
+
+    LayerProtoHelper::writeToProto(state.destinationFrame,
+                                   [&]() { return layerInfo->mutable_destination_frame(); });
 }
 
 bool Layer::isRemovedFromCurrentState() const  {
@@ -2197,101 +2205,69 @@
     return getCroppedBufferSize(getDrawingState());
 }
 
-void Layer::fillInputFrameInfo(WindowInfo& info, const ui::Transform& toNonRotatedDisplay) {
-    // Transform layer size to screen space and inset it by surface insets.
-    // If this is a portal window, set the touchableRegion to the layerBounds.
-    Rect layerBounds = info.portalToDisplayId == ADISPLAY_ID_NONE
-            ? getInputBounds()
-            : info.touchableRegion.getBounds();
-    if (!layerBounds.isValid()) {
-        layerBounds = getInputBounds();
+void Layer::fillInputFrameInfo(WindowInfo& info, const ui::Transform& screenToDisplay) {
+    Rect tmpBounds = getInputBounds();
+    if (!tmpBounds.isValid()) {
+        info.touchableRegion.clear();
+        // A layer could have invalid input bounds and still expect to receive touch input if it has
+        // replaceTouchableRegionWithCrop. For that case, the input transform needs to be calculated
+        // correctly to determine the coordinate space for input events. Use an empty rect so that
+        // the layer will receive input in its own layer space.
+        tmpBounds = Rect::EMPTY_RECT;
     }
 
-    if (!layerBounds.isValid()) {
-        // If the layer bounds is empty, set the frame to empty and clear the transform
-        info.frameLeft = 0;
-        info.frameTop = 0;
-        info.frameRight = 0;
-        info.frameBottom = 0;
-        info.transform.reset();
-        info.touchableRegion = Region();
-        info.flags = WindowInfo::Flag::NOT_TOUCH_MODAL | WindowInfo::Flag::NOT_FOCUSABLE;
-        return;
-    }
+    // InputDispatcher works in the display device's coordinate space. Here, we calculate the
+    // frame and transform used for the layer, which determines the bounds and the coordinate space
+    // within which the layer will receive input.
+    //
+    // The coordinate space within which each of the bounds are specified is explicitly documented
+    // in the variable name. For example "inputBoundsInLayer" is specified in layer space. A
+    // Transform converts one coordinate space to another, which is apparent in its naming. For
+    // example, "layerToDisplay" transforms layer space to display space.
+    //
+    // Coordinate space definitions:
+    //   - display: The display device's coordinate space. Correlates to pixels on the display.
+    //   - screen: The post-rotation coordinate space for the display, a.k.a. logical display space.
+    //   - layer: The coordinate space of this layer.
+    //   - input: The coordinate space in which this layer will receive input events. This could be
+    //            different than layer space if a surfaceInset is used, which changes the origin
+    //            of the input space.
+    const FloatRect inputBoundsInLayer = tmpBounds.toFloatRect();
 
-    ui::Transform layerToDisplay = getInputTransform();
-    // Transform that takes window coordinates to non-rotated display coordinates
-    ui::Transform t = toNonRotatedDisplay * layerToDisplay;
-    int32_t xSurfaceInset = info.surfaceInset;
-    int32_t ySurfaceInset = info.surfaceInset;
-    // Bring screenBounds into non-rotated space
-    Rect screenBounds = toNonRotatedDisplay.transform(Rect{mScreenBounds});
+    // Clamp surface inset to the input bounds.
+    const auto surfaceInset = static_cast<float>(info.surfaceInset);
+    const float xSurfaceInset =
+            std::max(0.f, std::min(surfaceInset, inputBoundsInLayer.getWidth() / 2.f));
+    const float ySurfaceInset =
+            std::max(0.f, std::min(surfaceInset, inputBoundsInLayer.getHeight() / 2.f));
 
-    const float xScale = t.getScaleX();
-    const float yScale = t.getScaleY();
-    if (xScale != 1.0f || yScale != 1.0f) {
-        xSurfaceInset = std::round(xSurfaceInset * xScale);
-        ySurfaceInset = std::round(ySurfaceInset * yScale);
-    }
+    // Apply the insets to the input bounds.
+    const FloatRect insetBoundsInLayer(inputBoundsInLayer.left + xSurfaceInset,
+                                       inputBoundsInLayer.top + ySurfaceInset,
+                                       inputBoundsInLayer.right - xSurfaceInset,
+                                       inputBoundsInLayer.bottom - ySurfaceInset);
 
-    // Transform the layer bounds from layer coordinate space to display coordinate space.
-    Rect transformedLayerBounds = t.transform(layerBounds);
+    // Crop the input bounds to ensure it is within the parent's bounds.
+    const FloatRect croppedInsetBoundsInLayer = mBounds.intersect(insetBoundsInLayer);
 
-    // clamp inset to layer bounds
-    xSurfaceInset = (xSurfaceInset >= 0)
-            ? std::min(xSurfaceInset, transformedLayerBounds.getWidth() / 2)
-            : 0;
-    ySurfaceInset = (ySurfaceInset >= 0)
-            ? std::min(ySurfaceInset, transformedLayerBounds.getHeight() / 2)
-            : 0;
+    const ui::Transform layerToScreen = getInputTransform();
+    const ui::Transform layerToDisplay = screenToDisplay * layerToScreen;
 
-    // inset while protecting from overflow TODO(b/161235021): What is going wrong
-    // in the overflow scenario?
-    {
-    int32_t tmp;
-    if (!__builtin_add_overflow(transformedLayerBounds.left, xSurfaceInset, &tmp))
-        transformedLayerBounds.left = tmp;
-    if (!__builtin_sub_overflow(transformedLayerBounds.right, xSurfaceInset, &tmp))
-        transformedLayerBounds.right = tmp;
-    if (!__builtin_add_overflow(transformedLayerBounds.top, ySurfaceInset, &tmp))
-        transformedLayerBounds.top = tmp;
-    if (!__builtin_sub_overflow(transformedLayerBounds.bottom, ySurfaceInset, &tmp))
-        transformedLayerBounds.bottom = tmp;
-    }
+    const Rect roundedFrameInDisplay{layerToDisplay.transform(croppedInsetBoundsInLayer)};
+    info.frameLeft = roundedFrameInDisplay.left;
+    info.frameTop = roundedFrameInDisplay.top;
+    info.frameRight = roundedFrameInDisplay.right;
+    info.frameBottom = roundedFrameInDisplay.bottom;
 
-    // Compute the correct transform to send to input. This will allow it to transform the
-    // input coordinates from display space into window space. Therefore, it needs to use the
-    // final layer frame to create the inverse transform. Since surface insets are added later,
-    // along with the overflow, the best way to ensure we get the correct transform is to use
-    // the final frame calculated.
-    // 1. Take the original transform set on the window and get the inverse transform. This is
-    //    used to get the final bounds in display space (ignorning the transform). Apply the
-    //    inverse transform on the layerBounds to get the untransformed frame (in layer space)
-    // 2. Take the top and left of the untransformed frame to get the real position on screen.
-    //    Apply the layer transform on top/left so it includes any scale or rotation. These will
-    //    be the new translation values for the transform.
-    // 3. Update the translation of the original transform to the new translation values.
-    // 4. Send the inverse transform to input so the coordinates can be transformed back into
-    //    window space.
-    ui::Transform inverseTransform = t.inverse();
-    Rect nonTransformedBounds = inverseTransform.transform(transformedLayerBounds);
-    vec2 translation = t.transform(nonTransformedBounds.left, nonTransformedBounds.top);
-    ui::Transform inputTransform(t);
-    inputTransform.set(translation.x, translation.y);
-    info.transform = inputTransform.inverse();
+    ui::Transform inputToLayer;
+    inputToLayer.set(insetBoundsInLayer.left, insetBoundsInLayer.top);
+    const ui::Transform inputToDisplay = layerToDisplay * inputToLayer;
 
-    // We need to send the layer bounds cropped to the screenbounds since the layer can be cropped.
-    // The frame should be the area the user sees on screen since it's used for occlusion
-    // detection.
-    transformedLayerBounds.intersect(screenBounds, &transformedLayerBounds);
-    info.frameLeft = transformedLayerBounds.left;
-    info.frameTop = transformedLayerBounds.top;
-    info.frameRight = transformedLayerBounds.right;
-    info.frameBottom = transformedLayerBounds.bottom;
+    // InputDispatcher expects a display-to-input transform.
+    info.transform = inputToDisplay.inverse();
 
-    // Position the touchable region relative to frame screen location and restrict it to frame
-    // bounds.
-    info.touchableRegion = inputTransform.transform(info.touchableRegion);
+    // The touchable region is specified in the input coordinate space. Change it to display space.
+    info.touchableRegion = inputToDisplay.transform(info.touchableRegion);
 }
 
 void Layer::fillTouchOcclusionMode(WindowInfo& info) {
@@ -2320,14 +2296,14 @@
 }
 
 void Layer::handleDropInputMode(gui::WindowInfo& info) const {
-    if (mDrawingState.inputInfo.inputFeatures.test(WindowInfo::Feature::NO_INPUT_CHANNEL)) {
+    if (mDrawingState.inputInfo.inputConfig.test(WindowInfo::InputConfig::NO_INPUT_CHANNEL)) {
         return;
     }
 
     // Check if we need to drop input unconditionally
     gui::DropInputMode dropInputMode = getDropInputMode();
     if (dropInputMode == gui::DropInputMode::ALL) {
-        info.inputFeatures |= WindowInfo::Feature::DROP_INPUT;
+        info.inputConfig |= WindowInfo::InputConfig::DROP_INPUT;
         ALOGV("Dropping input for %s as requested by policy.", getDebugName());
         return;
     }
@@ -2340,7 +2316,7 @@
     // Check if the parent has set an alpha on the layer
     sp<Layer> parent = mDrawingParent.promote();
     if (parent && parent->getAlpha() != 1.0_hf) {
-        info.inputFeatures |= WindowInfo::Feature::DROP_INPUT;
+        info.inputConfig |= WindowInfo::InputConfig::DROP_INPUT;
         ALOGV("Dropping input for %s as requested by policy because alpha=%f", getDebugName(),
               static_cast<float>(getAlpha()));
     }
@@ -2348,7 +2324,7 @@
     // Check if the parent has cropped the buffer
     Rect bufferSize = getCroppedBufferSize(getDrawingState());
     if (!bufferSize.isValid()) {
-        info.inputFeatures |= WindowInfo::Feature::DROP_INPUT_IF_OBSCURED;
+        info.inputConfig |= WindowInfo::InputConfig::DROP_INPUT_IF_OBSCURED;
         return;
     }
 
@@ -2360,7 +2336,7 @@
     bool croppedByParent = bufferInScreenSpace != Rect{mScreenBounds};
 
     if (croppedByParent) {
-        info.inputFeatures |= WindowInfo::Feature::DROP_INPUT;
+        info.inputConfig |= WindowInfo::InputConfig::DROP_INPUT;
         ALOGV("Dropping input for %s as requested by policy because buffer is cropped by parent",
               getDebugName());
     } else {
@@ -2368,51 +2344,24 @@
         // input if the window is obscured. This check should be done in surfaceflinger but the
         // logic currently resides in inputflinger. So pass the if_obscured check to input to only
         // drop input events if the window is obscured.
-        info.inputFeatures |= WindowInfo::Feature::DROP_INPUT_IF_OBSCURED;
+        info.inputConfig |= WindowInfo::InputConfig::DROP_INPUT_IF_OBSCURED;
     }
 }
 
-WindowInfo Layer::fillInputInfo(const sp<DisplayDevice>& display) {
+WindowInfo Layer::fillInputInfo(const ui::Transform& displayTransform, bool displayIsSecure) {
     if (!hasInputInfo()) {
         mDrawingState.inputInfo.name = getName();
         mDrawingState.inputInfo.ownerUid = mOwnerUid;
         mDrawingState.inputInfo.ownerPid = mOwnerPid;
-        mDrawingState.inputInfo.inputFeatures = WindowInfo::Feature::NO_INPUT_CHANNEL;
-        mDrawingState.inputInfo.flags = WindowInfo::Flag::NOT_TOUCH_MODAL;
-        mDrawingState.inputInfo.displayId = getLayerStack();
+        mDrawingState.inputInfo.inputConfig |= WindowInfo::InputConfig::NO_INPUT_CHANNEL;
+        mDrawingState.inputInfo.displayId = getLayerStack().id;
     }
 
     WindowInfo info = mDrawingState.inputInfo;
     info.id = sequence;
-    info.displayId = getLayerStack();
+    info.displayId = getLayerStack().id;
 
-    // Transform that goes from "logical(rotated)" display to the non-rotated display.
-    ui::Transform toNonRotatedDisplay;
-    if (display) {
-        // The physical orientation is set when the orientation of the display panel is different
-        // than the default orientation of the device. We do not need to expose the physical
-        // orientation of the panel outside of SurfaceFlinger.
-        const ui::Rotation inversePhysicalOrientation =
-                ui::ROTATION_0 - display->getPhysicalOrientation();
-        auto width = display->getWidth();
-        auto height = display->getHeight();
-        if (inversePhysicalOrientation == ui::ROTATION_90 ||
-            inversePhysicalOrientation == ui::ROTATION_270) {
-            std::swap(width, height);
-        }
-        const auto rotationFlags = ui::Transform::toRotationFlags(inversePhysicalOrientation);
-        const ui::Transform undoPhysicalOrientation(rotationFlags, width, height);
-        toNonRotatedDisplay = undoPhysicalOrientation * display->getTransform();
-
-        // Send the inverse of the display orientation so that input can transform points back to
-        // the rotated display space.
-        const ui::Rotation inverseOrientation = ui::ROTATION_0 - display->getOrientation();
-        info.displayOrientation = ui::Transform::toRotationFlags(inverseOrientation);
-
-        info.displayWidth = width;
-        info.displayHeight = height;
-    }
-    fillInputFrameInfo(info, toNonRotatedDisplay);
+    fillInputFrameInfo(info, displayTransform);
 
     // For compatibility reasons we let layers which can receive input
     // receive input before they have actually submitted a buffer. Because
@@ -2422,35 +2371,39 @@
     // We are just using these layers for occlusion detection in
     // InputDispatcher, and obviously if they aren't visible they can't occlude
     // anything.
-    info.visible = hasInputInfo() ? canReceiveInput() : isVisible();
+    const bool visible = hasInputInfo() ? canReceiveInput() : isVisible();
+    info.setInputConfig(WindowInfo::InputConfig::NOT_VISIBLE, !visible);
+
     info.alpha = getAlpha();
     fillTouchOcclusionMode(info);
     handleDropInputMode(info);
 
+    // If the window will be blacked out on a display because the display does not have the secure
+    // flag and the layer has the secure flag set, then drop input.
+    if (!displayIsSecure && isSecure()) {
+        info.inputConfig |= WindowInfo::InputConfig::DROP_INPUT;
+    }
+
     auto cropLayer = mDrawingState.touchableRegionCrop.promote();
     if (info.replaceTouchableRegionWithCrop) {
-        if (cropLayer == nullptr) {
-            info.touchableRegion = Region(toNonRotatedDisplay.transform(Rect{mScreenBounds}));
-        } else {
-            info.touchableRegion =
-                    Region(toNonRotatedDisplay.transform(Rect{cropLayer->mScreenBounds}));
-        }
+        const Rect bounds(cropLayer ? cropLayer->mScreenBounds : mScreenBounds);
+        info.touchableRegion = Region(displayTransform.transform(bounds));
     } else if (cropLayer != nullptr) {
         info.touchableRegion = info.touchableRegion.intersect(
-                toNonRotatedDisplay.transform(Rect{cropLayer->mScreenBounds}));
+                displayTransform.transform(Rect{cropLayer->mScreenBounds}));
     }
 
     // Inherit the trusted state from the parent hierarchy, but don't clobber the trusted state
     // if it was set by WM for a known system overlay
-    info.trustedOverlay = info.trustedOverlay || isTrustedOverlay();
-
+    if (isTrustedOverlay()) {
+        info.inputConfig |= WindowInfo::InputConfig::TRUSTED_OVERLAY;
+    }
 
     // If the layer is a clone, we need to crop the input region to cloned root to prevent
     // touches from going outside the cloned area.
     if (isClone()) {
-        sp<Layer> clonedRoot = getClonedRoot();
-        if (clonedRoot != nullptr) {
-            Rect rect = toNonRotatedDisplay.transform(Rect{clonedRoot->mScreenBounds});
+        if (const sp<Layer> clonedRoot = getClonedRoot()) {
+            const Rect rect = displayTransform.transform(Rect{clonedRoot->mScreenBounds});
             info.touchableRegion = info.touchableRegion.intersect(rect);
         }
     }
@@ -2469,7 +2422,8 @@
 }
 
 bool Layer::hasInputInfo() const {
-    return mDrawingState.inputInfo.token != nullptr;
+    return mDrawingState.inputInfo.token != nullptr ||
+            mDrawingState.inputInfo.inputConfig.test(WindowInfo::InputConfig::NO_INPUT_CHANNEL);
 }
 
 bool Layer::canReceiveInput() const {
@@ -2576,7 +2530,7 @@
     }
     // Cloned layers shouldn't handle watch outside since their z order is not determined by
     // WM or the client.
-    mDrawingState.inputInfo.flags &= ~WindowInfo::Flag::WATCH_OUTSIDE_TOUCH;
+    mDrawingState.inputInfo.setInputConfig(WindowInfo::InputConfig::WATCH_OUTSIDE_TOUCH, false);
 }
 
 void Layer::updateClonedRelatives(const std::map<sp<Layer>, sp<Layer>>& clonedLayersMap) {
@@ -2645,14 +2599,14 @@
     }
 }
 
-bool Layer::getPrimaryDisplayOnly() const {
+bool Layer::isInternalDisplayOverlay() const {
     const State& s(mDrawingState);
     if (s.flags & layer_state_t::eLayerSkipScreenshot) {
         return true;
     }
 
     sp<Layer> parent = mDrawingParent.promote();
-    return parent == nullptr ? false : parent->getPrimaryDisplayOnly();
+    return parent && parent->isInternalDisplayOverlay();
 }
 
 void Layer::setClonedChild(const sp<Layer>& clonedChild) {
@@ -2696,12 +2650,11 @@
 // ---------------------------------------------------------------------------
 
 std::ostream& operator<<(std::ostream& stream, const Layer::FrameRate& rate) {
-    return stream << "{rate=" << rate.rate
-                  << " type=" << Layer::frameRateCompatibilityString(rate.type)
-                  << " seamlessness=" << toString(rate.seamlessness) << "}";
+    return stream << "{rate=" << rate.rate << " type=" << ftl::enum_string(rate.type)
+                  << " seamlessness=" << ftl::enum_string(rate.seamlessness) << '}';
 }
 
-}; // namespace android
+} // namespace android
 
 #if defined(__gl_h_)
 #error "don't include gl/gl.h in this file"
diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h
index c1af468..846460d 100644
--- a/services/surfaceflinger/Layer.h
+++ b/services/surfaceflinger/Layer.h
@@ -1,4 +1,3 @@
-
 /*
  * Copyright (C) 2007 The Android Open Source Project
  *
@@ -18,7 +17,6 @@
 #pragma once
 
 #include <android/gui/DropInputMode.h>
-#include <compositionengine/LayerFE.h>
 #include <gui/BufferQueue.h>
 #include <gui/ISurfaceComposerClient.h>
 #include <gui/LayerState.h>
@@ -39,6 +37,10 @@
 #include <utils/RefBase.h>
 #include <utils/Timers.h>
 
+#include <compositionengine/LayerFE.h>
+#include <scheduler/Fps.h>
+#include <scheduler/Seamlessness.h>
+
 #include <chrono>
 #include <cstdint>
 #include <list>
@@ -49,15 +51,13 @@
 #include "ClientCache.h"
 #include "DisplayHardware/ComposerHal.h"
 #include "DisplayHardware/HWComposer.h"
-#include "Fps.h"
 #include "FrameTracker.h"
 #include "LayerVector.h"
 #include "MonitoredProducer.h"
 #include "RenderArea.h"
 #include "Scheduler/LayerInfo.h"
-#include "Scheduler/Seamlessness.h"
 #include "SurfaceFlinger.h"
-#include "SurfaceTracing.h"
+#include "Tracing/LayerTracing.h"
 #include "TransactionCallbackInvoker.h"
 
 using namespace android::surfaceflinger;
@@ -85,20 +85,19 @@
 } // namespace frametimeline
 
 struct LayerCreationArgs {
-    LayerCreationArgs(SurfaceFlinger*, sp<Client>, std::string name, uint32_t w, uint32_t h,
-                      uint32_t flags, LayerMetadata);
+    LayerCreationArgs(SurfaceFlinger*, sp<Client>, std::string name, uint32_t flags, LayerMetadata);
 
     SurfaceFlinger* flinger;
     const sp<Client> client;
     std::string name;
-    uint32_t w;
-    uint32_t h;
     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 {
@@ -152,12 +151,7 @@
         Geometry requested_legacy;
         int32_t z;
 
-        // The identifier of the layer stack this layer belongs to. A layer can
-        // only be associated to a single layer stack. A layer stack is a
-        // z-ordered group of layers which can be associated to one or more
-        // displays. Using the same layer stack on different displays is a way
-        // to achieve mirroring.
-        uint32_t layerStack;
+        ui::LayerStack layerStack;
 
         uint32_t flags;
         uint8_t reserved[2];
@@ -285,6 +279,8 @@
         sp<IBinder> releaseBufferEndpoint;
 
         gui::DropInputMode dropInputMode;
+
+        bool autoRefresh = false;
     };
 
     /*
@@ -332,7 +328,6 @@
 
     static bool isLayerFocusedBasedOnPriority(int32_t priority);
     static void miniDumpHeader(std::string& result);
-    static std::string frameRateCompatibilityString(FrameRateCompatibility compatibility);
 
     // Provide unique string for each class type in the Layer hierarchy
     virtual const char* getType() const = 0;
@@ -371,8 +366,7 @@
     // Set a 2x2 transformation matrix on the layer. This transform
     // will be applied after parent transforms, but before any final
     // producer specified transform.
-    virtual bool setMatrix(const layer_state_t::matrix22_t& matrix,
-                           bool allowNonRectPreservingTransforms);
+    virtual bool setMatrix(const layer_state_t::matrix22_t& matrix);
 
     // This second set of geometry attributes are controlled by
     // setGeometryAppliesWithResize, and their default mode is to be
@@ -408,8 +402,8 @@
     virtual bool setTransparentRegionHint(const Region& transparent);
     virtual bool setTrustedOverlay(bool);
     virtual bool setFlags(uint32_t flags, uint32_t mask);
-    virtual bool setLayerStack(uint32_t layerStack);
-    virtual uint32_t getLayerStack() const;
+    virtual bool setLayerStack(ui::LayerStack);
+    virtual ui::LayerStack getLayerStack() const;
     virtual bool setMetadata(const LayerMetadata& data);
     virtual void setChildrenDrawingParent(const sp<Layer>&);
     virtual bool reparent(const sp<IBinder>& newParentHandle);
@@ -421,17 +415,13 @@
     // Used only to set BufferStateLayer state
     virtual bool setTransform(uint32_t /*transform*/) { return false; };
     virtual bool setTransformToDisplayInverse(bool /*transformToDisplayInverse*/) { return false; };
-    virtual bool setBuffer(const std::shared_ptr<renderengine::ExternalTexture>& /*buffer*/,
-                           const sp<Fence>& /*acquireFence*/, nsecs_t /*postTime*/,
+    virtual bool setBuffer(std::shared_ptr<renderengine::ExternalTexture>& /* buffer */,
+                           const BufferData& /* bufferData */, nsecs_t /* postTime */,
                            nsecs_t /*desiredPresentTime*/, bool /*isAutoTimestamp*/,
-                           const client_cache_t& /*clientCacheId*/, uint64_t /* frameNumber */,
                            std::optional<nsecs_t> /* dequeueTime */,
-                           const FrameTimelineInfo& /*info*/,
-                           const sp<ITransactionCompletedListener>& /* releaseBufferListener */,
-                           const sp<IBinder>& /* releaseBufferEndpoint */) {
+                           const FrameTimelineInfo& /*info*/) {
         return false;
     };
-    virtual bool setAcquireFence(const sp<Fence>& /*fence*/) { return false; };
     virtual bool setDataspace(ui::Dataspace /*dataspace*/) { return false; };
     virtual bool setHdrMetadata(const HdrMetadata& /*hdrMetadata*/) { return false; };
     virtual bool setSurfaceDamageRegion(const Region& /*surfaceDamage*/) { return false; };
@@ -532,18 +522,14 @@
 
     virtual bool shouldPresentNow(nsecs_t /*expectedPresentTime*/) const { return false; }
 
-    virtual uint64_t getHeadFrameNumber(nsecs_t /* expectedPresentTime */) const { return 0; }
-
     /*
      * called after composition.
      * returns true if the layer latched a new buffer this frame.
      */
-    virtual bool onPostComposition(const DisplayDevice*,
+    virtual void onPostComposition(const DisplayDevice*,
                                    const std::shared_ptr<FenceTime>& /*glDoneFence*/,
                                    const std::shared_ptr<FenceTime>& /*presentFence*/,
-                                   const CompositorTiming&) {
-        return false;
-    }
+                                   const CompositorTiming&) {}
 
     // If a buffer was replaced this frame, release the former buffer
     virtual void releasePendingBuffer(nsecs_t /*dequeueReadyTime*/) { }
@@ -562,8 +548,6 @@
         return false;
     }
 
-    virtual bool isBufferLatched() const { return false; }
-
     virtual void latchAndReleaseBuffer() {}
 
     /*
@@ -578,6 +562,9 @@
     virtual uint32_t getBufferTransform() const { return 0; }
 
     virtual sp<GraphicBuffer> getBuffer() const { return nullptr; }
+    virtual const std::shared_ptr<renderengine::ExternalTexture>& getExternalTexture() const {
+        return mDrawingState.buffer;
+    };
 
     virtual ui::Transform::RotationFlags getTransformHint() const { return ui::Transform::ROT_0; }
 
@@ -604,10 +591,6 @@
     }
     virtual FrameRate getFrameRateForLayerTree() const;
 
-    virtual std::vector<OccupancyTracker::Segment> getOccupancyHistory(bool /*forceFlush*/) {
-        return {};
-    }
-
     virtual bool getTransformToDisplayInverse() const { return false; }
 
     // Returns how rounded corners should be drawn for this layer.
@@ -635,7 +618,13 @@
     void prepareCompositionState(compositionengine::LayerFE::StateSubset subset) override;
     std::vector<compositionengine::LayerFE::LayerSettings> prepareClientCompositionList(
             compositionengine::LayerFE::ClientCompositionTargetSettings&) override;
-    void onLayerDisplayed(const sp<Fence>& releaseFence) override;
+    void onLayerDisplayed(
+            std::shared_future<renderengine::RenderEngineResult> futureRenderEngineResult) override;
+
+    void setWasClientComposed(const sp<Fence>& fence) override {
+        mLastClientCompositionFence = fence;
+    }
+
     const char* getDebugName() const override;
 
     bool setShadowRadius(float shadowRadius);
@@ -648,13 +637,12 @@
     bool isLegacyDataSpace() const;
 
     uint32_t getTransactionFlags() const { return mTransactionFlags; }
-    uint32_t getTransactionFlags(uint32_t flags);
-    uint32_t setTransactionFlags(uint32_t flags);
 
-    // Deprecated, please use compositionengine::Output::belongsInOutput()
-    // instead.
-    // TODO(lpique): Move the remaining callers (screencap) to the new function.
-    bool belongsToDisplay(uint32_t layerStack) const { return getLayerStack() == layerStack; }
+    // Sets the masked bits.
+    void setTransactionFlags(uint32_t mask);
+
+    // Clears and returns the masked bits.
+    uint32_t clearTransactionFlags(uint32_t mask);
 
     FloatRect getBounds(const Region& activeTransparentRegion) const;
     FloatRect getBounds() const;
@@ -687,24 +675,30 @@
      */
     bool isHiddenByPolicy() const;
 
+    // True if the layer should be skipped in screenshots, screen recordings,
+    // and mirroring to external or virtual displays.
+    bool isInternalDisplayOverlay() const;
+
+    ui::LayerFilter getOutputFilter() const {
+        return {getLayerStack(), isInternalDisplayOverlay()};
+    }
+
     bool isRemovedFromCurrentState() const;
 
-    LayerProto* writeToProto(LayersProto& layersProto, uint32_t traceFlags, const DisplayDevice*);
+    LayerProto* writeToProto(LayersProto& layersProto, uint32_t traceFlags);
 
     // Write states that are modified by the main thread. This includes drawing
     // state as well as buffer data. This should be called in the main or tracing
     // thread.
-    void writeToProtoDrawingState(LayerProto* layerInfo, uint32_t traceFlags, const DisplayDevice*);
+    void writeToProtoDrawingState(LayerProto* layerInfo);
     // Write drawing or current state. If writing current state, the caller should hold the
     // external mStateLock. If writing drawing state, this function should be called on the
     // main or tracing thread.
     void writeToProtoCommonState(LayerProto* layerInfo, LayerVector::StateSet,
-                                 uint32_t traceFlags = SurfaceTracing::TRACE_ALL);
+                                 uint32_t traceFlags = LayerTracing::TRACE_ALL);
 
     gui::WindowInfo::Type getWindowType() const { return mWindowType; }
 
-    bool getPrimaryDisplayOnly() const;
-
     void updateMirrorInfo();
 
     /*
@@ -757,6 +751,7 @@
                                   FrameEventHistoryDelta* outDelta);
 
     ui::Transform getTransform() const;
+    bool isTransformValid() const;
 
     // Returns the Alpha of the Surface, accounting for the Alpha
     // of parent Surfaces in the hierarchy (alpha's will be multiplied
@@ -854,18 +849,19 @@
     bool getPremultipledAlpha() const;
     void setInputInfo(const gui::WindowInfo& info);
 
-    gui::WindowInfo fillInputInfo(const sp<DisplayDevice>& display);
+    gui::WindowInfo fillInputInfo(const ui::Transform& displayTransform, bool displayIsSecure);
+
     /**
      * Returns whether this layer has an explicitly set input-info.
      */
     bool hasInputInfo() const;
 
-    // Sets the parent's gameMode for this layer and all its children. Parent's gameMode is applied
-    // only to layers that do not have the GAME_MODE_METADATA set by WMShell. Any layer(along with
-    // its children) that has the metadata set will use the gameMode from the metadata.
-    void setGameModeForTree(int32_t parentGameMode);
-    void setGameMode(int32_t gameMode) { mGameMode = gameMode; };
-    int32_t getGameMode() const { return mGameMode; }
+    // Sets the GameMode for the tree rooted at this layer. A layer in the tree inherits this
+    // GameMode unless it (or an ancestor) has GAME_MODE_METADATA.
+    void setGameModeForTree(GameMode);
+
+    void setGameMode(GameMode gameMode) { mGameMode = gameMode; }
+    GameMode getGameMode() const { return mGameMode; }
 
     virtual uid_t getOwnerUid() const { return mOwnerUid; }
 
@@ -886,7 +882,7 @@
     // 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.
-    int32_t sequence{sSequence++};
+    const int32_t sequence;
 
     bool mPendingHWCDestroy{false};
 
@@ -901,6 +897,8 @@
     virtual std::string getPendingBufferCounterName() { return ""; }
     virtual bool updateGeometry() { return false; }
 
+    virtual bool simpleBufferUpdate(const layer_state_t&) const { return false; }
+
 protected:
     friend class impl::SurfaceInterceptor;
 
@@ -961,6 +959,19 @@
     bool usingRelativeZ(LayerVector::StateSet) const;
 
     virtual ui::Transform getInputTransform() const;
+    /**
+     * Get the bounds in layer space within which this layer can receive input.
+     *
+     * These bounds are used to:
+     * - Determine the input frame for the layer to be used for occlusion detection; and
+     * - Determine the coordinate space within which the layer will receive input. The top-left of
+     *   this rect will be the origin of the coordinate space that the input events sent to the
+     *   layer will be in (prior to accounting for surface insets).
+     *
+     * The layer can still receive touch input if these bounds are invalid if
+     * "replaceTouchableRegionWithCrop" is specified. In this case, the layer will receive input
+     * in this layer's space, regardless of the specified crop layer.
+     */
     virtual Rect getInputBounds() const;
 
     // constant
@@ -1036,13 +1047,16 @@
 
     mutable bool mDrawingStateModified = false;
 
+    sp<Fence> mLastClientCompositionFence;
+    bool mAlreadyDisplayedThisCompose = false;
 private:
     virtual void setTransformHint(ui::Transform::RotationFlags) {}
 
     // Returns true if the layer can draw shadows on its border.
     virtual bool canDrawShadows() const { return true; }
 
-    Hwc2::IComposerClient::Composition getCompositionType(const DisplayDevice&) const;
+    aidl::android::hardware::graphics::composer3::Composition getCompositionType(
+            const DisplayDevice&) const;
 
     /**
      * Returns an unsorted vector of all layers that are part of this tree.
@@ -1078,8 +1092,8 @@
     // hasInputInfo() or no-op if no such parent is found.
     void fillTouchOcclusionMode(gui::WindowInfo& info);
 
-    // Fills in the frame and transform info for the gui::WindowInfo
-    void fillInputFrameInfo(gui::WindowInfo& info, const ui::Transform& toNonRotatedDisplay);
+    // Fills in the frame and transform info for the gui::WindowInfo.
+    void fillInputFrameInfo(gui::WindowInfo&, const ui::Transform& screenToDisplay);
 
     // Cached properties computed from drawing state
     // Effective transform taking into account parent transforms and any parent scaling, which is
@@ -1115,14 +1129,16 @@
     // shadow radius is the set shadow radius, otherwise its the parent's shadow radius.
     float mEffectiveShadowRadius = 0.f;
 
-    // Game mode for the layer. Set by WindowManagerShell, game mode is used in
-    // metrics(SurfaceFlingerStats).
-    int32_t mGameMode = 0;
+    // Game mode for the layer. Set by WindowManagerShell and recorded by SurfaceFlingerStats.
+    GameMode mGameMode = GameMode::Unsupported;
 
     // A list of regions on this layer that should have blurs.
     const std::vector<BlurRegion> getBlurRegions() const;
 
     bool mIsAtRoot = false;
+
+    uint32_t mLayerCreationFlags;
+    bool findInHierarchy(const sp<Layer>&);
 };
 
 std::ostream& operator<<(std::ostream& stream, const Layer::FrameRate& rate);
diff --git a/services/surfaceflinger/LayerProtoHelper.cpp b/services/surfaceflinger/LayerProtoHelper.cpp
index 1062126..0506c47 100644
--- a/services/surfaceflinger/LayerProtoHelper.cpp
+++ b/services/surfaceflinger/LayerProtoHelper.cpp
@@ -53,28 +53,52 @@
         return;
     }
 
+    writeToProto(region, getRegionProto());
+}
+
+void LayerProtoHelper::writeToProto(const Region& region, RegionProto* regionProto) {
+    if (region.isEmpty()) {
+        return;
+    }
+
     Region::const_iterator head = region.begin();
     Region::const_iterator const tail = region.end();
     // Use a lambda do avoid writing the object header when the object is empty
-    RegionProto* regionProto = getRegionProto();
     while (head != tail) {
-        std::function<RectProto*()> getProtoRect = [&]() { return regionProto->add_rect(); };
-        writeToProto(*head, getProtoRect);
+        writeToProto(*head, regionProto->add_rect());
         head++;
     }
 }
 
+void LayerProtoHelper::readFromProto(const RegionProto& regionProto, Region& outRegion) {
+    for (int i = 0; i < regionProto.rect_size(); i++) {
+        Rect rect;
+        readFromProto(regionProto.rect(i), rect);
+        outRegion.orSelf(rect);
+    }
+}
+
 void LayerProtoHelper::writeToProto(const Rect& rect, std::function<RectProto*()> getRectProto) {
     if (rect.left != 0 || rect.right != 0 || rect.top != 0 || rect.bottom != 0) {
         // Use a lambda do avoid writing the object header when the object is empty
-        RectProto* rectProto = getRectProto();
-        rectProto->set_left(rect.left);
-        rectProto->set_top(rect.top);
-        rectProto->set_bottom(rect.bottom);
-        rectProto->set_right(rect.right);
+        writeToProto(rect, getRectProto());
     }
 }
 
+void LayerProtoHelper::writeToProto(const Rect& rect, RectProto* rectProto) {
+    rectProto->set_left(rect.left);
+    rectProto->set_top(rect.top);
+    rectProto->set_bottom(rect.bottom);
+    rectProto->set_right(rect.right);
+}
+
+void LayerProtoHelper::readFromProto(const RectProto& proto, Rect& outRect) {
+    outRect.left = proto.left();
+    outRect.top = proto.top();
+    outRect.bottom = proto.bottom();
+    outRect.right = proto.right();
+}
+
 void LayerProtoHelper::writeToProto(const FloatRect& rect,
                                     std::function<FloatRectProto*()> getFloatRectProto) {
     if (rect.left != 0 || rect.right != 0 || rect.top != 0 || rect.bottom != 0) {
@@ -130,16 +154,16 @@
     }
 }
 
-void LayerProtoHelper::writeToProto(const sp<GraphicBuffer>& buffer,
+void LayerProtoHelper::writeToProto(const renderengine::ExternalTexture& buffer,
                                     std::function<ActiveBufferProto*()> getActiveBufferProto) {
-    if (buffer->getWidth() != 0 || buffer->getHeight() != 0 || buffer->getStride() != 0 ||
-        buffer->format != 0) {
+    if (buffer.getWidth() != 0 || buffer.getHeight() != 0 || buffer.getUsage() != 0 ||
+        buffer.getPixelFormat() != 0) {
         // Use a lambda do avoid writing the object header when the object is empty
         ActiveBufferProto* activeBufferProto = getActiveBufferProto();
-        activeBufferProto->set_width(buffer->getWidth());
-        activeBufferProto->set_height(buffer->getHeight());
-        activeBufferProto->set_stride(buffer->getStride());
-        activeBufferProto->set_format(buffer->format);
+        activeBufferProto->set_width(buffer.getWidth());
+        activeBufferProto->set_height(buffer.getHeight());
+        activeBufferProto->set_stride(buffer.getUsage());
+        activeBufferProto->set_format(buffer.getPixelFormat());
     }
 }
 
@@ -151,12 +175,12 @@
     }
 
     InputWindowInfoProto* proto = getInputWindowInfoProto();
-    proto->set_layout_params_flags(inputInfo.flags.get());
+    proto->set_layout_params_flags(inputInfo.layoutParamsFlags.get());
     using U = std::underlying_type_t<WindowInfo::Type>;
     // TODO(b/129481165): This static assert can be safely removed once conversion warnings
     // are re-enabled.
     static_assert(std::is_same_v<U, int32_t>);
-    proto->set_layout_params_type(static_cast<U>(inputInfo.type));
+    proto->set_layout_params_type(static_cast<U>(inputInfo.layoutParamsType));
 
     LayerProtoHelper::writeToProto({inputInfo.frameLeft, inputInfo.frameTop, inputInfo.frameRight,
                                     inputInfo.frameBottom},
@@ -165,9 +189,10 @@
                                    [&]() { return proto->mutable_touchable_region(); });
 
     proto->set_surface_inset(inputInfo.surfaceInset);
-    proto->set_visible(inputInfo.visible);
-    proto->set_focusable(inputInfo.focusable);
-    proto->set_has_wallpaper(inputInfo.hasWallpaper);
+    using InputConfig = gui::WindowInfo::InputConfig;
+    proto->set_visible(!inputInfo.inputConfig.test(InputConfig::NOT_VISIBLE));
+    proto->set_focusable(!inputInfo.inputConfig.test(InputConfig::NOT_FOCUSABLE));
+    proto->set_has_wallpaper(inputInfo.inputConfig.test(InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER));
 
     proto->set_global_scale_factor(inputInfo.globalScaleFactor);
     LayerProtoHelper::writeToProtoDeprecated(inputInfo.transform, proto->mutable_transform());
@@ -189,6 +214,39 @@
     }
 }
 
+void LayerProtoHelper::readFromProto(const ColorTransformProto& colorTransformProto, mat4& matrix) {
+    for (int i = 0; i < mat4::ROW_SIZE; i++) {
+        for (int j = 0; j < mat4::COL_SIZE; j++) {
+            matrix[i][j] = colorTransformProto.val(i * mat4::COL_SIZE + j);
+        }
+    }
+}
+
+void LayerProtoHelper::writeToProto(const android::BlurRegion region, BlurRegion* proto) {
+    proto->set_blur_radius(region.blurRadius);
+    proto->set_corner_radius_tl(region.cornerRadiusTL);
+    proto->set_corner_radius_tr(region.cornerRadiusTR);
+    proto->set_corner_radius_bl(region.cornerRadiusBL);
+    proto->set_corner_radius_br(region.cornerRadiusBR);
+    proto->set_alpha(region.alpha);
+    proto->set_left(region.left);
+    proto->set_top(region.top);
+    proto->set_right(region.right);
+    proto->set_bottom(region.bottom);
+}
+
+void LayerProtoHelper::readFromProto(const BlurRegion& proto, android::BlurRegion& outRegion) {
+    outRegion.blurRadius = proto.blur_radius();
+    outRegion.cornerRadiusTL = proto.corner_radius_tl();
+    outRegion.cornerRadiusTR = proto.corner_radius_tr();
+    outRegion.cornerRadiusBL = proto.corner_radius_bl();
+    outRegion.cornerRadiusBR = proto.corner_radius_br();
+    outRegion.alpha = proto.alpha();
+    outRegion.left = proto.left();
+    outRegion.top = proto.top();
+    outRegion.right = proto.right();
+    outRegion.bottom = proto.bottom();
+}
 } // namespace surfaceflinger
 } // namespace android
 
diff --git a/services/surfaceflinger/LayerProtoHelper.h b/services/surfaceflinger/LayerProtoHelper.h
index 36e0647..6ade143 100644
--- a/services/surfaceflinger/LayerProtoHelper.h
+++ b/services/surfaceflinger/LayerProtoHelper.h
@@ -15,10 +15,12 @@
  */
 
 #include <layerproto/LayerProtoHeader.h>
+#include <renderengine/ExternalTexture.h>
 
 #include <Layer.h>
 #include <gui/WindowInfo.h>
 #include <math/vec4.h>
+#include <ui/BlurRegion.h>
 #include <ui/GraphicBuffer.h>
 #include <ui/Rect.h>
 #include <ui/Region.h>
@@ -33,9 +35,13 @@
     static void writeSizeToProto(const uint32_t w, const uint32_t h,
                                  std::function<SizeProto*()> getSizeProto);
     static void writeToProto(const Rect& rect, std::function<RectProto*()> getRectProto);
+    static void writeToProto(const Rect& rect, RectProto* rectProto);
+    static void readFromProto(const RectProto& proto, Rect& outRect);
     static void writeToProto(const FloatRect& rect,
                              std::function<FloatRectProto*()> getFloatRectProto);
     static void writeToProto(const Region& region, std::function<RegionProto*()> getRegionProto);
+    static void writeToProto(const Region& region, RegionProto* regionProto);
+    static void readFromProto(const RegionProto& regionProto, Region& outRegion);
     static void writeToProto(const half4 color, std::function<ColorProto*()> getColorProto);
     // This writeToProto for transform is incorrect, but due to backwards compatibility, we can't
     // update Layers to use it. Use writeTransformToProto for any new transform proto data.
@@ -43,12 +49,15 @@
                                        TransformProto* transformProto);
     static void writeTransformToProto(const ui::Transform& transform,
                                       TransformProto* transformProto);
-    static void writeToProto(const sp<GraphicBuffer>& buffer,
+    static void writeToProto(const renderengine::ExternalTexture& buffer,
                              std::function<ActiveBufferProto*()> getActiveBufferProto);
     static void writeToProto(const gui::WindowInfo& inputInfo,
                              const wp<Layer>& touchableRegionBounds,
                              std::function<InputWindowInfoProto*()> getInputWindowInfoProto);
     static void writeToProto(const mat4 matrix, ColorTransformProto* colorTransformProto);
+    static void readFromProto(const ColorTransformProto& colorTransformProto, mat4& matrix);
+    static void writeToProto(const android::BlurRegion region, BlurRegion*);
+    static void readFromProto(const BlurRegion& proto, android::BlurRegion& outRegion);
 };
 
 } // namespace surfaceflinger
diff --git a/services/surfaceflinger/LayerRenderArea.cpp b/services/surfaceflinger/LayerRenderArea.cpp
index 11fe6d0..896f254 100644
--- a/services/surfaceflinger/LayerRenderArea.cpp
+++ b/services/surfaceflinger/LayerRenderArea.cpp
@@ -26,18 +26,12 @@
 namespace android {
 namespace {
 
-struct ReparentForDrawing {
-    const sp<Layer>& oldParent;
-
-    ReparentForDrawing(const sp<Layer>& oldParent, const sp<Layer>& newParent,
-                       const Rect& drawingBounds)
-          : oldParent(oldParent) {
+void reparentForDrawing(const sp<Layer>& oldParent, const sp<Layer>& newParent,
+                   const Rect& drawingBounds) {
         // Compute and cache the bounds for the new parent layer.
         newParent->computeBounds(drawingBounds.toFloatRect(), ui::Transform(),
-                                 0.f /* shadowRadius */);
+            0.f /* shadowRadius */);
         oldParent->setChildrenDrawingParent(newParent);
-    }
-    ~ReparentForDrawing() { oldParent->setChildrenDrawingParent(oldParent); }
 };
 
 } // namespace
@@ -112,15 +106,21 @@
         }
         drawLayers();
     } else {
-        uint32_t w = static_cast<uint32_t>(getWidth());
-        uint32_t h = static_cast<uint32_t>(getHeight());
         // In the "childrenOnly" case we reparent the children to a screenshot
         // layer which has no properties set and which does not draw.
+        //  We hold the statelock as the reparent-for-drawing operation modifies the
+        //  hierarchy and there could be readers on Binder threads, like dump.
         sp<ContainerLayer> screenshotParentLayer = mFlinger.getFactory().createContainerLayer(
-                {&mFlinger, nullptr, "Screenshot Parent"s, w, h, 0, LayerMetadata()});
-
-        ReparentForDrawing reparent(mLayer, screenshotParentLayer, sourceCrop);
+                  {&mFlinger, nullptr, "Screenshot Parent"s, 0, LayerMetadata()});
+        {
+            Mutex::Autolock _l(mFlinger.mStateLock);
+            reparentForDrawing(mLayer, screenshotParentLayer, sourceCrop);
+        }
         drawLayers();
+        {
+            Mutex::Autolock _l(mFlinger.mStateLock);
+            mLayer->setChildrenDrawingParent(mLayer);
+        }
     }
 }
 
diff --git a/services/surfaceflinger/LayerRenderArea.h b/services/surfaceflinger/LayerRenderArea.h
index 6a90694..41273e0 100644
--- a/services/surfaceflinger/LayerRenderArea.h
+++ b/services/surfaceflinger/LayerRenderArea.h
@@ -46,6 +46,7 @@
     Rect getSourceCrop() const override;
 
     void render(std::function<void()> drawLayers) override;
+    virtual sp<Layer> getParentLayer() const { return mLayer; }
 
 private:
     const sp<Layer> mLayer;
@@ -58,4 +59,4 @@
     const bool mChildrenOnly;
 };
 
-} // namespace android
\ No newline at end of file
+} // namespace android
diff --git a/services/surfaceflinger/LayerVector.cpp b/services/surfaceflinger/LayerVector.cpp
index aee820a..f52e60d 100644
--- a/services/surfaceflinger/LayerVector.cpp
+++ b/services/surfaceflinger/LayerVector.cpp
@@ -45,8 +45,8 @@
     const auto& lState = l->getDrawingState();
     const auto& rState = r->getDrawingState();
 
-    uint32_t ls = lState.layerStack;
-    uint32_t rs = rState.layerStack;
+    const auto ls = lState.layerStack;
+    const auto rs = rState.layerStack;
     if (ls != rs)
         return (ls > rs) ? 1 : -1;
 
diff --git a/services/surfaceflinger/MonitoredProducer.cpp b/services/surfaceflinger/MonitoredProducer.cpp
index 6b2d745..df76f50 100644
--- a/services/surfaceflinger/MonitoredProducer.cpp
+++ b/services/surfaceflinger/MonitoredProducer.cpp
@@ -33,13 +33,7 @@
     mFlinger(flinger),
     mLayer(layer) {}
 
-MonitoredProducer::~MonitoredProducer() {
-    // Remove ourselves from SurfaceFlinger's list. We do this asynchronously
-    // because we don't know where this destructor is called from. It could be
-    // called with the mStateLock held, leading to a dead-lock (it actually
-    // happens).
-    mFlinger->removeGraphicBufferProducerAsync(onAsBinder());
-}
+MonitoredProducer::~MonitoredProducer() {}
 
 status_t MonitoredProducer::requestBuffer(int slot, sp<GraphicBuffer>* buf) {
     return mProducer->requestBuffer(slot, buf);
diff --git a/services/surfaceflinger/RefreshRateOverlay.cpp b/services/surfaceflinger/RefreshRateOverlay.cpp
index bc32a1d..81c1566 100644
--- a/services/surfaceflinger/RefreshRateOverlay.cpp
+++ b/services/surfaceflinger/RefreshRateOverlay.cpp
@@ -25,92 +25,77 @@
 #include "Client.h"
 #include "Layer.h"
 
+#include <SkBlendMode.h>
+#include <SkPaint.h>
+#include <SkRect.h>
+#include <SkSurface.h>
 #include <gui/IProducerListener.h>
+#include <gui/SurfaceComposerClient.h>
+#include <gui/SurfaceControl.h>
 
 #undef LOG_TAG
 #define LOG_TAG "RefreshRateOverlay"
 
 namespace android {
 
-void RefreshRateOverlay::SevenSegmentDrawer::drawRect(const Rect& r, const half4& color,
-                                                      const sp<GraphicBuffer>& buffer,
-                                                      uint8_t* pixels) {
-    for (int32_t j = r.top; j < r.bottom; j++) {
-        if (j >= buffer->getHeight()) {
-            break;
-        }
-
-        for (int32_t i = r.left; i < r.right; i++) {
-            if (i >= buffer->getWidth()) {
-                break;
-            }
-
-            uint8_t* iter = pixels + 4 * (i + (buffer->getStride() * j));
-            iter[0] = uint8_t(color.r * 255);
-            iter[1] = uint8_t(color.g * 255);
-            iter[2] = uint8_t(color.b * 255);
-            iter[3] = uint8_t(color.a * 255);
-        }
-    }
-}
-
-void RefreshRateOverlay::SevenSegmentDrawer::drawSegment(Segment segment, int left,
-                                                         const half4& color,
-                                                         const sp<GraphicBuffer>& buffer,
-                                                         uint8_t* pixels) {
-    const Rect rect = [&]() {
+void RefreshRateOverlay::SevenSegmentDrawer::drawSegment(Segment segment, int left, SkColor& color,
+                                                         SkCanvas& canvas) {
+    const SkRect rect = [&]() {
         switch (segment) {
             case Segment::Upper:
-                return Rect(left, 0, left + DIGIT_WIDTH, DIGIT_SPACE);
+                return SkRect::MakeLTRB(left, 0, left + DIGIT_WIDTH, DIGIT_SPACE);
             case Segment::UpperLeft:
-                return Rect(left, 0, left + DIGIT_SPACE, DIGIT_HEIGHT / 2);
+                return SkRect::MakeLTRB(left, 0, left + DIGIT_SPACE, DIGIT_HEIGHT / 2);
             case Segment::UpperRight:
-                return Rect(left + DIGIT_WIDTH - DIGIT_SPACE, 0, left + DIGIT_WIDTH,
-                            DIGIT_HEIGHT / 2);
+                return SkRect::MakeLTRB(left + DIGIT_WIDTH - DIGIT_SPACE, 0, left + DIGIT_WIDTH,
+                                        DIGIT_HEIGHT / 2);
             case Segment::Middle:
-                return Rect(left, DIGIT_HEIGHT / 2 - DIGIT_SPACE / 2, left + DIGIT_WIDTH,
-                            DIGIT_HEIGHT / 2 + DIGIT_SPACE / 2);
+                return SkRect::MakeLTRB(left, DIGIT_HEIGHT / 2 - DIGIT_SPACE / 2,
+                                        left + DIGIT_WIDTH, DIGIT_HEIGHT / 2 + DIGIT_SPACE / 2);
             case Segment::LowerLeft:
-                return Rect(left, DIGIT_HEIGHT / 2, left + DIGIT_SPACE, DIGIT_HEIGHT);
+                return SkRect::MakeLTRB(left, DIGIT_HEIGHT / 2, left + DIGIT_SPACE, DIGIT_HEIGHT);
             case Segment::LowerRight:
-                return Rect(left + DIGIT_WIDTH - DIGIT_SPACE, DIGIT_HEIGHT / 2, left + DIGIT_WIDTH,
-                            DIGIT_HEIGHT);
-            case Segment::Buttom:
-                return Rect(left, DIGIT_HEIGHT - DIGIT_SPACE, left + DIGIT_WIDTH, DIGIT_HEIGHT);
+                return SkRect::MakeLTRB(left + DIGIT_WIDTH - DIGIT_SPACE, DIGIT_HEIGHT / 2,
+                                        left + DIGIT_WIDTH, DIGIT_HEIGHT);
+            case Segment::Bottom:
+                return SkRect::MakeLTRB(left, DIGIT_HEIGHT - DIGIT_SPACE, left + DIGIT_WIDTH,
+                                        DIGIT_HEIGHT);
         }
     }();
 
-    drawRect(rect, color, buffer, pixels);
+    SkPaint paint;
+    paint.setColor(color);
+    paint.setBlendMode(SkBlendMode::kSrc);
+    canvas.drawRect(rect, paint);
 }
 
-void RefreshRateOverlay::SevenSegmentDrawer::drawDigit(int digit, int left, const half4& color,
-                                                       const sp<GraphicBuffer>& buffer,
-                                                       uint8_t* pixels) {
+void RefreshRateOverlay::SevenSegmentDrawer::drawDigit(int digit, int left, SkColor& color,
+                                                       SkCanvas& canvas) {
     if (digit < 0 || digit > 9) return;
 
     if (digit == 0 || digit == 2 || digit == 3 || digit == 5 || digit == 6 || digit == 7 ||
         digit == 8 || digit == 9)
-        drawSegment(Segment::Upper, left, color, buffer, pixels);
+        drawSegment(Segment::Upper, left, color, canvas);
     if (digit == 0 || digit == 4 || digit == 5 || digit == 6 || digit == 8 || digit == 9)
-        drawSegment(Segment::UpperLeft, left, color, buffer, pixels);
+        drawSegment(Segment::UpperLeft, left, color, canvas);
     if (digit == 0 || digit == 1 || digit == 2 || digit == 3 || digit == 4 || digit == 7 ||
         digit == 8 || digit == 9)
-        drawSegment(Segment::UpperRight, left, color, buffer, pixels);
+        drawSegment(Segment::UpperRight, left, color, canvas);
     if (digit == 2 || digit == 3 || digit == 4 || digit == 5 || digit == 6 || digit == 8 ||
         digit == 9)
-        drawSegment(Segment::Middle, left, color, buffer, pixels);
+        drawSegment(Segment::Middle, left, color, canvas);
     if (digit == 0 || digit == 2 || digit == 6 || digit == 8)
-        drawSegment(Segment::LowerLeft, left, color, buffer, pixels);
+        drawSegment(Segment::LowerLeft, left, color, canvas);
     if (digit == 0 || digit == 1 || digit == 3 || digit == 4 || digit == 5 || digit == 6 ||
         digit == 7 || digit == 8 || digit == 9)
-        drawSegment(Segment::LowerRight, left, color, buffer, pixels);
+        drawSegment(Segment::LowerRight, left, color, canvas);
     if (digit == 0 || digit == 2 || digit == 3 || digit == 5 || digit == 6 || digit == 8 ||
         digit == 9)
-        drawSegment(Segment::Buttom, left, color, buffer, pixels);
+        drawSegment(Segment::Bottom, left, color, canvas);
 }
 
-std::vector<sp<GraphicBuffer>> RefreshRateOverlay::SevenSegmentDrawer::drawNumber(
-        int number, const half4& color, bool showSpinner) {
+std::vector<sp<GraphicBuffer>> RefreshRateOverlay::SevenSegmentDrawer::draw(
+        int number, SkColor& color, ui::Transform::RotationFlags rotation, bool showSpinner) {
     if (number < 0 || number > 1000) return {};
 
     const auto hundreds = number / 100;
@@ -120,55 +105,76 @@
     std::vector<sp<GraphicBuffer>> buffers;
     const auto loopCount = showSpinner ? 6 : 1;
     for (int i = 0; i < loopCount; i++) {
+        // Pre-rotate the buffer before it reaches SurfaceFlinger.
+        SkMatrix canvasTransform = SkMatrix();
+        auto [bufferWidth, bufferHeight] = [&] {
+            switch (rotation) {
+                case ui::Transform::ROT_90:
+                    canvasTransform.setTranslate(BUFFER_HEIGHT, 0);
+                    canvasTransform.preRotate(90);
+                    return std::make_tuple(BUFFER_HEIGHT, BUFFER_WIDTH);
+                case ui::Transform::ROT_270:
+                    canvasTransform.setRotate(270, BUFFER_WIDTH / 2.0, BUFFER_WIDTH / 2.0);
+                    return std::make_tuple(BUFFER_HEIGHT, BUFFER_WIDTH);
+                default:
+                    return std::make_tuple(BUFFER_WIDTH, BUFFER_HEIGHT);
+            }
+        }();
         sp<GraphicBuffer> buffer =
-                new GraphicBuffer(BUFFER_WIDTH, BUFFER_HEIGHT, HAL_PIXEL_FORMAT_RGBA_8888, 1,
+                new GraphicBuffer(bufferWidth, bufferHeight, HAL_PIXEL_FORMAT_RGBA_8888, 1,
                                   GRALLOC_USAGE_SW_WRITE_RARELY | GRALLOC_USAGE_HW_COMPOSER |
                                           GRALLOC_USAGE_HW_TEXTURE,
                                   "RefreshRateOverlayBuffer");
         const status_t bufferStatus = buffer->initCheck();
         LOG_ALWAYS_FATAL_IF(bufferStatus != OK, "RefreshRateOverlay: Buffer failed to allocate: %d",
                             bufferStatus);
-        uint8_t* pixels;
-        buffer->lock(GRALLOC_USAGE_SW_WRITE_RARELY, reinterpret_cast<void**>(&pixels));
-        // Clear buffer content
-        drawRect(Rect(BUFFER_WIDTH, BUFFER_HEIGHT), half4(0), buffer, pixels);
+
+        sk_sp<SkSurface> surface = SkSurface::MakeRasterN32Premul(bufferWidth, bufferHeight);
+        SkCanvas* canvas = surface->getCanvas();
+        canvas->setMatrix(canvasTransform);
+
         int left = 0;
         if (hundreds != 0) {
-            drawDigit(hundreds, left, color, buffer, pixels);
+            drawDigit(hundreds, left, color, *canvas);
         }
         left += DIGIT_WIDTH + DIGIT_SPACE;
 
         if (tens != 0) {
-            drawDigit(tens, left, color, buffer, pixels);
+            drawDigit(tens, left, color, *canvas);
         }
         left += DIGIT_WIDTH + DIGIT_SPACE;
 
-        drawDigit(ones, left, color, buffer, pixels);
+        drawDigit(ones, left, color, *canvas);
         left += DIGIT_WIDTH + DIGIT_SPACE;
 
         if (showSpinner) {
             switch (i) {
                 case 0:
-                    drawSegment(Segment::Upper, left, color, buffer, pixels);
+                    drawSegment(Segment::Upper, left, color, *canvas);
                     break;
                 case 1:
-                    drawSegment(Segment::UpperRight, left, color, buffer, pixels);
+                    drawSegment(Segment::UpperRight, left, color, *canvas);
                     break;
                 case 2:
-                    drawSegment(Segment::LowerRight, left, color, buffer, pixels);
+                    drawSegment(Segment::LowerRight, left, color, *canvas);
                     break;
                 case 3:
-                    drawSegment(Segment::Buttom, left, color, buffer, pixels);
+                    drawSegment(Segment::Bottom, left, color, *canvas);
                     break;
                 case 4:
-                    drawSegment(Segment::LowerLeft, left, color, buffer, pixels);
+                    drawSegment(Segment::LowerLeft, left, color, *canvas);
                     break;
                 case 5:
-                    drawSegment(Segment::UpperLeft, left, color, buffer, pixels);
+                    drawSegment(Segment::UpperLeft, left, color, *canvas);
                     break;
             }
         }
 
+        void* pixels = nullptr;
+        buffer->lock(GRALLOC_USAGE_SW_WRITE_RARELY, reinterpret_cast<void**>(&pixels));
+        const SkImageInfo& imageInfo = surface->imageInfo();
+        size_t dstRowBytes = buffer->getStride() * imageInfo.bytesPerPixel();
+        canvas->readPixels(imageInfo, pixels, dstRowBytes, 0, 0);
         buffer->unlock();
         buffers.emplace_back(buffer);
     }
@@ -186,35 +192,49 @@
 }
 
 bool RefreshRateOverlay::createLayer() {
-    int32_t layerId;
-    const status_t ret =
-            mFlinger.createLayer(String8("RefreshRateOverlay"), mClient,
-                                 SevenSegmentDrawer::getWidth(), SevenSegmentDrawer::getHeight(),
-                                 PIXEL_FORMAT_RGBA_8888,
-                                 ISurfaceComposerClient::eFXSurfaceBufferState, LayerMetadata(),
-                                 &mIBinder, &mGbp, nullptr, &layerId);
-    if (ret) {
+    mSurfaceControl =
+            SurfaceComposerClient::getDefault()
+                    ->createSurface(String8("RefreshRateOverlay"), SevenSegmentDrawer::getWidth(),
+                                    SevenSegmentDrawer::getHeight(), PIXEL_FORMAT_RGBA_8888,
+                                    ISurfaceComposerClient::eFXSurfaceBufferState);
+
+    if (!mSurfaceControl) {
         ALOGE("failed to create buffer state layer");
         return false;
     }
 
-    mLayer = mClient->getLayerUser(mIBinder);
-    mLayer->setFrameRate(Layer::FrameRate(Fps(0.0f), Layer::FrameRateCompatibility::NoVote));
-    mLayer->setIsAtRoot(true);
-
-    // setting Layer's Z requires resorting layersSortedByZ
-    ssize_t idx = mFlinger.mDrawingState.layersSortedByZ.indexOf(mLayer);
-    if (mLayer->setLayer(INT32_MAX - 2) && idx >= 0) {
-        mFlinger.mDrawingState.layersSortedByZ.removeAt(idx);
-        mFlinger.mDrawingState.layersSortedByZ.add(mLayer);
-    }
+    SurfaceComposerClient::Transaction()
+            .setFrameRate(mSurfaceControl, 0.0f,
+                          static_cast<int8_t>(Layer::FrameRateCompatibility::NoVote),
+                          static_cast<int8_t>(scheduler::Seamlessness::OnlySeamless))
+            .setLayer(mSurfaceControl, INT32_MAX - 2)
+            .setTrustedOverlay(mSurfaceControl, true)
+            .apply();
 
     return true;
 }
 
-const std::vector<std::shared_ptr<renderengine::ExternalTexture>>&
-RefreshRateOverlay::getOrCreateBuffers(uint32_t fps) {
-    if (mBufferCache.find(fps) == mBufferCache.end()) {
+const std::vector<sp<GraphicBuffer>>& RefreshRateOverlay::getOrCreateBuffers(uint32_t fps) {
+    ui::Transform::RotationFlags transformHint =
+            static_cast<ui::Transform::RotationFlags>(mSurfaceControl->getTransformHint());
+    // Tell SurfaceFlinger about the pre-rotation on the buffer.
+    const auto transform = [&] {
+        switch (transformHint) {
+            case ui::Transform::ROT_90:
+                return ui::Transform::ROT_270;
+            case ui::Transform::ROT_270:
+                return ui::Transform::ROT_90;
+            default:
+                return ui::Transform::ROT_0;
+        }
+    }();
+
+    SurfaceComposerClient::Transaction t;
+    t.setTransform(mSurfaceControl, transform);
+    t.apply();
+
+    if (mBufferCache.find(transformHint) == mBufferCache.end() ||
+        mBufferCache.at(transformHint).find(fps) == mBufferCache.at(transformHint).end()) {
         // Ensure the range is > 0, so we don't divide by 0.
         const auto rangeLength = std::max(1u, mHighFps - mLowFps);
         // Clip values outside the range [mLowFps, mHighFps]. The current fps may be outside
@@ -222,25 +242,18 @@
         fps = std::max(fps, mLowFps);
         fps = std::min(fps, mHighFps);
         const auto fpsScale = static_cast<float>(fps - mLowFps) / rangeLength;
-        half4 color;
-        color.r = HIGH_FPS_COLOR.r * fpsScale + LOW_FPS_COLOR.r * (1 - fpsScale);
-        color.g = HIGH_FPS_COLOR.g * fpsScale + LOW_FPS_COLOR.g * (1 - fpsScale);
-        color.b = HIGH_FPS_COLOR.b * fpsScale + LOW_FPS_COLOR.b * (1 - fpsScale);
-        color.a = ALPHA;
-        auto buffers = SevenSegmentDrawer::drawNumber(fps, color, mShowSpinner);
-        std::vector<std::shared_ptr<renderengine::ExternalTexture>> textures;
-        std::transform(buffers.begin(), buffers.end(), std::back_inserter(textures),
-                       [&](const auto& buffer) -> std::shared_ptr<renderengine::ExternalTexture> {
-                           return std::make_shared<
-                                   renderengine::ExternalTexture>(buffer,
-                                                                  mFlinger.getRenderEngine(),
-                                                                  renderengine::ExternalTexture::
-                                                                          Usage::READABLE);
-                       });
-        mBufferCache.emplace(fps, textures);
+        SkColor4f colorBase = SkColor4f::FromColor(HIGH_FPS_COLOR) * fpsScale;
+        SkColor4f lowFpsColor = SkColor4f::FromColor(LOW_FPS_COLOR) * (1 - fpsScale);
+        colorBase.fR = colorBase.fR + lowFpsColor.fR;
+        colorBase.fG = colorBase.fG + lowFpsColor.fG;
+        colorBase.fB = colorBase.fB + lowFpsColor.fB;
+        colorBase.fA = ALPHA;
+        SkColor color = colorBase.toSkColor();
+        auto buffers = SevenSegmentDrawer::draw(fps, color, transformHint, mShowSpinner);
+        mBufferCache[transformHint].emplace(fps, buffers);
     }
 
-    return mBufferCache[fps];
+    return mBufferCache[transformHint][fps];
 }
 
 void RefreshRateOverlay::setViewport(ui::Size viewport) {
@@ -250,44 +263,37 @@
     Rect frame((3 * width) >> 4, height >> 5);
     frame.offsetBy(width >> 5, height >> 4);
 
-    layer_state_t::matrix22_t matrix;
-    matrix.dsdx = frame.getWidth() / static_cast<float>(SevenSegmentDrawer::getWidth());
-    matrix.dtdx = 0;
-    matrix.dtdy = 0;
-    matrix.dsdy = frame.getHeight() / static_cast<float>(SevenSegmentDrawer::getHeight());
-    mLayer->setMatrix(matrix, true);
-    mLayer->setPosition(frame.left, frame.top);
-    mFlinger.mTransactionFlags.fetch_or(eTransactionMask);
+    SurfaceComposerClient::Transaction t;
+    t.setMatrix(mSurfaceControl,
+                frame.getWidth() / static_cast<float>(SevenSegmentDrawer::getWidth()), 0, 0,
+                frame.getHeight() / static_cast<float>(SevenSegmentDrawer::getHeight()));
+    t.setPosition(mSurfaceControl, frame.left, frame.top);
+    t.apply();
 }
 
-void RefreshRateOverlay::setLayerStack(uint32_t stack) {
-    mLayer->setLayerStack(stack);
-    mFlinger.mTransactionFlags.fetch_or(eTransactionMask);
+void RefreshRateOverlay::setLayerStack(ui::LayerStack stack) {
+    SurfaceComposerClient::Transaction t;
+    t.setLayerStack(mSurfaceControl, stack);
+    t.apply();
 }
 
-void RefreshRateOverlay::changeRefreshRate(const Fps& fps) {
+void RefreshRateOverlay::changeRefreshRate(Fps fps) {
     mCurrentFps = fps.getIntValue();
     auto buffer = getOrCreateBuffers(*mCurrentFps)[mFrame];
-    mLayer->setBuffer(buffer, Fence::NO_FENCE, 0, 0, true, {},
-                      mLayer->getHeadFrameNumber(-1 /* expectedPresentTime */),
-                      std::nullopt /* dequeueTime */, FrameTimelineInfo{},
-                      nullptr /* releaseBufferListener */, nullptr /* releaseBufferEndpoint */);
-
-    mFlinger.mTransactionFlags.fetch_or(eTransactionMask);
+    SurfaceComposerClient::Transaction t;
+    t.setBuffer(mSurfaceControl, buffer);
+    t.apply();
 }
 
-void RefreshRateOverlay::onInvalidate() {
+void RefreshRateOverlay::animate() {
     if (!mCurrentFps.has_value()) return;
 
     const auto& buffers = getOrCreateBuffers(*mCurrentFps);
     mFrame = (mFrame + 1) % buffers.size();
     auto buffer = buffers[mFrame];
-    mLayer->setBuffer(buffer, Fence::NO_FENCE, 0, 0, true, {},
-                      mLayer->getHeadFrameNumber(-1 /* expectedPresentTime */),
-                      std::nullopt /* dequeueTime */, FrameTimelineInfo{},
-                      nullptr /* releaseBufferListener */, nullptr /* releaseBufferEndpoint */);
-
-    mFlinger.mTransactionFlags.fetch_or(eTransactionMask);
+    SurfaceComposerClient::Transaction t;
+    t.setBuffer(mSurfaceControl, buffer);
+    t.apply();
 }
 
 } // namespace android
diff --git a/services/surfaceflinger/RefreshRateOverlay.h b/services/surfaceflinger/RefreshRateOverlay.h
index f9baa89..381df37 100644
--- a/services/surfaceflinger/RefreshRateOverlay.h
+++ b/services/surfaceflinger/RefreshRateOverlay.h
@@ -16,15 +16,18 @@
 
 #pragma once
 
+#include <SkCanvas.h>
+#include <SkColor.h>
+#include <unordered_map>
+
 #include <math/vec4.h>
 #include <renderengine/RenderEngine.h>
+#include <ui/LayerStack.h>
 #include <ui/Rect.h>
 #include <ui/Size.h>
 #include <utils/StrongPointer.h>
 
-#include <unordered_map>
-
-#include "Fps.h"
+#include <scheduler/Fps.h>
 
 namespace android {
 
@@ -34,33 +37,30 @@
 class IGraphicBufferProducer;
 class Layer;
 class SurfaceFlinger;
+class SurfaceControl;
 
 class RefreshRateOverlay {
 public:
     RefreshRateOverlay(SurfaceFlinger&, uint32_t lowFps, uint32_t highFps, bool showSpinner);
 
-    void setLayerStack(uint32_t stack);
+    void setLayerStack(ui::LayerStack);
     void setViewport(ui::Size);
-    void changeRefreshRate(const Fps&);
-    void onInvalidate();
+    void changeRefreshRate(Fps);
+    void animate();
 
 private:
     class SevenSegmentDrawer {
     public:
-        static std::vector<sp<GraphicBuffer>> drawNumber(int number, const half4& color,
-                                                         bool showSpinner);
+        static std::vector<sp<GraphicBuffer>> draw(int number, SkColor& color,
+                                                   ui::Transform::RotationFlags, bool showSpinner);
         static uint32_t getHeight() { return BUFFER_HEIGHT; }
         static uint32_t getWidth() { return BUFFER_WIDTH; }
 
     private:
-        enum class Segment { Upper, UpperLeft, UpperRight, Middle, LowerLeft, LowerRight, Buttom };
+        enum class Segment { Upper, UpperLeft, UpperRight, Middle, LowerLeft, LowerRight, Bottom };
 
-        static void drawRect(const Rect& r, const half4& color, const sp<GraphicBuffer>& buffer,
-                             uint8_t* pixels);
-        static void drawSegment(Segment segment, int left, const half4& color,
-                                const sp<GraphicBuffer>& buffer, uint8_t* pixels);
-        static void drawDigit(int digit, int left, const half4& color,
-                              const sp<GraphicBuffer>& buffer, uint8_t* pixels);
+        static void drawSegment(Segment segment, int left, SkColor& color, SkCanvas& canvas);
+        static void drawDigit(int digit, int left, SkColor& color, SkCanvas& canvas);
 
         static constexpr uint32_t DIGIT_HEIGHT = 100;
         static constexpr uint32_t DIGIT_WIDTH = 64;
@@ -71,28 +71,30 @@
     };
 
     bool createLayer();
-    const std::vector<std::shared_ptr<renderengine::ExternalTexture>>& getOrCreateBuffers(
-            uint32_t fps);
+
+    const std::vector<sp<GraphicBuffer>>& getOrCreateBuffers(uint32_t fps);
 
     SurfaceFlinger& mFlinger;
     const sp<Client> mClient;
-    sp<Layer> mLayer;
     sp<IBinder> mIBinder;
     sp<IGraphicBufferProducer> mGbp;
 
-    std::unordered_map<int, std::vector<std::shared_ptr<renderengine::ExternalTexture>>>
+    std::unordered_map<ui::Transform::RotationFlags,
+                       std::unordered_map<int, std::vector<sp<GraphicBuffer>>>>
             mBufferCache;
     std::optional<int> mCurrentFps;
     int mFrame = 0;
     static constexpr float ALPHA = 0.8f;
-    const half3 LOW_FPS_COLOR = half3(1.0f, 0.0f, 0.0f);
-    const half3 HIGH_FPS_COLOR = half3(0.0f, 1.0f, 0.0f);
+    const SkColor LOW_FPS_COLOR = SK_ColorRED;
+    const SkColor HIGH_FPS_COLOR = SK_ColorGREEN;
 
     const bool mShowSpinner;
 
     // Interpolate the colors between these values.
     const uint32_t mLowFps;
     const uint32_t mHighFps;
+
+    sp<SurfaceControl> mSurfaceControl;
 };
 
 } // namespace android
diff --git a/services/surfaceflinger/RegionSamplingThread.cpp b/services/surfaceflinger/RegionSamplingThread.cpp
index aa2fec5..e29e6ab 100644
--- a/services/surfaceflinger/RegionSamplingThread.cpp
+++ b/services/surfaceflinger/RegionSamplingThread.cpp
@@ -30,8 +30,9 @@
 #include <compositionengine/impl/OutputCompositionState.h>
 #include <cutils/properties.h>
 #include <ftl/future.h>
-#include <gui/IRegionSamplingListener.h>
+#include <gui/SpHash.h>
 #include <gui/SyncScreenCaptureListener.h>
+#include <renderengine/impl/ExternalTexture.h>
 #include <ui/DisplayStatInfo.h>
 #include <utils/Trace.h>
 
@@ -46,10 +47,7 @@
 namespace android {
 using namespace std::chrono_literals;
 
-template <typename T>
-struct SpHash {
-    size_t operator()(const sp<T>& p) const { return std::hash<T*>()(p.get()); }
-};
+using gui::SpHash;
 
 constexpr auto lumaSamplingStepTag = "LumaSamplingStep";
 enum class samplingStep {
@@ -150,7 +148,7 @@
     if (mSampleRequestTime.has_value()) {
         ATRACE_INT(lumaSamplingStepTag, static_cast<int>(samplingStep::waitForSamplePhase));
         mSampleRequestTime.reset();
-        mFlinger.scheduleRegionSamplingThread();
+        mFlinger.scheduleSample();
     }
 }
 
@@ -352,14 +350,18 @@
         LOG_ALWAYS_FATAL_IF(bufferStatus != OK, "captureSample: Buffer failed to allocate: %d",
                             bufferStatus);
         buffer = std::make_shared<
-                renderengine::ExternalTexture>(graphicBuffer, mFlinger.getRenderEngine(),
-                                               renderengine::ExternalTexture::Usage::WRITEABLE);
+                renderengine::impl::ExternalTexture>(graphicBuffer, mFlinger.getRenderEngine(),
+                                                     renderengine::impl::ExternalTexture::Usage::
+                                                             WRITEABLE);
     }
 
-    const sp<SyncScreenCaptureListener> captureListener = new SyncScreenCaptureListener();
-    mFlinger.captureScreenCommon(std::move(renderAreaFuture), traverseLayers, buffer,
-                                 true /* regionSampling */, false /* grayscale */, captureListener);
-    ScreenCaptureResults captureResults = captureListener->waitForResults();
+    auto captureScreenResultFuture =
+            mFlinger.captureScreenCommon(std::move(renderAreaFuture), traverseLayers, buffer,
+                                         true /* regionSampling */, false /* grayscale */, nullptr);
+    auto& captureScreenResult = captureScreenResultFuture.get();
+    if (captureScreenResult.drawFence.ok()) {
+        sync_wait(captureScreenResult.drawFence.get(), -1);
+    }
 
     std::vector<Descriptor> activeDescriptors;
     for (const auto& descriptor : descriptors) {
diff --git a/services/surfaceflinger/RegionSamplingThread.h b/services/surfaceflinger/RegionSamplingThread.h
index 2231853..686b4b1 100644
--- a/services/surfaceflinger/RegionSamplingThread.h
+++ b/services/surfaceflinger/RegionSamplingThread.h
@@ -17,6 +17,7 @@
 #pragma once
 
 #include <android-base/thread_annotations.h>
+#include <android/gui/IRegionSamplingListener.h>
 #include <binder/IBinder.h>
 #include <renderengine/ExternalTexture.h>
 #include <ui/GraphicBuffer.h>
@@ -30,15 +31,17 @@
 #include <unordered_map>
 
 #include "Scheduler/OneShotTimer.h"
+#include "WpHash.h"
 
 namespace android {
 
-class IRegionSamplingListener;
 class Layer;
 class Scheduler;
 class SurfaceFlinger;
 struct SamplingOffsetCallback;
 
+using gui::IRegionSamplingListener;
+
 float sampleArea(const uint32_t* data, int32_t width, int32_t height, int32_t stride,
                  uint32_t orientation, const Rect& area);
 
@@ -88,11 +91,6 @@
         sp<IRegionSamplingListener> listener;
     };
 
-    struct WpHash {
-        size_t operator()(const wp<IBinder>& p) const {
-            return std::hash<IBinder*>()(p.unsafe_get());
-        }
-    };
     std::vector<float> sampleBuffer(
             const sp<GraphicBuffer>& buffer, const Point& leftTop,
             const std::vector<RegionSamplingThread::Descriptor>& descriptors, uint32_t orientation);
diff --git a/services/surfaceflinger/RenderArea.cpp b/services/surfaceflinger/RenderArea.cpp
index 9a6c853..5fea521 100644
--- a/services/surfaceflinger/RenderArea.cpp
+++ b/services/surfaceflinger/RenderArea.cpp
@@ -14,10 +14,6 @@
  * limitations under the License.
  */
 
-// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wconversion"
-
 #include "RenderArea.h"
 
 namespace android {
@@ -33,6 +29,3 @@
 }
 
 } // namespace android
-
-// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic pop // ignored "-Wconversion"
diff --git a/services/surfaceflinger/RenderArea.h b/services/surfaceflinger/RenderArea.h
index c9f7f46..387364c 100644
--- a/services/surfaceflinger/RenderArea.h
+++ b/services/surfaceflinger/RenderArea.h
@@ -4,6 +4,7 @@
 #include <ui/Transform.h>
 
 #include <functional>
+#include "Layer.h"
 
 namespace android {
 
@@ -85,6 +86,10 @@
     // Returns the source display viewport.
     const Rect& getLayerStackSpaceRect() const { return mLayerStackSpaceRect; }
 
+    // If this is a LayerRenderArea, return the root layer of the
+    // capture operation.
+    virtual sp<Layer> getParentLayer() const { return nullptr; }
+
 protected:
     const bool mAllowSecureLayers;
 
diff --git a/services/surfaceflinger/Scheduler/Android.bp b/services/surfaceflinger/Scheduler/Android.bp
new file mode 100644
index 0000000..5de796d
--- /dev/null
+++ b/services/surfaceflinger/Scheduler/Android.bp
@@ -0,0 +1,63 @@
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_native_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_native_license"],
+}
+
+cc_defaults {
+    name: "libscheduler_defaults",
+    defaults: ["surfaceflinger_defaults"],
+    cflags: [
+        "-DLOG_TAG=\"Scheduler\"",
+        "-DATRACE_TAG=ATRACE_TAG_GRAPHICS",
+    ],
+    shared_libs: [
+        "libbase",
+        "libcutils",
+        "liblog",
+        "libutils",
+    ],
+}
+
+cc_library_headers {
+    name: "libscheduler_headers",
+    defaults: ["libscheduler_defaults"],
+    export_include_dirs: ["include"],
+}
+
+// TODO(b/185535769): Remove libsurfaceflinger_unittest's dependency on AsyncCallRecorder.
+cc_library_headers {
+    name: "libscheduler_test_headers",
+    defaults: ["libscheduler_defaults"],
+    export_include_dirs: ["tests"],
+}
+
+cc_library_static {
+    name: "libscheduler",
+    defaults: ["libscheduler_defaults"],
+    srcs: [
+        "src/Timer.cpp",
+    ],
+    local_include_dirs: ["include"],
+    export_include_dirs: ["include"],
+}
+
+cc_test {
+    name: "libscheduler_test",
+    test_suites: ["device-tests"],
+    defaults: ["libscheduler_defaults"],
+    srcs: [
+        "tests/TimerTest.cpp",
+    ],
+    static_libs: [
+        "libgmock",
+        "libgtest",
+        "libscheduler",
+    ],
+    sanitize: {
+        address: true,
+    },
+}
diff --git a/services/surfaceflinger/Scheduler/DispSyncSource.cpp b/services/surfaceflinger/Scheduler/DispSyncSource.cpp
index 50b38c9..747032b 100644
--- a/services/surfaceflinger/Scheduler/DispSyncSource.cpp
+++ b/services/surfaceflinger/Scheduler/DispSyncSource.cpp
@@ -23,6 +23,7 @@
 #include <mutex>
 
 #include "EventThread.h"
+#include "VSyncTracker.h"
 #include "VsyncController.h"
 
 namespace android::scheduler {
@@ -114,7 +115,7 @@
     std::chrono::nanoseconds mLastCallTime GUARDED_BY(mMutex) = 0ns;
 };
 
-DispSyncSource::DispSyncSource(scheduler::VSyncDispatch& vSyncDispatch,
+DispSyncSource::DispSyncSource(VSyncDispatch& vSyncDispatch, VSyncTracker& vSyncTracker,
                                std::chrono::nanoseconds workDuration,
                                std::chrono::nanoseconds readyDuration, bool traceVsync,
                                const char* name)
@@ -122,6 +123,7 @@
         mValue(base::StringPrintf("VSYNC-%s", name), 0),
         mTraceVsync(traceVsync),
         mVsyncOnLabel(base::StringPrintf("VsyncOn-%s", name)),
+        mVSyncTracker(vSyncTracker),
         mWorkDuration(base::StringPrintf("VsyncWorkDuration-%s", name), workDuration),
         mReadyDuration(readyDuration) {
     mCallbackRepeater =
@@ -180,10 +182,18 @@
     }
 
     if (callback != nullptr) {
-        callback->onVSyncEvent(targetWakeupTime, vsyncTime, readyTime);
+        callback->onVSyncEvent(targetWakeupTime, {vsyncTime, readyTime});
     }
 }
 
+VSyncSource::VSyncData DispSyncSource::getLatestVSyncData() const {
+    std::lock_guard lock(mVsyncMutex);
+    nsecs_t expectedPresentTime = mVSyncTracker.nextAnticipatedVSyncTimeFrom(
+            systemTime() + mWorkDuration.get().count() + mReadyDuration.count());
+    nsecs_t deadline = expectedPresentTime - mWorkDuration.get().count() - mReadyDuration.count();
+    return {expectedPresentTime, deadline};
+}
+
 void DispSyncSource::dump(std::string& result) const {
     std::lock_guard lock(mVsyncMutex);
     StringAppendF(&result, "DispSyncSource: %s(%s)\n", mName, mEnabled ? "enabled" : "disabled");
diff --git a/services/surfaceflinger/Scheduler/DispSyncSource.h b/services/surfaceflinger/Scheduler/DispSyncSource.h
index 2fce235..edcd3ac 100644
--- a/services/surfaceflinger/Scheduler/DispSyncSource.h
+++ b/services/surfaceflinger/Scheduler/DispSyncSource.h
@@ -24,11 +24,13 @@
 
 namespace android::scheduler {
 class CallbackRepeater;
+class VSyncTracker;
 
 class DispSyncSource final : public VSyncSource {
 public:
-    DispSyncSource(VSyncDispatch& vSyncDispatch, std::chrono::nanoseconds workDuration,
-                   std::chrono::nanoseconds readyDuration, bool traceVsync, const char* name);
+    DispSyncSource(VSyncDispatch& vSyncDispatch, VSyncTracker& vSyncTracker,
+                   std::chrono::nanoseconds workDuration, std::chrono::nanoseconds readyDuration,
+                   bool traceVsync, const char* name);
 
     ~DispSyncSource() override;
 
@@ -38,6 +40,7 @@
     void setCallback(VSyncSource::Callback* callback) override;
     void setDuration(std::chrono::nanoseconds workDuration,
                      std::chrono::nanoseconds readyDuration) override;
+    VSyncData getLatestVSyncData() const override;
 
     void dump(std::string&) const override;
 
@@ -50,6 +53,8 @@
     const bool mTraceVsync;
     const std::string mVsyncOnLabel;
 
+    const VSyncTracker& mVSyncTracker;
+
     std::unique_ptr<CallbackRepeater> mCallbackRepeater;
 
     std::mutex mCallbackMutex;
diff --git a/services/surfaceflinger/Scheduler/EventThread.cpp b/services/surfaceflinger/Scheduler/EventThread.cpp
index 2bdcaf6..5ba8a1b 100644
--- a/services/surfaceflinger/Scheduler/EventThread.cpp
+++ b/services/surfaceflinger/Scheduler/EventThread.cpp
@@ -87,9 +87,10 @@
                                 to_string(event.header.displayId).c_str(),
                                 event.hotplug.connected ? "connected" : "disconnected");
         case DisplayEventReceiver::DISPLAY_EVENT_VSYNC:
-            return StringPrintf("VSync{displayId=%s, count=%u, expectedVSyncTimestamp=%" PRId64 "}",
+            return StringPrintf("VSync{displayId=%s, count=%u, expectedPresentationTime=%" PRId64
+                                "}",
                                 to_string(event.header.displayId).c_str(), event.vsync.count,
-                                event.vsync.expectedVSyncTimestamp);
+                                event.vsync.vsyncData.preferredExpectedPresentationTime());
         case DisplayEventReceiver::DISPLAY_EVENT_MODE_CHANGE:
             return StringPrintf("ModeChanged{displayId=%s, modeId=%u}",
                                 to_string(event.header.displayId).c_str(), event.modeChange.modeId);
@@ -107,14 +108,19 @@
 }
 
 DisplayEventReceiver::Event makeVSync(PhysicalDisplayId displayId, nsecs_t timestamp,
-                                      uint32_t count, nsecs_t expectedVSyncTimestamp,
-                                      nsecs_t deadlineTimestamp, int64_t vsyncId) {
+                                      uint32_t count, nsecs_t expectedPresentationTime,
+                                      nsecs_t deadlineTimestamp) {
     DisplayEventReceiver::Event event;
     event.header = {DisplayEventReceiver::DISPLAY_EVENT_VSYNC, displayId, timestamp};
     event.vsync.count = count;
-    event.vsync.expectedVSyncTimestamp = expectedVSyncTimestamp;
-    event.vsync.deadlineTimestamp = deadlineTimestamp;
-    event.vsync.vsyncId = vsyncId;
+    event.vsync.vsyncData.preferredFrameTimelineIndex = 0;
+    // Temporarily store the current vsync information in frameTimelines[0], marked as
+    // platform-preferred. When the event is dispatched later, the frame interval at that time is
+    // used with this information to generate multiple frame timeline choices.
+    event.vsync.vsyncData.frameTimelines[0] = {.vsyncId = FrameTimelineInfo::INVALID_VSYNC_ID,
+                                               .deadlineTimestamp = deadlineTimestamp,
+                                               .expectedPresentationTime =
+                                                       expectedPresentationTime};
     return event;
 }
 
@@ -170,20 +176,28 @@
     mEventThread->registerDisplayEventConnection(this);
 }
 
-status_t EventThreadConnection::stealReceiveChannel(gui::BitTube* outChannel) {
+binder::Status EventThreadConnection::stealReceiveChannel(gui::BitTube* outChannel) {
     outChannel->setReceiveFd(mChannel.moveReceiveFd());
     outChannel->setSendFd(base::unique_fd(dup(mChannel.getSendFd())));
-    return NO_ERROR;
+    return binder::Status::ok();
 }
 
-status_t EventThreadConnection::setVsyncRate(uint32_t rate) {
-    mEventThread->setVsyncRate(rate, this);
-    return NO_ERROR;
+binder::Status EventThreadConnection::setVsyncRate(int rate) {
+    mEventThread->setVsyncRate(static_cast<uint32_t>(rate), this);
+    return binder::Status::ok();
 }
 
-void EventThreadConnection::requestNextVsync() {
-    ATRACE_NAME("requestNextVsync");
+binder::Status EventThreadConnection::requestNextVsync() {
+    ATRACE_CALL();
     mEventThread->requestNextVsync(this);
+    return binder::Status::ok();
+}
+
+binder::Status EventThreadConnection::getLatestVsyncEventData(
+        ParcelableVsyncEventData* outVsyncEventData) {
+    ATRACE_CALL();
+    outVsyncEventData->vsync = mEventThread->getLatestVsyncEventData(this);
+    return binder::Status::ok();
 }
 
 status_t EventThreadConnection::postEvent(const DisplayEventReceiver::Event& event) {
@@ -330,6 +344,21 @@
     }
 }
 
+VsyncEventData EventThread::getLatestVsyncEventData(
+        const sp<EventThreadConnection>& connection) const {
+    VsyncEventData vsyncEventData;
+    nsecs_t frameInterval = mGetVsyncPeriodFunction(connection->mOwnerUid);
+    vsyncEventData.frameInterval = frameInterval;
+    VSyncSource::VSyncData vsyncData;
+    {
+        std::lock_guard<std::mutex> lock(mMutex);
+        vsyncData = mVSyncSource->getLatestVSyncData();
+    }
+    generateFrameTimeline(vsyncEventData, frameInterval, systemTime(SYSTEM_TIME_MONOTONIC),
+                          vsyncData.expectedPresentationTime, vsyncData.deadlineTimestamp);
+    return vsyncEventData;
+}
+
 void EventThread::onScreenReleased() {
     std::lock_guard<std::mutex> lock(mMutex);
     if (!mVSyncState || mVSyncState->synthetic) {
@@ -350,21 +379,13 @@
     mCondition.notify_all();
 }
 
-void EventThread::onVSyncEvent(nsecs_t timestamp, nsecs_t expectedVSyncTimestamp,
-                               nsecs_t deadlineTimestamp) {
+void EventThread::onVSyncEvent(nsecs_t timestamp, VSyncSource::VSyncData vsyncData) {
     std::lock_guard<std::mutex> lock(mMutex);
 
     LOG_FATAL_IF(!mVSyncState);
-    const int64_t vsyncId = [&] {
-        if (mTokenManager != nullptr) {
-            return mTokenManager->generateTokenForPredictions(
-                    {timestamp, deadlineTimestamp, expectedVSyncTimestamp});
-        }
-        return FrameTimelineInfo::INVALID_VSYNC_ID;
-    }();
-
     mPendingEvents.push_back(makeVSync(mVSyncState->displayId, timestamp, ++mVSyncState->count,
-                                       expectedVSyncTimestamp, deadlineTimestamp, vsyncId));
+                                       vsyncData.expectedPresentationTime,
+                                       vsyncData.deadlineTimestamp));
     mCondition.notify_all();
 }
 
@@ -499,16 +520,9 @@
                 const auto now = systemTime(SYSTEM_TIME_MONOTONIC);
                 const auto deadlineTimestamp = now + timeout.count();
                 const auto expectedVSyncTime = deadlineTimestamp + timeout.count();
-                const int64_t vsyncId = [&] {
-                    if (mTokenManager != nullptr) {
-                        return mTokenManager->generateTokenForPredictions(
-                                {now, deadlineTimestamp, expectedVSyncTime});
-                    }
-                    return FrameTimelineInfo::INVALID_VSYNC_ID;
-                }();
                 mPendingEvents.push_back(makeVSync(mVSyncState->displayId, now,
                                                    ++mVSyncState->count, expectedVSyncTime,
-                                                   deadlineTimestamp, vsyncId));
+                                                   deadlineTimestamp));
             }
         }
     }
@@ -518,7 +532,8 @@
                                      const sp<EventThreadConnection>& connection) const {
     const auto throttleVsync = [&] {
         return mThrottleVsyncCallback &&
-                mThrottleVsyncCallback(event.vsync.expectedVSyncTimestamp, connection->mOwnerUid);
+                mThrottleVsyncCallback(event.vsync.vsyncData.preferredExpectedPresentationTime(),
+                                       connection->mOwnerUid);
     };
 
     switch (event.header.type) {
@@ -567,12 +582,50 @@
     }
 }
 
+int64_t EventThread::generateToken(nsecs_t timestamp, nsecs_t deadlineTimestamp,
+                                   nsecs_t expectedPresentationTime) const {
+    if (mTokenManager != nullptr) {
+        return mTokenManager->generateTokenForPredictions(
+                {timestamp, deadlineTimestamp, expectedPresentationTime});
+    }
+    return FrameTimelineInfo::INVALID_VSYNC_ID;
+}
+
+void EventThread::generateFrameTimeline(VsyncEventData& outVsyncEventData, nsecs_t frameInterval,
+                                        nsecs_t timestamp,
+                                        nsecs_t preferredExpectedPresentationTime,
+                                        nsecs_t preferredDeadlineTimestamp) const {
+    // Add 1 to ensure the preferredFrameTimelineIndex entry (when multiplier == 0) is included.
+    for (int64_t multiplier = -VsyncEventData::kFrameTimelinesLength + 1, currentIndex = 0;
+         currentIndex < VsyncEventData::kFrameTimelinesLength; multiplier++) {
+        nsecs_t deadlineTimestamp = preferredDeadlineTimestamp + multiplier * frameInterval;
+        // Valid possible frame timelines must have future values.
+        if (deadlineTimestamp > timestamp) {
+            if (multiplier == 0) {
+                outVsyncEventData.preferredFrameTimelineIndex = currentIndex;
+            }
+            nsecs_t expectedPresentationTime =
+                    preferredExpectedPresentationTime + multiplier * frameInterval;
+            outVsyncEventData.frameTimelines[currentIndex] =
+                    {.vsyncId =
+                             generateToken(timestamp, deadlineTimestamp, expectedPresentationTime),
+                     .deadlineTimestamp = deadlineTimestamp,
+                     .expectedPresentationTime = expectedPresentationTime};
+            currentIndex++;
+        }
+    }
+}
+
 void EventThread::dispatchEvent(const DisplayEventReceiver::Event& event,
                                 const DisplayEventConsumers& consumers) {
     for (const auto& consumer : consumers) {
         DisplayEventReceiver::Event copy = event;
         if (event.header.type == DisplayEventReceiver::DISPLAY_EVENT_VSYNC) {
-            copy.vsync.frameInterval = mGetVsyncPeriodFunction(consumer->mOwnerUid);
+            const int64_t frameInterval = mGetVsyncPeriodFunction(consumer->mOwnerUid);
+            copy.vsync.vsyncData.frameInterval = frameInterval;
+            generateFrameTimeline(copy.vsync.vsyncData, frameInterval, copy.header.timestamp,
+                                  event.vsync.vsyncData.preferredExpectedPresentationTime(),
+                                  event.vsync.vsyncData.preferredDeadlineTimestamp());
         }
         switch (consumer->postEvent(copy)) {
             case NO_ERROR:
diff --git a/services/surfaceflinger/Scheduler/EventThread.h b/services/surfaceflinger/Scheduler/EventThread.h
index 9265a25..c406478 100644
--- a/services/surfaceflinger/Scheduler/EventThread.h
+++ b/services/surfaceflinger/Scheduler/EventThread.h
@@ -17,8 +17,8 @@
 #pragma once
 
 #include <android-base/thread_annotations.h>
+#include <android/gui/BnDisplayEventConnection.h>
 #include <gui/DisplayEventReceiver.h>
-#include <gui/IDisplayEventConnection.h>
 #include <private/gui/BitTube.h>
 #include <sys/types.h>
 #include <utils/Errors.h>
@@ -45,6 +45,9 @@
 class TokenManager;
 } // namespace frametimeline
 
+using gui::ParcelableVsyncEventData;
+using gui::VsyncEventData;
+
 // ---------------------------------------------------------------------------
 
 using ResyncCallback = std::function<void()>;
@@ -62,11 +65,16 @@
 
 class VSyncSource {
 public:
+    class VSyncData {
+    public:
+        nsecs_t expectedPresentationTime;
+        nsecs_t deadlineTimestamp;
+    };
+
     class Callback {
     public:
         virtual ~Callback() {}
-        virtual void onVSyncEvent(nsecs_t when, nsecs_t expectedVSyncTimestamp,
-                                  nsecs_t deadlineTimestamp) = 0;
+        virtual void onVSyncEvent(nsecs_t when, VSyncData vsyncData) = 0;
     };
 
     virtual ~VSyncSource() {}
@@ -76,11 +84,12 @@
     virtual void setCallback(Callback* callback) = 0;
     virtual void setDuration(std::chrono::nanoseconds workDuration,
                              std::chrono::nanoseconds readyDuration) = 0;
+    virtual VSyncData getLatestVSyncData() const = 0;
 
     virtual void dump(std::string& result) const = 0;
 };
 
-class EventThreadConnection : public BnDisplayEventConnection {
+class EventThreadConnection : public gui::BnDisplayEventConnection {
 public:
     EventThreadConnection(EventThread*, uid_t callingUid, ResyncCallback,
                           ISurfaceComposer::EventRegistrationFlags eventRegistration = {});
@@ -88,9 +97,10 @@
 
     virtual status_t postEvent(const DisplayEventReceiver::Event& event);
 
-    status_t stealReceiveChannel(gui::BitTube* outChannel) override;
-    status_t setVsyncRate(uint32_t rate) override;
-    void requestNextVsync() override; // asynchronous
+    binder::Status stealReceiveChannel(gui::BitTube* outChannel) override;
+    binder::Status setVsyncRate(int rate) override;
+    binder::Status requestNextVsync() override; // asynchronous
+    binder::Status getLatestVsyncEventData(ParcelableVsyncEventData* outVsyncEventData) override;
 
     // Called in response to requestNextVsync.
     const ResyncCallback resyncCallback;
@@ -140,6 +150,8 @@
     virtual void setVsyncRate(uint32_t rate, const sp<EventThreadConnection>& connection) = 0;
     // Requests the next vsync. If resetIdleTimer is set to true, it resets the idle timer.
     virtual void requestNextVsync(const sp<EventThreadConnection>& connection) = 0;
+    virtual VsyncEventData getLatestVsyncEventData(
+            const sp<EventThreadConnection>& connection) const = 0;
 
     // Retrieves the number of event connections tracked by this EventThread.
     virtual size_t getEventThreadConnectionCount() = 0;
@@ -164,6 +176,8 @@
     status_t registerDisplayEventConnection(const sp<EventThreadConnection>& connection) override;
     void setVsyncRate(uint32_t rate, const sp<EventThreadConnection>& connection) override;
     void requestNextVsync(const sp<EventThreadConnection>& connection) override;
+    VsyncEventData getLatestVsyncEventData(
+            const sp<EventThreadConnection>& connection) const override;
 
     // called before the screen is turned off from main thread
     void onScreenReleased() override;
@@ -201,8 +215,13 @@
             REQUIRES(mMutex);
 
     // Implements VSyncSource::Callback
-    void onVSyncEvent(nsecs_t timestamp, nsecs_t expectedVSyncTimestamp,
-                      nsecs_t deadlineTimestamp) override;
+    void onVSyncEvent(nsecs_t timestamp, VSyncSource::VSyncData vsyncData) override;
+
+    int64_t generateToken(nsecs_t timestamp, nsecs_t deadlineTimestamp,
+                          nsecs_t expectedPresentationTime) const;
+    void generateFrameTimeline(VsyncEventData& outVsyncEventData, nsecs_t frameInterval,
+                               nsecs_t timestamp, nsecs_t preferredExpectedPresentationTime,
+                               nsecs_t preferredDeadlineTimestamp) const;
 
     const std::unique_ptr<VSyncSource> mVSyncSource GUARDED_BY(mMutex);
     frametimeline::TokenManager* const mTokenManager;
diff --git a/services/surfaceflinger/Scheduler/FrameRateOverrideMappings.cpp b/services/surfaceflinger/Scheduler/FrameRateOverrideMappings.cpp
new file mode 100644
index 0000000..c233455
--- /dev/null
+++ b/services/surfaceflinger/Scheduler/FrameRateOverrideMappings.cpp
@@ -0,0 +1,143 @@
+/*
+ * 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 "FrameRateOverrideMappings.h"
+
+namespace android::scheduler {
+using FrameRateOverride = DisplayEventReceiver::Event::FrameRateOverride;
+
+std::optional<Fps> FrameRateOverrideMappings::getFrameRateOverrideForUid(
+        uid_t uid, bool supportsFrameRateOverrideByContent) const {
+    std::lock_guard lock(mFrameRateOverridesLock);
+
+    {
+        const auto iter = mFrameRateOverridesFromBackdoor.find(uid);
+        if (iter != mFrameRateOverridesFromBackdoor.end()) {
+            return iter->second;
+        }
+    }
+
+    {
+        const auto iter = mFrameRateOverridesFromGameManager.find(uid);
+        if (iter != mFrameRateOverridesFromGameManager.end()) {
+            return iter->second;
+        }
+    }
+
+    if (!supportsFrameRateOverrideByContent) {
+        return std::nullopt;
+    }
+
+    {
+        const auto iter = mFrameRateOverridesByContent.find(uid);
+        if (iter != mFrameRateOverridesByContent.end()) {
+            return iter->second;
+        }
+    }
+
+    return std::nullopt;
+}
+
+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()}));
+
+    for (const auto& [uid, frameRate] : mFrameRateOverridesFromBackdoor) {
+        overrides.emplace_back(FrameRateOverride{uid, frameRate.getValue()});
+    }
+    for (const auto& [uid, frameRate] : mFrameRateOverridesFromGameManager) {
+        if (std::find_if(overrides.begin(), overrides.end(),
+                         [uid = uid](auto i) { return i.uid == uid; }) == overrides.end()) {
+            overrides.emplace_back(FrameRateOverride{uid, frameRate.getValue()});
+        }
+    }
+
+    if (!supportsFrameRateOverrideByContent) {
+        return overrides;
+    }
+
+    for (const auto& [uid, frameRate] : mFrameRateOverridesByContent) {
+        if (std::find_if(overrides.begin(), overrides.end(),
+                         [uid = uid](auto i) { return i.uid == uid; }) == overrides.end()) {
+            overrides.emplace_back(FrameRateOverride{uid, frameRate.getValue()});
+        }
+    }
+
+    return overrides;
+}
+
+void FrameRateOverrideMappings::dump(std::string& result) const {
+    using base::StringAppendF;
+
+    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");
+
+    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");
+
+    StringAppendF(&result, "Frame Rate Overrides (setFrameRate): {");
+    for (const auto& [uid, frameRate] : mFrameRateOverridesByContent) {
+        StringAppendF(&result, "[uid: %d frameRate: %s], ", uid, to_string(frameRate).c_str());
+    }
+    StringAppendF(&result, "}\n");
+}
+
+bool FrameRateOverrideMappings::updateFrameRateOverridesByContent(
+        const UidToFrameRateOverride& frameRateOverrides) {
+    std::lock_guard lock(mFrameRateOverridesLock);
+    if (!std::equal(mFrameRateOverridesByContent.begin(), mFrameRateOverridesByContent.end(),
+                    frameRateOverrides.begin(), frameRateOverrides.end(),
+                    [](const auto& lhs, const auto& rhs) {
+                        return lhs.first == rhs.first && isApproxEqual(lhs.second, rhs.second);
+                    })) {
+        mFrameRateOverridesByContent = frameRateOverrides;
+        return true;
+    }
+    return false;
+}
+
+void FrameRateOverrideMappings::setGameModeRefreshRateForUid(FrameRateOverride frameRateOverride) {
+    std::lock_guard lock(mFrameRateOverridesLock);
+    if (frameRateOverride.frameRateHz != 0.f) {
+        mFrameRateOverridesFromGameManager[frameRateOverride.uid] =
+                Fps::fromValue(frameRateOverride.frameRateHz);
+    } else {
+        mFrameRateOverridesFromGameManager.erase(frameRateOverride.uid);
+    }
+}
+
+void FrameRateOverrideMappings::setPreferredRefreshRateForUid(FrameRateOverride frameRateOverride) {
+    std::lock_guard lock(mFrameRateOverridesLock);
+    if (frameRateOverride.frameRateHz != 0.f) {
+        mFrameRateOverridesFromBackdoor[frameRateOverride.uid] =
+                Fps::fromValue(frameRateOverride.frameRateHz);
+    } else {
+        mFrameRateOverridesFromBackdoor.erase(frameRateOverride.uid);
+    }
+}
+} // namespace android::scheduler
diff --git a/services/surfaceflinger/Scheduler/FrameRateOverrideMappings.h b/services/surfaceflinger/Scheduler/FrameRateOverrideMappings.h
new file mode 100644
index 0000000..4185a4c
--- /dev/null
+++ b/services/surfaceflinger/Scheduler/FrameRateOverrideMappings.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 <android-base/thread_annotations.h>
+#include <gui/DisplayEventReceiver.h>
+#include <scheduler/Fps.h>
+#include <sys/types.h>
+#include <map>
+#include <optional>
+
+namespace android::scheduler {
+class FrameRateOverrideMappings {
+    using FrameRateOverride = DisplayEventReceiver::Event::FrameRateOverride;
+    using UidToFrameRateOverride = std::map<uid_t, Fps>;
+
+public:
+    std::optional<Fps> getFrameRateOverrideForUid(uid_t uid,
+                                                  bool supportsFrameRateOverrideByContent) const
+            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)
+            EXCLUDES(mFrameRateOverridesLock);
+    void setPreferredRefreshRateForUid(FrameRateOverride frameRateOverride)
+            EXCLUDES(mFrameRateOverridesLock);
+
+private:
+    // 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;
+
+    // mappings between a UID and a preferred refresh rate that this app would
+    // run at.
+    UidToFrameRateOverride mFrameRateOverridesByContent GUARDED_BY(mFrameRateOverridesLock);
+    UidToFrameRateOverride mFrameRateOverridesFromBackdoor GUARDED_BY(mFrameRateOverridesLock);
+    UidToFrameRateOverride mFrameRateOverridesFromGameManager GUARDED_BY(mFrameRateOverridesLock);
+};
+} // namespace android::scheduler
diff --git a/services/surfaceflinger/Scheduler/InjectVSyncSource.h b/services/surfaceflinger/Scheduler/InjectVSyncSource.h
index 016b076..760a4ee 100644
--- a/services/surfaceflinger/Scheduler/InjectVSyncSource.h
+++ b/services/surfaceflinger/Scheduler/InjectVSyncSource.h
@@ -39,13 +39,14 @@
                            nsecs_t deadlineTimestamp) {
         std::lock_guard<std::mutex> lock(mCallbackMutex);
         if (mCallback) {
-            mCallback->onVSyncEvent(when, expectedVSyncTimestamp, deadlineTimestamp);
+            mCallback->onVSyncEvent(when, {expectedVSyncTimestamp, deadlineTimestamp});
         }
     }
 
     const char* getName() const override { return "inject"; }
     void setVSyncEnabled(bool) override {}
     void setDuration(std::chrono::nanoseconds, std::chrono::nanoseconds) override {}
+    VSyncData getLatestVSyncData() const override { return {}; }
     void dump(std::string&) const override {}
 
 private:
diff --git a/services/surfaceflinger/Scheduler/LayerHistory.cpp b/services/surfaceflinger/Scheduler/LayerHistory.cpp
index 84e3548..5f64efa 100644
--- a/services/surfaceflinger/Scheduler/LayerHistory.cpp
+++ b/services/surfaceflinger/Scheduler/LayerHistory.cpp
@@ -33,7 +33,6 @@
 
 #include "../Layer.h"
 #include "LayerInfo.h"
-#include "SchedulerUtils.h"
 
 namespace android::scheduler {
 
@@ -84,42 +83,37 @@
 
 void LayerHistory::registerLayer(Layer* layer, LayerVoteType type) {
     std::lock_guard lock(mLock);
-    for (const auto& info : mLayerInfos) {
-        LOG_ALWAYS_FATAL_IF(info.first == layer, "%s already registered", layer->getName().c_str());
-    }
+    LOG_ALWAYS_FATAL_IF(findLayer(layer->getSequence()).first != LayerStatus::NotFound,
+                        "%s already registered", layer->getName().c_str());
     auto info = std::make_unique<LayerInfo>(layer->getName(), layer->getOwnerUid(), type);
-    mLayerInfos.emplace_back(layer, std::move(info));
+
+    // The layer can be placed on either map, it is assumed that partitionLayers() will be called
+    // to correct them.
+    mInactiveLayerInfos.insert({layer->getSequence(), std::make_pair(layer, std::move(info))});
 }
 
 void LayerHistory::deregisterLayer(Layer* layer) {
     std::lock_guard lock(mLock);
-
-    const auto it = std::find_if(mLayerInfos.begin(), mLayerInfos.end(),
-                                 [layer](const auto& pair) { return pair.first == layer; });
-    LOG_ALWAYS_FATAL_IF(it == mLayerInfos.end(), "%s: unknown layer %p", __FUNCTION__, layer);
-
-    const size_t i = static_cast<size_t>(it - mLayerInfos.begin());
-    if (i < mActiveLayersEnd) {
-        mActiveLayersEnd--;
+    if (!mActiveLayerInfos.erase(layer->getSequence())) {
+        if (!mInactiveLayerInfos.erase(layer->getSequence())) {
+            LOG_ALWAYS_FATAL("%s: unknown layer %p", __FUNCTION__, layer);
+        }
     }
-    const size_t last = mLayerInfos.size() - 1;
-    std::swap(mLayerInfos[i], mLayerInfos[last]);
-    mLayerInfos.erase(mLayerInfos.begin() + static_cast<long>(last));
 }
 
 void LayerHistory::record(Layer* layer, nsecs_t presentTime, nsecs_t now,
                           LayerUpdateType updateType) {
     std::lock_guard lock(mLock);
+    auto id = layer->getSequence();
 
-    const auto it = std::find_if(mLayerInfos.begin(), mLayerInfos.end(),
-                                 [layer](const auto& pair) { return pair.first == layer; });
-    if (it == mLayerInfos.end()) {
+    auto [found, layerPair] = findLayer(id);
+    if (found == LayerStatus::NotFound) {
         // Offscreen layer
-        ALOGV("LayerHistory::record: %s not registered", layer->getName().c_str());
+        ALOGV("%s: %s not registered", __func__, layer->getName().c_str());
         return;
     }
 
-    const auto& info = it->second;
+    const auto& info = layerPair->second;
     const auto layerProps = LayerInfo::LayerProps{
             .visible = layer->isVisible(),
             .bounds = layer->getBounds(),
@@ -131,29 +125,30 @@
     info->setLastPresentTime(presentTime, now, updateType, mModeChangePending, layerProps);
 
     // Activate layer if inactive.
-    if (const auto end = activeLayers().end(); it >= end) {
-        std::iter_swap(it, end);
-        mActiveLayersEnd++;
+    if (found == LayerStatus::LayerInInactiveMap) {
+        mActiveLayerInfos.insert(
+                {id, std::make_pair(layerPair->first, std::move(layerPair->second))});
+        mInactiveLayerInfos.erase(id);
     }
 }
 
-LayerHistory::Summary LayerHistory::summarize(const RefreshRateConfigs& refreshRateConfigs,
-                                              nsecs_t now) {
-    LayerHistory::Summary summary;
+auto LayerHistory::summarize(const RefreshRateConfigs& configs, nsecs_t now) -> Summary {
+    Summary summary;
 
     std::lock_guard lock(mLock);
 
     partitionLayers(now);
 
-    for (const auto& [layer, info] : activeLayers()) {
+    for (const auto& [key, value] : mActiveLayerInfos) {
+        auto& info = value.second;
         const auto frameRateSelectionPriority = info->getFrameRateSelectionPriority();
         const auto layerFocused = Layer::isLayerFocusedBasedOnPriority(frameRateSelectionPriority);
         ALOGV("%s has priority: %d %s focused", info->getName().c_str(), frameRateSelectionPriority,
               layerFocused ? "" : "not");
 
-        const auto vote = info->getRefreshRateVote(refreshRateConfigs, now);
+        const auto vote = info->getRefreshRateVote(configs, now);
         // Skip NoVote layer as those don't have any requirements
-        if (vote.type == LayerHistory::LayerVoteType::NoVote) {
+        if (vote.type == LayerVoteType::NoVote) {
             continue;
         }
 
@@ -179,12 +174,29 @@
 void LayerHistory::partitionLayers(nsecs_t now) {
     const nsecs_t threshold = getActiveLayerThreshold(now);
 
-    // Collect expired and inactive layers after active layers.
-    size_t i = 0;
-    while (i < mActiveLayersEnd) {
-        auto& [layerUnsafe, info] = mLayerInfos[i];
+    // iterate over inactive map
+    LayerInfos::iterator it = mInactiveLayerInfos.begin();
+    while (it != mInactiveLayerInfos.end()) {
+        auto& [layerUnsafe, info] = it->second;
         if (isLayerActive(*info, threshold)) {
-            i++;
+            // move this to the active map
+
+            mActiveLayerInfos.insert({it->first, std::move(it->second)});
+            it = mInactiveLayerInfos.erase(it);
+        } else {
+            if (CC_UNLIKELY(mTraceEnabled)) {
+                trace(*info, LayerVoteType::NoVote, 0);
+            }
+            info->onLayerInactive(now);
+            it++;
+        }
+    }
+
+    // iterate over active map
+    it = mActiveLayerInfos.begin();
+    while (it != mActiveLayerInfos.end()) {
+        auto& [layerUnsafe, info] = it->second;
+        if (isLayerActive(*info, threshold)) {
             // Set layer vote if set
             const auto frameRate = info->getSetFrameRateVote();
             const auto voteType = [&]() {
@@ -206,30 +218,54 @@
             } else {
                 info->resetLayerVote();
             }
-            continue;
-        }
 
-        if (CC_UNLIKELY(mTraceEnabled)) {
-            trace(*info, LayerHistory::LayerVoteType::NoVote, 0);
+            it++;
+        } else {
+            if (CC_UNLIKELY(mTraceEnabled)) {
+                trace(*info, LayerVoteType::NoVote, 0);
+            }
+            info->onLayerInactive(now);
+            // move this to the inactive map
+            mInactiveLayerInfos.insert({it->first, std::move(it->second)});
+            it = mActiveLayerInfos.erase(it);
         }
-
-        info->onLayerInactive(now);
-        std::swap(mLayerInfos[i], mLayerInfos[--mActiveLayersEnd]);
     }
 }
 
 void LayerHistory::clear() {
     std::lock_guard lock(mLock);
-
-    for (const auto& [layer, info] : activeLayers()) {
-        info->clearHistory(systemTime());
+    for (const auto& [key, value] : mActiveLayerInfos) {
+        value.second->clearHistory(systemTime());
     }
 }
 
 std::string LayerHistory::dump() const {
     std::lock_guard lock(mLock);
-    return base::StringPrintf("LayerHistory{size=%zu, active=%zu}", mLayerInfos.size(),
-                              mActiveLayersEnd);
+    return base::StringPrintf("LayerHistory{size=%zu, active=%zu}",
+                              mActiveLayerInfos.size() + mInactiveLayerInfos.size(),
+                              mActiveLayerInfos.size());
+}
+
+float LayerHistory::getLayerFramerate(nsecs_t now, int32_t id) const {
+    std::lock_guard lock(mLock);
+    auto [found, layerPair] = findLayer(id);
+    if (found != LayerStatus::NotFound) {
+        return layerPair->second->getFps(now).getValue();
+    }
+    return 0.f;
+}
+
+auto LayerHistory::findLayer(int32_t id) -> std::pair<LayerStatus, LayerPair*> {
+    // the layer could be in either the active or inactive map, try both
+    auto it = mActiveLayerInfos.find(id);
+    if (it != mActiveLayerInfos.end()) {
+        return {LayerStatus::LayerInActiveMap, &(it->second)};
+    }
+    it = mInactiveLayerInfos.find(id);
+    if (it != mInactiveLayerInfos.end()) {
+        return {LayerStatus::LayerInInactiveMap, &(it->second)};
+    }
+    return {LayerStatus::NotFound, nullptr};
 }
 
 } // namespace android::scheduler
diff --git a/services/surfaceflinger/Scheduler/LayerHistory.h b/services/surfaceflinger/Scheduler/LayerHistory.h
index 92236f5..7b6096f 100644
--- a/services/surfaceflinger/Scheduler/LayerHistory.h
+++ b/services/surfaceflinger/Scheduler/LayerHistory.h
@@ -20,6 +20,7 @@
 #include <utils/RefBase.h>
 #include <utils/Timers.h>
 
+#include <map>
 #include <memory>
 #include <mutex>
 #include <string>
@@ -31,11 +32,9 @@
 namespace android {
 
 class Layer;
-class TestableScheduler;
 
 namespace scheduler {
 
-class LayerHistoryTest;
 class LayerInfo;
 
 class LayerHistory {
@@ -74,34 +73,45 @@
     void deregisterLayer(Layer*);
     std::string dump() const;
 
+    // return the frames per second of the layer with the given sequence id.
+    float getLayerFramerate(nsecs_t now, int32_t id) const;
+
 private:
-    friend LayerHistoryTest;
-    friend TestableScheduler;
+    friend class LayerHistoryTest;
+    friend class TestableScheduler;
 
     using LayerPair = std::pair<Layer*, std::unique_ptr<LayerInfo>>;
-    using LayerInfos = std::vector<LayerPair>;
+    // keyed by id as returned from Layer::getSequence()
+    using LayerInfos = std::unordered_map<int32_t, LayerPair>;
 
-    struct ActiveLayers {
-        LayerInfos& infos;
-        const size_t index;
+    // Iterates over layers maps moving all active layers to mActiveLayerInfos and all inactive
+    // layers to mInactiveLayerInfos.
+    // worst case time complexity is O(2 * inactive + active)
+    void partitionLayers(nsecs_t now) REQUIRES(mLock);
 
-        auto begin() { return infos.begin(); }
-        auto end() { return begin() + static_cast<long>(index); }
+    enum class LayerStatus {
+        NotFound,
+        LayerInActiveMap,
+        LayerInInactiveMap,
     };
 
-    ActiveLayers activeLayers() REQUIRES(mLock) { return {mLayerInfos, mActiveLayersEnd}; }
+    // looks up a layer by sequence id in both layerInfo maps.
+    // The first element indicates if and where the item was found
+    std::pair<LayerStatus, LayerPair*> findLayer(int32_t id) REQUIRES(mLock);
 
-    // Iterates over layers in a single pass, swapping pairs such that active layers precede
-    // inactive layers, and inactive layers precede expired layers. Removes expired layers by
-    // truncating after inactive layers.
-    void partitionLayers(nsecs_t now) REQUIRES(mLock);
+    std::pair<LayerStatus, const LayerPair*> findLayer(int32_t id) const REQUIRES(mLock) {
+        return const_cast<LayerHistory*>(this)->findLayer(id);
+    }
 
     mutable std::mutex mLock;
 
-    // Partitioned such that active layers precede inactive layers. For fast lookup, the few active
-    // layers are at the front, and weak pointers are stored in contiguous memory to hit the cache.
-    LayerInfos mLayerInfos GUARDED_BY(mLock);
-    size_t mActiveLayersEnd GUARDED_BY(mLock) = 0;
+    // Partitioned into two maps to facility two kinds of retrieval:
+    // 1. retrieval of a layer by id (attempt lookup in both maps)
+    // 2. retrieval of all active layers (iterate that map)
+    // The partitioning is allowed to become out of date but calling partitionLayers refreshes the
+    // validity of each map.
+    LayerInfos mActiveLayerInfos GUARDED_BY(mLock);
+    LayerInfos mInactiveLayerInfos GUARDED_BY(mLock);
 
     uint32_t mDisplayArea = 0;
 
diff --git a/services/surfaceflinger/Scheduler/LayerInfo.cpp b/services/surfaceflinger/Scheduler/LayerInfo.cpp
index 8a45b66..943615c 100644
--- a/services/surfaceflinger/Scheduler/LayerInfo.cpp
+++ b/services/surfaceflinger/Scheduler/LayerInfo.cpp
@@ -28,6 +28,7 @@
 
 #include <cutils/compiler.h>
 #include <cutils/trace.h>
+#include <ftl/enum.h>
 
 #undef LOG_TAG
 #define LOG_TAG "LayerInfo"
@@ -41,7 +42,7 @@
       : mName(name),
         mOwnerUid(ownerUid),
         mDefaultVote(defaultVote),
-        mLayerVote({defaultVote, Fps(0.0f)}),
+        mLayerVote({defaultVote, Fps()}),
         mRefreshRateHistory(name) {}
 
 void LayerInfo::setLastPresentTime(nsecs_t lastPresentTime, nsecs_t now, LayerUpdateType updateType,
@@ -74,12 +75,16 @@
 }
 
 bool LayerInfo::isFrequent(nsecs_t now) const {
+    using fps_approx_ops::operator>=;
     // If we know nothing about this layer we consider it as frequent as it might be the start
     // of an animation.
     if (mFrameTimes.size() < kFrequentLayerWindowSize) {
         return true;
     }
+    return getFps(now) >= kMinFpsForFrequentLayer;
+}
 
+Fps LayerInfo::getFps(nsecs_t now) const {
     // Find the first active frame
     auto it = mFrameTimes.begin();
     for (; it != mFrameTimes.end(); ++it) {
@@ -90,13 +95,12 @@
 
     const auto numFrames = std::distance(it, mFrameTimes.end());
     if (numFrames < kFrequentLayerWindowSize) {
-        return false;
+        return Fps();
     }
 
     // Layer is considered frequent if the average frame rate is higher than the threshold
     const auto totalTime = mFrameTimes.back().queueTime - it->queueTime;
-    return Fps::fromPeriodNsecs(totalTime / (numFrames - 1))
-            .greaterThanOrEqualWithMargin(kMinFpsForFrequentLayer);
+    return Fps::fromPeriodNsecs(totalTime / (numFrames - 1));
 }
 
 bool LayerInfo::isAnimating(nsecs_t now) const {
@@ -191,17 +195,17 @@
         return std::nullopt;
     }
 
-    const auto averageFrameTime = calculateAverageFrameTime();
-    if (averageFrameTime.has_value()) {
+    if (const auto averageFrameTime = calculateAverageFrameTime()) {
         const auto refreshRate = Fps::fromPeriodNsecs(*averageFrameTime);
         const bool refreshRateConsistent = mRefreshRateHistory.add(refreshRate, now);
         if (refreshRateConsistent) {
             const auto knownRefreshRate = refreshRateConfigs.findClosestKnownFrameRate(refreshRate);
-            // To avoid oscillation, use the last calculated refresh rate if it is
-            // close enough
+            using fps_approx_ops::operator!=;
+
+            // To avoid oscillation, use the last calculated refresh rate if it is close enough.
             if (std::abs(mLastRefreshRate.calculated.getValue() - refreshRate.getValue()) >
                         MARGIN &&
-                !mLastRefreshRate.reported.equalsWithMargin(knownRefreshRate)) {
+                mLastRefreshRate.reported != knownRefreshRate) {
                 mLastRefreshRate.calculated = refreshRate;
                 mLastRefreshRate.reported = knownRefreshRate;
             }
@@ -228,7 +232,7 @@
     if (isAnimating(now)) {
         ALOGV("%s is animating", mName.c_str());
         mLastRefreshRate.animatingOrInfrequent = true;
-        return {LayerHistory::LayerVoteType::Max, Fps(0.0f)};
+        return {LayerHistory::LayerVoteType::Max, Fps()};
     }
 
     if (!isFrequent(now)) {
@@ -236,7 +240,7 @@
         mLastRefreshRate.animatingOrInfrequent = true;
         // Infrequent layers vote for mininal refresh rate for
         // battery saving purposes and also to prevent b/135718869.
-        return {LayerHistory::LayerVoteType::Min, Fps(0.0f)};
+        return {LayerHistory::LayerVoteType::Min, Fps()};
     }
 
     // If the layer was previously tagged as animating or infrequent, we clear
@@ -253,13 +257,13 @@
     }
 
     ALOGV("%s Max (can't resolve refresh rate)", mName.c_str());
-    return {LayerHistory::LayerVoteType::Max, Fps(0.0f)};
+    return {LayerHistory::LayerVoteType::Max, Fps()};
 }
 
-const char* LayerInfo::getTraceTag(android::scheduler::LayerHistory::LayerVoteType type) const {
+const char* LayerInfo::getTraceTag(LayerHistory::LayerVoteType type) const {
     if (mTraceTags.count(type) == 0) {
-        const auto tag = "LFPS " + mName + " " + RefreshRateConfigs::layerVoteTypeString(type);
-        mTraceTags.emplace(type, tag);
+        auto tag = "LFPS " + mName + " " + ftl::enum_string(type);
+        mTraceTags.emplace(type, std::move(tag));
     }
 
     return mTraceTags.at(type).c_str();
@@ -300,9 +304,13 @@
 bool LayerInfo::RefreshRateHistory::isConsistent() const {
     if (mRefreshRates.empty()) return true;
 
-    const auto max = std::max_element(mRefreshRates.begin(), mRefreshRates.end());
-    const auto min = std::min_element(mRefreshRates.begin(), mRefreshRates.end());
-    const auto consistent =
+    const auto [min, max] =
+            std::minmax_element(mRefreshRates.begin(), mRefreshRates.end(),
+                                [](const auto& lhs, const auto& rhs) {
+                                    return isStrictlyLess(lhs.refreshRate, rhs.refreshRate);
+                                });
+
+    const bool consistent =
             max->refreshRate.getValue() - min->refreshRate.getValue() < MARGIN_CONSISTENT_FPS;
 
     if (CC_UNLIKELY(sTraceEnabled)) {
@@ -321,4 +329,4 @@
 } // namespace android::scheduler
 
 // TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic pop // ignored "-Wextra"
\ No newline at end of file
+#pragma clang diagnostic pop // ignored "-Wextra"
diff --git a/services/surfaceflinger/Scheduler/LayerInfo.h b/services/surfaceflinger/Scheduler/LayerInfo.h
index ce9783c..8a3b0b9 100644
--- a/services/surfaceflinger/Scheduler/LayerInfo.h
+++ b/services/surfaceflinger/Scheduler/LayerInfo.h
@@ -16,16 +16,19 @@
 
 #pragma once
 
+#include <chrono>
+#include <deque>
+#include <optional>
+#include <string>
+#include <unordered_map>
+
 #include <ui/Transform.h>
 #include <utils/Timers.h>
 
-#include <chrono>
-#include <deque>
+#include <scheduler/Seamlessness.h>
 
 #include "LayerHistory.h"
 #include "RefreshRateConfigs.h"
-#include "Scheduler/Seamlessness.h"
-#include "SchedulerUtils.h"
 
 namespace android {
 
@@ -51,7 +54,7 @@
     // is within a threshold. If a layer is infrequent, its average refresh rate is disregarded in
     // favor of a low refresh rate.
     static constexpr size_t kFrequentLayerWindowSize = 3;
-    static constexpr Fps kMinFpsForFrequentLayer{10.0f};
+    static constexpr Fps kMinFpsForFrequentLayer = 10_Hz;
     static constexpr auto kMaxPeriodForFrequentLayerNs =
             std::chrono::nanoseconds(kMinFpsForFrequentLayer.getPeriodNsecs()) + 1ms;
 
@@ -62,7 +65,7 @@
     // Holds information about the layer vote
     struct LayerVote {
         LayerHistory::LayerVoteType type = LayerHistory::LayerVoteType::Heuristic;
-        Fps fps{0.0f};
+        Fps fps;
         Seamlessness seamlessness = Seamlessness::Default;
     };
 
@@ -78,6 +81,8 @@
 
         NoVote, // Layer doesn't have any requirements for the refresh rate and
                 // should not be considered when the display refresh rate is determined.
+
+        ftl_last = NoVote
     };
 
     // Encapsulates the frame rate and compatibility of the layer. This information will be used
@@ -86,19 +91,17 @@
         using Seamlessness = scheduler::Seamlessness;
 
         Fps rate;
-        FrameRateCompatibility type;
-        Seamlessness seamlessness;
+        FrameRateCompatibility type = FrameRateCompatibility::Default;
+        Seamlessness seamlessness = Seamlessness::Default;
 
-        FrameRate()
-              : rate(0),
-                type(FrameRateCompatibility::Default),
-                seamlessness(Seamlessness::Default) {}
+        FrameRate() = default;
+
         FrameRate(Fps rate, FrameRateCompatibility type,
                   Seamlessness seamlessness = Seamlessness::OnlySeamless)
               : rate(rate), type(type), seamlessness(getSeamlessness(rate, seamlessness)) {}
 
         bool operator==(const FrameRate& other) const {
-            return rate.equalsWithMargin(other.rate) && type == other.type &&
+            return isApproxEqual(rate, other.rate) && type == other.type &&
                     seamlessness == other.seamlessness;
         }
 
@@ -151,7 +154,7 @@
     void setDefaultLayerVote(LayerHistory::LayerVoteType type) { mDefaultVote = type; }
 
     // Resets the layer vote to its default.
-    void resetLayerVote() { mLayerVote = {mDefaultVote, Fps(0.0f), Seamlessness::Default}; }
+    void resetLayerVote() { mLayerVote = {mDefaultVote, Fps(), Seamlessness::Default}; }
 
     std::string getName() const { return mName; }
 
@@ -174,6 +177,9 @@
     // Returns a C string for tracing a vote
     const char* getTraceTag(LayerHistory::LayerVoteType type) const;
 
+    // Return the framerate of this layer.
+    Fps getFps(nsecs_t now) const;
+
     void onLayerInactive(nsecs_t now) {
         // Mark mFrameTimeValidSince to now to ignore all previous frame times.
         // We are not deleting the old frame to keep track of whether we should treat the first
@@ -201,9 +207,9 @@
     // Holds information about the calculated and reported refresh rate
     struct RefreshRateHeuristicData {
         // Rate calculated on the layer
-        Fps calculated{0.0f};
+        Fps calculated;
         // Last reported rate for LayerInfo::getRefreshRate()
-        Fps reported{0.0f};
+        Fps reported;
         // Whether the last reported rate for LayerInfo::getRefreshRate()
         // was due to animation or infrequent updates
         bool animatingOrInfrequent = false;
@@ -229,14 +235,8 @@
 
         // Holds the refresh rate when it was calculated
         struct RefreshRateData {
-            Fps refreshRate{0.0f};
+            Fps refreshRate;
             nsecs_t timestamp = 0;
-
-            bool operator<(const RefreshRateData& other) const {
-                // We don't need comparison with margins since we are using
-                // this to find the min and max refresh rates.
-                return refreshRate.getValue() < other.refreshRate.getValue();
-            }
         };
 
         // Holds tracing strings
@@ -268,7 +268,7 @@
 
     // Used for sanitizing the heuristic data. If two frames are less than
     // this period apart from each other they'll be considered as duplicates.
-    static constexpr nsecs_t kMinPeriodBetweenFrames = Fps(240.f).getPeriodNsecs();
+    static constexpr nsecs_t kMinPeriodBetweenFrames = (240_Hz).getPeriodNsecs();
     // Used for sanitizing the heuristic data. If two frames are more than
     // this period apart from each other, the interval between them won't be
     // taken into account when calculating average frame rate.
diff --git a/services/surfaceflinger/Scheduler/MessageQueue.cpp b/services/surfaceflinger/Scheduler/MessageQueue.cpp
index 4d51125..f2af85e 100644
--- a/services/surfaceflinger/Scheduler/MessageQueue.cpp
+++ b/services/surfaceflinger/Scheduler/MessageQueue.cpp
@@ -27,49 +27,44 @@
 #include "EventThread.h"
 #include "FrameTimeline.h"
 #include "MessageQueue.h"
-#include "SurfaceFlinger.h"
 
 namespace android::impl {
 
-void MessageQueue::Handler::dispatchRefresh() {
-    if ((mEventMask.fetch_or(eventMaskRefresh) & eventMaskRefresh) == 0) {
-        mQueue.mLooper->sendMessage(this, Message(MessageQueue::REFRESH));
-    }
-}
-
-void MessageQueue::Handler::dispatchInvalidate(int64_t vsyncId, nsecs_t expectedVSyncTimestamp) {
-    if ((mEventMask.fetch_or(eventMaskInvalidate) & eventMaskInvalidate) == 0) {
+void MessageQueue::Handler::dispatchFrame(int64_t vsyncId, nsecs_t expectedVsyncTime) {
+    if (!mFramePending.exchange(true)) {
         mVsyncId = vsyncId;
-        mExpectedVSyncTime = expectedVSyncTimestamp;
-        mQueue.mLooper->sendMessage(this, Message(MessageQueue::INVALIDATE));
+        mExpectedVsyncTime = expectedVsyncTime;
+        mQueue.mLooper->sendMessage(this, Message());
     }
 }
 
-bool MessageQueue::Handler::invalidatePending() {
-    constexpr auto pendingMask = eventMaskInvalidate | eventMaskRefresh;
-    return (mEventMask.load() & pendingMask) != 0;
+bool MessageQueue::Handler::isFramePending() const {
+    return mFramePending.load();
 }
 
-void MessageQueue::Handler::handleMessage(const Message& message) {
-    switch (message.what) {
-        case INVALIDATE:
-            mEventMask.fetch_and(~eventMaskInvalidate);
-            mQueue.mFlinger->onMessageReceived(message.what, mVsyncId, mExpectedVSyncTime);
-            break;
-        case REFRESH:
-            mEventMask.fetch_and(~eventMaskRefresh);
-            mQueue.mFlinger->onMessageReceived(message.what, mVsyncId, mExpectedVSyncTime);
-            break;
+void MessageQueue::Handler::handleMessage(const Message&) {
+    mFramePending.store(false);
+
+    const nsecs_t frameTime = systemTime();
+    auto& compositor = mQueue.mCompositor;
+
+    if (!compositor.commit(frameTime, mVsyncId, mExpectedVsyncTime)) {
+        return;
     }
+
+    compositor.composite(frameTime, mVsyncId);
+    compositor.sample();
 }
 
-// ---------------------------------------------------------------------------
+MessageQueue::MessageQueue(ICompositor& compositor)
+      : MessageQueue(compositor, sp<Handler>::make(*this)) {}
 
-void MessageQueue::init(const sp<SurfaceFlinger>& flinger) {
-    mFlinger = flinger;
-    mLooper = new Looper(true);
-    mHandler = new Handler(*this);
-}
+constexpr bool kAllowNonCallbacks = true;
+
+MessageQueue::MessageQueue(ICompositor& compositor, sp<Handler> handler)
+      : mCompositor(compositor),
+        mLooper(sp<Looper>::make(kAllowNonCallbacks)),
+        mHandler(std::move(handler)) {}
 
 // TODO(b/169865816): refactor VSyncInjections to use MessageQueue directly
 // and remove the EventThread from MessageQueue
@@ -110,11 +105,13 @@
     {
         std::lock_guard lock(mVsync.mutex);
         mVsync.lastCallbackTime = std::chrono::nanoseconds(vsyncTime);
-        mVsync.scheduled = false;
+        mVsync.scheduledFrameTime.reset();
     }
-    mHandler->dispatchInvalidate(mVsync.tokenManager->generateTokenForPredictions(
-                                         {targetWakeupTime, readyTime, vsyncTime}),
-                                 vsyncTime);
+
+    const auto vsyncId = mVsync.tokenManager->generateTokenForPredictions(
+            {targetWakeupTime, readyTime, vsyncTime});
+
+    mHandler->dispatchFrame(vsyncId, vsyncTime);
 }
 
 void MessageQueue::initVsync(scheduler::VSyncDispatch& dispatch,
@@ -135,8 +132,8 @@
     ATRACE_CALL();
     std::lock_guard lock(mVsync.mutex);
     mVsync.workDuration = workDuration;
-    if (mVsync.scheduled) {
-        mVsync.expectedWakeupTime = mVsync.registration->schedule(
+    if (mVsync.scheduledFrameTime) {
+        mVsync.scheduledFrameTime = mVsync.registration->schedule(
                 {mVsync.workDuration.get().count(),
                  /*readyDuration=*/0, mVsync.lastCallbackTime.count()});
     }
@@ -168,7 +165,7 @@
     mLooper->sendMessage(handler, Message());
 }
 
-void MessageQueue::invalidate() {
+void MessageQueue::scheduleFrame() {
     ATRACE_CALL();
 
     {
@@ -181,41 +178,35 @@
     }
 
     std::lock_guard lock(mVsync.mutex);
-    mVsync.scheduled = true;
-    mVsync.expectedWakeupTime =
+    mVsync.scheduledFrameTime =
             mVsync.registration->schedule({.workDuration = mVsync.workDuration.get().count(),
                                            .readyDuration = 0,
                                            .earliestVsync = mVsync.lastCallbackTime.count()});
 }
 
-void MessageQueue::refresh() {
-    mHandler->dispatchRefresh();
-}
-
 void MessageQueue::injectorCallback() {
     ssize_t n;
     DisplayEventReceiver::Event buffer[8];
     while ((n = DisplayEventReceiver::getEvents(&mInjector.tube, buffer, 8)) > 0) {
         for (int i = 0; i < n; i++) {
             if (buffer[i].header.type == DisplayEventReceiver::DISPLAY_EVENT_VSYNC) {
-                mHandler->dispatchInvalidate(buffer[i].vsync.vsyncId,
-                                             buffer[i].vsync.expectedVSyncTimestamp);
+                auto& vsync = buffer[i].vsync;
+                mHandler->dispatchFrame(vsync.vsyncData.preferredVsyncId(),
+                                        vsync.vsyncData.preferredExpectedPresentationTime());
                 break;
             }
         }
     }
 }
 
-std::optional<std::chrono::steady_clock::time_point> MessageQueue::nextExpectedInvalidate() {
-    if (mHandler->invalidatePending()) {
-        return std::chrono::steady_clock::now();
+auto MessageQueue::getScheduledFrameTime() const -> std::optional<Clock::time_point> {
+    if (mHandler->isFramePending()) {
+        return Clock::now();
     }
 
     std::lock_guard lock(mVsync.mutex);
-    if (mVsync.scheduled) {
-        LOG_ALWAYS_FATAL_IF(!mVsync.expectedWakeupTime.has_value(), "callback was never scheduled");
-        const auto expectedWakeupTime = std::chrono::nanoseconds(*mVsync.expectedWakeupTime);
-        return std::optional<std::chrono::steady_clock::time_point>(expectedWakeupTime);
+    if (const auto time = mVsync.scheduledFrameTime) {
+        return Clock::time_point(std::chrono::nanoseconds(*time));
     }
 
     return std::nullopt;
diff --git a/services/surfaceflinger/Scheduler/MessageQueue.h b/services/surfaceflinger/Scheduler/MessageQueue.h
index 58ce9b9..4082e26 100644
--- a/services/surfaceflinger/Scheduler/MessageQueue.h
+++ b/services/surfaceflinger/Scheduler/MessageQueue.h
@@ -22,7 +22,7 @@
 #include <utility>
 
 #include <android-base/thread_annotations.h>
-#include <gui/IDisplayEventConnection.h>
+#include <android/gui/IDisplayEventConnection.h>
 #include <private/gui/BitTube.h>
 #include <utils/Looper.h>
 #include <utils/Timers.h>
@@ -33,7 +33,14 @@
 
 namespace android {
 
-class SurfaceFlinger;
+struct ICompositor {
+    virtual bool commit(nsecs_t frameTime, int64_t vsyncId, nsecs_t expectedVsyncTime) = 0;
+    virtual void composite(nsecs_t frameTime, int64_t vsyncId) = 0;
+    virtual void sample() = 0;
+
+protected:
+    ~ICompositor() = default;
+};
 
 template <typename F>
 class Task : public MessageHandler {
@@ -56,65 +63,60 @@
 
 class MessageQueue {
 public:
-    enum {
-        INVALIDATE = 0,
-        REFRESH = 1,
-    };
-
     virtual ~MessageQueue() = default;
 
-    virtual void init(const sp<SurfaceFlinger>& flinger) = 0;
     virtual void initVsync(scheduler::VSyncDispatch&, frametimeline::TokenManager&,
                            std::chrono::nanoseconds workDuration) = 0;
     virtual void setDuration(std::chrono::nanoseconds workDuration) = 0;
     virtual void setInjector(sp<EventThreadConnection>) = 0;
     virtual void waitMessage() = 0;
     virtual void postMessage(sp<MessageHandler>&&) = 0;
-    virtual void invalidate() = 0;
-    virtual void refresh() = 0;
-    virtual std::optional<std::chrono::steady_clock::time_point> nextExpectedInvalidate() = 0;
-};
+    virtual void scheduleFrame() = 0;
 
-// ---------------------------------------------------------------------------
+    using Clock = std::chrono::steady_clock;
+    virtual std::optional<Clock::time_point> getScheduledFrameTime() const = 0;
+};
 
 namespace impl {
 
 class MessageQueue : public android::MessageQueue {
 protected:
     class Handler : public MessageHandler {
-        enum : uint32_t {
-            eventMaskInvalidate = 0x1,
-            eventMaskRefresh = 0x2,
-            eventMaskTransaction = 0x4
-        };
         MessageQueue& mQueue;
-        std::atomic<uint32_t> mEventMask;
-        std::atomic<int64_t> mVsyncId;
-        std::atomic<nsecs_t> mExpectedVSyncTime;
+        std::atomic_bool mFramePending = false;
+        std::atomic<int64_t> mVsyncId = 0;
+        std::atomic<nsecs_t> mExpectedVsyncTime = 0;
 
     public:
-        explicit Handler(MessageQueue& queue) : mQueue(queue), mEventMask(0) {}
+        explicit Handler(MessageQueue& queue) : mQueue(queue) {}
         void handleMessage(const Message& message) override;
-        virtual void dispatchRefresh();
-        virtual void dispatchInvalidate(int64_t vsyncId, nsecs_t expectedVSyncTimestamp);
-        virtual bool invalidatePending();
+
+        bool isFramePending() const;
+
+        virtual void dispatchFrame(int64_t vsyncId, nsecs_t expectedVsyncTime);
     };
 
     friend class Handler;
 
-    sp<SurfaceFlinger> mFlinger;
-    sp<Looper> mLooper;
+    // For tests.
+    MessageQueue(ICompositor&, sp<Handler>);
+
+    void vsyncCallback(nsecs_t vsyncTime, nsecs_t targetWakeupTime, nsecs_t readyTime);
+
+private:
+    ICompositor& mCompositor;
+    const sp<Looper> mLooper;
+    const sp<Handler> mHandler;
 
     struct Vsync {
         frametimeline::TokenManager* tokenManager = nullptr;
         std::unique_ptr<scheduler::VSyncCallbackRegistration> registration;
 
-        std::mutex mutex;
+        mutable std::mutex mutex;
         TracedOrdinal<std::chrono::nanoseconds> workDuration
                 GUARDED_BY(mutex) = {"VsyncWorkDuration-sf", std::chrono::nanoseconds(0)};
         std::chrono::nanoseconds lastCallbackTime GUARDED_BY(mutex) = std::chrono::nanoseconds{0};
-        bool scheduled GUARDED_BY(mutex) = false;
-        std::optional<nsecs_t> expectedWakeupTime GUARDED_BY(mutex);
+        std::optional<nsecs_t> scheduledFrameTime GUARDED_BY(mutex);
         TracedOrdinal<int> value = {"VSYNC-sf", 0};
     };
 
@@ -127,14 +129,11 @@
     Vsync mVsync;
     Injector mInjector;
 
-    sp<Handler> mHandler;
-
-    void vsyncCallback(nsecs_t vsyncTime, nsecs_t targetWakeupTime, nsecs_t readyTime);
     void injectorCallback();
 
 public:
-    ~MessageQueue() override = default;
-    void init(const sp<SurfaceFlinger>& flinger) override;
+    explicit MessageQueue(ICompositor&);
+
     void initVsync(scheduler::VSyncDispatch&, frametimeline::TokenManager&,
                    std::chrono::nanoseconds workDuration) override;
     void setDuration(std::chrono::nanoseconds workDuration) override;
@@ -143,13 +142,9 @@
     void waitMessage() override;
     void postMessage(sp<MessageHandler>&&) override;
 
-    // sends INVALIDATE message at next VSYNC
-    void invalidate() override;
+    void scheduleFrame() override;
 
-    // sends REFRESH message at next VSYNC
-    void refresh() override;
-
-    std::optional<std::chrono::steady_clock::time_point> nextExpectedInvalidate() override;
+    std::optional<Clock::time_point> getScheduledFrameTime() const override;
 };
 
 } // namespace impl
diff --git a/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp b/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp
index a56827e..3226f22 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp
+++ b/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp
@@ -21,79 +21,122 @@
 #pragma clang diagnostic push
 #pragma clang diagnostic ignored "-Wextra"
 
-#include "RefreshRateConfigs.h"
-#include <android-base/properties.h>
-#include <android-base/stringprintf.h>
-#include <utils/Trace.h>
 #include <chrono>
 #include <cmath>
+
+#include <android-base/properties.h>
+#include <android-base/stringprintf.h>
+#include <ftl/enum.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 score;
+};
+
+template <typename Iterator>
+const DisplayModePtr& getMaxScoreRefreshRate(Iterator begin, Iterator end) {
+    const auto it =
+            std::max_element(begin, end, [](RefreshRateScore max, RefreshRateScore current) {
+                const auto& [modeIt, score] = current;
+
+                std::string name = to_string(modeIt->second->getFps());
+                ALOGV("%s scores %.2f", name.c_str(), score);
+
+                ATRACE_INT(name.c_str(), static_cast<int>(std::round(score * 100)));
+
+                constexpr float kEpsilon = 0.0001f;
+                return score > max.score * (1 + kEpsilon);
+            });
+
+    return it->modeIt->second;
+}
+
+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(),
-                              RefreshRateConfigs::layerVoteTypeString(layer.vote).c_str(), weight,
-                              toString(layer.seamlessness).c_str(),
+    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 = {Fps(24.0f), Fps(30.0f), Fps(45.0f), Fps(60.0f), Fps(72.0f)};
+    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 to the set
-    for (const auto& mode : modes) {
-        const auto refreshRate = Fps::fromPeriodNsecs(mode->getVsyncPeriod());
-        knownFrameRates.emplace_back(refreshRate);
+    // 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(), Fps::comparesLess);
+    // Sort and remove duplicates.
+    std::sort(knownFrameRates.begin(), knownFrameRates.end(), isStrictlyLess);
     knownFrameRates.erase(std::unique(knownFrameRates.begin(), knownFrameRates.end(),
-                                      Fps::EqualsWithMargin()),
+                                      isApproxEqual),
                           knownFrameRates.end());
     return knownFrameRates;
 }
 
-} // namespace
+// 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());
 
-using AllRefreshRatesMapType = RefreshRateConfigs::AllRefreshRatesMapType;
-using RefreshRate = RefreshRateConfigs::RefreshRate;
+    for (auto it = modes.begin(); it != modes.end(); ++it) {
+        const auto& [id, mode] = *it;
 
-std::string RefreshRate::toString() const {
-    return base::StringPrintf("{id=%d, hwcId=%d, fps=%.2f, width=%d, height=%d group=%d}",
-                              getModeId().value(), mode->getHwcId(), getFps().getValue(),
-                              mode->getWidth(), mode->getHeight(), getModeGroup());
-}
-
-std::string RefreshRateConfigs::layerVoteTypeString(LayerVoteType vote) {
-    switch (vote) {
-        case LayerVoteType::NoVote:
-            return "NoVote";
-        case LayerVoteType::Min:
-            return "Min";
-        case LayerVoteType::Max:
-            return "Max";
-        case LayerVoteType::Heuristic:
-            return "Heuristic";
-        case LayerVoteType::ExplicitDefault:
-            return "ExplicitDefault";
-        case LayerVoteType::ExplicitExactOrMultiple:
-            return "ExplicitExactOrMultiple";
-        case LayerVoteType::ExplicitExact:
-            return "ExplicitExact";
+        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;
+}
+
+} // namespace
+
 std::string RefreshRateConfigs::Policy::toString() const {
     return base::StringPrintf("default mode ID: %d, allowGroupSwitching = %d"
                               ", primary range: %s, app request range: %s",
                               defaultMode.value(), allowGroupSwitching,
-                              primaryRange.toString().c_str(), appRequestRange.toString().c_str());
+                              to_string(primaryRange).c_str(), to_string(appRequestRange).c_str());
 }
 
 std::pair<nsecs_t, nsecs_t> RefreshRateConfigs::getDisplayFrames(nsecs_t layerPeriod,
@@ -108,16 +151,15 @@
     return {quotient, remainder};
 }
 
-bool RefreshRateConfigs::isVoteAllowed(const LayerRequirement& layer,
-                                       const RefreshRate& refreshRate) const {
+bool RefreshRateConfigs::isVoteAllowed(const LayerRequirement& layer, Fps refreshRate) const {
+    using namespace fps_approx_ops;
+
     switch (layer.vote) {
         case LayerVoteType::ExplicitExactOrMultiple:
         case LayerVoteType::Heuristic:
             if (mConfig.frameRateMultipleThreshold != 0 &&
-                refreshRate.getFps().greaterThanOrEqualWithMargin(
-                        Fps(mConfig.frameRateMultipleThreshold)) &&
-                layer.desiredRefreshRate.lessThanWithMargin(
-                        Fps(mConfig.frameRateMultipleThreshold / 2))) {
+                refreshRate >= Fps::fromValue(mConfig.frameRateMultipleThreshold) &&
+                layer.desiredRefreshRate < Fps::fromValue(mConfig.frameRateMultipleThreshold / 2)) {
                 // Don't vote high refresh rates past the threshold for layers with a low desired
                 // refresh rate. For example, desired 24 fps with 120 Hz threshold means no vote for
                 // 120 Hz, but desired 60 fps should have a vote.
@@ -134,11 +176,11 @@
     return true;
 }
 
-float RefreshRateConfigs::calculateNonExactMatchingLayerScoreLocked(
-        const LayerRequirement& layer, const RefreshRate& refreshRate) const {
+float RefreshRateConfigs::calculateNonExactMatchingLayerScoreLocked(const LayerRequirement& layer,
+                                                                    Fps refreshRate) const {
     constexpr float kScoreForFractionalPairs = .8f;
 
-    const auto displayPeriod = refreshRate.getVsyncPeriod();
+    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
@@ -161,7 +203,7 @@
 
     if (layer.vote == LayerVoteType::ExplicitExactOrMultiple ||
         layer.vote == LayerVoteType::Heuristic) {
-        if (isFractionalPairOrMultiple(refreshRate.getFps(), layer.desiredRefreshRate)) {
+        if (isFractionalPairOrMultiple(refreshRate, layer.desiredRefreshRate)) {
             return kScoreForFractionalPairs;
         }
 
@@ -196,8 +238,7 @@
     return 0;
 }
 
-float RefreshRateConfigs::calculateLayerScoreLocked(const LayerRequirement& layer,
-                                                    const RefreshRate& refreshRate,
+float RefreshRateConfigs::calculateLayerScoreLocked(const LayerRequirement& layer, Fps refreshRate,
                                                     bool isSeamlessSwitch) const {
     if (!isVoteAllowed(layer, refreshRate)) {
         return 0;
@@ -209,31 +250,31 @@
 
     // If the layer wants Max, give higher score to the higher refresh rate
     if (layer.vote == LayerVoteType::Max) {
-        const auto ratio = refreshRate.getFps().getValue() /
-                mAppRequestRefreshRates.back()->getFps().getValue();
+        const auto& maxRefreshRate = mAppRequestRefreshRates.back()->second;
+        const auto ratio = refreshRate.getValue() / maxRefreshRate->getFps().getValue();
         // use ratio^2 to get a lower score the more we get further from peak
         return ratio * ratio;
     }
 
     if (layer.vote == LayerVoteType::ExplicitExact) {
-        const int divider = getFrameRateDivider(refreshRate.getFps(), layer.desiredRefreshRate);
-        if (mSupportsFrameRateOverride) {
+        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 divider > 0;
+            return divisor > 0;
         }
 
-        return divider == 1;
+        return divisor == 1;
     }
 
-    // If the layer frame rate is a divider of the refresh rate it should score
+    // If the layer frame rate is a divisor of the refresh rate it should score
     // the highest score.
-    if (getFrameRateDivider(refreshRate.getFps(), layer.desiredRefreshRate) > 0) {
+    if (getFrameRateDivisor(refreshRate, layer.desiredRefreshRate) > 0) {
         return 1.0f * seamlessness;
     }
 
-    // The layer frame rate is not a divider of the refresh rate,
+    // 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;
@@ -241,68 +282,26 @@
             kNonExactMatchingPenalty;
 }
 
-struct RefreshRateScore {
-    const RefreshRate* refreshRate;
-    float score;
-};
-
-RefreshRate RefreshRateConfigs::getBestRefreshRate(const std::vector<LayerRequirement>& layers,
-                                                   const GlobalSignals& globalSignals,
-                                                   GlobalSignals* outSignalsConsidered) const {
+auto RefreshRateConfigs::getBestRefreshRate(const std::vector<LayerRequirement>& layers,
+                                            GlobalSignals signals) const
+        -> std::pair<DisplayModePtr, GlobalSignals> {
     std::lock_guard lock(mLock);
 
-    if (auto cached = getCachedBestRefreshRate(layers, globalSignals, outSignalsConsidered)) {
-        return *cached;
+    if (mGetBestRefreshRateCache &&
+        mGetBestRefreshRateCache->arguments == std::make_pair(layers, signals)) {
+        return mGetBestRefreshRateCache->result;
     }
 
-    GlobalSignals signalsConsidered;
-    RefreshRate result = getBestRefreshRateLocked(layers, globalSignals, &signalsConsidered);
-    lastBestRefreshRateInvocation.emplace(
-            GetBestRefreshRateInvocation{.layerRequirements = layers,
-                                         .globalSignals = globalSignals,
-                                         .outSignalsConsidered = signalsConsidered,
-                                         .resultingBestRefreshRate = result});
-    if (outSignalsConsidered) {
-        *outSignalsConsidered = signalsConsidered;
-    }
+    const auto result = getBestRefreshRateLocked(layers, signals);
+    mGetBestRefreshRateCache = GetBestRefreshRateCache{{layers, signals}, result};
     return result;
 }
 
-std::optional<RefreshRate> RefreshRateConfigs::getCachedBestRefreshRate(
-        const std::vector<LayerRequirement>& layers, const GlobalSignals& globalSignals,
-        GlobalSignals* outSignalsConsidered) const {
-    const bool sameAsLastCall = lastBestRefreshRateInvocation &&
-            lastBestRefreshRateInvocation->layerRequirements == layers &&
-            lastBestRefreshRateInvocation->globalSignals == globalSignals;
-
-    if (sameAsLastCall) {
-        if (outSignalsConsidered) {
-            *outSignalsConsidered = lastBestRefreshRateInvocation->outSignalsConsidered;
-        }
-        return lastBestRefreshRateInvocation->resultingBestRefreshRate;
-    }
-
-    return {};
-}
-
-RefreshRate RefreshRateConfigs::getBestRefreshRateLocked(
-        const std::vector<LayerRequirement>& layers, const GlobalSignals& globalSignals,
-        GlobalSignals* outSignalsConsidered) const {
+auto RefreshRateConfigs::getBestRefreshRateLocked(const std::vector<LayerRequirement>& layers,
+                                                  GlobalSignals signals) const
+        -> std::pair<DisplayModePtr, GlobalSignals> {
     ATRACE_CALL();
-    ALOGV("getBestRefreshRate %zu layers", layers.size());
-
-    if (outSignalsConsidered) *outSignalsConsidered = {};
-    const auto setTouchConsidered = [&] {
-        if (outSignalsConsidered) {
-            outSignalsConsidered->touch = true;
-        }
-    };
-
-    const auto setIdleConsidered = [&] {
-        if (outSignalsConsidered) {
-            outSignalsConsidered->idle = true;
-        }
-    };
+    ALOGV("%s: %zu layers", __func__, layers.size());
 
     int noVoteLayers = 0;
     int minVoteLayers = 0;
@@ -312,6 +311,7 @@
     int explicitExact = 0;
     float maxExplicitWeight = 0;
     int seamedFocusedLayers = 0;
+
     for (const auto& layer : layers) {
         switch (layer.vote) {
             case LayerVoteType::NoVote:
@@ -347,67 +347,76 @@
     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 ? mActiveModeIt->second->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 (globalSignals.touch && !hasExplicitVoteLayers) {
-        ALOGV("TouchBoost - choose %s", getMaxRefreshRateByPolicyLocked().getName().c_str());
-        setTouchConsidered();
-        return getMaxRefreshRateByPolicyLocked();
+    if (signals.touch && !hasExplicitVoteLayers) {
+        const DisplayModePtr& max = getMaxRefreshRateByPolicyLocked(anchorGroup);
+        ALOGV("TouchBoost - choose %s", to_string(max->getFps()).c_str());
+        return {max, 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 Policy* policy = getCurrentPolicyLocked();
     const bool primaryRangeIsSingleRate =
-            policy->primaryRange.min.equalsWithMargin(policy->primaryRange.max);
+            isApproxEqual(policy->primaryRange.min, policy->primaryRange.max);
 
-    if (!globalSignals.touch && globalSignals.idle &&
-        !(primaryRangeIsSingleRate && hasExplicitVoteLayers)) {
-        ALOGV("Idle - choose %s", getMinRefreshRateByPolicyLocked().getName().c_str());
-        setIdleConsidered();
-        return getMinRefreshRateByPolicyLocked();
+    if (!signals.touch && signals.idle && !(primaryRangeIsSingleRate && hasExplicitVoteLayers)) {
+        const DisplayModePtr& min = getMinRefreshRateByPolicyLocked();
+        ALOGV("Idle - choose %s", to_string(min->getFps()).c_str());
+        return {min, GlobalSignals{.idle = true}};
     }
 
     if (layers.empty() || noVoteLayers == layers.size()) {
-        return getMaxRefreshRateByPolicyLocked();
+        const DisplayModePtr& max = getMaxRefreshRateByPolicyLocked(anchorGroup);
+        ALOGV("no layers with votes - choose %s", to_string(max->getFps()).c_str());
+        return {max, kNoSignals};
     }
 
     // Only if all layers want Min we should return Min
     if (noVoteLayers + minVoteLayers == layers.size()) {
-        ALOGV("all layers Min - choose %s", getMinRefreshRateByPolicyLocked().getName().c_str());
-        return getMinRefreshRateByPolicyLocked();
+        const DisplayModePtr& min = getMinRefreshRateByPolicyLocked();
+        ALOGV("all layers Min - choose %s", to_string(min->getFps()).c_str());
+        return {min, kNoSignals};
     }
 
     // Find the best refresh rate based on score
     std::vector<RefreshRateScore> scores;
     scores.reserve(mAppRequestRefreshRates.size());
 
-    for (const auto refreshRate : mAppRequestRefreshRates) {
-        scores.emplace_back(RefreshRateScore{refreshRate, 0.0f});
+    for (const DisplayModeIterator modeIt : mAppRequestRefreshRates) {
+        scores.emplace_back(RefreshRateScore{modeIt, 0.0f});
     }
 
-    const auto& defaultMode = mRefreshRates.at(policy->defaultMode);
-
     for (const auto& layer : layers) {
         ALOGV("Calculating score for %s (%s, weight %.2f, desired %.2f) ", layer.name.c_str(),
-              layerVoteTypeString(layer.vote).c_str(), layer.weight,
+              ftl::enum_string(layer.vote).c_str(), layer.weight,
               layer.desiredRefreshRate.getValue());
         if (layer.vote == LayerVoteType::NoVote || layer.vote == LayerVoteType::Min) {
             continue;
         }
 
-        auto weight = layer.weight;
+        const auto weight = layer.weight;
 
-        for (auto i = 0u; i < scores.size(); i++) {
-            const bool isSeamlessSwitch =
-                    scores[i].refreshRate->getModeGroup() == mCurrentRefreshRate->getModeGroup();
+        for (auto& [modeIt, score] : scores) {
+            const auto& [id, mode] = *modeIt;
+            const bool isSeamlessSwitch = mode->getGroup() == mActiveModeIt->second->getGroup();
 
             if (layer.seamlessness == Seamlessness::OnlySeamless && !isSeamlessSwitch) {
                 ALOGV("%s ignores %s to avoid non-seamless switch. Current mode = %s",
-                      formatLayerInfo(layer, weight).c_str(),
-                      scores[i].refreshRate->toString().c_str(),
-                      mCurrentRefreshRate->toString().c_str());
+                      formatLayerInfo(layer, weight).c_str(), to_string(*mode).c_str(),
+                      to_string(*mActiveModeIt->second).c_str());
                 continue;
             }
 
@@ -415,9 +424,8 @@
                 !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(),
-                      scores[i].refreshRate->toString().c_str(),
-                      mCurrentRefreshRate->toString().c_str());
+                      formatLayerInfo(layer, weight).c_str(), to_string(*mode).c_str(),
+                      to_string(*mActiveModeIt->second).c_str());
                 continue;
             }
 
@@ -426,19 +434,14 @@
             // 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 = seamedFocusedLayers > 0
-                    ? scores[i].refreshRate->getModeGroup() == mCurrentRefreshRate->getModeGroup()
-                    : scores[i].refreshRate->getModeGroup() == defaultMode->getModeGroup();
-
+            const bool isInPolicyForDefault = mode->getGroup() == anchorGroup;
             if (layer.seamlessness == Seamlessness::Default && !isInPolicyForDefault) {
                 ALOGV("%s ignores %s. Current mode = %s", formatLayerInfo(layer, weight).c_str(),
-                      scores[i].refreshRate->toString().c_str(),
-                      mCurrentRefreshRate->toString().c_str());
+                      to_string(*mode).c_str(), to_string(*mActiveModeIt->second).c_str());
                 continue;
             }
 
-            bool inPrimaryRange = scores[i].refreshRate->inPolicy(policy->primaryRange.min,
-                                                                  policy->primaryRange.max);
+            const bool inPrimaryRange = policy->primaryRange.includes(mode->getFps());
             if ((primaryRangeIsSingleRate || !inPrimaryRange) &&
                 !(layer.focused &&
                   (layer.vote == LayerVoteType::ExplicitDefault ||
@@ -449,30 +452,31 @@
             }
 
             const auto layerScore =
-                    calculateLayerScoreLocked(layer, *scores[i].refreshRate, isSeamlessSwitch);
+                    calculateLayerScoreLocked(layer, mode->getFps(), isSeamlessSwitch);
             ALOGV("%s gives %s score of %.4f", formatLayerInfo(layer, weight).c_str(),
-                  scores[i].refreshRate->getName().c_str(), layerScore);
-            scores[i].score += weight * layerScore;
+                  to_string(mode->getFps()).c_str(), layerScore);
+
+            score += weight * layerScore;
         }
     }
 
     // Now that we scored all the refresh rates we need to pick the one that got the highest score.
     // In case of a tie we will pick the higher refresh rate if any of the layers wanted Max,
     // or the lower otherwise.
-    const RefreshRate* bestRefreshRate = maxVoteLayers > 0
-            ? getBestRefreshRate(scores.rbegin(), scores.rend())
-            : getBestRefreshRate(scores.begin(), scores.end());
+    const DisplayModePtr& bestRefreshRate = maxVoteLayers > 0
+            ? getMaxScoreRefreshRate(scores.rbegin(), scores.rend())
+            : getMaxScoreRefreshRate(scores.begin(), scores.end());
 
     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 (std::all_of(scores.begin(), scores.end(),
                         [](RefreshRateScore score) { return score.score == 0; })) {
-            ALOGV("layers not scored - choose %s",
-                  getMaxRefreshRateByPolicyLocked().getName().c_str());
-            return getMaxRefreshRateByPolicyLocked();
+            const DisplayModePtr& max = getMaxRefreshRateByPolicyLocked(anchorGroup);
+            ALOGV("layers not scored - choose %s", to_string(max->getFps()).c_str());
+            return {max, kNoSignals};
         } else {
-            return *bestRefreshRate;
+            return {bestRefreshRate, kNoSignals};
         }
     }
 
@@ -480,10 +484,10 @@
     // 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 RefreshRate& touchRefreshRate = getMaxRefreshRateByPolicyLocked();
+    const DisplayModePtr& touchRefreshRate = getMaxRefreshRateByPolicyLocked(anchorGroup);
 
     const bool touchBoostForExplicitExact = [&] {
-        if (mSupportsFrameRateOverride) {
+        if (mSupportsFrameRateOverrideByContent) {
             // Enable touch boost if there are other layers besides exact
             return explicitExact + noVoteLayers != layers.size();
         } else {
@@ -491,14 +495,16 @@
             return explicitExact == 0;
         }
     }();
-    if (globalSignals.touch && explicitDefaultVoteLayers == 0 && touchBoostForExplicitExact &&
-        bestRefreshRate->getFps().lessThanWithMargin(touchRefreshRate.getFps())) {
-        setTouchConsidered();
-        ALOGV("TouchBoost - choose %s", touchRefreshRate.getName().c_str());
-        return touchRefreshRate;
+
+    using fps_approx_ops::operator<;
+
+    if (signals.touch && explicitDefaultVoteLayers == 0 && touchBoostForExplicitExact &&
+        bestRefreshRate->getFps() < touchRefreshRate->getFps()) {
+        ALOGV("TouchBoost - choose %s", to_string(touchRefreshRate->getFps()).c_str());
+        return {touchRefreshRate, GlobalSignals{.touch = true}};
     }
 
-    return *bestRefreshRate;
+    return {bestRefreshRate, kNoSignals};
 }
 
 std::unordered_map<uid_t, std::vector<const RefreshRateConfigs::LayerRequirement*>>
@@ -532,26 +538,28 @@
     return layersByUid;
 }
 
-std::vector<RefreshRateScore> initializeScoresForAllRefreshRates(
-        const AllRefreshRatesMapType& refreshRates) {
-    std::vector<RefreshRateScore> scores;
-    scores.reserve(refreshRates.size());
-    for (const auto& [ignored, refreshRate] : refreshRates) {
-        scores.emplace_back(RefreshRateScore{refreshRate.get(), 0.0f});
-    }
-    std::sort(scores.begin(), scores.end(),
-              [](const auto& a, const auto& b) { return *a.refreshRate < *b.refreshRate; });
-    return scores;
-}
-
 RefreshRateConfigs::UidToFrameRateOverride RefreshRateConfigs::getFrameRateOverrides(
-        const std::vector<LayerRequirement>& layers, Fps displayFrameRate, bool touch) const {
+        const std::vector<LayerRequirement>& layers, Fps displayRefreshRate,
+        GlobalSignals globalSignals) const {
     ATRACE_CALL();
-    if (!mSupportsFrameRateOverride) return {};
 
-    ALOGV("getFrameRateOverrides %zu layers", layers.size());
+    ALOGV("%s: %zu layers", __func__, layers.size());
+
     std::lock_guard lock(mLock);
-    std::vector<RefreshRateScore> scores = initializeScoresForAllRefreshRates(mRefreshRates);
+
+    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;
@@ -563,12 +571,12 @@
                                 return layer->vote == LayerVoteType::ExplicitExactOrMultiple;
                             });
 
-        if (touch && hasExplicitExactOrMultiple) {
+        if (globalSignals.touch && hasExplicitExactOrMultiple) {
             continue;
         }
 
-        for (auto& score : scores) {
-            score.score = 0;
+        for (auto& [_, score] : scores) {
+            score = 0;
         }
 
         for (const auto& layer : layersWithSameUid) {
@@ -579,219 +587,176 @@
             LOG_ALWAYS_FATAL_IF(layer->vote != LayerVoteType::ExplicitDefault &&
                                 layer->vote != LayerVoteType::ExplicitExactOrMultiple &&
                                 layer->vote != LayerVoteType::ExplicitExact);
-            for (RefreshRateScore& score : scores) {
-                const auto layerScore = calculateLayerScoreLocked(*layer, *score.refreshRate,
-                                                                  /*isSeamlessSwitch*/ true);
-                score.score += layer->weight * layerScore;
+            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 divider of the
+        // We just care about the refresh rates which are a divisor of the
         // display refresh rate
-        auto iter =
-                std::remove_if(scores.begin(), scores.end(), [&](const RefreshRateScore& score) {
-                    return getFrameRateDivider(displayFrameRate, score.refreshRate->getFps()) == 0;
-                });
-        scores.erase(iter, scores.end());
+        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(),
-                        [](const RefreshRateScore& score) { return score.score == 0; })) {
+                        [](RefreshRateScore score) { return score.score == 0; })) {
             continue;
         }
 
         // Now that we scored all the refresh rates we need to pick the one that got the highest
         // score.
-        const RefreshRate* bestRefreshRate = getBestRefreshRate(scores.begin(), scores.end());
+        const DisplayModePtr& bestRefreshRate =
+                getMaxScoreRefreshRate(scores.begin(), scores.end());
+
         frameRateOverrides.emplace(uid, bestRefreshRate->getFps());
     }
 
     return frameRateOverrides;
 }
 
-template <typename Iter>
-const RefreshRate* RefreshRateConfigs::getBestRefreshRate(Iter begin, Iter end) const {
-    constexpr auto kEpsilon = 0.0001f;
-    const RefreshRate* bestRefreshRate = begin->refreshRate;
-    float max = begin->score;
-    for (auto i = begin; i != end; ++i) {
-        const auto [refreshRate, score] = *i;
-        ALOGV("%s scores %.2f", refreshRate->getName().c_str(), score);
-
-        ATRACE_INT(refreshRate->getName().c_str(), round<int>(score * 100));
-
-        if (score > max * (1 + kEpsilon)) {
-            max = score;
-            bestRefreshRate = refreshRate;
-        }
-    }
-
-    return bestRefreshRate;
-}
-
 std::optional<Fps> RefreshRateConfigs::onKernelTimerChanged(
-        std::optional<DisplayModeId> desiredActiveConfigId, bool timerExpired) const {
+        std::optional<DisplayModeId> desiredActiveModeId, bool timerExpired) const {
     std::lock_guard lock(mLock);
 
-    const auto& current = desiredActiveConfigId ? *mRefreshRates.at(*desiredActiveConfigId)
-                                                : *mCurrentRefreshRate;
-    const auto& min = *mMinSupportedRefreshRate;
+    const DisplayModePtr& current = desiredActiveModeId
+            ? mDisplayModes.get(*desiredActiveModeId)->get()
+            : mActiveModeIt->second;
 
-    if (current != min) {
-        const auto& refreshRate = timerExpired ? min : current;
-        return refreshRate.getFps();
+    const DisplayModePtr& min = mMinRefreshRateModeIt->second;
+    if (current == min) {
+        return {};
     }
 
-    return {};
+    const auto& mode = timerExpired ? min : current;
+    return mode->getFps();
 }
 
-const RefreshRate& RefreshRateConfigs::getMinRefreshRateByPolicyLocked() const {
-    for (auto refreshRate : mPrimaryRefreshRates) {
-        if (mCurrentRefreshRate->getModeGroup() == refreshRate->getModeGroup()) {
-            return *refreshRate;
+const DisplayModePtr& RefreshRateConfigs::getMinRefreshRateByPolicyLocked() const {
+    for (const DisplayModeIterator modeIt : mPrimaryRefreshRates) {
+        const auto& mode = modeIt->second;
+        if (mActiveModeIt->second->getGroup() == mode->getGroup()) {
+            return mode;
         }
     }
+
     ALOGE("Can't find min refresh rate by policy with the same mode group"
           " as the current mode %s",
-          mCurrentRefreshRate->toString().c_str());
-    // Defaulting to the lowest refresh rate
-    return *mPrimaryRefreshRates.front();
+          to_string(*mActiveModeIt->second).c_str());
+
+    // Default to the lowest refresh rate.
+    return mPrimaryRefreshRates.front()->second;
 }
 
-RefreshRate RefreshRateConfigs::getMaxRefreshRateByPolicy() const {
+DisplayModePtr RefreshRateConfigs::getMaxRefreshRateByPolicy() const {
     std::lock_guard lock(mLock);
     return getMaxRefreshRateByPolicyLocked();
 }
 
-const RefreshRate& RefreshRateConfigs::getMaxRefreshRateByPolicyLocked() const {
-    for (auto it = mPrimaryRefreshRates.rbegin(); it != mPrimaryRefreshRates.rend(); it++) {
-        const auto& refreshRate = (**it);
-        if (mCurrentRefreshRate->getModeGroup() == refreshRate.getModeGroup()) {
-            return refreshRate;
+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 mode group"
           " as the current mode %s",
-          mCurrentRefreshRate->toString().c_str());
-    // Defaulting to the highest refresh rate
-    return *mPrimaryRefreshRates.back();
+          to_string(*mActiveModeIt->second).c_str());
+
+    // Default to the highest refresh rate.
+    return mPrimaryRefreshRates.back()->second;
 }
 
-RefreshRate RefreshRateConfigs::getCurrentRefreshRate() const {
+DisplayModePtr RefreshRateConfigs::getActiveMode() const {
     std::lock_guard lock(mLock);
-    return *mCurrentRefreshRate;
+    return mActiveModeIt->second;
 }
 
-RefreshRate RefreshRateConfigs::getCurrentRefreshRateByPolicy() const {
-    std::lock_guard lock(mLock);
-    return getCurrentRefreshRateByPolicyLocked();
-}
-
-const RefreshRate& RefreshRateConfigs::getCurrentRefreshRateByPolicyLocked() const {
-    if (std::find(mAppRequestRefreshRates.begin(), mAppRequestRefreshRates.end(),
-                  mCurrentRefreshRate) != mAppRequestRefreshRates.end()) {
-        return *mCurrentRefreshRate;
-    }
-    return *mRefreshRates.at(getCurrentPolicyLocked()->defaultMode);
-}
-
-void RefreshRateConfigs::setCurrentModeId(DisplayModeId modeId) {
+void RefreshRateConfigs::setActiveModeId(DisplayModeId modeId) {
     std::lock_guard lock(mLock);
 
     // Invalidate the cached invocation to getBestRefreshRate. This forces
     // the refresh rate to be recomputed on the next call to getBestRefreshRate.
-    lastBestRefreshRateInvocation.reset();
+    mGetBestRefreshRateCache.reset();
 
-    mCurrentRefreshRate = mRefreshRates.at(modeId).get();
+    mActiveModeIt = mDisplayModes.find(modeId);
+    LOG_ALWAYS_FATAL_IF(mActiveModeIt == mDisplayModes.end());
 }
 
-RefreshRateConfigs::RefreshRateConfigs(const DisplayModes& modes, DisplayModeId currentModeId,
+RefreshRateConfigs::RefreshRateConfigs(DisplayModes modes, DisplayModeId activeModeId,
                                        Config config)
       : mKnownFrameRates(constructKnownFrameRates(modes)), mConfig(config) {
     initializeIdleTimer();
-    updateDisplayModes(modes, currentModeId);
+    updateDisplayModes(std::move(modes), activeModeId);
 }
 
 void RefreshRateConfigs::initializeIdleTimer() {
-    if (mConfig.idleTimerTimeoutMs > 0) {
-        const auto getCallback = [this]() -> std::optional<IdleTimerCallbacks::Callbacks> {
-            std::scoped_lock lock(mIdleTimerCallbacksMutex);
-            if (!mIdleTimerCallbacks.has_value()) return {};
-            return mConfig.supportKernelIdleTimer ? mIdleTimerCallbacks->kernel
-                                                  : mIdleTimerCallbacks->platform;
-        };
-
+    if (mConfig.idleTimerTimeout > 0ms) {
         mIdleTimer.emplace(
-                "IdleTimer", std::chrono::milliseconds(mConfig.idleTimerTimeoutMs),
-                [getCallback] {
-                    if (const auto callback = getCallback()) callback->onReset();
+                "IdleTimer", mConfig.idleTimerTimeout,
+                [this] {
+                    std::scoped_lock lock(mIdleTimerCallbacksMutex);
+                    if (const auto callbacks = getIdleTimerCallbacks()) {
+                        callbacks->onReset();
+                    }
                 },
-                [getCallback] {
-                    if (const auto callback = getCallback()) callback->onExpired();
+                [this] {
+                    std::scoped_lock lock(mIdleTimerCallbacksMutex);
+                    if (const auto callbacks = getIdleTimerCallbacks()) {
+                        callbacks->onExpired();
+                    }
                 });
     }
 }
 
-void RefreshRateConfigs::updateDisplayModes(const DisplayModes& modes,
-                                            DisplayModeId currentModeId) {
+void RefreshRateConfigs::updateDisplayModes(DisplayModes modes, DisplayModeId activeModeId) {
     std::lock_guard lock(mLock);
 
-    // The current mode should be supported
-    LOG_ALWAYS_FATAL_IF(std::none_of(modes.begin(), modes.end(), [&](DisplayModePtr mode) {
-        return mode->getId() == currentModeId;
-    }));
-
     // Invalidate the cached invocation to getBestRefreshRate. This forces
     // the refresh rate to be recomputed on the next call to getBestRefreshRate.
-    lastBestRefreshRateInvocation.reset();
+    mGetBestRefreshRateCache.reset();
 
-    mRefreshRates.clear();
-    for (const auto& mode : modes) {
-        const auto modeId = mode->getId();
-        mRefreshRates.emplace(modeId,
-                              std::make_unique<RefreshRate>(mode, RefreshRate::ConstructorTag(0)));
-        if (modeId == currentModeId) {
-            mCurrentRefreshRate = mRefreshRates.at(modeId).get();
-        }
-    }
+    mDisplayModes = std::move(modes);
+    mActiveModeIt = mDisplayModes.find(activeModeId);
+    LOG_ALWAYS_FATAL_IF(mActiveModeIt == mDisplayModes.end());
 
-    std::vector<const RefreshRate*> sortedModes;
-    getSortedRefreshRateListLocked([](const RefreshRate&) { return true; }, &sortedModes);
+    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 = currentModeId;
-    mMinSupportedRefreshRate = sortedModes.front();
-    mMaxSupportedRefreshRate = sortedModes.back();
+    mDisplayManagerPolicy.defaultMode = activeModeId;
 
-    mSupportsFrameRateOverride = false;
-    if (mConfig.enableFrameRateOverride) {
-        for (const auto& mode1 : sortedModes) {
-            for (const auto& mode2 : sortedModes) {
-                if (getFrameRateDivider(mode1->getFps(), mode2->getFps()) >= 2) {
-                    mSupportsFrameRateOverride = true;
-                    break;
-                }
-            }
-        }
-    }
+    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.
-    auto iter = mRefreshRates.find(policy.defaultMode);
-    if (iter == mRefreshRates.end()) {
+    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;
     }
-    const RefreshRate& refreshRate = *iter->second;
-    if (!refreshRate.inPolicy(policy.primaryRange.min, policy.primaryRange.max)) {
-        ALOGE("Default mode is not in the primary range.");
-        return false;
-    }
-    return policy.appRequestRange.min.lessThanOrEqualWithMargin(policy.primaryRange.min) &&
-            policy.appRequestRange.max.greaterThanOrEqualWithMargin(policy.primaryRange.max);
+
+    using namespace fps_approx_ops;
+    return policy.appRequestRange.min <= policy.primaryRange.min &&
+            policy.appRequestRange.max >= policy.primaryRange.max;
 }
 
 status_t RefreshRateConfigs::setDisplayManagerPolicy(const Policy& policy) {
@@ -800,7 +765,7 @@
         ALOGE("Invalid refresh rate policy: %s", policy.toString().c_str());
         return BAD_VALUE;
     }
-    lastBestRefreshRateInvocation.reset();
+    mGetBestRefreshRateCache.reset();
     Policy previousPolicy = *getCurrentPolicyLocked();
     mDisplayManagerPolicy = policy;
     if (*getCurrentPolicyLocked() == previousPolicy) {
@@ -815,7 +780,7 @@
     if (policy && !isPolicyValidLocked(*policy)) {
         return BAD_VALUE;
     }
-    lastBestRefreshRateInvocation.reset();
+    mGetBestRefreshRateCache.reset();
     Policy previousPolicy = *getCurrentPolicyLocked();
     mOverridePolicy = policy;
     if (*getCurrentPolicyLocked() == previousPolicy) {
@@ -841,130 +806,102 @@
 
 bool RefreshRateConfigs::isModeAllowed(DisplayModeId modeId) const {
     std::lock_guard lock(mLock);
-    for (const RefreshRate* refreshRate : mAppRequestRefreshRates) {
-        if (refreshRate->getModeId() == modeId) {
-            return true;
-        }
-    }
-    return false;
-}
-
-void RefreshRateConfigs::getSortedRefreshRateListLocked(
-        const std::function<bool(const RefreshRate&)>& shouldAddRefreshRate,
-        std::vector<const RefreshRate*>* outRefreshRates) {
-    outRefreshRates->clear();
-    outRefreshRates->reserve(mRefreshRates.size());
-    for (const auto& [type, refreshRate] : mRefreshRates) {
-        if (shouldAddRefreshRate(*refreshRate)) {
-            ALOGV("getSortedRefreshRateListLocked: mode %d added to list policy",
-                  refreshRate->getModeId().value());
-            outRefreshRates->push_back(refreshRate.get());
-        }
-    }
-
-    std::sort(outRefreshRates->begin(), outRefreshRates->end(),
-              [](const auto refreshRate1, const auto refreshRate2) {
-                  if (refreshRate1->mode->getVsyncPeriod() !=
-                      refreshRate2->mode->getVsyncPeriod()) {
-                      return refreshRate1->mode->getVsyncPeriod() >
-                              refreshRate2->mode->getVsyncPeriod();
-                  } else {
-                      return refreshRate1->mode->getGroup() > refreshRate2->mode->getGroup();
-                  }
-              });
+    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 based on vsync period
+    // Filter modes based on current policy and sort on refresh rate.
     const Policy* policy = getCurrentPolicyLocked();
-    const auto& defaultMode = mRefreshRates.at(policy->defaultMode)->mode;
-    ALOGV("constructAvailableRefreshRates: %s ", policy->toString().c_str());
+    ALOGV("%s: %s ", __func__, policy->toString().c_str());
 
-    auto filterRefreshRates =
-            [&](Fps min, Fps max, const char* listName,
-                std::vector<const RefreshRate*>* outRefreshRates) REQUIRES(mLock) {
-                getSortedRefreshRateListLocked(
-                        [&](const RefreshRate& refreshRate) REQUIRES(mLock) {
-                            const auto& mode = refreshRate.mode;
+    const auto& defaultMode = mDisplayModes.get(policy->defaultMode)->get();
 
-                            return mode->getHeight() == defaultMode->getHeight() &&
-                                    mode->getWidth() == defaultMode->getWidth() &&
-                                    mode->getDpiX() == defaultMode->getDpiX() &&
-                                    mode->getDpiY() == defaultMode->getDpiY() &&
-                                    (policy->allowGroupSwitching ||
-                                     mode->getGroup() == defaultMode->getGroup()) &&
-                                    refreshRate.inPolicy(min, max);
-                        },
-                        outRefreshRates);
+    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());
+        };
 
-                LOG_ALWAYS_FATAL_IF(outRefreshRates->empty(),
-                                    "No matching modes for %s range: min=%s max=%s", listName,
-                                    to_string(min).c_str(), to_string(max).c_str());
-                auto stringifyRefreshRates = [&]() -> std::string {
-                    std::string str;
-                    for (auto refreshRate : *outRefreshRates) {
-                        base::StringAppendF(&str, "%s ", refreshRate->getName().c_str());
-                    }
-                    return str;
-                };
-                ALOGV("%s refresh rates: %s", listName, stringifyRefreshRates().c_str());
-            };
+        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());
 
-    filterRefreshRates(policy->primaryRange.min, policy->primaryRange.max, "primary",
-                       &mPrimaryRefreshRates);
-    filterRefreshRates(policy->appRequestRange.min, policy->appRequestRange.max, "app request",
-                       &mAppRequestRefreshRates);
+        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 {
-    if (frameRate.lessThanOrEqualWithMargin(*mKnownFrameRates.begin())) {
-        return *mKnownFrameRates.begin();
+    using namespace fps_approx_ops;
+
+    if (frameRate <= mKnownFrameRates.front()) {
+        return mKnownFrameRates.front();
     }
 
-    if (frameRate.greaterThanOrEqualWithMargin(*std::prev(mKnownFrameRates.end()))) {
-        return *std::prev(mKnownFrameRates.end());
+    if (frameRate >= mKnownFrameRates.back()) {
+        return mKnownFrameRates.back();
     }
 
     auto lowerBound = std::lower_bound(mKnownFrameRates.begin(), mKnownFrameRates.end(), frameRate,
-                                       Fps::comparesLess);
+                                       isStrictlyLess);
 
-    const auto distance1 = std::abs((frameRate.getValue() - lowerBound->getValue()));
-    const auto distance2 = std::abs((frameRate.getValue() - std::prev(lowerBound)->getValue()));
+    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 auto& deviceMin = *mMinSupportedRefreshRate;
-    const auto& minByPolicy = getMinRefreshRateByPolicyLocked();
-    const auto& maxByPolicy = getMaxRefreshRateByPolicyLocked();
-    const auto& currentPolicy = getCurrentPolicyLocked();
+
+    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 (deviceMin < minByPolicy) {
-        return RefreshRateConfigs::KernelIdleTimerAction::TurnOff;
+    if (isStrictlyLess(deviceMinFps, minByPolicy->getFps())) {
+        return KernelIdleTimerAction::TurnOff;
     }
+
+    const DisplayModePtr& maxByPolicy = getMaxRefreshRateByPolicyLocked();
     if (minByPolicy == maxByPolicy) {
-        // when min primary range in display manager policy is below device min turn on the timer.
-        if (currentPolicy->primaryRange.min.lessThanWithMargin(deviceMin.getFps())) {
-            return RefreshRateConfigs::KernelIdleTimerAction::TurnOn;
+        // 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 RefreshRateConfigs::KernelIdleTimerAction::TurnOff;
+        return KernelIdleTimerAction::TurnOff;
     }
+
     // Turn on the timer in all other cases.
-    return RefreshRateConfigs::KernelIdleTimerAction::TurnOn;
+    return KernelIdleTimerAction::TurnOn;
 }
 
-int RefreshRateConfigs::getFrameRateDivider(Fps displayFrameRate, Fps layerFrameRate) {
+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 = displayFrameRate.getValue() / layerFrameRate.getValue();
+    const auto numPeriods = displayRefreshRate.getValue() / layerFrameRate.getValue();
     const auto numPeriodsRounded = std::round(numPeriods);
     if (std::abs(numPeriods - numPeriodsRounded) > kThreshold) {
         return 0;
@@ -974,14 +911,14 @@
 }
 
 bool RefreshRateConfigs::isFractionalPairOrMultiple(Fps smaller, Fps bigger) {
-    if (smaller.getValue() > bigger.getValue()) {
+    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 bigger.equalsWithMargin(Fps(smaller.getValue() * multiplier / kCoef)) ||
-            bigger.equalsWithMargin(Fps(smaller.getValue() * multiplier * kCoef));
+    return isApproxEqual(bigger, Fps::fromValue(smaller.getValue() * multiplier / kCoef)) ||
+            isApproxEqual(bigger, Fps::fromValue(smaller.getValue() * multiplier * kCoef));
 }
 
 void RefreshRateConfigs::dump(std::string& result) const {
@@ -994,21 +931,36 @@
                             currentPolicy.toString().c_str());
     }
 
-    auto mode = mCurrentRefreshRate->mode;
-    base::StringAppendF(&result, "Current mode: %s\n", mCurrentRefreshRate->toString().c_str());
+    base::StringAppendF(&result, "Active mode: %s\n", to_string(*mActiveModeIt->second).c_str());
 
-    result.append("Refresh rates:\n");
-    for (const auto& [id, refreshRate] : mRefreshRates) {
-        mode = refreshRate->mode;
-        base::StringAppendF(&result, "\t%s\n", refreshRate->toString().c_str());
+    result.append("Display modes:\n");
+    for (const auto& [id, mode] : mDisplayModes) {
+        result.push_back('\t');
+        result.append(to_string(*mode));
+        result.push_back('\n');
     }
 
-    base::StringAppendF(&result, "Supports Frame Rate Override: %s\n",
-                        mSupportsFrameRateOverride ? "yes" : "no");
-    base::StringAppendF(&result, "Idle timer: (%s) %s\n",
-                        mConfig.supportKernelIdleTimer ? "kernel" : "platform",
-                        mIdleTimer ? mIdleTimer->dump().c_str() : "off");
-    result.append("\n");
+    base::StringAppendF(&result, "Supports Frame Rate Override By Content: %s\n",
+                        mSupportsFrameRateOverrideByContent ? "yes" : "no");
+
+    result.append("Idle timer: ");
+    if (const auto controller = mConfig.kernelIdleTimerController) {
+        base::StringAppendF(&result, "(kernel via %s) ", ftl::enum_string(*controller).c_str());
+    } else {
+        result.append("(platform) ");
+    }
+
+    if (mIdleTimer) {
+        result.append(mIdleTimer->dump());
+    } else {
+        result.append("off");
+    }
+
+    result.append("\n\n");
+}
+
+std::chrono::milliseconds RefreshRateConfigs::getIdleTimerTimeout() {
+    return mConfig.idleTimerTimeout;
 }
 
 } // namespace android::scheduler
diff --git a/services/surfaceflinger/Scheduler/RefreshRateConfigs.h b/services/surfaceflinger/Scheduler/RefreshRateConfigs.h
index 99f217c..05a8692 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateConfigs.h
+++ b/services/surfaceflinger/Scheduler/RefreshRateConfigs.h
@@ -16,31 +16,31 @@
 
 #pragma once
 
-#include <android-base/stringprintf.h>
-#include <gui/DisplayEventReceiver.h>
-
 #include <algorithm>
 #include <numeric>
 #include <optional>
 #include <type_traits>
+#include <utility>
+
+#include <gui/DisplayEventReceiver.h>
+
+#include <scheduler/Fps.h>
+#include <scheduler/Seamlessness.h>
 
 #include "DisplayHardware/DisplayMode.h"
 #include "DisplayHardware/HWComposer.h"
-#include "Fps.h"
 #include "Scheduler/OneShotTimer.h"
-#include "Scheduler/SchedulerUtils.h"
-#include "Scheduler/Seamlessness.h"
 #include "Scheduler/StrongTyping.h"
 
 namespace android::scheduler {
 
 using namespace std::chrono_literals;
 
-enum class RefreshRateConfigEvent : unsigned { None = 0b0, Changed = 0b1 };
+enum class DisplayModeEvent : unsigned { None = 0b0, Changed = 0b1 };
 
-inline RefreshRateConfigEvent operator|(RefreshRateConfigEvent lhs, RefreshRateConfigEvent rhs) {
-    using T = std::underlying_type_t<RefreshRateConfigEvent>;
-    return static_cast<RefreshRateConfigEvent>(static_cast<T>(lhs) | static_cast<T>(rhs));
+inline DisplayModeEvent operator|(DisplayModeEvent lhs, DisplayModeEvent rhs) {
+    using T = std::underlying_type_t<DisplayModeEvent>;
+    return static_cast<DisplayModeEvent>(static_cast<T>(lhs) | static_cast<T>(rhs));
 }
 
 using FrameRateOverride = DisplayEventReceiver::Event::FrameRateOverride;
@@ -56,69 +56,6 @@
     static constexpr nsecs_t MARGIN_FOR_PERIOD_CALCULATION =
             std::chrono::nanoseconds(800us).count();
 
-    class RefreshRate {
-    private:
-        // Effectively making the constructor private while allowing
-        // std::make_unique to create the object
-        struct ConstructorTag {
-            explicit ConstructorTag(int) {}
-        };
-
-    public:
-        RefreshRate(DisplayModePtr mode, ConstructorTag) : mode(mode) {}
-
-        DisplayModeId getModeId() const { return mode->getId(); }
-        nsecs_t getVsyncPeriod() const { return mode->getVsyncPeriod(); }
-        int32_t getModeGroup() const { return mode->getGroup(); }
-        std::string getName() const { return to_string(getFps()); }
-        Fps getFps() const { return mode->getFps(); }
-        DisplayModePtr getMode() const { return mode; }
-
-        // Checks whether the fps of this RefreshRate struct is within a given min and max refresh
-        // rate passed in. Margin of error is applied to the boundaries for approximation.
-        bool inPolicy(Fps minRefreshRate, Fps maxRefreshRate) const {
-            return minRefreshRate.lessThanOrEqualWithMargin(getFps()) &&
-                    getFps().lessThanOrEqualWithMargin(maxRefreshRate);
-        }
-
-        bool operator!=(const RefreshRate& other) const { return mode != other.mode; }
-
-        bool operator<(const RefreshRate& other) const {
-            return getFps().getValue() < other.getFps().getValue();
-        }
-
-        bool operator==(const RefreshRate& other) const { return !(*this != other); }
-
-        std::string toString() const;
-        friend std::ostream& operator<<(std::ostream& os, const RefreshRate& refreshRate) {
-            return os << refreshRate.toString();
-        }
-
-    private:
-        friend RefreshRateConfigs;
-        friend class RefreshRateConfigsTest;
-
-        DisplayModePtr mode;
-    };
-
-    using AllRefreshRatesMapType =
-            std::unordered_map<DisplayModeId, std::unique_ptr<const RefreshRate>>;
-
-    struct FpsRange {
-        Fps min{0.0f};
-        Fps max{std::numeric_limits<float>::max()};
-
-        bool operator==(const FpsRange& other) const {
-            return min.equalsWithMargin(other.min) && max.equalsWithMargin(other.max);
-        }
-
-        bool operator!=(const FpsRange& other) const { return !(*this == other); }
-
-        std::string toString() const {
-            return base::StringPrintf("[%s %s]", to_string(min).c_str(), to_string(max).c_str());
-        }
-    };
-
     struct Policy {
     private:
         static constexpr int kAllowGroupSwitchingDefault = false;
@@ -143,24 +80,24 @@
 
         Policy() = default;
 
-        Policy(DisplayModeId defaultMode, const FpsRange& range)
+        Policy(DisplayModeId defaultMode, FpsRange range)
               : Policy(defaultMode, kAllowGroupSwitchingDefault, range, range) {}
 
-        Policy(DisplayModeId defaultMode, bool allowGroupSwitching, const FpsRange& range)
+        Policy(DisplayModeId defaultMode, bool allowGroupSwitching, FpsRange range)
               : Policy(defaultMode, allowGroupSwitching, range, range) {}
 
-        Policy(DisplayModeId defaultMode, const FpsRange& primaryRange,
-               const FpsRange& appRequestRange)
+        Policy(DisplayModeId defaultMode, FpsRange primaryRange, FpsRange appRequestRange)
               : Policy(defaultMode, kAllowGroupSwitchingDefault, primaryRange, appRequestRange) {}
 
-        Policy(DisplayModeId defaultMode, bool allowGroupSwitching, const FpsRange& primaryRange,
-               const FpsRange& appRequestRange)
+        Policy(DisplayModeId defaultMode, bool allowGroupSwitching, FpsRange primaryRange,
+               FpsRange appRequestRange)
               : defaultMode(defaultMode),
                 allowGroupSwitching(allowGroupSwitching),
                 primaryRange(primaryRange),
                 appRequestRange(appRequestRange) {}
 
         bool operator==(const Policy& other) const {
+            using namespace fps_approx_ops;
             return defaultMode == other.defaultMode && primaryRange == other.primaryRange &&
                     appRequestRange == other.appRequestRange &&
                     allowGroupSwitching == other.allowGroupSwitching;
@@ -209,6 +146,7 @@
         ExplicitExact,           // Specific refresh rate that was provided by the app with
                                  // Exact compatibility
 
+        ftl_last = ExplicitExact
     };
 
     // Captures the layer requirements for a refresh rate. This will be used to determine the
@@ -221,7 +159,7 @@
         // Layer vote type.
         LayerVoteType vote = LayerVoteType::NoVote;
         // Layer's desired refresh rate, if applicable.
-        Fps desiredRefreshRate{0.0f};
+        Fps desiredRefreshRate;
         // If a seamless mode switch is required.
         Seamlessness seamlessness = Seamlessness::Default;
         // Layer's weight in the range of [0, 1]. The higher the weight the more impact this layer
@@ -232,7 +170,7 @@
 
         bool operator==(const LayerRequirement& other) const {
             return name == other.name && vote == other.vote &&
-                    desiredRefreshRate.equalsWithMargin(other.desiredRefreshRate) &&
+                    isApproxEqual(desiredRefreshRate, other.desiredRefreshRate) &&
                     seamlessness == other.seamlessness && weight == other.weight &&
                     focused == other.focused;
         }
@@ -247,24 +185,19 @@
         // True if the system hasn't seen any buffers posted to layers recently.
         bool idle = false;
 
-        bool operator==(const GlobalSignals& other) const {
+        bool operator==(GlobalSignals other) const {
             return touch == other.touch && idle == other.idle;
         }
     };
 
-    // Returns the refresh rate that fits best to the given layers.
-    //   layers - The layer requirements to consider.
-    //   globalSignals - global state of touch and idle
-    //   outSignalsConsidered - An output param that tells the caller whether the refresh rate was
-    //                          chosen based on touch boost and/or idle timer.
-    RefreshRate getBestRefreshRate(const std::vector<LayerRequirement>& layers,
-                                   const GlobalSignals& globalSignals,
-                                   GlobalSignals* outSignalsConsidered = nullptr) const
-            EXCLUDES(mLock);
+    // Returns the refresh rate that best fits the given layers, and whether the refresh rate was
+    // chosen based on touch boost and/or idle timer.
+    std::pair<DisplayModePtr, GlobalSignals> getBestRefreshRate(
+            const std::vector<LayerRequirement>&, GlobalSignals) const EXCLUDES(mLock);
 
     FpsRange getSupportedRefreshRateRange() const EXCLUDES(mLock) {
         std::lock_guard lock(mLock);
-        return {mMinSupportedRefreshRate->getFps(), mMaxSupportedRefreshRate->getFps()};
+        return {mMinRefreshRateModeIt->second->getFps(), mMaxRefreshRateModeIt->second->getFps()};
     }
 
     std::optional<Fps> onKernelTimerChanged(std::optional<DisplayModeId> desiredActiveModeId,
@@ -272,32 +205,16 @@
 
     // Returns the highest refresh rate according to the current policy. May change at runtime. Only
     // uses the primary range, not the app request range.
-    RefreshRate getMaxRefreshRateByPolicy() const EXCLUDES(mLock);
+    DisplayModePtr getMaxRefreshRateByPolicy() const EXCLUDES(mLock);
 
-    // Returns the current refresh rate
-    RefreshRate getCurrentRefreshRate() const EXCLUDES(mLock);
-
-    // Returns the current refresh rate, if allowed. Otherwise the default that is allowed by
-    // the policy.
-    RefreshRate getCurrentRefreshRateByPolicy() const;
-
-    // Returns the refresh rate that corresponds to a DisplayModeId. This may change at
-    // runtime.
-    // TODO(b/159590486) An invalid mode id may be given here if the dipslay modes have changed.
-    RefreshRate getRefreshRateFromModeId(DisplayModeId modeId) const EXCLUDES(mLock) {
-        std::lock_guard lock(mLock);
-        return *mRefreshRates.at(modeId);
-    };
-
-    // Stores the current modeId the device operates at
-    void setCurrentModeId(DisplayModeId) EXCLUDES(mLock);
-
-    // Returns a string that represents the layer vote type
-    static std::string layerVoteTypeString(LayerVoteType vote);
+    void setActiveModeId(DisplayModeId) EXCLUDES(mLock);
+    DisplayModePtr getActiveMode() const EXCLUDES(mLock);
 
     // Returns a known frame rate that is the closest to frameRate
     Fps findClosestKnownFrameRate(Fps frameRate) const;
 
+    enum class KernelIdleTimerController { Sysprop, HwcApi, ftl_last = HwcApi };
+
     // Configuration flags.
     struct Config {
         bool enableFrameRateOverride = false;
@@ -308,24 +225,28 @@
         int frameRateMultipleThreshold = 0;
 
         // The Idle Timer timeout. 0 timeout means no idle timer.
-        int32_t idleTimerTimeoutMs = 0;
+        std::chrono::milliseconds idleTimerTimeout = 0ms;
 
-        // Whether to use idle timer callbacks that support the kernel timer.
-        bool supportKernelIdleTimer = false;
+        // The controller representing how the kernel idle timer will be configured
+        // either on the HWC api or sysprop.
+        std::optional<KernelIdleTimerController> kernelIdleTimerController;
     };
 
-    RefreshRateConfigs(const DisplayModes&, DisplayModeId,
+    RefreshRateConfigs(DisplayModes, DisplayModeId activeModeId,
                        Config config = {.enableFrameRateOverride = false,
                                         .frameRateMultipleThreshold = 0,
-                                        .idleTimerTimeoutMs = 0,
-                                        .supportKernelIdleTimer = false});
+                                        .idleTimerTimeout = 0ms,
+                                        .kernelIdleTimerController = {}});
+
+    RefreshRateConfigs(const RefreshRateConfigs&) = delete;
+    RefreshRateConfigs& operator=(const RefreshRateConfigs&) = 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.
     bool canSwitch() const EXCLUDES(mLock) {
         std::lock_guard lock(mLock);
-        return mRefreshRates.size() > 1;
+        return mDisplayModes.size() > 1;
     }
 
     // Class to enumerate options around toggling the kernel timer on and off.
@@ -333,43 +254,51 @@
         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 supportsFrameRateOverride() const { return mSupportsFrameRateOverride; }
+    bool supportsFrameRateOverrideByContent() const { return mSupportsFrameRateOverrideByContent; }
 
-    // Return the display refresh rate divider to match the layer
+    // 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
     // layer refresh rate.
-    static int getFrameRateDivider(Fps displayFrameRate, Fps layerFrameRate);
+    static int getFrameRateDivisor(Fps displayRefreshRate, Fps layerFrameRate);
 
     // Returns if the provided frame rates have a ratio t*1000/1001 or t*1001/1000
     // for an integer t.
     static bool isFractionalPairOrMultiple(Fps, Fps);
 
     using UidToFrameRateOverride = std::map<uid_t, Fps>;
+
     // Returns the frame rate override for each uid.
-    //
-    // @param layers list of visible layers
-    // @param displayFrameRate the display frame rate
-    // @param touch whether touch timer is active (i.e. user touched the screen recently)
-    UidToFrameRateOverride getFrameRateOverrides(const std::vector<LayerRequirement>& layers,
-                                                 Fps displayFrameRate, bool touch) const
+    UidToFrameRateOverride getFrameRateOverrides(const std::vector<LayerRequirement>&,
+                                                 Fps displayFrameRate, GlobalSignals) const
             EXCLUDES(mLock);
 
-    bool supportsKernelIdleTimer() const { return mConfig.supportKernelIdleTimer; }
+    std::optional<KernelIdleTimerController> kernelIdleTimerController() {
+        return mConfig.kernelIdleTimerController;
+    }
 
-    void setIdleTimerCallbacks(std::function<void()> platformTimerReset,
-                               std::function<void()> platformTimerExpired,
-                               std::function<void()> kernelTimerReset,
-                               std::function<void()> kernelTimerExpired) {
+    struct IdleTimerCallbacks {
+        struct Callbacks {
+            std::function<void()> onReset;
+            std::function<void()> onExpired;
+        };
+
+        Callbacks platform;
+        Callbacks kernel;
+    };
+
+    void setIdleTimerCallbacks(IdleTimerCallbacks callbacks) EXCLUDES(mIdleTimerCallbacksMutex) {
         std::scoped_lock lock(mIdleTimerCallbacksMutex);
-        mIdleTimerCallbacks.emplace();
-        mIdleTimerCallbacks->platform.onReset = std::move(platformTimerReset);
-        mIdleTimerCallbacks->platform.onExpired = std::move(platformTimerExpired);
-        mIdleTimerCallbacks->kernel.onReset = std::move(kernelTimerReset);
-        mIdleTimerCallbacks->kernel.onExpired = std::move(kernelTimerExpired);
+        mIdleTimerCallbacks = std::move(callbacks);
+    }
+
+    void clearIdleTimerCallbacks() EXCLUDES(mIdleTimerCallbacksMutex) {
+        std::scoped_lock lock(mIdleTimerCallbacksMutex);
+        mIdleTimerCallbacks.reset();
     }
 
     void startIdleTimer() {
@@ -388,40 +317,23 @@
         if (!mIdleTimer) {
             return;
         }
-        if (kernelOnly && !mConfig.supportKernelIdleTimer) {
+        if (kernelOnly && !mConfig.kernelIdleTimerController.has_value()) {
             return;
         }
         mIdleTimer->reset();
-    };
+    }
 
     void dump(std::string& result) const EXCLUDES(mLock);
 
-    RefreshRateConfigs(const RefreshRateConfigs&) = delete;
-    void operator=(const RefreshRateConfigs&) = delete;
+    std::chrono::milliseconds getIdleTimerTimeout();
 
 private:
-    friend class RefreshRateConfigsTest;
+    friend struct TestableRefreshRateConfigs;
 
     void constructAvailableRefreshRates() REQUIRES(mLock);
 
-    void getSortedRefreshRateListLocked(
-            const std::function<bool(const RefreshRate&)>& shouldAddRefreshRate,
-            std::vector<const RefreshRate*>* outRefreshRates) REQUIRES(mLock);
-
-    std::optional<RefreshRate> getCachedBestRefreshRate(const std::vector<LayerRequirement>& layers,
-                                                        const GlobalSignals& globalSignals,
-                                                        GlobalSignals* outSignalsConsidered) const
-            REQUIRES(mLock);
-
-    RefreshRate getBestRefreshRateLocked(const std::vector<LayerRequirement>& layers,
-                                         const GlobalSignals& globalSignals,
-                                         GlobalSignals* outSignalsConsidered) const REQUIRES(mLock);
-
-    // Returns the refresh rate with the highest score in the collection specified from begin
-    // to end. If there are more than one with the same highest refresh rate, the first one is
-    // returned.
-    template <typename Iter>
-    const RefreshRate* getBestRefreshRate(Iter begin, Iter end) const;
+    std::pair<DisplayModePtr, GlobalSignals> getBestRefreshRateLocked(
+            const std::vector<LayerRequirement>&, GlobalSignals) const REQUIRES(mLock);
 
     // Returns number of display frames and remainder when dividing the layer refresh period by
     // display refresh period.
@@ -429,60 +341,56 @@
 
     // Returns the lowest refresh rate according to the current policy. May change at runtime. Only
     // uses the primary range, not the app request range.
-    const RefreshRate& getMinRefreshRateByPolicyLocked() const REQUIRES(mLock);
+    const DisplayModePtr& getMinRefreshRateByPolicyLocked() const REQUIRES(mLock);
 
     // Returns the highest refresh rate according to the current policy. May change at runtime. Only
     // uses the primary range, not the app request range.
-    const RefreshRate& getMaxRefreshRateByPolicyLocked() const REQUIRES(mLock);
-
-    // Returns the current refresh rate, if allowed. Otherwise the default that is allowed by
-    // the policy.
-    const RefreshRate& getCurrentRefreshRateByPolicyLocked() const REQUIRES(mLock);
+    const DisplayModePtr& getMaxRefreshRateByPolicyLocked(int anchorGroup) const REQUIRES(mLock);
+    const DisplayModePtr& getMaxRefreshRateByPolicyLocked() const REQUIRES(mLock) {
+        return getMaxRefreshRateByPolicyLocked(mActiveModeIt->second->getGroup());
+    }
 
     const Policy* getCurrentPolicyLocked() const REQUIRES(mLock);
     bool isPolicyValidLocked(const Policy& policy) const REQUIRES(mLock);
 
     // Returns whether the layer is allowed to vote for the given refresh rate.
-    bool isVoteAllowed(const LayerRequirement&, const RefreshRate&) const;
+    bool isVoteAllowed(const LayerRequirement&, Fps) const;
 
     // 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&, const RefreshRate&,
+    float calculateLayerScoreLocked(const LayerRequirement&, Fps refreshRate,
                                     bool isSeamlessSwitch) const REQUIRES(mLock);
 
-    float calculateNonExactMatchingLayerScoreLocked(const LayerRequirement&,
-                                                    const RefreshRate&) const REQUIRES(mLock);
+    float calculateNonExactMatchingLayerScoreLocked(const LayerRequirement&, Fps refreshRate) const
+            REQUIRES(mLock);
 
-    void updateDisplayModes(const DisplayModes& mode, DisplayModeId currentModeId) EXCLUDES(mLock);
+    void updateDisplayModes(DisplayModes, DisplayModeId activeModeId) EXCLUDES(mLock);
 
     void initializeIdleTimer();
 
-    // The list of refresh rates, indexed by display modes ID. This may change after this
-    // object is initialized.
-    AllRefreshRatesMapType mRefreshRates GUARDED_BY(mLock);
+    std::optional<IdleTimerCallbacks::Callbacks> getIdleTimerCallbacks() const
+            REQUIRES(mIdleTimerCallbacksMutex) {
+        if (!mIdleTimerCallbacks) return {};
+        return mConfig.kernelIdleTimerController.has_value() ? mIdleTimerCallbacks->kernel
+                                                             : mIdleTimerCallbacks->platform;
+    }
 
-    // The list of refresh rates in the primary range of the current policy, ordered by vsyncPeriod
-    // (the first element is the lowest refresh rate).
-    std::vector<const RefreshRate*> mPrimaryRefreshRates GUARDED_BY(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);
 
-    // The list of refresh rates in the app request range of the current policy, ordered by
-    // vsyncPeriod (the first element is the lowest refresh rate).
-    std::vector<const RefreshRate*> mAppRequestRefreshRates GUARDED_BY(mLock);
+    DisplayModeIterator mActiveModeIt GUARDED_BY(mLock);
+    DisplayModeIterator mMinRefreshRateModeIt GUARDED_BY(mLock);
+    DisplayModeIterator mMaxRefreshRateModeIt GUARDED_BY(mLock);
 
-    // The current display mode. This will change at runtime. This is set by SurfaceFlinger on
-    // the main thread, and read by the Scheduler (and other objects) on other threads.
-    const RefreshRate* mCurrentRefreshRate 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);
 
-    // The policy values will change at runtime. They're set by SurfaceFlinger on the main thread,
-    // and read by the Scheduler (and other objects) on other threads.
     Policy mDisplayManagerPolicy GUARDED_BY(mLock);
     std::optional<Policy> mOverridePolicy GUARDED_BY(mLock);
 
-    // The min and max refresh rates supported by the device.
-    // This may change at runtime.
-    const RefreshRate* mMinSupportedRefreshRate GUARDED_BY(mLock);
-    const RefreshRate* mMaxSupportedRefreshRate GUARDED_BY(mLock);
-
     mutable std::mutex mLock;
 
     // A sorted list of known frame rates that a Heuristic layer will choose
@@ -490,32 +398,19 @@
     const std::vector<Fps> mKnownFrameRates;
 
     const Config mConfig;
-    bool mSupportsFrameRateOverride;
+    bool mSupportsFrameRateOverrideByContent;
 
-    struct GetBestRefreshRateInvocation {
-        std::vector<LayerRequirement> layerRequirements;
-        GlobalSignals globalSignals;
-        GlobalSignals outSignalsConsidered;
-        RefreshRate resultingBestRefreshRate;
+    struct GetBestRefreshRateCache {
+        std::pair<std::vector<LayerRequirement>, GlobalSignals> arguments;
+        std::pair<DisplayModePtr, GlobalSignals> result;
     };
-    mutable std::optional<GetBestRefreshRateInvocation> lastBestRefreshRateInvocation
-            GUARDED_BY(mLock);
+    mutable std::optional<GetBestRefreshRateCache> mGetBestRefreshRateCache GUARDED_BY(mLock);
 
-    // Timer that records time between requests for next vsync.
-    std::optional<scheduler::OneShotTimer> mIdleTimer;
-
-    struct IdleTimerCallbacks {
-        struct Callbacks {
-            std::function<void()> onReset;
-            std::function<void()> onExpired;
-        };
-
-        Callbacks platform;
-        Callbacks kernel;
-    };
-
+    // 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;
 };
 
 } // namespace android::scheduler
diff --git a/services/surfaceflinger/Scheduler/RefreshRateStats.h b/services/surfaceflinger/Scheduler/RefreshRateStats.h
index 208a767..ed65bc6 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateStats.h
+++ b/services/surfaceflinger/Scheduler/RefreshRateStats.h
@@ -16,15 +16,20 @@
 
 #pragma once
 
-#include <numeric>
+#include <chrono>
+#include <cinttypes>
+#include <cstdlib>
+#include <string>
 
-#include "Fps.h"
-#include "Scheduler/SchedulerUtils.h"
+#include <android-base/stringprintf.h>
+#include <ftl/small_map.h>
+#include <utils/Timers.h>
+
+#include <scheduler/Fps.h>
+
+#include "DisplayHardware/Hal.h"
 #include "TimeStats/TimeStats.h"
 
-#include "android-base/stringprintf.h"
-#include "utils/Timers.h"
-
 namespace android::scheduler {
 
 /**
@@ -39,15 +44,16 @@
     static constexpr int64_t MS_PER_HOUR = 60 * MS_PER_MIN;
     static constexpr int64_t MS_PER_DAY = 24 * MS_PER_HOUR;
 
+    using PowerMode = android::hardware::graphics::composer::hal::PowerMode;
+
 public:
-    RefreshRateStats(TimeStats& timeStats, Fps currentRefreshRate,
-                     android::hardware::graphics::composer::hal::PowerMode currentPowerMode)
+    // TODO(b/185535769): Inject clock to avoid sleeping in tests.
+    RefreshRateStats(TimeStats& timeStats, Fps currentRefreshRate, PowerMode currentPowerMode)
           : mTimeStats(timeStats),
             mCurrentRefreshRate(currentRefreshRate),
             mCurrentPowerMode(currentPowerMode) {}
 
-    // Sets power mode.
-    void setPowerMode(android::hardware::graphics::composer::hal::PowerMode mode) {
+    void setPowerMode(PowerMode mode) {
         if (mCurrentPowerMode == mode) {
             return;
         }
@@ -58,7 +64,7 @@
     // Sets config mode. If the mode has changed, it records how much time was spent in the previous
     // mode.
     void setRefreshRate(Fps currRefreshRate) {
-        if (mCurrentRefreshRate.equalsWithMargin(currRefreshRate)) {
+        if (isApproxEqual(mCurrentRefreshRate, currRefreshRate)) {
             return;
         }
         mTimeStats.incrementRefreshRateSwitches();
@@ -66,25 +72,26 @@
         mCurrentRefreshRate = currRefreshRate;
     }
 
-    // Returns a map between human readable refresh rate and number of seconds the device spent in
-    // that mode.
-    std::unordered_map<std::string, int64_t> getTotalTimes() {
+    // Maps stringified refresh rate to total time spent in that mode.
+    using TotalTimes = ftl::SmallMap<std::string, std::chrono::milliseconds, 3>;
+
+    TotalTimes getTotalTimes() {
         // If the power mode is on, then we are probably switching between the config modes. If
         // it's not then the screen is probably off. Make sure to flush times before printing
         // them.
         flushTime();
 
-        std::unordered_map<std::string, int64_t> totalTime;
-        // Multiple configs may map to the same name, e.g. "60fps". Add the
-        // times for such configs together.
-        for (const auto& [configId, time] : mConfigModesTotalTime) {
-            totalTime[to_string(configId)] = 0;
+        TotalTimes totalTimes = ftl::init::map("ScreenOff", mScreenOffTime);
+        const auto zero = std::chrono::milliseconds::zero();
+
+        // Sum the times for modes that map to the same name, e.g. "60 Hz".
+        for (const auto& [fps, time] : mFpsTotalTimes) {
+            const auto string = to_string(fps);
+            const auto total = std::as_const(totalTimes).get(string).value_or(std::cref(zero));
+            totalTimes.emplace_or_replace(string, total.get() + time);
         }
-        for (const auto& [configId, time] : mConfigModesTotalTime) {
-            totalTime[to_string(configId)] += time;
-        }
-        totalTime["ScreenOff"] = mScreenOffTime;
-        return totalTime;
+
+        return totalTimes;
     }
 
     // Traverses through the map of config modes and returns how long they've been running in easy
@@ -102,28 +109,32 @@
     // Calculates the time that passed in ms between the last time we recorded time and the time
     // this method was called.
     void flushTime() {
-        nsecs_t currentTime = systemTime();
-        nsecs_t timeElapsed = currentTime - mPreviousRecordedTime;
-        int64_t timeElapsedMs = ns2ms(timeElapsed);
+        const nsecs_t currentTime = systemTime();
+        const nsecs_t timeElapsed = currentTime - mPreviousRecordedTime;
         mPreviousRecordedTime = currentTime;
 
+        const auto duration = std::chrono::milliseconds{ns2ms(timeElapsed)};
+        const auto zero = std::chrono::milliseconds::zero();
+
         uint32_t fps = 0;
-        if (mCurrentPowerMode == android::hardware::graphics::composer::hal::PowerMode::ON) {
+
+        if (mCurrentPowerMode == PowerMode::ON) {
             // Normal power mode is counted under different config modes.
-            if (mConfigModesTotalTime.find(mCurrentRefreshRate) == mConfigModesTotalTime.end()) {
-                mConfigModesTotalTime[mCurrentRefreshRate] = 0;
-            }
-            mConfigModesTotalTime[mCurrentRefreshRate] += timeElapsedMs;
+            const auto total = std::as_const(mFpsTotalTimes)
+                                       .get(mCurrentRefreshRate)
+                                       .value_or(std::cref(zero));
+            mFpsTotalTimes.emplace_or_replace(mCurrentRefreshRate, total.get() + duration);
+
             fps = static_cast<uint32_t>(mCurrentRefreshRate.getIntValue());
         } else {
-            mScreenOffTime += timeElapsedMs;
+            mScreenOffTime += duration;
         }
         mTimeStats.recordRefreshRate(fps, timeElapsed);
     }
 
     // Formats the time in milliseconds into easy to read format.
-    static std::string getDateFormatFromMs(int64_t timeMs) {
-        auto [days, dayRemainderMs] = std::div(timeMs, MS_PER_DAY);
+    static std::string getDateFormatFromMs(std::chrono::milliseconds time) {
+        auto [days, dayRemainderMs] = std::div(static_cast<int64_t>(time.count()), MS_PER_DAY);
         auto [hours, hourRemainderMs] = std::div(dayRemainderMs, MS_PER_HOUR);
         auto [mins, minsRemainderMs] = std::div(hourRemainderMs, MS_PER_MIN);
         auto [sec, secRemainderMs] = std::div(minsRemainderMs, MS_PER_S);
@@ -136,11 +147,10 @@
     TimeStats& mTimeStats;
 
     Fps mCurrentRefreshRate;
-    android::hardware::graphics::composer::hal::PowerMode mCurrentPowerMode;
+    PowerMode mCurrentPowerMode;
 
-    std::unordered_map<Fps, int64_t /* duration in ms */, std::hash<Fps>, Fps::EqualsInBuckets>
-            mConfigModesTotalTime;
-    int64_t mScreenOffTime = 0;
+    ftl::SmallMap<Fps, std::chrono::milliseconds, 2, FpsApproxEqual> mFpsTotalTimes;
+    std::chrono::milliseconds mScreenOffTime = std::chrono::milliseconds::zero();
 
     nsecs_t mPreviousRecordedTime = systemTime();
 };
diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp
index 1989d57..3aa0a5f 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.cpp
+++ b/services/surfaceflinger/Scheduler/Scheduler.cpp
@@ -42,15 +42,12 @@
 #include "../Layer.h"
 #include "DispSyncSource.h"
 #include "EventThread.h"
+#include "FrameRateOverrideMappings.h"
 #include "InjectVSyncSource.h"
 #include "OneShotTimer.h"
-#include "SchedulerUtils.h"
 #include "SurfaceFlingerProperties.h"
-#include "Timer.h"
-#include "VSyncDispatchTimerQueue.h"
 #include "VSyncPredictor.h"
 #include "VSyncReactor.h"
-#include "VsyncController.h"
 
 #define RETURN_IF_INVALID_HANDLE(handle, ...)                        \
     do {                                                             \
@@ -60,74 +57,23 @@
         }                                                            \
     } while (false)
 
-using namespace std::string_literals;
+namespace android::scheduler {
 
-namespace android {
+Scheduler::Scheduler(ICompositor& compositor, ISchedulerCallback& callback, FeatureFlags features)
+      : impl::MessageQueue(compositor), mFeatures(features), mSchedulerCallback(callback) {}
 
-using gui::WindowInfo;
+Scheduler::~Scheduler() {
+    // Stop timers and wait for their threads to exit.
+    mDisplayPowerTimer.reset();
+    mTouchTimer.reset();
 
-namespace {
-
-std::unique_ptr<scheduler::VSyncTracker> createVSyncTracker() {
-    // TODO(b/144707443): Tune constants.
-    constexpr int kDefaultRate = 60;
-    constexpr auto initialPeriod = std::chrono::duration<nsecs_t, std::ratio<1, kDefaultRate>>(1);
-    constexpr nsecs_t idealPeriod =
-            std::chrono::duration_cast<std::chrono::nanoseconds>(initialPeriod).count();
-    constexpr size_t vsyncTimestampHistorySize = 20;
-    constexpr size_t minimumSamplesForPrediction = 6;
-    constexpr uint32_t discardOutlierPercent = 20;
-    return std::make_unique<scheduler::VSyncPredictor>(idealPeriod, vsyncTimestampHistorySize,
-                                                       minimumSamplesForPrediction,
-                                                       discardOutlierPercent);
+    // Stop idle timer and clear callbacks, as the RefreshRateConfigs may outlive the Scheduler.
+    setRefreshRateConfigs(nullptr);
 }
 
-std::unique_ptr<scheduler::VSyncDispatch> createVSyncDispatch(scheduler::VSyncTracker& tracker) {
-    // TODO(b/144707443): Tune constants.
-    constexpr std::chrono::nanoseconds vsyncMoveThreshold = 3ms;
-    constexpr std::chrono::nanoseconds timerSlack = 500us;
-    return std::make_unique<
-            scheduler::VSyncDispatchTimerQueue>(std::make_unique<scheduler::Timer>(), tracker,
-                                                timerSlack.count(), vsyncMoveThreshold.count());
-}
-
-const char* toContentDetectionString(bool useContentDetection) {
-    return useContentDetection ? "on" : "off";
-}
-
-} // namespace
-
-class PredictedVsyncTracer {
-public:
-    PredictedVsyncTracer(scheduler::VSyncDispatch& dispatch)
-          : mRegistration(dispatch, std::bind(&PredictedVsyncTracer::callback, this),
-                          "PredictedVsyncTracer") {
-        scheduleRegistration();
-    }
-
-private:
-    TracedOrdinal<bool> mParity = {"VSYNC-predicted", 0};
-    scheduler::VSyncCallbackRegistration mRegistration;
-
-    void scheduleRegistration() { mRegistration.schedule({0, 0, 0}); }
-
-    void callback() {
-        mParity = !mParity;
-        scheduleRegistration();
-    }
-};
-
-Scheduler::Scheduler(const std::shared_ptr<scheduler::RefreshRateConfigs>& configs,
-                     ISchedulerCallback& callback)
-      : Scheduler(configs, callback,
-                  {.useContentDetection = sysprop::use_content_detection_for_refresh_rate(false)}) {
-}
-
-Scheduler::Scheduler(const std::shared_ptr<scheduler::RefreshRateConfigs>& configs,
-                     ISchedulerCallback& callback, Options options)
-      : Scheduler(createVsyncSchedule(configs->supportsKernelIdleTimer()), configs, callback,
-                  createLayerHistory(), options) {
+void Scheduler::startTimers() {
     using namespace sysprop;
+    using namespace std::string_literals;
 
     if (const int64_t millis = set_touch_timer_ms(0); millis > 0) {
         // Touch events are coming to SF every 100ms, so the timer needs to be higher than that
@@ -147,77 +93,58 @@
     }
 }
 
-Scheduler::Scheduler(VsyncSchedule schedule,
-                     const std::shared_ptr<scheduler::RefreshRateConfigs>& configs,
-                     ISchedulerCallback& schedulerCallback,
-                     std::unique_ptr<LayerHistory> layerHistory, Options options)
-      : mOptions(options),
-        mVsyncSchedule(std::move(schedule)),
-        mLayerHistory(std::move(layerHistory)),
-        mSchedulerCallback(schedulerCallback),
-        mPredictedVsyncTracer(
-                base::GetBoolProperty("debug.sf.show_predicted_vsync", false)
-                        ? std::make_unique<PredictedVsyncTracer>(*mVsyncSchedule.dispatch)
-                        : nullptr) {
-    setRefreshRateConfigs(configs);
-    mSchedulerCallback.setVsyncEnabled(false);
+void Scheduler::setRefreshRateConfigs(std::shared_ptr<RefreshRateConfigs> configs) {
+    {
+        // The current RefreshRateConfigs instance may outlive this call, so unbind its idle timer.
+        std::scoped_lock lock(mRefreshRateConfigsLock);
+        if (mRefreshRateConfigs) {
+            mRefreshRateConfigs->stopIdleTimer();
+            mRefreshRateConfigs->clearIdleTimerCallbacks();
+        }
+    }
+    {
+        // Clear state that depends on the current instance.
+        std::scoped_lock lock(mPolicyLock);
+        mPolicy = {};
+    }
+
+    std::scoped_lock lock(mRefreshRateConfigsLock);
+    mRefreshRateConfigs = std::move(configs);
+    if (!mRefreshRateConfigs) return;
+
+    mRefreshRateConfigs->setIdleTimerCallbacks(
+            {.platform = {.onReset = [this] { idleTimerCallback(TimerState::Reset); },
+                          .onExpired = [this] { idleTimerCallback(TimerState::Expired); }},
+             .kernel = {.onReset = [this] { kernelIdleTimerCallback(TimerState::Reset); },
+                        .onExpired = [this] { kernelIdleTimerCallback(TimerState::Expired); }}});
+
+    mRefreshRateConfigs->startIdleTimer();
 }
 
-Scheduler::~Scheduler() {
-    // Ensure the OneShotTimer threads are joined before we start destroying state.
-    mDisplayPowerTimer.reset();
-    mTouchTimer.reset();
-    mRefreshRateConfigs.reset();
+void Scheduler::run() {
+    while (true) {
+        waitMessage();
+    }
 }
 
-Scheduler::VsyncSchedule Scheduler::createVsyncSchedule(bool supportKernelTimer) {
-    auto clock = std::make_unique<scheduler::SystemClock>();
-    auto tracker = createVSyncTracker();
-    auto dispatch = createVSyncDispatch(*tracker);
-
-    // TODO(b/144707443): Tune constants.
-    constexpr size_t pendingFenceLimit = 20;
-    auto controller =
-            std::make_unique<scheduler::VSyncReactor>(std::move(clock), *tracker, pendingFenceLimit,
-                                                      supportKernelTimer);
-    return {std::move(controller), std::move(tracker), std::move(dispatch)};
-}
-
-std::unique_ptr<LayerHistory> Scheduler::createLayerHistory() {
-    return std::make_unique<scheduler::LayerHistory>();
+void Scheduler::createVsyncSchedule(FeatureFlags features) {
+    mVsyncSchedule.emplace(features);
 }
 
 std::unique_ptr<VSyncSource> Scheduler::makePrimaryDispSyncSource(
         const char* name, std::chrono::nanoseconds workDuration,
         std::chrono::nanoseconds readyDuration, bool traceVsync) {
-    return std::make_unique<scheduler::DispSyncSource>(*mVsyncSchedule.dispatch, workDuration,
+    return std::make_unique<scheduler::DispSyncSource>(mVsyncSchedule->getDispatch(),
+                                                       mVsyncSchedule->getTracker(), workDuration,
                                                        readyDuration, traceVsync, name);
 }
 
 std::optional<Fps> Scheduler::getFrameRateOverride(uid_t uid) const {
-    {
-        std::scoped_lock lock(mRefreshRateConfigsLock);
-        if (!mRefreshRateConfigs->supportsFrameRateOverride()) {
-            return std::nullopt;
-        }
-    }
-
-    std::lock_guard lock(mFrameRateOverridesLock);
-    {
-        const auto iter = mFrameRateOverridesFromBackdoor.find(uid);
-        if (iter != mFrameRateOverridesFromBackdoor.end()) {
-            return std::make_optional<Fps>(iter->second);
-        }
-    }
-
-    {
-        const auto iter = mFrameRateOverridesByContent.find(uid);
-        if (iter != mFrameRateOverridesByContent.end()) {
-            return std::make_optional<Fps>(iter->second);
-        }
-    }
-
-    return std::nullopt;
+    const auto refreshRateConfigs = holdRefreshRateConfigs();
+    const bool supportsFrameRateOverrideByContent =
+            refreshRateConfigs->supportsFrameRateOverrideByContent();
+    return mFrameRateOverrideMappings
+            .getFrameRateOverrideForUid(uid, supportsFrameRateOverrideByContent);
 }
 
 bool Scheduler::isVsyncValid(nsecs_t expectedVsyncTimestamp, uid_t uid) const {
@@ -226,14 +153,11 @@
         return true;
     }
 
-    return mVsyncSchedule.tracker->isVSyncInPhase(expectedVsyncTimestamp, *frameRate);
+    return mVsyncSchedule->getTracker().isVSyncInPhase(expectedVsyncTimestamp, *frameRate);
 }
 
 impl::EventThread::ThrottleVsyncCallback Scheduler::makeThrottleVsyncCallback() const {
     std::scoped_lock lock(mRefreshRateConfigsLock);
-    if (!mRefreshRateConfigs->supportsFrameRateOverride()) {
-        return {};
-    }
 
     return [this](nsecs_t expectedVsyncTimestamp, uid_t uid) {
         return !isVsyncValid(expectedVsyncTimestamp, uid);
@@ -242,26 +166,23 @@
 
 impl::EventThread::GetVsyncPeriodFunction Scheduler::makeGetVsyncPeriodFunction() const {
     return [this](uid_t uid) {
-        const auto refreshRateConfigs = holdRefreshRateConfigs();
-        nsecs_t basePeriod = refreshRateConfigs->getCurrentRefreshRate().getVsyncPeriod();
+        const Fps refreshRate = holdRefreshRateConfigs()->getActiveMode()->getFps();
+        const nsecs_t basePeriod = refreshRate.getPeriodNsecs();
+
         const auto frameRate = getFrameRateOverride(uid);
         if (!frameRate.has_value()) {
             return basePeriod;
         }
 
-        const auto divider =
-                scheduler::RefreshRateConfigs::getFrameRateDivider(refreshRateConfigs
-                                                                           ->getCurrentRefreshRate()
-                                                                           .getFps(),
-                                                                   *frameRate);
-        if (divider <= 1) {
+        const auto divisor = RefreshRateConfigs::getFrameRateDivisor(refreshRate, *frameRate);
+        if (divisor <= 1) {
             return basePeriod;
         }
-        return basePeriod * divider;
+        return basePeriod * divisor;
     };
 }
 
-Scheduler::ConnectionHandle Scheduler::createConnection(
+ConnectionHandle Scheduler::createConnection(
         const char* connectionName, frametimeline::TokenManager* tokenManager,
         std::chrono::nanoseconds workDuration, std::chrono::nanoseconds readyDuration,
         impl::EventThread::InterceptVSyncsCallback interceptCallback) {
@@ -275,7 +196,7 @@
     return createConnection(std::move(eventThread));
 }
 
-Scheduler::ConnectionHandle Scheduler::createConnection(std::unique_ptr<EventThread> eventThread) {
+ConnectionHandle Scheduler::createConnection(std::unique_ptr<EventThread> eventThread) {
     const ConnectionHandle handle = ConnectionHandle{mNextConnectionHandleId++};
     ALOGV("Creating a connection handle with ID %" PRIuPTR, handle.id);
 
@@ -339,18 +260,13 @@
 }
 
 void Scheduler::onFrameRateOverridesChanged(ConnectionHandle handle, PhysicalDisplayId displayId) {
-    std::vector<FrameRateOverride> overrides;
-    {
-        std::lock_guard lock(mFrameRateOverridesLock);
-        for (const auto& [uid, frameRate] : mFrameRateOverridesFromBackdoor) {
-            overrides.emplace_back(FrameRateOverride{uid, frameRate.getValue()});
-        }
-        for (const auto& [uid, frameRate] : mFrameRateOverridesByContent) {
-            if (mFrameRateOverridesFromBackdoor.count(uid) == 0) {
-                overrides.emplace_back(FrameRateOverride{uid, frameRate.getValue()});
-            }
-        }
-    }
+    const auto refreshRateConfigs = holdRefreshRateConfigs();
+    const bool supportsFrameRateOverrideByContent =
+            refreshRateConfigs->supportsFrameRateOverrideByContent();
+
+    std::vector<FrameRateOverride> overrides =
+            mFrameRateOverrideMappings.getAllFrameRateOverrides(supportsFrameRateOverrideByContent);
+
     android::EventThread* thread;
     {
         std::lock_guard lock(mConnectionsLock);
@@ -362,24 +278,24 @@
 
 void Scheduler::onPrimaryDisplayModeChanged(ConnectionHandle handle, DisplayModePtr mode) {
     {
-        std::lock_guard<std::mutex> lock(mFeatureStateLock);
+        std::lock_guard<std::mutex> lock(mPolicyLock);
         // Cache the last reported modes for primary display.
-        mFeatures.cachedModeChangedParams = {handle, mode};
+        mPolicy.cachedModeChangedParams = {handle, mode};
 
         // Invalidate content based refresh rate selection so it could be calculated
         // again for the new refresh rate.
-        mFeatures.contentRequirements.clear();
+        mPolicy.contentRequirements.clear();
     }
     onNonPrimaryDisplayModeChanged(handle, mode);
 }
 
 void Scheduler::dispatchCachedReportedMode() {
     // Check optional fields first.
-    if (!mFeatures.mode) {
+    if (!mPolicy.mode) {
         ALOGW("No mode ID found, not dispatching cached mode.");
         return;
     }
-    if (!mFeatures.cachedModeChangedParams.has_value()) {
+    if (!mPolicy.cachedModeChangedParams) {
         ALOGW("No mode changed params found, not dispatching cached mode.");
         return;
     }
@@ -388,18 +304,18 @@
     // 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->getCurrentRefreshRate().getMode() != mFeatures.mode) {
+        mRefreshRateConfigs->getActiveMode() != mPolicy.mode) {
         return;
     }
 
     // If there is no change from cached mode, there is no need to dispatch an event
-    if (mFeatures.mode == mFeatures.cachedModeChangedParams->mode) {
+    if (mPolicy.mode == mPolicy.cachedModeChangedParams->mode) {
         return;
     }
 
-    mFeatures.cachedModeChangedParams->mode = mFeatures.mode;
-    onNonPrimaryDisplayModeChanged(mFeatures.cachedModeChangedParams->handle,
-                                   mFeatures.cachedModeChangedParams->mode);
+    mPolicy.cachedModeChangedParams->mode = mPolicy.mode;
+    onNonPrimaryDisplayModeChanged(mPolicy.cachedModeChangedParams->handle,
+                                   mPolicy.cachedModeChangedParams->mode);
 }
 
 void Scheduler::onNonPrimaryDisplayModeChanged(ConnectionHandle handle, DisplayModePtr mode) {
@@ -440,12 +356,12 @@
 }
 
 DisplayStatInfo Scheduler::getDisplayStatInfo(nsecs_t now) {
-    const auto vsyncTime = mVsyncSchedule.tracker->nextAnticipatedVSyncTimeFrom(now);
-    const auto vsyncPeriod = mVsyncSchedule.tracker->currentPeriod();
+    const auto vsyncTime = mVsyncSchedule->getTracker().nextAnticipatedVSyncTimeFrom(now);
+    const auto vsyncPeriod = mVsyncSchedule->getTracker().currentPeriod();
     return DisplayStatInfo{.vsyncTime = vsyncTime, .vsyncPeriod = vsyncPeriod};
 }
 
-Scheduler::ConnectionHandle Scheduler::enableVSyncInjection(bool enable) {
+ConnectionHandle Scheduler::enableVSyncInjection(bool enable) {
     if (mInjectVSyncs == enable) {
         return {};
     }
@@ -486,7 +402,7 @@
 void Scheduler::enableHardwareVsync() {
     std::lock_guard<std::mutex> lock(mHWVsyncLock);
     if (!mPrimaryHWVsyncEnabled && mHWVsyncAvailable) {
-        mVsyncSchedule.tracker->resetModel();
+        mVsyncSchedule->getTracker().resetModel();
         mSchedulerCallback.setVsyncEnabled(true);
         mPrimaryHWVsyncEnabled = true;
     }
@@ -503,7 +419,7 @@
     }
 }
 
-void Scheduler::resyncToHardwareVsync(bool makeAvailable, nsecs_t period) {
+void Scheduler::resyncToHardwareVsync(bool makeAvailable, Fps refreshRate) {
     {
         std::lock_guard<std::mutex> lock(mHWVsyncLock);
         if (makeAvailable) {
@@ -515,11 +431,7 @@
         }
     }
 
-    if (period <= 0) {
-        return;
-    }
-
-    setVsyncPeriod(period);
+    setVsyncPeriod(refreshRate.getPeriodNsecs());
 }
 
 void Scheduler::resync() {
@@ -529,20 +441,22 @@
     const nsecs_t last = mLastResyncTime.exchange(now);
 
     if (now - last > kIgnoreDelay) {
-        const auto vsyncPeriod = [&] {
+        const auto refreshRate = [&] {
             std::scoped_lock lock(mRefreshRateConfigsLock);
-            return mRefreshRateConfigs->getCurrentRefreshRate().getVsyncPeriod();
+            return mRefreshRateConfigs->getActiveMode()->getFps();
         }();
-        resyncToHardwareVsync(false, vsyncPeriod);
+        resyncToHardwareVsync(false, refreshRate);
     }
 }
 
 void Scheduler::setVsyncPeriod(nsecs_t period) {
+    if (period <= 0) return;
+
     std::lock_guard<std::mutex> lock(mHWVsyncLock);
-    mVsyncSchedule.controller->startPeriodTransition(period);
+    mVsyncSchedule->getController().startPeriodTransition(period);
 
     if (!mPrimaryHWVsyncEnabled) {
-        mVsyncSchedule.tracker->resetModel();
+        mVsyncSchedule->getTracker().resetModel();
         mSchedulerCallback.setVsyncEnabled(true);
         mPrimaryHWVsyncEnabled = true;
     }
@@ -555,8 +469,9 @@
     { // Scope for the lock
         std::lock_guard<std::mutex> lock(mHWVsyncLock);
         if (mPrimaryHWVsyncEnabled) {
-            needsHwVsync = mVsyncSchedule.controller->addHwVsyncTimestamp(timestamp, hwcVsyncPeriod,
-                                                                          periodFlushed);
+            needsHwVsync =
+                    mVsyncSchedule->getController().addHwVsyncTimestamp(timestamp, hwcVsyncPeriod,
+                                                                        periodFlushed);
         }
     }
 
@@ -567,24 +482,23 @@
     }
 }
 
-void Scheduler::addPresentFence(const std::shared_ptr<FenceTime>& fenceTime) {
-    if (mVsyncSchedule.controller->addPresentFence(fenceTime)) {
+void Scheduler::addPresentFence(std::shared_ptr<FenceTime> fence) {
+    if (mVsyncSchedule->getController().addPresentFence(std::move(fence))) {
         enableHardwareVsync();
     } else {
         disableHardwareVsync(false);
     }
 }
 
-void Scheduler::setIgnorePresentFences(bool ignore) {
-    mVsyncSchedule.controller->setIgnorePresentFences(ignore);
-}
-
 void Scheduler::registerLayer(Layer* layer) {
+    using WindowType = gui::WindowInfo::Type;
+
     scheduler::LayerHistory::LayerVoteType voteType;
 
-    if (!mOptions.useContentDetection || layer->getWindowType() == WindowInfo::Type::STATUS_BAR) {
+    if (!mFeatures.test(Feature::kContentDetection) ||
+        layer->getWindowType() == WindowType::STATUS_BAR) {
         voteType = scheduler::LayerHistory::LayerVoteType::NoVote;
-    } else if (layer->getWindowType() == WindowInfo::Type::WALLPAPER) {
+    } else if (layer->getWindowType() == WindowType::WALLPAPER) {
         // Running Wallpaper at Min is considered as part of content detection.
         voteType = scheduler::LayerHistory::LayerVoteType::Min;
     } else {
@@ -594,11 +508,11 @@
     // If the content detection feature is off, we still keep the layer history,
     // since we use it for other features (like Frame Rate API), so layers
     // still need to be registered.
-    mLayerHistory->registerLayer(layer, voteType);
+    mLayerHistory.registerLayer(layer, voteType);
 }
 
 void Scheduler::deregisterLayer(Layer* layer) {
-    mLayerHistory->deregisterLayer(layer);
+    mLayerHistory.deregisterLayer(layer);
 }
 
 void Scheduler::recordLayerHistory(Layer* layer, nsecs_t presentTime,
@@ -608,56 +522,21 @@
         if (!mRefreshRateConfigs->canSwitch()) return;
     }
 
-    mLayerHistory->record(layer, presentTime, systemTime(), updateType);
+    mLayerHistory.record(layer, presentTime, systemTime(), updateType);
 }
 
 void Scheduler::setModeChangePending(bool pending) {
-    mLayerHistory->setModeChangePending(pending);
+    mLayerHistory.setModeChangePending(pending);
 }
 
 void Scheduler::chooseRefreshRateForContent() {
-    {
-        std::scoped_lock lock(mRefreshRateConfigsLock);
-        if (!mRefreshRateConfigs->canSwitch()) return;
-    }
+    const auto configs = holdRefreshRateConfigs();
+    if (!configs->canSwitch()) return;
 
     ATRACE_CALL();
 
-    const auto refreshRateConfigs = holdRefreshRateConfigs();
-    scheduler::LayerHistory::Summary summary =
-            mLayerHistory->summarize(*refreshRateConfigs, systemTime());
-    scheduler::RefreshRateConfigs::GlobalSignals consideredSignals;
-    DisplayModePtr newMode;
-    bool frameRateChanged;
-    bool frameRateOverridesChanged;
-    {
-        std::lock_guard<std::mutex> lock(mFeatureStateLock);
-        mFeatures.contentRequirements = summary;
-
-        newMode = calculateRefreshRateModeId(&consideredSignals);
-        frameRateOverridesChanged = updateFrameRateOverrides(consideredSignals, newMode->getFps());
-
-        if (mFeatures.mode == newMode) {
-            // 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 due to a previous idleConsidered
-            if (!consideredSignals.idle) {
-                dispatchCachedReportedMode();
-            }
-            frameRateChanged = false;
-        } else {
-            mFeatures.mode = newMode;
-            frameRateChanged = true;
-        }
-    }
-    if (frameRateChanged) {
-        auto newRefreshRate = refreshRateConfigs->getRefreshRateFromModeId(newMode->getId());
-        mSchedulerCallback.changeRefreshRate(newRefreshRate,
-                                             consideredSignals.idle ? ModeEvent::None
-                                                                    : ModeEvent::Changed);
-    }
-    if (frameRateOverridesChanged) {
-        mSchedulerCallback.triggerOnFrameRateOverridesChanged();
-    }
+    LayerHistory::Summary summary = mLayerHistory.summarize(*configs, systemTime());
+    applyPolicy(&Policy::contentRequirements, std::move(summary));
 }
 
 void Scheduler::resetIdleTimer() {
@@ -665,7 +544,7 @@
     mRefreshRateConfigs->resetIdleTimer(/*kernelOnly*/ false);
 }
 
-void Scheduler::notifyTouchEvent() {
+void Scheduler::onTouchHint() {
     if (mTouchTimer) {
         mTouchTimer->reset();
 
@@ -676,8 +555,8 @@
 
 void Scheduler::setDisplayPowerState(bool normal) {
     {
-        std::lock_guard<std::mutex> lock(mFeatureStateLock);
-        mFeatures.isDisplayPowerStateNormal = normal;
+        std::lock_guard<std::mutex> lock(mPolicyLock);
+        mPolicy.isDisplayPowerStateNormal = normal;
     }
 
     if (mDisplayPowerTimer) {
@@ -686,7 +565,7 @@
 
     // Display Power event will boost the refresh rate to performance.
     // Clear Layer History to get fresh FPS detection
-    mLayerHistory->clear();
+    mLayerHistory.clear();
 }
 
 void Scheduler::kernelIdleTimerCallback(TimerState state) {
@@ -694,20 +573,20 @@
 
     // TODO(145561154): cleanup the kernel idle timer implementation and the refresh rate
     // magic number
-    const auto refreshRate = [&] {
+    const Fps refreshRate = [&] {
         std::scoped_lock lock(mRefreshRateConfigsLock);
-        return mRefreshRateConfigs->getCurrentRefreshRate();
+        return mRefreshRateConfigs->getActiveMode()->getFps();
     }();
 
-    constexpr Fps FPS_THRESHOLD_FOR_KERNEL_TIMER{65.0f};
-    if (state == TimerState::Reset &&
-        refreshRate.getFps().greaterThanWithMargin(FPS_THRESHOLD_FOR_KERNEL_TIMER)) {
+    constexpr Fps FPS_THRESHOLD_FOR_KERNEL_TIMER = 65_Hz;
+    using namespace fps_approx_ops;
+
+    if (state == TimerState::Reset && refreshRate > FPS_THRESHOLD_FOR_KERNEL_TIMER) {
         // If we're not in performance mode then the kernel timer shouldn't do
         // anything, as the refresh rate during DPU power collapse will be the
         // same.
-        resyncToHardwareVsync(true /* makeAvailable */, refreshRate.getVsyncPeriod());
-    } else if (state == TimerState::Expired &&
-               refreshRate.getFps().lessThanOrEqualWithMargin(FPS_THRESHOLD_FOR_KERNEL_TIMER)) {
+        resyncToHardwareVsync(true /* makeAvailable */, refreshRate);
+    } else if (state == TimerState::Expired && refreshRate <= FPS_THRESHOLD_FOR_KERNEL_TIMER) {
         // Disable HW VSYNC if the timer expired, as we don't need it enabled if
         // we're not pushing frames, and if we're in PERFORMANCE mode then we'll
         // need to update the VsyncController model anyway.
@@ -718,7 +597,7 @@
 }
 
 void Scheduler::idleTimerCallback(TimerState state) {
-    handleTimerStateChanged(&mFeatures.idleTimer, state);
+    applyPolicy(&Policy::idleTimer, state);
     ATRACE_INT("ExpiredIdleTimer", static_cast<int>(state));
 }
 
@@ -728,14 +607,14 @@
     // Clear layer history to get fresh FPS detection.
     // NOTE: Instead of checking all the layers, we should be checking the layer
     // that is currently on top. b/142507166 will give us this capability.
-    if (handleTimerStateChanged(&mFeatures.touch, touch)) {
-        mLayerHistory->clear();
+    if (applyPolicy(&Policy::touch, touch).touch) {
+        mLayerHistory.clear();
     }
     ATRACE_INT("TouchState", static_cast<int>(touch));
 }
 
 void Scheduler::displayPowerTimerCallback(TimerState state) {
-    handleTimerStateChanged(&mFeatures.displayPowerTimer, state);
+    applyPolicy(&Policy::displayPowerTimer, state);
     ATRACE_INT("ExpiredDisplayPowerTimer", static_cast<int>(state));
 }
 
@@ -745,23 +624,10 @@
     StringAppendF(&result, "+  Touch timer: %s\n",
                   mTouchTimer ? mTouchTimer->dump().c_str() : "off");
     StringAppendF(&result, "+  Content detection: %s %s\n\n",
-                  toContentDetectionString(mOptions.useContentDetection),
-                  mLayerHistory ? mLayerHistory->dump().c_str() : "(no layer history)");
+                  mFeatures.test(Feature::kContentDetection) ? "on" : "off",
+                  mLayerHistory.dump().c_str());
 
-    {
-        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");
-
-        StringAppendF(&result, "Frame Rate Overrides (setFrameRate): {");
-        for (const auto& [uid, frameRate] : mFrameRateOverridesByContent) {
-            StringAppendF(&result, "[uid: %d frameRate: %s], ", uid, to_string(frameRate).c_str());
-        }
-        StringAppendF(&result, "}\n");
-    }
+    mFrameRateOverrideMappings.dump(result);
 
     {
         std::lock_guard lock(mHWVsyncLock);
@@ -771,117 +637,95 @@
     }
 }
 
-void Scheduler::dumpVsync(std::string& s) const {
-    using base::StringAppendF;
-
-    StringAppendF(&s, "VSyncReactor:\n");
-    mVsyncSchedule.controller->dump(s);
-    StringAppendF(&s, "VSyncDispatch:\n");
-    mVsyncSchedule.dispatch->dump(s);
+void Scheduler::dumpVsync(std::string& out) const {
+    mVsyncSchedule->dump(out);
 }
 
-bool Scheduler::updateFrameRateOverrides(
-        scheduler::RefreshRateConfigs::GlobalSignals consideredSignals, Fps displayRefreshRate) {
+bool Scheduler::updateFrameRateOverrides(GlobalSignals consideredSignals, Fps displayRefreshRate) {
     const auto refreshRateConfigs = holdRefreshRateConfigs();
-    if (!refreshRateConfigs->supportsFrameRateOverride()) {
-        return false;
-    }
 
+    // we always update mFrameRateOverridesByContent here
+    // supportsFrameRateOverridesByContent will be checked
+    // when getting FrameRateOverrides from mFrameRateOverrideMappings
     if (!consideredSignals.idle) {
         const auto frameRateOverrides =
-                refreshRateConfigs->getFrameRateOverrides(mFeatures.contentRequirements,
-                                                          displayRefreshRate,
-                                                          consideredSignals.touch);
-        std::lock_guard lock(mFrameRateOverridesLock);
-        if (!std::equal(mFrameRateOverridesByContent.begin(), mFrameRateOverridesByContent.end(),
-                        frameRateOverrides.begin(), frameRateOverrides.end(),
-                        [](const std::pair<uid_t, Fps>& a, const std::pair<uid_t, Fps>& b) {
-                            return a.first == b.first && a.second.equalsWithMargin(b.second);
-                        })) {
-            mFrameRateOverridesByContent = frameRateOverrides;
-            return true;
-        }
+                refreshRateConfigs->getFrameRateOverrides(mPolicy.contentRequirements,
+                                                          displayRefreshRate, consideredSignals);
+        return mFrameRateOverrideMappings.updateFrameRateOverridesByContent(frameRateOverrides);
     }
     return false;
 }
 
-template <class T>
-bool Scheduler::handleTimerStateChanged(T* currentState, T newState) {
+template <typename S, typename T>
+auto Scheduler::applyPolicy(S Policy::*statePtr, T&& newState) -> GlobalSignals {
     DisplayModePtr newMode;
+    GlobalSignals consideredSignals;
+
     bool refreshRateChanged = false;
     bool frameRateOverridesChanged;
-    scheduler::RefreshRateConfigs::GlobalSignals consideredSignals;
+
     const auto refreshRateConfigs = holdRefreshRateConfigs();
     {
-        std::lock_guard<std::mutex> lock(mFeatureStateLock);
-        if (*currentState == newState) {
-            return false;
-        }
-        *currentState = newState;
-        newMode = calculateRefreshRateModeId(&consideredSignals);
+        std::lock_guard<std::mutex> lock(mPolicyLock);
+
+        auto& currentState = mPolicy.*statePtr;
+        if (currentState == newState) return {};
+        currentState = std::forward<T>(newState);
+
+        std::tie(newMode, consideredSignals) = chooseDisplayMode();
         frameRateOverridesChanged = updateFrameRateOverrides(consideredSignals, newMode->getFps());
-        if (mFeatures.mode == newMode) {
+
+        if (mPolicy.mode == newMode) {
             // 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 due to a previous idleConsidered
+            // about a mode change, since it was suppressed if previously considered idle.
             if (!consideredSignals.idle) {
                 dispatchCachedReportedMode();
             }
         } else {
-            mFeatures.mode = newMode;
+            mPolicy.mode = newMode;
             refreshRateChanged = true;
         }
     }
     if (refreshRateChanged) {
-        const RefreshRate& newRefreshRate =
-                refreshRateConfigs->getRefreshRateFromModeId(newMode->getId());
-
-        mSchedulerCallback.changeRefreshRate(newRefreshRate,
-                                             consideredSignals.idle ? ModeEvent::None
-                                                                    : ModeEvent::Changed);
+        mSchedulerCallback.requestDisplayMode(std::move(newMode),
+                                              consideredSignals.idle ? DisplayModeEvent::None
+                                                                     : DisplayModeEvent::Changed);
     }
     if (frameRateOverridesChanged) {
         mSchedulerCallback.triggerOnFrameRateOverridesChanged();
     }
-    return consideredSignals.touch;
+    return consideredSignals;
 }
 
-DisplayModePtr Scheduler::calculateRefreshRateModeId(
-        scheduler::RefreshRateConfigs::GlobalSignals* consideredSignals) {
+auto Scheduler::chooseDisplayMode() -> std::pair<DisplayModePtr, GlobalSignals> {
     ATRACE_CALL();
-    if (consideredSignals) *consideredSignals = {};
 
-    const auto refreshRateConfigs = holdRefreshRateConfigs();
+    const auto configs = holdRefreshRateConfigs();
+
     // If Display Power is not in normal operation we want to be in performance mode. When coming
     // back to normal mode, a grace period is given with DisplayPowerTimer.
     if (mDisplayPowerTimer &&
-        (!mFeatures.isDisplayPowerStateNormal ||
-         mFeatures.displayPowerTimer == TimerState::Reset)) {
-        return refreshRateConfigs->getMaxRefreshRateByPolicy().getMode();
+        (!mPolicy.isDisplayPowerStateNormal || mPolicy.displayPowerTimer == TimerState::Reset)) {
+        constexpr GlobalSignals kNoSignals;
+        return {configs->getMaxRefreshRateByPolicy(), kNoSignals};
     }
 
-    const bool touchActive = mTouchTimer && mFeatures.touch == TouchState::Active;
-    const bool idle = mFeatures.idleTimer == TimerState::Expired;
+    const GlobalSignals signals{.touch = mTouchTimer && mPolicy.touch == TouchState::Active,
+                                .idle = mPolicy.idleTimer == TimerState::Expired};
 
-    return refreshRateConfigs
-            ->getBestRefreshRate(mFeatures.contentRequirements,
-                                 {.touch = touchActive, .idle = idle}, consideredSignals)
-            .getMode();
+    return configs->getBestRefreshRate(mPolicy.contentRequirements, signals);
 }
 
 DisplayModePtr Scheduler::getPreferredDisplayMode() {
-    std::lock_guard<std::mutex> lock(mFeatureStateLock);
-    // Make sure that the default mode ID is first updated, before returned.
-    if (mFeatures.mode) {
-        mFeatures.mode = calculateRefreshRateModeId();
+    std::lock_guard<std::mutex> lock(mPolicyLock);
+    // Make sure the stored mode is up to date.
+    if (mPolicy.mode) {
+        mPolicy.mode = chooseDisplayMode().first;
     }
-    return mFeatures.mode;
+    return mPolicy.mode;
 }
 
 void Scheduler::onNewVsyncPeriodChangeTimeline(const hal::VsyncPeriodChangeTimeline& timeline) {
-    if (timeline.refreshRequired) {
-        mSchedulerCallback.repaintEverythingForHWC();
-    }
-
     std::lock_guard<std::mutex> lock(mVsyncTimelineLock);
     mLastVsyncPeriodChangeTimeline = std::make_optional(timeline);
 
@@ -891,27 +735,29 @@
     }
 }
 
-void Scheduler::onDisplayRefreshed(nsecs_t timestamp) {
-    bool callRepaint = false;
-    {
-        std::lock_guard<std::mutex> lock(mVsyncTimelineLock);
-        if (mLastVsyncPeriodChangeTimeline && mLastVsyncPeriodChangeTimeline->refreshRequired) {
-            if (mLastVsyncPeriodChangeTimeline->refreshTimeNanos < timestamp) {
-                mLastVsyncPeriodChangeTimeline->refreshRequired = false;
-            } else {
-                // We need to send another refresh as refreshTimeNanos is still in the future
-                callRepaint = true;
-            }
+bool Scheduler::onPostComposition(nsecs_t presentTime) {
+    std::lock_guard<std::mutex> lock(mVsyncTimelineLock);
+    if (mLastVsyncPeriodChangeTimeline && mLastVsyncPeriodChangeTimeline->refreshRequired) {
+        if (presentTime < mLastVsyncPeriodChangeTimeline->refreshTimeNanos) {
+            // We need to composite again as refreshTimeNanos is still in the future.
+            return true;
         }
-    }
 
-    if (callRepaint) {
-        mSchedulerCallback.repaintEverythingForHWC();
+        mLastVsyncPeriodChangeTimeline->refreshRequired = false;
     }
+    return false;
 }
 
 void Scheduler::onActiveDisplayAreaChanged(uint32_t displayArea) {
-    mLayerHistory->setDisplayArea(displayArea);
+    mLayerHistory.setDisplayArea(displayArea);
+}
+
+void Scheduler::setGameModeRefreshRateForUid(FrameRateOverride frameRateOverride) {
+    if (frameRateOverride.frameRateHz > 0.f && frameRateOverride.frameRateHz < 1.f) {
+        return;
+    }
+
+    mFrameRateOverrideMappings.setGameModeRefreshRateForUid(frameRateOverride);
 }
 
 void Scheduler::setPreferredRefreshRateForUid(FrameRateOverride frameRateOverride) {
@@ -919,19 +765,14 @@
         return;
     }
 
-    std::lock_guard lock(mFrameRateOverridesLock);
-    if (frameRateOverride.frameRateHz != 0.f) {
-        mFrameRateOverridesFromBackdoor[frameRateOverride.uid] = Fps(frameRateOverride.frameRateHz);
-    } else {
-        mFrameRateOverridesFromBackdoor.erase(frameRateOverride.uid);
-    }
+    mFrameRateOverrideMappings.setPreferredRefreshRateForUid(frameRateOverride);
 }
 
 std::chrono::steady_clock::time_point Scheduler::getPreviousVsyncFrom(
         nsecs_t expectedPresentTime) const {
     const auto presentTime = std::chrono::nanoseconds(expectedPresentTime);
-    const auto vsyncPeriod = std::chrono::nanoseconds(mVsyncSchedule.tracker->currentPeriod());
+    const auto vsyncPeriod = std::chrono::nanoseconds(mVsyncSchedule->getTracker().currentPeriod());
     return std::chrono::steady_clock::time_point(presentTime - vsyncPeriod);
 }
 
-} // namespace android
+} // namespace android::scheduler
diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h
index bbbbca5..0c72124 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.h
+++ b/services/surfaceflinger/Scheduler/Scheduler.h
@@ -17,7 +17,9 @@
 #pragma once
 
 #include <atomic>
+#include <cstdint>
 #include <functional>
+#include <future>
 #include <memory>
 #include <mutex>
 #include <optional>
@@ -30,36 +32,61 @@
 #include <ui/GraphicTypes.h>
 #pragma clang diagnostic pop // ignored "-Wconversion -Wextra"
 
+#include <scheduler/Features.h>
+
 #include "EventThread.h"
+#include "FrameRateOverrideMappings.h"
 #include "LayerHistory.h"
+#include "MessageQueue.h"
 #include "OneShotTimer.h"
 #include "RefreshRateConfigs.h"
-#include "SchedulerUtils.h"
+#include "VsyncSchedule.h"
+
+namespace android::scheduler {
+
+// Opaque handle to scheduler connection.
+struct ConnectionHandle {
+    using Id = std::uintptr_t;
+    static constexpr Id INVALID_ID = static_cast<Id>(-1);
+
+    Id id = INVALID_ID;
+
+    explicit operator bool() const { return id != INVALID_ID; }
+};
+
+inline bool operator==(ConnectionHandle lhs, ConnectionHandle rhs) {
+    return lhs.id == rhs.id;
+}
+
+} // namespace android::scheduler
+
+namespace std {
+
+template <>
+struct hash<android::scheduler::ConnectionHandle> {
+    size_t operator()(android::scheduler::ConnectionHandle handle) const {
+        return hash<android::scheduler::ConnectionHandle::Id>()(handle.id);
+    }
+};
+
+} // namespace std
 
 namespace android {
 
-using namespace std::chrono_literals;
-using scheduler::LayerHistory;
-
 class FenceTime;
 class InjectVSyncSource;
-class PredictedVsyncTracer;
-
-namespace scheduler {
-class VsyncController;
-class VSyncDispatch;
-class VSyncTracker;
-} // namespace scheduler
 
 namespace frametimeline {
 class TokenManager;
 } // namespace frametimeline
 
+namespace scheduler {
+
 struct ISchedulerCallback {
+    using DisplayModeEvent = scheduler::DisplayModeEvent;
+
     virtual void setVsyncEnabled(bool) = 0;
-    virtual void changeRefreshRate(const scheduler::RefreshRateConfigs::RefreshRate&,
-                                   scheduler::RefreshRateConfigEvent) = 0;
-    virtual void repaintEverythingForHWC() = 0;
+    virtual void requestDisplayMode(DisplayModePtr, DisplayModeEvent) = 0;
     virtual void kernelTimerChanged(bool expired) = 0;
     virtual void triggerOnFrameRateOverridesChanged() = 0;
 
@@ -67,15 +94,37 @@
     ~ISchedulerCallback() = default;
 };
 
-class Scheduler {
+class Scheduler : impl::MessageQueue {
+    using Impl = impl::MessageQueue;
+
 public:
-    using RefreshRate = scheduler::RefreshRateConfigs::RefreshRate;
-    using ModeEvent = scheduler::RefreshRateConfigEvent;
+    Scheduler(ICompositor&, ISchedulerCallback&, FeatureFlags);
+    virtual ~Scheduler();
 
-    Scheduler(const std::shared_ptr<scheduler::RefreshRateConfigs>&, ISchedulerCallback&);
-    ~Scheduler();
+    void startTimers();
+    void setRefreshRateConfigs(std::shared_ptr<RefreshRateConfigs>)
+            EXCLUDES(mRefreshRateConfigsLock);
 
-    using ConnectionHandle = scheduler::ConnectionHandle;
+    void run();
+
+    void createVsyncSchedule(FeatureFlags);
+
+    using Impl::initVsync;
+    using Impl::setInjector;
+
+    using Impl::getScheduledFrameTime;
+    using Impl::setDuration;
+
+    using Impl::scheduleFrame;
+
+    // Schedule an asynchronous or synchronous task on the main thread.
+    template <typename F, typename T = std::invoke_result_t<F>>
+    [[nodiscard]] std::future<T> schedule(F&& f) {
+        auto [task, future] = makeTask(std::move(f));
+        postMessage(std::move(task));
+        return std::move(future);
+    }
+
     ConnectionHandle createConnection(const char* connectionName, frametimeline::TokenManager*,
                                       std::chrono::nanoseconds workDuration,
                                       std::chrono::nanoseconds readyDuration,
@@ -87,13 +136,13 @@
     sp<EventThreadConnection> getEventConnection(ConnectionHandle);
 
     void onHotplugReceived(ConnectionHandle, PhysicalDisplayId, bool connected);
-    void onPrimaryDisplayModeChanged(ConnectionHandle, DisplayModePtr) EXCLUDES(mFeatureStateLock);
+    void onPrimaryDisplayModeChanged(ConnectionHandle, DisplayModePtr) EXCLUDES(mPolicyLock);
     void onNonPrimaryDisplayModeChanged(ConnectionHandle, DisplayModePtr);
     void onScreenAcquired(ConnectionHandle);
     void onScreenReleased(ConnectionHandle);
 
     void onFrameRateOverridesChanged(ConnectionHandle, PhysicalDisplayId)
-            EXCLUDES(mFrameRateOverridesLock) EXCLUDES(mConnectionsLock);
+            EXCLUDES(mConnectionsLock);
 
     // Modifies work duration in the event thread.
     void setDuration(ConnectionHandle, std::chrono::nanoseconds workDuration,
@@ -112,16 +161,15 @@
     // If makeAvailable is true, then hardware vsync will be turned on.
     // Otherwise, if hardware vsync is not already enabled then this method will
     // no-op.
-    // The period is the vsync period from the current display configuration.
-    void resyncToHardwareVsync(bool makeAvailable, nsecs_t period);
+    void resyncToHardwareVsync(bool makeAvailable, Fps refreshRate);
     void resync() EXCLUDES(mRefreshRateConfigsLock);
+    void forceNextResync() { mLastResyncTime = 0; }
 
     // Passes a vsync sample to VsyncController. periodFlushed will be true if
     // VsyncController detected that the vsync period changed, and false otherwise.
     void addResyncSample(nsecs_t timestamp, std::optional<nsecs_t> hwcVsyncPeriod,
                          bool* periodFlushed);
-    void addPresentFence(const std::shared_ptr<FenceTime>&);
-    void setIgnorePresentFences(bool ignore);
+    void addPresentFence(std::shared_ptr<FenceTime>);
 
     // Layers are registered on creation, and unregistered when the weak reference expires.
     void registerLayer(Layer*);
@@ -135,17 +183,16 @@
 
     void resetIdleTimer();
 
-    // Function that resets the touch timer.
-    void notifyTouchEvent();
+    // Indicates that touch interaction is taking place.
+    void onTouchHint();
 
     void setDisplayPowerState(bool normal);
 
-    scheduler::VSyncDispatch& getVsyncDispatch() { return *mVsyncSchedule.dispatch; }
+    VSyncDispatch& getVsyncDispatch() { return mVsyncSchedule->getDispatch(); }
 
     // Returns true if a given vsync timestamp is considered valid vsync
     // for a given uid
-    bool isVsyncValid(nsecs_t expectedVsyncTimestamp, uid_t uid) const
-            EXCLUDES(mFrameRateOverridesLock);
+    bool isVsyncValid(nsecs_t expectedVsyncTimestamp, uid_t uid) const;
 
     std::chrono::steady_clock::time_point getPreviousVsyncFrom(nsecs_t expectedPresentTime) const;
 
@@ -159,8 +206,8 @@
     // Notifies the scheduler about a refresh rate timeline change.
     void onNewVsyncPeriodChangeTimeline(const hal::VsyncPeriodChangeTimeline& timeline);
 
-    // Notifies the scheduler when the display was refreshed
-    void onDisplayRefreshed(nsecs_t timestamp);
+    // Notifies the scheduler post composition. Returns if recomposite is needed.
+    bool onPostComposition(nsecs_t presentTime);
 
     // Notifies the scheduler when the display size has changed. Called from SF's main thread
     void onActiveDisplayAreaChanged(uint32_t displayArea);
@@ -174,76 +221,30 @@
 
     // Stores the preferred refresh rate that an app should run at.
     // FrameRateOverride.refreshRateHz == 0 means no preference.
-    void setPreferredRefreshRateForUid(FrameRateOverride) EXCLUDES(mFrameRateOverridesLock);
-    // Retrieves the overridden refresh rate for a given uid.
-    std::optional<Fps> getFrameRateOverride(uid_t uid) const
-            EXCLUDES(mRefreshRateConfigsLock, mFrameRateOverridesLock);
+    void setPreferredRefreshRateForUid(FrameRateOverride);
 
-    void setRefreshRateConfigs(std::shared_ptr<scheduler::RefreshRateConfigs> refreshRateConfigs)
-            EXCLUDES(mRefreshRateConfigsLock) {
-        // We need to stop the idle timer on the previous RefreshRateConfigs instance
-        // and cleanup the scheduler's state before we switch to the other RefreshRateConfigs.
-        {
-            std::scoped_lock lock(mRefreshRateConfigsLock);
-            if (mRefreshRateConfigs) mRefreshRateConfigs->stopIdleTimer();
-        }
-        {
-            std::scoped_lock lock(mFeatureStateLock);
-            mFeatures = {};
-        }
-        {
-            std::scoped_lock lock(mRefreshRateConfigsLock);
-            mRefreshRateConfigs = std::move(refreshRateConfigs);
-            mRefreshRateConfigs->setIdleTimerCallbacks(
-                    [this] { std::invoke(&Scheduler::idleTimerCallback, this, TimerState::Reset); },
-                    [this] {
-                        std::invoke(&Scheduler::idleTimerCallback, this, TimerState::Expired);
-                    },
-                    [this] {
-                        std::invoke(&Scheduler::kernelIdleTimerCallback, this, TimerState::Reset);
-                    },
-                    [this] {
-                        std::invoke(&Scheduler::kernelIdleTimerCallback, this, TimerState::Expired);
-                    });
-            mRefreshRateConfigs->startIdleTimer();
-        }
-    }
+    void setGameModeRefreshRateForUid(FrameRateOverride);
+
+    // Retrieves the overridden refresh rate for a given uid.
+    std::optional<Fps> getFrameRateOverride(uid_t uid) const EXCLUDES(mRefreshRateConfigsLock);
 
     nsecs_t getVsyncPeriodFromRefreshRateConfigs() const EXCLUDES(mRefreshRateConfigsLock) {
         std::scoped_lock lock(mRefreshRateConfigsLock);
-        return mRefreshRateConfigs->getCurrentRefreshRate().getVsyncPeriod();
+        return mRefreshRateConfigs->getActiveMode()->getFps().getPeriodNsecs();
+    }
+
+    // Returns the framerate of the layer with the given sequence ID
+    float getLayerFramerate(nsecs_t now, int32_t id) const {
+        return mLayerHistory.getLayerFramerate(now, id);
     }
 
 private:
     friend class TestableScheduler;
 
-    // In order to make sure that the features don't override themselves, we need a state machine
-    // to keep track which feature requested the config change.
     enum class ContentDetectionState { Off, On };
     enum class TimerState { Reset, Expired };
     enum class TouchState { Inactive, Active };
 
-    struct Options {
-        // Whether to use content detection at all.
-        bool useContentDetection;
-    };
-
-    struct VsyncSchedule {
-        std::unique_ptr<scheduler::VsyncController> controller;
-        std::unique_ptr<scheduler::VSyncTracker> tracker;
-        std::unique_ptr<scheduler::VSyncDispatch> dispatch;
-    };
-
-    // Unlike the testing constructor, this creates the VsyncSchedule, LayerHistory, and timers.
-    Scheduler(const std::shared_ptr<scheduler::RefreshRateConfigs>&, ISchedulerCallback&, Options);
-
-    // Used by tests to inject mocks.
-    Scheduler(VsyncSchedule, const std::shared_ptr<scheduler::RefreshRateConfigs>&,
-              ISchedulerCallback&, std::unique_ptr<LayerHistory>, Options);
-
-    static VsyncSchedule createVsyncSchedule(bool supportKernelIdleTimer);
-    static std::unique_ptr<LayerHistory> createLayerHistory();
-
     // Create a connection on the given EventThread.
     ConnectionHandle createConnection(std::unique_ptr<EventThread>);
     sp<EventThreadConnection> createConnectionInternal(
@@ -255,29 +256,29 @@
     void touchTimerCallback(TimerState);
     void displayPowerTimerCallback(TimerState);
 
-    // handles various timer features to change the refresh rate.
-    template <class T>
-    bool handleTimerStateChanged(T* currentState, T newState);
-
     void setVsyncPeriod(nsecs_t period);
 
-    // This function checks whether individual features that are affecting the refresh rate
-    // selection were initialized, prioritizes them, and calculates the DisplayModeId
-    // for the suggested refresh rate.
-    DisplayModePtr calculateRefreshRateModeId(
-            scheduler::RefreshRateConfigs::GlobalSignals* consideredSignals = nullptr)
-            REQUIRES(mFeatureStateLock);
+    using GlobalSignals = RefreshRateConfigs::GlobalSignals;
 
-    void dispatchCachedReportedMode() REQUIRES(mFeatureStateLock) EXCLUDES(mRefreshRateConfigsLock);
-    bool updateFrameRateOverrides(scheduler::RefreshRateConfigs::GlobalSignals consideredSignals,
-                                  Fps displayRefreshRate) REQUIRES(mFeatureStateLock)
-            EXCLUDES(mFrameRateOverridesLock);
+    struct Policy;
+
+    // Sets the S state of the policy to the T value under mPolicyLock, and chooses a display mode
+    // that fulfills the new policy if the state changed. Returns the signals that were considered.
+    template <typename S, typename T>
+    GlobalSignals applyPolicy(S Policy::*, T&&) EXCLUDES(mPolicyLock);
+
+    // Returns the display mode that fulfills the policy, and the signals that were considered.
+    std::pair<DisplayModePtr, GlobalSignals> chooseDisplayMode() REQUIRES(mPolicyLock);
+
+    bool updateFrameRateOverrides(GlobalSignals, Fps displayRefreshRate) REQUIRES(mPolicyLock);
+
+    void dispatchCachedReportedMode() REQUIRES(mPolicyLock) EXCLUDES(mRefreshRateConfigsLock);
 
     impl::EventThread::ThrottleVsyncCallback makeThrottleVsyncCallback() const
             EXCLUDES(mRefreshRateConfigsLock);
     impl::EventThread::GetVsyncPeriodFunction makeGetVsyncPeriodFunction() const;
 
-    std::shared_ptr<scheduler::RefreshRateConfigs> holdRefreshRateConfigs() const
+    std::shared_ptr<RefreshRateConfigs> holdRefreshRateConfigs() const
             EXCLUDES(mRefreshRateConfigsLock) {
         std::scoped_lock lock(mRefreshRateConfigsLock);
         return mRefreshRateConfigs;
@@ -303,66 +304,54 @@
 
     std::atomic<nsecs_t> mLastResyncTime = 0;
 
-    const Options mOptions;
-    VsyncSchedule mVsyncSchedule;
+    const FeatureFlags mFeatures;
+    std::optional<VsyncSchedule> mVsyncSchedule;
 
     // Used to choose refresh rate if content detection is enabled.
-    std::unique_ptr<LayerHistory> mLayerHistory;
+    LayerHistory mLayerHistory;
 
     // Timer used to monitor touch events.
-    std::optional<scheduler::OneShotTimer> mTouchTimer;
+    std::optional<OneShotTimer> mTouchTimer;
     // Timer used to monitor display power mode.
-    std::optional<scheduler::OneShotTimer> mDisplayPowerTimer;
+    std::optional<OneShotTimer> mDisplayPowerTimer;
 
     ISchedulerCallback& mSchedulerCallback;
 
-    // In order to make sure that the features don't override themselves, we need a state machine
-    // to keep track which feature requested the config change.
-    mutable std::mutex mFeatureStateLock;
+    mutable std::mutex mPolicyLock;
 
-    struct {
+    struct Policy {
+        // Policy for choosing the display mode.
+        LayerHistory::Summary contentRequirements;
         TimerState idleTimer = TimerState::Reset;
         TouchState touch = TouchState::Inactive;
         TimerState displayPowerTimer = TimerState::Expired;
-
-        DisplayModePtr mode;
-        LayerHistory::Summary contentRequirements;
-
         bool isDisplayPowerStateNormal = true;
 
-        // Used to cache the last parameters of onPrimaryDisplayModeChanged
+        // Chosen display mode.
+        DisplayModePtr mode;
+
         struct ModeChangedParams {
             ConnectionHandle handle;
             DisplayModePtr mode;
         };
 
+        // Parameters for latest dispatch of mode change event.
         std::optional<ModeChangedParams> cachedModeChangedParams;
-    } mFeatures GUARDED_BY(mFeatureStateLock);
+    } mPolicy GUARDED_BY(mPolicyLock);
 
     mutable std::mutex mRefreshRateConfigsLock;
-    std::shared_ptr<scheduler::RefreshRateConfigs> mRefreshRateConfigs
-            GUARDED_BY(mRefreshRateConfigsLock);
+    std::shared_ptr<RefreshRateConfigs> mRefreshRateConfigs GUARDED_BY(mRefreshRateConfigsLock);
 
     std::mutex mVsyncTimelineLock;
     std::optional<hal::VsyncPeriodChangeTimeline> mLastVsyncPeriodChangeTimeline
             GUARDED_BY(mVsyncTimelineLock);
     static constexpr std::chrono::nanoseconds MAX_VSYNC_APPLIED_TIME = 200ms;
 
-    const std::unique_ptr<PredictedVsyncTracer> mPredictedVsyncTracer;
-
-    // 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;
-
-    // mappings between a UID and a preferred refresh rate that this app would
-    // run at.
-    scheduler::RefreshRateConfigs::UidToFrameRateOverride mFrameRateOverridesByContent
-            GUARDED_BY(mFrameRateOverridesLock);
-    scheduler::RefreshRateConfigs::UidToFrameRateOverride mFrameRateOverridesFromBackdoor
-            GUARDED_BY(mFrameRateOverridesLock);
+    FrameRateOverrideMappings mFrameRateOverrideMappings;
 
     // Keeps track of whether the screen is acquired for debug
     std::atomic<bool> mScreenAcquired = false;
 };
 
+} // namespace scheduler
 } // namespace android
diff --git a/services/surfaceflinger/Scheduler/SchedulerUtils.cpp b/services/surfaceflinger/Scheduler/SchedulerUtils.cpp
deleted file mode 100644
index e8e0444..0000000
--- a/services/surfaceflinger/Scheduler/SchedulerUtils.cpp
+++ /dev/null
@@ -1,39 +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 "SchedulerUtils.h"
-
-#include <cinttypes>
-#include <numeric>
-#include <unordered_map>
-#include <vector>
-
-namespace android {
-namespace scheduler {
-
-int64_t calculate_median(std::vector<int64_t>* v) {
-    if (!v || v->empty()) {
-        return 0;
-    }
-
-    size_t n = v->size() / 2;
-    nth_element(v->begin(), v->begin() + static_cast<long>(n), v->end());
-    return v->at(n);
-}
-
-} // namespace scheduler
-} // namespace android
-
diff --git a/services/surfaceflinger/Scheduler/SchedulerUtils.h b/services/surfaceflinger/Scheduler/SchedulerUtils.h
deleted file mode 100644
index 04a4cd1..0000000
--- a/services/surfaceflinger/Scheduler/SchedulerUtils.h
+++ /dev/null
@@ -1,97 +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.
- */
-
-#pragma once
-
-#include <utils/Timers.h>
-#include <cinttypes>
-#include <numeric>
-#include <unordered_map>
-#include <vector>
-
-namespace android::scheduler {
-
-// Opaque handle to scheduler connection.
-struct ConnectionHandle {
-    using Id = std::uintptr_t;
-    static constexpr Id INVALID_ID = static_cast<Id>(-1);
-
-    Id id = INVALID_ID;
-
-    explicit operator bool() const { return id != INVALID_ID; }
-};
-
-inline bool operator==(ConnectionHandle lhs, ConnectionHandle rhs) {
-    return lhs.id == rhs.id;
-}
-
-// Calculates the statistical mean (average) in the data structure (array, vector). The
-// function does not modify the contents of the array.
-template <typename T>
-auto calculate_mean(const T& v) {
-    using V = typename T::value_type;
-    V sum = std::accumulate(v.begin(), v.end(), static_cast<V>(0));
-    return sum / static_cast<V>(v.size());
-}
-
-// Calculates the statistical median in the vector. Return 0 if the vector is empty. The
-// function modifies the vector contents.
-int64_t calculate_median(std::vector<int64_t>* v);
-
-// Calculates the statistical mode in the vector. Return 0 if the vector is empty.
-template <typename T>
-auto calculate_mode(const T& v) {
-    if (v.empty()) {
-        return 0;
-    }
-
-    // Create a map with all the counts for the indivicual values in the vector.
-    std::unordered_map<int64_t, int> counts;
-    for (int64_t value : v) {
-        counts[value]++;
-    }
-
-    // Sort the map, and return the number with the highest count. If two numbers have
-    // the same count, first one is returned.
-    using ValueType = const decltype(counts)::value_type&;
-    const auto compareCounts = [](ValueType l, ValueType r) { return l.second <= r.second; };
-    return static_cast<int>(std::max_element(counts.begin(), counts.end(), compareCounts)->first);
-}
-
-template <class T, size_t N>
-constexpr size_t arrayLen(T (&)[N]) {
-    return N;
-}
-
-static constexpr size_t max64print = std::numeric_limits<nsecs_t>::digits10 + 1;
-
-template <typename T>
-static inline T round(float f) {
-    return static_cast<T>(std::round(f));
-}
-
-} // namespace android::scheduler
-
-namespace std {
-
-template <>
-struct hash<android::scheduler::ConnectionHandle> {
-    size_t operator()(android::scheduler::ConnectionHandle handle) const {
-        return hash<android::scheduler::ConnectionHandle::Id>()(handle.id);
-    }
-};
-
-} // namespace std
diff --git a/services/surfaceflinger/Scheduler/VSyncDispatch.h b/services/surfaceflinger/Scheduler/VSyncDispatch.h
index b52706f..2bfe204 100644
--- a/services/surfaceflinger/Scheduler/VSyncDispatch.h
+++ b/services/surfaceflinger/Scheduler/VSyncDispatch.h
@@ -16,17 +16,15 @@
 
 #pragma once
 
-#include <utils/Log.h>
-#include <utils/Timers.h>
 #include <functional>
 #include <optional>
 #include <string>
 
+#include <utils/Timers.h>
+
 #include "StrongTyping.h"
 
 namespace android::scheduler {
-class TimeKeeper;
-class VSyncTracker;
 
 using ScheduleResult = std::optional<nsecs_t>;
 
@@ -64,8 +62,7 @@
      *                          invocation of callbackFn.
      *
      */
-    virtual CallbackToken registerCallback(Callback const& callbackFn,
-                                           std::string callbackName) = 0;
+    virtual CallbackToken registerCallback(Callback, std::string callbackName) = 0;
 
     /*
      * Unregisters a callback.
@@ -142,8 +139,9 @@
 
 protected:
     VSyncDispatch() = default;
-    VSyncDispatch(VSyncDispatch const&) = delete;
-    VSyncDispatch& operator=(VSyncDispatch const&) = delete;
+
+    VSyncDispatch(const VSyncDispatch&) = delete;
+    VSyncDispatch& operator=(const VSyncDispatch&) = delete;
 };
 
 /*
@@ -152,11 +150,11 @@
  */
 class VSyncCallbackRegistration {
 public:
-    VSyncCallbackRegistration(VSyncDispatch&, VSyncDispatch::Callback const& callbackFn,
-                              std::string const& callbackName);
+    VSyncCallbackRegistration(VSyncDispatch&, VSyncDispatch::Callback, std::string callbackName);
+    ~VSyncCallbackRegistration();
+
     VSyncCallbackRegistration(VSyncCallbackRegistration&&);
     VSyncCallbackRegistration& operator=(VSyncCallbackRegistration&&);
-    ~VSyncCallbackRegistration();
 
     // See documentation for VSyncDispatch::schedule.
     ScheduleResult schedule(VSyncDispatch::ScheduleTiming scheduleTiming);
@@ -165,9 +163,6 @@
     CancelResult cancel();
 
 private:
-    VSyncCallbackRegistration(VSyncCallbackRegistration const&) = delete;
-    VSyncCallbackRegistration& operator=(VSyncCallbackRegistration const&) = delete;
-
     std::reference_wrapper<VSyncDispatch> mDispatch;
     VSyncDispatch::CallbackToken mToken;
     bool mValidToken;
diff --git a/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp b/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp
index b805bf6..27f4311 100644
--- a/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp
+++ b/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp
@@ -15,18 +15,24 @@
  */
 
 #define ATRACE_TAG ATRACE_TAG_GRAPHICS
-#include <android-base/stringprintf.h>
-#include <utils/Trace.h>
+
 #include <vector>
 
-#include "TimeKeeper.h"
+#include <android-base/stringprintf.h>
+#include <ftl/concat.h>
+#include <utils/Trace.h>
+
+#include <scheduler/TimeKeeper.h>
+
 #include "VSyncDispatchTimerQueue.h"
 #include "VSyncTracker.h"
 
 namespace android::scheduler {
+
 using base::StringAppendF;
 
 namespace {
+
 nsecs_t getExpectedCallbackTime(nsecs_t nextVsyncTime,
                                 const VSyncDispatch::ScheduleTiming& timing) {
     return nextVsyncTime - timing.readyDuration - timing.workDuration;
@@ -38,17 +44,17 @@
             std::max(timing.earliestVsync, now + timing.workDuration + timing.readyDuration));
     return getExpectedCallbackTime(nextVsyncTime, timing);
 }
+
 } // namespace
 
 VSyncDispatch::~VSyncDispatch() = default;
 VSyncTracker::~VSyncTracker() = default;
-TimeKeeper::~TimeKeeper() = default;
 
-VSyncDispatchTimerQueueEntry::VSyncDispatchTimerQueueEntry(std::string const& name,
-                                                           VSyncDispatch::Callback const& cb,
+VSyncDispatchTimerQueueEntry::VSyncDispatchTimerQueueEntry(std::string name,
+                                                           VSyncDispatch::Callback callback,
                                                            nsecs_t minVsyncDistance)
-      : mName(name),
-        mCallback(cb),
+      : mName(std::move(name)),
+        mCallback(std::move(callback)),
         mMinVsyncDistance(minVsyncDistance) {}
 
 std::optional<nsecs_t> VSyncDispatchTimerQueueEntry::lastExecutedVsyncTarget() const {
@@ -222,16 +228,6 @@
     rearmTimerSkippingUpdateFor(now, mCallbacks.end());
 }
 
-void VSyncDispatchTimerQueue::TraceBuffer::note(std::string_view name, nsecs_t alarmIn,
-                                                nsecs_t vsFor) {
-    if (ATRACE_ENABLED()) {
-        snprintf(str_buffer.data(), str_buffer.size(), "%.4s%s%" PRId64 "%s%" PRId64,
-                 name.substr(0, kMaxNamePrint).data(), kTraceNamePrefix, alarmIn,
-                 kTraceNameSeparator, vsFor);
-    }
-    ATRACE_NAME(str_buffer.data());
-}
-
 void VSyncDispatchTimerQueue::rearmTimerSkippingUpdateFor(
         nsecs_t now, CallbackMap::iterator const& skipUpdateIt) {
     std::optional<nsecs_t> min;
@@ -247,16 +243,18 @@
             callback->update(mTracker, now);
         }
         auto const wakeupTime = *callback->wakeupTime();
-        if (!min || (min && *min > wakeupTime)) {
+        if (!min || *min > wakeupTime) {
             nextWakeupName = callback->name();
             min = wakeupTime;
             targetVsync = callback->targetVsync();
         }
     }
 
-    if (min && (min < mIntendedWakeupTime)) {
-        if (targetVsync && nextWakeupName) {
-            mTraceBuffer.note(*nextWakeupName, *min - now, *targetVsync - now);
+    if (min && min < mIntendedWakeupTime) {
+        if (ATRACE_ENABLED() && nextWakeupName && targetVsync) {
+            ftl::Concat trace(ftl::truncated<5>(*nextWakeupName), " alarm in ", ns2us(*min - now),
+                              "us; VSYNC in ", ns2us(*targetVsync - now), "us");
+            ATRACE_NAME(trace.c_str());
         }
         setTimer(*min, now);
     } else {
@@ -305,13 +303,13 @@
 }
 
 VSyncDispatchTimerQueue::CallbackToken VSyncDispatchTimerQueue::registerCallback(
-        Callback const& callbackFn, std::string callbackName) {
+        Callback callback, std::string callbackName) {
     std::lock_guard lock(mMutex);
     return CallbackToken{
             mCallbacks
                     .emplace(++mCallbackToken,
-                             std::make_shared<VSyncDispatchTimerQueueEntry>(callbackName,
-                                                                            callbackFn,
+                             std::make_shared<VSyncDispatchTimerQueueEntry>(std::move(callbackName),
+                                                                            std::move(callback),
                                                                             mMinVsyncDistance))
                     .first->first};
 }
@@ -406,10 +404,10 @@
 }
 
 VSyncCallbackRegistration::VSyncCallbackRegistration(VSyncDispatch& dispatch,
-                                                     VSyncDispatch::Callback const& callbackFn,
-                                                     std::string const& callbackName)
+                                                     VSyncDispatch::Callback callback,
+                                                     std::string callbackName)
       : mDispatch(dispatch),
-        mToken(dispatch.registerCallback(callbackFn, callbackName)),
+        mToken(dispatch.registerCallback(std::move(callback), std::move(callbackName))),
         mValidToken(true) {}
 
 VSyncCallbackRegistration::VSyncCallbackRegistration(VSyncCallbackRegistration&& other)
diff --git a/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.h b/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.h
index 26237b6..4923031 100644
--- a/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.h
+++ b/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.h
@@ -16,8 +16,6 @@
 
 #pragma once
 
-#include <android-base/thread_annotations.h>
-#include <array>
 #include <functional>
 #include <memory>
 #include <mutex>
@@ -25,11 +23,15 @@
 #include <string_view>
 #include <unordered_map>
 
-#include "SchedulerUtils.h"
+#include <android-base/thread_annotations.h>
+
 #include "VSyncDispatch.h"
 
 namespace android::scheduler {
 
+class TimeKeeper;
+class VSyncTracker;
+
 // VSyncDispatchTimerQueueEntry is a helper class representing internal state for each entry in
 // VSyncDispatchTimerQueue hoisted to public for unit testing.
 class VSyncDispatchTimerQueueEntry {
@@ -38,7 +40,7 @@
     // Valid transition: disarmed -> armed ( when scheduled )
     // Valid transition: armed -> running -> disarmed ( when timer is called)
     // Valid transition: armed -> disarmed ( when cancelled )
-    VSyncDispatchTimerQueueEntry(std::string const& name, VSyncDispatch::Callback const& fn,
+    VSyncDispatchTimerQueueEntry(std::string name, VSyncDispatch::Callback,
                                  nsecs_t minVsyncDistance);
     std::string_view name() const;
 
@@ -47,10 +49,9 @@
     std::optional<nsecs_t> lastExecutedVsyncTarget() const;
 
     // This moves the state from disarmed->armed and will calculate the wakeupTime.
-    ScheduleResult schedule(VSyncDispatch::ScheduleTiming timing, VSyncTracker& tracker,
-                            nsecs_t now);
+    ScheduleResult schedule(VSyncDispatch::ScheduleTiming, VSyncTracker&, nsecs_t now);
     // This will update armed entries with the latest vsync information. Entry remains armed.
-    void update(VSyncTracker& tracker, nsecs_t now);
+    void update(VSyncTracker&, nsecs_t now);
 
     // This will return empty if not armed, or the next calculated wakeup time if armed.
     // It will not update the wakeupTime.
@@ -83,11 +84,11 @@
     void dump(std::string& result) const;
 
 private:
-    std::string const mName;
-    VSyncDispatch::Callback const mCallback;
+    const std::string mName;
+    const VSyncDispatch::Callback mCallback;
 
     VSyncDispatch::ScheduleTiming mScheduleTiming;
-    nsecs_t const mMinVsyncDistance;
+    const nsecs_t mMinVsyncDistance;
 
     struct ArmingInfo {
         nsecs_t mActualWakeupTime;
@@ -117,19 +118,19 @@
     //                                  should be grouped into one wakeup.
     // \param[in] minVsyncDistance      The minimum distance between two vsync estimates before the
     //                                  vsyncs are considered the same vsync event.
-    explicit VSyncDispatchTimerQueue(std::unique_ptr<TimeKeeper> tk, VSyncTracker& tracker,
-                                     nsecs_t timerSlack, nsecs_t minVsyncDistance);
+    VSyncDispatchTimerQueue(std::unique_ptr<TimeKeeper>, VSyncTracker&, nsecs_t timerSlack,
+                            nsecs_t minVsyncDistance);
     ~VSyncDispatchTimerQueue();
 
-    CallbackToken registerCallback(Callback const& callbackFn, std::string callbackName) final;
-    void unregisterCallback(CallbackToken token) final;
-    ScheduleResult schedule(CallbackToken token, ScheduleTiming scheduleTiming) final;
-    CancelResult cancel(CallbackToken token) final;
-    void dump(std::string& result) const final;
+    CallbackToken registerCallback(Callback, std::string callbackName) final;
+    void unregisterCallback(CallbackToken) final;
+    ScheduleResult schedule(CallbackToken, ScheduleTiming) final;
+    CancelResult cancel(CallbackToken) final;
+    void dump(std::string&) const final;
 
 private:
-    VSyncDispatchTimerQueue(VSyncDispatchTimerQueue const&) = delete;
-    VSyncDispatchTimerQueue& operator=(VSyncDispatchTimerQueue const&) = delete;
+    VSyncDispatchTimerQueue(const VSyncDispatchTimerQueue&) = delete;
+    VSyncDispatchTimerQueue& operator=(const VSyncDispatchTimerQueue&) = delete;
 
     using CallbackMap =
             std::unordered_map<CallbackToken, std::shared_ptr<VSyncDispatchTimerQueueEntry>>;
@@ -153,17 +154,6 @@
     CallbackMap mCallbacks GUARDED_BY(mMutex);
     nsecs_t mIntendedWakeupTime GUARDED_BY(mMutex) = kInvalidTime;
 
-    struct TraceBuffer {
-        static constexpr char const kTraceNamePrefix[] = "-alarm in:";
-        static constexpr char const kTraceNameSeparator[] = " for vs:";
-        static constexpr size_t kMaxNamePrint = 4;
-        static constexpr size_t kNumTsPrinted = 2;
-        static constexpr size_t maxlen = kMaxNamePrint + arrayLen(kTraceNamePrefix) +
-                arrayLen(kTraceNameSeparator) - 1 + (kNumTsPrinted * max64print);
-        std::array<char, maxlen> str_buffer;
-        void note(std::string_view name, nsecs_t in, nsecs_t vs);
-    } mTraceBuffer GUARDED_BY(mMutex);
-
     // For debugging purposes
     nsecs_t mLastTimerCallback GUARDED_BY(mMutex) = kInvalidTime;
     nsecs_t mLastTimerSchedule GUARDED_BY(mMutex) = kInvalidTime;
diff --git a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp
index e9bd92a..77782e9 100644
--- a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp
+++ b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp
@@ -18,24 +18,27 @@
 #pragma clang diagnostic push
 #pragma clang diagnostic ignored "-Wextra"
 
+#undef LOG_TAG
+#define LOG_TAG "VSyncPredictor"
+
 #define ATRACE_TAG ATRACE_TAG_GRAPHICS
-//#define LOG_NDEBUG 0
-#include "VSyncPredictor.h"
+
+#include <algorithm>
+#include <chrono>
+#include <sstream>
+
 #include <android-base/logging.h>
 #include <android-base/stringprintf.h>
 #include <cutils/compiler.h>
 #include <cutils/properties.h>
 #include <utils/Log.h>
 #include <utils/Trace.h>
-#include <algorithm>
-#include <chrono>
-#include <sstream>
-#include "RefreshRateConfigs.h"
 
-#undef LOG_TAG
-#define LOG_TAG "VSyncPredictor"
+#include "RefreshRateConfigs.h"
+#include "VSyncPredictor.h"
 
 namespace android::scheduler {
+
 using base::StringAppendF;
 
 static auto constexpr kMaxPercent = 100u;
@@ -121,7 +124,8 @@
         mTimestamps[mLastTimestampIndex] = timestamp;
     }
 
-    if (mTimestamps.size() < kMinimumSamplesForPrediction) {
+    const size_t numSamples = mTimestamps.size();
+    if (numSamples < kMinimumSamplesForPrediction) {
         mRateMap[mIdealPeriod] = {mIdealPeriod, 0};
         return true;
     }
@@ -141,36 +145,44 @@
     //
     // intercept = mean(Y) - slope * mean(X)
     //
-    std::vector<nsecs_t> vsyncTS(mTimestamps.size());
-    std::vector<nsecs_t> ordinals(mTimestamps.size());
+    std::vector<nsecs_t> vsyncTS(numSamples);
+    std::vector<nsecs_t> ordinals(numSamples);
 
-    // normalizing to the oldest timestamp cuts down on error in calculating the intercept.
-    auto const oldest_ts = *std::min_element(mTimestamps.begin(), mTimestamps.end());
+    // Normalizing to the oldest timestamp cuts down on error in calculating the intercept.
+    const auto oldestTS = *std::min_element(mTimestamps.begin(), mTimestamps.end());
     auto it = mRateMap.find(mIdealPeriod);
     auto const currentPeriod = it->second.slope;
-    // TODO (b/144707443): its important that there's some precision in the mean of the ordinals
-    //                     for the intercept calculation, so scale the ordinals by 1000 to continue
-    //                     fixed point calculation. Explore expanding
-    //                     scheduler::utils::calculate_mean to have a fixed point fractional part.
-    static constexpr int64_t kScalingFactor = 1000;
 
-    for (auto i = 0u; i < mTimestamps.size(); i++) {
+    // The mean of the ordinals must be precise for the intercept calculation, so scale them up for
+    // fixed-point arithmetic.
+    constexpr int64_t kScalingFactor = 1000;
+
+    nsecs_t meanTS = 0;
+    nsecs_t meanOrdinal = 0;
+
+    for (size_t i = 0; i < numSamples; i++) {
         traceInt64If("VSP-ts", mTimestamps[i]);
 
-        vsyncTS[i] = mTimestamps[i] - oldest_ts;
-        ordinals[i] = ((vsyncTS[i] + (currentPeriod / 2)) / currentPeriod) * kScalingFactor;
+        const auto timestamp = mTimestamps[i] - oldestTS;
+        vsyncTS[i] = timestamp;
+        meanTS += timestamp;
+
+        const auto ordinal = (vsyncTS[i] + currentPeriod / 2) / currentPeriod * kScalingFactor;
+        ordinals[i] = ordinal;
+        meanOrdinal += ordinal;
     }
 
-    auto meanTS = scheduler::calculate_mean(vsyncTS);
-    auto meanOrdinal = scheduler::calculate_mean(ordinals);
-    for (size_t i = 0; i < vsyncTS.size(); i++) {
+    meanTS /= numSamples;
+    meanOrdinal /= numSamples;
+
+    for (size_t i = 0; i < numSamples; i++) {
         vsyncTS[i] -= meanTS;
         ordinals[i] -= meanOrdinal;
     }
 
-    auto top = 0ll;
-    auto bottom = 0ll;
-    for (size_t i = 0; i < vsyncTS.size(); i++) {
+    nsecs_t top = 0;
+    nsecs_t bottom = 0;
+    for (size_t i = 0; i < numSamples; i++) {
         top += vsyncTS[i] * ordinals[i];
         bottom += ordinals[i] * ordinals[i];
     }
@@ -244,7 +256,7 @@
 
 /*
  * Returns whether a given vsync timestamp is in phase with a frame rate.
- * If the frame rate is not a divider of the refresh rate, it is always considered in phase.
+ * If the frame rate is not a divisor of the refresh rate, it is always considered in phase.
  * For example, if the vsync timestamps are (16.6,33.3,50.0,66.6):
  * isVSyncInPhase(16.6, 30) = true
  * isVSyncInPhase(33.3, 30) = false
@@ -259,42 +271,42 @@
     };
 
     std::lock_guard lock(mMutex);
-    const auto divider =
-            RefreshRateConfigs::getFrameRateDivider(Fps::fromPeriodNsecs(mIdealPeriod), frameRate);
-    if (divider <= 1 || timePoint == 0) {
+    const auto divisor =
+            RefreshRateConfigs::getFrameRateDivisor(Fps::fromPeriodNsecs(mIdealPeriod), frameRate);
+    if (divisor <= 1 || timePoint == 0) {
         return true;
     }
 
     const nsecs_t period = mRateMap[mIdealPeriod].slope;
     const nsecs_t justBeforeTimePoint = timePoint - period / 2;
-    const nsecs_t dividedPeriod = mIdealPeriod / divider;
+    const nsecs_t dividedPeriod = mIdealPeriod / divisor;
 
-    // If this is the first time we have asked about this divider with the
+    // If this is the first time we have asked about this divisor with the
     // current vsync period, it is considered in phase and we store the closest
     // vsync timestamp
-    const auto knownTimestampIter = mRateDividerKnownTimestampMap.find(dividedPeriod);
-    if (knownTimestampIter == mRateDividerKnownTimestampMap.end()) {
+    const auto knownTimestampIter = mRateDivisorKnownTimestampMap.find(dividedPeriod);
+    if (knownTimestampIter == mRateDivisorKnownTimestampMap.end()) {
         const auto vsync = nextAnticipatedVSyncTimeFromLocked(justBeforeTimePoint);
-        mRateDividerKnownTimestampMap[dividedPeriod] = vsync;
+        mRateDivisorKnownTimestampMap[dividedPeriod] = vsync;
         return true;
     }
 
-    // Find the next N vsync timestamp where N is the divider.
+    // Find the next N vsync timestamp where N is the divisor.
     // One of these vsyncs will be in phase. We return the one which is
     // the most aligned with the last known in phase vsync
-    std::vector<VsyncError> vsyncs(static_cast<size_t>(divider));
+    std::vector<VsyncError> vsyncs(static_cast<size_t>(divisor));
     const nsecs_t knownVsync = knownTimestampIter->second;
     nsecs_t point = justBeforeTimePoint;
-    for (size_t i = 0; i < divider; i++) {
+    for (size_t i = 0; i < divisor; i++) {
         const nsecs_t vsync = nextAnticipatedVSyncTimeFromLocked(point);
-        const auto numPeriods = static_cast<float>(vsync - knownVsync) / (period * divider);
+        const auto numPeriods = static_cast<float>(vsync - knownVsync) / (period * divisor);
         const auto error = std::abs(std::round(numPeriods) - numPeriods);
         vsyncs[i] = {vsync, error};
         point = vsync + 1;
     }
 
     const auto minVsyncError = std::min_element(vsyncs.begin(), vsyncs.end());
-    mRateDividerKnownTimestampMap[dividedPeriod] = minVsyncError->vsyncTimestamp;
+    mRateDivisorKnownTimestampMap[dividedPeriod] = minVsyncError->vsyncTimestamp;
     return std::abs(minVsyncError->vsyncTimestamp - timePoint) < period / 2;
 }
 
@@ -365,4 +377,4 @@
 } // namespace android::scheduler
 
 // TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic pop // ignored "-Wextra"
\ No newline at end of file
+#pragma clang diagnostic pop // ignored "-Wextra"
diff --git a/services/surfaceflinger/Scheduler/VSyncPredictor.h b/services/surfaceflinger/Scheduler/VSyncPredictor.h
index 40e6944..3181102 100644
--- a/services/surfaceflinger/Scheduler/VSyncPredictor.h
+++ b/services/surfaceflinger/Scheduler/VSyncPredictor.h
@@ -16,11 +16,12 @@
 
 #pragma once
 
-#include <android-base/thread_annotations.h>
 #include <mutex>
 #include <unordered_map>
 #include <vector>
-#include "SchedulerUtils.h"
+
+#include <android-base/thread_annotations.h>
+
 #include "VSyncTracker.h"
 
 namespace android::scheduler {
@@ -95,7 +96,7 @@
     std::unordered_map<nsecs_t, Model> mutable mRateMap GUARDED_BY(mMutex);
 
     // Map between the divided vsync period and the last known vsync timestamp
-    std::unordered_map<nsecs_t, nsecs_t> mutable mRateDividerKnownTimestampMap GUARDED_BY(mMutex);
+    std::unordered_map<nsecs_t, nsecs_t> mutable mRateDivisorKnownTimestampMap GUARDED_BY(mMutex);
 
     size_t mLastTimestampIndex GUARDED_BY(mMutex) = 0;
     std::vector<nsecs_t> mTimestamps GUARDED_BY(mMutex);
diff --git a/services/surfaceflinger/Scheduler/VSyncReactor.cpp b/services/surfaceflinger/Scheduler/VSyncReactor.cpp
index ee973f7..bdcab51 100644
--- a/services/surfaceflinger/Scheduler/VSyncReactor.cpp
+++ b/services/surfaceflinger/Scheduler/VSyncReactor.cpp
@@ -18,21 +18,22 @@
 #undef LOG_TAG
 #define LOG_TAG "VSyncReactor"
 //#define LOG_NDEBUG 0
-#include "VSyncReactor.h"
+
 #include <cutils/properties.h>
 #include <log/log.h>
 #include <utils/Trace.h>
+
 #include "../TracedOrdinal.h"
-#include "TimeKeeper.h"
 #include "VSyncDispatch.h"
+#include "VSyncReactor.h"
 #include "VSyncTracker.h"
 
 namespace android::scheduler {
+
 using base::StringAppendF;
 
 VsyncController::~VsyncController() = default;
 
-Clock::~Clock() = default;
 nsecs_t SystemClock::now() const {
     return systemTime(SYSTEM_TIME_MONOTONIC);
 }
@@ -47,7 +48,7 @@
 
 VSyncReactor::~VSyncReactor() = default;
 
-bool VSyncReactor::addPresentFence(const std::shared_ptr<android::FenceTime>& fence) {
+bool VSyncReactor::addPresentFence(std::shared_ptr<FenceTime> fence) {
     if (!fence) {
         return false;
     }
@@ -80,7 +81,7 @@
         if (mPendingLimit == mUnfiredFences.size()) {
             mUnfiredFences.erase(mUnfiredFences.begin());
         }
-        mUnfiredFences.push_back(fence);
+        mUnfiredFences.push_back(std::move(fence));
     } else {
         timestampAccepted &= mTracker.addVsyncTimestamp(signalTime);
     }
diff --git a/services/surfaceflinger/Scheduler/VSyncReactor.h b/services/surfaceflinger/Scheduler/VSyncReactor.h
index 449d4c3..6a1950a 100644
--- a/services/surfaceflinger/Scheduler/VSyncReactor.h
+++ b/services/surfaceflinger/Scheduler/VSyncReactor.h
@@ -16,14 +16,18 @@
 
 #pragma once
 
-#include <android-base/thread_annotations.h>
-#include <ui/FenceTime.h>
 #include <memory>
 #include <mutex>
 #include <unordered_map>
 #include <vector>
-#include "TimeKeeper.h"
+
+#include <android-base/thread_annotations.h>
+#include <ui/FenceTime.h>
+
+#include <scheduler/TimeKeeper.h>
+
 #include "VsyncController.h"
+
 namespace android::scheduler {
 
 class Clock;
@@ -37,7 +41,7 @@
                  bool supportKernelIdleTimer);
     ~VSyncReactor();
 
-    bool addPresentFence(const std::shared_ptr<android::FenceTime>& fence) final;
+    bool addPresentFence(std::shared_ptr<FenceTime>) final;
     void setIgnorePresentFences(bool ignore) final;
 
     void startPeriodTransition(nsecs_t period) final;
diff --git a/services/surfaceflinger/Scheduler/VSyncTracker.h b/services/surfaceflinger/Scheduler/VSyncTracker.h
index 95750ad..76315d2 100644
--- a/services/surfaceflinger/Scheduler/VSyncTracker.h
+++ b/services/surfaceflinger/Scheduler/VSyncTracker.h
@@ -17,7 +17,9 @@
 #pragma once
 
 #include <utils/Timers.h>
-#include "Fps.h"
+
+#include <scheduler/Fps.h>
+
 #include "VSyncDispatch.h"
 
 namespace android::scheduler {
diff --git a/services/surfaceflinger/Scheduler/VsyncConfiguration.cpp b/services/surfaceflinger/Scheduler/VsyncConfiguration.cpp
index 43e0297..ff31651 100644
--- a/services/surfaceflinger/Scheduler/VsyncConfiguration.cpp
+++ b/services/surfaceflinger/Scheduler/VsyncConfiguration.cpp
@@ -48,14 +48,12 @@
 }
 
 PhaseOffsets::VsyncConfigSet VsyncConfiguration::getConfigsForRefreshRateLocked(Fps fps) const {
-    const auto iter = mOffsetsCache.find(fps);
-    if (iter != mOffsetsCache.end()) {
-        return iter->second;
+    if (const auto offsets = mOffsetsCache.get(fps)) {
+        return offsets->get();
     }
 
-    const auto offset = constructOffsets(fps.getPeriodNsecs());
-    mOffsetsCache[fps] = offset;
-    return offset;
+    const auto [it, _] = mOffsetsCache.try_emplace(fps, constructOffsets(fps.getPeriodNsecs()));
+    return it->second;
 }
 
 void VsyncConfiguration::dump(std::string& result) const {
diff --git a/services/surfaceflinger/Scheduler/VsyncConfiguration.h b/services/surfaceflinger/Scheduler/VsyncConfiguration.h
index 3e53b3f..02ebd70 100644
--- a/services/surfaceflinger/Scheduler/VsyncConfiguration.h
+++ b/services/surfaceflinger/Scheduler/VsyncConfiguration.h
@@ -17,13 +17,14 @@
 #pragma once
 
 #include <mutex>
-#include <type_traits>
-#include <unordered_map>
-#include <vector>
+#include <optional>
+#include <string>
 
+#include <ftl/small_map.h>
 #include <utils/Timers.h>
 
-#include "Fps.h"
+#include <scheduler/Fps.h>
+
 #include "VsyncModulator.h"
 
 namespace android::scheduler {
@@ -88,9 +89,8 @@
 
     VsyncConfigSet getConfigsForRefreshRateLocked(Fps fps) const REQUIRES(mLock);
 
-    mutable std::unordered_map<Fps, VsyncConfigSet, std::hash<Fps>, Fps::EqualsInBuckets>
-            mOffsetsCache GUARDED_BY(mLock);
-    std::atomic<Fps> mRefreshRateFps GUARDED_BY(mLock);
+    mutable ftl::SmallMap<Fps, VsyncConfigSet, 2, FpsApproxEqual> mOffsetsCache GUARDED_BY(mLock);
+    Fps mRefreshRateFps GUARDED_BY(mLock);
     mutable std::mutex mLock;
 };
 
diff --git a/services/surfaceflinger/Scheduler/VsyncController.h b/services/surfaceflinger/Scheduler/VsyncController.h
index 0f0df22..59f6537 100644
--- a/services/surfaceflinger/Scheduler/VsyncController.h
+++ b/services/surfaceflinger/Scheduler/VsyncController.h
@@ -17,19 +17,15 @@
 #pragma once
 
 #include <cstddef>
+#include <memory>
 
+#include <ui/FenceTime.h>
 #include <utils/Mutex.h>
 #include <utils/RefBase.h>
 #include <utils/Timers.h>
 
-#include <ui/FenceTime.h>
-
-#include <memory>
-
 namespace android::scheduler {
 
-class FenceTime;
-
 class VsyncController {
 public:
     virtual ~VsyncController();
@@ -43,7 +39,7 @@
      *                      an accurate prediction,
      *                      False otherwise
      */
-    virtual bool addPresentFence(const std::shared_ptr<android::FenceTime>&) = 0;
+    virtual bool addPresentFence(std::shared_ptr<FenceTime>) = 0;
 
     /*
      * Adds a hw sync timestamp to the model. The controller will use the timestamp
diff --git a/services/surfaceflinger/Scheduler/VsyncModulator.cpp b/services/surfaceflinger/Scheduler/VsyncModulator.cpp
index 245db0f..be57b2a 100644
--- a/services/surfaceflinger/Scheduler/VsyncModulator.cpp
+++ b/services/surfaceflinger/Scheduler/VsyncModulator.cpp
@@ -134,16 +134,27 @@
     return mVsyncConfig;
 }
 
-const VsyncModulator::VsyncConfig& VsyncModulator::getNextVsyncConfig() const {
+auto VsyncModulator::getNextVsyncConfigType() const -> VsyncConfigType {
     // Early offsets are used if we're in the middle of a refresh rate
     // change, or if we recently begin a transaction.
     if (!mEarlyWakeupRequests.empty() || mTransactionSchedule == Schedule::EarlyEnd ||
         mEarlyTransactionFrames > 0 || mRefreshRateChangePending) {
-        return mVsyncConfigSet.early;
+        return VsyncConfigType::Early;
     } else if (mEarlyGpuFrames > 0) {
-        return mVsyncConfigSet.earlyGpu;
+        return VsyncConfigType::EarlyGpu;
     } else {
-        return mVsyncConfigSet.late;
+        return VsyncConfigType::Late;
+    }
+}
+
+const VsyncModulator::VsyncConfig& VsyncModulator::getNextVsyncConfig() const {
+    switch (getNextVsyncConfigType()) {
+        case VsyncConfigType::Early:
+            return mVsyncConfigSet.early;
+        case VsyncConfigType::EarlyGpu:
+            return mVsyncConfigSet.earlyGpu;
+        case VsyncConfigType::Late:
+            return mVsyncConfigSet.late;
     }
 }
 
@@ -176,4 +187,9 @@
     static_cast<void>(updateVsyncConfigLocked());
 }
 
+bool VsyncModulator::isVsyncConfigDefault() const {
+    std::lock_guard<std::mutex> lock(mMutex);
+    return getNextVsyncConfigType() == VsyncConfigType::Late;
+}
+
 } // namespace android::scheduler
diff --git a/services/surfaceflinger/Scheduler/VsyncModulator.h b/services/surfaceflinger/Scheduler/VsyncModulator.h
index b2b0451..537cae1 100644
--- a/services/surfaceflinger/Scheduler/VsyncModulator.h
+++ b/services/surfaceflinger/Scheduler/VsyncModulator.h
@@ -25,6 +25,8 @@
 #include <binder/IBinder.h>
 #include <utils/Timers.h>
 
+#include "../WpHash.h"
+
 namespace android::scheduler {
 
 // State machine controlled by transaction flags. VsyncModulator switches to early phase offsets
@@ -107,11 +109,16 @@
 
     [[nodiscard]] VsyncConfigOpt onDisplayRefresh(bool usedGpuComposition);
 
+    [[nodiscard]] bool isVsyncConfigDefault() const;
+
 protected:
     // Called from unit tests as well
     void binderDied(const wp<IBinder>&) override EXCLUDES(mMutex);
 
 private:
+    enum class VsyncConfigType { Early, EarlyGpu, Late };
+
+    VsyncConfigType getNextVsyncConfigType() const REQUIRES(mMutex);
     const VsyncConfig& getNextVsyncConfig() const REQUIRES(mMutex);
     [[nodiscard]] VsyncConfig updateVsyncConfig() EXCLUDES(mMutex);
     [[nodiscard]] VsyncConfig updateVsyncConfigLocked() REQUIRES(mMutex);
@@ -124,12 +131,6 @@
     using Schedule = TransactionSchedule;
     std::atomic<Schedule> mTransactionSchedule = Schedule::Late;
 
-    struct WpHash {
-        size_t operator()(const wp<IBinder>& p) const {
-            return std::hash<IBinder*>()(p.unsafe_get());
-        }
-    };
-
     std::unordered_set<wp<IBinder>, WpHash> mEarlyWakeupRequests GUARDED_BY(mMutex);
     std::atomic<bool> mRefreshRateChangePending = false;
 
diff --git a/services/surfaceflinger/Scheduler/VsyncSchedule.cpp b/services/surfaceflinger/Scheduler/VsyncSchedule.cpp
new file mode 100644
index 0000000..e611658
--- /dev/null
+++ b/services/surfaceflinger/Scheduler/VsyncSchedule.cpp
@@ -0,0 +1,113 @@
+/*
+ * 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.
+ */
+
+#include <scheduler/Fps.h>
+#include <scheduler/Timer.h>
+
+#include "VsyncSchedule.h"
+
+#include "VSyncDispatchTimerQueue.h"
+#include "VSyncPredictor.h"
+#include "VSyncReactor.h"
+
+#include "../TracedOrdinal.h"
+
+namespace android::scheduler {
+
+class VsyncSchedule::PredictedVsyncTracer {
+    // Invoked from the thread of the VsyncDispatch owned by this VsyncSchedule.
+    constexpr auto makeVsyncCallback() {
+        return [this](nsecs_t, nsecs_t, nsecs_t) {
+            mParity = !mParity;
+            schedule();
+        };
+    }
+
+public:
+    explicit PredictedVsyncTracer(VsyncDispatch& dispatch)
+          : mRegistration(dispatch, makeVsyncCallback(), __func__) {
+        schedule();
+    }
+
+private:
+    void schedule() { mRegistration.schedule({0, 0, 0}); }
+
+    TracedOrdinal<bool> mParity = {"VSYNC-predicted", 0};
+    VSyncCallbackRegistration mRegistration;
+};
+
+VsyncSchedule::VsyncSchedule(FeatureFlags features)
+      : mTracker(createTracker()),
+        mDispatch(createDispatch(*mTracker)),
+        mController(createController(*mTracker, features)) {
+    if (features.test(Feature::kTracePredictedVsync)) {
+        mTracer = std::make_unique<PredictedVsyncTracer>(*mDispatch);
+    }
+}
+
+VsyncSchedule::VsyncSchedule(TrackerPtr tracker, DispatchPtr dispatch, ControllerPtr controller)
+      : mTracker(std::move(tracker)),
+        mDispatch(std::move(dispatch)),
+        mController(std::move(controller)) {}
+
+VsyncSchedule::VsyncSchedule(VsyncSchedule&&) = default;
+VsyncSchedule::~VsyncSchedule() = default;
+
+void VsyncSchedule::dump(std::string& out) const {
+    out.append("VsyncController:\n");
+    mController->dump(out);
+
+    out.append("VsyncDispatch:\n");
+    mDispatch->dump(out);
+}
+
+VsyncSchedule::TrackerPtr VsyncSchedule::createTracker() {
+    // TODO(b/144707443): Tune constants.
+    constexpr nsecs_t kInitialPeriod = (60_Hz).getPeriodNsecs();
+    constexpr size_t kHistorySize = 20;
+    constexpr size_t kMinSamplesForPrediction = 6;
+    constexpr uint32_t kDiscardOutlierPercent = 20;
+
+    return std::make_unique<VSyncPredictor>(kInitialPeriod, kHistorySize, kMinSamplesForPrediction,
+                                            kDiscardOutlierPercent);
+}
+
+VsyncSchedule::DispatchPtr VsyncSchedule::createDispatch(VsyncTracker& tracker) {
+    using namespace std::chrono_literals;
+
+    // TODO(b/144707443): Tune constants.
+    constexpr std::chrono::nanoseconds kGroupDispatchWithin = 500us;
+    constexpr std::chrono::nanoseconds kSnapToSameVsyncWithin = 3ms;
+
+    return std::make_unique<VSyncDispatchTimerQueue>(std::make_unique<Timer>(), tracker,
+                                                     kGroupDispatchWithin.count(),
+                                                     kSnapToSameVsyncWithin.count());
+}
+
+VsyncSchedule::ControllerPtr VsyncSchedule::createController(VsyncTracker& tracker,
+                                                             FeatureFlags features) {
+    // TODO(b/144707443): Tune constants.
+    constexpr size_t kMaxPendingFences = 20;
+    const bool hasKernelIdleTimer = features.test(Feature::kKernelIdleTimer);
+
+    auto reactor = std::make_unique<VSyncReactor>(std::make_unique<SystemClock>(), tracker,
+                                                  kMaxPendingFences, hasKernelIdleTimer);
+
+    reactor->setIgnorePresentFences(!features.test(Feature::kPresentFences));
+    return reactor;
+}
+
+} // namespace android::scheduler
diff --git a/services/surfaceflinger/Scheduler/VsyncSchedule.h b/services/surfaceflinger/Scheduler/VsyncSchedule.h
new file mode 100644
index 0000000..0d9b114
--- /dev/null
+++ b/services/surfaceflinger/Scheduler/VsyncSchedule.h
@@ -0,0 +1,75 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <memory>
+#include <string>
+
+#include <scheduler/Features.h>
+
+namespace android::scheduler {
+
+// TODO(b/185535769): Rename classes, and remove aliases.
+class VSyncDispatch;
+class VSyncTracker;
+
+class VsyncController;
+using VsyncDispatch = VSyncDispatch;
+using VsyncTracker = VSyncTracker;
+
+// Schedule that synchronizes to hardware VSYNC of a physical display.
+class VsyncSchedule {
+public:
+    explicit VsyncSchedule(FeatureFlags);
+    VsyncSchedule(VsyncSchedule&&);
+    ~VsyncSchedule();
+
+    // TODO(b/185535769): Hide behind API.
+    const VsyncTracker& getTracker() const { return *mTracker; }
+    VsyncTracker& getTracker() { return *mTracker; }
+    VsyncController& getController() { return *mController; }
+
+    // TODO(b/185535769): Remove once VsyncSchedule owns all registrations.
+    VsyncDispatch& getDispatch() { return *mDispatch; }
+
+    void dump(std::string&) const;
+
+private:
+    friend class TestableScheduler;
+
+    using TrackerPtr = std::unique_ptr<VsyncTracker>;
+    using DispatchPtr = std::unique_ptr<VsyncDispatch>;
+    using ControllerPtr = std::unique_ptr<VsyncController>;
+
+    // For tests.
+    VsyncSchedule(TrackerPtr, DispatchPtr, ControllerPtr);
+
+    static TrackerPtr createTracker();
+    static DispatchPtr createDispatch(VsyncTracker&);
+    static ControllerPtr createController(VsyncTracker&, FeatureFlags);
+
+    class PredictedVsyncTracer;
+    using TracerPtr = std::unique_ptr<PredictedVsyncTracer>;
+
+    // Effectively const except in move constructor.
+    TrackerPtr mTracker;
+    DispatchPtr mDispatch;
+    ControllerPtr mController;
+    TracerPtr mTracer;
+};
+
+} // namespace android::scheduler
diff --git a/services/surfaceflinger/DisplayHardware/Hash.h b/services/surfaceflinger/Scheduler/include/scheduler/Features.h
similarity index 68%
copy from services/surfaceflinger/DisplayHardware/Hash.h
copy to services/surfaceflinger/Scheduler/include/scheduler/Features.h
index a7b6c71..0e96678 100644
--- a/services/surfaceflinger/DisplayHardware/Hash.h
+++ b/services/surfaceflinger/Scheduler/include/scheduler/Features.h
@@ -16,12 +16,19 @@
 
 #pragma once
 
+#include <ftl/Flags.h>
+
 #include <cstdint>
-#include <string_view>
 
-namespace android {
+namespace android::scheduler {
 
-// CityHash64 implementation that only hashes at most the first 16 characters of the given string.
-uint64_t cityHash64Len0To16(std::string_view sv);
+enum class Feature : std::uint8_t {
+    kPresentFences = 0b1,
+    kKernelIdleTimer = 0b10,
+    kContentDetection = 0b100,
+    kTracePredictedVsync = 0b1000,
+};
 
-} // namespace android
\ No newline at end of file
+using FeatureFlags = Flags<Feature>;
+
+} // namespace android::scheduler
diff --git a/services/surfaceflinger/Scheduler/include/scheduler/Fps.h b/services/surfaceflinger/Scheduler/include/scheduler/Fps.h
new file mode 100644
index 0000000..bd4f409
--- /dev/null
+++ b/services/surfaceflinger/Scheduler/include/scheduler/Fps.h
@@ -0,0 +1,154 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <cmath>
+#include <limits>
+#include <ostream>
+#include <string>
+#include <type_traits>
+
+#include <android-base/stringprintf.h>
+#include <utils/Timers.h>
+
+namespace android {
+
+// Frames per second, stored as floating-point frequency. Provides conversion from/to period in
+// nanoseconds, and relational operators with precision threshold.
+//
+//     const Fps fps = 60_Hz;
+//
+//     using namespace fps_approx_ops;
+//     assert(fps == Fps::fromPeriodNsecs(16'666'667));
+//
+class Fps {
+public:
+    constexpr Fps() = default;
+
+    static constexpr Fps fromValue(float frequency) {
+        return frequency > 0.f ? Fps(frequency, static_cast<nsecs_t>(1e9f / frequency)) : Fps();
+    }
+
+    static constexpr Fps fromPeriodNsecs(nsecs_t period) {
+        return period > 0 ? Fps(1e9f / period, period) : Fps();
+    }
+
+    constexpr bool isValid() const { return mFrequency > 0.f; }
+
+    constexpr float getValue() const { return mFrequency; }
+    int getIntValue() const { return static_cast<int>(std::round(mFrequency)); }
+
+    constexpr nsecs_t getPeriodNsecs() const { return mPeriod; }
+
+private:
+    constexpr Fps(float frequency, nsecs_t period) : mFrequency(frequency), mPeriod(period) {}
+
+    float mFrequency = 0.f;
+    nsecs_t mPeriod = 0;
+};
+
+struct FpsRange {
+    Fps min = Fps::fromValue(0.f);
+    Fps max = Fps::fromValue(std::numeric_limits<float>::max());
+
+    bool includes(Fps) const;
+};
+
+static_assert(std::is_trivially_copyable_v<Fps>);
+
+constexpr Fps operator""_Hz(unsigned long long frequency) {
+    return Fps::fromValue(static_cast<float>(frequency));
+}
+
+constexpr Fps operator""_Hz(long double frequency) {
+    return Fps::fromValue(static_cast<float>(frequency));
+}
+
+inline bool isStrictlyLess(Fps lhs, Fps rhs) {
+    return lhs.getValue() < rhs.getValue();
+}
+
+// Does not satisfy equivalence relation.
+inline bool isApproxEqual(Fps lhs, Fps rhs) {
+    // TODO(b/185536303): Replace with ULP distance.
+    return std::abs(lhs.getValue() - rhs.getValue()) < 0.001f;
+}
+
+// Does not satisfy strict weak order.
+inline bool isApproxLess(Fps lhs, Fps rhs) {
+    return isStrictlyLess(lhs, rhs) && !isApproxEqual(lhs, rhs);
+}
+
+namespace fps_approx_ops {
+
+inline bool operator==(Fps lhs, Fps rhs) {
+    return isApproxEqual(lhs, rhs);
+}
+
+inline bool operator<(Fps lhs, Fps rhs) {
+    return isApproxLess(lhs, rhs);
+}
+
+inline bool operator!=(Fps lhs, Fps rhs) {
+    return !isApproxEqual(lhs, rhs);
+}
+
+inline bool operator>(Fps lhs, Fps rhs) {
+    return isApproxLess(rhs, lhs);
+}
+
+inline bool operator<=(Fps lhs, Fps rhs) {
+    return !isApproxLess(rhs, lhs);
+}
+
+inline bool operator>=(Fps lhs, Fps rhs) {
+    return !isApproxLess(lhs, rhs);
+}
+
+inline bool operator==(FpsRange lhs, FpsRange rhs) {
+    return isApproxEqual(lhs.min, rhs.min) && isApproxEqual(lhs.max, rhs.max);
+}
+
+inline bool operator!=(FpsRange lhs, FpsRange rhs) {
+    return !(lhs == rhs);
+}
+
+} // namespace fps_approx_ops
+
+inline bool FpsRange::includes(Fps fps) const {
+    using fps_approx_ops::operator<=;
+    return min <= fps && fps <= max;
+}
+
+struct FpsApproxEqual {
+    bool operator()(Fps lhs, Fps rhs) const { return isApproxEqual(lhs, rhs); }
+};
+
+inline std::string to_string(Fps fps) {
+    return base::StringPrintf("%.2f Hz", fps.getValue());
+}
+
+inline std::ostream& operator<<(std::ostream& stream, Fps fps) {
+    return stream << to_string(fps);
+}
+
+inline std::string to_string(FpsRange range) {
+    const auto [min, max] = range;
+    return base::StringPrintf("[%s, %s]", to_string(min).c_str(), to_string(max).c_str());
+}
+
+} // namespace android
diff --git a/services/surfaceflinger/Scheduler/Seamlessness.h b/services/surfaceflinger/Scheduler/include/scheduler/Seamlessness.h
similarity index 67%
rename from services/surfaceflinger/Scheduler/Seamlessness.h
rename to services/surfaceflinger/Scheduler/include/scheduler/Seamlessness.h
index 3e42a4d..93bf726 100644
--- a/services/surfaceflinger/Scheduler/Seamlessness.h
+++ b/services/surfaceflinger/Scheduler/include/scheduler/Seamlessness.h
@@ -16,11 +16,11 @@
 
 #pragma once
 
-#include <cstring>
 #include <ostream>
 
-namespace android {
-namespace scheduler {
+#include <ftl/enum.h>
+
+namespace android::scheduler {
 
 // The seamlessness requirement of a Layer.
 enum class Seamlessness {
@@ -31,24 +31,14 @@
     // Indicates no preference for seamlessness. For such layers the system will
     // prefer seamless switches, but also non-seamless switches to the group of the
     // default config are allowed.
-    Default
+    Default,
+
+    ftl_last = Default
 };
 
-inline std::string toString(Seamlessness seamlessness) {
-    switch (seamlessness) {
-        case Seamlessness::OnlySeamless:
-            return "OnlySeamless";
-        case Seamlessness::SeamedAndSeamless:
-            return "SeamedAndSeamless";
-        case Seamlessness::Default:
-            return "Default";
-    }
-}
-
 // Used by gtest
-inline std::ostream& operator<<(std::ostream& os, Seamlessness val) {
-    return os << toString(val);
+inline std::ostream& operator<<(std::ostream& os, Seamlessness s) {
+    return os << ftl::enum_string(s);
 }
 
-} // namespace scheduler
-} // namespace android
+} // namespace android::scheduler
diff --git a/services/surfaceflinger/Scheduler/TimeKeeper.h b/services/surfaceflinger/Scheduler/include/scheduler/TimeKeeper.h
similarity index 81%
rename from services/surfaceflinger/Scheduler/TimeKeeper.h
rename to services/surfaceflinger/Scheduler/include/scheduler/TimeKeeper.h
index 40dd841..319390b 100644
--- a/services/surfaceflinger/Scheduler/TimeKeeper.h
+++ b/services/surfaceflinger/Scheduler/include/scheduler/TimeKeeper.h
@@ -16,14 +16,17 @@
 
 #pragma once
 
-#include <utils/Timers.h>
 #include <functional>
+#include <string>
+
+#include <utils/Timers.h>
 
 namespace android::scheduler {
 
 class Clock {
 public:
     virtual ~Clock();
+
     /*
      * Returns the SYSTEM_TIME_MONOTONIC, used by testing infra to stub time.
      */
@@ -31,8 +34,9 @@
 
 protected:
     Clock() = default;
-    Clock(Clock const&) = delete;
-    Clock& operator=(Clock const&) = delete;
+
+    Clock(const Clock&) = delete;
+    Clock& operator=(const Clock&) = delete;
 };
 
 /*
@@ -46,19 +50,20 @@
      * Arms callback to fired when time is current based on CLOCK_MONOTONIC
      * There is only one timer, and subsequent calls will reset the callback function and the time.
      */
-    virtual void alarmAt(std::function<void()> const& callback, nsecs_t time) = 0;
+    virtual void alarmAt(std::function<void()>, nsecs_t time) = 0;
 
     /*
      * Cancels an existing pending callback
      */
     virtual void alarmCancel() = 0;
 
-    virtual void dump(std::string& result) const = 0;
+    virtual void dump(std::string&) const = 0;
 
 protected:
-    TimeKeeper(TimeKeeper const&) = delete;
-    TimeKeeper& operator=(TimeKeeper const&) = delete;
     TimeKeeper() = default;
+
+    TimeKeeper(const TimeKeeper&) = delete;
+    TimeKeeper& operator=(const TimeKeeper&) = delete;
 };
 
 } // namespace android::scheduler
diff --git a/services/surfaceflinger/Scheduler/Timer.h b/services/surfaceflinger/Scheduler/include/scheduler/Timer.h
similarity index 71%
rename from services/surfaceflinger/Scheduler/Timer.h
rename to services/surfaceflinger/Scheduler/include/scheduler/Timer.h
index 69ce079..58ad6cb 100644
--- a/services/surfaceflinger/Scheduler/Timer.h
+++ b/services/surfaceflinger/Scheduler/include/scheduler/Timer.h
@@ -16,11 +16,14 @@
 
 #pragma once
 
-#include "TimeKeeper.h"
+#include <array>
+#include <functional>
+#include <mutex>
+#include <thread>
 
 #include <android-base/thread_annotations.h>
-#include <array>
-#include <thread>
+
+#include <scheduler/TimeKeeper.h>
 
 namespace android::scheduler {
 
@@ -28,23 +31,38 @@
 public:
     Timer();
     ~Timer();
+
     nsecs_t now() const final;
 
     // NB: alarmAt and alarmCancel are threadsafe; with the last-returning function being effectual
     //     Most users will want to serialize thes calls so as to be aware of the timer state.
-    void alarmAt(std::function<void()> const& cb, nsecs_t time) final;
+    void alarmAt(std::function<void()>, nsecs_t time) final;
     void alarmCancel() final;
-    void dump(std::string& result) const final;
+
+    void dump(std::string&) const final;
+
+protected:
+    // For unit testing
+    int mEpollFd = -1;
 
 private:
-    enum class DebugState { Reset, Running, Waiting, Reading, InCallback, Terminated };
-    void reset();
-    void cleanup();
-    void setDebugState(DebugState state) EXCLUDES(mMutex);
-    const char* strDebugState(DebugState state) const;
+    enum class DebugState {
+        Reset,
+        Running,
+        Waiting,
+        Reading,
+        InCallback,
+        Terminated,
+
+        ftl_last = Terminated
+    };
+
+    void reset() EXCLUDES(mMutex);
+    void cleanup() REQUIRES(mMutex);
+    void setDebugState(DebugState) EXCLUDES(mMutex);
 
     int mTimerFd = -1;
-    int mEpollFd = -1;
+
     std::array<int, 2> mPipes = {-1, -1};
 
     std::thread mDispatchThread;
@@ -54,6 +72,7 @@
 
     mutable std::mutex mMutex;
     std::function<void()> mCallback GUARDED_BY(mMutex);
+    bool mExpectingCallback GUARDED_BY(mMutex) = false;
     DebugState mDebugState GUARDED_BY(mMutex);
 };
 
diff --git a/services/surfaceflinger/Scheduler/Timer.cpp b/services/surfaceflinger/Scheduler/src/Timer.cpp
similarity index 78%
rename from services/surfaceflinger/Scheduler/Timer.cpp
rename to services/surfaceflinger/Scheduler/src/Timer.cpp
index c9c2d84..a4cf57f 100644
--- a/services/surfaceflinger/Scheduler/Timer.cpp
+++ b/services/surfaceflinger/Scheduler/src/Timer.cpp
@@ -16,24 +16,28 @@
 
 #undef LOG_TAG
 #define LOG_TAG "SchedulerTimer"
-#define ATRACE_TAG ATRACE_TAG_GRAPHICS
-#include <android-base/stringprintf.h>
-#include <log/log.h>
-#include <sys/epoll.h>
-#include <sys/timerfd.h>
-#include <sys/unistd.h>
-#include <utils/Trace.h>
+
 #include <chrono>
 #include <cstdint>
 
-#include "SchedulerUtils.h"
-#include "Timer.h"
+#include <sys/epoll.h>
+#include <sys/timerfd.h>
+#include <sys/unistd.h>
+
+#include <ftl/concat.h>
+#include <ftl/enum.h>
+#include <log/log.h>
+#include <utils/Trace.h>
+
+#include <scheduler/Timer.h>
 
 namespace android::scheduler {
-using base::StringAppendF;
 
-static constexpr size_t kReadPipe = 0;
-static constexpr size_t kWritePipe = 1;
+constexpr size_t kReadPipe = 0;
+constexpr size_t kWritePipe = 1;
+
+Clock::~Clock() = default;
+TimeKeeper::~TimeKeeper() = default;
 
 Timer::Timer() {
     reset();
@@ -47,13 +51,25 @@
 }
 
 void Timer::reset() {
-    cleanup();
-    mTimerFd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC | TFD_NONBLOCK);
-    mEpollFd = epoll_create1(EPOLL_CLOEXEC);
-    if (pipe2(mPipes.data(), O_CLOEXEC | O_NONBLOCK)) {
-        ALOGE("could not create TimerDispatch mPipes");
-        return;
-    };
+    std::function<void()> cb;
+    {
+        std::lock_guard lock(mMutex);
+        if (mExpectingCallback && mCallback) {
+            cb = mCallback;
+        }
+
+        cleanup();
+        mTimerFd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC | TFD_NONBLOCK);
+        mEpollFd = epoll_create1(EPOLL_CLOEXEC);
+        if (pipe2(mPipes.data(), O_CLOEXEC | O_NONBLOCK)) {
+            ALOGE("could not create TimerDispatch mPipes");
+        }
+    }
+    if (cb) {
+        setDebugState(DebugState::InCallback);
+        cb();
+        setDebugState(DebugState::Running);
+    }
     setDebugState(DebugState::Reset);
 }
 
@@ -77,6 +93,8 @@
         close(mPipes[kWritePipe]);
         mPipes[kWritePipe] = -1;
     }
+    mExpectingCallback = false;
+    mCallback = {};
 }
 
 void Timer::endDispatch() {
@@ -88,13 +106,14 @@
     return systemTime(SYSTEM_TIME_MONOTONIC);
 }
 
-void Timer::alarmAt(std::function<void()> const& cb, nsecs_t time) {
+void Timer::alarmAt(std::function<void()> callback, nsecs_t time) {
     std::lock_guard lock(mMutex);
     using namespace std::literals;
     static constexpr int ns_per_s =
             std::chrono::duration_cast<std::chrono::nanoseconds>(1s).count();
 
-    mCallback = cb;
+    mCallback = std::move(callback);
+    mExpectingCallback = true;
 
     struct itimerspec old_timer;
     struct itimerspec new_timer {
@@ -161,9 +180,6 @@
     }
 
     uint64_t iteration = 0;
-    char const traceNamePrefix[] = "TimerIteration #";
-    static constexpr size_t maxlen = arrayLen(traceNamePrefix) + max64print;
-    std::array<char, maxlen> str_buffer;
 
     while (true) {
         setDebugState(DebugState::Waiting);
@@ -172,9 +188,8 @@
 
         setDebugState(DebugState::Running);
         if (ATRACE_ENABLED()) {
-            snprintf(str_buffer.data(), str_buffer.size(), "%s%" PRIu64, traceNamePrefix,
-                     iteration++);
-            ATRACE_NAME(str_buffer.data());
+            ftl::Concat trace("TimerIteration #", iteration++);
+            ATRACE_NAME(trace.c_str());
         }
 
         if (nfds == -1) {
@@ -194,6 +209,7 @@
                 {
                     std::lock_guard lock(mMutex);
                     cb = mCallback;
+                    mExpectingCallback = false;
                 }
                 if (cb) {
                     setDebugState(DebugState::InCallback);
@@ -215,26 +231,11 @@
     mDebugState = state;
 }
 
-const char* Timer::strDebugState(DebugState state) const {
-    switch (state) {
-        case DebugState::Reset:
-            return "Reset";
-        case DebugState::Running:
-            return "Running";
-        case DebugState::Waiting:
-            return "Waiting";
-        case DebugState::Reading:
-            return "Reading";
-        case DebugState::InCallback:
-            return "InCallback";
-        case DebugState::Terminated:
-            return "Terminated";
-    }
-}
-
 void Timer::dump(std::string& result) const {
     std::lock_guard lock(mMutex);
-    StringAppendF(&result, "\t\tDebugState: %s\n", strDebugState(mDebugState));
+    result.append("\t\tDebugState: ");
+    result.append(ftl::enum_string(mDebugState));
+    result.push_back('\n');
 }
 
 } // namespace android::scheduler
diff --git a/services/surfaceflinger/tests/unittests/AsyncCallRecorder.h b/services/surfaceflinger/Scheduler/tests/AsyncCallRecorder.h
similarity index 98%
rename from services/surfaceflinger/tests/unittests/AsyncCallRecorder.h
rename to services/surfaceflinger/Scheduler/tests/AsyncCallRecorder.h
index 8bed766..57f0dab 100644
--- a/services/surfaceflinger/tests/unittests/AsyncCallRecorder.h
+++ b/services/surfaceflinger/Scheduler/tests/AsyncCallRecorder.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2018 The Android Open Source Project
+ * 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.
diff --git a/services/surfaceflinger/tests/unittests/TimerTest.cpp b/services/surfaceflinger/Scheduler/tests/TimerTest.cpp
similarity index 61%
rename from services/surfaceflinger/tests/unittests/TimerTest.cpp
rename to services/surfaceflinger/Scheduler/tests/TimerTest.cpp
index cda6bbf..47d968c 100644
--- a/services/surfaceflinger/tests/unittests/TimerTest.cpp
+++ b/services/surfaceflinger/Scheduler/tests/TimerTest.cpp
@@ -14,32 +14,48 @@
  * limitations under the License.
  */
 
-#include "AsyncCallRecorder.h"
-#include "Scheduler/TimeKeeper.h"
-#include "Scheduler/Timer.h"
-
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
 
-using namespace testing;
-using namespace std::literals;
+#include <scheduler/TimeKeeper.h>
+#include <scheduler/Timer.h>
+
+#include "AsyncCallRecorder.h"
 
 namespace android::scheduler {
 
+struct TestableTimer : public Timer {
+public:
+    void makeEpollError() {
+        // close the epoll file descriptor to cause an epoll error
+        close(mEpollFd);
+    }
+};
+
 struct TimerTest : testing::Test {
-    static constexpr int mIterations = 20;
+    static constexpr int kIterations = 20;
 
     AsyncCallRecorder<void (*)()> mCallbackRecorder;
-    Timer mTimer;
+    TestableTimer mTimer;
 
     void timerCallback() { mCallbackRecorder.recordCall(); }
 };
 
 TEST_F(TimerTest, callsCallbackIfScheduledInPast) {
-    for (int i = 0; i < mIterations; i++) {
-        mTimer.alarmAt(std::bind(&TimerTest::timerCallback, this), systemTime() - 10'000'00);
+    for (int i = 0; i < kIterations; i++) {
+        mTimer.alarmAt(std::bind(&TimerTest::timerCallback, this), systemTime() - 1'000'000);
         EXPECT_TRUE(mCallbackRecorder.waitForCall().has_value());
         EXPECT_FALSE(mCallbackRecorder.waitForUnexpectedCall().has_value());
     }
 }
+
+TEST_F(TimerTest, recoversAfterEpollError) {
+    for (int i = 0; i < kIterations; i++) {
+        mTimer.makeEpollError();
+        mTimer.alarmAt(std::bind(&TimerTest::timerCallback, this), systemTime() - 1'000'000);
+        EXPECT_TRUE(mCallbackRecorder.waitForCall().has_value());
+        EXPECT_FALSE(mCallbackRecorder.waitForUnexpectedCall().has_value());
+    }
+}
+
 } // namespace android::scheduler
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index ce793ad..9f5a7df 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -26,6 +26,7 @@
 
 #include <android-base/properties.h>
 #include <android/configuration.h>
+#include <android/gui/IDisplayEventConnection.h>
 #include <android/hardware/configstore/1.0/ISurfaceFlingerConfigs.h>
 #include <android/hardware/configstore/1.1/ISurfaceFlingerConfigs.h>
 #include <android/hardware/configstore/1.1/types.h>
@@ -49,9 +50,9 @@
 #include <cutils/compiler.h>
 #include <cutils/properties.h>
 #include <ftl/future.h>
+#include <ftl/small_map.h>
 #include <gui/BufferQueue.h>
 #include <gui/DebugEGLImageTracker.h>
-#include <gui/IDisplayEventConnection.h>
 #include <gui/IProducerListener.h>
 #include <gui/LayerDebugInfo.h>
 #include <gui/LayerMetadata.h>
@@ -65,8 +66,10 @@
 #include <private/gui/SyncFeatures.h>
 #include <processgroup/processgroup.h>
 #include <renderengine/RenderEngine.h>
+#include <renderengine/impl/ExternalTexture.h>
 #include <sys/types.h>
 #include <ui/ColorSpace.h>
+#include <ui/DataspaceUtils.h>
 #include <ui/DebugUtils.h>
 #include <ui/DisplayId.h>
 #include <ui/DisplayMode.h>
@@ -93,6 +96,8 @@
 #include <type_traits>
 #include <unordered_map>
 
+#include <ui/DisplayIdentification.h>
+#include "BackgroundExecutor.h"
 #include "BufferLayer.h"
 #include "BufferQueueLayer.h"
 #include "BufferStateLayer.h"
@@ -101,7 +106,6 @@
 #include "ContainerLayer.h"
 #include "DisplayDevice.h"
 #include "DisplayHardware/ComposerHal.h"
-#include "DisplayHardware/DisplayIdentification.h"
 #include "DisplayHardware/FramebufferSurface.h"
 #include "DisplayHardware/HWComposer.h"
 #include "DisplayHardware/Hal.h"
@@ -109,6 +113,7 @@
 #include "DisplayRenderArea.h"
 #include "EffectLayer.h"
 #include "Effects/Daltonizer.h"
+#include "FlagManager.h"
 #include "FpsReporter.h"
 #include "FrameTimeline/FrameTimeline.h"
 #include "FrameTracer/FrameTracer.h"
@@ -124,7 +129,6 @@
 #include "Scheduler/DispSyncSource.h"
 #include "Scheduler/EventThread.h"
 #include "Scheduler/LayerHistory.h"
-#include "Scheduler/MessageQueue.h"
 #include "Scheduler/Scheduler.h"
 #include "Scheduler/VsyncConfiguration.h"
 #include "Scheduler/VsyncController.h"
@@ -138,17 +142,21 @@
 #include "android-base/stringprintf.h"
 #include "android-base/strings.h"
 
+#include <aidl/android/hardware/graphics/common/DisplayDecorationSupport.h>
+#include <aidl/android/hardware/graphics/composer3/DisplayCapability.h>
+
 #define MAIN_THREAD ACQUIRE(mStateLock) RELEASE(mStateLock)
 
+// Note: The parentheses around `expr` are needed to deduce an lvalue or rvalue reference.
 #define ON_MAIN_THREAD(expr)                                       \
-    [&] {                                                          \
+    [&]() -> decltype(auto) {                                      \
         LOG_FATAL_IF(std::this_thread::get_id() != mMainThreadId); \
         UnnecessaryLock lock(mStateLock);                          \
         return (expr);                                             \
     }()
 
 #define MAIN_THREAD_GUARD(expr)                                    \
-    [&] {                                                          \
+    [&]() -> decltype(auto) {                                      \
         LOG_FATAL_IF(std::this_thread::get_id() != mMainThreadId); \
         MainThreadScopedGuard lock(SF_MAIN_THREAD);                \
         return (expr);                                             \
@@ -158,6 +166,12 @@
 #define NO_THREAD_SAFETY_ANALYSIS \
     _Pragma("GCC error \"Prefer MAIN_THREAD macros or {Conditional,Timed,Unnecessary}Lock.\"")
 
+using aidl::android::hardware::graphics::common::DisplayDecorationSupport;
+using aidl::android::hardware::graphics::composer3::Capability;
+using aidl::android::hardware::graphics::composer3::DisplayCapability;
+using KernelIdleTimerController =
+        ::android::scheduler::RefreshRateConfigs::KernelIdleTimerController;
+
 namespace android {
 
 using namespace std::string_literals;
@@ -168,6 +182,8 @@
 
 using android::hardware::power::Boost;
 using base::StringAppendF;
+using gui::DisplayInfo;
+using gui::IDisplayEventConnection;
 using gui::IWindowInfosListener;
 using gui::WindowInfo;
 using ui::ColorMode;
@@ -238,11 +254,6 @@
     const status_t status;
 };
 
-struct SCOPED_CAPABILITY UnnecessaryLock {
-    explicit UnnecessaryLock(Mutex& mutex) ACQUIRE(mutex) {}
-    ~UnnecessaryLock() RELEASE() {}
-};
-
 // TODO(b/141333600): Consolidate with DisplayMode::Builder::getDefaultDensity.
 constexpr float FALLBACK_DENSITY = ACONFIGURATION_DENSITY_TV;
 
@@ -261,52 +272,33 @@
     return dataspace == Dataspace::V0_SRGB || dataspace == Dataspace::DISPLAY_P3;
 }
 
-class FrameRateFlexibilityToken : public BBinder {
-public:
-    FrameRateFlexibilityToken(std::function<void()> callback) : mCallback(callback) {}
-    virtual ~FrameRateFlexibilityToken() { mCallback(); }
-
-private:
-    std::function<void()> mCallback;
-};
-
-enum Permission {
-    ACCESS_SURFACE_FLINGER = 0x1,
-    ROTATE_SURFACE_FLINGER = 0x2,
-};
-
-struct IdleTimerConfig {
-    int32_t timeoutMs;
-    bool supportKernelIdleTimer;
-};
-
-IdleTimerConfig getIdleTimerConfiguration(DisplayId displayId) {
-    // TODO(adyabr): use ro.surface_flinger.* namespace
-
+std::chrono::milliseconds getIdleTimerTimeout(DisplayId displayId) {
     const auto displayIdleTimerMsKey = [displayId] {
         std::stringstream ss;
         ss << "debug.sf.set_idle_timer_ms_" << displayId.value;
         return ss.str();
     }();
 
+    const int32_t displayIdleTimerMs = base::GetIntProperty(displayIdleTimerMsKey, 0);
+    if (displayIdleTimerMs > 0) {
+        return std::chrono::milliseconds(displayIdleTimerMs);
+    }
+
+    const int32_t setIdleTimerMs = base::GetIntProperty("debug.sf.set_idle_timer_ms", 0);
+    const int32_t millis = setIdleTimerMs ? setIdleTimerMs : sysprop::set_idle_timer_ms(0);
+    return std::chrono::milliseconds(millis);
+}
+
+bool getKernelIdleTimerSyspropConfig(DisplayId displayId) {
     const auto displaySupportKernelIdleTimerKey = [displayId] {
         std::stringstream ss;
         ss << "debug.sf.support_kernel_idle_timer_" << displayId.value;
         return ss.str();
     }();
 
-    const int32_t displayIdleTimerMs = base::GetIntProperty(displayIdleTimerMsKey, 0);
     const auto displaySupportKernelIdleTimer =
             base::GetBoolProperty(displaySupportKernelIdleTimerKey, false);
-
-    if (displayIdleTimerMs > 0) {
-        return {displayIdleTimerMs, displaySupportKernelIdleTimer};
-    }
-
-    const int32_t setIdleTimerMs = base::GetIntProperty("debug.sf.set_idle_timer_ms", 0);
-    const int32_t millis = setIdleTimerMs ? setIdleTimerMs : sysprop::set_idle_timer_ms(0);
-
-    return {millis, sysprop::support_kernel_idle_timer(false)};
+    return displaySupportKernelIdleTimer || sysprop::support_kernel_idle_timer(false);
 }
 
 }  // namespace anonymous
@@ -320,6 +312,7 @@
 const String16 sControlDisplayBrightness("android.permission.CONTROL_DISPLAY_BRIGHTNESS");
 const String16 sDump("android.permission.DUMP");
 const String16 sCaptureBlackoutContent("android.permission.CAPTURE_BLACKOUT_CONTENT");
+const String16 sInternalSystemWindow("android.permission.INTERNAL_SYSTEM_WINDOW");
 
 const char* KERNEL_IDLE_TIMER_PROP = "graphics.display.kernel_idle_timer.enabled";
 
@@ -331,16 +324,12 @@
 uint32_t SurfaceFlinger::maxGraphicsWidth;
 uint32_t SurfaceFlinger::maxGraphicsHeight;
 bool SurfaceFlinger::hasWideColorDisplay;
-ui::Rotation SurfaceFlinger::internalDisplayOrientation = ui::ROTATION_0;
-bool SurfaceFlinger::useColorManagement;
 bool SurfaceFlinger::useContextPriority;
 Dataspace SurfaceFlinger::defaultCompositionDataspace = Dataspace::V0_SRGB;
 ui::PixelFormat SurfaceFlinger::defaultCompositionPixelFormat = ui::PixelFormat::RGBA_8888;
 Dataspace SurfaceFlinger::wideColorGamutCompositionDataspace = Dataspace::V0_SRGB;
 ui::PixelFormat SurfaceFlinger::wideColorGamutCompositionPixelFormat = ui::PixelFormat::RGBA_8888;
-bool SurfaceFlinger::useFrameRateApi;
-bool SurfaceFlinger::enableSdrDimming;
-bool SurfaceFlinger::enableLatchUnsignaled;
+LatchUnsignaledConfig SurfaceFlinger::enableLatchUnsignaledConfig;
 
 std::string decodeDisplayColorSetting(DisplayColorSetting displayColorSetting) {
     switch(displayColorSetting) {
@@ -364,20 +353,28 @@
             PermissionCache::checkPermission(sRotateSurfaceFlinger, pid, uid);
 }
 
+bool callingThreadHasInternalSystemWindowAccess() {
+    IPCThreadState* ipc = IPCThreadState::self();
+    const int pid = ipc->getCallingPid();
+    const int uid = ipc->getCallingUid();
+    return uid == AID_GRAPHICS || uid == AID_SYSTEM ||
+        PermissionCache::checkPermission(sInternalSystemWindow, pid, uid);
+}
+
 SurfaceFlinger::SurfaceFlinger(Factory& factory, SkipInitializationTag)
       : mFactory(factory),
+        mPid(getpid()),
         mInterceptor(mFactory.createSurfaceInterceptor()),
         mTimeStats(std::make_shared<impl::TimeStats>()),
         mFrameTracer(mFactory.createFrameTracer()),
-        mFrameTimeline(mFactory.createFrameTimeline(mTimeStats, getpid())),
-        mEventQueue(mFactory.createMessageQueue()),
+        mFrameTimeline(mFactory.createFrameTimeline(mTimeStats, mPid)),
         mCompositionEngine(mFactory.createCompositionEngine()),
         mHwcServiceName(base::GetProperty("debug.sf.hwc_service_name"s, "default"s)),
         mTunnelModeEnabledReporter(new TunnelModeEnabledReporter()),
         mInternalDisplayDensity(getDensityFromProperty("ro.sf.lcd_density", true)),
         mEmulatedDisplayDensity(getDensityFromProperty("qemu.sf.lcd_density", false)),
         mPowerAdvisor(*this),
-        mWindowInfosListenerInvoker(new WindowInfosListenerInvoker(this)) {
+        mWindowInfosListenerInvoker(sp<WindowInfosListenerInvoker>::make(*this)) {
     ALOGI("Using HWComposer service: %s", mHwcServiceName.c_str());
 }
 
@@ -396,11 +393,6 @@
     maxGraphicsHeight = std::max(max_graphics_height(0), 0);
 
     hasWideColorDisplay = has_wide_color_display(false);
-
-    // Android 12 and beyond, color management in display pipeline is turned on
-    // by default.
-    useColorManagement = use_color_management(true);
-
     mDefaultCompositionDataspace =
             static_cast<ui::Dataspace>(default_composition_dataspace(Dataspace::V0_SRGB));
     mWideColorGamutCompositionDataspace = static_cast<ui::Dataspace>(wcg_composition_dataspace(
@@ -423,22 +415,6 @@
 
     useContextPriority = use_context_priority(true);
 
-    using Values = SurfaceFlingerProperties::primary_display_orientation_values;
-    switch (primary_display_orientation(Values::ORIENTATION_0)) {
-        case Values::ORIENTATION_0:
-            break;
-        case Values::ORIENTATION_90:
-            internalDisplayOrientation = ui::ROTATION_90;
-            break;
-        case Values::ORIENTATION_180:
-            internalDisplayOrientation = ui::ROTATION_180;
-            break;
-        case Values::ORIENTATION_270:
-            internalDisplayOrientation = ui::ROTATION_270;
-            break;
-    }
-    ALOGV("Internal Display Orientation: %s", toCString(internalDisplayOrientation));
-
     mInternalDisplayPrimaries = sysprop::getDisplayNativePrimaries();
 
     // debugging stuff...
@@ -450,10 +426,7 @@
     property_get("ro.build.type", value, "user");
     mIsUserBuild = strcmp(value, "user") == 0;
 
-    property_get("debug.sf.showupdates", value, "0");
-    mDebugRegion = atoi(value);
-
-    ALOGI_IF(mDebugRegion, "showupdates enabled");
+    mDebugFlashDelay = base::GetUintProperty("debug.sf.showupdates"s, 0u);
 
     // DDMS debugging deprecated (b/120782499)
     property_get("debug.sf.ddms", value, "0");
@@ -501,28 +474,35 @@
         android::hardware::details::setTrebleTestingOverride(true);
     }
 
-    useFrameRateApi = use_frame_rate_api(true);
-
     mRefreshRateOverlaySpinner = property_get_bool("sf.debug.show_refresh_rate_overlay_spinner", 0);
 
-    // Debug property overrides ro. property
-    enableSdrDimming = property_get_bool("debug.sf.enable_sdr_dimming", enable_sdr_dimming(false));
+    if (!mIsUserBuild && base::GetBoolProperty("debug.sf.enable_transaction_tracing"s, true)) {
+        mTransactionTracing.emplace();
+    }
 
-    enableLatchUnsignaled = base::GetBoolProperty("debug.sf.latch_unsignaled"s, false);
+    mIgnoreHdrCameraLayers = ignore_hdr_camera_layers(false);
+}
+
+LatchUnsignaledConfig SurfaceFlinger::getLatchUnsignaledConfig() {
+    if (base::GetBoolProperty("debug.sf.latch_unsignaled"s, false)) {
+        return LatchUnsignaledConfig::Always;
+    }
+
+    if (base::GetBoolProperty("debug.sf.auto_latch_unsignaled"s, true)) {
+        return LatchUnsignaledConfig::AutoSingleLayer;
+    }
+
+    return LatchUnsignaledConfig::Disabled;
 }
 
 SurfaceFlinger::~SurfaceFlinger() = default;
 
-void SurfaceFlinger::onFirstRef() {
-    mEventQueue->init(this);
-}
-
 void SurfaceFlinger::binderDied(const wp<IBinder>&) {
     // the window manager died on us. prepare its eulogy.
     mBootFinished = false;
 
-    // Sever the link to inputflinger since its gone as well.
-    static_cast<void>(schedule([=] { mInputFlinger = nullptr; }));
+    // Sever the link to inputflinger since it's gone as well.
+    static_cast<void>(mScheduler->schedule([=] { mInputFlinger = nullptr; }));
 
     // restore initial conditions (default device unblank, etc)
     initializeDisplays();
@@ -532,16 +512,7 @@
 }
 
 void SurfaceFlinger::run() {
-    while (true) {
-        mEventQueue->waitMessage();
-    }
-}
-
-template <typename F, typename T>
-inline std::future<T> SurfaceFlinger::schedule(F&& f) {
-    auto [task, future] = makeTask(std::move(f));
-    mEventQueue->postMessage(std::move(task));
-    return std::move(future);
+    mScheduler->run();
 }
 
 sp<ISurfaceComposerClient> SurfaceFlinger::createConnection() {
@@ -590,13 +561,13 @@
 
     const ssize_t index = mCurrentState.displays.indexOfKey(displayToken);
     if (index < 0) {
-        ALOGE("%s: Invalid display token %p", __FUNCTION__, displayToken.get());
+        ALOGE("%s: Invalid display token %p", __func__, displayToken.get());
         return;
     }
 
     const DisplayDeviceState& state = mCurrentState.displays.valueAt(index);
     if (state.physical) {
-        ALOGE("%s: Invalid operation on physical display", __FUNCTION__);
+        ALOGE("%s: Invalid operation on physical display", __func__);
         return;
     }
     mInterceptor->saveDisplayDeletion(state.sequenceId);
@@ -650,17 +621,20 @@
 }
 
 std::vector<PhysicalDisplayId> SurfaceFlinger::getPhysicalDisplayIdsLocked() const {
-    const auto display = getDefaultDisplayDeviceLocked();
-    if (!display) {
-        return {};
-    }
-
     std::vector<PhysicalDisplayId> displayIds;
     displayIds.reserve(mPhysicalDisplayTokens.size());
-    displayIds.push_back(display->getPhysicalId());
+    const auto defaultDisplayId = [this]() REQUIRES(mStateLock) {
+        if (const auto display = getDefaultDisplayDeviceLocked()) {
+            return display->getPhysicalId();
+        }
+
+        // fallback to the internal display id if the active display is unknown
+        return getInternalDisplayIdLocked();
+    }();
+    displayIds.push_back(defaultDisplayId);
 
     for (const auto& [id, token] : mPhysicalDisplayTokens) {
-        if (id != display->getPhysicalId()) {
+        if (id != defaultDisplayId) {
             displayIds.push_back(id);
         }
     }
@@ -670,12 +644,7 @@
 
 status_t SurfaceFlinger::getPrimaryPhysicalDisplayId(PhysicalDisplayId* id) const {
     Mutex::Autolock lock(mStateLock);
-    const auto display = getInternalDisplayIdLocked();
-    if (!display) {
-        return NAME_NOT_FOUND;
-    }
-
-    *id = *display;
+    *id = getInternalDisplayIdLocked();
     return NO_ERROR;
 }
 
@@ -723,6 +692,7 @@
 
     mFrameTracer->initialize();
     mFrameTimeline->onBootFinished();
+    getRenderEngine().setEnableTracing(mFlagManager.use_skia_tracing());
 
     // wait patiently for the window manager death
     const String16 name("window");
@@ -742,7 +712,7 @@
 
     sp<IBinder> input(defaultServiceManager()->getService(String16("inputflinger")));
 
-    static_cast<void>(schedule([=] {
+    static_cast<void>(mScheduler->schedule([=] {
         if (input == nullptr) {
             ALOGE("Failed to link to input service");
         } else {
@@ -751,6 +721,17 @@
 
         readPersistentProperties();
         mPowerAdvisor.onBootFinished();
+        mPowerAdvisor.enablePowerHint(mFlagManager.use_adpf_cpu_hint());
+        if (mPowerAdvisor.usePowerHintSession()) {
+            std::optional<pid_t> renderEngineTid = getRenderEngine().getRenderEngineTid();
+            std::vector<int32_t> tidList;
+            tidList.emplace_back(gettid());
+            if (renderEngineTid.has_value()) {
+                tidList.emplace_back(*renderEngineTid);
+            }
+            mPowerAdvisor.startPowerHintSession(tidList);
+        }
+
         mBootStage = BootStage::FINISHED;
 
         if (property_get_bool("sf.debug.show_refresh_rate_overlay", false)) {
@@ -783,7 +764,7 @@
     if (std::this_thread::get_id() == mMainThreadId) {
         return genTextures();
     } else {
-        return schedule(genTextures).get();
+        return mScheduler->schedule(genTextures).get();
     }
 }
 
@@ -795,6 +776,25 @@
     ATRACE_INT("TexturePoolSize", mTexturePool.size());
 }
 
+static std::optional<renderengine::RenderEngine::RenderEngineType>
+chooseRenderEngineTypeViaSysProp() {
+    char prop[PROPERTY_VALUE_MAX];
+    property_get(PROPERTY_DEBUG_RENDERENGINE_BACKEND, prop, "");
+
+    if (strcmp(prop, "gles") == 0) {
+        return renderengine::RenderEngine::RenderEngineType::GLES;
+    } else if (strcmp(prop, "threaded") == 0) {
+        return renderengine::RenderEngine::RenderEngineType::THREADED;
+    } else if (strcmp(prop, "skiagl") == 0) {
+        return renderengine::RenderEngine::RenderEngineType::SKIA_GL;
+    } else if (strcmp(prop, "skiaglthreaded") == 0) {
+        return renderengine::RenderEngine::RenderEngineType::SKIA_GL_THREADED;
+    } else {
+        ALOGE("Unrecognized RenderEngineType %s; ignoring!", prop);
+        return {};
+    }
+}
+
 // Do not call property_set on main thread which will be blocked by init
 // Use StartPropertySetThread instead.
 void SurfaceFlinger::init() {
@@ -805,19 +805,21 @@
     // Get a RenderEngine for the given display / config (can't fail)
     // TODO(b/77156734): We need to stop casting and use HAL types when possible.
     // Sending maxFrameBufferAcquiredBuffers as the cache size is tightly tuned to single-display.
-    mCompositionEngine->setRenderEngine(renderengine::RenderEngine::create(
-            renderengine::RenderEngineCreationArgs::Builder()
-                    .setPixelFormat(static_cast<int32_t>(defaultCompositionPixelFormat))
-                    .setImageCacheSize(maxFrameBufferAcquiredBuffers)
-                    .setUseColorManagerment(useColorManagement)
-                    .setEnableProtectedContext(enable_protected_contents(false))
-                    .setPrecacheToneMapperShaderOnly(false)
-                    .setSupportsBackgroundBlur(mSupportsBlur)
-                    .setContextPriority(
-                            useContextPriority
-                                    ? renderengine::RenderEngine::ContextPriority::REALTIME
-                                    : renderengine::RenderEngine::ContextPriority::MEDIUM)
-                    .build()));
+    auto builder = renderengine::RenderEngineCreationArgs::Builder()
+                           .setPixelFormat(static_cast<int32_t>(defaultCompositionPixelFormat))
+                           .setImageCacheSize(maxFrameBufferAcquiredBuffers)
+                           .setUseColorManagerment(useColorManagement)
+                           .setEnableProtectedContext(enable_protected_contents(false))
+                           .setPrecacheToneMapperShaderOnly(false)
+                           .setSupportsBackgroundBlur(mSupportsBlur)
+                           .setContextPriority(
+                                   useContextPriority
+                                           ? renderengine::RenderEngine::ContextPriority::REALTIME
+                                           : renderengine::RenderEngine::ContextPriority::MEDIUM);
+    if (auto type = chooseRenderEngineTypeViaSysProp()) {
+        builder.setRenderEngineType(type.value());
+    }
+    mCompositionEngine->setRenderEngine(renderengine::RenderEngine::create(builder.build()));
     mMaxRenderTargetSize =
             std::min(getRenderEngine().getMaxTextureSize(), getRenderEngine().getMaxViewportDims());
 
@@ -828,9 +830,11 @@
 
     mCompositionEngine->setTimeStats(mTimeStats);
     mCompositionEngine->setHwComposer(getFactory().createHWComposer(mHwcServiceName));
-    mCompositionEngine->getHwComposer().setCallback(this);
+    mCompositionEngine->getHwComposer().setCallback(*this);
     ClientCache::getInstance().setRenderEngine(&getRenderEngine());
 
+    enableLatchUnsignaledConfig = getLatchUnsignaledConfig();
+
     if (base::GetBoolProperty("debug.sf.enable_hwc_vds"s, false)) {
         enableHalVirtualDisplays(true);
     }
@@ -838,10 +842,10 @@
     // Process any initial hotplug and resulting display changes.
     processDisplayHotplugEventsLocked();
     const auto display = getDefaultDisplayDeviceLocked();
-    LOG_ALWAYS_FATAL_IF(!display, "Missing internal display after registering composer callback.");
+    LOG_ALWAYS_FATAL_IF(!display, "Missing primary display after registering composer callback.");
     const auto displayId = display->getPhysicalId();
     LOG_ALWAYS_FATAL_IF(!getHwComposer().isConnected(displayId),
-                        "Internal display is disconnected.");
+                        "Primary display is disconnected.");
 
     // initialize our drawing state
     mDrawingState = mCurrentState;
@@ -870,7 +874,7 @@
     // Inform native graphics APIs whether the present timestamp is supported:
 
     const bool presentFenceReliable =
-            !getHwComposer().hasCapability(hal::Capability::PRESENT_FENCE_IS_NOT_RELIABLE);
+            !getHwComposer().hasCapability(Capability::PRESENT_FENCE_IS_NOT_RELIABLE);
     mStartPropertySetThread = getFactory().createStartPropertySetThread(presentFenceReliable);
 
     if (mStartPropertySetThread->Start() != NO_ERROR) {
@@ -916,9 +920,8 @@
 }
 
 bool SurfaceFlinger::authenticateSurfaceTextureLocked(
-        const sp<IGraphicBufferProducer>& bufferProducer) const {
-    sp<IBinder> surfaceTextureBinder(IInterface::asBinder(bufferProducer));
-    return mGraphicBufferProducerList.count(surfaceTextureBinder.get()) > 0;
+        const sp<IGraphicBufferProducer>& /* bufferProducer */) const {
+    return false;
 }
 
 status_t SurfaceFlinger::getSupportedFrameTimestamps(
@@ -935,7 +938,7 @@
     };
     ConditionalLock _l(mStateLock,
             std::this_thread::get_id() != mMainThreadId);
-    if (!getHwComposer().hasCapability(hal::Capability::PRESENT_FENCE_IS_NOT_RELIABLE)) {
+    if (!getHwComposer().hasCapability(Capability::PRESENT_FENCE_IS_NOT_RELIABLE)) {
         outSupported->push_back(FrameEvent::DISPLAY_PRESENT);
     }
     return NO_ERROR;
@@ -993,6 +996,7 @@
 
     info->secure = display->isSecure();
     info->deviceProductInfo = display->getDeviceProductInfo();
+    info->installOrientation = display->getPhysicalOrientation();
 
     return NO_ERROR;
 }
@@ -1010,24 +1014,26 @@
         return NAME_NOT_FOUND;
     }
 
-    info->activeDisplayModeId = static_cast<int32_t>(display->getActiveMode()->getId().value());
+    const auto displayId = PhysicalDisplayId::tryCast(display->getId());
+    if (!displayId) {
+        return INVALID_OPERATION;
+    }
+
+    info->activeDisplayModeId = display->getActiveMode()->getId().value();
 
     const auto& supportedModes = display->getSupportedModes();
     info->supportedDisplayModes.clear();
     info->supportedDisplayModes.reserve(supportedModes.size());
-    for (const auto& mode : supportedModes) {
+
+    for (const auto& [id, mode] : supportedModes) {
         ui::DisplayMode outMode;
-        outMode.id = static_cast<int32_t>(mode->getId().value());
+        outMode.id = static_cast<int32_t>(id.value());
 
-        auto width = mode->getWidth();
-        auto height = mode->getHeight();
+        auto [width, height] = mode->getResolution();
+        auto [xDpi, yDpi] = mode->getDpi();
 
-        auto xDpi = mode->getDpiX();
-        auto yDpi = mode->getDpiY();
-
-        if (display->isPrimary() &&
-            (internalDisplayOrientation == ui::ROTATION_90 ||
-             internalDisplayOrientation == ui::ROTATION_270)) {
+        if (const auto physicalOrientation = display->getPhysicalOrientation();
+            physicalOrientation == ui::ROTATION_90 || physicalOrientation == ui::ROTATION_270) {
             std::swap(width, height);
             std::swap(xDpi, yDpi);
         }
@@ -1046,7 +1052,7 @@
         outMode.refreshRate = Fps::fromPeriodNsecs(period).getValue();
 
         const auto vsyncConfigSet =
-                mVsyncConfiguration->getConfigsForRefreshRate(Fps(outMode.refreshRate));
+                mVsyncConfiguration->getConfigsForRefreshRate(Fps::fromValue(outMode.refreshRate));
         outMode.appVsyncOffset = vsyncConfigSet.late.appOffset;
         outMode.sfVsyncOffset = vsyncConfigSet.late.sfOffset;
         outMode.group = mode->getGroup();
@@ -1069,18 +1075,25 @@
     }
 
     info->activeColorMode = display->getCompositionDisplay()->getState().colorMode;
-    const auto displayId = display->getPhysicalId();
-    info->supportedColorModes = getDisplayColorModes(displayId);
-
+    info->supportedColorModes = getDisplayColorModes(*display);
     info->hdrCapabilities = display->getHdrCapabilities();
+
     info->autoLowLatencyModeSupported =
-            getHwComposer().hasDisplayCapability(displayId,
-                                                 hal::DisplayCapability::AUTO_LOW_LATENCY_MODE);
-    std::vector<hal::ContentType> types;
-    getHwComposer().getSupportedContentTypes(displayId, &types);
-    info->gameContentTypeSupported = std::any_of(types.begin(), types.end(), [](auto type) {
-        return type == hal::ContentType::GAME;
-    });
+            getHwComposer().hasDisplayCapability(*displayId,
+                                                 DisplayCapability::AUTO_LOW_LATENCY_MODE);
+    info->gameContentTypeSupported =
+            getHwComposer().supportsContentType(*displayId, hal::ContentType::GAME);
+
+    info->preferredBootDisplayMode = static_cast<ui::DisplayModeId>(-1);
+
+    if (getHwComposer().hasCapability(Capability::BOOT_DISPLAY_CONFIG)) {
+        if (const auto hwcId = getHwComposer().getPreferredBootDisplayMode(*displayId)) {
+            if (const auto modeId = display->translateModeId(*hwcId)) {
+                info->preferredBootDisplayMode = modeId->value();
+            }
+        }
+    }
+
     return NO_ERROR;
 }
 
@@ -1107,11 +1120,11 @@
     }
 
     if (display->setDesiredActiveMode(info)) {
-        // This will trigger HWC refresh without resetting the idle timer.
-        repaintEverythingForHWC();
+        scheduleComposite(FrameHint::kNone);
+
         // Start receiving vsync samples now, so that we can detect a period
         // switch.
-        mScheduler->resyncToHardwareVsync(true, info.mode->getVsyncPeriod());
+        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);
@@ -1121,14 +1134,14 @@
     }
 }
 
-status_t SurfaceFlinger::setActiveMode(const sp<IBinder>& displayToken, int modeId) {
+status_t SurfaceFlinger::setActiveModeFromBackdoor(const sp<IBinder>& displayToken, int modeId) {
     ATRACE_CALL();
 
     if (!displayToken) {
         return BAD_VALUE;
     }
 
-    auto future = schedule([=]() -> status_t {
+    auto future = mScheduler->schedule([=]() -> status_t {
         const auto display = ON_MAIN_THREAD(getDisplayDeviceLocked(displayToken));
         if (!display) {
             ALOGE("Attempt to set allowed display modes for invalid display token %p",
@@ -1162,7 +1175,7 @@
     return future.get();
 }
 
-void SurfaceFlinger::setActiveModeInternal() {
+void SurfaceFlinger::updateInternalStateWithChangedMode() {
     ATRACE_CALL();
 
     const auto display = getDefaultDisplayDeviceLocked();
@@ -1177,7 +1190,7 @@
         return;
     }
 
-    if (display->getActiveMode()->getSize() != upcomingModeInfo.mode->getSize()) {
+    if (display->getActiveMode()->getResolution() != upcomingModeInfo.mode->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).
@@ -1198,7 +1211,7 @@
     mRefreshRateStats->setRefreshRate(refreshRate);
     updatePhaseConfiguration(refreshRate);
 
-    if (upcomingModeInfo.event != Scheduler::ModeEvent::None) {
+    if (upcomingModeInfo.event != DisplayModeEvent::None) {
         mScheduler->onPrimaryDisplayModeChanged(mAppConnectionHandle, upcomingModeInfo.mode);
     }
 }
@@ -1213,13 +1226,14 @@
 void SurfaceFlinger::desiredActiveModeChangeDone(const sp<DisplayDevice>& display) {
     const auto refreshRate = display->getDesiredActiveMode()->mode->getFps();
     clearDesiredActiveModeState(display);
-    mScheduler->resyncToHardwareVsync(true, refreshRate.getPeriodNsecs());
+    mScheduler->resyncToHardwareVsync(true, refreshRate);
     updatePhaseConfiguration(refreshRate);
 }
 
-void SurfaceFlinger::performSetActiveMode() {
+void SurfaceFlinger::setActiveModeInHwcIfNeeded() {
     ATRACE_CALL();
-    ALOGV("%s", __FUNCTION__);
+
+    std::optional<PhysicalDisplayId> displayToUpdateImmediately;
 
     for (const auto& iter : mDisplays) {
         const auto& display = iter.second;
@@ -1254,8 +1268,7 @@
               to_string(display->getId()).c_str());
 
         if (display->getActiveMode()->getId() == desiredActiveMode->mode->getId()) {
-            // display is not valid or we are already in the requested mode
-            // on both cases there is nothing left to do
+            // we are already in the requested mode, there is nothing left to do
             desiredActiveModeChangeDone(display);
             continue;
         }
@@ -1266,7 +1279,7 @@
         const auto displayModeAllowed =
                 display->refreshRateConfigs().isModeAllowed(desiredActiveMode->mode->getId());
         if (!displayModeAllowed) {
-            desiredActiveModeChangeDone(display);
+            clearDesiredActiveModeState(display);
             continue;
         }
 
@@ -1286,33 +1299,52 @@
         }
         mScheduler->onNewVsyncPeriodChangeTimeline(outTimeline);
 
-        // Scheduler will submit an empty frame to HWC if needed.
-        mSetActiveModePending = true;
+        if (outTimeline.refreshRequired) {
+            scheduleComposite(FrameHint::kNone);
+            mSetActiveModePending = true;
+        } else {
+            // Updating the internal state should be done outside the loop,
+            // because it can recreate a DisplayDevice and modify mDisplays
+            // which will invalidate the iterator.
+            displayToUpdateImmediately = display->getPhysicalId();
+        }
+    }
+
+    if (displayToUpdateImmediately) {
+        updateInternalStateWithChangedMode();
+
+        const auto display = getDisplayDeviceLocked(*displayToUpdateImmediately);
+        const auto desiredActiveMode = display->getDesiredActiveMode();
+        if (desiredActiveMode &&
+            display->getActiveMode()->getId() == desiredActiveMode->mode->getId()) {
+            desiredActiveModeChangeDone(display);
+        }
     }
 }
 
 void SurfaceFlinger::disableExpensiveRendering() {
-    schedule([=]() MAIN_THREAD {
+    auto future = mScheduler->schedule([=]() MAIN_THREAD {
         ATRACE_CALL();
         if (mPowerAdvisor.isUsingExpensiveRendering()) {
-            const auto& displays = ON_MAIN_THREAD(mDisplays);
-            for (const auto& [_, display] : displays) {
-                const static constexpr auto kDisable = false;
+            for (const auto& [_, display] : mDisplays) {
+                constexpr bool kDisable = false;
                 mPowerAdvisor.setExpensiveRenderingExpected(display->getId(), kDisable);
             }
         }
-    }).wait();
+    });
+
+    future.wait();
 }
 
-std::vector<ColorMode> SurfaceFlinger::getDisplayColorModes(PhysicalDisplayId displayId) {
-    auto modes = getHwComposer().getColorModes(displayId);
-    bool isInternalDisplay = displayId == getInternalDisplayIdLocked();
+std::vector<ColorMode> SurfaceFlinger::getDisplayColorModes(const DisplayDevice& display) {
+    auto modes = getHwComposer().getColorModes(display.getPhysicalId());
 
-    // If it's built-in display and the configuration claims it's not wide color capable,
+    // If the display is internal and the configuration claims it's not wide color capable,
     // filter out all wide color modes. The typical reason why this happens is that the
     // hardware is not good enough to support GPU composition of wide color, and thus the
     // OEMs choose to disable this capability.
-    if (isInternalDisplay && !hasWideColorDisplay) {
+    if (display.getConnectionType() == ui::DisplayConnectionType::Internal &&
+        !hasWideColorDisplay) {
         const auto newEnd = std::remove_if(modes.begin(), modes.end(), isWideColorMode);
         modes.erase(newEnd, modes.end());
     }
@@ -1336,54 +1368,112 @@
 }
 
 status_t SurfaceFlinger::setActiveColorMode(const sp<IBinder>& displayToken, ColorMode mode) {
-    schedule([=]() MAIN_THREAD {
-        const auto displayId = getPhysicalDisplayIdLocked(displayToken);
-        if (!displayId) {
-            ALOGE("Invalid display token %p", displayToken.get());
-            return;
-        }
-        const auto modes = getDisplayColorModes(*displayId);
-        bool exists = std::find(std::begin(modes), std::end(modes), mode) != std::end(modes);
-        if (mode < ColorMode::NATIVE || !exists) {
-            ALOGE("Attempt to set invalid active color mode %s (%d) for display token %p",
-                  decodeColorMode(mode).c_str(), mode, displayToken.get());
-            return;
-        }
+    if (!displayToken) {
+        return BAD_VALUE;
+    }
+
+    auto future = mScheduler->schedule([=]() MAIN_THREAD -> status_t {
         const auto display = getDisplayDeviceLocked(displayToken);
         if (!display) {
             ALOGE("Attempt to set active color mode %s (%d) for invalid display token %p",
                   decodeColorMode(mode).c_str(), mode, displayToken.get());
-        } else if (display->isVirtual()) {
+            return NAME_NOT_FOUND;
+        }
+
+        if (display->isVirtual()) {
             ALOGW("Attempt to set active color mode %s (%d) for virtual display",
                   decodeColorMode(mode).c_str(), mode);
-        } else {
-            display->getCompositionDisplay()->setColorProfile(
-                    compositionengine::Output::ColorProfile{mode, Dataspace::UNKNOWN,
-                                                            RenderIntent::COLORIMETRIC,
-                                                            Dataspace::UNKNOWN});
+            return INVALID_OPERATION;
         }
-    }).wait();
 
+        const auto modes = getDisplayColorModes(*display);
+        const bool exists = std::find(modes.begin(), modes.end(), mode) != modes.end();
+
+        if (mode < ColorMode::NATIVE || !exists) {
+            ALOGE("Attempt to set invalid active color mode %s (%d) for display token %p",
+                  decodeColorMode(mode).c_str(), mode, displayToken.get());
+            return BAD_VALUE;
+        }
+
+        display->getCompositionDisplay()->setColorProfile(
+                {mode, Dataspace::UNKNOWN, RenderIntent::COLORIMETRIC, Dataspace::UNKNOWN});
+
+        return NO_ERROR;
+    });
+
+    // TODO(b/195698395): Propagate error.
+    future.wait();
     return NO_ERROR;
 }
 
+status_t SurfaceFlinger::getBootDisplayModeSupport(bool* outSupport) const {
+    auto future = mScheduler->schedule([=]() MAIN_THREAD mutable -> status_t {
+        *outSupport = getHwComposer().hasCapability(Capability::BOOT_DISPLAY_CONFIG);
+        return NO_ERROR;
+    });
+    return future.get();
+}
+
+status_t SurfaceFlinger::setBootDisplayMode(const sp<IBinder>& displayToken,
+                                            ui::DisplayModeId modeId) {
+    const char* const whence = __func__;
+    auto future = mScheduler->schedule([=]() MAIN_THREAD -> status_t {
+        const auto display = getDisplayDeviceLocked(displayToken);
+        if (!display) {
+            ALOGE("%s: Invalid display token %p", whence, displayToken.get());
+            return NAME_NOT_FOUND;
+        }
+
+        if (display->isVirtual()) {
+            ALOGE("%s: Invalid operation on virtual display", whence);
+            return INVALID_OPERATION;
+        }
+
+        const auto displayId = display->getPhysicalId();
+        const auto mode = display->getMode(DisplayModeId{modeId});
+        if (!mode) {
+            ALOGE("%s: Invalid mode %d for display %s", whence, modeId,
+                  to_string(displayId).c_str());
+            return BAD_VALUE;
+        }
+
+        return getHwComposer().setBootDisplayMode(displayId, mode->getHwcId());
+    });
+    return future.get();
+}
+
+status_t SurfaceFlinger::clearBootDisplayMode(const sp<IBinder>& displayToken) {
+    const char* const whence = __func__;
+    auto future = mScheduler->schedule([=]() MAIN_THREAD -> status_t {
+        if (const auto displayId = getPhysicalDisplayIdLocked(displayToken)) {
+            return getHwComposer().clearBootDisplayMode(*displayId);
+        } else {
+            ALOGE("%s: Invalid display token %p", whence, displayToken.get());
+            return BAD_VALUE;
+        }
+    });
+    return future.get();
+}
+
 void SurfaceFlinger::setAutoLowLatencyMode(const sp<IBinder>& displayToken, bool on) {
-    static_cast<void>(schedule([=]() MAIN_THREAD {
+    const char* const whence = __func__;
+    static_cast<void>(mScheduler->schedule([=]() MAIN_THREAD {
         if (const auto displayId = getPhysicalDisplayIdLocked(displayToken)) {
             getHwComposer().setAutoLowLatencyMode(*displayId, on);
         } else {
-            ALOGE("%s: Invalid display token %p", __FUNCTION__, displayToken.get());
+            ALOGE("%s: Invalid display token %p", whence, displayToken.get());
         }
     }));
 }
 
 void SurfaceFlinger::setGameContentType(const sp<IBinder>& displayToken, bool on) {
-    static_cast<void>(schedule([=]() MAIN_THREAD {
+    const char* const whence = __func__;
+    static_cast<void>(mScheduler->schedule([=]() MAIN_THREAD {
         if (const auto displayId = getPhysicalDisplayIdLocked(displayToken)) {
             const auto type = on ? hal::ContentType::GAME : hal::ContentType::NONE;
             getHwComposer().setContentType(*displayId, type);
         } else {
-            ALOGE("%s: Invalid display token %p", __FUNCTION__, displayToken.get());
+            ALOGE("%s: Invalid display token %p", whence, displayToken.get());
         }
     }));
 }
@@ -1406,7 +1496,7 @@
 
     auto display = getDisplayDeviceLocked(displayToken);
     if (!display) {
-        ALOGE("%s: Invalid display token %p", __FUNCTION__, displayToken.get());
+        ALOGE("%s: Invalid display token %p", __func__, displayToken.get());
         return NAME_NOT_FOUND;
     }
 
@@ -1442,17 +1532,18 @@
 status_t SurfaceFlinger::setDisplayContentSamplingEnabled(const sp<IBinder>& displayToken,
                                                           bool enable, uint8_t componentMask,
                                                           uint64_t maxFrames) {
-    return schedule([=]() MAIN_THREAD -> status_t {
-               if (const auto displayId = getPhysicalDisplayIdLocked(displayToken)) {
-                   return getHwComposer().setDisplayContentSamplingEnabled(*displayId, enable,
-                                                                           componentMask,
-                                                                           maxFrames);
-               } else {
-                   ALOGE("%s: Invalid display token %p", __FUNCTION__, displayToken.get());
-                   return NAME_NOT_FOUND;
-               }
-           })
-            .get();
+    const char* const whence = __func__;
+    auto future = mScheduler->schedule([=]() MAIN_THREAD -> status_t {
+        if (const auto displayId = getPhysicalDisplayIdLocked(displayToken)) {
+            return getHwComposer().setDisplayContentSamplingEnabled(*displayId, enable,
+                                                                    componentMask, maxFrames);
+        } else {
+            ALOGE("%s: Invalid display token %p", whence, displayToken.get());
+            return NAME_NOT_FOUND;
+        }
+    });
+
+    return future.get();
 }
 
 status_t SurfaceFlinger::getDisplayedContentSample(const sp<IBinder>& displayToken,
@@ -1494,14 +1585,15 @@
 }
 
 status_t SurfaceFlinger::enableVSyncInjections(bool enable) {
-    schedule([=] {
+    auto future = mScheduler->schedule([=] {
         Mutex::Autolock lock(mStateLock);
 
         if (const auto handle = mScheduler->enableVSyncInjection(enable)) {
-            mEventQueue->setInjector(enable ? mScheduler->getEventConnection(handle) : nullptr);
+            mScheduler->setInjector(enable ? mScheduler->getEventConnection(handle) : nullptr);
         }
-    }).wait();
+    });
 
+    future.wait();
     return NO_ERROR;
 }
 
@@ -1517,12 +1609,14 @@
 
 status_t SurfaceFlinger::getLayerDebugInfo(std::vector<LayerDebugInfo>* outLayers) {
     outLayers->clear();
-    schedule([=] {
+    auto future = mScheduler->schedule([=] {
         const auto display = ON_MAIN_THREAD(getDefaultDisplayDeviceLocked());
         mDrawingState.traverseInZOrder([&](Layer* layer) {
             outLayers->push_back(layer->getLayerDebugInfo(display.get()));
         });
-    }).wait();
+    });
+
+    future.wait();
     return NO_ERROR;
 }
 
@@ -1540,7 +1634,7 @@
 status_t SurfaceFlinger::addRegionSamplingListener(const Rect& samplingArea,
                                                    const sp<IBinder>& stopLayerHandle,
                                                    const sp<IRegionSamplingListener>& listener) {
-    if (!listener || samplingArea == Rect::INVALID_RECT) {
+    if (!listener || samplingArea == Rect::INVALID_RECT || samplingArea.isEmpty()) {
         return BAD_VALUE;
     }
 
@@ -1606,28 +1700,60 @@
     if (!displayId) {
         return NAME_NOT_FOUND;
     }
-    *outSupport =
-            getHwComposer().hasDisplayCapability(*displayId, hal::DisplayCapability::BRIGHTNESS);
+    *outSupport = getHwComposer().hasDisplayCapability(*displayId, DisplayCapability::BRIGHTNESS);
     return NO_ERROR;
 }
 
+bool SurfaceFlinger::hasVisibleHdrLayer(const sp<DisplayDevice>& display) {
+    bool hasHdrLayers = false;
+    mDrawingState.traverse([&,
+                            compositionDisplay = display->getCompositionDisplay()](Layer* layer) {
+        hasHdrLayers |= (layer->isVisible() &&
+                         compositionDisplay->includesLayer(layer->getCompositionEngineLayerFE()) &&
+                         isHdrDataspace(layer->getDataSpace()));
+    });
+    return hasHdrLayers;
+}
+
 status_t SurfaceFlinger::setDisplayBrightness(const sp<IBinder>& displayToken,
                                               const gui::DisplayBrightness& brightness) {
     if (!displayToken) {
         return BAD_VALUE;
     }
 
-    return ftl::chain(schedule([=]() MAIN_THREAD {
+    const char* const whence = __func__;
+    return ftl::chain(mScheduler->schedule([=]() MAIN_THREAD {
                if (const auto display = getDisplayDeviceLocked(displayToken)) {
-                   if (enableSdrDimming) {
-                       display->getCompositionDisplay()
-                               ->setDisplayBrightness(brightness.sdrWhitePointNits,
-                                                      brightness.displayBrightnessNits);
+                   const bool supportsDisplayBrightnessCommand =
+                           getHwComposer().getComposer()->isSupported(
+                                   Hwc2::Composer::OptionalFeature::DisplayBrightnessCommand);
+                   // If we support applying display brightness as a command, then we also support
+                   // dimming SDR layers.
+                   if (supportsDisplayBrightnessCommand) {
+                       auto compositionDisplay = display->getCompositionDisplay();
+                       float currentDimmingRatio =
+                               compositionDisplay->editState().sdrWhitePointNits /
+                               compositionDisplay->editState().displayBrightnessNits;
+                       compositionDisplay->setDisplayBrightness(brightness.sdrWhitePointNits,
+                                                                brightness.displayBrightnessNits);
+                       MAIN_THREAD_GUARD(display->stageBrightness(brightness.displayBrightness));
+                       if (brightness.sdrWhitePointNits / brightness.displayBrightnessNits !=
+                           currentDimmingRatio) {
+                           scheduleComposite(FrameHint::kNone);
+                       } else {
+                           scheduleCommit(FrameHint::kNone);
+                       }
+                       return ftl::yield<status_t>(OK);
+                   } else {
+                       return getHwComposer()
+                               .setDisplayBrightness(display->getPhysicalId(),
+                                                     brightness.displayBrightness,
+                                                     Hwc2::Composer::DisplayBrightnessOptions{
+                                                             .applyImmediately = true});
                    }
-                   return getHwComposer().setDisplayBrightness(display->getPhysicalId(),
-                                                               brightness.displayBrightness);
+
                } else {
-                   ALOGE("%s: Invalid display token %p", __FUNCTION__, displayToken.get());
+                   ALOGE("%s: Invalid display token %p", whence, displayToken.get());
                    return ftl::yield<status_t>(NAME_NOT_FOUND);
                }
            }))
@@ -1683,12 +1809,29 @@
     Boost powerBoost = static_cast<Boost>(boostId);
 
     if (powerBoost == Boost::INTERACTION) {
-        mScheduler->notifyTouchEvent();
+        mScheduler->onTouchHint();
     }
 
     return NO_ERROR;
 }
 
+status_t SurfaceFlinger::getDisplayDecorationSupport(
+        const sp<IBinder>& displayToken,
+        std::optional<DisplayDecorationSupport>* outSupport) const {
+    if (!displayToken || !outSupport) {
+        return BAD_VALUE;
+    }
+
+    Mutex::Autolock lock(mStateLock);
+
+    const auto displayId = getPhysicalDisplayIdLocked(displayToken);
+    if (!displayId) {
+        return NAME_NOT_FOUND;
+    }
+    getHwComposer().getDisplayDecorationSupport(*displayId, outSupport);
+    return NO_ERROR;
+}
+
 // ----------------------------------------------------------------------------
 
 sp<IDisplayEventConnection> SurfaceFlinger::createDisplayEventConnection(
@@ -1700,21 +1843,26 @@
     return mScheduler->createDisplayEventConnection(handle, eventRegistration);
 }
 
-void SurfaceFlinger::signalTransaction() {
-    mScheduler->resetIdleTimer();
+void SurfaceFlinger::scheduleCommit(FrameHint hint) {
+    if (hint == FrameHint::kActive) {
+        mScheduler->resetIdleTimer();
+    }
     mPowerAdvisor.notifyDisplayUpdateImminent();
-    mEventQueue->invalidate();
+    mScheduler->scheduleFrame();
 }
 
-void SurfaceFlinger::signalLayerUpdate() {
-    mScheduler->resetIdleTimer();
-    mPowerAdvisor.notifyDisplayUpdateImminent();
-    mEventQueue->invalidate();
+void SurfaceFlinger::scheduleComposite(FrameHint hint) {
+    mMustComposite = true;
+    scheduleCommit(hint);
 }
 
-void SurfaceFlinger::signalRefresh() {
-    mRefreshPending = true;
-    mEventQueue->refresh();
+void SurfaceFlinger::scheduleRepaint() {
+    mGeometryDirty = true;
+    scheduleComposite(FrameHint::kActive);
+}
+
+void SurfaceFlinger::scheduleSample() {
+    static_cast<void>(mScheduler->schedule([this] { sample(); }));
 }
 
 nsecs_t SurfaceFlinger::getVsyncPeriodFromHWC() const {
@@ -1727,7 +1875,15 @@
 
 void SurfaceFlinger::onComposerHalVsync(hal::HWDisplayId hwcDisplayId, int64_t timestamp,
                                         std::optional<hal::VsyncPeriodNanos> vsyncPeriod) {
-    ATRACE_CALL();
+    const std::string tracePeriod = [vsyncPeriod]() {
+        if (ATRACE_ENABLED() && vsyncPeriod) {
+            std::stringstream ss;
+            ss << "(" << *vsyncPeriod << ")";
+            return ss.str();
+        }
+        return std::string();
+    }();
+    ATRACE_FORMAT("onComposerHalVsync%s", tracePeriod.c_str());
 
     Mutex::Autolock lock(mStateLock);
     const auto displayId = getHwComposer().toPhysicalDisplayId(hwcDisplayId);
@@ -1760,28 +1916,10 @@
     *compositorTiming = getBE().mCompositorTiming;
 }
 
-void SurfaceFlinger::changeRefreshRateLocked(const RefreshRate& refreshRate,
-                                             Scheduler::ModeEvent event) {
-    const auto display = getDefaultDisplayDeviceLocked();
-    if (!display || mBootStage != BootStage::FINISHED) {
-        return;
-    }
-    ATRACE_CALL();
-
-    // Don't do any updating if the current fps is the same as the new one.
-    if (!display->refreshRateConfigs().isModeAllowed(refreshRate.getModeId())) {
-        ALOGV("Skipping mode %d as it is not part of allowed modes",
-              refreshRate.getModeId().value());
-        return;
-    }
-
-    setDesiredActiveMode({refreshRate.getMode(), event});
-}
-
 void SurfaceFlinger::onComposerHalHotplug(hal::HWDisplayId hwcDisplayId,
                                           hal::Connection connection) {
-    ALOGI("%s(%" PRIu64 ", %s)", __func__, hwcDisplayId,
-          connection == hal::Connection::CONNECTED ? "connected" : "disconnected");
+    const bool connected = connection == hal::Connection::CONNECTED;
+    ALOGI("%s HAL display %" PRIu64, connected ? "Connecting" : "Disconnecting", hwcDisplayId);
 
     // Only lock if we're not on the main thread. This function is normally
     // called on a hwbinder thread, but for the primary display it's called on
@@ -1803,6 +1941,10 @@
         hal::HWDisplayId, const hal::VsyncPeriodChangeTimeline& timeline) {
     Mutex::Autolock lock(mStateLock);
     mScheduler->onNewVsyncPeriodChangeTimeline(timeline);
+
+    if (timeline.refreshRequired) {
+        scheduleComposite(FrameHint::kNone);
+    }
 }
 
 void SurfaceFlinger::onComposerHalSeamlessPossible(hal::HWDisplayId) {
@@ -1812,14 +1954,19 @@
 
 void SurfaceFlinger::onComposerHalRefresh(hal::HWDisplayId) {
     Mutex::Autolock lock(mStateLock);
-    repaintEverythingForHWC();
+    scheduleComposite(FrameHint::kNone);
+}
+
+void SurfaceFlinger::onComposerHalVsyncIdle(hal::HWDisplayId) {
+    ATRACE_CALL();
+    mScheduler->forceNextResync();
 }
 
 void SurfaceFlinger::setVsyncEnabled(bool enabled) {
     ATRACE_CALL();
 
     // On main thread to avoid race conditions with display power state.
-    static_cast<void>(schedule([=]() MAIN_THREAD {
+    static_cast<void>(mScheduler->schedule([=]() MAIN_THREAD {
         mHWCVsyncPendingState = enabled ? hal::Vsync::ENABLE : hal::Vsync::DISABLE;
 
         if (const auto display = getDefaultDisplayDeviceLocked();
@@ -1867,40 +2014,37 @@
                                                           : stats.vsyncTime + stats.vsyncPeriod;
 }
 
-void SurfaceFlinger::onMessageReceived(int32_t what, int64_t vsyncId, nsecs_t expectedVSyncTime) {
-    switch (what) {
-        case MessageQueue::INVALIDATE: {
-            onMessageInvalidate(vsyncId, expectedVSyncTime);
-            break;
-        }
-        case MessageQueue::REFRESH: {
-            onMessageRefresh();
-            break;
-        }
+bool SurfaceFlinger::commit(nsecs_t frameTime, int64_t vsyncId, nsecs_t expectedVsyncTime) {
+    MainThreadScopedGuard mainThreadGuard(SF_MAIN_THREAD);
+    // we set this once at the beginning of commit to ensure consistency throughout the whole frame
+    mPowerHintSessionData.sessionEnabled = mPowerAdvisor.usePowerHintSession();
+    if (mPowerHintSessionData.sessionEnabled) {
+        mPowerHintSessionData.commitStart = systemTime();
     }
-}
 
-void SurfaceFlinger::onMessageInvalidate(int64_t vsyncId, nsecs_t expectedVSyncTime) {
-    const nsecs_t frameStart = systemTime();
     // calculate the expected present time once and use the cached
     // value throughout this frame to make sure all layers are
     // seeing this same value.
-    if (expectedVSyncTime >= frameStart) {
-        mExpectedPresentTime = expectedVSyncTime;
+    if (expectedVsyncTime >= frameTime) {
+        mExpectedPresentTime = expectedVsyncTime;
     } else {
-        const DisplayStatInfo stats = mScheduler->getDisplayStatInfo(frameStart);
+        const DisplayStatInfo stats = mScheduler->getDisplayStatInfo(frameTime);
         mExpectedPresentTime = calculateExpectedPresentTime(stats);
     }
 
     const nsecs_t lastScheduledPresentTime = mScheduledPresentTime;
-    mScheduledPresentTime = expectedVSyncTime;
+    mScheduledPresentTime = expectedVsyncTime;
 
+    if (mPowerHintSessionData.sessionEnabled) {
+        mPowerAdvisor.setTargetWorkDuration(mExpectedPresentTime -
+                                            mPowerHintSessionData.commitStart);
+    }
     const auto vsyncIn = [&] {
         if (!ATRACE_ENABLED()) return 0.f;
         return (mExpectedPresentTime - systemTime()) / 1e6f;
     }();
-    ATRACE_FORMAT("onMessageInvalidate %" PRId64 " vsyncIn %.2fms%s", vsyncId, vsyncIn,
-                  mExpectedPresentTime == expectedVSyncTime ? "" : " (adjusted)");
+    ATRACE_FORMAT("%s %" PRId64 " vsyncIn %.2fms%s", __func__, vsyncId, vsyncIn,
+                  mExpectedPresentTime == expectedVsyncTime ? "" : " (adjusted)");
 
     // When Backpressure propagation is enabled we want to give a small grace period
     // for the present fence to fire instead of just giving up on this frame to handle cases
@@ -1947,11 +2091,11 @@
     }
 
     // If we are in the middle of a mode change and the fence hasn't
-    // fired yet just wait for the next invalidate
+    // fired yet just wait for the next commit.
     if (mSetActiveModePending) {
         if (framePending) {
-            mEventQueue->invalidate();
-            return;
+            mScheduler->scheduleFrame();
+            return false;
         }
 
         // We received the present fence from the HWC, so we assume it successfully updated
@@ -1959,50 +2103,64 @@
         mSetActiveModePending = false;
         {
             Mutex::Autolock lock(mStateLock);
-            setActiveModeInternal();
+            updateInternalStateWithChangedMode();
         }
     }
 
     if (framePending) {
         if ((hwcFrameMissed && !gpuFrameMissed) || mPropagateBackpressureClientComposition) {
-            signalLayerUpdate();
-            return;
+            scheduleCommit(FrameHint::kNone);
+            return false;
         }
     }
 
     if (mTracingEnabledChanged) {
-        mTracingEnabled = mTracing.isEnabled();
+        mLayerTracingEnabled = mLayerTracing.isEnabled();
         mTracingEnabledChanged = false;
     }
 
     if (mRefreshRateOverlaySpinner) {
-        if (Mutex::Autolock lock(mStateLock);
-            const auto display = getDefaultDisplayDeviceLocked()) {
-            if (display) {
-                display->onInvalidate();
-            } else {
-                ALOGW("%s: default display is null", __func__);
-            }
+        Mutex::Autolock lock(mStateLock);
+        if (const auto display = getDefaultDisplayDeviceLocked()) {
+            display->animateRefreshRateOverlay();
         }
     }
 
-    bool refreshNeeded;
+    // Composite if transactions were committed, or if requested by HWC.
+    bool mustComposite = mMustComposite.exchange(false);
     {
-        mTracePostComposition = mTracing.flagIsSet(SurfaceTracing::TRACE_COMPOSITION) ||
-                mTracing.flagIsSet(SurfaceTracing::TRACE_SYNC) ||
-                mTracing.flagIsSet(SurfaceTracing::TRACE_BUFFERS);
-        const bool tracePreComposition = mTracingEnabled && !mTracePostComposition;
-        ConditionalLockGuard<std::mutex> lock(mTracingLock, tracePreComposition);
+        mFrameTimeline->setSfWakeUp(vsyncId, frameTime, Fps::fromPeriodNsecs(stats.vsyncPeriod));
 
-        mFrameTimeline->setSfWakeUp(vsyncId, frameStart, Fps::fromPeriodNsecs(stats.vsyncPeriod));
-
-        refreshNeeded = handleMessageTransaction();
-        refreshNeeded |= handleMessageInvalidate();
-        if (tracePreComposition) {
-            if (mVisibleRegionsDirty) {
-                mTracing.notifyLocked("visibleRegionsDirty");
-            }
+        bool needsTraversal = false;
+        if (clearTransactionFlags(eTransactionFlushNeeded)) {
+            needsTraversal |= commitCreatedLayers();
+            needsTraversal |= flushTransactionQueues(vsyncId);
         }
+
+        const bool shouldCommit =
+                (getTransactionFlags() & ~eTransactionFlushNeeded) || needsTraversal;
+        if (shouldCommit) {
+            commitTransactions();
+        }
+
+        if (transactionFlushNeeded()) {
+            setTransactionFlags(eTransactionFlushNeeded);
+        }
+
+        mustComposite |= shouldCommit;
+        mustComposite |= latchBuffers();
+
+        // This has to be called after latchBuffers because we want to include the layers that have
+        // been latched in the commit callback
+        if (!needsTraversal) {
+            // Invoke empty transaction callbacks early.
+            mTransactionCallbackInvoker.sendCallbacks(false /* onCommitOnly */);
+        } else {
+            // Invoke OnCommit callbacks.
+            mTransactionCallbackInvoker.sendCallbacks(true /* onCommitOnly */);
+        }
+
+        updateLayerGeometry();
     }
 
     // Layers need to get updated (in the previous line) before we can use them for
@@ -2012,60 +2170,28 @@
     {
         Mutex::Autolock _l(mStateLock);
         mScheduler->chooseRefreshRateForContent();
+        setActiveModeInHwcIfNeeded();
     }
 
-    ON_MAIN_THREAD(performSetActiveMode());
-
     updateCursorAsync();
     updateInputFlinger();
 
-    refreshNeeded |= mRepaintEverything;
-    if (refreshNeeded && CC_LIKELY(mBootStage != BootStage::BOOTLOADER)) {
-        // Signal a refresh if a transaction modified the window state,
-        // a new buffer was latched, or if HWC has requested a full
-        // repaint
-        if (mFrameStartTime <= 0) {
-            // We should only use the time of the first invalidate
-            // message that signals a refresh as the beginning of the
-            // frame. Otherwise the real frame time will be
-            // underestimated.
-            mFrameStartTime = frameStart;
-        }
-
-        // Run the refresh immediately after invalidate as there is no point going thru the message
-        // queue again, and to ensure that we actually refresh the screen instead of handling
-        // other messages that were queued us already in the MessageQueue.
-        mRefreshPending = true;
-        onMessageRefresh();
+    if (mLayerTracingEnabled && !mLayerTracing.flagIsSet(LayerTracing::TRACE_COMPOSITION)) {
+        // This will block and tracing should only be enabled for debugging.
+        mLayerTracing.notify(mVisibleRegionsDirty, frameTime);
     }
-    notifyRegionSamplingThread();
+
+    MAIN_THREAD_GUARD(persistDisplayBrightness(mustComposite));
+
+    return mustComposite && CC_LIKELY(mBootStage != BootStage::BOOTLOADER);
 }
 
-bool SurfaceFlinger::handleMessageTransaction() {
-    ATRACE_CALL();
-
-    if (getTransactionFlags(eTransactionFlushNeeded)) {
-        flushTransactionQueues();
+void SurfaceFlinger::composite(nsecs_t frameTime, int64_t vsyncId) {
+    ATRACE_FORMAT("%s %" PRId64, __func__, vsyncId);
+    MainThreadScopedGuard mainThreadGuard(SF_MAIN_THREAD);
+    if (mPowerHintSessionData.sessionEnabled) {
+        mPowerHintSessionData.compositeStart = systemTime();
     }
-    uint32_t transactionFlags = peekTransactionFlags();
-    bool runHandleTransaction =
-            ((transactionFlags & (~eTransactionFlushNeeded)) != 0) || mForceTraversal;
-
-    if (runHandleTransaction) {
-        handleTransaction(eTransactionMask);
-    }
-
-    if (transactionFlushNeeded()) {
-        setTransactionFlags(eTransactionFlushNeeded);
-    }
-
-    return runHandleTransaction;
-}
-
-void SurfaceFlinger::onMessageRefresh() {
-    ATRACE_CALL();
-
-    mRefreshPending = false;
 
     compositionengine::CompositionRefreshArgs refreshArgs;
     const auto& displays = ON_MAIN_THREAD(mDisplays);
@@ -2083,7 +2209,6 @@
             refreshArgs.layersWithQueuedFrames.push_back(layerFE);
     }
 
-    refreshArgs.repaintEverything = mRepaintEverything.exchange(false);
     refreshArgs.outputColorSetting = useColorManagement
             ? mDisplayColorSetting
             : compositionengine::OutputColorSetting::kUnmanaged;
@@ -2091,7 +2216,7 @@
     refreshArgs.forceOutputColorMode = mForceColorMode;
 
     refreshArgs.updatingOutputGeometryThisFrame = mVisibleRegionsDirty;
-    refreshArgs.updatingGeometryThisFrame = mGeometryInvalid || mVisibleRegionsDirty;
+    refreshArgs.updatingGeometryThisFrame = mGeometryDirty.exchange(false) || mVisibleRegionsDirty;
     refreshArgs.blursAreExpensive = mBlursAreExpensive;
     refreshArgs.internalDisplayRotationFlags = DisplayDevice::getPrimaryDisplayRotationFlags();
 
@@ -2100,31 +2225,36 @@
         mDrawingState.colorMatrixChanged = false;
     }
 
-    refreshArgs.devOptForceClientComposition = mDebugDisableHWC || mDebugRegion;
+    refreshArgs.devOptForceClientComposition = mDebugDisableHWC;
 
-    if (mDebugRegion != 0) {
-        refreshArgs.devOptFlashDirtyRegionsDelay =
-                std::chrono::milliseconds(mDebugRegion > 1 ? mDebugRegion : 0);
+    if (mDebugFlashDelay != 0) {
+        refreshArgs.devOptForceClientComposition = true;
+        refreshArgs.devOptFlashDirtyRegionsDelay = std::chrono::milliseconds(mDebugFlashDelay);
     }
 
-    const auto prevVsyncTime = mScheduler->getPreviousVsyncFrom(mExpectedPresentTime);
+    const auto expectedPresentTime = mExpectedPresentTime.load();
+    const auto prevVsyncTime = mScheduler->getPreviousVsyncFrom(expectedPresentTime);
     const auto hwcMinWorkDuration = mVsyncConfiguration->getCurrentConfigs().hwcMinWorkDuration;
     refreshArgs.earliestPresentTime = prevVsyncTime - hwcMinWorkDuration;
     refreshArgs.previousPresentFence = mPreviousPresentFences[0].fenceTime;
-    refreshArgs.nextInvalidateTime = mEventQueue->nextExpectedInvalidate();
-
-    mGeometryInvalid = false;
+    refreshArgs.scheduledFrameTime = mScheduler->getScheduledFrameTime();
+    refreshArgs.expectedPresentTime = expectedPresentTime;
 
     // Store the present time just before calling to the composition engine so we could notify
     // the scheduler.
     const auto presentTime = systemTime();
 
     mCompositionEngine->present(refreshArgs);
-    mTimeStats->recordFrameDuration(mFrameStartTime, systemTime());
-    // Reset the frame start time now that we've recorded this frame.
-    mFrameStartTime = 0;
 
-    mScheduler->onDisplayRefreshed(presentTime);
+    if (mPowerHintSessionData.sessionEnabled) {
+        mPowerHintSessionData.presentEnd = systemTime();
+    }
+
+    mTimeStats->recordFrameDuration(frameTime, systemTime());
+
+    if (mScheduler->onPostComposition(presentTime)) {
+        scheduleComposite(FrameHint::kNone);
+    }
 
     postFrame();
     postComposition();
@@ -2154,29 +2284,28 @@
     modulateVsync(&VsyncModulator::onDisplayRefresh, usedGpuComposition);
 
     mLayersWithQueuedFrames.clear();
-    if (mTracingEnabled && mTracePostComposition) {
-        // This may block if SurfaceTracing is running in sync mode.
-        if (mVisibleRegionsDirty) {
-            mTracing.notify("visibleRegionsDirty");
-        } else if (mTracing.flagIsSet(SurfaceTracing::TRACE_BUFFERS)) {
-            mTracing.notify("bufferLatched");
-        }
+    if (mLayerTracingEnabled && mLayerTracing.flagIsSet(LayerTracing::TRACE_COMPOSITION)) {
+        // This will block and should only be used for debugging.
+        mLayerTracing.notify(mVisibleRegionsDirty, frameTime);
     }
 
     mVisibleRegionsWereDirtyThisFrame = mVisibleRegionsDirty; // Cache value for use in post-comp
     mVisibleRegionsDirty = false;
 
     if (mCompositionEngine->needsAnotherUpdate()) {
-        signalLayerUpdate();
+        scheduleCommit(FrameHint::kNone);
+    }
+
+    // calculate total render time for performance hinting if adpf cpu hint is enabled,
+    if (mPowerHintSessionData.sessionEnabled) {
+        const nsecs_t flingerDuration =
+                (mPowerHintSessionData.presentEnd - mPowerHintSessionData.commitStart);
+        mPowerAdvisor.sendActualWorkDuration(flingerDuration, mPowerHintSessionData.presentEnd);
     }
 }
 
-bool SurfaceFlinger::handleMessageInvalidate() {
+void SurfaceFlinger::updateLayerGeometry() {
     ATRACE_CALL();
-    bool refreshNeeded = handlePageFlip();
-
-    // Send on commit callbacks
-    mTransactionCallbackInvoker.sendCallbacks();
 
     if (mVisibleRegionsDirty) {
         computeLayerBounds();
@@ -2188,7 +2317,6 @@
         invalidateLayerStack(layer, visibleReg);
     }
     mLayersPendingRefresh.clear();
-    return refreshNeeded;
 }
 
 void SurfaceFlinger::updateCompositorTiming(const DisplayStatInfo& stats, nsecs_t compositeTime,
@@ -2219,16 +2347,19 @@
 
 void SurfaceFlinger::setCompositorTimingSnapped(const DisplayStatInfo& stats,
                                                 nsecs_t compositeToPresentLatency) {
+    // Avoid division by 0 by defaulting to 60Hz
+    const auto vsyncPeriod = stats.vsyncPeriod ?: (60_Hz).getPeriodNsecs();
+
     // Integer division and modulo round toward 0 not -inf, so we need to
     // treat negative and positive offsets differently.
     nsecs_t idealLatency = (mVsyncConfiguration->getCurrentConfigs().late.sfOffset > 0)
-            ? (stats.vsyncPeriod -
-               (mVsyncConfiguration->getCurrentConfigs().late.sfOffset % stats.vsyncPeriod))
-            : ((-mVsyncConfiguration->getCurrentConfigs().late.sfOffset) % stats.vsyncPeriod);
+            ? (vsyncPeriod -
+               (mVsyncConfiguration->getCurrentConfigs().late.sfOffset % vsyncPeriod))
+            : ((-mVsyncConfiguration->getCurrentConfigs().late.sfOffset) % vsyncPeriod);
 
     // Just in case mVsyncConfiguration->getCurrentConfigs().late.sf == -vsyncInterval.
     if (idealLatency <= 0) {
-        idealLatency = stats.vsyncPeriod;
+        idealLatency = vsyncPeriod;
     }
 
     // Snap the latency to a value that removes scheduling jitter from the
@@ -2237,17 +2368,66 @@
     // something (such as user input) to an accurate diasplay time.
     // Snapping also allows an app to precisely calculate
     // mVsyncConfiguration->getCurrentConfigs().late.sf with (presentLatency % interval).
-    nsecs_t bias = stats.vsyncPeriod / 2;
-    int64_t extraVsyncs = (compositeToPresentLatency - idealLatency + bias) / stats.vsyncPeriod;
-    nsecs_t snappedCompositeToPresentLatency =
-            (extraVsyncs > 0) ? idealLatency + (extraVsyncs * stats.vsyncPeriod) : idealLatency;
+    const nsecs_t bias = vsyncPeriod / 2;
+    const int64_t extraVsyncs = ((compositeToPresentLatency - idealLatency + bias) / vsyncPeriod);
+    const nsecs_t snappedCompositeToPresentLatency =
+            (extraVsyncs > 0) ? idealLatency + (extraVsyncs * vsyncPeriod) : idealLatency;
 
     std::lock_guard<std::mutex> lock(getBE().mCompositorTimingLock);
     getBE().mCompositorTiming.deadline = stats.vsyncTime - idealLatency;
-    getBE().mCompositorTiming.interval = stats.vsyncPeriod;
+    getBE().mCompositorTiming.interval = vsyncPeriod;
     getBE().mCompositorTiming.presentLatency = snappedCompositeToPresentLatency;
 }
 
+bool SurfaceFlinger::isHdrLayer(Layer* layer) const {
+    if (!isHdrDataspace(layer->getDataSpace())) {
+        return false;
+    }
+    if (mIgnoreHdrCameraLayers) {
+        auto buffer = layer->getBuffer();
+        if (buffer && (buffer->getUsage() & GRALLOC_USAGE_HW_CAMERA_WRITE) != 0) {
+            return false;
+        }
+    }
+    return true;
+}
+
+ui::Rotation SurfaceFlinger::getPhysicalDisplayOrientation(DisplayId displayId,
+                                                           bool isPrimary) const {
+    const auto id = PhysicalDisplayId::tryCast(displayId);
+    if (!id) {
+        return ui::ROTATION_0;
+    }
+    if (getHwComposer().getComposer()->isSupported(
+                Hwc2::Composer::OptionalFeature::PhysicalDisplayOrientation)) {
+        switch (getHwComposer().getPhysicalDisplayOrientation(*id)) {
+            case Hwc2::AidlTransform::ROT_90:
+                return ui::ROTATION_90;
+            case Hwc2::AidlTransform::ROT_180:
+                return ui::ROTATION_180;
+            case Hwc2::AidlTransform::ROT_270:
+                return ui::ROTATION_270;
+            default:
+                return ui::ROTATION_0;
+        }
+    }
+
+    if (isPrimary) {
+        using Values = SurfaceFlingerProperties::primary_display_orientation_values;
+        switch (primary_display_orientation(Values::ORIENTATION_0)) {
+            case Values::ORIENTATION_90:
+                return ui::ROTATION_90;
+            case Values::ORIENTATION_180:
+                return ui::ROTATION_180;
+            case Values::ORIENTATION_270:
+                return ui::ROTATION_270;
+            default:
+                break;
+        }
+    }
+    return ui::ROTATION_0;
+}
+
 void SurfaceFlinger::postComposition() {
     ATRACE_CALL();
     ALOGV("postComposition");
@@ -2297,13 +2477,9 @@
     }
 
     for (const auto& layer: mLayersWithQueuedFrames) {
-        const bool frameLatched =
-                layer->onPostComposition(display, glCompositionDoneFenceTime,
-                                         mPreviousPresentFences[0].fenceTime, compositorTiming);
+        layer->onPostComposition(display, glCompositionDoneFenceTime,
+                                 mPreviousPresentFences[0].fenceTime, compositorTiming);
         layer->releasePendingBuffer(/*dequeueReadyTime*/ now);
-        if (frameLatched) {
-            recordBufferingStats(layer->getName(), layer->getOccupancyHistory(false));
-        }
     }
 
     std::vector<std::pair<std::shared_ptr<compositionengine::Display>, sp<HdrLayerInfoReporter>>>
@@ -2336,13 +2512,8 @@
             int32_t maxArea = 0;
             mDrawingState.traverse([&, compositionDisplay = compositionDisplay](Layer* layer) {
                 const auto layerFe = layer->getCompositionEngineLayerFE();
-                if (layer->isVisible() && compositionDisplay->belongsInOutput(layerFe)) {
-                    const Dataspace transfer =
-                        static_cast<Dataspace>(layer->getDataSpace() & Dataspace::TRANSFER_MASK);
-                    const bool isHdr = (transfer == Dataspace::TRANSFER_ST2084 ||
-                                        transfer == Dataspace::TRANSFER_HLG);
-
-                    if (isHdr) {
+                if (layer->isVisible() && compositionDisplay->includesLayer(layerFe)) {
+                    if (isHdrLayer(layer)) {
                         const auto* outputLayer =
                             compositionDisplay->getOutputLayerForLayer(layerFe);
                         if (outputLayer) {
@@ -2366,7 +2537,8 @@
     mVisibleRegionsWereDirtyThisFrame = false;
 
     mTransactionCallbackInvoker.addPresentFence(mPreviousPresentFences[0].fence);
-    mTransactionCallbackInvoker.sendCallbacks();
+    mTransactionCallbackInvoker.sendCallbacks(false /* onCommitOnly */);
+    mTransactionCallbackInvoker.clearCompletedTransactions();
 
     if (display && display->isInternal() && display->getPowerMode() == hal::PowerMode::ON &&
         mPreviousPresentFences[0].fenceTime->isValid()) {
@@ -2463,6 +2635,13 @@
     // Find the largest width and height among all the displays.
     int32_t maxDisplayWidth = 0;
     int32_t maxDisplayHeight = 0;
+
+    // If there are no displays, set a valid display bounds so we can still compute a valid layer
+    // bounds.
+    if (ON_MAIN_THREAD(mDisplays.size()) == 0) {
+        maxDisplayWidth = maxDisplayHeight = 5000;
+    }
+
     for (const auto& pair : ON_MAIN_THREAD(mDisplays)) {
         const auto& displayDevice = pair.second;
         int32_t width = displayDevice->getWidth();
@@ -2499,36 +2678,33 @@
     }
 }
 
-void SurfaceFlinger::handleTransaction(uint32_t transactionFlags) {
+void SurfaceFlinger::commitTransactions() {
     ATRACE_CALL();
 
-    // here we keep a copy of the drawing state (that is the state that's
-    // going to be overwritten by handleTransactionLocked()) outside of
-    // mStateLock so that the side-effects of the State assignment
-    // don't happen with mStateLock held (which can cause deadlocks).
+    // Keep a copy of the drawing state (that is going to be overwritten
+    // by commitTransactionsLocked) outside of mStateLock so that the side
+    // effects of the State assignment don't happen with mStateLock held,
+    // which can cause deadlocks.
     State drawingState(mDrawingState);
 
-    Mutex::Autolock _l(mStateLock);
+    Mutex::Autolock lock(mStateLock);
     mDebugInTransaction = systemTime();
 
     // Here we're guaranteed that some transaction flags are set
-    // so we can call handleTransactionLocked() unconditionally.
-    // We call getTransactionFlags(), which will also clear the flags,
-    // with mStateLock held to guarantee that mCurrentState won't change
-    // until the transaction is committed.
+    // so we can call commitTransactionsLocked unconditionally.
+    // We clear the flags with mStateLock held to guarantee that
+    // mCurrentState won't change until the transaction is committed.
     modulateVsync(&VsyncModulator::onTransactionCommit);
-    transactionFlags = getTransactionFlags(eTransactionMask);
-    handleTransactionLocked(transactionFlags);
+    commitTransactionsLocked(clearTransactionFlags(eTransactionMask));
 
     mDebugInTransaction = 0;
-    // here the transaction has been committed
 }
 
-void SurfaceFlinger::loadDisplayModes(PhysicalDisplayId displayId, DisplayModes& outModes,
-                                      DisplayModePtr& outActiveMode) const {
+std::pair<DisplayModes, DisplayModePtr> SurfaceFlinger::loadDisplayModes(
+        PhysicalDisplayId displayId) const {
     std::vector<HWComposer::HWCDisplayMode> hwcModes;
     std::optional<hal::HWDisplayId> activeModeHwcId;
-    bool activeModeIsSupported;
+
     int attempt = 0;
     constexpr int kMaxAttempts = 3;
     do {
@@ -2536,63 +2712,60 @@
         activeModeHwcId = getHwComposer().getActiveMode(displayId);
         LOG_ALWAYS_FATAL_IF(!activeModeHwcId, "HWC returned no active mode");
 
-        activeModeIsSupported =
-                std::any_of(hwcModes.begin(), hwcModes.end(),
-                            [activeModeHwcId](const HWComposer::HWCDisplayMode& mode) {
-                                return mode.hwcId == *activeModeHwcId;
-                            });
-    } while (!activeModeIsSupported && ++attempt < kMaxAttempts);
-    LOG_ALWAYS_FATAL_IF(!activeModeIsSupported,
+        const auto isActiveMode = [activeModeHwcId](const HWComposer::HWCDisplayMode& mode) {
+            return mode.hwcId == *activeModeHwcId;
+        };
+
+        if (std::any_of(hwcModes.begin(), hwcModes.end(), isActiveMode)) {
+            break;
+        }
+    } while (++attempt < kMaxAttempts);
+
+    LOG_ALWAYS_FATAL_IF(attempt == kMaxAttempts,
                         "After %d attempts HWC still returns an active mode which is not"
-                        " supported. Active mode ID = %" PRIu64 " . Supported modes = %s",
+                        " supported. Active mode ID = %" PRIu64 ". Supported modes = %s",
                         kMaxAttempts, *activeModeHwcId, base::Join(hwcModes, ", ").c_str());
 
     DisplayModes oldModes;
-
     if (const auto token = getPhysicalDisplayTokenLocked(displayId)) {
         oldModes = getDisplayDeviceLocked(token)->getSupportedModes();
     }
 
-    int largestUsedModeId = -1; // Use int instead of DisplayModeId for signedness
-    for (const auto& mode : oldModes) {
-        const auto id = static_cast<int>(mode->getId().value());
-        if (id > largestUsedModeId) {
-            largestUsedModeId = id;
-        }
-    }
+    ui::DisplayModeId nextModeId = 1 +
+            std::accumulate(oldModes.begin(), oldModes.end(), static_cast<ui::DisplayModeId>(-1),
+                            [](ui::DisplayModeId max, const auto& pair) {
+                                return std::max(max, pair.first.value());
+                            });
 
     DisplayModes newModes;
-    int32_t nextModeId = largestUsedModeId + 1;
     for (const auto& hwcMode : hwcModes) {
-        newModes.push_back(DisplayMode::Builder(hwcMode.hwcId)
-                                   .setId(DisplayModeId{nextModeId++})
-                                   .setPhysicalDisplayId(displayId)
-                                   .setWidth(hwcMode.width)
-                                   .setHeight(hwcMode.height)
-                                   .setVsyncPeriod(hwcMode.vsyncPeriod)
-                                   .setDpiX(hwcMode.dpiX)
-                                   .setDpiY(hwcMode.dpiY)
-                                   .setGroup(hwcMode.configGroup)
-                                   .build());
+        const DisplayModeId id{nextModeId++};
+        newModes.try_emplace(id,
+                             DisplayMode::Builder(hwcMode.hwcId)
+                                     .setId(id)
+                                     .setPhysicalDisplayId(displayId)
+                                     .setResolution({hwcMode.width, hwcMode.height})
+                                     .setVsyncPeriod(hwcMode.vsyncPeriod)
+                                     .setDpiX(hwcMode.dpiX)
+                                     .setDpiY(hwcMode.dpiY)
+                                     .setGroup(hwcMode.configGroup)
+                                     .build());
     }
 
-    const bool modesAreSame =
+    const bool sameModes =
             std::equal(newModes.begin(), newModes.end(), oldModes.begin(), oldModes.end(),
-                       [](DisplayModePtr left, DisplayModePtr right) {
-                           return left->equalsExceptDisplayModeId(right);
+                       [](const auto& lhs, const auto& rhs) {
+                           return equalsExceptDisplayModeId(*lhs.second, *rhs.second);
                        });
 
-    if (modesAreSame) {
-        // The supported modes have not changed, keep the old IDs.
-        outModes = oldModes;
-    } else {
-        outModes = newModes;
-    }
+    // Keep IDs if modes have not changed.
+    const auto& modes = sameModes ? oldModes : newModes;
+    const DisplayModePtr activeMode =
+            std::find_if(modes.begin(), modes.end(), [activeModeHwcId](const auto& pair) {
+                return pair.second->getHwcId() == activeModeHwcId;
+            })->second;
 
-    outActiveMode = *std::find_if(outModes.begin(), outModes.end(),
-                                  [activeModeHwcId](const DisplayModePtr& mode) {
-                                      return mode->getHwcId() == *activeModeHwcId;
-                                  });
+    return {modes, activeMode};
 }
 
 void SurfaceFlinger::processDisplayHotplugEventsLocked() {
@@ -2608,9 +2781,7 @@
         const auto it = mPhysicalDisplayTokens.find(displayId);
 
         if (event.connection == hal::Connection::CONNECTED) {
-            DisplayModes supportedModes;
-            DisplayModePtr activeMode;
-            loadDisplayModes(displayId, supportedModes, activeMode);
+            auto [supportedModes, activeMode] = loadDisplayModes(displayId);
 
             if (it == mPhysicalDisplayTokens.end()) {
                 ALOGV("Creating display %s", to_string(displayId).c_str());
@@ -2621,7 +2792,7 @@
                                   .hwcDisplayId = event.hwcDisplayId,
                                   .deviceProductInfo = std::move(info->deviceProductInfo),
                                   .supportedModes = std::move(supportedModes),
-                                  .activeMode = activeMode};
+                                  .activeMode = std::move(activeMode)};
                 state.isSecure = true; // All physical displays are currently considered secure.
                 state.displayName = std::move(info->name);
 
@@ -2636,7 +2807,7 @@
                 auto& state = mCurrentState.displays.editValueFor(token);
                 state.sequenceId = DisplayDeviceState{}.sequenceId; // Generate new sequenceId
                 state.physical->supportedModes = std::move(supportedModes);
-                state.physical->activeMode = activeMode;
+                state.physical->activeMode = std::move(activeMode);
                 if (getHwComposer().updatesDeviceProductInfoOnHotplugReconnect()) {
                     state.physical->deviceProductInfo = std::move(info->deviceProductInfo);
                 }
@@ -2683,14 +2854,15 @@
         creationArgs.connectionType = physical->type;
         creationArgs.supportedModes = physical->supportedModes;
         creationArgs.activeModeId = physical->activeMode->getId();
-        const auto [idleTimerTimeoutMs, supportKernelIdleTimer] =
-                getIdleTimerConfiguration(compositionDisplay->getId());
+        const auto [kernelIdleTimerController, idleTimerTimeoutMs] =
+                getKernelIdleTimerProperties(compositionDisplay->getId());
+
         scheduler::RefreshRateConfigs::Config config =
                 {.enableFrameRateOverride = android::sysprop::enable_frame_rate_override(false),
                  .frameRateMultipleThreshold =
                          base::GetIntProperty("debug.sf.frame_rate_multiple_threshold", 0),
-                 .idleTimerTimeoutMs = idleTimerTimeoutMs,
-                 .supportKernelIdleTimer = supportKernelIdleTimer};
+                 .idleTimerTimeout = idleTimerTimeoutMs,
+                 .kernelIdleTimerController = kernelIdleTimerController};
         creationArgs.refreshRateConfigs =
                 std::make_shared<scheduler::RefreshRateConfigs>(creationArgs.supportedModes,
                                                                 creationArgs.activeModeId, config);
@@ -2730,7 +2902,8 @@
     }
 
     creationArgs.physicalOrientation =
-            creationArgs.isPrimary ? internalDisplayOrientation : ui::ROTATION_0;
+            getPhysicalDisplayOrientation(compositionDisplay->getId(), creationArgs.isPrimary);
+    ALOGV("Display Orientation: %s", toCString(creationArgs.physicalOrientation));
 
     // virtual displays are always considered enabled
     creationArgs.initialPowerMode = state.isVirtual() ? hal::PowerMode::ON : hal::PowerMode::OFF;
@@ -2758,6 +2931,7 @@
     display->setProjection(state.orientation, state.layerStackSpaceRect,
                            state.orientedDisplaySpaceRect);
     display->setDisplayName(state.displayName);
+    display->setFlags(state.flags);
 
     return display;
 }
@@ -2767,7 +2941,7 @@
     ui::Size resolution(0, 0);
     ui::PixelFormat pixelFormat = static_cast<ui::PixelFormat>(PIXEL_FORMAT_UNKNOWN);
     if (state.physical) {
-        resolution = state.physical->activeMode->getSize();
+        resolution = state.physical->activeMode->getResolution();
         pixelFormat = static_cast<ui::PixelFormat>(PIXEL_FORMAT_RGBA_8888);
     } else if (state.surface != nullptr) {
         int status = state.surface->query(NATIVE_WINDOW_WIDTH, &resolution.width);
@@ -2788,14 +2962,12 @@
     compositionengine::DisplayCreationArgsBuilder builder;
     if (const auto& physical = state.physical) {
         builder.setId(physical->id);
-        builder.setConnectionType(physical->type);
     } else {
         builder.setId(acquireVirtualDisplay(resolution, pixelFormat));
     }
 
     builder.setPixels(resolution);
     builder.setIsSecure(state.isSecure);
-    builder.setLayerStackId(state.layerStack);
     builder.setPowerAdvisor(&mPowerAdvisor);
     builder.setName(state.displayName);
     auto compositionDisplay = getCompositionEngine().createDisplay(builder.build());
@@ -2823,7 +2995,7 @@
         LOG_FATAL_IF(!displayId);
         displaySurface =
                 sp<FramebufferSurface>::make(getHwComposer(), *displayId, bqConsumer,
-                                             state.physical->activeMode->getSize(),
+                                             state.physical->activeMode->getResolution(),
                                              ui::Size(maxGraphicsWidth, maxGraphicsHeight));
         producer = bqProducer;
     }
@@ -2855,7 +3027,7 @@
     mDisplays.erase(displayToken);
 
     if (display && display->isVirtual()) {
-        static_cast<void>(schedule([display = std::move(display)] {
+        static_cast<void>(mScheduler->schedule([display = std::move(display)] {
             // Destroy the display without holding the mStateLock.
             // This is a temporary solution until we can manage transaction queues without
             // holding the mStateLock.
@@ -2901,7 +3073,7 @@
             setPowerModeInternal(display, hal::PowerMode::ON);
 
             // TODO(b/175678251) Call a listener instead.
-            if (currentState.physical->hwcDisplayId == getHwComposer().getInternalHwcDisplayId()) {
+            if (currentState.physical->hwcDisplayId == getHwComposer().getPrimaryHwcDisplayId()) {
                 updateInternalDisplayVsyncLocked(display);
             }
         }
@@ -2936,7 +3108,7 @@
 }
 void SurfaceFlinger::updateInternalDisplayVsyncLocked(const sp<DisplayDevice>& activeDisplay) {
     mVsyncConfiguration->reset();
-    const Fps refreshRate = activeDisplay->refreshRateConfigs().getCurrentRefreshRate().getFps();
+    const Fps refreshRate = activeDisplay->refreshRateConfigs().getActiveMode()->getFps();
     updatePhaseConfiguration(refreshRate);
     mRefreshRateStats->setRefreshRate(refreshRate);
 }
@@ -2981,14 +3153,13 @@
     mDrawingState.displays = mCurrentState.displays;
 }
 
-void SurfaceFlinger::handleTransactionLocked(uint32_t transactionFlags) {
-    // Commit display transactions
+void SurfaceFlinger::commitTransactionsLocked(uint32_t transactionFlags) {
+    // Commit display transactions.
     const bool displayTransactionNeeded = transactionFlags & eDisplayTransactionNeeded;
     if (displayTransactionNeeded) {
         processDisplayChangesLocked();
         processDisplayHotplugEventsLocked();
     }
-    mForceTraversal = false;
     mForceTransactionDisplayChange = displayTransactionNeeded;
 
     if (mSomeChildrenChanged) {
@@ -2996,18 +3167,9 @@
         mSomeChildrenChanged = false;
     }
 
-    // Update transform hint
+    // Update transform hint.
     if (transactionFlags & (eTransformHintUpdateNeeded | eDisplayTransactionNeeded)) {
-        // The transform hint might have changed for some layers
-        // (either because a display has changed, or because a layer
-        // as changed).
-        //
-        // Walk through all the layers in currentLayers,
-        // and update their transform hint.
-        //
-        // If a layer is visible only on a single display, then that
-        // display is used to calculate the hint, otherwise we use the
-        // default display.
+        // Layers and/or displays have changed, so update the transform hint for each layer.
         //
         // NOTE: we do this here, rather than when presenting the display so that
         // the hint is set before we acquire a buffer from the surface texture.
@@ -3018,30 +3180,29 @@
         // (soon to become the drawing state list).
         //
         sp<const DisplayDevice> hintDisplay;
-        uint32_t currentlayerStack = 0;
-        bool first = true;
+        ui::LayerStack layerStack;
+
         mCurrentState.traverse([&](Layer* layer) REQUIRES(mStateLock) {
             // NOTE: we rely on the fact that layers are sorted by
             // layerStack first (so we don't have to traverse the list
             // of displays for every layer).
-            uint32_t layerStack = layer->getLayerStack();
-            if (first || currentlayerStack != layerStack) {
-                currentlayerStack = layerStack;
-                // figure out if this layerstack is mirrored
-                // (more than one display) if so, pick the default display,
-                // if not, pick the only display it's on.
+            if (const auto filter = layer->getOutputFilter(); layerStack != filter.layerStack) {
+                layerStack = filter.layerStack;
                 hintDisplay = nullptr;
+
+                // Find the display that includes the layer.
                 for (const auto& [token, display] : mDisplays) {
-                    if (display->getCompositionDisplay()
-                                ->belongsInOutput(layer->getLayerStack(),
-                                                  layer->getPrimaryDisplayOnly())) {
-                        if (hintDisplay) {
-                            hintDisplay = nullptr;
-                            break;
-                        } else {
-                            hintDisplay = display;
-                        }
+                    if (!display->getCompositionDisplay()->includesLayer(filter)) {
+                        continue;
                     }
+
+                    // Pick the primary display if another display mirrors the layer.
+                    if (hintDisplay) {
+                        hintDisplay = nullptr;
+                        break;
+                    }
+
+                    hintDisplay = display;
                 }
             }
 
@@ -3055,20 +3216,10 @@
                 hintDisplay = getDefaultDisplayDeviceLocked();
             }
 
-            // could be null if there is no display available at all to get
-            // the transform hint from.
-            if (hintDisplay) {
-                layer->updateTransformHint(hintDisplay->getTransformHint());
-            }
-
-            first = false;
+            layer->updateTransformHint(hintDisplay->getTransformHint());
         });
     }
 
-    /*
-     * Perform our own transaction if needed
-     */
-
     if (mLayersAdded) {
         mLayersAdded = false;
         // Layers have been added.
@@ -3090,7 +3241,9 @@
         });
     }
 
-    commitTransaction();
+    doCommitTransactions();
+    signalSynchronousTransactions(CountDownLatch::eSyncTransaction);
+    mAnimTransactionPending = false;
 }
 
 void SurfaceFlinger::updateInputFlinger() {
@@ -3099,41 +3252,111 @@
         return;
     }
 
+    std::vector<WindowInfo> windowInfos;
+    std::vector<DisplayInfo> displayInfos;
+    bool updateWindowInfo = false;
     if (mVisibleRegionsDirty || mInputInfoChanged) {
         mInputInfoChanged = false;
-        notifyWindowInfos();
-    } else if (mInputWindowCommands.syncInputWindows) {
-        // If the caller requested to sync input windows, but there are no
-        // changes to input windows, notify immediately.
-        windowInfosReported();
+        updateWindowInfo = true;
+        buildWindowInfos(windowInfos, displayInfos);
     }
+    if (!updateWindowInfo && mInputWindowCommands.empty()) {
+        return;
+    }
+    BackgroundExecutor::getInstance().execute([updateWindowInfo,
+                                               windowInfos = std::move(windowInfos),
+                                               displayInfos = std::move(displayInfos),
+                                               inputWindowCommands =
+                                                       std::move(mInputWindowCommands),
+                                               inputFlinger = mInputFlinger, this]() {
+        ATRACE_NAME("BackgroundExecutor::updateInputFlinger");
+        if (updateWindowInfo) {
+            mWindowInfosListenerInvoker->windowInfosChanged(windowInfos, displayInfos,
+                                                            inputWindowCommands.syncInputWindows);
+        } else if (inputWindowCommands.syncInputWindows) {
+            // If the caller requested to sync input windows, but there are no
+            // changes to input windows, notify immediately.
+            windowInfosReported();
+        }
+        for (const auto& focusRequest : inputWindowCommands.focusRequests) {
+            inputFlinger->setFocusedWindow(focusRequest);
+        }
+    });
 
-    for (const auto& focusRequest : mInputWindowCommands.focusRequests) {
-        mInputFlinger->setFocusedWindow(focusRequest);
-    }
     mInputWindowCommands.clear();
 }
 
-bool enablePerWindowInputRotation() {
-    static bool value =
-            android::base::GetBoolProperty("persist.debug.per_window_input_rotation", false);
-    return value;
+void SurfaceFlinger::persistDisplayBrightness(bool needsComposite) {
+    const bool supportsDisplayBrightnessCommand = getHwComposer().getComposer()->isSupported(
+            Hwc2::Composer::OptionalFeature::DisplayBrightnessCommand);
+    if (!supportsDisplayBrightnessCommand) {
+        return;
+    }
+
+    for (const auto& [_, display] : ON_MAIN_THREAD(mDisplays)) {
+        if (const auto brightness = display->getStagedBrightness(); brightness) {
+            if (!needsComposite) {
+                const status_t error =
+                        getHwComposer()
+                                .setDisplayBrightness(display->getPhysicalId(), *brightness,
+                                                      Hwc2::Composer::DisplayBrightnessOptions{
+                                                              .applyImmediately = true})
+                                .get();
+
+                ALOGE_IF(error != NO_ERROR,
+                         "Error setting display brightness for display %s: %d (%s)",
+                         display->getDebugName().c_str(), error, strerror(error));
+            }
+            display->persistBrightness(needsComposite);
+        }
+    }
 }
 
-void SurfaceFlinger::notifyWindowInfos() {
-    std::vector<WindowInfo> windowInfos;
+void SurfaceFlinger::buildWindowInfos(std::vector<WindowInfo>& outWindowInfos,
+                                      std::vector<DisplayInfo>& outDisplayInfos) {
+    ftl::SmallMap<ui::LayerStack, DisplayDevice::InputInfo, 4> displayInputInfos;
+
+    for (const auto& [_, display] : ON_MAIN_THREAD(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;
 
     mDrawingState.traverseInReverseZOrder([&](Layer* layer) {
         if (!layer->needsInputInfo()) return;
-        sp<DisplayDevice> display = enablePerWindowInputRotation()
-                ? ON_MAIN_THREAD(getDisplayWithInputByLayer(layer))
-                : nullptr;
-        // When calculating the screen bounds we ignore the transparent region since it may
-        // result in an unwanted offset.
-        windowInfos.push_back(layer->fillInputInfo(display));
+
+        // Do not create WindowInfos for windows on displays that cannot receive input.
+        if (const auto opt = displayInputInfos.get(layer->getLayerStack())) {
+            const auto& info = opt->get();
+            outWindowInfos.push_back(layer->fillInputInfo(info.transform, info.isSecure));
+        }
     });
-    mWindowInfosListenerInvoker->windowInfosChanged(windowInfos,
-                                                    mInputWindowCommands.syncInputWindows);
+
+    sNumWindowInfos = outWindowInfos.size();
+
+    outDisplayInfos.reserve(displayInputInfos.size());
+    for (const auto& [_, info] : displayInputInfos) {
+        outDisplayInfos.push_back(info.info);
+    }
 }
 
 void SurfaceFlinger::updateCursorAsync() {
@@ -3147,13 +3370,25 @@
     mCompositionEngine->updateCursorAsync(refreshArgs);
 }
 
-void SurfaceFlinger::changeRefreshRate(const RefreshRate& refreshRate, Scheduler::ModeEvent event) {
+void SurfaceFlinger::requestDisplayMode(DisplayModePtr mode, DisplayModeEvent event) {
     // 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);
-    changeRefreshRateLocked(refreshRate, event);
+
+    const auto display = getDefaultDisplayDeviceLocked();
+    if (!display || mBootStage != BootStage::FINISHED) {
+        return;
+    }
+    ATRACE_CALL();
+
+    if (!display->refreshRateConfigs().isModeAllowed(mode->getId())) {
+        ALOGV("Skipping disallowed mode %d", mode->getId().value());
+        return;
+    }
+
+    setDesiredActiveMode({std::move(mode), event});
 }
 
 void SurfaceFlinger::triggerOnFrameRateOverridesChanged() {
@@ -3181,8 +3416,35 @@
     mVsyncConfiguration = getFactory().createVsyncConfiguration(currRefreshRate);
     mVsyncModulator = sp<VsyncModulator>::make(mVsyncConfiguration->getCurrentConfigs());
 
-    // start the EventThread
-    mScheduler = getFactory().createScheduler(display->holdRefreshRateConfigs(), *this);
+    using Feature = scheduler::Feature;
+    scheduler::FeatureFlags features;
+
+    if (sysprop::use_content_detection_for_refresh_rate(false)) {
+        features |= Feature::kContentDetection;
+    }
+    if (base::GetBoolProperty("debug.sf.show_predicted_vsync"s, false)) {
+        features |= Feature::kTracePredictedVsync;
+    }
+    if (!base::GetBoolProperty("debug.sf.vsync_reactor_ignore_present_fences"s, false) &&
+        !getHwComposer().hasCapability(Capability::PRESENT_FENCE_IS_NOT_RELIABLE)) {
+        features |= Feature::kPresentFences;
+    }
+
+    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->setRefreshRateConfigs(std::move(configs));
+    }
+    setVsyncEnabled(false);
+    mScheduler->startTimers();
+
     const auto configs = mVsyncConfiguration->getCurrentConfigs();
     const nsecs_t vsyncPeriod = currRefreshRate.getPeriodNsecs();
     mAppConnectionHandle =
@@ -3198,8 +3460,8 @@
                                              mInterceptor->saveVSyncEvent(timestamp);
                                          });
 
-    mEventQueue->initVsync(mScheduler->getVsyncDispatch(), *mFrameTimeline->getTokenManager(),
-                           configs.late.sfWorkDuration);
+    mScheduler->initVsync(mScheduler->getVsyncDispatch(), *mFrameTimeline->getTokenManager(),
+                          configs.late.sfWorkDuration);
 
     mRegionSamplingThread =
             new RegionSamplingThread(*this, RegionSamplingThread::EnvironmentTimingTunables());
@@ -3212,11 +3474,6 @@
     // 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, display->getActiveMode());
-    static auto ignorePresentFences =
-            base::GetBoolProperty("debug.sf.vsync_reactor_ignore_present_fences"s, false);
-    mScheduler->setIgnorePresentFences(
-            ignorePresentFences ||
-            getHwComposer().hasCapability(hal::Capability::PRESENT_FENCE_IS_NOT_RELIABLE));
 }
 
 void SurfaceFlinger::updatePhaseConfiguration(const Fps& refreshRate) {
@@ -3233,22 +3490,15 @@
     mScheduler->setDuration(mSfConnectionHandle,
                             /*workDuration=*/std::chrono::nanoseconds(vsyncPeriod),
                             /*readyDuration=*/config.sfWorkDuration);
-    mEventQueue->setDuration(config.sfWorkDuration);
+    mScheduler->setDuration(config.sfWorkDuration);
 }
 
-void SurfaceFlinger::commitTransaction() {
+void SurfaceFlinger::doCommitTransactions() {
     ATRACE_CALL();
-    commitTransactionLocked();
-    signalSynchronousTransactions(CountDownLatch::eSyncTransaction);
-    mAnimTransactionPending = false;
-}
 
-void SurfaceFlinger::commitTransactionLocked() {
     if (!mLayersPendingRemoval.isEmpty()) {
         // Notify removed layers now that they can't be drawn from
         for (const auto& l : mLayersPendingRemoval) {
-            recordBufferingStats(l->getName(), l->getOccupancyHistory(true));
-
             // Ensure any buffers set to display on any children are released.
             if (l->isRemovedFromCurrentState()) {
                 l->latchAndReleaseBuffer();
@@ -3287,11 +3537,10 @@
 void SurfaceFlinger::commitOffscreenLayers() {
     for (Layer* offscreenLayer : mOffscreenLayers) {
         offscreenLayer->traverse(LayerVector::StateSet::Drawing, [](Layer* layer) {
-            uint32_t trFlags = layer->getTransactionFlags(eTransactionNeeded);
-            if (!trFlags) return;
-
-            layer->doTransaction(0);
-            layer->commitChildList();
+            if (layer->clearTransactionFlags(eTransactionNeeded)) {
+                layer->doTransaction(0);
+                layer->commitChildList();
+            }
         });
     }
 }
@@ -3299,17 +3548,16 @@
 void SurfaceFlinger::invalidateLayerStack(const sp<const Layer>& layer, const Region& dirty) {
     for (const auto& [token, displayDevice] : ON_MAIN_THREAD(mDisplays)) {
         auto display = displayDevice->getCompositionDisplay();
-        if (display->belongsInOutput(layer->getLayerStack(), layer->getPrimaryDisplayOnly())) {
+        if (display->includesLayer(layer->getOutputFilter())) {
             display->editState().dirtyRegion.orSelf(dirty);
         }
     }
 }
 
-bool SurfaceFlinger::handlePageFlip() {
+bool SurfaceFlinger::latchBuffers() {
     ATRACE_CALL();
-    ALOGV("handlePageFlip");
 
-    nsecs_t latchTime = systemTime();
+    const nsecs_t latchTime = systemTime();
 
     bool visibleRegions = false;
     bool frameQueued = false;
@@ -3327,14 +3575,14 @@
     // Display is now waiting on Layer 1's frame, which is behind layer 0's
     // second frame. But layer 0's second frame could be waiting on display.
     mDrawingState.traverse([&](Layer* layer) {
-         uint32_t trFlags = layer->getTransactionFlags(eTransactionNeeded);
-         if (trFlags || mForceTransactionDisplayChange) {
-             const uint32_t flags = layer->doTransaction(0);
-             if (flags & Layer::eVisibleRegion)
-                 mVisibleRegionsDirty = true;
-         }
+        if (layer->clearTransactionFlags(eTransactionNeeded) || mForceTransactionDisplayChange) {
+            const uint32_t flags = layer->doTransaction(0);
+            if (flags & Layer::eVisibleRegion) {
+                mVisibleRegionsDirty = true;
+            }
+        }
 
-         if (layer->hasReadyFrame()) {
+        if (layer->hasReadyFrame()) {
             frameQueued = true;
             if (layer->shouldPresentNow(expectedPresentTime)) {
                 mLayersWithQueuedFrames.emplace(layer);
@@ -3342,7 +3590,7 @@
                 ATRACE_NAME("!layer->shouldPresentNow()");
                 layer->useEmptyDamage();
             }
-         } else {
+        } else {
             layer->useEmptyDamage();
         }
     });
@@ -3364,11 +3612,9 @@
         for (const auto& layer : mLayersWithQueuedFrames) {
             if (layer->latchBuffer(visibleRegions, latchTime, expectedPresentTime)) {
                 mLayersPendingRefresh.push_back(layer);
-            }
-            layer->useSurfaceDamage();
-            if (layer->isBufferLatched()) {
                 newDataLatched = true;
             }
+            layer->useSurfaceDamage();
         }
     }
 
@@ -3378,7 +3624,7 @@
     // queued frame that shouldn't be displayed during this vsync period, wake
     // up during the next vsync period to check again.
     if (frameQueued && (mLayersWithQueuedFrames.empty() || !newDataLatched)) {
-        signalLayerUpdate();
+        scheduleCommit(FrameHint::kNone);
     }
 
     // enter boot animation on first buffer latch
@@ -3395,164 +3641,261 @@
     return !mLayersWithQueuedFrames.empty() && newDataLatched;
 }
 
-void SurfaceFlinger::invalidateHwcGeometry() {
-    mGeometryInvalid = true;
-}
-
 status_t SurfaceFlinger::addClientLayer(const sp<Client>& client, const sp<IBinder>& handle,
-                                        const sp<IGraphicBufferProducer>& gbc, const sp<Layer>& lbc,
-                                        const sp<IBinder>& parentHandle,
-                                        const sp<Layer>& parentLayer, bool addToRoot,
-                                        uint32_t* outTransformHint) {
+                                        const sp<Layer>& layer, const wp<Layer>& parent,
+                                        bool addToRoot, uint32_t* outTransformHint) {
     if (mNumLayers >= ISurfaceComposer::MAX_LAYERS) {
         ALOGE("AddClientLayer failed, mNumLayers (%zu) >= MAX_LAYERS (%zu)", mNumLayers.load(),
               ISurfaceComposer::MAX_LAYERS);
         return NO_MEMORY;
     }
 
-    wp<IBinder> initialProducer;
-    if (gbc != nullptr) {
-        initialProducer = IInterface::asBinder(gbc);
+    {
+        std::scoped_lock<std::mutex> lock(mCreatedLayersLock);
+        mCreatedLayers.emplace_back(layer, parent, addToRoot);
     }
-    setLayerCreatedState(handle, lbc, parentHandle, parentLayer, initialProducer, addToRoot);
 
-    // Create a transaction includes the initial parent and producer.
-    Vector<ComposerState> states;
-    Vector<DisplayState> displays;
-
-    ComposerState composerState;
-    composerState.state.what = layer_state_t::eLayerCreated;
-    composerState.state.surface = handle;
-    states.add(composerState);
-
-    lbc->updateTransformHint(mActiveDisplayTransformHint);
+    layer->updateTransformHint(mActiveDisplayTransformHint);
     if (outTransformHint) {
         *outTransformHint = mActiveDisplayTransformHint;
     }
     // attach this layer to the client
-    client->attachLayer(handle, lbc);
+    if (client != nullptr) {
+        client->attachLayer(handle, layer);
+    }
 
-    return setTransactionState(FrameTimelineInfo{}, states, displays, 0 /* flags */, nullptr,
-                               InputWindowCommands{}, -1 /* desiredPresentTime */,
-                               true /* isAutoTimestamp */, {}, false /* hasListenerCallbacks */, {},
-                               0 /* Undefined transactionId */);
+    setTransactionFlags(eTransactionNeeded);
+    return NO_ERROR;
 }
 
-void SurfaceFlinger::removeGraphicBufferProducerAsync(const wp<IBinder>& binder) {
-    static_cast<void>(schedule([=] {
-        Mutex::Autolock lock(mStateLock);
-        mGraphicBufferProducerList.erase(binder);
-    }));
-}
-
-uint32_t SurfaceFlinger::peekTransactionFlags() {
+uint32_t SurfaceFlinger::getTransactionFlags() const {
     return mTransactionFlags;
 }
 
-uint32_t SurfaceFlinger::getTransactionFlags(uint32_t flags) {
-    return mTransactionFlags.fetch_and(~flags) & flags;
+uint32_t SurfaceFlinger::clearTransactionFlags(uint32_t mask) {
+    return mTransactionFlags.fetch_and(~mask) & mask;
 }
 
-uint32_t SurfaceFlinger::setTransactionFlags(uint32_t flags) {
-    return setTransactionFlags(flags, TransactionSchedule::Late);
+void SurfaceFlinger::setTransactionFlags(uint32_t mask, TransactionSchedule schedule,
+                                         const sp<IBinder>& applyToken) {
+    modulateVsync(&VsyncModulator::setTransactionSchedule, schedule, applyToken);
+
+    if (const bool scheduled = mTransactionFlags.fetch_or(mask) & mask; !scheduled) {
+        scheduleCommit(FrameHint::kActive);
+    }
 }
 
-uint32_t SurfaceFlinger::setTransactionFlags(uint32_t flags, TransactionSchedule schedule,
-                                             const sp<IBinder>& token) {
-    uint32_t old = mTransactionFlags.fetch_or(flags);
-    modulateVsync(&VsyncModulator::setTransactionSchedule, schedule, token);
-    if ((old & flags) == 0) signalTransaction();
-    return old;
+bool SurfaceFlinger::stopTransactionProcessing(
+        const std::unordered_set<sp<IBinder>, SpHash<IBinder>>&
+                applyTokensWithUnsignaledTransactions) const {
+    if (enableLatchUnsignaledConfig == LatchUnsignaledConfig::AutoSingleLayer) {
+        // if we are in LatchUnsignaledConfig::AutoSingleLayer
+        // then we should have only one applyToken for processing.
+        // so we can stop further transactions on this applyToken.
+        return !applyTokensWithUnsignaledTransactions.empty();
+    }
+
+    return false;
 }
 
-void SurfaceFlinger::setTraversalNeeded() {
-    mForceTraversal = true;
+int SurfaceFlinger::flushUnsignaledPendingTransactionQueues(
+        std::vector<TransactionState>& transactions,
+        std::unordered_map<sp<IBinder>, uint64_t, SpHash<IBinder>>& bufferLayersReadyToPresent,
+        std::unordered_set<sp<IBinder>, SpHash<IBinder>>& applyTokensWithUnsignaledTransactions) {
+    return flushPendingTransactionQueues(transactions, bufferLayersReadyToPresent,
+                                         applyTokensWithUnsignaledTransactions,
+                                         /*tryApplyUnsignaled*/ true);
 }
 
-void SurfaceFlinger::flushTransactionQueues() {
+int SurfaceFlinger::flushPendingTransactionQueues(
+        std::vector<TransactionState>& transactions,
+        std::unordered_map<sp<IBinder>, uint64_t, SpHash<IBinder>>& bufferLayersReadyToPresent,
+        std::unordered_set<sp<IBinder>, SpHash<IBinder>>& applyTokensWithUnsignaledTransactions,
+        bool tryApplyUnsignaled) {
+    int transactionsPendingBarrier = 0;
+    auto it = mPendingTransactionQueues.begin();
+    while (it != mPendingTransactionQueues.end()) {
+        auto& [applyToken, transactionQueue] = *it;
+        while (!transactionQueue.empty()) {
+            if (stopTransactionProcessing(applyTokensWithUnsignaledTransactions)) {
+                ATRACE_NAME("stopTransactionProcessing");
+                break;
+            }
+
+            auto& transaction = transactionQueue.front();
+            const auto ready =
+                    transactionIsReadyToBeApplied(transaction.frameTimelineInfo,
+                                                  transaction.isAutoTimestamp,
+                                                  transaction.desiredPresentTime,
+                                                  transaction.originUid, transaction.states,
+                                                  bufferLayersReadyToPresent, transactions.size(),
+                                                  tryApplyUnsignaled);
+            ATRACE_INT("TransactionReadiness", static_cast<int>(ready));
+            if (ready == TransactionReadiness::NotReady) {
+                setTransactionFlags(eTransactionFlushNeeded);
+                break;
+            }
+            if (ready == TransactionReadiness::NotReadyBarrier) {
+                transactionsPendingBarrier++;
+                setTransactionFlags(eTransactionFlushNeeded);
+                break;
+            }
+            transaction.traverseStatesWithBuffers([&](const layer_state_t& state) {
+                const bool frameNumberChanged = state.bufferData->flags.test(
+                        BufferData::BufferDataChange::frameNumberChanged);
+                if (frameNumberChanged) {
+                    bufferLayersReadyToPresent[state.surface] = state.bufferData->frameNumber;
+                } else {
+                    // Barrier function only used for BBQ which always includes a frame number
+                    bufferLayersReadyToPresent[state.surface] =
+                        std::numeric_limits<uint64_t>::max();
+                }
+            });
+            const bool appliedUnsignaled = (ready == TransactionReadiness::ReadyUnsignaled);
+            if (appliedUnsignaled) {
+                applyTokensWithUnsignaledTransactions.insert(transaction.applyToken);
+            }
+
+            transactions.emplace_back(std::move(transaction));
+            transactionQueue.pop();
+        }
+
+        if (transactionQueue.empty()) {
+            it = mPendingTransactionQueues.erase(it);
+            mTransactionQueueCV.broadcast();
+        } else {
+            it = std::next(it, 1);
+        }
+    }
+    return transactionsPendingBarrier;
+}
+
+bool SurfaceFlinger::flushTransactionQueues(int64_t vsyncId) {
     // to prevent onHandleDestroyed from being called while the lock is held,
     // we must keep a copy of the transactions (specifically the composer
     // states) around outside the scope of the lock
-    std::vector<const TransactionState> transactions;
+    std::vector<TransactionState> transactions;
     // Layer handles that have transactions with buffers that are ready to be applied.
-    std::unordered_set<sp<IBinder>, ISurfaceComposer::SpHash<IBinder>> bufferLayersReadyToPresent;
+    std::unordered_map<sp<IBinder>, uint64_t, SpHash<IBinder>> bufferLayersReadyToPresent;
+    std::unordered_set<sp<IBinder>, SpHash<IBinder>> applyTokensWithUnsignaledTransactions;
     {
         Mutex::Autolock _l(mStateLock);
         {
             Mutex::Autolock _l(mQueueLock);
-            // Collect transactions from pending transaction queue.
-            auto it = mPendingTransactionQueues.begin();
-            while (it != mPendingTransactionQueues.end()) {
-                auto& [applyToken, transactionQueue] = *it;
 
-                while (!transactionQueue.empty()) {
-                    auto& transaction = transactionQueue.front();
-                    if (!transactionIsReadyToBeApplied(transaction.frameTimelineInfo,
-                                                       transaction.isAutoTimestamp,
-                                                       transaction.desiredPresentTime,
-                                                       transaction.originUid, transaction.states,
-                                                       bufferLayersReadyToPresent)) {
-                        setTransactionFlags(eTransactionFlushNeeded);
-                        break;
-                    }
-                    transaction.traverseStatesWithBuffers([&](const layer_state_t& state) {
-                        bufferLayersReadyToPresent.insert(state.surface);
-                    });
-                    transactions.emplace_back(std::move(transaction));
-                    transactionQueue.pop();
-                }
+            int lastTransactionsPendingBarrier = 0;
+            int transactionsPendingBarrier = 0;
+            // First collect transactions from the pending transaction queues.
+            // We are not allowing unsignaled buffers here as we want to
+            // collect all the transactions from applyTokens that are ready first.
+            transactionsPendingBarrier =
+                    flushPendingTransactionQueues(transactions, bufferLayersReadyToPresent,
+                            applyTokensWithUnsignaledTransactions, /*tryApplyUnsignaled*/ false);
 
-                if (transactionQueue.empty()) {
-                    it = mPendingTransactionQueues.erase(it);
-                    mTransactionQueueCV.broadcast();
-                } else {
-                    it = std::next(it, 1);
-                }
-            }
-
-            // Collect transactions from current transaction queue or queue to pending transactions.
-            // Case 1: push to pending when transactionIsReadyToBeApplied is false.
-            // Case 2: push to pending when there exist a pending queue.
-            // Case 3: others are ready to apply.
+            // Second, collect transactions from the transaction queue.
+            // Here as well we are not allowing unsignaled buffers for the same
+            // reason as above.
             while (!mTransactionQueue.empty()) {
                 auto& transaction = mTransactionQueue.front();
-                bool pendingTransactions = mPendingTransactionQueues.find(transaction.applyToken) !=
+                const bool pendingTransactions =
+                        mPendingTransactionQueues.find(transaction.applyToken) !=
                         mPendingTransactionQueues.end();
-                if (pendingTransactions ||
-                    !transactionIsReadyToBeApplied(transaction.frameTimelineInfo,
-                                                   transaction.isAutoTimestamp,
-                                                   transaction.desiredPresentTime,
-                                                   transaction.originUid, transaction.states,
-                                                   bufferLayersReadyToPresent)) {
+                const auto ready = [&]() REQUIRES(mStateLock) {
+                    if (pendingTransactions) {
+                        ATRACE_NAME("pendingTransactions");
+                        return TransactionReadiness::NotReady;
+                    }
+
+                    return transactionIsReadyToBeApplied(transaction.frameTimelineInfo,
+                                                         transaction.isAutoTimestamp,
+                                                         transaction.desiredPresentTime,
+                                                         transaction.originUid, transaction.states,
+                                                         bufferLayersReadyToPresent,
+                                                         transactions.size(),
+                                                         /*tryApplyUnsignaled*/ false);
+                }();
+                ATRACE_INT("TransactionReadiness", static_cast<int>(ready));
+                if (ready != TransactionReadiness::Ready) {
+                    if (ready == TransactionReadiness::NotReadyBarrier) {
+                        transactionsPendingBarrier++;
+                    }
                     mPendingTransactionQueues[transaction.applyToken].push(std::move(transaction));
                 } else {
                     transaction.traverseStatesWithBuffers([&](const layer_state_t& state) {
-                        bufferLayersReadyToPresent.insert(state.surface);
+                        const bool frameNumberChanged = state.bufferData->flags.test(
+                                BufferData::BufferDataChange::frameNumberChanged);
+                        if (frameNumberChanged) {
+                            bufferLayersReadyToPresent[state.surface] = state.bufferData->frameNumber;
+                        } else {
+                            // Barrier function only used for BBQ which always includes a frame number.
+                            // This value only used for barrier logic.
+                            bufferLayersReadyToPresent[state.surface] =
+                                std::numeric_limits<uint64_t>::max();
+                        }
                     });
                     transactions.emplace_back(std::move(transaction));
                 }
-                mTransactionQueue.pop();
+                mTransactionQueue.pop_front();
                 ATRACE_INT("TransactionQueue", mTransactionQueue.size());
             }
-        }
 
-        // Now apply all transactions.
-        for (const auto& transaction : transactions) {
-            applyTransactionState(transaction.frameTimelineInfo, transaction.states,
-                                  transaction.displays, transaction.flags,
-                                  transaction.inputWindowCommands, transaction.desiredPresentTime,
-                                  transaction.isAutoTimestamp, transaction.buffer,
-                                  transaction.postTime, transaction.permissions,
-                                  transaction.hasListenerCallbacks, transaction.listenerCallbacks,
-                                  transaction.originPid, transaction.originUid, transaction.id);
-            if (transaction.transactionCommittedSignal) {
-                mTransactionCommittedSignals.emplace_back(
-                        std::move(transaction.transactionCommittedSignal));
+            // Transactions with a buffer pending on a barrier may be on a different applyToken
+            // than the transaction which satisfies our barrier. In fact this is the exact use case
+            // that the primitive is designed for. This means we may first process
+            // the barrier dependent transaction, determine it ineligible to complete
+            // and then satisfy in a later inner iteration of flushPendingTransactionQueues.
+            // The barrier dependent transaction was eligible to be presented in this frame
+            // but we would have prevented it without case. To fix this we continually
+            // loop through flushPendingTransactionQueues until we perform an iteration
+            // where the number of transactionsPendingBarrier doesn't change. This way
+            // we can continue to resolve dependency chains of barriers as far as possible.
+            while (lastTransactionsPendingBarrier != transactionsPendingBarrier) {
+                lastTransactionsPendingBarrier = transactionsPendingBarrier;
+                transactionsPendingBarrier =
+                    flushPendingTransactionQueues(transactions, bufferLayersReadyToPresent,
+                        applyTokensWithUnsignaledTransactions,
+                        /*tryApplyUnsignaled*/ false);
             }
+
+            // We collected all transactions that could apply without latching unsignaled buffers.
+            // If we are allowing latch unsignaled of some form, now it's the time to go over the
+            // transactions that were not applied and try to apply them unsignaled.
+            if (enableLatchUnsignaledConfig != LatchUnsignaledConfig::Disabled) {
+                flushUnsignaledPendingTransactionQueues(transactions, bufferLayersReadyToPresent,
+                                                        applyTokensWithUnsignaledTransactions);
+            }
+
+            return applyTransactions(transactions, vsyncId);
         }
     }
 }
 
+bool SurfaceFlinger::applyTransactions(std::vector<TransactionState>& transactions,
+                                       int64_t vsyncId) {
+    bool needsTraversal = false;
+    // Now apply all transactions.
+    for (auto& transaction : transactions) {
+        needsTraversal |=
+                applyTransactionState(transaction.frameTimelineInfo, transaction.states,
+                                      transaction.displays, transaction.flags,
+                                      transaction.inputWindowCommands,
+                                      transaction.desiredPresentTime, transaction.isAutoTimestamp,
+                                      transaction.buffer, transaction.postTime,
+                                      transaction.permissions, transaction.hasListenerCallbacks,
+                                      transaction.listenerCallbacks, transaction.originPid,
+                                      transaction.originUid, transaction.id);
+        if (transaction.transactionCommittedSignal) {
+            mTransactionCommittedSignals.emplace_back(
+                    std::move(transaction.transactionCommittedSignal));
+        }
+    }
+
+    if (mTransactionTracing) {
+        mTransactionTracing->addCommittedTransactions(transactions, vsyncId);
+    }
+    return needsTraversal;
+}
+
 bool SurfaceFlinger::transactionFlushNeeded() {
     Mutex::Autolock _l(mQueueLock);
     return !mPendingTransactionQueues.empty() || !mTransactionQueue.empty();
@@ -3579,43 +3922,83 @@
     return prediction->presentTime >= expectedPresentTime &&
             prediction->presentTime - expectedPresentTime >= earlyLatchVsyncThreshold;
 }
+bool SurfaceFlinger::shouldLatchUnsignaled(const sp<Layer>& layer, const layer_state_t& state,
+                                           size_t numStates, size_t totalTXapplied) const {
+    if (enableLatchUnsignaledConfig == LatchUnsignaledConfig::Disabled) {
+        ALOGV("%s: false (LatchUnsignaledConfig::Disabled)", __func__);
+        return false;
+    }
 
-bool SurfaceFlinger::transactionIsReadyToBeApplied(
+    if (enableLatchUnsignaledConfig == LatchUnsignaledConfig::Always) {
+        ALOGV("%s: true (LatchUnsignaledConfig::Always)", __func__);
+        return true;
+    }
+
+    // We only want to latch unsignaled when a single layer is updated in this
+    // transaction (i.e. not a blast sync transaction).
+    if (numStates != 1) {
+        ALOGV("%s: false (numStates=%zu)", __func__, numStates);
+        return false;
+    }
+
+    if (enableLatchUnsignaledConfig == LatchUnsignaledConfig::AutoSingleLayer) {
+        if (totalTXapplied > 0) {
+            ALOGV("%s: false (LatchUnsignaledConfig::AutoSingleLayer; totalTXapplied=%zu)",
+                  __func__, totalTXapplied);
+            return false;
+        }
+
+        // We don't want to latch unsignaled if are in early / client composition
+        // as it leads to jank due to RenderEngine waiting for unsignaled buffer
+        // or window animations being slow.
+        const auto isDefaultVsyncConfig = mVsyncModulator->isVsyncConfigDefault();
+        if (!isDefaultVsyncConfig) {
+            ALOGV("%s: false (LatchUnsignaledConfig::AutoSingleLayer; !isDefaultVsyncConfig)",
+                  __func__);
+            return false;
+        }
+    }
+
+    if (!layer->simpleBufferUpdate(state)) {
+        ALOGV("%s: false (!simpleBufferUpdate)", __func__);
+        return false;
+    }
+
+    ALOGV("%s: true", __func__);
+    return true;
+}
+
+auto SurfaceFlinger::transactionIsReadyToBeApplied(
         const FrameTimelineInfo& info, bool isAutoTimestamp, int64_t desiredPresentTime,
         uid_t originUid, const Vector<ComposerState>& states,
-        const std::unordered_set<sp<IBinder>, ISurfaceComposer::SpHash<IBinder>>&
-                bufferLayersReadyToPresent) const {
-    ATRACE_CALL();
+        const std::unordered_map<
+            sp<IBinder>, uint64_t, SpHash<IBinder>>& bufferLayersReadyToPresent,
+        size_t totalTXapplied, bool tryApplyUnsignaled) const -> TransactionReadiness {
+    ATRACE_FORMAT("transactionIsReadyToBeApplied vsyncId: %" PRId64, info.vsyncId);
     const nsecs_t expectedPresentTime = mExpectedPresentTime.load();
     // Do not present if the desiredPresentTime has not passed unless it is more than one second
     // in the future. We ignore timestamps more than 1 second in the future for stability reasons.
     if (!isAutoTimestamp && desiredPresentTime >= expectedPresentTime &&
         desiredPresentTime < expectedPresentTime + s2ns(1)) {
         ATRACE_NAME("not current");
-        return false;
+        return TransactionReadiness::NotReady;
     }
 
     if (!mScheduler->isVsyncValid(expectedPresentTime, originUid)) {
         ATRACE_NAME("!isVsyncValid");
-        return false;
+        return TransactionReadiness::NotReady;
     }
 
     // If the client didn't specify desiredPresentTime, use the vsyncId to determine the expected
     // present time of this transaction.
     if (isAutoTimestamp && frameIsEarly(expectedPresentTime, info.vsyncId)) {
         ATRACE_NAME("frameIsEarly");
-        return false;
+        return TransactionReadiness::NotReady;
     }
 
+    bool fenceUnsignaled = false;
     for (const ComposerState& state : states) {
         const layer_state_t& s = state.state;
-        const bool acquireFenceChanged = (s.what & layer_state_t::eAcquireFenceChanged);
-        if (acquireFenceChanged && s.acquireFence && !enableLatchUnsignaled &&
-            s.acquireFence->getStatus() == Fence::Status::Unsignaled) {
-            ATRACE_NAME("fence unsignaled");
-            return false;
-        }
-
         sp<Layer> layer = nullptr;
         if (s.surface) {
             layer = fromHandle(s.surface).promote();
@@ -3627,42 +4010,51 @@
             continue;
         }
 
-        ATRACE_NAME(layer->getName().c_str());
+        if (s.hasBufferChanges() && s.bufferData->hasBarrier &&
+            ((layer->getDrawingState().frameNumber) < s.bufferData->barrierFrameNumber)) {
+            const bool willApplyBarrierFrame =
+                (bufferLayersReadyToPresent.find(s.surface) != bufferLayersReadyToPresent.end()) &&
+                (bufferLayersReadyToPresent.at(s.surface) >= s.bufferData->barrierFrameNumber);
+            if (!willApplyBarrierFrame) {
+                ATRACE_NAME("NotReadyBarrier");
+                return TransactionReadiness::NotReadyBarrier;
+            }
+        }
+
+        const bool allowLatchUnsignaled = tryApplyUnsignaled &&
+                shouldLatchUnsignaled(layer, s, states.size(), totalTXapplied);
+        ATRACE_FORMAT("%s allowLatchUnsignaled=%s", layer->getName().c_str(),
+                      allowLatchUnsignaled ? "true" : "false");
+
+        const bool acquireFenceChanged = s.bufferData &&
+                s.bufferData->flags.test(BufferData::BufferDataChange::fenceChanged) &&
+                s.bufferData->acquireFence;
+        fenceUnsignaled = fenceUnsignaled ||
+                (acquireFenceChanged &&
+                 s.bufferData->acquireFence->getStatus() == Fence::Status::Unsignaled);
+
+        if (fenceUnsignaled && !allowLatchUnsignaled) {
+            ATRACE_NAME("fence unsignaled");
+            return TransactionReadiness::NotReady;
+        }
 
         if (s.hasBufferChanges()) {
             // If backpressure is enabled and we already have a buffer to commit, keep the
             // transaction in the queue.
-            const bool hasPendingBuffer =
-                    bufferLayersReadyToPresent.find(s.surface) != bufferLayersReadyToPresent.end();
+            const bool hasPendingBuffer = bufferLayersReadyToPresent.find(s.surface) !=
+                bufferLayersReadyToPresent.end();
             if (layer->backpressureEnabled() && hasPendingBuffer && isAutoTimestamp) {
                 ATRACE_NAME("hasPendingBuffer");
-                return false;
+                return TransactionReadiness::NotReady;
             }
         }
     }
-    return true;
+    return fenceUnsignaled ? TransactionReadiness::ReadyUnsignaled : TransactionReadiness::Ready;
 }
 
 void SurfaceFlinger::queueTransaction(TransactionState& state) {
     Mutex::Autolock _l(mQueueLock);
 
-    // If its TransactionQueue already has a pending TransactionState or if it is pending
-    auto itr = mPendingTransactionQueues.find(state.applyToken);
-    // if this is an animation frame, wait until prior animation frame has
-    // been applied by SF
-    if (state.flags & eAnimation) {
-        while (itr != mPendingTransactionQueues.end()) {
-            status_t err = mTransactionQueueCV.waitRelative(mQueueLock, s2ns(5));
-            if (CC_UNLIKELY(err != NO_ERROR)) {
-                ALOGW_IF(err == TIMED_OUT,
-                         "setTransactionState timed out "
-                         "waiting for animation frame to apply");
-                break;
-            }
-            itr = mPendingTransactionQueues.find(state.applyToken);
-        }
-    }
-
     // Generate a CountDownLatch pending state if this is a synchronous transaction.
     if ((state.flags & eSynchronous) || state.inputWindowCommands.syncInputWindows) {
         state.transactionCommittedSignal = std::make_shared<CountDownLatch>(
@@ -3671,7 +4063,7 @@
                          : CountDownLatch::eSyncTransaction));
     }
 
-    mTransactionQueue.emplace(state);
+    mTransactionQueue.emplace_back(state);
     ATRACE_INT("TransactionQueue", mTransactionQueue.size());
 
     const auto schedule = [](uint32_t flags) {
@@ -3688,7 +4080,8 @@
     // applyTransactionState is called on the main SF thread.  While a given process may wish
     // to wait on synchronous transactions, the main SF thread should apply the transaction and
     // set the value to notify this after committed.
-    if (!transactionCommittedSignal.wait_until(std::chrono::seconds(5))) {
+    if (!transactionCommittedSignal.wait_until(
+                std::chrono::nanoseconds(mAnimationTransactionTimeout))) {
         ALOGE("setTransactionState timed out!");
     }
 }
@@ -3713,15 +4106,20 @@
     ATRACE_CALL();
 
     uint32_t permissions =
-            callingThreadHasUnscopedSurfaceFlingerAccess() ? Permission::ACCESS_SURFACE_FLINGER : 0;
+        callingThreadHasUnscopedSurfaceFlingerAccess() ?
+        layer_state_t::Permission::ACCESS_SURFACE_FLINGER : 0;
     // Avoid checking for rotation permissions if the caller already has ACCESS_SURFACE_FLINGER
     // permissions.
-    if ((permissions & Permission::ACCESS_SURFACE_FLINGER) ||
+    if ((permissions & layer_state_t::Permission::ACCESS_SURFACE_FLINGER) ||
         callingThreadHasRotateSurfaceFlingerAccess()) {
-        permissions |= Permission::ROTATE_SURFACE_FLINGER;
+        permissions |= layer_state_t::Permission::ROTATE_SURFACE_FLINGER;
     }
 
-    if (!(permissions & Permission::ACCESS_SURFACE_FLINGER) &&
+    if (callingThreadHasInternalSystemWindowAccess()) {
+        permissions |= layer_state_t::Permission::INTERNAL_SYSTEM_WINDOW;
+    }
+
+    if (!(permissions & layer_state_t::Permission::ACCESS_SURFACE_FLINGER) &&
         (flags & (eEarlyWakeupStart | eEarlyWakeupEnd))) {
         ALOGE("Only WindowManager is allowed to use eEarlyWakeup[Start|End] flags");
         flags &= ~(eEarlyWakeupStart | eEarlyWakeupEnd);
@@ -3745,6 +4143,10 @@
     state.traverseStatesWithBuffers([&](const layer_state_t& state) {
         mBufferCountTracker.increment(state.surface->localBinder());
     });
+
+    if (mTransactionTracing) {
+        mTransactionTracing->addQueuedTransaction(state);
+    }
     queueTransaction(state);
 
     // Check the pending state to make sure the transaction is synchronous.
@@ -3755,8 +4157,8 @@
     return NO_ERROR;
 }
 
-void SurfaceFlinger::applyTransactionState(const FrameTimelineInfo& frameTimelineInfo,
-                                           const Vector<ComposerState>& states,
+bool SurfaceFlinger::applyTransactionState(const FrameTimelineInfo& frameTimelineInfo,
+                                           Vector<ComposerState>& states,
                                            const Vector<DisplayState>& displays, uint32_t flags,
                                            const InputWindowCommands& inputWindowCommands,
                                            const int64_t desiredPresentTime, bool isAutoTimestamp,
@@ -3774,36 +4176,27 @@
     // that listeners with SurfaceControls will start registration during setClientStateLocked
     // below.
     for (const auto& listener : listenerCallbacks) {
-        mTransactionCallbackInvoker.startRegistration(listener);
-        mTransactionCallbackInvoker.endRegistration(listener);
+        mTransactionCallbackInvoker.addEmptyTransaction(listener);
     }
 
-    std::unordered_set<ListenerCallbacks, ListenerCallbacksHash> listenerCallbacksWithSurfaces;
     uint32_t clientStateFlags = 0;
-    for (const ComposerState& state : states) {
-        clientStateFlags |=
-                setClientStateLocked(frameTimelineInfo, state, desiredPresentTime, isAutoTimestamp,
-                                     postTime, permissions, listenerCallbacksWithSurfaces);
+    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(); layer) {
+            if (const auto layer = fromHandle(state.state.surface).promote()) {
+                using LayerUpdateType = scheduler::LayerHistory::LayerUpdateType;
                 mScheduler->recordLayerHistory(layer.get(),
                                                isAutoTimestamp ? 0 : desiredPresentTime,
-                                               LayerHistory::LayerUpdateType::AnimationTX);
+                                               LayerUpdateType::AnimationTX);
             }
         }
     }
 
-    for (const auto& listenerCallback : listenerCallbacksWithSurfaces) {
-        mTransactionCallbackInvoker.endRegistration(listenerCallback);
-    }
-
-    // If the state doesn't require a traversal and there are callbacks, send them now
-    if (!(clientStateFlags & eTraversalNeeded) && hasListenerCallbacks) {
-        mTransactionCallbackInvoker.sendCallbacks();
-    }
     transactionFlags |= clientStateFlags;
 
-    if (permissions & Permission::ACCESS_SURFACE_FLINGER) {
+    if (permissions & layer_state_t::Permission::ACCESS_SURFACE_FLINGER) {
         transactionFlags |= addInputWindowCommands(inputWindowCommands);
     } else if (!inputWindowCommands.empty()) {
         ALOGE("Only privileged callers are allowed to send input commands.");
@@ -3822,6 +4215,7 @@
         transactionFlags = eTransactionNeeded;
     }
 
+    bool needsTraversal = false;
     if (transactionFlags) {
         if (mInterceptor->isEnabled()) {
             mInterceptor->saveTransaction(states, mCurrentState.displays, displays, flags,
@@ -3832,7 +4226,7 @@
         // so we don't have to wake up again next frame to preform an unnecessary traversal.
         if (transactionFlags & eTraversalNeeded) {
             transactionFlags = transactionFlags & (~eTraversalNeeded);
-            mForceTraversal = true;
+            needsTraversal = true;
         }
         if (transactionFlags) {
             setTransactionFlags(transactionFlags);
@@ -3842,6 +4236,8 @@
             mAnimTransactionPending = true;
         }
     }
+
+    return needsTraversal;
 }
 
 uint32_t SurfaceFlinger::setDisplayStateLocked(const DisplayState& s) {
@@ -3910,12 +4306,12 @@
     return true;
 }
 
-uint32_t SurfaceFlinger::setClientStateLocked(
-        const FrameTimelineInfo& frameTimelineInfo, const ComposerState& composerState,
-        int64_t desiredPresentTime, bool isAutoTimestamp, int64_t postTime, uint32_t permissions,
-        std::unordered_set<ListenerCallbacks, ListenerCallbacksHash>& outListenerCallbacks) {
-    const layer_state_t& s = composerState.state;
-    const bool privileged = permissions & Permission::ACCESS_SURFACE_FLINGER;
+uint32_t SurfaceFlinger::setClientStateLocked(const FrameTimelineInfo& frameTimelineInfo,
+                                              ComposerState& composerState,
+                                              int64_t desiredPresentTime, bool isAutoTimestamp,
+                                              int64_t postTime, uint32_t permissions) {
+    layer_state_t& s = composerState.state;
+    s.sanitize(permissions);
 
     std::vector<ListenerCallbacks> filteredListeners;
     for (auto& listener : s.listeners) {
@@ -3926,16 +4322,12 @@
 
         ListenerCallbacks onCommitCallbacks = listener.filter(CallbackId::Type::ON_COMMIT);
         if (!onCommitCallbacks.callbackIds.empty()) {
-            mTransactionCallbackInvoker.startRegistration(onCommitCallbacks);
             filteredListeners.push_back(onCommitCallbacks);
-            outListenerCallbacks.insert(onCommitCallbacks);
         }
 
         ListenerCallbacks onCompleteCallbacks = listener.filter(CallbackId::Type::ON_COMPLETE);
         if (!onCompleteCallbacks.callbackIds.empty()) {
-            mTransactionCallbackInvoker.startRegistration(onCompleteCallbacks);
             filteredListeners.push_back(onCompleteCallbacks);
-            outListenerCallbacks.insert(onCompleteCallbacks);
         }
     }
 
@@ -3943,15 +4335,7 @@
     uint32_t flags = 0;
     sp<Layer> layer = nullptr;
     if (s.surface) {
-        if (what & layer_state_t::eLayerCreated) {
-            layer = handleLayerCreatedLocked(s.surface);
-            if (layer) {
-                flags |= eTransactionNeeded | eTraversalNeeded;
-                mLayersAdded = true;
-            }
-        } else {
-            layer = fromHandle(s.surface).promote();
-        }
+        layer = fromHandle(s.surface).promote();
     } 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");
@@ -4037,36 +4421,14 @@
         }
     }
     if (what & layer_state_t::eMatrixChanged) {
-        // TODO: b/109894387
-        //
-        // SurfaceFlinger's renderer is not prepared to handle cropping in the face of arbitrary
-        // rotation. To see the problem observe that if we have a square parent, and a child
-        // of the same size, then we rotate the child 45 degrees around it's center, the child
-        // must now be cropped to a non rectangular 8 sided region.
-        //
-        // Of course we can fix this in the future. For now, we are lucky, SurfaceControl is
-        // private API, and arbitrary rotation is used in limited use cases, for instance:
-        // - WindowManager only uses rotation in one case, which is on a top level layer in which
-        //   cropping is not an issue.
-        // - Launcher, as a privileged app, uses this to transition an application to PiP
-        //   (picture-in-picture) mode.
-        //
-        // However given that abuse of rotation matrices could lead to surfaces extending outside
-        // of cropped areas, we need to prevent non-root clients without permission
-        // ACCESS_SURFACE_FLINGER nor ROTATE_SURFACE_FLINGER
-        // (a.k.a. everyone except WindowManager / tests / Launcher) from setting non rectangle
-        // preserving transformations.
-        const bool allowNonRectPreservingTransforms =
-                permissions & Permission::ROTATE_SURFACE_FLINGER;
-        if (layer->setMatrix(s.matrix, allowNonRectPreservingTransforms)) flags |= eTraversalNeeded;
+        if (layer->setMatrix(s.matrix)) flags |= eTraversalNeeded;
     }
     if (what & layer_state_t::eTransparentRegionChanged) {
         if (layer->setTransparentRegionHint(s.transparentRegion))
             flags |= eTraversalNeeded;
     }
     if (what & layer_state_t::eFlagsChanged) {
-        if (layer->setFlags(s.flags, s.mask))
-            flags |= eTraversalNeeded;
+        if (layer->setFlags(s.flags, s.mask)) flags |= eTraversalNeeded;
     }
     if (what & layer_state_t::eCornerRadiusChanged) {
         if (layer->setCornerRadius(s.cornerRadius))
@@ -4108,9 +4470,6 @@
     if (what & layer_state_t::eCropChanged) {
         if (layer->setCrop(s.crop)) flags |= eTraversalNeeded;
     }
-    if (what & layer_state_t::eAcquireFenceChanged) {
-        if (layer->setAcquireFence(s.acquireFence)) flags |= eTraversalNeeded;
-    }
     if (what & layer_state_t::eDataspaceChanged) {
         if (layer->setDataspace(s.dataspace)) flags |= eTraversalNeeded;
     }
@@ -4127,23 +4486,20 @@
         if (layer->setSidebandStream(s.sidebandStream)) flags |= eTraversalNeeded;
     }
     if (what & layer_state_t::eInputInfoChanged) {
-        if (privileged) {
-            layer->setInputInfo(*s.windowInfoHandle->getInfo());
-            flags |= eTraversalNeeded;
-        } else {
-            ALOGE("Attempt to update WindowInfo without permission ACCESS_SURFACE_FLINGER");
-        }
+        layer->setInputInfo(*s.windowInfoHandle->getInfo());
+        flags |= eTraversalNeeded;
     }
     std::optional<nsecs_t> dequeueBufferTimestamp;
     if (what & layer_state_t::eMetadataChanged) {
         dequeueBufferTimestamp = s.metadata.getInt64(METADATA_DEQUEUE_TIME);
-        auto gameMode = s.metadata.getInt32(METADATA_GAME_MODE, -1);
-        if (gameMode != -1) {
+
+        if (const int32_t gameMode = s.metadata.getInt32(METADATA_GAME_MODE, -1); gameMode != -1) {
             // The transaction will be received on the Task layer and needs to be applied to all
             // child layers. Child layers that are added at a later point will obtain the game mode
             // info through addChild().
-            layer->setGameModeForTree(gameMode);
+            layer->setGameModeForTree(static_cast<GameMode>(gameMode));
         }
+
         if (layer->setMetadata(s.metadata)) flags |= eTraversalNeeded;
     }
     if (what & layer_state_t::eColorSpaceAgnosticChanged) {
@@ -4155,21 +4511,19 @@
         if (layer->setShadowRadius(s.shadowRadius)) flags |= eTraversalNeeded;
     }
     if (what & layer_state_t::eFrameRateSelectionPriority) {
-        if (privileged && layer->setFrameRateSelectionPriority(s.frameRateSelectionPriority)) {
+        if (layer->setFrameRateSelectionPriority(s.frameRateSelectionPriority)) {
             flags |= eTraversalNeeded;
         }
     }
     if (what & layer_state_t::eFrameRateChanged) {
-        if (ValidateFrameRate(s.frameRate, s.frameRateCompatibility, s.changeFrameRateStrategy,
-                              "SurfaceFlinger::setClientStateLocked", privileged)) {
-            const auto compatibility =
-                    Layer::FrameRate::convertCompatibility(s.frameRateCompatibility);
-            const auto strategy =
-                    Layer::FrameRate::convertChangeFrameRateStrategy(s.changeFrameRateStrategy);
+        const auto compatibility =
+            Layer::FrameRate::convertCompatibility(s.frameRateCompatibility);
+        const auto strategy =
+            Layer::FrameRate::convertChangeFrameRateStrategy(s.changeFrameRateStrategy);
 
-            if (layer->setFrameRate(Layer::FrameRate(Fps(s.frameRate), compatibility, strategy))) {
-                flags |= eTraversalNeeded;
-            }
+        if (layer->setFrameRate(
+                Layer::FrameRate(Fps::fromValue(s.frameRate), compatibility, strategy))) {
+          flags |= eTraversalNeeded;
         }
     }
     if (what & layer_state_t::eFixedTransformHintChanged) {
@@ -4181,12 +4535,8 @@
         layer->setAutoRefresh(s.autoRefresh);
     }
     if (what & layer_state_t::eTrustedOverlayChanged) {
-        if (privileged) {
-            if (layer->setTrustedOverlay(s.isTrustedOverlay)) {
-                flags |= eTraversalNeeded;
-            }
-        } else {
-            ALOGE("Attempt to set trusted overlay without permission ACCESS_SURFACE_FLINGER");
+        if (layer->setTrustedOverlay(s.isTrustedOverlay)) {
+            flags |= eTraversalNeeded;
         }
     }
     if (what & layer_state_t::eStretchChanged) {
@@ -4205,13 +4555,9 @@
         }
     }
     if (what & layer_state_t::eDropInputModeChanged) {
-        if (privileged) {
-            if (layer->setDropInputMode(s.dropInputMode)) {
-                flags |= eTraversalNeeded;
-                mInputInfoChanged = true;
-            }
-        } else {
-            ALOGE("Attempt to update InputPolicyFlags without permission ACCESS_SURFACE_FLINGER");
+        if (layer->setDropInputMode(s.dropInputMode)) {
+            flags |= eTraversalNeeded;
+            mInputInfoChanged = true;
         }
     }
     // This has to happen after we reparent children because when we reparent to null we remove
@@ -4237,41 +4583,12 @@
             callbackHandles.emplace_back(new CallbackHandle(listener, callbackIds, s.surface));
         }
     }
-    bool bufferChanged = what & layer_state_t::eBufferChanged;
-    bool cacheIdChanged = what & layer_state_t::eCachedBufferChanged;
-    bool bufferSizeExceedsLimit = false;
-    std::shared_ptr<renderengine::ExternalTexture> buffer;
-    if (bufferChanged && cacheIdChanged && s.buffer != nullptr) {
-        bufferSizeExceedsLimit =
-                exceedsMaxRenderTargetSize(s.buffer->getWidth(), s.buffer->getHeight());
-        if (!bufferSizeExceedsLimit) {
-            ClientCache::getInstance().add(s.cachedBuffer, s.buffer);
-            buffer = ClientCache::getInstance().get(s.cachedBuffer);
-        }
-    } else if (cacheIdChanged) {
-        buffer = ClientCache::getInstance().get(s.cachedBuffer);
-    } else if (bufferChanged && s.buffer != nullptr) {
-        bufferSizeExceedsLimit =
-                exceedsMaxRenderTargetSize(s.buffer->getWidth(), s.buffer->getHeight());
-        if (!bufferSizeExceedsLimit) {
-            buffer = std::make_shared<
-                    renderengine::ExternalTexture>(s.buffer, getRenderEngine(),
-                                                   renderengine::ExternalTexture::Usage::READABLE);
-        }
-    }
-    ALOGE_IF(bufferSizeExceedsLimit,
-             "Attempted to create an ExternalTexture for layer %s that exceeds render target size "
-             "limit.",
-             layer->getDebugName());
-    if (buffer) {
-        const bool frameNumberChanged = what & layer_state_t::eFrameNumberChanged;
-        const uint64_t frameNumber = frameNumberChanged
-                ? s.frameNumber
-                : layer->getHeadFrameNumber(-1 /* expectedPresentTime */) + 1;
 
-        if (layer->setBuffer(buffer, s.acquireFence, postTime, desiredPresentTime, isAutoTimestamp,
-                             s.cachedBuffer, frameNumber, dequeueBufferTimestamp, frameTimelineInfo,
-                             s.releaseBufferListener, s.releaseBufferEndpoint)) {
+    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)) {
             flags |= eTraversalNeeded;
         }
     } else if (frameTimelineInfo.vsyncId != FrameTimelineInfo::INVALID_VSYNC_ID) {
@@ -4289,25 +4606,22 @@
     return hasChanges ? eTraversalNeeded : 0;
 }
 
-status_t SurfaceFlinger::mirrorLayer(const sp<Client>& client, const sp<IBinder>& mirrorFromHandle,
-                                     sp<IBinder>* outHandle, int32_t* outLayerId) {
+status_t SurfaceFlinger::mirrorLayer(const LayerCreationArgs& args,
+                                     const sp<IBinder>& mirrorFromHandle, sp<IBinder>* outHandle,
+                                     int32_t* outLayerId) {
     if (!mirrorFromHandle) {
         return NAME_NOT_FOUND;
     }
 
     sp<Layer> mirrorLayer;
     sp<Layer> mirrorFrom;
-    std::string uniqueName = getUniqueLayerName("MirrorRoot");
-
     {
         Mutex::Autolock _l(mStateLock);
         mirrorFrom = fromHandle(mirrorFromHandle).promote();
         if (!mirrorFrom) {
             return NAME_NOT_FOUND;
         }
-
-        status_t result = createContainerLayer(client, std::move(uniqueName), -1, -1, 0,
-                                               LayerMetadata(), outHandle, &mirrorLayer);
+        status_t result = createContainerLayer(args, outHandle, &mirrorLayer);
         if (result != NO_ERROR) {
             return result;
         }
@@ -4316,22 +4630,17 @@
     }
 
     *outLayerId = mirrorLayer->sequence;
-    return addClientLayer(client, *outHandle, nullptr, mirrorLayer, nullptr, nullptr, false,
-                          nullptr /* outTransformHint */);
+    if (mTransactionTracing) {
+        mTransactionTracing->onMirrorLayerAdded((*outHandle)->localBinder(), mirrorLayer->sequence,
+                                                args.name, mirrorFrom->sequence);
+    }
+    return addClientLayer(args.client, *outHandle, mirrorLayer /* layer */, nullptr /* parent */,
+                          false /* addToRoot */, nullptr /* outTransformHint */);
 }
 
-status_t SurfaceFlinger::createLayer(const String8& name, const sp<Client>& client, uint32_t w,
-                                     uint32_t h, PixelFormat format, uint32_t flags,
-                                     LayerMetadata metadata, sp<IBinder>* handle,
-                                     sp<IGraphicBufferProducer>* gbp,
+status_t SurfaceFlinger::createLayer(LayerCreationArgs& args, sp<IBinder>* outHandle,
                                      const sp<IBinder>& parentHandle, int32_t* outLayerId,
                                      const sp<Layer>& parentLayer, uint32_t* outTransformHint) {
-    if (int32_t(w|h) < 0) {
-        ALOGE("createLayer() failed, w or h is negative (w=%d, h=%d)",
-                int(w), int(h));
-        return BAD_VALUE;
-    }
-
     ALOG_ASSERT(parentLayer == nullptr || parentHandle == nullptr,
             "Expected only one of parentLayer or parentHandle to be non-null. "
             "Programmer error?");
@@ -4340,40 +4649,22 @@
 
     sp<Layer> layer;
 
-    std::string uniqueName = getUniqueLayerName(name.string());
-
-    switch (flags & ISurfaceComposerClient::eFXSurfaceMask) {
+    switch (args.flags & ISurfaceComposerClient::eFXSurfaceMask) {
         case ISurfaceComposerClient::eFXSurfaceBufferQueue:
         case ISurfaceComposerClient::eFXSurfaceBufferState: {
-            result = createBufferStateLayer(client, std::move(uniqueName), w, h, flags,
-                                            std::move(metadata), handle, &layer);
+            result = createBufferStateLayer(args, outHandle, &layer);
             std::atomic<int32_t>* pendingBufferCounter = layer->getPendingBufferCounter();
             if (pendingBufferCounter) {
                 std::string counterName = layer->getPendingBufferCounterName();
-                mBufferCountTracker.add((*handle)->localBinder(), counterName,
+                mBufferCountTracker.add((*outHandle)->localBinder(), counterName,
                                         pendingBufferCounter);
             }
         } break;
         case ISurfaceComposerClient::eFXSurfaceEffect:
-            // check if buffer size is set for color layer.
-            if (w > 0 || h > 0) {
-                ALOGE("createLayer() failed, w or h cannot be set for color layer (w=%d, h=%d)",
-                      int(w), int(h));
-                return BAD_VALUE;
-            }
-
-            result = createEffectLayer(client, std::move(uniqueName), w, h, flags,
-                                       std::move(metadata), handle, &layer);
+            result = createEffectLayer(args, outHandle, &layer);
             break;
         case ISurfaceComposerClient::eFXSurfaceContainer:
-            // check if buffer size is set for container layer.
-            if (w > 0 || h > 0) {
-                ALOGE("createLayer() failed, w or h cannot be set for container layer (w=%d, h=%d)",
-                      int(w), int(h));
-                return BAD_VALUE;
-            }
-            result = createContainerLayer(client, std::move(uniqueName), w, h, flags,
-                                          std::move(metadata), handle, &layer);
+            result = createContainerLayer(args, outHandle, &layer);
             break;
         default:
             result = BAD_VALUE;
@@ -4384,47 +4675,38 @@
         return result;
     }
 
-    bool addToRoot = callingThreadHasUnscopedSurfaceFlingerAccess();
-    result = addClientLayer(client, *handle, *gbp, layer, parentHandle, parentLayer, addToRoot,
-                            outTransformHint);
+    bool addToRoot = args.addToRoot && callingThreadHasUnscopedSurfaceFlingerAccess();
+    wp<Layer> parent(parentHandle != nullptr ? fromHandle(parentHandle) : parentLayer);
+    if (parentHandle != nullptr && parent == nullptr) {
+        ALOGE("Invalid parent handle %p.", parentHandle.get());
+        addToRoot = false;
+    }
+    if (parentLayer != nullptr) {
+        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();
+    }
+    if (mTransactionTracing) {
+        mTransactionTracing->onLayerAdded((*outHandle)->localBinder(), layer->sequence, args.name,
+                                          args.flags, parentId);
+    }
+
+    result = addClientLayer(args.client, *outHandle, layer, parent, addToRoot, outTransformHint);
     if (result != NO_ERROR) {
         return result;
     }
-    mInterceptor->saveSurfaceCreation(layer);
 
-    setTransactionFlags(eTransactionNeeded);
     *outLayerId = layer->sequence;
     return result;
 }
 
-std::string SurfaceFlinger::getUniqueLayerName(const char* name) {
-    unsigned dupeCounter = 0;
-
-    // Tack on our counter whether there is a hit or not, so everyone gets a tag
-    std::string uniqueName = base::StringPrintf("%s#%u", name, dupeCounter);
-
-    // Grab the state lock since we're accessing mCurrentState
-    Mutex::Autolock lock(mStateLock);
-
-    // Loop over layers until we're sure there is no matching name
-    bool matchFound = true;
-    while (matchFound) {
-        matchFound = false;
-        mCurrentState.traverse([&](Layer* layer) {
-            if (layer->getName() == uniqueName) {
-                matchFound = true;
-                uniqueName = base::StringPrintf("%s#%u", name, ++dupeCounter);
-            }
-        });
-    }
-
-    ALOGV_IF(dupeCounter > 0, "duplicate layer name: changing %s to %s", name, uniqueName.c_str());
-    return uniqueName;
-}
-
-status_t SurfaceFlinger::createBufferQueueLayer(const sp<Client>& client, std::string name,
-                                                uint32_t w, uint32_t h, uint32_t flags,
-                                                LayerMetadata metadata, PixelFormat& format,
+status_t SurfaceFlinger::createBufferQueueLayer(LayerCreationArgs& args, PixelFormat& format,
                                                 sp<IBinder>* handle,
                                                 sp<IGraphicBufferProducer>* gbp,
                                                 sp<Layer>* outLayer) {
@@ -4440,7 +4722,6 @@
     }
 
     sp<BufferQueueLayer> layer;
-    LayerCreationArgs args(this, client, std::move(name), w, h, flags, std::move(metadata));
     args.textureName = getNewTexture();
     {
         // Grab the SF state lock during this since it's the only safe way to access
@@ -4450,7 +4731,7 @@
         layer = getFactory().createBufferQueueLayer(args);
     }
 
-    status_t err = layer->setDefaultBufferProperties(w, h, format);
+    status_t err = layer->setDefaultBufferProperties(0, 0, format);
     if (err == NO_ERROR) {
         *handle = layer->getHandle();
         *gbp = layer->getProducer();
@@ -4461,34 +4742,24 @@
     return err;
 }
 
-status_t SurfaceFlinger::createBufferStateLayer(const sp<Client>& client, std::string name,
-                                                uint32_t w, uint32_t h, uint32_t flags,
-                                                LayerMetadata metadata, sp<IBinder>* handle,
+status_t SurfaceFlinger::createBufferStateLayer(LayerCreationArgs& args, sp<IBinder>* handle,
                                                 sp<Layer>* outLayer) {
-    LayerCreationArgs args(this, client, std::move(name), w, h, flags, std::move(metadata));
     args.textureName = getNewTexture();
-    sp<BufferStateLayer> layer = getFactory().createBufferStateLayer(args);
-    *handle = layer->getHandle();
-    *outLayer = layer;
-
-    return NO_ERROR;
-}
-
-status_t SurfaceFlinger::createEffectLayer(const sp<Client>& client, std::string name, uint32_t w,
-                                           uint32_t h, uint32_t flags, LayerMetadata metadata,
-                                           sp<IBinder>* handle, sp<Layer>* outLayer) {
-    *outLayer = getFactory().createEffectLayer(
-            {this, client, std::move(name), w, h, flags, std::move(metadata)});
+    *outLayer = getFactory().createBufferStateLayer(args);
     *handle = (*outLayer)->getHandle();
     return NO_ERROR;
 }
 
-status_t SurfaceFlinger::createContainerLayer(const sp<Client>& client, std::string name,
-                                              uint32_t w, uint32_t h, uint32_t flags,
-                                              LayerMetadata metadata, sp<IBinder>* handle,
+status_t SurfaceFlinger::createEffectLayer(const LayerCreationArgs& args, sp<IBinder>* handle,
+                                           sp<Layer>* outLayer) {
+    *outLayer = getFactory().createEffectLayer(args);
+    *handle = (*outLayer)->getHandle();
+    return NO_ERROR;
+}
+
+status_t SurfaceFlinger::createContainerLayer(const LayerCreationArgs& args, sp<IBinder>* handle,
                                               sp<Layer>* outLayer) {
-    *outLayer = getFactory().createContainerLayer(
-            {this, client, std::move(name), w, h, flags, std::move(metadata)});
+    *outLayer = getFactory().createContainerLayer(args);
     *handle = (*outLayer)->getHandle();
     return NO_ERROR;
 }
@@ -4512,6 +4783,9 @@
     markLayerPendingRemovalLocked(layer);
     mBufferCountTracker.remove(handle);
     layer.clear();
+    if (mTransactionTracing) {
+        mTransactionTracing->onHandleRemoved(handle);
+    }
 }
 
 // ---------------------------------------------------------------------------
@@ -4530,7 +4804,7 @@
     d.what = DisplayState::eDisplayProjectionChanged |
              DisplayState::eLayerStackChanged;
     d.token = token;
-    d.layerStack = 0;
+    d.layerStack = ui::DEFAULT_LAYER_STACK;
     d.orientation = ui::ROTATION_0;
     d.orientedDisplaySpaceRect.makeInvalid();
     d.layerStackSpaceRect.makeInvalid();
@@ -4539,14 +4813,15 @@
     displays.add(d);
 
     nsecs_t now = systemTime();
+
+    int64_t transactionId = (((int64_t)mPid) << 32) | mUniqueTransactionId++;
     // It should be on the main thread, apply it directly.
     applyTransactionState(FrameTimelineInfo{}, state, displays, 0, mInputWindowCommands,
                           /* desiredPresentTime */ now, true, {}, /* postTime */ now, true, false,
-                          {}, getpid(), getuid(), 0 /* Undefined transactionId */);
+                          {}, mPid, getuid(), transactionId);
 
     setPowerModeInternal(display, hal::PowerMode::ON);
-    const nsecs_t vsyncPeriod =
-            display->refreshRateConfigs().getCurrentRefreshRate().getVsyncPeriod();
+    const nsecs_t vsyncPeriod = display->refreshRateConfigs().getActiveMode()->getVsyncPeriod();
     mAnimFrameTracker.setDisplayRefreshPeriod(vsyncPeriod);
     mActiveDisplayTransformHint = display->getTransformHint();
     // Use phase of 0 since phase is not known.
@@ -4557,32 +4832,12 @@
 
 void SurfaceFlinger::initializeDisplays() {
     // Async since we may be called from the main thread.
-    static_cast<void>(schedule([this]() MAIN_THREAD { onInitializeDisplays(); }));
-}
-
-sp<DisplayDevice> SurfaceFlinger::getDisplayWithInputByLayer(Layer* layer) const {
-    sp<DisplayDevice> display;
-    for (const auto& pair : mDisplays) {
-        const auto& displayDevice = pair.second;
-        if (!displayDevice->receivesInput() ||
-            !displayDevice->getCompositionDisplay()
-                     ->belongsInOutput(layer->getLayerStack(), layer->getPrimaryDisplayOnly())) {
-            continue;
-        }
-        // Don't return immediately so that we can log duplicates.
-        if (display) {
-            ALOGE("Multiple display devices claim to accept input for the same layerstack: %d",
-                  layer->getLayerStack());
-            continue;
-        }
-        display = displayDevice;
-    }
-    return display;
+    static_cast<void>(mScheduler->schedule([this]() MAIN_THREAD { onInitializeDisplays(); }));
 }
 
 void SurfaceFlinger::setPowerModeInternal(const sp<DisplayDevice>& display, hal::PowerMode mode) {
     if (display->isVirtual()) {
-        ALOGE("%s: Invalid operation on virtual display", __FUNCTION__);
+        ALOGE("%s: Invalid operation on virtual display", __func__);
         return;
     }
 
@@ -4605,7 +4860,7 @@
     if (mInterceptor->isEnabled()) {
         mInterceptor->savePowerModeUpdate(display->getSequenceId(), static_cast<int32_t>(mode));
     }
-    const auto vsyncPeriod = display->refreshRateConfigs().getCurrentRefreshRate().getVsyncPeriod();
+    const auto refreshRate = display->refreshRateConfigs().getActiveMode()->getFps();
     if (currentMode == hal::PowerMode::OFF) {
         // Turn on the display
         if (display->isInternal() && (!activeDisplay || !activeDisplay->isPoweredOn())) {
@@ -4623,12 +4878,12 @@
         if (isDisplayActiveLocked(display) && mode != hal::PowerMode::DOZE_SUSPEND) {
             setHWCVsyncEnabled(displayId, mHWCVsyncPendingState);
             mScheduler->onScreenAcquired(mAppConnectionHandle);
-            mScheduler->resyncToHardwareVsync(true, vsyncPeriod);
+            mScheduler->resyncToHardwareVsync(true, refreshRate);
         }
 
         mVisibleRegionsDirty = true;
         mHasPoweredOff = true;
-        repaintEverything();
+        scheduleComposite(FrameHint::kActive);
     } else if (mode == hal::PowerMode::OFF) {
         // Turn off the display
         if (SurfaceFlinger::setSchedFifo(false) != NO_ERROR) {
@@ -4653,7 +4908,7 @@
         getHwComposer().setPowerMode(displayId, mode);
         if (isDisplayActiveLocked(display) && currentMode == hal::PowerMode::DOZE_SUSPEND) {
             mScheduler->onScreenAcquired(mAppConnectionHandle);
-            mScheduler->resyncToHardwareVsync(true, vsyncPeriod);
+            mScheduler->resyncToHardwareVsync(true, refreshRate);
         }
     } else if (mode == hal::PowerMode::DOZE_SUSPEND) {
         // Leave display going to doze
@@ -4677,7 +4932,7 @@
 }
 
 void SurfaceFlinger::setPowerMode(const sp<IBinder>& displayToken, int mode) {
-    schedule([=]() MAIN_THREAD {
+    auto future = mScheduler->schedule([=]() MAIN_THREAD {
         const auto display = getDisplayDeviceLocked(displayToken);
         if (!display) {
             ALOGE("Attempt to set power mode %d for invalid display token %p", mode,
@@ -4687,7 +4942,9 @@
         } else {
             setPowerModeInternal(display, static_cast<hal::PowerMode>(mode));
         }
-    }).wait();
+    });
+
+    future.wait();
 }
 
 status_t SurfaceFlinger::doDump(int fd, const DumpArgs& args, bool asProto) {
@@ -4737,7 +4994,7 @@
         }
 
         if (dumpLayers) {
-            LayersTraceFileProto traceFileProto = SurfaceTracing::createLayersTraceFileProto();
+            LayersTraceFileProto traceFileProto = mLayerTracing.createTraceFileProto();
             LayersTraceProto* layersTrace = traceFileProto.add_entry();
             LayersProto layersProto = dumpProtoFromMainThread();
             layersTrace->mutable_layers()->Swap(&layersProto);
@@ -4759,8 +5016,11 @@
 }
 
 status_t SurfaceFlinger::dumpCritical(int fd, const DumpArgs&, bool asProto) {
-    if (asProto && mTracing.isEnabled()) {
-        mTracing.writeToFile();
+    if (asProto) {
+        mLayerTracing.writeToFile();
+        if (mTransactionTracing) {
+            mTransactionTracing->writeToFile();
+        }
     }
 
     return doDump(fd, DumpArgs(), asProto);
@@ -4872,24 +5132,6 @@
                   bucketTimeSec, percent);
 }
 
-void SurfaceFlinger::recordBufferingStats(const std::string& layerName,
-                                          std::vector<OccupancyTracker::Segment>&& history) {
-    Mutex::Autolock lock(getBE().mBufferingStatsMutex);
-    auto& stats = getBE().mBufferingStats[layerName];
-    for (const auto& segment : history) {
-        if (!segment.usedThirdBuffer) {
-            stats.twoBufferTime += segment.totalTime;
-        }
-        if (segment.occupancyAverage < 1.0f) {
-            stats.doubleBufferedTime += segment.totalTime;
-        } else if (segment.occupancyAverage < 2.0f) {
-            stats.tripleBufferedTime += segment.totalTime;
-        }
-        ++stats.numSegments;
-        stats.totalTime += segment.totalTime;
-    }
-}
-
 void SurfaceFlinger::dumpFrameEventsLocked(std::string& result) {
     result.append("Layer frame timestamps:\n");
     // Traverse all layers to dump frame-events for each layer
@@ -4897,38 +5139,6 @@
         [&] (Layer* layer) { layer->dumpFrameEvents(result); });
 }
 
-void SurfaceFlinger::dumpBufferingStats(std::string& result) const {
-    result.append("Buffering stats:\n");
-    result.append("  [Layer name] <Active time> <Two buffer> "
-            "<Double buffered> <Triple buffered>\n");
-    Mutex::Autolock lock(getBE().mBufferingStatsMutex);
-    typedef std::tuple<std::string, float, float, float> BufferTuple;
-    std::map<float, BufferTuple, std::greater<float>> sorted;
-    for (const auto& statsPair : getBE().mBufferingStats) {
-        const char* name = statsPair.first.c_str();
-        const SurfaceFlingerBE::BufferingStats& stats = statsPair.second;
-        if (stats.numSegments == 0) {
-            continue;
-        }
-        float activeTime = ns2ms(stats.totalTime) / 1000.0f;
-        float twoBufferRatio = static_cast<float>(stats.twoBufferTime) /
-                stats.totalTime;
-        float doubleBufferRatio = static_cast<float>(
-                stats.doubleBufferedTime) / stats.totalTime;
-        float tripleBufferRatio = static_cast<float>(
-                stats.tripleBufferedTime) / stats.totalTime;
-        sorted.insert({activeTime, {name, twoBufferRatio,
-                doubleBufferRatio, tripleBufferRatio}});
-    }
-    for (const auto& sortedPair : sorted) {
-        float activeTime = sortedPair.first;
-        const BufferTuple& values = sortedPair.second;
-        StringAppendF(&result, "  [%s] %.2f %.3f %.3f %.3f\n", std::get<0>(values).c_str(),
-                      activeTime, std::get<1>(values), std::get<2>(values), std::get<3>(values));
-    }
-    result.append("\n");
-}
-
 void SurfaceFlinger::dumpDisplayIdentificationData(std::string& result) const {
     for (const auto& [token, display] : mDisplays) {
         const auto displayId = PhysicalDisplayId::tryCast(display->getId());
@@ -5007,12 +5217,9 @@
 }
 
 LayersProto SurfaceFlinger::dumpDrawingStateProto(uint32_t traceFlags) const {
-    // If context is SurfaceTracing thread, mTracingLock blocks display transactions on main thread.
-    const auto display = ON_MAIN_THREAD(getDefaultDisplayDeviceLocked());
-
     LayersProto layersProto;
     for (const sp<Layer>& layer : mDrawingState.layersSortedByZ) {
-        layer->writeToProto(layersProto, traceFlags, display.get());
+        layer->writeToProto(layersProto, traceFlags);
     }
 
     return layersProto;
@@ -5023,7 +5230,7 @@
         DisplayProto* displayProto = layersTraceProto.add_displays();
         displayProto->set_id(display->getId().value);
         displayProto->set_name(display->getDisplayName());
-        displayProto->set_layer_stack(display->getLayerStack());
+        displayProto->set_layer_stack(display->getLayerStack().id);
         LayerProtoHelper::writeSizeToProto(display->getWidth(), display->getHeight(),
                                            [&]() { return displayProto->mutable_size(); });
         LayerProtoHelper::writeToProto(display->getLayerStackSpaceRect(), [&]() {
@@ -5031,6 +5238,7 @@
         });
         LayerProtoHelper::writeTransformToProto(display->getTransform(),
                                                 displayProto->mutable_transform());
+        displayProto->set_is_virtual(display->isVirtual());
     }
 }
 
@@ -5052,28 +5260,27 @@
         rootProto->add_children(offscreenLayer->sequence);
 
         // Add layer
-        LayerProto* layerProto =
-                offscreenLayer->writeToProto(layersProto, traceFlags, nullptr /*device*/);
+        LayerProto* layerProto = offscreenLayer->writeToProto(layersProto, traceFlags);
         layerProto->set_parent(offscreenRootLayerId);
     }
 }
 
 LayersProto SurfaceFlinger::dumpProtoFromMainThread(uint32_t traceFlags) {
-    return schedule([=] { return dumpDrawingStateProto(traceFlags); }).get();
+    return mScheduler->schedule([=] { return dumpDrawingStateProto(traceFlags); }).get();
 }
 
 void SurfaceFlinger::dumpOffscreenLayers(std::string& result) {
+    auto future = mScheduler->schedule([this] {
+        std::string result;
+        for (Layer* offscreenLayer : mOffscreenLayers) {
+            offscreenLayer->traverse(LayerVector::StateSet::Drawing,
+                                     [&](Layer* layer) { layer->dumpCallingUidPid(result); });
+        }
+        return result;
+    });
+
     result.append("Offscreen Layers:\n");
-    result.append(schedule([this] {
-                      std::string result;
-                      for (Layer* offscreenLayer : mOffscreenLayers) {
-                          offscreenLayer->traverse(LayerVector::StateSet::Drawing,
-                                                   [&](Layer* layer) {
-                                                       layer->dumpCallingUidPid(result);
-                                                   });
-                      }
-                      return result;
-                  }).get());
+    result.append(future.get());
 }
 
 void SurfaceFlinger::dumpAllLocked(const DumpArgs& args, std::string& result) const {
@@ -5120,15 +5327,11 @@
     StringAppendF(&result, "HWC missed frame count: %u\n", mHwcFrameMissedCount.load());
     StringAppendF(&result, "GPU missed frame count: %u\n\n", mGpuFrameMissedCount.load());
 
-    dumpBufferingStats(result);
-
     /*
      * Dump the visible layer list
      */
     colorizer.bold(result);
     StringAppendF(&result, "Visible layers (count = %zu)\n", mNumLayers.load());
-    StringAppendF(&result, "GraphicBufferProducers: %zu, max %zu\n",
-                  mGraphicBufferProducerList.size(), mMaxGraphicBufferProducerListSize);
     colorizer.reset(result);
 
     {
@@ -5191,8 +5394,10 @@
         std::string fps, xDpi, yDpi;
         if (const auto activeMode = display->getActiveMode()) {
             fps = to_string(activeMode->getFps());
-            xDpi = base::StringPrintf("%.2f", activeMode->getDpiX());
-            yDpi = base::StringPrintf("%.2f", activeMode->getDpiY());
+
+            const auto dpi = activeMode->getDpi();
+            xDpi = base::StringPrintf("%.2f", dpi.x);
+            yDpi = base::StringPrintf("%.2f", dpi.y);
         } else {
             fps = "unknown";
             xDpi = "unknown";
@@ -5210,8 +5415,16 @@
     /*
      * Tracing state
      */
-    mTracing.dump(result);
-    result.append("\n");
+    mLayerTracing.dump(result);
+
+    result.append("\nTransaction tracing: ");
+    if (mTransactionTracing) {
+        result.append("enabled\n");
+        mTransactionTracing->dump(result);
+    } else {
+        result.append("disabled\n");
+    }
+    result.push_back('\n');
 
     /*
      * HWC layer minidump
@@ -5244,9 +5457,9 @@
     colorizer.bold(result);
     result.append("h/w composer state:\n");
     colorizer.reset(result);
-    bool hwcDisabled = mDebugDisableHWC || mDebugRegion;
+    const bool hwcDisabled = mDebugDisableHWC || mDebugFlashDelay;
     StringAppendF(&result, "  h/w composer %s\n", hwcDisabled ? "disabled" : "enabled");
-    getHwComposer().dump(result);
+    dumpHwc(result);
 
     /*
      * Dump gralloc state
@@ -5254,6 +5467,11 @@
     const GraphicBufferAllocator& alloc(GraphicBufferAllocator::get());
     alloc.dump(result);
 
+    /*
+     * Dump flag/property manager state
+     */
+    mFlagManager.dump(result);
+
     result.append(mTimeStats->miniDump());
     result.append("\n");
 }
@@ -5303,6 +5521,9 @@
         case SET_DESIRED_DISPLAY_MODE_SPECS:
         case GET_DESIRED_DISPLAY_MODE_SPECS:
         case SET_ACTIVE_COLOR_MODE:
+        case GET_BOOT_DISPLAY_MODE_SUPPORT:
+        case SET_BOOT_DISPLAY_MODE:
+        case CLEAR_BOOT_DISPLAY_MODE:
         case GET_AUTO_LOW_LATENCY_MODE_SUPPORT:
         case SET_AUTO_LOW_LATENCY_MODE:
         case GET_GAME_CONTENT_TYPE_SUPPORT:
@@ -5317,11 +5538,9 @@
         case SET_GLOBAL_SHADOW_SETTINGS:
         case GET_PRIMARY_PHYSICAL_DISPLAY_ID:
         case ACQUIRE_FRAME_RATE_FLEXIBILITY_TOKEN: {
-            // ACQUIRE_FRAME_RATE_FLEXIBILITY_TOKEN and OVERRIDE_HDR_TYPES are used by CTS tests,
-            // which acquire the necessary permission dynamically. Don't use the permission cache
-            // for this check.
-            bool usePermissionCache =
-                    code != ACQUIRE_FRAME_RATE_FLEXIBILITY_TOKEN && code != OVERRIDE_HDR_TYPES;
+            // OVERRIDE_HDR_TYPES is used by CTS tests, which acquire the necessary
+            // permission dynamically. Don't use the permission cache for this check.
+            bool usePermissionCache = code != OVERRIDE_HDR_TYPES;
             if (!callingThreadHasUnscopedSurfaceFlingerAccess(usePermissionCache)) {
                 IPCThreadState* ipc = IPCThreadState::self();
                 ALOGE("Permission Denial: can't access SurfaceFlinger pid=%d, uid=%d",
@@ -5370,9 +5589,7 @@
         // special permissions.
         case SET_FRAME_RATE:
         case GET_DISPLAY_BRIGHTNESS_SUPPORT:
-        // captureLayers and captureDisplay will handle the permission check in the function
-        case CAPTURE_LAYERS:
-        case CAPTURE_DISPLAY:
+        case GET_DISPLAY_DECORATION_SUPPORT:
         case SET_FRAME_TIMELINE_INFO:
         case GET_GPU_CONTEXT_PRIORITY:
         case GET_MAX_ACQUIRED_BUFFER_COUNT: {
@@ -5407,8 +5624,7 @@
             }
             return OK;
         }
-        case ADD_TRANSACTION_TRACE_LISTENER:
-        case CAPTURE_DISPLAY_BY_ID: {
+        case ADD_TRANSACTION_TRACE_LISTENER: {
             IPCThreadState* ipc = IPCThreadState::self();
             const int uid = ipc->getCallingUid();
             if (uid == AID_ROOT || uid == AID_GRAPHICS || uid == AID_SYSTEM || uid == AID_SHELL) {
@@ -5416,6 +5632,13 @@
             }
             return PERMISSION_DENIED;
         }
+        case SET_OVERRIDE_FRAME_RATE: {
+            const int uid = IPCThreadState::self()->getCallingUid();
+            if (uid == AID_ROOT || uid == AID_SYSTEM) {
+                return OK;
+            }
+            return PERMISSION_DENIED;
+        }
         case ON_PULL_ATOM: {
             const int uid = IPCThreadState::self()->getCallingUid();
             if (uid == AID_SYSTEM) {
@@ -5431,6 +5654,11 @@
             }
             return PERMISSION_DENIED;
         }
+        case CAPTURE_LAYERS:
+        case CAPTURE_DISPLAY:
+        case CAPTURE_DISPLAY_BY_ID:
+            LOG_FATAL("Deprecated opcode: %d", code);
+            return PERMISSION_DENIED;
     }
 
     // These codes are used for the IBinder protocol to either interrogate the recipient
@@ -5441,9 +5669,9 @@
         code == IBinder::SYSPROPS_TRANSACTION) {
         return OK;
     }
-    // Numbers from 1000 to 1040 are currently used for backdoors. The code
+    // Numbers from 1000 to 1042 are currently used for backdoors. The code
     // in onTransact verifies that the user is root, and has access to use SF.
-    if (code >= 1000 && code <= 1040) {
+    if (code >= 1000 && code <= 1042) {
         ALOGV("Accessing SurfaceFlinger through backdoor code: %u", code);
         return OK;
     }
@@ -5454,9 +5682,8 @@
 
 status_t SurfaceFlinger::onTransact(uint32_t code, const Parcel& data, Parcel* reply,
                                     uint32_t flags) {
-    status_t credentialCheck = CheckTransactCodeCredentials(code);
-    if (credentialCheck != OK) {
-        return credentialCheck;
+    if (const status_t error = CheckTransactCodeCredentials(code); error != OK) {
+        return error;
     }
 
     status_t err = BnSurfaceComposer::onTransact(code, data, reply, flags);
@@ -5473,47 +5700,41 @@
         }
         int n;
         switch (code) {
-            case 1000: // SHOW_CPU, NOT SUPPORTED ANYMORE
-            case 1001: // SHOW_FPS, NOT SUPPORTED ANYMORE
+            case 1000: // Unused.
+            case 1001:
+                return NAME_NOT_FOUND;
+            case 1002: // Toggle flashing on surface damage.
+                if (const int delay = data.readInt32(); delay > 0) {
+                    mDebugFlashDelay = delay;
+                } else {
+                    mDebugFlashDelay = mDebugFlashDelay ? 0 : 1;
+                }
+                scheduleRepaint();
                 return NO_ERROR;
-            case 1002:  // SHOW_UPDATES
-                n = data.readInt32();
-                mDebugRegion = n ? n : (mDebugRegion ? 0 : 1);
-                invalidateHwcGeometry();
-                repaintEverything();
+            case 1004: // Force composite ahead of next VSYNC.
+            case 1006:
+                scheduleComposite(FrameHint::kActive);
                 return NO_ERROR;
-            case 1004:{ // repaint everything
-                repaintEverything();
+            case 1005: { // Force commit ahead of next VSYNC.
+                Mutex::Autolock lock(mStateLock);
+                setTransactionFlags(eTransactionNeeded | eDisplayTransactionNeeded |
+                                    eTraversalNeeded);
                 return NO_ERROR;
             }
-            case 1005:{ // force transaction
-                Mutex::Autolock _l(mStateLock);
-                setTransactionFlags(
-                        eTransactionNeeded|
-                        eDisplayTransactionNeeded|
-                        eTraversalNeeded);
+            case 1007: // Unused.
+                return NAME_NOT_FOUND;
+            case 1008: // Toggle forced GPU composition.
+                mDebugDisableHWC = data.readInt32() != 0;
+                scheduleRepaint();
                 return NO_ERROR;
-            }
-            case 1006:{ // send empty update
-                signalRefresh();
+            case 1009: // Toggle use of transform hint.
+                mDebugDisableTransformHint = data.readInt32() != 0;
+                scheduleRepaint();
                 return NO_ERROR;
-            }
-            case 1008:  // toggle use of hw composer
-                n = data.readInt32();
-                mDebugDisableHWC = n != 0;
-                invalidateHwcGeometry();
-                repaintEverything();
-                return NO_ERROR;
-            case 1009:  // toggle use of transform hint
-                n = data.readInt32();
-                mDebugDisableTransformHint = n != 0;
-                invalidateHwcGeometry();
-                repaintEverything();
-                return NO_ERROR;
-            case 1010:  // interrogate.
+            case 1010: // Interrogate.
                 reply->writeInt32(0);
                 reply->writeInt32(0);
-                reply->writeInt32(mDebugRegion);
+                reply->writeInt32(mDebugFlashDelay);
                 reply->writeInt32(0);
                 reply->writeInt32(mDebugDisableHWC);
                 return NO_ERROR;
@@ -5610,7 +5831,8 @@
             }
             case 1021: { // Disable HWC virtual displays
                 const bool enable = data.readInt32() != 0;
-                static_cast<void>(schedule([this, enable] { enableHalVirtualDisplays(enable); }));
+                static_cast<void>(
+                        mScheduler->schedule([this, enable] { enableHalVirtualDisplays(enable); }));
                 return NO_ERROR;
             }
             case 1022: { // Set saturation boost
@@ -5627,8 +5849,7 @@
                 if (data.readInt32(&colorMode) == NO_ERROR) {
                     mForceColorMode = static_cast<ColorMode>(colorMode);
                 }
-                invalidateHwcGeometry();
-                repaintEverything();
+                scheduleRepaint();
                 return NO_ERROR;
             }
             // Deprecate, use 1030 to check whether the device is color managed.
@@ -5638,22 +5859,33 @@
             case 1025: { // Set layer tracing
                 n = data.readInt32();
                 bool tracingEnabledChanged;
-                if (n) {
+                if (n == 1) {
+                    int64_t fixedStartingTime = data.readInt64();
                     ALOGD("LayerTracing enabled");
-                    tracingEnabledChanged = mTracing.enable();
+                    tracingEnabledChanged = mLayerTracing.enable();
                     if (tracingEnabledChanged) {
-                        schedule([&]() MAIN_THREAD { mTracing.notify("start"); }).wait();
+                        int64_t startingTime =
+                                (fixedStartingTime) ? fixedStartingTime : systemTime();
+                        mScheduler
+                                ->schedule([&]() MAIN_THREAD {
+                                    mLayerTracing.notify("start", startingTime);
+                                })
+                                .wait();
                     }
+                } else if (n == 2) {
+                    std::string filename = std::string(data.readCString());
+                    ALOGD("LayerTracing disabled. Trace wrote to %s", filename.c_str());
+                    tracingEnabledChanged = mLayerTracing.disable(filename.c_str());
                 } else {
                     ALOGD("LayerTracing disabled");
-                    tracingEnabledChanged = mTracing.disable();
+                    tracingEnabledChanged = mLayerTracing.disable();
                 }
                 mTracingEnabledChanged = tracingEnabledChanged;
                 reply->writeInt32(NO_ERROR);
                 return NO_ERROR;
             }
             case 1026: { // Get layer tracing status
-                reply->writeBool(mTracing.isEnabled());
+                reply->writeBool(mLayerTracing.isEnabled());
                 return NO_ERROR;
             }
             // Is a DisplayColorSetting supported?
@@ -5694,7 +5926,7 @@
                 }
 
                 ALOGD("Updating trace buffer to %d KB", n);
-                mTracing.setBufferSize(n * 1024);
+                mLayerTracing.setBufferSize(n * 1024);
                 reply->writeInt32(NO_ERROR);
                 return NO_ERROR;
             }
@@ -5739,12 +5971,12 @@
             case 1033: {
                 n = data.readUint32();
                 ALOGD("Updating trace flags to 0x%x", n);
-                mTracing.setTraceFlags(n);
+                mLayerTracing.setTraceFlags(n);
                 reply->writeInt32(NO_ERROR);
                 return NO_ERROR;
             }
             case 1034: {
-                schedule([&] {
+                auto future = mScheduler->schedule([&] {
                     switch (n = data.readInt32()) {
                         case 0:
                         case 1:
@@ -5754,64 +5986,78 @@
                             reply->writeBool(ON_MAIN_THREAD(isRefreshRateOverlayEnabled()));
                         }
                     }
-                }).get();
+                });
+
+                future.wait();
                 return NO_ERROR;
             }
             case 1035: {
                 const int modeId = data.readInt32();
-                mDebugDisplayModeSetByBackdoor = false;
 
-                const auto displayId = [&]() -> std::optional<PhysicalDisplayId> {
-                    uint64_t inputDisplayId = 0;
-                    if (data.readUint64(&inputDisplayId) == NO_ERROR) {
-                        const auto token = getPhysicalDisplayToken(
-                                static_cast<PhysicalDisplayId>(inputDisplayId));
-                        if (!token) {
-                            ALOGE("No display with id: %" PRIu64, inputDisplayId);
-                            return std::nullopt;
-                        }
-
-                        return std::make_optional<PhysicalDisplayId>(inputDisplayId);
+                const auto display = [&]() -> sp<IBinder> {
+                    uint64_t value;
+                    if (data.readUint64(&value) != NO_ERROR) {
+                        return getDefaultDisplayDevice()->getDisplayToken().promote();
                     }
 
-                    return getDefaultDisplayDevice()->getPhysicalId();
+                    if (const auto id = DisplayId::fromValue<PhysicalDisplayId>(value)) {
+                        return getPhysicalDisplayToken(*id);
+                    }
+
+                    ALOGE("Invalid physical display ID");
+                    return nullptr;
                 }();
 
-                if (!displayId) {
-                    ALOGE("No display found");
-                    return NO_ERROR;
-                }
-
-                status_t result = setActiveMode(getPhysicalDisplayToken(*displayId), modeId);
-                if (result != NO_ERROR) {
-                    return result;
-                }
-
-                mDebugDisplayModeSetByBackdoor = true;
-
-                return NO_ERROR;
+                mDebugDisplayModeSetByBackdoor = false;
+                const status_t result = setActiveModeFromBackdoor(display, modeId);
+                mDebugDisplayModeSetByBackdoor = result == NO_ERROR;
+                return result;
             }
+            // Turn on/off frame rate flexibility mode. When turned on it overrides the display
+            // manager frame rate policy a new policy which allows switching between all refresh
+            // rates.
             case 1036: {
-                if (data.readInt32() > 0) {
-                    status_t result =
-                            acquireFrameRateFlexibilityToken(&mDebugFrameRateFlexibilityToken);
-                    if (result != NO_ERROR) {
-                        return result;
-                    }
-                } else {
-                    mDebugFrameRateFlexibilityToken = nullptr;
+                if (data.readInt32() > 0) { // turn on
+                    return mScheduler
+                            ->schedule([this] {
+                                const auto display =
+                                        ON_MAIN_THREAD(getDefaultDisplayDeviceLocked());
+
+                                // This is a little racy, but not in a way that hurts anything. As
+                                // we grab the defaultMode from the display manager policy, we could
+                                // be setting a new display manager policy, leaving us using a stale
+                                // 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::Policy overridePolicy;
+                                overridePolicy.defaultMode = display->refreshRateConfigs()
+                                                                     .getDisplayManagerPolicy()
+                                                                     .defaultMode;
+                                overridePolicy.allowGroupSwitching = true;
+                                constexpr bool kOverridePolicy = true;
+                                return setDesiredDisplayModeSpecsInternal(display, overridePolicy,
+                                                                          kOverridePolicy);
+                            })
+                            .get();
+                } else { // turn off
+                    return mScheduler
+                            ->schedule([this] {
+                                const auto display =
+                                        ON_MAIN_THREAD(getDefaultDisplayDeviceLocked());
+                                constexpr bool kOverridePolicy = true;
+                                return setDesiredDisplayModeSpecsInternal(display, {},
+                                                                          kOverridePolicy);
+                            })
+                            .get();
                 }
-                return NO_ERROR;
             }
             // Inject a hotplug connected event for the primary display. This will deallocate and
             // reallocate the display state including framebuffers.
             case 1037: {
-                std::optional<hal::HWDisplayId> hwcId;
-                {
-                    Mutex::Autolock lock(mStateLock);
-                    hwcId = getHwComposer().getInternalHwcDisplayId();
-                }
-                onComposerHalHotplug(*hwcId, hal::Connection::CONNECTED);
+                const hal::HWDisplayId hwcId =
+                        (Mutex::Autolock(mStateLock), getHwComposer().getPrimaryHwcDisplayId());
+
+                onComposerHalHotplug(hwcId, hal::Connection::CONNECTED);
                 return NO_ERROR;
             }
             // Modify the max number of display frames stored within FrameTimeline
@@ -5846,38 +6092,58 @@
             // Second argument is an optional uint64 - if present, then limits enabling/disabling
             // caching to a particular physical display
             case 1040: {
-                status_t error =
-                        schedule([&] {
-                            n = data.readInt32();
-                            std::optional<PhysicalDisplayId> inputId = std::nullopt;
-                            if (uint64_t inputDisplayId;
-                                data.readUint64(&inputDisplayId) == NO_ERROR) {
-                                const auto token = getPhysicalDisplayToken(
-                                        static_cast<PhysicalDisplayId>(inputDisplayId));
-                                if (!token) {
-                                    ALOGE("No display with id: %" PRIu64, inputDisplayId);
-                                    return NAME_NOT_FOUND;
-                                }
-
-                                inputId = std::make_optional<PhysicalDisplayId>(inputDisplayId);
+                auto future = mScheduler->schedule([&] {
+                    n = data.readInt32();
+                    std::optional<PhysicalDisplayId> inputId = std::nullopt;
+                    if (uint64_t inputDisplayId; data.readUint64(&inputDisplayId) == NO_ERROR) {
+                        inputId = DisplayId::fromValue<PhysicalDisplayId>(inputDisplayId);
+                        if (!inputId || getPhysicalDisplayToken(*inputId)) {
+                            ALOGE("No display with id: %" PRIu64, inputDisplayId);
+                            return NAME_NOT_FOUND;
+                        }
+                    }
+                    {
+                        Mutex::Autolock lock(mStateLock);
+                        mLayerCachingEnabled = n != 0;
+                        for (const auto& [_, display] : mDisplays) {
+                            if (!inputId || *inputId == display->getPhysicalId()) {
+                                display->enableLayerCaching(mLayerCachingEnabled);
                             }
-                            {
-                                Mutex::Autolock lock(mStateLock);
-                                mLayerCachingEnabled = n != 0;
-                                for (const auto& [_, display] : mDisplays) {
-                                    if (!inputId || *inputId == display->getPhysicalId()) {
-                                        display->enableLayerCaching(mLayerCachingEnabled);
-                                    }
-                                }
-                            }
-                            return OK;
-                        }).get();
+                        }
+                    }
+                    return OK;
+                });
 
-                if (error != OK) {
+                if (const status_t error = future.get(); error != OK) {
                     return error;
                 }
-                invalidateHwcGeometry();
-                repaintEverything();
+                scheduleRepaint();
+                return NO_ERROR;
+            }
+            case 1041: { // Transaction tracing
+                if (mTransactionTracing) {
+                    if (data.readInt32()) {
+                        // Transaction tracing is always running but allow the user to temporarily
+                        // increase the buffer when actively debugging.
+                        mTransactionTracing->setBufferSize(
+                                TransactionTracing::ACTIVE_TRACING_BUFFER_SIZE);
+                    } else {
+                        mTransactionTracing->writeToFile();
+                        mTransactionTracing->setBufferSize(
+                                TransactionTracing::CONTINUOUS_TRACING_BUFFER_SIZE);
+                    }
+                }
+                reply->writeInt32(NO_ERROR);
+                return NO_ERROR;
+            }
+            case 1042: { // Write layers trace or transaction trace to file
+                if (mTransactionTracing) {
+                    mTransactionTracing->writeToFile();
+                }
+                if (mLayerTracingEnabled) {
+                    mLayerTracing.writeToFile();
+                }
+                reply->writeInt32(NO_ERROR);
                 return NO_ERROR;
             }
         }
@@ -5885,31 +6151,20 @@
     return err;
 }
 
-void SurfaceFlinger::repaintEverything() {
-    mRepaintEverything = true;
-    signalTransaction();
-}
-
-void SurfaceFlinger::repaintEverythingForHWC() {
-    mRepaintEverything = true;
-    mPowerAdvisor.notifyDisplayUpdateImminent();
-    mEventQueue->invalidate();
-}
-
 void SurfaceFlinger::kernelTimerChanged(bool expired) {
     static bool updateOverlay =
             property_get_bool("debug.sf.kernel_idle_timer_update_overlay", true);
     if (!updateOverlay) return;
-    if (Mutex::Autolock lock(mStateLock); !isRefreshRateOverlayEnabled()) return;
 
     // Update the overlay on the main thread to avoid race conditions with
-    // mRefreshRateConfigs->getCurrentRefreshRate()
-    static_cast<void>(schedule([=] {
+    // mRefreshRateConfigs->getActiveMode()
+    static_cast<void>(mScheduler->schedule([=] {
         const auto display = ON_MAIN_THREAD(getDefaultDisplayDeviceLocked());
         if (!display) {
             ALOGW("%s: default display is null", __func__);
             return;
         }
+        if (!display->isRefreshRateOverlayEnabled()) return;
 
         const auto desiredActiveMode = display->getDesiredActiveMode();
         const std::optional<DisplayModeId> desiredModeId = desiredActiveMode
@@ -5919,11 +6174,52 @@
         const bool timerExpired = mKernelIdleTimerEnabled && expired;
 
         if (display->onKernelTimerChanged(desiredModeId, timerExpired)) {
-            mEventQueue->invalidate();
+            mScheduler->scheduleFrame();
         }
     }));
 }
 
+std::pair<std::optional<KernelIdleTimerController>, std::chrono::milliseconds>
+SurfaceFlinger::getKernelIdleTimerProperties(DisplayId displayId) {
+    const bool isKernelIdleTimerHwcSupported = getHwComposer().getComposer()->isSupported(
+            android::Hwc2::Composer::OptionalFeature::KernelIdleTimer);
+    const auto timeout = getIdleTimerTimeout(displayId);
+    if (isKernelIdleTimerHwcSupported) {
+        if (const auto id = PhysicalDisplayId::tryCast(displayId);
+            getHwComposer().hasDisplayIdleTimerCapability(*id)) {
+            // In order to decide if we can use the HWC api for idle timer
+            // we query DisplayCapability::DISPLAY_IDLE_TIMER directly on the composer
+            // without relying on hasDisplayCapability.
+            // hasDisplayCapability relies on DisplayCapabilities
+            // which are updated after we set the PowerMode::ON.
+            // DISPLAY_IDLE_TIMER is a display driver property
+            // and is available before the PowerMode::ON
+            return {KernelIdleTimerController::HwcApi, timeout};
+        }
+        return {std::nullopt, timeout};
+    }
+    if (getKernelIdleTimerSyspropConfig(displayId)) {
+        return {KernelIdleTimerController::Sysprop, timeout};
+    }
+
+    return {std::nullopt, timeout};
+}
+
+void SurfaceFlinger::updateKernelIdleTimer(std::chrono::milliseconds timeout,
+                                           KernelIdleTimerController controller,
+                                           PhysicalDisplayId displayId) {
+    switch (controller) {
+        case KernelIdleTimerController::HwcApi: {
+            getHwComposer().setIdleTimerEnabled(displayId, timeout);
+            break;
+        }
+        case KernelIdleTimerController::Sysprop: {
+            base::SetProperty(KERNEL_IDLE_TIMER_PROP, timeout > 0ms ? "true" : "false");
+            break;
+        }
+    }
+}
+
 void SurfaceFlinger::toggleKernelIdleTimer() {
     using KernelIdleTimerAction = scheduler::RefreshRateConfigs::KernelIdleTimerAction;
 
@@ -5935,23 +6231,31 @@
 
     // If the support for kernel idle timer is disabled for the active display,
     // don't do anything.
-    if (!display->refreshRateConfigs().supportsKernelIdleTimer()) {
+    const std::optional<KernelIdleTimerController> kernelIdleTimerController =
+            display->refreshRateConfigs().kernelIdleTimerController();
+    if (!kernelIdleTimerController.has_value()) {
         return;
     }
 
     const KernelIdleTimerAction action = display->refreshRateConfigs().getIdleTimerAction();
+
     switch (action) {
         case KernelIdleTimerAction::TurnOff:
             if (mKernelIdleTimerEnabled) {
                 ATRACE_INT("KernelIdleTimer", 0);
-                base::SetProperty(KERNEL_IDLE_TIMER_PROP, "false");
+                std::chrono::milliseconds constexpr kTimerDisabledTimeout = 0ms;
+                updateKernelIdleTimer(kTimerDisabledTimeout, kernelIdleTimerController.value(),
+                                      display->getPhysicalId());
                 mKernelIdleTimerEnabled = false;
             }
             break;
         case KernelIdleTimerAction::TurnOn:
             if (!mKernelIdleTimerEnabled) {
                 ATRACE_INT("KernelIdleTimer", 1);
-                base::SetProperty(KERNEL_IDLE_TIMER_PROP, "true");
+                const std::chrono::milliseconds timeout =
+                        display->refreshRateConfigs().getIdleTimerTimeout();
+                updateKernelIdleTimer(timeout, kernelIdleTimerController.value(),
+                                      display->getPhysicalId());
                 mKernelIdleTimerEnabled = true;
             }
             break;
@@ -6114,12 +6418,13 @@
         traverseLayersInLayerStack(layerStack, args.uid, visitor);
     };
 
-    return captureScreenCommon(std::move(renderAreaFuture), traverseLayers, reqSize,
-                               args.pixelFormat, args.allowProtected, args.grayscale,
-                               captureListener);
+    auto captureResultFuture = captureScreenCommon(std::move(renderAreaFuture), traverseLayers,
+                                                   reqSize, args.pixelFormat, args.allowProtected,
+                                                   args.grayscale, captureListener);
+    return captureResultFuture.get().status;
 }
 
-status_t SurfaceFlinger::captureDisplay(uint64_t displayIdOrLayerStack,
+status_t SurfaceFlinger::captureDisplay(DisplayId displayId,
                                         const sp<IScreenCaptureListener>& captureListener) {
     ui::LayerStack layerStack;
     wp<const DisplayDevice> displayWeak;
@@ -6127,21 +6432,14 @@
     ui::Dataspace dataspace;
     {
         Mutex::Autolock lock(mStateLock);
-        auto display = getDisplayDeviceLocked(PhysicalDisplayId{displayIdOrLayerStack});
 
-        // Fall back to first display whose layer stack matches.
-        if (!display) {
-            const auto layerStack = static_cast<ui::LayerStack>(displayIdOrLayerStack);
-            display = findDisplay(WithLayerStack(layerStack));
-        }
-
+        const auto display = getDisplayDeviceLocked(displayId);
         if (!display) {
             return NAME_NOT_FOUND;
         }
 
-        layerStack = display->getLayerStack();
         displayWeak = display;
-
+        layerStack = display->getLayerStack();
         size = display->getLayerStackSpaceRect().getSize();
 
         dataspace =
@@ -6158,9 +6456,15 @@
         traverseLayersInLayerStack(layerStack, CaptureArgs::UNSET_UID, visitor);
     };
 
-    return captureScreenCommon(std::move(renderAreaFuture), traverseLayers, size,
-                               ui::PixelFormat::RGBA_8888, false /* allowProtected */,
-                               false /* grayscale */, captureListener);
+    if (captureListener == nullptr) {
+        ALOGE("capture screen must provide a capture listener callback");
+        return BAD_VALUE;
+    }
+    auto captureResultFuture =
+            captureScreenCommon(std::move(renderAreaFuture), traverseLayers, size,
+                                ui::PixelFormat::RGBA_8888, false /* allowProtected */,
+                                false /* grayscale */, captureListener);
+    return captureResultFuture.get().status;
 }
 
 status_t SurfaceFlinger::captureLayers(const LayerCaptureArgs& args,
@@ -6175,7 +6479,7 @@
     ui::Size reqSize;
     sp<Layer> parent;
     Rect crop(args.sourceCrop);
-    std::unordered_set<sp<Layer>, ISurfaceComposer::SpHash<Layer>> excludeLayers;
+    std::unordered_set<sp<Layer>, SpHash<Layer>> excludeLayers;
     ui::Dataspace dataspace;
 
     // Call this before holding mStateLock to avoid any deadlocking.
@@ -6229,17 +6533,6 @@
         // and failed if display is not in native mode. This provide a way to force using native
         // colors when capture.
         dataspace = args.dataspace;
-        if (dataspace == ui::Dataspace::UNKNOWN) {
-            auto display = findDisplay(WithLayerStack(parent->getLayerStack()));
-            if (!display) {
-                // If the layer is not on a display, use the dataspace for the default display.
-                display = getDefaultDisplayDeviceLocked();
-            }
-
-            const ui::ColorMode colorMode = display->getCompositionDisplay()->getState().colorMode;
-            dataspace = pickDataspaceFromColorMode(colorMode);
-        }
-
     } // mStateLock
 
     // really small crop or frameScale
@@ -6278,23 +6571,28 @@
         });
     };
 
-    return captureScreenCommon(std::move(renderAreaFuture), traverseLayers, reqSize,
-                               args.pixelFormat, args.allowProtected, args.grayscale,
-                               captureListener);
+    if (captureListener == nullptr) {
+        ALOGE("capture screen must provide a capture listener callback");
+        return BAD_VALUE;
+    }
+
+    auto captureResultFuture = captureScreenCommon(std::move(renderAreaFuture), traverseLayers,
+                                                   reqSize, args.pixelFormat, args.allowProtected,
+                                                   args.grayscale, captureListener);
+    return captureResultFuture.get().status;
 }
 
-status_t SurfaceFlinger::captureScreenCommon(RenderAreaFuture renderAreaFuture,
-                                             TraverseLayersFunction traverseLayers,
-                                             ui::Size bufferSize, ui::PixelFormat reqPixelFormat,
-                                             bool allowProtected, bool grayscale,
-                                             const sp<IScreenCaptureListener>& captureListener) {
+std::shared_future<renderengine::RenderEngineResult> SurfaceFlinger::captureScreenCommon(
+        RenderAreaFuture renderAreaFuture, TraverseLayersFunction traverseLayers,
+        ui::Size bufferSize, ui::PixelFormat reqPixelFormat, bool allowProtected, bool grayscale,
+        const sp<IScreenCaptureListener>& captureListener) {
     ATRACE_CALL();
 
     if (exceedsMaxRenderTargetSize(bufferSize.getWidth(), bufferSize.getHeight())) {
         ALOGE("Attempted to capture screen with size (%" PRId32 ", %" PRId32
               ") that exceeds render target size limit.",
               bufferSize.getWidth(), bufferSize.getHeight());
-        return BAD_VALUE;
+        return ftl::yield<renderengine::RenderEngineResult>({BAD_VALUE, base::unique_fd()}).share();
     }
 
     // Loop over all visible layers to see whether there's any protected layer. A protected layer is
@@ -6304,14 +6602,15 @@
     const bool supportsProtected = getRenderEngine().supportsProtectedContent();
     bool hasProtectedLayer = false;
     if (allowProtected && supportsProtected) {
-        hasProtectedLayer = schedule([=]() {
-                                bool protectedLayerFound = false;
-                                traverseLayers([&](Layer* layer) {
-                                    protectedLayerFound = protectedLayerFound ||
-                                            (layer->isVisible() && layer->isProtected());
-                                });
-                                return protectedLayerFound;
-                            }).get();
+        auto future = mScheduler->schedule([=]() {
+            bool protectedLayerFound = false;
+            traverseLayers([&](Layer* layer) {
+                protectedLayerFound =
+                        protectedLayerFound || (layer->isVisible() && layer->isProtected());
+            });
+            return protectedLayerFound;
+        });
+        hasProtectedLayer = future.get();
     }
 
     const uint32_t usage = GRALLOC_USAGE_HW_COMPOSER | GRALLOC_USAGE_HW_RENDER |
@@ -6327,57 +6626,75 @@
     const status_t bufferStatus = buffer->initCheck();
     LOG_ALWAYS_FATAL_IF(bufferStatus != OK, "captureScreenCommon: Buffer failed to allocate: %d",
                         bufferStatus);
-    const auto texture = std::make_shared<
-            renderengine::ExternalTexture>(buffer, getRenderEngine(),
-                                           renderengine::ExternalTexture::Usage::WRITEABLE);
+    const std::shared_ptr<renderengine::ExternalTexture> texture = std::make_shared<
+            renderengine::impl::ExternalTexture>(buffer, getRenderEngine(),
+                                                 renderengine::impl::ExternalTexture::Usage::
+                                                         WRITEABLE);
     return captureScreenCommon(std::move(renderAreaFuture), traverseLayers, texture,
                                false /* regionSampling */, grayscale, captureListener);
 }
 
-status_t SurfaceFlinger::captureScreenCommon(
+std::shared_future<renderengine::RenderEngineResult> SurfaceFlinger::captureScreenCommon(
         RenderAreaFuture renderAreaFuture, TraverseLayersFunction traverseLayers,
         const std::shared_ptr<renderengine::ExternalTexture>& buffer, bool regionSampling,
         bool grayscale, const sp<IScreenCaptureListener>& captureListener) {
     ATRACE_CALL();
 
-    if (captureListener == nullptr) {
-        ALOGE("capture screen must provide a capture listener callback");
-        return BAD_VALUE;
-    }
-
     bool canCaptureBlackoutContent = hasCaptureBlackoutContentPermission();
 
-    static_cast<void>(schedule([=, renderAreaFuture = std::move(renderAreaFuture)]() mutable {
-        if (mRefreshPending) {
-            ALOGW("Skipping screenshot for now");
-            captureScreenCommon(std::move(renderAreaFuture), traverseLayers, buffer, regionSampling,
-                                grayscale, captureListener);
-            return;
-        }
+    auto scheduleResultFuture = mScheduler->schedule([=,
+                                                      renderAreaFuture =
+                                                              std::move(renderAreaFuture)]() mutable
+                                                     -> std::shared_future<
+                                                             renderengine::RenderEngineResult> {
         ScreenCaptureResults captureResults;
         std::unique_ptr<RenderArea> renderArea = renderAreaFuture.get();
         if (!renderArea) {
             ALOGW("Skipping screen capture because of invalid render area.");
             captureResults.result = NO_MEMORY;
             captureListener->onScreenCaptureCompleted(captureResults);
-            return;
+            return ftl::yield<renderengine::RenderEngineResult>({NO_ERROR, base::unique_fd()})
+                    .share();
         }
 
-        status_t result = NO_ERROR;
+        std::shared_future<renderengine::RenderEngineResult> renderEngineResultFuture;
+
         renderArea->render([&] {
-            result = renderScreenImplLocked(*renderArea, traverseLayers, buffer,
-                                            canCaptureBlackoutContent, regionSampling, grayscale,
-                                            captureResults);
+            renderEngineResultFuture =
+                    renderScreenImpl(*renderArea, traverseLayers, buffer,
+                                           canCaptureBlackoutContent, regionSampling, grayscale,
+                                           captureResults);
         });
+        // spring up a thread to unblock SF main thread and wait for
+        // RenderEngineResult to be available
+        if (captureListener != nullptr) {
+            std::async([=]() mutable {
+                ATRACE_NAME("captureListener is nonnull!");
+                auto& [status, drawFence] = renderEngineResultFuture.get();
+                captureResults.result = status;
+                captureResults.fence = new Fence(dup(drawFence));
+                captureListener->onScreenCaptureCompleted(captureResults);
+            });
+        }
+        return renderEngineResultFuture;
+    });
 
-        captureResults.result = result;
-        captureListener->onScreenCaptureCompleted(captureResults);
-    }));
-
-    return NO_ERROR;
+    // flatten scheduleResultFuture object to single shared_future object
+    if (captureListener == nullptr) {
+        std::future<renderengine::RenderEngineResult> captureScreenResultFuture =
+                ftl::chain(std::move(scheduleResultFuture))
+                        .then([=](std::shared_future<renderengine::RenderEngineResult> futureObject)
+                                      -> renderengine::RenderEngineResult {
+                            auto& [status, drawFence] = futureObject.get();
+                            return {status, base::unique_fd(dup(drawFence))};
+                        });
+        return captureScreenResultFuture.share();
+    } else {
+        return ftl::yield<renderengine::RenderEngineResult>({NO_ERROR, base::unique_fd()}).share();
+    }
 }
 
-status_t SurfaceFlinger::renderScreenImplLocked(
+std::shared_future<renderengine::RenderEngineResult> SurfaceFlinger::renderScreenImpl(
         const RenderArea& renderArea, TraverseLayersFunction traverseLayers,
         const std::shared_ptr<renderengine::ExternalTexture>& buffer,
         bool canCaptureBlackoutContent, bool regionSampling, bool grayscale,
@@ -6389,18 +6706,34 @@
                 captureResults.capturedSecureLayers || (layer->isVisible() && layer->isSecure());
     });
 
-    const bool useProtected = buffer->getBuffer()->getUsage() & GRALLOC_USAGE_PROTECTED;
+    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.
     if (captureResults.capturedSecureLayers && !canCaptureBlackoutContent) {
         ALOGW("FB is protected: PERMISSION_DENIED");
-        return PERMISSION_DENIED;
+        return ftl::yield<renderengine::RenderEngineResult>({PERMISSION_DENIED, base::unique_fd()})
+                .share();
     }
 
     captureResults.buffer = buffer->getBuffer();
-    captureResults.capturedDataspace = renderArea.getReqDataSpace();
+    auto dataspace = renderArea.getReqDataSpace();
+    auto parent = renderArea.getParentLayer();
+    if ((dataspace == ui::Dataspace::UNKNOWN) && (parent != nullptr)) {
+        Mutex::Autolock lock(mStateLock);
+        auto display = findDisplay([layerStack = parent->getLayerStack()](const auto& display) {
+            return display.getLayerStack() == layerStack;
+        });
+        if (!display) {
+            // If the layer is not on a display, use the dataspace for the default display.
+            display = getDefaultDisplayDeviceLocked();
+        }
+
+        const ui::ColorMode colorMode = display->getCompositionDisplay()->getState().colorMode;
+        dataspace = pickDataspaceFromColorMode(colorMode);
+    }
+    captureResults.capturedDataspace = dataspace;
 
     const auto reqWidth = renderArea.getReqWidth();
     const auto reqHeight = renderArea.getReqHeight();
@@ -6418,7 +6751,7 @@
     clientCompositionDisplay.clip = sourceCrop;
     clientCompositionDisplay.orientation = rotation;
 
-    clientCompositionDisplay.outputDataspace = renderArea.getReqDataSpace();
+    clientCompositionDisplay.outputDataspace = dataspace;
     clientCompositionDisplay.maxLuminance = DisplayDevice::sDefaultMaxLumiance;
 
     const float colorSaturation = grayscale ? 0 : 1;
@@ -6436,7 +6769,6 @@
 
     const auto display = renderArea.getDisplayDevice();
     std::vector<Layer*> renderedLayers;
-    Region clearRegion = Region::INVALID_REGION;
     bool disableBlurs = false;
     traverseLayers([&](Layer* layer) {
         disableBlurs |= layer->getDrawingState().sidebandStream != nullptr;
@@ -6448,7 +6780,6 @@
                         renderArea.needsFiltering(),
                 renderArea.isSecure(),
                 useProtected,
-                clearRegion,
                 layerStackSpaceRect,
                 clientCompositionDisplay.outputDataspace,
                 true,  /* realContentIsVisible */
@@ -6457,6 +6788,8 @@
                                        BlurSetting::Disabled
                              : compositionengine::LayerFE::ClientCompositionTargetSettings::
                                        BlurSetting::Enabled,
+                DisplayDevice::sDefaultMaxLumiance,
+
         };
         std::vector<compositionengine::LayerFE::LayerSettings> results =
                 layer->prepareClientCompositionList(targetSettings);
@@ -6480,35 +6813,35 @@
 
     });
 
-    std::vector<const renderengine::LayerSettings*> clientCompositionLayerPointers(
-            clientCompositionLayers.size());
+    std::vector<renderengine::LayerSettings> clientRenderEngineLayers;
+    clientRenderEngineLayers.reserve(clientCompositionLayers.size());
     std::transform(clientCompositionLayers.begin(), clientCompositionLayers.end(),
-                   clientCompositionLayerPointers.begin(),
-                   std::pointer_traits<renderengine::LayerSettings*>::pointer_to);
+                   std::back_inserter(clientRenderEngineLayers),
+                   [](compositionengine::LayerFE::LayerSettings& settings)
+                           -> renderengine::LayerSettings { return settings; });
 
-    clientCompositionDisplay.clearRegion = clearRegion;
     // 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;
-    base::unique_fd drawFence;
     getRenderEngine().useProtectedContext(useProtected);
 
     const constexpr bool kUseFramebufferCache = false;
-    getRenderEngine().drawLayers(clientCompositionDisplay, clientCompositionLayerPointers, buffer,
-                                 kUseFramebufferCache, std::move(bufferFence), &drawFence);
+    std::future<renderengine::RenderEngineResult> drawLayersResult =
+            getRenderEngine().drawLayers(clientCompositionDisplay, clientRenderEngineLayers, buffer,
+                                         kUseFramebufferCache, std::move(bufferFence));
 
-    if (drawFence >= 0) {
-        sp<Fence> releaseFence = new Fence(dup(drawFence));
-        for (auto* layer : renderedLayers) {
-            layer->onLayerDisplayed(releaseFence);
-        }
+    std::shared_future<renderengine::RenderEngineResult> drawLayersResultFuture =
+            drawLayersResult.share(); // drawLayersResult will be moved to shared one
+
+    for (auto* layer : renderedLayers) {
+        // make a copy of shared_future object for each layer
+        layer->onLayerDisplayed(drawLayersResultFuture);
     }
 
-    captureResults.fence = new Fence(drawFence.release());
     // Always switch back to unprotected context.
     getRenderEngine().useProtectedContext(false);
 
-    return NO_ERROR;
+    return drawLayersResultFuture;
 }
 
 void SurfaceFlinger::windowInfosReported() {
@@ -6535,12 +6868,12 @@
     // We loop through the first level of layers without traversing,
     // as we need to determine which layers belong to the requested display.
     for (const auto& layer : mDrawingState.layersSortedByZ) {
-        if (!layer->belongsToDisplay(layerStack)) {
+        if (layer->getLayerStack() != layerStack) {
             continue;
         }
         // relative layers are traversed in Layer::traverseInZOrder
         layer->traverseInZOrder(LayerVector::StateSet::Drawing, [&](Layer* layer) {
-            if (layer->getPrimaryDisplayOnly()) {
+            if (layer->isInternalDisplayOverlay()) {
                 return;
             }
             if (!layer->isVisible()) {
@@ -6604,7 +6937,7 @@
     if (display->refreshRateConfigs().isModeAllowed(preferredDisplayMode->getId())) {
         ALOGV("switching to Scheduler preferred display mode %d",
               preferredDisplayMode->getId().value());
-        setDesiredActiveMode({preferredDisplayMode, Scheduler::ModeEvent::Changed});
+        setDesiredActiveMode({preferredDisplayMode, DisplayModeEvent::Changed});
     } else {
         LOG_ALWAYS_FATAL("Desired display mode not allowed: %d",
                          preferredDisplayMode->getId().value());
@@ -6623,7 +6956,7 @@
         return BAD_VALUE;
     }
 
-    auto future = schedule([=]() -> status_t {
+    auto future = mScheduler->schedule([=]() -> status_t {
         const auto display = ON_MAIN_THREAD(getDisplayDeviceLocked(displayToken));
         if (!display) {
             ALOGE("Attempt to set desired display modes for invalid display token %p",
@@ -6636,8 +6969,10 @@
             using Policy = scheduler::RefreshRateConfigs::Policy;
             const Policy policy{DisplayModeId(defaultMode),
                                 allowGroupSwitching,
-                                {Fps(primaryRefreshRateMin), Fps(primaryRefreshRateMax)},
-                                {Fps(appRequestRefreshRateMin), Fps(appRequestRefreshRateMax)}};
+                                {Fps::fromValue(primaryRefreshRateMin),
+                                 Fps::fromValue(primaryRefreshRateMax)},
+                                {Fps::fromValue(appRequestRefreshRateMin),
+                                 Fps::fromValue(appRequestRefreshRateMax)}};
             constexpr bool kOverridePolicy = false;
 
             return setDesiredDisplayModeSpecsInternal(display, policy, kOverridePolicy);
@@ -6699,6 +7034,13 @@
     if (!layer->isRemovedFromCurrentState()) {
         mScheduler->deregisterLayer(layer);
     }
+    if (mTransactionTracing) {
+        mTransactionTracing->onLayerRemoved(layer->getSequence());
+    }
+}
+
+void SurfaceFlinger::onLayerUpdate() {
+    scheduleCommit(FrameHint::kActive);
 }
 
 // WARNING: ONLY CALL THIS FROM LAYER DTOR
@@ -6754,7 +7096,7 @@
         return BAD_VALUE;
     }
 
-    static_cast<void>(schedule([=] {
+    static_cast<void>(mScheduler->schedule([=] {
         Mutex::Autolock lock(mStateLock);
         if (authenticateSurfaceTextureLocked(surface)) {
             sp<Layer> layer = (static_cast<MonitoredProducer*>(surface.get()))->getLayer();
@@ -6765,7 +7107,7 @@
             const auto strategy =
                     Layer::FrameRate::convertChangeFrameRateStrategy(changeFrameRateStrategy);
             if (layer->setFrameRate(
-                        Layer::FrameRate(Fps{frameRate},
+                        Layer::FrameRate(Fps::fromValue(frameRate),
                                          Layer::FrameRate::convertCompatibility(compatibility),
                                          strategy))) {
                 setTransactionFlags(eTraversalNeeded);
@@ -6780,72 +7122,15 @@
     return NO_ERROR;
 }
 
-status_t SurfaceFlinger::acquireFrameRateFlexibilityToken(sp<IBinder>* outToken) {
-    if (!outToken) {
-        return BAD_VALUE;
-    }
+status_t SurfaceFlinger::setOverrideFrameRate(uid_t uid, float frameRate) {
+    PhysicalDisplayId displayId = [&]() {
+        Mutex::Autolock lock(mStateLock);
+        return getDefaultDisplayDeviceLocked()->getPhysicalId();
+    }();
 
-    auto future = schedule([this] {
-        status_t result = NO_ERROR;
-        sp<IBinder> token;
-
-        if (mFrameRateFlexibilityTokenCount == 0) {
-            const auto display = ON_MAIN_THREAD(getDefaultDisplayDeviceLocked());
-
-            // This is a little racy, but not in a way that hurts anything. As we grab the
-            // defaultMode from the display manager policy, we could be setting a new display
-            // manager policy, leaving us using a stale 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::Policy overridePolicy;
-            overridePolicy.defaultMode =
-                    display->refreshRateConfigs().getDisplayManagerPolicy().defaultMode;
-            overridePolicy.allowGroupSwitching = true;
-            constexpr bool kOverridePolicy = true;
-            result = setDesiredDisplayModeSpecsInternal(display, overridePolicy, kOverridePolicy);
-        }
-
-        if (result == NO_ERROR) {
-            mFrameRateFlexibilityTokenCount++;
-            // Handing out a reference to the SurfaceFlinger object, as we're doing in the line
-            // below, is something to consider carefully. The lifetime of the
-            // FrameRateFlexibilityToken isn't tied to SurfaceFlinger object lifetime, so if this
-            // SurfaceFlinger object were to be destroyed while the token still exists, the token
-            // destructor would be accessing a stale SurfaceFlinger reference, and crash. This is ok
-            // in this case, for two reasons:
-            //   1. Once SurfaceFlinger::run() is called by main_surfaceflinger.cpp, the only way
-            //   the program exits is via a crash. So we won't have a situation where the
-            //   SurfaceFlinger object is dead but the process is still up.
-            //   2. The frame rate flexibility token is acquired/released only by CTS tests, so even
-            //   if condition 1 were changed, the problem would only show up when running CTS tests,
-            //   not on end user devices, so we could spot it and fix it without serious impact.
-            token = new FrameRateFlexibilityToken(
-                    [this]() { onFrameRateFlexibilityTokenReleased(); });
-            ALOGD("Frame rate flexibility token acquired. count=%d",
-                  mFrameRateFlexibilityTokenCount);
-        }
-
-        return std::make_pair(result, token);
-    });
-
-    status_t result;
-    std::tie(result, *outToken) = future.get();
-    return result;
-}
-
-void SurfaceFlinger::onFrameRateFlexibilityTokenReleased() {
-    static_cast<void>(schedule([this] {
-        LOG_ALWAYS_FATAL_IF(mFrameRateFlexibilityTokenCount == 0,
-                            "Failed tracking frame rate flexibility tokens");
-        mFrameRateFlexibilityTokenCount--;
-        ALOGD("Frame rate flexibility token released. count=%d", mFrameRateFlexibilityTokenCount);
-        if (mFrameRateFlexibilityTokenCount == 0) {
-            const auto display = ON_MAIN_THREAD(getDefaultDisplayDeviceLocked());
-            constexpr bool kOverridePolicy = true;
-            status_t result = setDesiredDisplayModeSpecsInternal(display, {}, kOverridePolicy);
-            LOG_ALWAYS_FATAL_IF(result < 0, "Failed releasing frame rate flexibility token");
-        }
-    }));
+    mScheduler->setGameModeRefreshRateForUid(FrameRateOverride{static_cast<uid_t>(uid), frameRate});
+    mScheduler->onFrameRateOverridesChanged(mAppConnectionHandle, displayId);
+    return NO_ERROR;
 }
 
 status_t SurfaceFlinger::setFrameTimelineInfo(const sp<IGraphicBufferProducer>& surface,
@@ -6899,33 +7184,29 @@
 }
 
 status_t SurfaceFlinger::getMaxAcquiredBufferCount(int* buffers) const {
-    const auto maxSupportedRefreshRate = [&] {
-        const auto display = getDefaultDisplayDevice();
-        if (display) {
-            return display->refreshRateConfigs().getSupportedRefreshRateRange().max;
+    Fps maxRefreshRate = 60_Hz;
+
+    if (!getHwComposer().isHeadless()) {
+        if (const auto display = getDefaultDisplayDevice()) {
+            maxRefreshRate = display->refreshRateConfigs().getSupportedRefreshRateRange().max;
         }
-        ALOGW("%s: default display is null", __func__);
-        return Fps(60);
-    }();
-    *buffers = getMaxAcquiredBufferCountForRefreshRate(maxSupportedRefreshRate);
+    }
+
+    *buffers = getMaxAcquiredBufferCountForRefreshRate(maxRefreshRate);
     return NO_ERROR;
 }
 
-int SurfaceFlinger::getMaxAcquiredBufferCountForCurrentRefreshRate(uid_t uid) const {
-    const auto refreshRate = [&] {
-        const auto frameRateOverride = mScheduler->getFrameRateOverride(uid);
-        if (frameRateOverride.has_value()) {
-            return frameRateOverride.value();
-        }
+uint32_t SurfaceFlinger::getMaxAcquiredBufferCountForCurrentRefreshRate(uid_t uid) const {
+    Fps refreshRate = 60_Hz;
 
-        const auto display = ON_MAIN_THREAD(getDefaultDisplayDeviceLocked());
-        if (display) {
-            return display->refreshRateConfigs().getCurrentRefreshRate().getFps();
+    if (const auto frameRateOverride = mScheduler->getFrameRateOverride(uid)) {
+        refreshRate = *frameRateOverride;
+    } else if (!getHwComposer().isHeadless()) {
+        if (const auto display = ON_MAIN_THREAD(getDefaultDisplayDeviceLocked())) {
+            refreshRate = display->refreshRateConfigs().getActiveMode()->getFps();
         }
+    }
 
-        ALOGW("%s: default display is null", __func__);
-        return Fps(60);
-    }();
     return getMaxAcquiredBufferCountForRefreshRate(refreshRate);
 }
 
@@ -6935,7 +7216,7 @@
     return calculateMaxAcquiredBufferCount(refreshRate, presentLatency);
 }
 
-void SurfaceFlinger::TransactionState::traverseStatesWithBuffers(
+void TransactionState::traverseStatesWithBuffers(
         std::function<void(const layer_state_t&)> visitor) {
     for (const auto& state : states) {
         if (state.state.hasBufferChanges() && state.state.hasValidBuffer() && state.state.surface) {
@@ -6944,62 +7225,24 @@
     }
 }
 
-void SurfaceFlinger::setLayerCreatedState(const sp<IBinder>& handle, const wp<Layer>& layer,
-                                          const wp<IBinder>& parent, const wp<Layer> parentLayer,
-                                          const wp<IBinder>& producer, bool addToRoot) {
-    Mutex::Autolock lock(mCreatedLayersLock);
-    mCreatedLayers[handle->localBinder()] =
-            std::make_unique<LayerCreatedState>(layer, parent, parentLayer, producer, addToRoot);
-}
-
-auto SurfaceFlinger::getLayerCreatedState(const sp<IBinder>& handle) {
-    Mutex::Autolock lock(mCreatedLayersLock);
-    BBinder* b = nullptr;
-    if (handle) {
-        b = handle->localBinder();
-    }
-
-    if (b == nullptr) {
-        return std::unique_ptr<LayerCreatedState>(nullptr);
-    }
-
-    auto it = mCreatedLayers.find(b);
-    if (it == mCreatedLayers.end()) {
-        ALOGE("Can't find layer from handle %p", handle.get());
-        return std::unique_ptr<LayerCreatedState>(nullptr);
-    }
-
-    auto state = std::move(it->second);
-    mCreatedLayers.erase(it);
-    return state;
-}
-
-sp<Layer> SurfaceFlinger::handleLayerCreatedLocked(const sp<IBinder>& handle) {
-    const auto& state = getLayerCreatedState(handle);
-    if (!state) {
-        return nullptr;
-    }
-
-    sp<Layer> layer = state->layer.promote();
+void SurfaceFlinger::handleLayerCreatedLocked(const LayerCreatedState& state) {
+    sp<Layer> layer = state.layer.promote();
     if (!layer) {
-        ALOGE("Invalid layer %p", state->layer.unsafe_get());
-        return nullptr;
+        ALOGD("Layer was destroyed soon after creation %p", state.layer.unsafe_get());
+        return;
     }
 
     sp<Layer> parent;
-    bool allowAddRoot = state->addToRoot;
-    if (state->initialParent != nullptr) {
-        parent = fromHandle(state->initialParent.promote()).promote();
+    bool addToRoot = state.addToRoot;
+    if (state.initialParent != nullptr) {
+        parent = state.initialParent.promote();
         if (parent == nullptr) {
-            ALOGE("Invalid parent %p", state->initialParent.unsafe_get());
-            allowAddRoot = false;
+            ALOGD("Parent was destroyed soon after creation %p", state.initialParent.unsafe_get());
+            addToRoot = false;
         }
-    } else if (state->initialParentLayer != nullptr) {
-        parent = state->initialParentLayer.promote();
-        allowAddRoot = false;
     }
 
-    if (parent == nullptr && allowAddRoot) {
+    if (parent == nullptr && addToRoot) {
         layer->setIsAtRoot(true);
         mCurrentState.layersSortedByZ.add(layer);
     } else if (parent == nullptr) {
@@ -7013,32 +7256,15 @@
 
     layer->updateTransformHint(mActiveDisplayTransformHint);
 
-    if (state->initialProducer != nullptr) {
-        mGraphicBufferProducerList.insert(state->initialProducer);
-        LOG_ALWAYS_FATAL_IF(mGraphicBufferProducerList.size() > mMaxGraphicBufferProducerListSize,
-                            "Suspected IGBP leak: %zu IGBPs (%zu max), %zu Layers",
-                            mGraphicBufferProducerList.size(), mMaxGraphicBufferProducerListSize,
-                            mNumLayers.load());
-        if (mGraphicBufferProducerList.size() > mGraphicBufferProducerListSizeLogThreshold) {
-            ALOGW("Suspected IGBP leak: %zu IGBPs (%zu max), %zu Layers",
-                  mGraphicBufferProducerList.size(), mMaxGraphicBufferProducerListSize,
-                  mNumLayers.load());
-        }
-    }
-
-    return layer;
+    mInterceptor->saveSurfaceCreation(layer);
 }
 
-void SurfaceFlinger::scheduleRegionSamplingThread() {
-    static_cast<void>(schedule([&] { notifyRegionSamplingThread(); }));
-}
-
-void SurfaceFlinger::notifyRegionSamplingThread() {
+void SurfaceFlinger::sample() {
     if (!mLumaSampling || !mRegionSamplingThread) {
         return;
     }
 
-    mRegionSamplingThread->onCompositionComplete(mEventQueue->nextExpectedInvalidate());
+    mRegionSamplingThread->onCompositionComplete(mScheduler->getScheduledFrameTime());
 }
 
 void SurfaceFlinger::onActiveDisplaySizeChanged(const sp<DisplayDevice>& activeDisplay) {
@@ -7082,6 +7308,84 @@
     return NO_ERROR;
 }
 
+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);
+        }
+    } 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);
+        }
+    }
+    ALOGE_IF(bufferSizeExceedsLimit,
+             "Attempted to create an ExternalTexture for layer %s that exceeds render target size "
+             "limit.",
+             layerName);
+    return buffer;
+}
+
+bool SurfaceFlinger::commitCreatedLayers() {
+    std::vector<LayerCreatedState> createdLayers;
+    {
+        std::scoped_lock<std::mutex> lock(mCreatedLayersLock);
+        createdLayers = std::move(mCreatedLayers);
+        mCreatedLayers.clear();
+        if (createdLayers.size() == 0) {
+            return false;
+        }
+    }
+
+    Mutex::Autolock _l(mStateLock);
+    for (const auto& createdLayer : createdLayers) {
+        handleLayerCreatedLocked(createdLayer);
+    }
+    createdLayers.clear();
+    mLayersAdded = true;
+    return true;
+}
+
+// gui::ISurfaceComposer
+binder::Status SurfaceComposerAIDL::captureDisplay(
+        const DisplayCaptureArgs& args, const sp<IScreenCaptureListener>& captureListener) {
+    status_t status = mFlinger->captureDisplay(args, captureListener);
+    return binder::Status::fromStatusT(status);
+}
+
+binder::Status SurfaceComposerAIDL::captureDisplayById(
+        int64_t displayId, const sp<IScreenCaptureListener>& captureListener) {
+    status_t status;
+    IPCThreadState* ipc = IPCThreadState::self();
+    const int uid = ipc->getCallingUid();
+    if (uid == AID_ROOT || uid == AID_GRAPHICS || uid == AID_SYSTEM || uid == AID_SHELL) {
+        std::optional<DisplayId> id = DisplayId::fromValue(static_cast<uint64_t>(displayId));
+        status = mFlinger->captureDisplay(*id, captureListener);
+    } else {
+        status = PERMISSION_DENIED;
+    }
+    return binder::Status::fromStatusT(status);
+}
+
+binder::Status SurfaceComposerAIDL::captureLayers(
+        const LayerCaptureArgs& args, const sp<IScreenCaptureListener>& captureListener) {
+    status_t status = mFlinger->captureLayers(args, captureListener);
+    return binder::Status::fromStatusT(status);
+}
+
 } // namespace android
 
 #if defined(__gl_h_)
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index 74fe7d9..f2636b4 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -23,7 +23,7 @@
  */
 
 #include <android-base/thread_annotations.h>
-#include <compositionengine/OutputColorSetting.h>
+#include <android/gui/BnSurfaceComposer.h>
 #include <cutils/atomic.h>
 #include <cutils/compiler.h>
 #include <gui/BufferQueue.h>
@@ -32,7 +32,6 @@
 #include <gui/ISurfaceComposerClient.h>
 #include <gui/ITransactionCompletedListener.h>
 #include <gui/LayerState.h>
-#include <gui/OccupancyTracker.h>
 #include <layerproto/LayerProtoHeader.h>
 #include <math/mat4.h>
 #include <renderengine/LayerSettings.h>
@@ -48,13 +47,16 @@
 #include <utils/Trace.h>
 #include <utils/threads.h>
 
+#include <compositionengine/OutputColorSetting.h>
+#include <scheduler/Fps.h>
+
 #include "ClientCache.h"
 #include "DisplayDevice.h"
 #include "DisplayHardware/HWC2.h"
 #include "DisplayHardware/PowerAdvisor.h"
 #include "DisplayIdGenerator.h"
 #include "Effects/Daltonizer.h"
-#include "Fps.h"
+#include "FlagManager.h"
 #include "FrameTracker.h"
 #include "LayerVector.h"
 #include "Scheduler/RefreshRateConfigs.h"
@@ -62,9 +64,11 @@
 #include "Scheduler/Scheduler.h"
 #include "Scheduler/VsyncModulator.h"
 #include "SurfaceFlingerFactory.h"
-#include "SurfaceTracing.h"
 #include "TracedOrdinal.h"
+#include "Tracing/LayerTracing.h"
+#include "Tracing/TransactionTracing.h"
 #include "TransactionCallbackInvoker.h"
+#include "TransactionState.h"
 
 #include <atomic>
 #include <cstdint>
@@ -83,12 +87,15 @@
 #include <unordered_set>
 #include <utility>
 
+#include <aidl/android/hardware/graphics/common/DisplayDecorationSupport.h>
+
 using namespace android::surfaceflinger;
 
 namespace android {
 
 class Client;
 class EventThread;
+class FlagManager;
 class FpsReporter;
 class TunnelModeEnabledReporter;
 class HdrLayerInfoReporter;
@@ -101,8 +108,13 @@
 class RenderArea;
 class TimeStats;
 class FrameTracer;
+class ScreenCapturer;
 class WindowInfosListenerInvoker;
 
+using gui::CaptureArgs;
+using gui::DisplayCaptureArgs;
+using gui::IRegionSamplingListener;
+using gui::LayerCaptureArgs;
 using gui::ScreenCaptureResults;
 
 namespace frametimeline {
@@ -133,6 +145,21 @@
     eTransactionMask = 0x1f,
 };
 
+// Latch Unsignaled buffer behaviours
+enum class LatchUnsignaledConfig {
+    // All buffers are latched signaled.
+    Disabled,
+
+    // Latch unsignaled is permitted when a single layer is updated in a frame,
+    // and the update includes just a buffer update (i.e. no sync transactions
+    // or geometry changes).
+    AutoSingleLayer,
+
+    // All buffers are latched unsignaled. This behaviour is discouraged as it
+    // can break sync transactions, stall the display and cause undesired side effects.
+    Always,
+};
+
 using DisplayColorSetting = compositionengine::OutputColorSetting;
 
 struct SurfaceFlingerBE {
@@ -154,29 +181,19 @@
     nsecs_t mFrameBuckets[NUM_BUCKETS] = {};
     nsecs_t mTotalTime = 0;
     std::atomic<nsecs_t> mLastSwapTime = 0;
+};
 
-    // Double- vs. triple-buffering stats
-    struct BufferingStats {
-        size_t numSegments = 0;
-        nsecs_t totalTime = 0;
-
-        // "Two buffer" means that a third buffer was never used, whereas
-        // "double-buffered" means that on average the segment only used two
-        // buffers (though it may have used a third for some part of the
-        // segment)
-        nsecs_t twoBufferTime = 0;
-        nsecs_t doubleBufferedTime = 0;
-        nsecs_t tripleBufferedTime = 0;
-    };
-    mutable Mutex mBufferingStatsMutex;
-    std::unordered_map<std::string, BufferingStats> mBufferingStats;
+struct SCOPED_CAPABILITY UnnecessaryLock {
+    explicit UnnecessaryLock(Mutex& mutex) ACQUIRE(mutex) {}
+    ~UnnecessaryLock() RELEASE() {}
 };
 
 class SurfaceFlinger : public BnSurfaceComposer,
                        public PriorityDumper,
                        private IBinder::DeathRecipient,
                        private HWC2::ComposerCallback,
-                       private ISchedulerCallback {
+                       private ICompositor,
+                       private scheduler::ISchedulerCallback {
 public:
     struct SkipInitializationTag {};
 
@@ -240,10 +257,8 @@
     // found on devices with wide color gamut (e.g. Display-P3) display.
     static bool hasWideColorDisplay;
 
-    static ui::Rotation internalDisplayOrientation;
-
     // Indicate if device wants color management on its display.
-    static bool useColorManagement;
+    static const constexpr bool useColorManagement = true;
 
     static bool useContextPriority;
 
@@ -259,18 +274,9 @@
     static ui::Dataspace wideColorGamutCompositionDataspace;
     static ui::PixelFormat wideColorGamutCompositionPixelFormat;
 
-    // Whether to use frame rate API when deciding about the refresh rate of the display. This
-    // variable is caches in SF, so that we can check it with each layer creation, and a void the
-    // overhead that is caused by reading from sysprop.
-    static bool useFrameRateApi;
-
     static constexpr SkipInitializationTag SkipInitialization;
 
-    // Whether or not SDR layers should be dimmed to the desired SDR white point instead of
-    // being treated as native display brightness
-    static bool enableSdrDimming;
-
-    static bool enableLatchUnsignaled;
+    static LatchUnsignaledConfig enableLatchUnsignaledConfig;
 
     // must be called before clients can connect
     void init() ANDROID_API;
@@ -281,12 +287,17 @@
     SurfaceFlingerBE& getBE() { return mBE; }
     const SurfaceFlingerBE& getBE() const { return mBE; }
 
-    // Schedule an asynchronous or synchronous task on the main thread.
-    template <typename F, typename T = std::invoke_result_t<F>>
-    [[nodiscard]] std::future<T> schedule(F&&);
+    // Indicates frame activity, i.e. whether commit and/or composite is taking place.
+    enum class FrameHint { kNone, kActive };
 
-    // force full composition on all displays
-    void repaintEverything();
+    // Schedule commit of transactions on the main thread ahead of the next VSYNC.
+    void scheduleCommit(FrameHint);
+    // As above, but also force composite regardless if transactions were committed.
+    void scheduleComposite(FrameHint);
+    // As above, but also force dirty geometry to repaint.
+    void scheduleRepaint();
+    // Schedule sampling independently from commit or composite.
+    void scheduleSample();
 
     surfaceflinger::Factory& getFactory() { return mFactory; }
 
@@ -300,11 +311,6 @@
     // utility function to delete a texture on the main thread
     void deleteTextureAsync(uint32_t texture);
 
-    // called on the main thread by MessageQueue when an internal message
-    // is received
-    // TODO: this should be made accessible only to MessageQueue
-    void onMessageReceived(int32_t what, int64_t vsyncId, nsecs_t expectedVSyncTime);
-
     renderengine::RenderEngine& getRenderEngine() const;
 
     bool authenticateSurfaceTextureLocked(
@@ -312,6 +318,7 @@
 
     void onLayerFirstRef(Layer*);
     void onLayerDestroyed(Layer*);
+    void onLayerUpdate();
 
     void removeHierarchyFromOffscreenLayers(Layer* layer);
     void removeFromOffscreenLayers(Layer* layer);
@@ -343,23 +350,22 @@
     // We're reference counted, never destroy SurfaceFlinger directly
     virtual ~SurfaceFlinger();
 
-    virtual uint32_t setClientStateLocked(
-            const FrameTimelineInfo& info, const ComposerState& composerState,
-            int64_t desiredPresentTime, bool isAutoTimestamp, int64_t postTime,
-            uint32_t permissions,
-            std::unordered_set<ListenerCallbacks, ListenerCallbacksHash>& listenerCallbacks)
-            REQUIRES(mStateLock);
-    virtual void commitTransactionLocked();
-
     virtual void processDisplayAdded(const wp<IBinder>& displayToken, const DisplayDeviceState&)
             REQUIRES(mStateLock);
 
+    virtual std::shared_ptr<renderengine::ExternalTexture> getExternalTextureFromBufferData(
+            const BufferData& bufferData, const char* layerName) const;
+
     // Returns true if any display matches a `bool(const DisplayDevice&)` predicate.
     template <typename Predicate>
     bool hasDisplay(Predicate p) const REQUIRES(mStateLock) {
         return static_cast<bool>(findDisplay(p));
     }
 
+    bool exceedsMaxRenderTargetSize(uint32_t width, uint32_t height) const {
+        return width > mMaxRenderTargetSize || height > mMaxRenderTargetSize;
+    }
+
 private:
     friend class BufferLayer;
     friend class BufferQueueLayer;
@@ -371,14 +377,14 @@
     friend class MonitoredProducer;
     friend class RefreshRateOverlay;
     friend class RegionSamplingThread;
-    friend class SurfaceTracing;
+    friend class LayerRenderArea;
+    friend class LayerTracing;
 
     // For unit tests
     friend class TestableSurfaceFlinger;
     friend class TransactionApplicationTest;
     friend class TunnelModeEnabledReporterTest;
 
-    using RefreshRate = scheduler::RefreshRateConfigs::RefreshRate;
     using VsyncModulator = scheduler::VsyncModulator;
     using TransactionSchedule = scheduler::TransactionSchedule;
     using TraverseLayersFunction = std::function<void(const LayerVector::Visitor&)>;
@@ -459,6 +465,8 @@
     };
 
     using ActiveModeInfo = DisplayDevice::ActiveModeInfo;
+    using KernelIdleTimerController =
+            ::android::scheduler::RefreshRateConfigs::KernelIdleTimerController;
 
     enum class BootStage {
         BOOTLOADER,
@@ -471,96 +479,6 @@
         hal::Connection connection = hal::Connection::INVALID;
     };
 
-    class CountDownLatch {
-    public:
-        enum {
-            eSyncTransaction = 1 << 0,
-            eSyncInputWindows = 1 << 1,
-        };
-        explicit CountDownLatch(uint32_t flags) : mFlags(flags) {}
-
-        // True if there is no waiting condition after count down.
-        bool countDown(uint32_t flag) {
-            std::unique_lock<std::mutex> lock(mMutex);
-            if (mFlags == 0) {
-                return true;
-            }
-            mFlags &= ~flag;
-            if (mFlags == 0) {
-                mCountDownComplete.notify_all();
-                return true;
-            }
-            return false;
-        }
-
-        // Return true if triggered.
-        bool wait_until(const std::chrono::seconds& timeout) const {
-            std::unique_lock<std::mutex> lock(mMutex);
-            const auto untilTime = std::chrono::system_clock::now() + timeout;
-            while (mFlags != 0) {
-                // Conditional variables can be woken up sporadically, so we check count
-                // to verify the wakeup was triggered by |countDown|.
-                if (std::cv_status::timeout == mCountDownComplete.wait_until(lock, untilTime)) {
-                    return false;
-                }
-            }
-            return true;
-        }
-
-    private:
-        uint32_t mFlags;
-        mutable std::condition_variable mCountDownComplete;
-        mutable std::mutex mMutex;
-    };
-
-    struct TransactionState {
-        TransactionState(const FrameTimelineInfo& frameTimelineInfo,
-                         const Vector<ComposerState>& composerStates,
-                         const Vector<DisplayState>& displayStates, uint32_t transactionFlags,
-                         const sp<IBinder>& applyToken,
-                         const InputWindowCommands& inputWindowCommands, int64_t desiredPresentTime,
-                         bool isAutoTimestamp, const client_cache_t& uncacheBuffer,
-                         int64_t postTime, uint32_t permissions, bool hasListenerCallbacks,
-                         std::vector<ListenerCallbacks> listenerCallbacks, int originPid,
-                         int originUid, uint64_t transactionId)
-              : frameTimelineInfo(frameTimelineInfo),
-                states(composerStates),
-                displays(displayStates),
-                flags(transactionFlags),
-                applyToken(applyToken),
-                inputWindowCommands(inputWindowCommands),
-                desiredPresentTime(desiredPresentTime),
-                isAutoTimestamp(isAutoTimestamp),
-                buffer(uncacheBuffer),
-                postTime(postTime),
-                permissions(permissions),
-                hasListenerCallbacks(hasListenerCallbacks),
-                listenerCallbacks(listenerCallbacks),
-                originPid(originPid),
-                originUid(originUid),
-                id(transactionId) {}
-
-        void traverseStatesWithBuffers(std::function<void(const layer_state_t&)> visitor);
-
-        FrameTimelineInfo frameTimelineInfo;
-        Vector<ComposerState> states;
-        Vector<DisplayState> displays;
-        uint32_t flags;
-        sp<IBinder> applyToken;
-        InputWindowCommands inputWindowCommands;
-        const int64_t desiredPresentTime;
-        const bool isAutoTimestamp;
-        client_cache_t buffer;
-        const int64_t postTime;
-        uint32_t permissions;
-        bool hasListenerCallbacks;
-        std::vector<ListenerCallbacks> listenerCallbacks;
-        int originPid;
-        int originUid;
-        uint64_t id;
-        std::shared_ptr<CountDownLatch> transactionCommittedSignal;
-    };
-
     template <typename F, std::enable_if_t<!std::is_member_function_pointer_v<F>>* = nullptr>
     static Dumper dumper(F&& dump) {
         using namespace std::placeholders;
@@ -631,12 +549,10 @@
     sp<IDisplayEventConnection> createDisplayEventConnection(
             ISurfaceComposer::VsyncSource vsyncSource = eVsyncSourceApp,
             ISurfaceComposer::EventRegistrationFlags eventRegistration = {}) override;
-    status_t captureDisplay(const DisplayCaptureArgs& args,
-                            const sp<IScreenCaptureListener>& captureListener) override;
-    status_t captureDisplay(uint64_t displayOrLayerStack,
-                            const sp<IScreenCaptureListener>& captureListener) override;
-    status_t captureLayers(const LayerCaptureArgs& args,
-                           const sp<IScreenCaptureListener>& captureListener) override;
+
+    status_t captureDisplay(const DisplayCaptureArgs&, const sp<IScreenCaptureListener>&);
+    status_t captureDisplay(DisplayId, const sp<IScreenCaptureListener>&);
+    status_t captureLayers(const LayerCaptureArgs&, const sp<IScreenCaptureListener>&);
 
     status_t getDisplayStats(const sp<IBinder>& displayToken, DisplayStatInfo* stats) override;
     status_t getDisplayState(const sp<IBinder>& displayToken, ui::DisplayState*)
@@ -648,6 +564,9 @@
     status_t getDisplayNativePrimaries(const sp<IBinder>& displayToken,
                                        ui::DisplayPrimaries&) override;
     status_t setActiveColorMode(const sp<IBinder>& displayToken, ui::ColorMode colorMode) override;
+    status_t getBootDisplayModeSupport(bool* outSupport) const override;
+    status_t setBootDisplayMode(const sp<IBinder>& displayToken, ui::DisplayModeId id) override;
+    status_t clearBootDisplayMode(const sp<IBinder>& displayToken) override;
     void setAutoLowLatencyMode(const sp<IBinder>& displayToken, bool on) override;
     void setGameContentType(const sp<IBinder>& displayToken, bool on) override;
     void setPowerMode(const sp<IBinder>& displayToken, int mode) override;
@@ -707,13 +626,18 @@
     status_t notifyPowerBoost(int32_t boostId) override;
     status_t setGlobalShadowSettings(const half4& ambientColor, const half4& spotColor,
                                      float lightPosY, float lightPosZ, float lightRadius) override;
+    status_t getDisplayDecorationSupport(
+            const sp<IBinder>& displayToken,
+            std::optional<aidl::android::hardware::graphics::common::DisplayDecorationSupport>*
+                    outSupport) const override;
     status_t setFrameRate(const sp<IGraphicBufferProducer>& surface, float frameRate,
                           int8_t compatibility, int8_t changeFrameRateStrategy) override;
-    status_t acquireFrameRateFlexibilityToken(sp<IBinder>* outToken) override;
 
     status_t setFrameTimelineInfo(const sp<IGraphicBufferProducer>& surface,
                                   const FrameTimelineInfo& frameTimelineInfo) override;
 
+    status_t setOverrideFrameRate(uid_t uid, float frameRate) override;
+
     status_t addTransactionTraceListener(
             const sp<gui::ITransactionTraceListener>& listener) override;
 
@@ -729,9 +653,6 @@
     // Implements IBinder::DeathRecipient.
     void binderDied(const wp<IBinder>& who) override;
 
-    // Implements RefBase.
-    void onFirstRef() override;
-
     // HWC2::ComposerCallback overrides:
     void onComposerHalVsync(hal::HWDisplayId, int64_t timestamp,
                             std::optional<hal::VsyncPeriodNanos>) override;
@@ -740,6 +661,20 @@
     void onComposerHalVsyncPeriodTimingChanged(hal::HWDisplayId,
                                                const hal::VsyncPeriodChangeTimeline&) override;
     void onComposerHalSeamlessPossible(hal::HWDisplayId) override;
+    void onComposerHalVsyncIdle(hal::HWDisplayId) override;
+
+    // ICompositor overrides:
+
+    // Commits transactions for layers and displays. Returns whether any state has been invalidated,
+    // i.e. whether a frame should be composited for each display.
+    bool commit(nsecs_t frameTime, int64_t vsyncId, nsecs_t expectedVsyncTime) override;
+
+    // Composites a frame for each display. CompositionEngine performs GPU and/or HAL composition
+    // via RenderEngine and the Composer HAL, respectively.
+    void composite(nsecs_t frameTime, int64_t vsyncId) override;
+
+    // Samples the composited frame via RegionSamplingThread.
+    void sample() override;
 
     /*
      * ISchedulerCallback
@@ -747,42 +682,38 @@
 
     // Toggles hardware VSYNC by calling into HWC.
     void setVsyncEnabled(bool) override;
-    // Initiates a refresh rate change to be applied on invalidate.
-    void changeRefreshRate(const Scheduler::RefreshRate&, Scheduler::ModeEvent) override;
-    // Forces full composition on all displays without resetting the scheduler idle timer.
-    void repaintEverythingForHWC() override;
+    // Sets the desired display mode if allowed by policy.
+    void requestDisplayMode(DisplayModePtr, DisplayModeEvent) override;
     // Called when kernel idle timer has expired. Used to update the refresh rate overlay.
     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);
+    // 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>
+            getKernelIdleTimerProperties(DisplayId) REQUIRES(mStateLock);
+    // Updates the kernel idle timer either through HWC or through sysprop
+    // depending on which controller is provided
+    void updateKernelIdleTimer(std::chrono::milliseconds timeoutMs, KernelIdleTimerController,
+                               PhysicalDisplayId) REQUIRES(mStateLock);
     // Keeps track of whether the kernel idle timer is currently enabled, so we don't have to
     // make calls to sys prop each time.
     bool mKernelIdleTimerEnabled = false;
     // Show spinner with refresh rate overlay
     bool mRefreshRateOverlaySpinner = false;
 
-    /*
-     * Message handling
-     */
-    // Can only be called from the main thread or with mStateLock held
-    void signalTransaction();
-    // Can only be called from the main thread or with mStateLock held
-    void signalLayerUpdate();
-    void signalRefresh();
-
     // Called on the main thread in response to initializeDisplays()
     void onInitializeDisplays() REQUIRES(mStateLock);
     // Sets the desired active mode bit. It obtains the lock, and sets mDesiredActiveMode.
     void setDesiredActiveMode(const ActiveModeInfo& info) REQUIRES(mStateLock);
-    status_t setActiveMode(const sp<IBinder>& displayToken, int id);
-    // Once HWC has returned the present fence, this sets the active mode and a new refresh
-    // rate in SF.
-    void setActiveModeInternal() REQUIRES(mStateLock);
+    status_t setActiveModeFromBackdoor(const sp<IBinder>& displayToken, int id);
+    // Sets the active mode and a new refresh rate in SF.
+    void updateInternalStateWithChangedMode() REQUIRES(mStateLock);
     // Calls to setActiveMode on the main thread if there is a pending mode change
     // that needs to be applied.
-    void performSetActiveMode() REQUIRES(mStateLock);
+    void setActiveModeInHwcIfNeeded() REQUIRES(mStateLock);
     void clearDesiredActiveModeState(const sp<DisplayDevice>&) REQUIRES(mStateLock);
     // Called when active mode is no longer is progress
     void desiredActiveModeChangeDone(const sp<DisplayDevice>&) REQUIRES(mStateLock);
@@ -790,31 +721,28 @@
     void setPowerModeInternal(const sp<DisplayDevice>& display, hal::PowerMode mode)
             REQUIRES(mStateLock);
 
+    // Returns true if the display has a visible HDR layer in its layer stack.
+    bool hasVisibleHdrLayer(const sp<DisplayDevice>& display) REQUIRES(mStateLock);
+
     // Sets the desired display mode specs.
     status_t setDesiredDisplayModeSpecsInternal(
             const sp<DisplayDevice>& display,
             const std::optional<scheduler::RefreshRateConfigs::Policy>& policy, bool overridePolicy)
             EXCLUDES(mStateLock);
 
-    // Handle the INVALIDATE message queue event, latching new buffers and applying
-    // incoming transactions
-    void onMessageInvalidate(int64_t vsyncId, nsecs_t expectedVSyncTime);
+    void commitTransactions() EXCLUDES(mStateLock);
+    void commitTransactionsLocked(uint32_t transactionFlags) REQUIRES(mStateLock);
+    void doCommitTransactions() REQUIRES(mStateLock);
 
-    // Returns whether the transaction actually modified any state
-    bool handleMessageTransaction();
+    // Returns whether a new buffer has been latched.
+    bool latchBuffers();
 
-    // Handle the REFRESH message queue event, sending the current frame down to RenderEngine and
-    // the Composer HAL for presentation
-    void onMessageRefresh();
-
-    // Returns whether a new buffer has been latched (see handlePageFlip())
-    bool handleMessageInvalidate();
-
-    void handleTransaction(uint32_t transactionFlags);
-    void handleTransactionLocked(uint32_t transactionFlags) REQUIRES(mStateLock);
+    void updateLayerGeometry();
 
     void updateInputFlinger();
-    void notifyWindowInfos();
+    void persistDisplayBrightness(bool needsComposite) REQUIRES(SF_MAIN_THREAD);
+    void buildWindowInfos(std::vector<gui::WindowInfo>& outWindowInfos,
+                          std::vector<gui::DisplayInfo>& outDisplayInfos);
     void commitInputWindowCommands() REQUIRES(mStateLock);
     void updateCursorAsync();
 
@@ -822,16 +750,11 @@
     void updatePhaseConfiguration(const Fps&) REQUIRES(mStateLock);
     void setVsyncConfig(const VsyncModulator::VsyncConfig&, nsecs_t vsyncPeriod);
 
-    /* handlePageFlip - latch a new buffer if available and compute the dirty
-     * region. Returns whether a new buffer has been latched, i.e., whether it
-     * is necessary to perform a refresh during this vsync.
-     */
-    bool handlePageFlip();
 
     /*
      * Transactions
      */
-    void applyTransactionState(const FrameTimelineInfo& info, const Vector<ComposerState>& state,
+    bool applyTransactionState(const FrameTimelineInfo& info, Vector<ComposerState>& state,
                                const Vector<DisplayState>& displays, uint32_t flags,
                                const InputWindowCommands& inputWindowCommands,
                                const int64_t desiredPresentTime, bool isAutoTimestamp,
@@ -841,27 +764,56 @@
                                int originPid, int originUid, uint64_t transactionId)
             REQUIRES(mStateLock);
     // flush pending transaction that was presented after desiredPresentTime.
-    void flushTransactionQueues();
+    bool flushTransactionQueues(int64_t vsyncId);
     // Returns true if there is at least one transaction that needs to be flushed
     bool transactionFlushNeeded();
-    uint32_t getTransactionFlags(uint32_t flags);
-    uint32_t peekTransactionFlags();
-    // Can only be called from the main thread or with mStateLock held
-    uint32_t setTransactionFlags(uint32_t flags);
-    // Indicate SF should call doTraversal on layers, but don't trigger a wakeup! We use this cases
-    // where there are still pending transactions but we know they won't be ready until a frame
-    // arrives from a different layer. So we need to ensure we performTransaction from invalidate
-    // but there is no need to try and wake up immediately to do it. Rather we rely on
-    // onFrameAvailable or another layer update to wake us up.
-    void setTraversalNeeded();
-    uint32_t setTransactionFlags(uint32_t flags, TransactionSchedule, const sp<IBinder>& = {});
-    void commitTransaction() REQUIRES(mStateLock);
+
+    int flushPendingTransactionQueues(
+            std::vector<TransactionState>& transactions,
+            std::unordered_map<sp<IBinder>, uint64_t, SpHash<IBinder>>& bufferLayersReadyToPresent,
+            std::unordered_set<sp<IBinder>, SpHash<IBinder>>& applyTokensWithUnsignaledTransactions,
+            bool tryApplyUnsignaled) REQUIRES(mStateLock, mQueueLock);
+
+    int flushUnsignaledPendingTransactionQueues(
+            std::vector<TransactionState>& transactions,
+            std::unordered_map<sp<IBinder>, uint64_t, SpHash<IBinder>>& bufferLayersReadyToPresent,
+            std::unordered_set<sp<IBinder>, SpHash<IBinder>>& applyTokensWithUnsignaledTransactions)
+            REQUIRES(mStateLock, mQueueLock);
+
+    uint32_t setClientStateLocked(const FrameTimelineInfo&, ComposerState&,
+                                  int64_t desiredPresentTime, bool isAutoTimestamp,
+                                  int64_t postTime, uint32_t permissions) REQUIRES(mStateLock);
+
+    uint32_t getTransactionFlags() const;
+
+    // Sets the masked bits, and schedules a commit if needed.
+    void setTransactionFlags(uint32_t mask, TransactionSchedule = TransactionSchedule::Late,
+                             const sp<IBinder>& applyToken = nullptr);
+
+    // Clears and returns the masked bits.
+    uint32_t clearTransactionFlags(uint32_t mask);
+
     void commitOffscreenLayers();
-    bool transactionIsReadyToBeApplied(
+
+    enum class TransactionReadiness {
+        NotReady,
+        NotReadyBarrier,
+        Ready,
+        ReadyUnsignaled,
+    };
+    TransactionReadiness transactionIsReadyToBeApplied(
             const FrameTimelineInfo& info, bool isAutoTimestamp, int64_t desiredPresentTime,
             uid_t originUid, const Vector<ComposerState>& states,
-            const std::unordered_set<sp<IBinder>, ISurfaceComposer::SpHash<IBinder>>&
-                    bufferLayersReadyToPresent) const REQUIRES(mStateLock);
+            const std::unordered_map<
+                sp<IBinder>, uint64_t, SpHash<IBinder>>& bufferLayersReadyToPresent,
+            size_t totalTXapplied, bool tryApplyUnsignaled) const REQUIRES(mStateLock);
+    static LatchUnsignaledConfig getLatchUnsignaledConfig();
+    bool shouldLatchUnsignaled(const sp<Layer>& layer, const layer_state_t&, size_t numStates,
+                               size_t totalTXapplied) const;
+    bool stopTransactionProcessing(const std::unordered_set<sp<IBinder>, SpHash<IBinder>>&
+                                           applyTokensWithUnsignaledTransactions) const;
+    bool applyTransactions(std::vector<TransactionState>& transactions, int64_t vsyncId)
+            REQUIRES(mStateLock);
     uint32_t setDisplayStateLocked(const DisplayState& s) REQUIRES(mStateLock);
     uint32_t addInputWindowCommands(const InputWindowCommands& inputWindowCommands)
             REQUIRES(mStateLock);
@@ -869,35 +821,27 @@
     /*
      * Layer management
      */
-    status_t createLayer(const String8& name, const sp<Client>& client, uint32_t w, uint32_t h,
-                         PixelFormat format, uint32_t flags, LayerMetadata metadata,
-                         sp<IBinder>* handle, sp<IGraphicBufferProducer>* gbp,
+    status_t createLayer(LayerCreationArgs& args, sp<IBinder>* outHandle,
                          const sp<IBinder>& parentHandle, int32_t* outLayerId,
                          const sp<Layer>& parentLayer = nullptr,
                          uint32_t* outTransformHint = nullptr);
 
-    status_t createBufferQueueLayer(const sp<Client>& client, std::string name, uint32_t w,
-                                    uint32_t h, uint32_t flags, LayerMetadata metadata,
-                                    PixelFormat& format, sp<IBinder>* outHandle,
-                                    sp<IGraphicBufferProducer>* outGbp, sp<Layer>* outLayer);
+    status_t createBufferQueueLayer(LayerCreationArgs& args, PixelFormat& format,
+                                    sp<IBinder>* outHandle, sp<IGraphicBufferProducer>* outGbp,
+                                    sp<Layer>* outLayer);
 
-    status_t createBufferStateLayer(const sp<Client>& client, std::string name, uint32_t w,
-                                    uint32_t h, uint32_t flags, LayerMetadata metadata,
-                                    sp<IBinder>* outHandle, sp<Layer>* outLayer);
+    status_t createBufferStateLayer(LayerCreationArgs& args, sp<IBinder>* outHandle,
+                                    sp<Layer>* outLayer);
 
-    status_t createEffectLayer(const sp<Client>& client, std::string name, uint32_t w, uint32_t h,
-                               uint32_t flags, LayerMetadata metadata, sp<IBinder>* outHandle,
+    status_t createEffectLayer(const LayerCreationArgs& args, sp<IBinder>* outHandle,
                                sp<Layer>* outLayer);
 
-    status_t createContainerLayer(const sp<Client>& client, std::string name, uint32_t w,
-                                  uint32_t h, uint32_t flags, LayerMetadata metadata,
-                                  sp<IBinder>* outHandle, sp<Layer>* outLayer);
+    status_t createContainerLayer(const LayerCreationArgs& args, sp<IBinder>* outHandle,
+                                  sp<Layer>* outLayer);
 
-    status_t mirrorLayer(const sp<Client>& client, const sp<IBinder>& mirrorFromHandle,
+    status_t mirrorLayer(const LayerCreationArgs& args, const sp<IBinder>& mirrorFromHandle,
                          sp<IBinder>* outHandle, int32_t* outLayerId);
 
-    std::string getUniqueLayerName(const char* name);
-
     // 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.
@@ -906,9 +850,8 @@
 
     // add a layer to SurfaceFlinger
     status_t addClientLayer(const sp<Client>& client, const sp<IBinder>& handle,
-                            const sp<IGraphicBufferProducer>& gbc, const sp<Layer>& lbc,
-                            const sp<IBinder>& parentHandle, const sp<Layer>& parentLayer,
-                            bool addToRoot, uint32_t* outTransformHint);
+                            const sp<Layer>& lbc, const wp<Layer>& parentLayer, bool addToRoot,
+                            uint32_t* outTransformHint);
 
     // Traverse through all the layers and compute and cache its bounds.
     void computeLayerBounds();
@@ -916,17 +859,17 @@
     // Boot animation, on/off animations and screen capture
     void startBootAnim();
 
-    status_t captureScreenCommon(RenderAreaFuture, TraverseLayersFunction, ui::Size bufferSize,
-                                 ui::PixelFormat, bool allowProtected, bool grayscale,
-                                 const sp<IScreenCaptureListener>&);
-    status_t captureScreenCommon(RenderAreaFuture, TraverseLayersFunction,
-                                 const std::shared_ptr<renderengine::ExternalTexture>&,
-                                 bool regionSampling, bool grayscale,
-                                 const sp<IScreenCaptureListener>&);
-    status_t renderScreenImplLocked(const RenderArea&, TraverseLayersFunction,
-                                    const std::shared_ptr<renderengine::ExternalTexture>&,
-                                    bool canCaptureBlackoutContent, bool regionSampling,
-                                    bool grayscale, ScreenCaptureResults&);
+    std::shared_future<renderengine::RenderEngineResult> captureScreenCommon(
+            RenderAreaFuture, TraverseLayersFunction, ui::Size bufferSize, ui::PixelFormat,
+            bool allowProtected, bool grayscale, const sp<IScreenCaptureListener>&);
+    std::shared_future<renderengine::RenderEngineResult> captureScreenCommon(
+            RenderAreaFuture, TraverseLayersFunction,
+            const std::shared_ptr<renderengine::ExternalTexture>&, bool regionSampling,
+            bool grayscale, const sp<IScreenCaptureListener>&);
+    std::shared_future<renderengine::RenderEngineResult> renderScreenImpl(
+            const RenderArea&, TraverseLayersFunction,
+            const std::shared_ptr<renderengine::ExternalTexture>&, bool canCaptureBlackoutContent,
+            bool regionSampling, bool grayscale, ScreenCaptureResults&) EXCLUDES(mStateLock);
 
     // If the uid provided is not UNSET_UID, the traverse will skip any layers that don't have a
     // matching ownerUid
@@ -934,11 +877,7 @@
 
     void readPersistentProperties();
 
-    bool exceedsMaxRenderTargetSize(uint32_t width, uint32_t height) const {
-        return width > mMaxRenderTargetSize || height > mMaxRenderTargetSize;
-    }
-
-    int getMaxAcquiredBufferCountForCurrentRefreshRate(uid_t uid) const;
+    uint32_t getMaxAcquiredBufferCountForCurrentRefreshRate(uid_t uid) const;
 
     /*
      * Display and layer stack management
@@ -1009,8 +948,6 @@
     // region of all screens presenting this layer stack.
     void invalidateLayerStack(const sp<const Layer>& layer, const Region& dirty);
 
-    sp<DisplayDevice> getDisplayWithInputByLayer(Layer* layer) const REQUIRES(mStateLock);
-
     bool isDisplayActiveLocked(const sp<const DisplayDevice>& display) const REQUIRES(mStateLock) {
         return display->getDisplayToken() == mActiveDisplayToken;
     }
@@ -1029,8 +966,6 @@
     /*
      * Compositing
      */
-    void invalidateHwcGeometry();
-
     void postComposition();
     void getCompositorTiming(CompositorTiming* compositorTiming);
     void updateCompositorTiming(const DisplayStatInfo& stats, nsecs_t compositeTime,
@@ -1043,8 +978,9 @@
     /*
      * Display management
      */
-    void loadDisplayModes(PhysicalDisplayId displayId, DisplayModes& outModes,
-                          DisplayModePtr& outActiveMode) const REQUIRES(mStateLock);
+    std::pair<DisplayModes, DisplayModePtr> loadDisplayModes(PhysicalDisplayId) const
+            REQUIRES(mStateLock);
+
     sp<DisplayDevice> setupNewDisplayDeviceInternal(
             const wp<IBinder>& displayToken,
             std::shared_ptr<compositionengine::Display> compositionDisplay,
@@ -1070,10 +1006,6 @@
         getHwComposer().setVsyncEnabled(id, enabled);
     }
 
-    // Sets the refresh rate by switching active configs, if they are available for
-    // the desired refresh rate.
-    void changeRefreshRateLocked(const RefreshRate&, Scheduler::ModeEvent) REQUIRES(mStateLock);
-
     struct FenceWithFenceTime {
         sp<Fence> fence = Fence::NO_FENCE;
         std::shared_ptr<FenceTime> fenceTime = FenceTime::NO_FENCE;
@@ -1119,15 +1051,19 @@
         return {};
     }
 
-    // TODO(b/74619554): Remove special cases for primary display.
+    // TODO(b/182939859): SF conflates the primary (a.k.a. default) display with the first display
+    // connected at boot, which is typically internal. (Theoretically, it must be internal because
+    // SF does not support disconnecting it, though in practice HWC may circumvent this limitation.)
+    //
+    // SF inherits getInternalDisplayToken and getInternalDisplayId from ISurfaceComposer, so these
+    // locked counterparts are named consistently. Once SF supports headless mode and can designate
+    // any display as primary, the "internal" misnomer will be phased out.
     sp<IBinder> getInternalDisplayTokenLocked() const REQUIRES(mStateLock) {
-        const auto displayId = getInternalDisplayIdLocked();
-        return displayId ? getPhysicalDisplayTokenLocked(*displayId) : nullptr;
+        return getPhysicalDisplayTokenLocked(getInternalDisplayIdLocked());
     }
 
-    std::optional<PhysicalDisplayId> getInternalDisplayIdLocked() const REQUIRES(mStateLock) {
-        const auto hwcDisplayId = getHwComposer().getInternalHwcDisplayId();
-        return hwcDisplayId ? getHwComposer().toPhysicalDisplayId(*hwcDisplayId) : std::nullopt;
+    PhysicalDisplayId getInternalDisplayIdLocked() const REQUIRES(mStateLock) {
+        return getHwComposer().getPrimaryDisplayId();
     }
 
     // Toggles use of HAL/GPU virtual displays.
@@ -1158,21 +1094,17 @@
     void dumpStaticScreenStats(std::string& result) const;
     // Not const because each Layer needs to query Fences and cache timestamps.
     void dumpFrameEventsLocked(std::string& result);
-
-    void recordBufferingStats(const std::string& layerName,
-                              std::vector<OccupancyTracker::Segment>&& history);
-    void dumpBufferingStats(std::string& result) const;
     void dumpDisplayIdentificationData(std::string& result) const REQUIRES(mStateLock);
     void dumpRawDisplayIdentificationData(const DumpArgs&, std::string& result) const;
     void dumpWideColorInfo(std::string& result) const REQUIRES(mStateLock);
     LayersProto dumpDrawingStateProto(uint32_t traceFlags) const;
     void dumpOffscreenLayersProto(LayersProto& layersProto,
-                                  uint32_t traceFlags = SurfaceTracing::TRACE_ALL) const;
+                                  uint32_t traceFlags = LayerTracing::TRACE_ALL) const;
     void dumpDisplayProto(LayersTraceProto& layersTraceProto) const;
 
     // Dumps state from HW Composer
     void dumpHwc(std::string& result) const;
-    LayersProto dumpProtoFromMainThread(uint32_t traceFlags = SurfaceTracing::TRACE_ALL)
+    LayersProto dumpProtoFromMainThread(uint32_t traceFlags = LayerTracing::TRACE_ALL)
             EXCLUDES(mStateLock);
     void dumpOffscreenLayers(std::string& result) EXCLUDES(mStateLock);
     void dumpPlannerInfo(const DumpArgs& args, std::string& result) const REQUIRES(mStateLock);
@@ -1185,8 +1117,6 @@
         return doDump(fd, args, asProto);
     }
 
-    void onFrameRateFlexibilityTokenReleased();
-
     static mat4 calculateColorMatrix(float saturation);
 
     void updateColorMatrixLocked();
@@ -1208,8 +1138,7 @@
     /*
      * Misc
      */
-    std::vector<ui::ColorMode> getDisplayColorModes(PhysicalDisplayId displayId)
-            REQUIRES(mStateLock);
+    std::vector<ui::ColorMode> getDisplayColorModes(const DisplayDevice&) REQUIRES(mStateLock);
 
     static int calculateMaxAcquiredBufferCount(Fps refreshRate,
                                                std::chrono::nanoseconds presentLatency);
@@ -1218,9 +1147,14 @@
     void updateInternalDisplayVsyncLocked(const sp<DisplayDevice>& activeDisplay)
             REQUIRES(mStateLock);
 
+    bool isHdrLayer(Layer* layer) const;
+
+    ui::Rotation getPhysicalDisplayOrientation(DisplayId, bool isPrimary) const
+            REQUIRES(mStateLock);
+
     sp<StartPropertySetThread> mStartPropertySetThread;
     surfaceflinger::Factory& mFactory;
-
+    pid_t mPid;
     std::future<void> mRenderEnginePrimeCacheFuture;
 
     // access must be protected by mStateLock
@@ -1229,29 +1163,26 @@
     std::atomic<int32_t> mTransactionFlags = 0;
     std::vector<std::shared_ptr<CountDownLatch>> mTransactionCommittedSignals;
     bool mAnimTransactionPending = false;
+    std::atomic<uint32_t> mUniqueTransactionId = 1;
     SortedVector<sp<Layer>> mLayersPendingRemoval;
-    bool mForceTraversal = false;
 
     // global color transform states
     Daltonizer mDaltonizer;
     float mGlobalSaturationFactor = 1.0f;
     mat4 mClientColorMatrix;
 
-    // Can't be unordered_set because wp<> isn't hashable
-    std::set<wp<IBinder>> mGraphicBufferProducerList;
     size_t mMaxGraphicBufferProducerListSize = ISurfaceComposer::MAX_LAYERS;
     // If there are more GraphicBufferProducers tracked by SurfaceFlinger than
     // this threshold, then begin logging.
     size_t mGraphicBufferProducerListSizeLogThreshold =
             static_cast<size_t>(0.95 * static_cast<double>(MAX_LAYERS));
 
-    void removeGraphicBufferProducerAsync(const wp<IBinder>&);
-
     // protected by mStateLock (but we could use another lock)
     bool mLayersRemoved = false;
     bool mLayersAdded = false;
 
-    std::atomic<bool> mRepaintEverything = false;
+    std::atomic_bool mMustComposite = false;
+    std::atomic_bool mGeometryDirty = false;
 
     // constant members (no synchronization needed for access)
     const nsecs_t mBootTime = systemTime();
@@ -1269,6 +1200,7 @@
     // Used to ensure we omit a callback when HDR layer info listener is newly added but the
     // scene hasn't changed
     bool mAddingHDRLayerInfoListener = false;
+    bool mIgnoreHdrCameraLayers = false;
 
     // Set during transaction application stage to track if the input info or children
     // for a layer has changed.
@@ -1278,12 +1210,11 @@
     bool mSomeDataspaceChanged = false;
     bool mForceTransactionDisplayChange = false;
 
-    bool mGeometryInvalid = false;
     bool mAnimCompositionPending = false;
 
     // Tracks layers that have pending frames which are candidates for being
     // latched.
-    std::unordered_set<sp<Layer>, ISurfaceComposer::SpHash<Layer>> mLayersWithQueuedFrames;
+    std::unordered_set<sp<Layer>, SpHash<Layer>> mLayersWithQueuedFrames;
     // Tracks layers that need to update a display's dirty region.
     std::vector<sp<Layer>> mLayersPendingRefresh;
     std::array<FenceWithFenceTime, 2> mPreviousPresentFences;
@@ -1313,20 +1244,20 @@
         std::optional<DisplayIdGenerator<HalVirtualDisplayId>> hal;
     } mVirtualDisplayIdGenerators;
 
-    // don't use a lock for these, we don't care
-    int mDebugRegion = 0;
-    bool mDebugDisableHWC = false;
-    bool mDebugDisableTransformHint = false;
+    std::atomic_uint mDebugFlashDelay = 0;
+    std::atomic_bool mDebugDisableHWC = false;
+    std::atomic_bool mDebugDisableTransformHint = false;
+    std::atomic<nsecs_t> mDebugInTransaction = 0;
+    std::atomic_bool mForceFullDamage = false;
+
     bool mLayerCachingEnabled = false;
-    volatile nsecs_t mDebugInTransaction = 0;
-    bool mForceFullDamage = false;
     bool mPropagateBackpressureClientComposition = false;
     sp<SurfaceInterceptor> mInterceptor;
 
-    SurfaceTracing mTracing{*this};
-    std::mutex mTracingLock;
-    bool mTracingEnabled = false;
-    bool mTracePostComposition = false;
+    LayerTracing mLayerTracing{*this};
+    bool mLayerTracingEnabled = false;
+
+    std::optional<TransactionTracing> mTransactionTracing;
     std::atomic<bool> mTracingEnabledChanged = false;
 
     const std::shared_ptr<TimeStats> mTimeStats;
@@ -1343,18 +1274,9 @@
 
     TransactionCallbackInvoker mTransactionCallbackInvoker;
 
-    // these are thread safe
-    std::unique_ptr<MessageQueue> mEventQueue;
+    // Thread-safe.
     FrameTracker mAnimFrameTracker;
 
-    // protected by mDestroyedLayerLock;
-    mutable Mutex mDestroyedLayerLock;
-    Vector<Layer const *> mDestroyedLayers;
-
-    nsecs_t mRefreshStartTime = 0;
-
-    std::atomic<bool> mRefreshPending = false;
-
     // We maintain a pool of pre-generated texture names to hand out to avoid
     // layer creation needing to run on the main thread (which it would
     // otherwise need to do to access RenderEngine).
@@ -1366,7 +1288,7 @@
     Condition mTransactionQueueCV;
     std::unordered_map<sp<IBinder>, std::queue<TransactionState>, IListenerHash>
             mPendingTransactionQueues GUARDED_BY(mQueueLock);
-    std::queue<TransactionState> mTransactionQueue GUARDED_BY(mQueueLock);
+    std::deque<TransactionState> mTransactionQueue GUARDED_BY(mQueueLock);
     /*
      * Feature prototyping
      */
@@ -1399,6 +1321,7 @@
     ui::Dataspace mDefaultCompositionDataspace;
     ui::Dataspace mWideColorGamutCompositionDataspace;
     ui::Dataspace mColorSpaceAgnosticDataspace;
+    float mDimmingRatio = -1.f;
 
     SurfaceFlingerBE mBE;
     std::unique_ptr<compositionengine::CompositionEngine> mCompositionEngine;
@@ -1413,7 +1336,7 @@
     /*
      * Scheduler
      */
-    std::unique_ptr<Scheduler> mScheduler;
+    std::unique_ptr<scheduler::Scheduler> mScheduler;
     scheduler::ConnectionHandle mAppConnectionHandle;
     scheduler::ConnectionHandle mSfConnectionHandle;
 
@@ -1442,15 +1365,12 @@
     const float mInternalDisplayDensity;
     const float mEmulatedDisplayDensity;
 
-    sp<os::IInputFlinger> mInputFlinger;
     // Should only be accessed by the main thread.
+    sp<os::IInputFlinger> mInputFlinger;
     InputWindowCommands mInputWindowCommands;
 
     Hwc2::impl::PowerAdvisor mPowerAdvisor;
 
-    // This should only be accessed on the main thread.
-    nsecs_t mFrameStartTime = 0;
-
     void enableRefreshRateOverlay(bool enable) REQUIRES(mStateLock);
 
     // Flag used to set override desired display mode from backdoor
@@ -1462,31 +1382,18 @@
     // be any issues with a raw pointer referencing an invalid object.
     std::unordered_set<Layer*> mOffscreenLayers;
 
-    int mFrameRateFlexibilityTokenCount = 0;
-
-    sp<IBinder> mDebugFrameRateFlexibilityToken;
-
     BufferCountTracker mBufferCountTracker;
 
     std::unordered_map<DisplayId, sp<HdrLayerInfoReporter>> mHdrLayerInfoListeners
             GUARDED_BY(mStateLock);
-    mutable Mutex mCreatedLayersLock;
+    mutable std::mutex mCreatedLayersLock;
     struct LayerCreatedState {
-        LayerCreatedState(const wp<Layer>& layer, const wp<IBinder>& parent,
-                          const wp<Layer> parentLayer, const wp<IBinder>& producer, bool addToRoot)
-              : layer(layer),
-                initialParent(parent),
-                initialParentLayer(parentLayer),
-                initialProducer(producer),
-                addToRoot(addToRoot) {}
+        LayerCreatedState(const wp<Layer>& layer, const wp<Layer> parent, bool addToRoot)
+              : layer(layer), initialParent(parent), addToRoot(addToRoot) {}
         wp<Layer> layer;
         // Indicates the initial parent of the created layer, only used for creating layer in
         // SurfaceFlinger. If nullptr, it may add the created layer into the current root layers.
-        wp<IBinder> initialParent;
-        wp<Layer> initialParentLayer;
-        // Indicates the initial graphic buffer producer of the created layer, only used for
-        // creating layer in SurfaceFlinger.
-        wp<IBinder> initialProducer;
+        wp<Layer> initialParent;
         // Indicates whether the layer getting created should be added at root if there's no parent
         // and has permission ACCESS_SURFACE_FLINGER. If set to false and no parent, the layer will
         // be added offscreen.
@@ -1495,18 +1402,12 @@
 
     // A temporay pool that store the created layers and will be added to current state in main
     // thread.
-    std::unordered_map<BBinder*, std::unique_ptr<LayerCreatedState>> mCreatedLayers;
-    void setLayerCreatedState(const sp<IBinder>& handle, const wp<Layer>& layer,
-                              const wp<IBinder>& parent, const wp<Layer> parentLayer,
-                              const wp<IBinder>& producer, bool addToRoot);
-    auto getLayerCreatedState(const sp<IBinder>& handle);
-    sp<Layer> handleLayerCreatedLocked(const sp<IBinder>& handle) REQUIRES(mStateLock);
+    std::vector<LayerCreatedState> mCreatedLayers GUARDED_BY(mCreatedLayersLock);
+    bool commitCreatedLayers();
+    void handleLayerCreatedLocked(const LayerCreatedState& state) REQUIRES(mStateLock);
 
     std::atomic<ui::Transform::RotationFlags> mActiveDisplayTransformHint;
 
-    void scheduleRegionSamplingThread();
-    void notifyRegionSamplingThread();
-
     bool isRefreshRateOverlayEnabled() const REQUIRES(mStateLock) {
         return std::any_of(mDisplays.begin(), mDisplays.end(),
                            [](std::pair<wp<IBinder>, sp<DisplayDevice>> display) {
@@ -1517,6 +1418,38 @@
     wp<IBinder> mActiveDisplayToken GUARDED_BY(mStateLock);
 
     const sp<WindowInfosListenerInvoker> mWindowInfosListenerInvoker;
+
+    FlagManager mFlagManager;
+
+    // returns the framerate of the layer with the given sequence ID
+    float getLayerFramerate(nsecs_t now, int32_t id) const {
+        return mScheduler->getLayerFramerate(now, id);
+    }
+
+    struct {
+        bool sessionEnabled = false;
+        nsecs_t commitStart;
+        nsecs_t compositeStart;
+        nsecs_t presentEnd;
+    } mPowerHintSessionData GUARDED_BY(SF_MAIN_THREAD);
+
+    nsecs_t mAnimationTransactionTimeout = s2ns(5);
+
+    friend class SurfaceComposerAIDL;
+};
+
+class SurfaceComposerAIDL : public gui::BnSurfaceComposer {
+public:
+    SurfaceComposerAIDL(sp<SurfaceFlinger> sf) { mFlinger = sf; }
+
+    binder::Status captureDisplay(const DisplayCaptureArgs&,
+                                  const sp<IScreenCaptureListener>&) override;
+    binder::Status captureDisplayById(int64_t, const sp<IScreenCaptureListener>&) override;
+    binder::Status captureLayers(const LayerCaptureArgs&,
+                                 const sp<IScreenCaptureListener>&) override;
+
+private:
+    sp<SurfaceFlinger> mFlinger;
 };
 
 } // namespace android
diff --git a/services/surfaceflinger/SurfaceFlingerDefaultFactory.cpp b/services/surfaceflinger/SurfaceFlingerDefaultFactory.cpp
index 89d1c4d..b81b445 100644
--- a/services/surfaceflinger/SurfaceFlingerDefaultFactory.cpp
+++ b/services/surfaceflinger/SurfaceFlingerDefaultFactory.cpp
@@ -38,7 +38,6 @@
 #include "SurfaceInterceptor.h"
 
 #include "DisplayHardware/ComposerHal.h"
-#include "Scheduler/MessageQueue.h"
 #include "Scheduler/Scheduler.h"
 #include "Scheduler/VsyncConfiguration.h"
 #include "Scheduler/VsyncController.h"
@@ -51,10 +50,6 @@
     return std::make_unique<android::impl::HWComposer>(serviceName);
 }
 
-std::unique_ptr<MessageQueue> DefaultFactory::createMessageQueue() {
-    return std::make_unique<android::impl::MessageQueue>();
-}
-
 std::unique_ptr<scheduler::VsyncConfiguration> DefaultFactory::createVsyncConfiguration(
         Fps currentRefreshRate) {
     if (property_get_bool("debug.sf.use_phase_offsets_as_durations", false)) {
@@ -64,12 +59,6 @@
     }
 }
 
-std::unique_ptr<Scheduler> DefaultFactory::createScheduler(
-        const std::shared_ptr<scheduler::RefreshRateConfigs>& refreshRateConfigs,
-        ISchedulerCallback& callback) {
-    return std::make_unique<Scheduler>(std::move(refreshRateConfigs), callback);
-}
-
 sp<SurfaceInterceptor> DefaultFactory::createSurfaceInterceptor() {
     return new android::impl::SurfaceInterceptor();
 }
diff --git a/services/surfaceflinger/SurfaceFlingerDefaultFactory.h b/services/surfaceflinger/SurfaceFlingerDefaultFactory.h
index b8bf2ba..501629d 100644
--- a/services/surfaceflinger/SurfaceFlingerDefaultFactory.h
+++ b/services/surfaceflinger/SurfaceFlingerDefaultFactory.h
@@ -27,11 +27,8 @@
     virtual ~DefaultFactory();
 
     std::unique_ptr<HWComposer> createHWComposer(const std::string& serviceName) override;
-    std::unique_ptr<MessageQueue> createMessageQueue() override;
     std::unique_ptr<scheduler::VsyncConfiguration> createVsyncConfiguration(
             Fps currentRefreshRate) override;
-    std::unique_ptr<Scheduler> createScheduler(
-            const std::shared_ptr<scheduler::RefreshRateConfigs>&, ISchedulerCallback&) override;
     sp<SurfaceInterceptor> createSurfaceInterceptor() override;
     sp<StartPropertySetThread> createStartPropertySetThread(bool timestampPropertyValue) override;
     sp<DisplayDevice> createDisplayDevice(DisplayDeviceCreationArgs&) override;
diff --git a/services/surfaceflinger/SurfaceFlingerFactory.h b/services/surfaceflinger/SurfaceFlingerFactory.h
index 13c95dd..6153e8e 100644
--- a/services/surfaceflinger/SurfaceFlingerFactory.h
+++ b/services/surfaceflinger/SurfaceFlingerFactory.h
@@ -16,41 +16,38 @@
 
 #pragma once
 
-#include "Fps.h"
-
-#include <cutils/compiler.h>
-#include <utils/StrongPointer.h>
-
 #include <cinttypes>
 #include <functional>
 #include <memory>
 #include <string>
 
+#include <cutils/compiler.h>
+#include <utils/StrongPointer.h>
+
+#include <scheduler/Fps.h>
+
 namespace android {
 
 typedef int32_t PixelFormat;
 
 class BufferQueueLayer;
-class BufferStateLayer;
 class BufferLayerConsumer;
-class EffectLayer;
+class BufferStateLayer;
 class ContainerLayer;
 class DisplayDevice;
+class EffectLayer;
 class FrameTracer;
 class GraphicBuffer;
 class HWComposer;
 class IGraphicBufferConsumer;
 class IGraphicBufferProducer;
 class Layer;
-class MessageQueue;
-class Scheduler;
 class StartPropertySetThread;
 class SurfaceFlinger;
 class SurfaceInterceptor;
 class TimeStats;
 
 struct DisplayDeviceCreationArgs;
-struct ISchedulerCallback;
 struct LayerCreationArgs;
 
 namespace compositionengine {
@@ -76,11 +73,8 @@
 class Factory {
 public:
     virtual std::unique_ptr<HWComposer> createHWComposer(const std::string& serviceName) = 0;
-    virtual std::unique_ptr<MessageQueue> createMessageQueue() = 0;
     virtual std::unique_ptr<scheduler::VsyncConfiguration> createVsyncConfiguration(
             Fps currentRefreshRate) = 0;
-    virtual std::unique_ptr<Scheduler> createScheduler(
-            const std::shared_ptr<scheduler::RefreshRateConfigs>&, ISchedulerCallback&) = 0;
     virtual sp<SurfaceInterceptor> createSurfaceInterceptor() = 0;
 
     virtual sp<StartPropertySetThread> createStartPropertySetThread(
diff --git a/services/surfaceflinger/SurfaceFlingerProperties.cpp b/services/surfaceflinger/SurfaceFlingerProperties.cpp
index e15eae8..20fa091 100644
--- a/services/surfaceflinger/SurfaceFlingerProperties.cpp
+++ b/services/surfaceflinger/SurfaceFlingerProperties.cpp
@@ -195,17 +195,6 @@
     return SurfaceFlingerProperties::primary_display_orientation_values::ORIENTATION_0;
 }
 
-bool use_color_management(bool defaultValue) {
-    auto tmpuseColorManagement = SurfaceFlingerProperties::use_color_management();
-    auto tmpHasHDRDisplayVal = has_HDR_display(defaultValue);
-    auto tmpHasWideColorDisplayVal = has_wide_color_display(defaultValue);
-
-    auto tmpuseColorManagementVal = tmpuseColorManagement.has_value() ? *tmpuseColorManagement :
-        defaultValue;
-
-    return tmpuseColorManagementVal || tmpHasHDRDisplayVal || tmpHasWideColorDisplayVal;
-}
-
 int64_t default_composition_dataspace(Dataspace defaultValue) {
     auto temp = SurfaceFlingerProperties::default_composition_dataspace();
     if (temp.has_value()) {
@@ -315,14 +304,6 @@
     return defaultValue;
 }
 
-bool use_frame_rate_api(bool defaultValue) {
-    auto temp = SurfaceFlingerProperties::use_frame_rate_api();
-    if (temp.has_value()) {
-        return *temp;
-    }
-    return defaultValue;
-}
-
 bool enable_sdr_dimming(bool defaultValue) {
     return SurfaceFlingerProperties::enable_sdr_dimming().value_or(defaultValue);
 }
@@ -390,5 +371,9 @@
     return SurfaceFlingerProperties::enable_layer_caching().value_or(defaultValue);
 }
 
+bool ignore_hdr_camera_layers(bool defaultValue) {
+    return SurfaceFlingerProperties::ignore_hdr_camera_layers().value_or(defaultValue);
+}
+
 } // namespace sysprop
 } // namespace android
diff --git a/services/surfaceflinger/SurfaceFlingerProperties.h b/services/surfaceflinger/SurfaceFlingerProperties.h
index 039d316..080feee 100644
--- a/services/surfaceflinger/SurfaceFlingerProperties.h
+++ b/services/surfaceflinger/SurfaceFlingerProperties.h
@@ -59,8 +59,6 @@
 SurfaceFlingerProperties::primary_display_orientation_values primary_display_orientation(
         SurfaceFlingerProperties::primary_display_orientation_values defaultValue);
 
-bool use_color_management(bool defaultValue);
-
 int64_t default_composition_dataspace(
         android::hardware::graphics::common::V1_2::Dataspace defaultValue);
 
@@ -90,8 +88,6 @@
 
 bool support_kernel_idle_timer(bool defaultValue);
 
-bool use_frame_rate_api(bool defaultValue);
-
 int32_t display_update_imminent_timeout_ms(int32_t defaultValue);
 
 android::ui::DisplayPrimaries getDisplayNativePrimaries();
@@ -104,6 +100,8 @@
 
 bool enable_sdr_dimming(bool defaultValue);
 
+bool ignore_hdr_camera_layers(bool defaultValue);
+
 } // namespace sysprop
 } // namespace android
 #endif // SURFACEFLINGERPROPERTIES_H_
diff --git a/services/surfaceflinger/SurfaceInterceptor.cpp b/services/surfaceflinger/SurfaceInterceptor.cpp
index 9be3abe..0782fef 100644
--- a/services/surfaceflinger/SurfaceInterceptor.cpp
+++ b/services/surfaceflinger/SurfaceInterceptor.cpp
@@ -323,11 +323,10 @@
 }
 
 void SurfaceInterceptor::addLayerStackLocked(Transaction* transaction, int32_t layerId,
-        uint32_t layerStack)
-{
+                                             ui::LayerStack layerStack) {
     SurfaceChange* change(createSurfaceChangeLocked(transaction, layerId));
     LayerStackChange* layerStackChange(change->mutable_layer_stack());
-    layerStackChange->set_layer_stack(layerStack);
+    layerStackChange->set_layer_stack(layerStack.id);
 }
 
 void SurfaceInterceptor::addCropLocked(Transaction* transaction, int32_t layerId,
@@ -568,12 +567,11 @@
     }
 }
 
-void SurfaceInterceptor::addDisplayLayerStackLocked(Transaction* transaction,
-        int32_t sequenceId, uint32_t layerStack)
-{
+void SurfaceInterceptor::addDisplayLayerStackLocked(Transaction* transaction, int32_t sequenceId,
+                                                    ui::LayerStack layerStack) {
     DisplayChange* dispChange(createDisplayChangeLocked(transaction, sequenceId));
     LayerStackChange* layerStackChange(dispChange->mutable_layer_stack());
-    layerStackChange->set_layer_stack(layerStack);
+    layerStackChange->set_layer_stack(layerStack.id);
 }
 
 void SurfaceInterceptor::addDisplayFlagsLocked(Transaction* transaction, int32_t sequenceId,
diff --git a/services/surfaceflinger/SurfaceInterceptor.h b/services/surfaceflinger/SurfaceInterceptor.h
index 7b331b9..970c3e5 100644
--- a/services/surfaceflinger/SurfaceInterceptor.h
+++ b/services/surfaceflinger/SurfaceInterceptor.h
@@ -160,7 +160,7 @@
     void addTransparentRegionLocked(Transaction* transaction, int32_t layerId,
             const Region& transRegion);
     void addFlagsLocked(Transaction* transaction, int32_t layerId, uint8_t flags, uint8_t mask);
-    void addLayerStackLocked(Transaction* transaction, int32_t layerId, uint32_t layerStack);
+    void addLayerStackLocked(Transaction* transaction, int32_t layerId, ui::LayerStack);
     void addCropLocked(Transaction* transaction, int32_t layerId, const Rect& rect);
     void addCornerRadiusLocked(Transaction* transaction, int32_t layerId, float cornerRadius);
     void addBackgroundBlurRadiusLocked(Transaction* transaction, int32_t layerId,
@@ -183,8 +183,7 @@
     DisplayChange* createDisplayChangeLocked(Transaction* transaction, int32_t sequenceId);
     void addDisplaySurfaceLocked(Transaction* transaction, int32_t sequenceId,
             const sp<const IGraphicBufferProducer>& surface);
-    void addDisplayLayerStackLocked(Transaction* transaction, int32_t sequenceId,
-            uint32_t layerStack);
+    void addDisplayLayerStackLocked(Transaction* transaction, int32_t sequenceId, ui::LayerStack);
     void addDisplayFlagsLocked(Transaction* transaction, int32_t sequenceId, uint32_t flags);
     void addDisplaySizeLocked(Transaction* transaction, int32_t sequenceId, uint32_t w,
             uint32_t h);
diff --git a/services/surfaceflinger/SurfaceTracing.cpp b/services/surfaceflinger/SurfaceTracing.cpp
deleted file mode 100644
index 5963737..0000000
--- a/services/surfaceflinger/SurfaceTracing.cpp
+++ /dev/null
@@ -1,264 +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.
- */
-
-#undef LOG_TAG
-#define LOG_TAG "SurfaceTracing"
-#define ATRACE_TAG ATRACE_TAG_GRAPHICS
-
-#include "SurfaceTracing.h"
-#include <SurfaceFlinger.h>
-
-#include <android-base/file.h>
-#include <android-base/stringprintf.h>
-#include <log/log.h>
-#include <utils/SystemClock.h>
-#include <utils/Trace.h>
-
-namespace android {
-
-SurfaceTracing::SurfaceTracing(SurfaceFlinger& flinger) : mFlinger(flinger) {}
-
-bool SurfaceTracing::enable() {
-    std::scoped_lock lock(mTraceLock);
-    if (mEnabled) {
-        return false;
-    }
-
-    if (flagIsSet(TRACE_SYNC)) {
-        runner = std::make_unique<SurfaceTracing::Runner>(mFlinger, mConfig);
-    } else {
-        runner = std::make_unique<SurfaceTracing::AsyncRunner>(mFlinger, mConfig,
-                                                               mFlinger.mTracingLock);
-    }
-    mEnabled = true;
-    return true;
-}
-
-bool SurfaceTracing::disable() {
-    std::scoped_lock lock(mTraceLock);
-    if (!mEnabled) {
-        return false;
-    }
-    mEnabled = false;
-    runner->stop();
-    return true;
-}
-
-bool SurfaceTracing::isEnabled() const {
-    std::scoped_lock lock(mTraceLock);
-    return mEnabled;
-}
-
-status_t SurfaceTracing::writeToFile() {
-    std::scoped_lock lock(mTraceLock);
-    if (!mEnabled) {
-        return STATUS_OK;
-    }
-    return runner->writeToFile();
-}
-
-void SurfaceTracing::notify(const char* where) {
-    std::scoped_lock lock(mTraceLock);
-    if (mEnabled) {
-        runner->notify(where);
-    }
-}
-
-void SurfaceTracing::notifyLocked(const char* where) {
-    std::scoped_lock lock(mTraceLock);
-    if (mEnabled) {
-        runner->notifyLocked(where);
-    }
-}
-
-void SurfaceTracing::dump(std::string& result) const {
-    std::scoped_lock lock(mTraceLock);
-    base::StringAppendF(&result, "Tracing state: %s\n", mEnabled ? "enabled" : "disabled");
-    if (mEnabled) {
-        runner->dump(result);
-    }
-}
-
-void SurfaceTracing::LayersTraceBuffer::reset(size_t newSize) {
-    // use the swap trick to make sure memory is released
-    std::queue<LayersTraceProto>().swap(mStorage);
-    mSizeInBytes = newSize;
-    mUsedInBytes = 0U;
-}
-
-void SurfaceTracing::LayersTraceBuffer::emplace(LayersTraceProto&& proto) {
-    size_t protoSize = static_cast<size_t>(proto.ByteSize());
-    while (mUsedInBytes + protoSize > mSizeInBytes) {
-        if (mStorage.empty()) {
-            return;
-        }
-        mUsedInBytes -= static_cast<size_t>(mStorage.front().ByteSize());
-        mStorage.pop();
-    }
-    mUsedInBytes += protoSize;
-    mStorage.emplace();
-    mStorage.back().Swap(&proto);
-}
-
-void SurfaceTracing::LayersTraceBuffer::flush(LayersTraceFileProto* fileProto) {
-    fileProto->mutable_entry()->Reserve(static_cast<int>(mStorage.size()));
-
-    while (!mStorage.empty()) {
-        auto entry = fileProto->add_entry();
-        entry->Swap(&mStorage.front());
-        mStorage.pop();
-    }
-}
-
-SurfaceTracing::Runner::Runner(SurfaceFlinger& flinger, SurfaceTracing::Config& config)
-      : mFlinger(flinger), mConfig(config) {
-    mBuffer.setSize(mConfig.bufferSize);
-}
-
-void SurfaceTracing::Runner::notify(const char* where) {
-    LayersTraceProto entry = traceLayers(where);
-    mBuffer.emplace(std::move(entry));
-}
-
-status_t SurfaceTracing::Runner::stop() {
-    return writeToFile();
-}
-
-LayersTraceFileProto SurfaceTracing::createLayersTraceFileProto() {
-    LayersTraceFileProto fileProto;
-    fileProto.set_magic_number(uint64_t(LayersTraceFileProto_MagicNumber_MAGIC_NUMBER_H) << 32 |
-                               LayersTraceFileProto_MagicNumber_MAGIC_NUMBER_L);
-    return fileProto;
-}
-
-status_t SurfaceTracing::Runner::writeToFile() {
-    ATRACE_CALL();
-
-    LayersTraceFileProto fileProto = createLayersTraceFileProto();
-    std::string output;
-
-    mBuffer.flush(&fileProto);
-    mBuffer.reset(mConfig.bufferSize);
-
-    if (!fileProto.SerializeToString(&output)) {
-        ALOGE("Could not save the proto file! Permission denied");
-        return PERMISSION_DENIED;
-    }
-
-    // -rw-r--r--
-    const mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
-    if (!android::base::WriteStringToFile(output, DEFAULT_FILE_NAME, mode, getuid(), getgid(),
-                                          true)) {
-        ALOGE("Could not save the proto file! There are missing fields");
-        return PERMISSION_DENIED;
-    }
-
-    return NO_ERROR;
-}
-
-LayersTraceProto SurfaceTracing::Runner::traceLayers(const char* where) {
-    ATRACE_CALL();
-
-    LayersTraceProto entry;
-    entry.set_elapsed_realtime_nanos(elapsedRealtimeNano());
-    entry.set_where(where);
-    LayersProto layers(mFlinger.dumpDrawingStateProto(mConfig.flags));
-
-    if (flagIsSet(SurfaceTracing::TRACE_EXTRA)) {
-        mFlinger.dumpOffscreenLayersProto(layers);
-    }
-    entry.mutable_layers()->Swap(&layers);
-
-    if (flagIsSet(SurfaceTracing::TRACE_HWC)) {
-        std::string hwcDump;
-        mFlinger.dumpHwc(hwcDump);
-        entry.set_hwc_blob(hwcDump);
-    }
-    if (!flagIsSet(SurfaceTracing::TRACE_COMPOSITION)) {
-        entry.set_excludes_composition_state(true);
-    }
-    entry.set_missed_entries(mMissedTraceEntries);
-    mFlinger.dumpDisplayProto(entry);
-    return entry;
-}
-
-void SurfaceTracing::Runner::dump(std::string& result) const {
-    base::StringAppendF(&result, "  number of entries: %zu (%.2fMB / %.2fMB)\n",
-                        mBuffer.frameCount(), float(mBuffer.used()) / float(1_MB),
-                        float(mBuffer.size()) / float(1_MB));
-}
-
-SurfaceTracing::AsyncRunner::AsyncRunner(SurfaceFlinger& flinger, SurfaceTracing::Config& config,
-                                         std::mutex& sfLock)
-      : SurfaceTracing::Runner(flinger, config), mSfLock(sfLock) {
-    mEnabled = true;
-    mThread = std::thread(&AsyncRunner::loop, this);
-}
-
-void SurfaceTracing::AsyncRunner::loop() {
-    while (mEnabled) {
-        LayersTraceProto entry;
-        bool entryAdded = traceWhenNotified(&entry);
-        if (entryAdded) {
-            mBuffer.emplace(std::move(entry));
-        }
-        if (mWriteToFile) {
-            Runner::writeToFile();
-            mWriteToFile = false;
-        }
-    }
-}
-
-bool SurfaceTracing::AsyncRunner::traceWhenNotified(LayersTraceProto* outProto) {
-    std::unique_lock<std::mutex> lock(mSfLock);
-    mCanStartTrace.wait(lock);
-    if (!mAddEntry) {
-        return false;
-    }
-    *outProto = traceLayers(mWhere);
-    mAddEntry = false;
-    mMissedTraceEntries = 0;
-    return true;
-}
-
-void SurfaceTracing::AsyncRunner::notify(const char* where) {
-    std::scoped_lock lock(mSfLock);
-    notifyLocked(where);
-}
-
-void SurfaceTracing::AsyncRunner::notifyLocked(const char* where) {
-    mWhere = where;
-    if (mAddEntry) {
-        mMissedTraceEntries++;
-    }
-    mAddEntry = true;
-    mCanStartTrace.notify_one();
-}
-
-status_t SurfaceTracing::AsyncRunner::writeToFile() {
-    mWriteToFile = true;
-    mCanStartTrace.notify_one();
-    return STATUS_OK;
-}
-
-status_t SurfaceTracing::AsyncRunner::stop() {
-    mEnabled = false;
-    mCanStartTrace.notify_one();
-    mThread.join();
-    return Runner::writeToFile();
-}
-
-} // namespace android
diff --git a/services/surfaceflinger/SurfaceTracing.h b/services/surfaceflinger/SurfaceTracing.h
deleted file mode 100644
index cea1a33..0000000
--- a/services/surfaceflinger/SurfaceTracing.h
+++ /dev/null
@@ -1,164 +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.
- */
-
-#pragma once
-
-#include <android-base/thread_annotations.h>
-#include <layerproto/LayerProtoHeader.h>
-#include <utils/Errors.h>
-#include <utils/StrongPointer.h>
-
-#include <condition_variable>
-#include <memory>
-#include <mutex>
-#include <queue>
-#include <thread>
-
-using namespace android::surfaceflinger;
-
-namespace android {
-
-class SurfaceFlinger;
-constexpr auto operator""_MB(unsigned long long const num) {
-    return num * 1024 * 1024;
-}
-/*
- * SurfaceTracing records layer states during surface flinging. Manages tracing state and
- * configuration.
- */
-class SurfaceTracing {
-public:
-    SurfaceTracing(SurfaceFlinger& flinger);
-    bool enable();
-    bool disable();
-    status_t writeToFile();
-    bool isEnabled() const;
-    /*
-     * Adds a trace entry, must be called from the drawing thread or while holding the
-     * SurfaceFlinger tracing lock.
-     */
-    void notify(const char* where);
-    /*
-     * Adds a trace entry, called while holding the SurfaceFlinger tracing lock.
-     */
-    void notifyLocked(const char* where) /* REQUIRES(mSfLock) */;
-
-    void setBufferSize(size_t bufferSizeInBytes) { mConfig.bufferSize = bufferSizeInBytes; }
-    void dump(std::string& result) const;
-
-    enum : uint32_t {
-        TRACE_CRITICAL = 1 << 0,
-        TRACE_INPUT = 1 << 1,
-        TRACE_COMPOSITION = 1 << 2,
-        TRACE_EXTRA = 1 << 3,
-        TRACE_HWC = 1 << 4,
-        // Add non-geometry composition changes to the trace.
-        TRACE_BUFFERS = 1 << 5,
-        // Add entries from the drawing thread post composition.
-        TRACE_SYNC = 1 << 6,
-        TRACE_ALL = TRACE_CRITICAL | TRACE_INPUT | TRACE_COMPOSITION | TRACE_EXTRA,
-    };
-    void setTraceFlags(uint32_t flags) { mConfig.flags = flags; }
-    bool flagIsSet(uint32_t flags) { return (mConfig.flags & flags) == flags; }
-    static LayersTraceFileProto createLayersTraceFileProto();
-
-private:
-    class Runner;
-    static constexpr auto DEFAULT_BUFFER_SIZE = 5_MB;
-    static constexpr auto DEFAULT_FILE_NAME = "/data/misc/wmtrace/layers_trace.winscope";
-
-    SurfaceFlinger& mFlinger;
-    mutable std::mutex mTraceLock;
-    bool mEnabled GUARDED_BY(mTraceLock) = false;
-    std::unique_ptr<Runner> runner GUARDED_BY(mTraceLock);
-
-    struct Config {
-        uint32_t flags = TRACE_CRITICAL | TRACE_INPUT | TRACE_SYNC;
-        size_t bufferSize = DEFAULT_BUFFER_SIZE;
-    } mConfig;
-
-    /*
-     * ring buffer.
-     */
-    class LayersTraceBuffer {
-    public:
-        size_t size() const { return mSizeInBytes; }
-        size_t used() const { return mUsedInBytes; }
-        size_t frameCount() const { return mStorage.size(); }
-
-        void setSize(size_t newSize) { mSizeInBytes = newSize; }
-        void reset(size_t newSize);
-        void emplace(LayersTraceProto&& proto);
-        void flush(LayersTraceFileProto* fileProto);
-
-    private:
-        size_t mUsedInBytes = 0U;
-        size_t mSizeInBytes = DEFAULT_BUFFER_SIZE;
-        std::queue<LayersTraceProto> mStorage;
-    };
-
-    /*
-     * Implements a synchronous way of adding trace entries. This must be called
-     * from the drawing thread.
-     */
-    class Runner {
-    public:
-        Runner(SurfaceFlinger& flinger, SurfaceTracing::Config& config);
-        virtual ~Runner() = default;
-        virtual status_t stop();
-        virtual status_t writeToFile();
-        virtual void notify(const char* where);
-        /* Cannot be called with a synchronous runner. */
-        virtual void notifyLocked(const char* /* where */) {}
-        void dump(std::string& result) const;
-
-    protected:
-        bool flagIsSet(uint32_t flags) { return (mConfig.flags & flags) == flags; }
-        SurfaceFlinger& mFlinger;
-        SurfaceTracing::Config mConfig;
-        SurfaceTracing::LayersTraceBuffer mBuffer;
-        uint32_t mMissedTraceEntries = 0;
-        LayersTraceProto traceLayers(const char* where);
-    };
-
-    /*
-     * Implements asynchronous way to add trace entries called from a separate thread while holding
-     * the SurfaceFlinger tracing lock. Trace entries may be missed if the tracing thread is not
-     * scheduled in time.
-     */
-    class AsyncRunner : public Runner {
-    public:
-        AsyncRunner(SurfaceFlinger& flinger, SurfaceTracing::Config& config, std::mutex& sfLock);
-        virtual ~AsyncRunner() = default;
-        status_t stop() override;
-        status_t writeToFile() override;
-        void notify(const char* where) override;
-        void notifyLocked(const char* where);
-
-    private:
-        std::mutex& mSfLock;
-        std::condition_variable mCanStartTrace;
-        std::thread mThread;
-        const char* mWhere = "";
-        bool mWriteToFile = false;
-        bool mEnabled = false;
-        bool mAddEntry = false;
-        void loop();
-        bool traceWhenNotified(LayersTraceProto* outProto);
-    };
-};
-
-} // namespace android
diff --git a/services/surfaceflinger/TimeStats/Android.bp b/services/surfaceflinger/TimeStats/Android.bp
index bcc3e4e..4686eed 100644
--- a/services/surfaceflinger/TimeStats/Android.bp
+++ b/services/surfaceflinger/TimeStats/Android.bp
@@ -12,6 +12,9 @@
     srcs: [
         "TimeStats.cpp",
     ],
+    header_libs: [
+        "libscheduler_headers",
+    ],
     shared_libs: [
         "android.hardware.graphics.composer@2.4",
         "libbase",
@@ -24,6 +27,9 @@
         "libutils",
     ],
     export_include_dirs: ["."],
+    export_header_lib_headers: [
+        "libscheduler_headers",
+    ],
     export_shared_lib_headers: [
         "libtimestats_proto",
     ],
diff --git a/services/surfaceflinger/TimeStats/TimeStats.cpp b/services/surfaceflinger/TimeStats/TimeStats.cpp
index 7c1f21f..b1a2bda 100644
--- a/services/surfaceflinger/TimeStats/TimeStats.cpp
+++ b/services/surfaceflinger/TimeStats/TimeStats.cpp
@@ -14,7 +14,6 @@
  * limitations under the License.
  */
 
-#include <unordered_map>
 #undef LOG_TAG
 #define LOG_TAG "TimeStats"
 #define ATRACE_TAG ATRACE_TAG_GRAPHICS
@@ -28,6 +27,7 @@
 
 #include <algorithm>
 #include <chrono>
+#include <unordered_map>
 
 #include "TimeStats.h"
 #include "timestatsproto/TimeStatsHelper.h"
@@ -58,15 +58,15 @@
     return histogramProto;
 }
 
-SurfaceflingerStatsLayerInfo_GameMode gameModeToProto(int32_t gameMode) {
+SurfaceflingerStatsLayerInfo_GameMode gameModeToProto(GameMode gameMode) {
     switch (gameMode) {
-        case TimeStatsHelper::GameModeUnsupported:
+        case GameMode::Unsupported:
             return SurfaceflingerStatsLayerInfo::GAME_MODE_UNSUPPORTED;
-        case TimeStatsHelper::GameModeStandard:
+        case GameMode::Standard:
             return SurfaceflingerStatsLayerInfo::GAME_MODE_STANDARD;
-        case TimeStatsHelper::GameModePerformance:
+        case GameMode::Performance:
             return SurfaceflingerStatsLayerInfo::GAME_MODE_PERFORMANCE;
-        case TimeStatsHelper::GameModeBattery:
+        case GameMode::Battery:
             return SurfaceflingerStatsLayerInfo::GAME_MODE_BATTERY;
         default:
             return SurfaceflingerStatsLayerInfo::GAME_MODE_UNSPECIFIED;
@@ -454,7 +454,7 @@
 void TimeStats::flushAvailableRecordsToStatsLocked(int32_t layerId, Fps displayRefreshRate,
                                                    std::optional<Fps> renderRate,
                                                    SetFrameRateVote frameRateVote,
-                                                   int32_t gameMode) {
+                                                   GameMode gameMode) {
     ATRACE_CALL();
     ALOGV("[%d]-flushAvailableRecordsToStatsLocked", layerId);
 
@@ -554,7 +554,7 @@
 }
 
 bool TimeStats::canAddNewAggregatedStats(uid_t uid, const std::string& layerName,
-                                         int32_t gameMode) {
+                                         GameMode gameMode) {
     uint32_t layerRecords = 0;
     for (const auto& record : mTimeStats.stats) {
         if (record.second.stats.count({uid, layerName, gameMode}) > 0) {
@@ -564,11 +564,11 @@
         layerRecords += record.second.stats.size();
     }
 
-    return mTimeStats.stats.size() < MAX_NUM_LAYER_STATS;
+    return layerRecords < MAX_NUM_LAYER_STATS;
 }
 
 void TimeStats::setPostTime(int32_t layerId, uint64_t frameNumber, const std::string& layerName,
-                            uid_t uid, nsecs_t postTime, int32_t gameMode) {
+                            uid_t uid, nsecs_t postTime, GameMode gameMode) {
     if (!mEnabled.load()) return;
 
     ATRACE_CALL();
@@ -718,7 +718,7 @@
 
 void TimeStats::setPresentTime(int32_t layerId, uint64_t frameNumber, nsecs_t presentTime,
                                Fps displayRefreshRate, std::optional<Fps> renderRate,
-                               SetFrameRateVote frameRateVote, int32_t gameMode) {
+                               SetFrameRateVote frameRateVote, GameMode gameMode) {
     if (!mEnabled.load()) return;
 
     ATRACE_CALL();
@@ -744,7 +744,7 @@
 void TimeStats::setPresentFence(int32_t layerId, uint64_t frameNumber,
                                 const std::shared_ptr<FenceTime>& presentFence,
                                 Fps displayRefreshRate, std::optional<Fps> renderRate,
-                                SetFrameRateVote frameRateVote, int32_t gameMode) {
+                                SetFrameRateVote frameRateVote, GameMode gameMode) {
     if (!mEnabled.load()) return;
 
     ATRACE_CALL();
@@ -823,7 +823,7 @@
     // the first jank record is not dropped.
 
     static const std::string kDefaultLayerName = "none";
-    static constexpr int32_t kDefaultGameMode = TimeStatsHelper::GameModeUnsupported;
+    constexpr GameMode kDefaultGameMode = GameMode::Unsupported;
 
     const int32_t refreshRateBucket =
             clampToNearestBucket(info.refreshRate, REFRESH_RATE_BUCKET_WIDTH);
diff --git a/services/surfaceflinger/TimeStats/TimeStats.h b/services/surfaceflinger/TimeStats/TimeStats.h
index 9e70684..77c7973 100644
--- a/services/surfaceflinger/TimeStats/TimeStats.h
+++ b/services/surfaceflinger/TimeStats/TimeStats.h
@@ -17,21 +17,22 @@
 #pragma once
 
 #include <cstdint>
+#include <deque>
+#include <mutex>
+#include <optional>
+#include <unordered_map>
+#include <variant>
 
-#include <../Fps.h>
 #include <android/hardware/graphics/composer/2.4/IComposerClient.h>
 #include <gui/JankInfo.h>
+#include <gui/LayerMetadata.h>
 #include <timestatsproto/TimeStatsHelper.h>
 #include <timestatsproto/TimeStatsProtoHeader.h>
 #include <ui/FenceTime.h>
 #include <utils/String16.h>
 #include <utils/Vector.h>
 
-#include <deque>
-#include <mutex>
-#include <optional>
-#include <unordered_map>
-#include <variant>
+#include <scheduler/Fps.h>
 
 using namespace android::surfaceflinger;
 
@@ -79,7 +80,7 @@
                                             const std::shared_ptr<FenceTime>& readyFence) = 0;
 
     virtual void setPostTime(int32_t layerId, uint64_t frameNumber, const std::string& layerName,
-                             uid_t uid, nsecs_t postTime, int32_t gameMode) = 0;
+                             uid_t uid, nsecs_t postTime, GameMode) = 0;
     virtual void setLatchTime(int32_t layerId, uint64_t frameNumber, nsecs_t latchTime) = 0;
     // Reasons why latching a particular buffer may be skipped
     enum class LatchSkipReason {
@@ -101,11 +102,11 @@
     // rendering path, as they flush prior fences if those fences have fired.
     virtual void setPresentTime(int32_t layerId, uint64_t frameNumber, nsecs_t presentTime,
                                 Fps displayRefreshRate, std::optional<Fps> renderRate,
-                                SetFrameRateVote frameRateVote, int32_t gameMode) = 0;
+                                SetFrameRateVote frameRateVote, GameMode) = 0;
     virtual void setPresentFence(int32_t layerId, uint64_t frameNumber,
                                  const std::shared_ptr<FenceTime>& presentFence,
                                  Fps displayRefreshRate, std::optional<Fps> renderRate,
-                                 SetFrameRateVote frameRateVote, int32_t gameMode) = 0;
+                                 SetFrameRateVote frameRateVote, GameMode) = 0;
 
     // Increments janky frames, blamed to the provided {refreshRate, renderRate, uid, layerName}
     // key, with JankMetadata as supplementary reasons for the jank. Because FrameTimeline is the
@@ -123,19 +124,21 @@
         std::optional<Fps> renderRate;
         uid_t uid = 0;
         std::string layerName;
-        int32_t gameMode = 0;
+        GameMode gameMode = GameMode::Unsupported;
         int32_t reasons = 0;
         nsecs_t displayDeadlineDelta = 0;
         nsecs_t displayPresentJitter = 0;
         nsecs_t appDeadlineDelta = 0;
 
+        static bool isOptApproxEqual(std::optional<Fps> lhs, std::optional<Fps> rhs) {
+            return (!lhs && !rhs) || (lhs && rhs && isApproxEqual(*lhs, *rhs));
+        }
+
         bool operator==(const JankyFramesInfo& o) const {
-            return Fps::EqualsInBuckets{}(refreshRate, o.refreshRate) &&
-                    ((renderRate == std::nullopt && o.renderRate == std::nullopt) ||
-                     (renderRate != std::nullopt && o.renderRate != std::nullopt &&
-                      Fps::EqualsInBuckets{}(*renderRate, *o.renderRate))) &&
-                    uid == o.uid && layerName == o.layerName && gameMode == o.gameMode &&
-                    reasons == o.reasons && displayDeadlineDelta == o.displayDeadlineDelta &&
+            return isApproxEqual(refreshRate, o.refreshRate) &&
+                    isOptApproxEqual(renderRate, o.renderRate) && uid == o.uid &&
+                    layerName == o.layerName && gameMode == o.gameMode && reasons == o.reasons &&
+                    displayDeadlineDelta == o.displayDeadlineDelta &&
                     displayPresentJitter == o.displayPresentJitter &&
                     appDeadlineDelta == o.appDeadlineDelta;
         }
@@ -192,7 +195,7 @@
     struct LayerRecord {
         uid_t uid;
         std::string layerName;
-        int32_t gameMode = 0;
+        GameMode gameMode = GameMode::Unsupported;
         // This is the index in timeRecords, at which the timestamps for that
         // specific frame are still not fully received. This is not waiting for
         // fences to signal, but rather waiting to receive those fences/timestamps.
@@ -245,7 +248,7 @@
                                     const std::shared_ptr<FenceTime>& readyFence) override;
 
     void setPostTime(int32_t layerId, uint64_t frameNumber, const std::string& layerName, uid_t uid,
-                     nsecs_t postTime, int32_t gameMode) override;
+                     nsecs_t postTime, GameMode) override;
     void setLatchTime(int32_t layerId, uint64_t frameNumber, nsecs_t latchTime) override;
     void incrementLatchSkipped(int32_t layerId, LatchSkipReason reason) override;
     void incrementBadDesiredPresent(int32_t layerId) override;
@@ -254,12 +257,11 @@
     void setAcquireFence(int32_t layerId, uint64_t frameNumber,
                          const std::shared_ptr<FenceTime>& acquireFence) override;
     void setPresentTime(int32_t layerId, uint64_t frameNumber, nsecs_t presentTime,
-                        Fps displayRefreshRate, std::optional<Fps> renderRate,
-                        SetFrameRateVote frameRateVote, int32_t gameMode) override;
+                        Fps displayRefreshRate, std::optional<Fps> renderRate, SetFrameRateVote,
+                        GameMode) override;
     void setPresentFence(int32_t layerId, uint64_t frameNumber,
                          const std::shared_ptr<FenceTime>& presentFence, Fps displayRefreshRate,
-                         std::optional<Fps> renderRate, SetFrameRateVote frameRateVote,
-                         int32_t gameMode) override;
+                         std::optional<Fps> renderRate, SetFrameRateVote, GameMode) override;
 
     void incrementJankyFrames(const JankyFramesInfo& info) override;
     // Clean up the layer record
@@ -280,11 +282,11 @@
     bool populateLayerAtom(std::string* pulledData);
     bool recordReadyLocked(int32_t layerId, TimeRecord* timeRecord);
     void flushAvailableRecordsToStatsLocked(int32_t layerId, Fps displayRefreshRate,
-                                            std::optional<Fps> renderRate,
-                                            SetFrameRateVote frameRateVote, int32_t gameMode);
+                                            std::optional<Fps> renderRate, SetFrameRateVote,
+                                            GameMode);
     void flushPowerTimeLocked();
     void flushAvailableGlobalRecordsToStatsLocked();
-    bool canAddNewAggregatedStats(uid_t uid, const std::string& layerName, int32_t gameMode);
+    bool canAddNewAggregatedStats(uid_t uid, const std::string& layerName, GameMode);
 
     void enable();
     void disable();
diff --git a/services/surfaceflinger/TimeStats/timestatsproto/TimeStatsHelper.cpp b/services/surfaceflinger/TimeStats/timestatsproto/TimeStatsHelper.cpp
index ffb2f09..69afa2a 100644
--- a/services/surfaceflinger/TimeStats/timestatsproto/TimeStatsHelper.cpp
+++ b/services/surfaceflinger/TimeStats/timestatsproto/TimeStatsHelper.cpp
@@ -16,9 +16,10 @@
 #include "timestatsproto/TimeStatsHelper.h"
 
 #include <android-base/stringprintf.h>
-#include <inttypes.h>
+#include <ftl/enum.h>
 
 #include <array>
+#include <cinttypes>
 
 #define HISTOGRAM_SIZE 85
 
@@ -91,51 +92,15 @@
     return result;
 }
 
-std::string TimeStatsHelper::SetFrameRateVote::toString(FrameRateCompatibility compatibility) {
-    switch (compatibility) {
-        case FrameRateCompatibility::Undefined:
-            return "Undefined";
-        case FrameRateCompatibility::Default:
-            return "Default";
-        case FrameRateCompatibility::ExactOrMultiple:
-            return "ExactOrMultiple";
-    }
-}
-
-std::string TimeStatsHelper::SetFrameRateVote::toString(Seamlessness seamlessness) {
-    switch (seamlessness) {
-        case Seamlessness::Undefined:
-            return "Undefined";
-        case Seamlessness::ShouldBeSeamless:
-            return "ShouldBeSeamless";
-        case Seamlessness::NotRequired:
-            return "NotRequired";
-    }
-}
-
 std::string TimeStatsHelper::SetFrameRateVote::toString() const {
     std::string result;
     StringAppendF(&result, "frameRate = %.2f\n", frameRate);
     StringAppendF(&result, "frameRateCompatibility = %s\n",
-                  toString(frameRateCompatibility).c_str());
-    StringAppendF(&result, "seamlessness = %s\n", toString(seamlessness).c_str());
+                  ftl::enum_string(frameRateCompatibility).c_str());
+    StringAppendF(&result, "seamlessness = %s\n", ftl::enum_string(seamlessness).c_str());
     return result;
 }
 
-std::string TimeStatsHelper::TimeStatsLayer::toString(int32_t gameMode) const {
-    switch (gameMode) {
-        case TimeStatsHelper::GameModeUnsupported:
-            return "GameModeUnsupported";
-        case TimeStatsHelper::GameModeStandard:
-            return "GameModeStandard";
-        case TimeStatsHelper::GameModePerformance:
-            return "GameModePerformance";
-        case TimeStatsHelper::GameModeBattery:
-            return "GameModeBattery";
-        default:
-            return "GameModeUnspecified";
-    }
-}
 std::string TimeStatsHelper::TimeStatsLayer::toString() const {
     std::string result = "\n";
     StringAppendF(&result, "displayRefreshRate = %d fps\n", displayRefreshRateBucket);
@@ -143,7 +108,7 @@
     StringAppendF(&result, "uid = %d\n", uid);
     StringAppendF(&result, "layerName = %s\n", layerName.c_str());
     StringAppendF(&result, "packageName = %s\n", packageName.c_str());
-    StringAppendF(&result, "gameMode = %s\n", toString(gameMode).c_str());
+    StringAppendF(&result, "gameMode = %s\n", ftl::enum_string(gameMode).c_str());
     StringAppendF(&result, "totalFrames = %d\n", totalFrames);
     StringAppendF(&result, "droppedFrames = %d\n", droppedFrames);
     StringAppendF(&result, "lateAcquireFrames = %d\n", lateAcquireFrames);
diff --git a/services/surfaceflinger/TimeStats/timestatsproto/include/timestatsproto/TimeStatsHelper.h b/services/surfaceflinger/TimeStats/timestatsproto/include/timestatsproto/TimeStatsHelper.h
index 2afff8d..438561c 100644
--- a/services/surfaceflinger/TimeStats/timestatsproto/include/timestatsproto/TimeStatsHelper.h
+++ b/services/surfaceflinger/TimeStats/timestatsproto/include/timestatsproto/TimeStatsHelper.h
@@ -15,6 +15,7 @@
  */
 #pragma once
 
+#include <gui/LayerMetadata.h>
 #include <timestatsproto/TimeStatsProtoHeader.h>
 #include <utils/Timers.h>
 
@@ -63,6 +64,8 @@
             Undefined = 0,
             Default = 1,
             ExactOrMultiple = 2,
+
+            ftl_last = ExactOrMultiple
         } frameRateCompatibility = FrameRateCompatibility::Undefined;
 
         // Needs to be in sync with atoms.proto
@@ -70,25 +73,13 @@
             Undefined = 0,
             ShouldBeSeamless = 1,
             NotRequired = 2,
+
+            ftl_last = NotRequired
         } seamlessness = Seamlessness::Undefined;
 
-        static std::string toString(FrameRateCompatibility);
-        static std::string toString(Seamlessness);
         std::string toString() const;
     };
 
-    /**
-     * GameMode of the layer. GameModes are set by SysUI through WMShell.
-     * Actual game mode definitions are managed by GameManager.java
-     * The values defined here should always be in sync with the ones in GameManager.
-     */
-    enum GameMode {
-        GameModeUnsupported = 0,
-        GameModeStandard = 1,
-        GameModePerformance = 2,
-        GameModeBattery = 3,
-    };
-
     class TimeStatsLayer {
     public:
         uid_t uid;
@@ -96,7 +87,7 @@
         std::string packageName;
         int32_t displayRefreshRateBucket = 0;
         int32_t renderRateBucket = 0;
-        int32_t gameMode = 0;
+        GameMode gameMode = GameMode::Unsupported;
         int32_t totalFrames = 0;
         int32_t droppedFrames = 0;
         int32_t lateAcquireFrames = 0;
@@ -106,7 +97,6 @@
         std::unordered_map<std::string, Histogram> deltas;
 
         std::string toString() const;
-        std::string toString(int32_t gameMode) const;
         SFTimeStatsLayerProto toProto() const;
     };
 
@@ -137,13 +127,14 @@
     struct LayerStatsKey {
         uid_t uid = 0;
         std::string layerName;
-        int32_t gameMode = 0;
+        GameMode gameMode = GameMode::Unsupported;
 
         struct Hasher {
             size_t operator()(const LayerStatsKey& key) const {
                 size_t uidHash = std::hash<uid_t>{}(key.uid);
                 size_t layerNameHash = std::hash<std::string>{}(key.layerName);
-                size_t gameModeHash = std::hash<int32_t>{}(key.gameMode);
+                using T = std::underlying_type_t<GameMode>;
+                size_t gameModeHash = std::hash<T>{}(static_cast<T>(key.gameMode));
                 return HashCombine(uidHash, HashCombine(layerNameHash, gameModeHash));
             }
         };
diff --git a/services/surfaceflinger/TracedOrdinal.h b/services/surfaceflinger/TracedOrdinal.h
index eee4bec..558b3be 100644
--- a/services/surfaceflinger/TracedOrdinal.h
+++ b/services/surfaceflinger/TracedOrdinal.h
@@ -15,11 +15,14 @@
  */
 
 #pragma once
-#include <android-base/stringprintf.h>
+
+#include <chrono>
+#include <cmath>
+#include <functional>
+#include <string>
+
 #include <cutils/compiler.h>
 #include <utils/Trace.h>
-#include <cmath>
-#include <string>
 
 namespace std {
 template <class Rep, class Period>
@@ -75,7 +78,7 @@
         }
 
         if (mNameNegative.empty()) {
-            mNameNegative = base::StringPrintf("%sNegative", mName.c_str());
+            mNameNegative = mName + "Negative";
         }
 
         if (!std::signbit(mData)) {
diff --git a/services/surfaceflinger/Tracing/LayerTracing.cpp b/services/surfaceflinger/Tracing/LayerTracing.cpp
new file mode 100644
index 0000000..49554c7
--- /dev/null
+++ b/services/surfaceflinger/Tracing/LayerTracing.cpp
@@ -0,0 +1,135 @@
+/*
+ * 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.
+ */
+
+#undef LOG_TAG
+#define LOG_TAG "LayerTracing"
+#define ATRACE_TAG ATRACE_TAG_GRAPHICS
+
+#include <SurfaceFlinger.h>
+#include <android-base/stringprintf.h>
+#include <log/log.h>
+#include <utils/SystemClock.h>
+#include <utils/Trace.h>
+
+#include "LayerTracing.h"
+#include "RingBuffer.h"
+
+namespace android {
+
+LayerTracing::LayerTracing(SurfaceFlinger& flinger) : mFlinger(flinger) {
+    mBuffer = std::make_unique<RingBuffer<LayersTraceFileProto, LayersTraceProto>>();
+}
+
+LayerTracing::~LayerTracing() = default;
+
+bool LayerTracing::enable() {
+    std::scoped_lock lock(mTraceLock);
+    if (mEnabled) {
+        return false;
+    }
+    mBuffer->setSize(mBufferSizeInBytes);
+    mEnabled = true;
+    return true;
+}
+
+bool LayerTracing::disable(std::string filename) {
+    std::scoped_lock lock(mTraceLock);
+    if (!mEnabled) {
+        return false;
+    }
+    mEnabled = false;
+    LayersTraceFileProto fileProto = createTraceFileProto();
+    mBuffer->writeToFile(fileProto, filename);
+    mBuffer->reset();
+    return true;
+}
+
+bool LayerTracing::isEnabled() const {
+    std::scoped_lock lock(mTraceLock);
+    return mEnabled;
+}
+
+status_t LayerTracing::writeToFile() {
+    std::scoped_lock lock(mTraceLock);
+    if (!mEnabled) {
+        return STATUS_OK;
+    }
+    LayersTraceFileProto fileProto = createTraceFileProto();
+    return mBuffer->writeToFile(fileProto, FILE_NAME);
+}
+
+void LayerTracing::setTraceFlags(uint32_t flags) {
+    std::scoped_lock lock(mTraceLock);
+    mFlags = flags;
+}
+
+void LayerTracing::setBufferSize(size_t bufferSizeInBytes) {
+    std::scoped_lock lock(mTraceLock);
+    mBufferSizeInBytes = bufferSizeInBytes;
+}
+
+bool LayerTracing::flagIsSet(uint32_t flags) const {
+    return (mFlags & flags) == flags;
+}
+
+LayersTraceFileProto LayerTracing::createTraceFileProto() const {
+    LayersTraceFileProto fileProto;
+    fileProto.set_magic_number(uint64_t(LayersTraceFileProto_MagicNumber_MAGIC_NUMBER_H) << 32 |
+                               LayersTraceFileProto_MagicNumber_MAGIC_NUMBER_L);
+    return fileProto;
+}
+
+void LayerTracing::dump(std::string& result) const {
+    std::scoped_lock lock(mTraceLock);
+    base::StringAppendF(&result, "Tracing state: %s\n", mEnabled ? "enabled" : "disabled");
+    mBuffer->dump(result);
+}
+
+void LayerTracing::notify(bool visibleRegionDirty, int64_t time) {
+    std::scoped_lock lock(mTraceLock);
+    if (!mEnabled) {
+        return;
+    }
+
+    if (!visibleRegionDirty && !flagIsSet(LayerTracing::TRACE_BUFFERS)) {
+        return;
+    }
+
+    ATRACE_CALL();
+    LayersTraceProto entry;
+    entry.set_elapsed_realtime_nanos(time);
+    const char* where = visibleRegionDirty ? "visibleRegionsDirty" : "bufferLatched";
+    entry.set_where(where);
+    LayersProto layers(mFlinger.dumpDrawingStateProto(mFlags));
+
+    if (flagIsSet(LayerTracing::TRACE_EXTRA)) {
+        mFlinger.dumpOffscreenLayersProto(layers);
+    }
+    entry.mutable_layers()->Swap(&layers);
+
+    if (flagIsSet(LayerTracing::TRACE_HWC)) {
+        std::string hwcDump;
+        mFlinger.dumpHwc(hwcDump);
+        entry.set_hwc_blob(hwcDump);
+    }
+    if (!flagIsSet(LayerTracing::TRACE_COMPOSITION)) {
+        entry.set_excludes_composition_state(true);
+    }
+    mFlinger.dumpDisplayProto(entry);
+    mBuffer->emplace(std::move(entry));
+}
+
+} // namespace android
diff --git a/services/surfaceflinger/Tracing/LayerTracing.h b/services/surfaceflinger/Tracing/LayerTracing.h
new file mode 100644
index 0000000..88a19ec
--- /dev/null
+++ b/services/surfaceflinger/Tracing/LayerTracing.h
@@ -0,0 +1,77 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <android-base/thread_annotations.h>
+#include <layerproto/LayerProtoHeader.h>
+#include <utils/Errors.h>
+#include <utils/StrongPointer.h>
+#include <utils/Timers.h>
+
+#include <memory>
+#include <mutex>
+
+using namespace android::surfaceflinger;
+
+namespace android {
+
+template <typename FileProto, typename EntryProto>
+class RingBuffer;
+
+class SurfaceFlinger;
+
+/*
+ * LayerTracing records layer states during surface flinging. Manages tracing state and
+ * configuration.
+ */
+class LayerTracing {
+public:
+    LayerTracing(SurfaceFlinger& flinger);
+    ~LayerTracing();
+    bool enable();
+    bool disable(std::string filename = FILE_NAME);
+    bool isEnabled() const;
+    status_t writeToFile();
+    LayersTraceFileProto createTraceFileProto() const;
+    void notify(bool visibleRegionDirty, int64_t time);
+
+    enum : uint32_t {
+        TRACE_INPUT = 1 << 1,
+        TRACE_COMPOSITION = 1 << 2,
+        TRACE_EXTRA = 1 << 3,
+        TRACE_HWC = 1 << 4,
+        TRACE_BUFFERS = 1 << 5,
+        TRACE_ALL = TRACE_INPUT | TRACE_COMPOSITION | TRACE_EXTRA,
+    };
+    void setTraceFlags(uint32_t flags);
+    bool flagIsSet(uint32_t flags) const;
+    void setBufferSize(size_t bufferSizeInBytes);
+    void dump(std::string&) const;
+
+private:
+    static constexpr auto FILE_NAME = "/data/misc/wmtrace/layers_trace.winscope";
+
+    SurfaceFlinger& mFlinger;
+    uint32_t mFlags = TRACE_INPUT;
+    mutable std::mutex mTraceLock;
+    bool mEnabled GUARDED_BY(mTraceLock) = false;
+    std::unique_ptr<RingBuffer<LayersTraceFileProto, LayersTraceProto>> mBuffer
+            GUARDED_BY(mTraceLock);
+    size_t mBufferSizeInBytes GUARDED_BY(mTraceLock) = 20 * 1024 * 1024;
+};
+
+} // namespace android
diff --git a/services/surfaceflinger/Tracing/LocklessStack.h b/services/surfaceflinger/Tracing/LocklessStack.h
new file mode 100644
index 0000000..20f2aa8
--- /dev/null
+++ b/services/surfaceflinger/Tracing/LocklessStack.h
@@ -0,0 +1,72 @@
+/*
+ * 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.
+ */
+
+#pragma once
+#include <atomic>
+
+template <typename T>
+// Single consumer multi producer stack. We can understand the two operations independently to see
+// why they are without race condition.
+//
+// push is responsible for maintaining a linked list stored in mPush, and called from multiple
+// threads without lock. We can see that if two threads never observe the same value from
+// mPush.load, it just functions as a normal linked list. In the case where two threads observe the
+// same value, one of them has to execute the compare_exchange first. The one that doesn't execute
+// the compare exchange first, will receive false from compare_exchange. previousHead is updated (by
+// compare_exchange) to the most recent value of mPush, and we try again. It's relatively clear to
+// see that the process can repeat with an arbitrary number of threads.
+//
+// Pop is much simpler. If mPop is empty (as it begins) it atomically exchanges
+// the entire push list with null. This is safe, since the only other reader (push)
+// of mPush will retry if it changes in between it's read and atomic compare. We
+// then store the list and pop one element.
+//
+// If we already had something in the pop list we just pop directly.
+class LocklessStack {
+public:
+    class Entry {
+    public:
+        T* mValue;
+        std::atomic<Entry*> mNext;
+        Entry(T* value): mValue(value) {}
+    };
+    std::atomic<Entry*> mPush = nullptr;
+    std::atomic<Entry*> mPop = nullptr;
+    void push(T* value) {
+        Entry* entry = new Entry(value);
+        Entry* previousHead = mPush.load(/*std::memory_order_relaxed*/);
+        do {
+            entry->mNext = previousHead;
+        } while (!mPush.compare_exchange_weak(previousHead, entry)); /*std::memory_order_release*/
+    }
+    T* pop() {
+        Entry* popped = mPop.load(/*std::memory_order_acquire*/);
+        if (popped) {
+            // Single consumer so this is fine
+            mPop.store(popped->mNext/* , std::memory_order_release */);
+            auto value = popped->mValue;
+            delete popped;
+            return value;
+        } else {
+            Entry *grabbedList = mPush.exchange(nullptr/* , std::memory_order_acquire */);
+            if (!grabbedList) return nullptr;
+            mPop.store(grabbedList->mNext/* , std::memory_order_release */);
+            auto value = grabbedList->mValue;
+            delete grabbedList;
+            return value;
+        }
+    }
+};
diff --git a/services/surfaceflinger/Tracing/RingBuffer.h b/services/surfaceflinger/Tracing/RingBuffer.h
new file mode 100644
index 0000000..7e38c55
--- /dev/null
+++ b/services/surfaceflinger/Tracing/RingBuffer.h
@@ -0,0 +1,119 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <android-base/file.h>
+#include <android-base/stringprintf.h>
+
+#include <log/log.h>
+#include <utils/Errors.h>
+#include <utils/Timers.h>
+#include <utils/Trace.h>
+#include <chrono>
+#include <queue>
+
+namespace android {
+
+class SurfaceFlinger;
+
+template <typename FileProto, typename EntryProto>
+class RingBuffer {
+public:
+    size_t size() const { return mSizeInBytes; }
+    size_t used() const { return mUsedInBytes; }
+    size_t frameCount() const { return mStorage.size(); }
+    void setSize(size_t newSize) { mSizeInBytes = newSize; }
+    const std::string& front() const { return mStorage.front(); }
+    const std::string& back() const { return mStorage.back(); }
+
+    void reset() {
+        // use the swap trick to make sure memory is released
+        std::deque<std::string>().swap(mStorage);
+        mUsedInBytes = 0U;
+    }
+
+    void writeToProto(FileProto& fileProto) {
+        fileProto.mutable_entry()->Reserve(static_cast<int>(mStorage.size()) +
+                                           fileProto.entry().size());
+        for (const std::string& entry : mStorage) {
+            EntryProto* entryProto = fileProto.add_entry();
+            entryProto->ParseFromString(entry);
+        }
+    }
+
+    status_t writeToFile(FileProto& fileProto, std::string filename) {
+        ATRACE_CALL();
+        writeToProto(fileProto);
+        std::string output;
+        if (!fileProto.SerializeToString(&output)) {
+            ALOGE("Could not serialize proto.");
+            return UNKNOWN_ERROR;
+        }
+
+        // -rw-r--r--
+        const mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
+        if (!android::base::WriteStringToFile(output, filename, mode, getuid(), getgid(), true)) {
+            ALOGE("Could not save the proto file %s", filename.c_str());
+            return PERMISSION_DENIED;
+        }
+        return NO_ERROR;
+    }
+
+    std::vector<std::string> emplace(std::string&& serializedProto) {
+        std::vector<std::string> replacedEntries;
+        size_t protoSize = static_cast<size_t>(serializedProto.size());
+        while (mUsedInBytes + protoSize > mSizeInBytes) {
+            if (mStorage.empty()) {
+                return {};
+            }
+            mUsedInBytes -= static_cast<size_t>(mStorage.front().size());
+            replacedEntries.emplace_back(mStorage.front());
+            mStorage.pop_front();
+        }
+        mUsedInBytes += protoSize;
+        mStorage.emplace_back(serializedProto);
+        return replacedEntries;
+    }
+
+    std::vector<std::string> emplace(EntryProto&& proto) {
+        std::string serializedProto;
+        proto.SerializeToString(&serializedProto);
+        return emplace(std::move(serializedProto));
+    }
+
+    void dump(std::string& result) const {
+        std::chrono::milliseconds duration(0);
+        if (frameCount() > 0) {
+            EntryProto entry;
+            entry.ParseFromString(mStorage.front());
+            duration = std::chrono::duration_cast<std::chrono::milliseconds>(
+                    std::chrono::nanoseconds(systemTime() - entry.elapsed_realtime_nanos()));
+        }
+        const int64_t durationCount = duration.count();
+        base::StringAppendF(&result,
+                            "  number of entries: %zu (%.2fMB / %.2fMB) duration: %" PRIi64 "ms\n",
+                            frameCount(), float(used()) / (1024.f * 1024.f),
+                            float(size()) / (1024.f * 1024.f), durationCount);
+    }
+
+private:
+    size_t mUsedInBytes = 0U;
+    size_t mSizeInBytes = 0U;
+    std::deque<std::string> mStorage;
+};
+
+} // namespace android
diff --git a/services/surfaceflinger/Tracing/TransactionProtoParser.cpp b/services/surfaceflinger/Tracing/TransactionProtoParser.cpp
new file mode 100644
index 0000000..d249b60
--- /dev/null
+++ b/services/surfaceflinger/Tracing/TransactionProtoParser.cpp
@@ -0,0 +1,572 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <gui/SurfaceComposerClient.h>
+#include <ui/Rect.h>
+
+#include "LayerProtoHelper.h"
+#include "TransactionProtoParser.h"
+
+namespace android::surfaceflinger {
+
+proto::TransactionState TransactionProtoParser::toProto(const TransactionState& t) {
+    proto::TransactionState proto;
+    proto.set_pid(t.originPid);
+    proto.set_uid(t.originUid);
+    proto.set_vsync_id(t.frameTimelineInfo.vsyncId);
+    proto.set_input_event_id(t.frameTimelineInfo.inputEventId);
+    proto.set_post_time(t.postTime);
+    proto.set_transaction_id(t.id);
+
+    proto.mutable_layer_changes()->Reserve(static_cast<int32_t>(t.states.size()));
+    for (auto& layerState : t.states) {
+        proto.mutable_layer_changes()->Add(std::move(toProto(layerState.state)));
+    }
+
+    proto.mutable_display_changes()->Reserve(static_cast<int32_t>(t.displays.size()));
+    for (auto& displayState : t.displays) {
+        proto.mutable_display_changes()->Add(std::move(toProto(displayState)));
+    }
+    return proto;
+}
+
+proto::TransactionState TransactionProtoParser::toProto(
+        const std::map<int32_t /* layerId */, TracingLayerState>& states) {
+    proto::TransactionState proto;
+    proto.mutable_layer_changes()->Reserve(static_cast<int32_t>(states.size()));
+    for (auto& [layerId, state] : states) {
+        proto::LayerState layerProto = toProto(state);
+        if (layerProto.has_buffer_data()) {
+            proto::LayerState_BufferData* bufferProto = layerProto.mutable_buffer_data();
+            bufferProto->set_buffer_id(state.bufferId);
+            bufferProto->set_width(state.bufferWidth);
+            bufferProto->set_height(state.bufferHeight);
+            bufferProto->set_pixel_format(
+                    static_cast<proto::LayerState_BufferData_PixelFormat>(state.pixelFormat));
+            bufferProto->set_usage(state.bufferUsage);
+        }
+        layerProto.set_has_sideband_stream(state.hasSidebandStream);
+        layerProto.set_layer_id(state.layerId);
+        layerProto.set_parent_id(state.parentId);
+        layerProto.set_relative_parent_id(state.relativeParentId);
+        if (layerProto.has_window_info_handle()) {
+            layerProto.mutable_window_info_handle()->set_crop_layer_id(state.inputCropId);
+        }
+        proto.mutable_layer_changes()->Add(std::move(layerProto));
+    }
+    return proto;
+}
+
+proto::LayerState TransactionProtoParser::toProto(const layer_state_t& layer) {
+    proto::LayerState proto;
+    if (layer.surface) {
+        proto.set_layer_id(mMapper->getLayerId(layer.surface));
+    } else {
+        proto.set_layer_id(layer.layerId);
+    }
+
+    proto.set_what(layer.what);
+
+    if (layer.what & layer_state_t::ePositionChanged) {
+        proto.set_x(layer.x);
+        proto.set_y(layer.y);
+    }
+    if (layer.what & layer_state_t::eLayerChanged) {
+        proto.set_z(layer.z);
+    }
+    if (layer.what & layer_state_t::eSizeChanged) {
+        proto.set_w(layer.w);
+        proto.set_h(layer.h);
+    }
+    if (layer.what & layer_state_t::eLayerStackChanged) {
+        proto.set_layer_stack(layer.layerStack.id);
+    }
+    if (layer.what & layer_state_t::eFlagsChanged) {
+        proto.set_flags(layer.flags);
+        proto.set_mask(layer.mask);
+    }
+    if (layer.what & layer_state_t::eMatrixChanged) {
+        proto::LayerState_Matrix22* matrixProto = proto.mutable_matrix();
+        matrixProto->set_dsdx(layer.matrix.dsdx);
+        matrixProto->set_dsdy(layer.matrix.dsdy);
+        matrixProto->set_dtdx(layer.matrix.dtdx);
+        matrixProto->set_dtdy(layer.matrix.dtdy);
+    }
+    if (layer.what & layer_state_t::eCornerRadiusChanged) {
+        proto.set_corner_radius(layer.cornerRadius);
+    }
+    if (layer.what & layer_state_t::eBackgroundBlurRadiusChanged) {
+        proto.set_background_blur_radius(layer.backgroundBlurRadius);
+    }
+
+    if (layer.what & layer_state_t::eAlphaChanged) {
+        proto.set_alpha(layer.alpha);
+    }
+
+    if (layer.what & layer_state_t::eColorChanged) {
+        proto::LayerState_Color3* colorProto = proto.mutable_color();
+        colorProto->set_r(layer.color.r);
+        colorProto->set_g(layer.color.g);
+        colorProto->set_b(layer.color.b);
+    }
+    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::eTransformToDisplayInverseChanged) {
+        proto.set_transform_to_display_inverse(layer.transformToDisplayInverse);
+    }
+    if (layer.what & layer_state_t::eCropChanged) {
+        LayerProtoHelper::writeToProto(layer.crop, proto.mutable_crop());
+    }
+    if (layer.what & layer_state_t::eBufferChanged) {
+        proto::LayerState_BufferData* bufferProto = proto.mutable_buffer_data();
+        if (layer.bufferData->hasBuffer()) {
+            bufferProto->set_buffer_id(layer.bufferData->getId());
+            bufferProto->set_width(layer.bufferData->getWidth());
+            bufferProto->set_height(layer.bufferData->getHeight());
+            bufferProto->set_pixel_format(static_cast<proto::LayerState_BufferData_PixelFormat>(
+                    layer.bufferData->getPixelFormat()));
+            bufferProto->set_usage(layer.bufferData->getUsage());
+        } else {
+            uint64_t bufferId;
+            uint32_t width;
+            uint32_t height;
+            int32_t pixelFormat;
+            uint64_t usage;
+            mMapper->getGraphicBufferPropertiesFromCache(layer.bufferData->cachedBuffer, &bufferId,
+                                                         &width, &height, &pixelFormat, &usage);
+            bufferProto->set_buffer_id(bufferId);
+            bufferProto->set_width(width);
+            bufferProto->set_height(height);
+            bufferProto->set_pixel_format(
+                    static_cast<proto::LayerState_BufferData_PixelFormat>(pixelFormat));
+            bufferProto->set_usage(usage);
+        }
+        bufferProto->set_frame_number(layer.bufferData->frameNumber);
+        bufferProto->set_flags(layer.bufferData->flags.get());
+        bufferProto->set_cached_buffer_id(layer.bufferData->cachedBuffer.id);
+    }
+    if (layer.what & layer_state_t::eSidebandStreamChanged) {
+        proto.set_has_sideband_stream(layer.sidebandStream != nullptr);
+    }
+
+    if (layer.what & layer_state_t::eApiChanged) {
+        proto.set_api(layer.api);
+    }
+
+    if (layer.what & layer_state_t::eColorTransformChanged) {
+        LayerProtoHelper::writeToProto(layer.colorTransform, proto.mutable_color_transform());
+    }
+    if (layer.what & layer_state_t::eBlurRegionsChanged) {
+        for (auto& region : layer.blurRegions) {
+            LayerProtoHelper::writeToProto(region, proto.add_blur_regions());
+        }
+    }
+
+    if (layer.what & layer_state_t::eReparent) {
+        int64_t layerId = layer.parentSurfaceControlForChild
+                ? mMapper->getLayerId(layer.parentSurfaceControlForChild->getHandle())
+                : -1;
+        proto.set_parent_id(layerId);
+    }
+    if (layer.what & layer_state_t::eRelativeLayerChanged) {
+        int64_t layerId = layer.relativeLayerSurfaceControl
+                ? mMapper->getLayerId(layer.relativeLayerSurfaceControl->getHandle())
+                : -1;
+        proto.set_relative_parent_id(layerId);
+        proto.set_z(layer.z);
+    }
+
+    if (layer.what & layer_state_t::eInputInfoChanged) {
+        if (layer.windowInfoHandle) {
+            const gui::WindowInfo* inputInfo = layer.windowInfoHandle->getInfo();
+            proto::LayerState_WindowInfo* windowInfoProto = proto.mutable_window_info_handle();
+            windowInfoProto->set_layout_params_flags(inputInfo->layoutParamsFlags.get());
+            windowInfoProto->set_layout_params_type(
+                    static_cast<int32_t>(inputInfo->layoutParamsType));
+            LayerProtoHelper::writeToProto(inputInfo->touchableRegion,
+                                           windowInfoProto->mutable_touchable_region());
+            windowInfoProto->set_surface_inset(inputInfo->surfaceInset);
+            windowInfoProto->set_focusable(
+                    !inputInfo->inputConfig.test(gui::WindowInfo::InputConfig::NOT_FOCUSABLE));
+            windowInfoProto->set_has_wallpaper(inputInfo->inputConfig.test(
+                    gui::WindowInfo::InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER));
+            windowInfoProto->set_global_scale_factor(inputInfo->globalScaleFactor);
+            proto::LayerState_Transform* transformProto = windowInfoProto->mutable_transform();
+            transformProto->set_dsdx(inputInfo->transform.dsdx());
+            transformProto->set_dtdx(inputInfo->transform.dtdx());
+            transformProto->set_dtdy(inputInfo->transform.dtdy());
+            transformProto->set_dsdy(inputInfo->transform.dsdy());
+            transformProto->set_tx(inputInfo->transform.tx());
+            transformProto->set_ty(inputInfo->transform.ty());
+            windowInfoProto->set_replace_touchable_region_with_crop(
+                    inputInfo->replaceTouchableRegionWithCrop);
+            windowInfoProto->set_crop_layer_id(
+                    mMapper->getLayerId(inputInfo->touchableRegionCropHandle.promote()));
+        }
+    }
+    if (layer.what & layer_state_t::eBackgroundColorChanged) {
+        proto.set_bg_color_alpha(layer.bgColorAlpha);
+        proto.set_bg_color_dataspace(static_cast<int32_t>(layer.bgColorDataspace));
+        proto::LayerState_Color3* colorProto = proto.mutable_color();
+        colorProto->set_r(layer.color.r);
+        colorProto->set_g(layer.color.g);
+        colorProto->set_b(layer.color.b);
+    }
+    if (layer.what & layer_state_t::eColorSpaceAgnosticChanged) {
+        proto.set_color_space_agnostic(layer.colorSpaceAgnostic);
+    }
+    if (layer.what & layer_state_t::eShadowRadiusChanged) {
+        proto.set_shadow_radius(layer.shadowRadius);
+    }
+    if (layer.what & layer_state_t::eFrameRateSelectionPriority) {
+        proto.set_frame_rate_selection_priority(layer.frameRateSelectionPriority);
+    }
+    if (layer.what & layer_state_t::eFrameRateChanged) {
+        proto.set_frame_rate(layer.frameRate);
+        proto.set_frame_rate_compatibility(layer.frameRateCompatibility);
+        proto.set_change_frame_rate_strategy(layer.changeFrameRateStrategy);
+    }
+    if (layer.what & layer_state_t::eFixedTransformHintChanged) {
+        proto.set_fixed_transform_hint(layer.fixedTransformHint);
+    }
+    if (layer.what & layer_state_t::eAutoRefreshChanged) {
+        proto.set_auto_refresh(layer.autoRefresh);
+    }
+    if (layer.what & layer_state_t::eTrustedOverlayChanged) {
+        proto.set_is_trusted_overlay(layer.isTrustedOverlay);
+    }
+    if (layer.what & layer_state_t::eBufferCropChanged) {
+        LayerProtoHelper::writeToProto(layer.bufferCrop, proto.mutable_buffer_crop());
+    }
+    if (layer.what & layer_state_t::eDestinationFrameChanged) {
+        LayerProtoHelper::writeToProto(layer.destinationFrame, proto.mutable_destination_frame());
+    }
+    if (layer.what & layer_state_t::eDropInputModeChanged) {
+        proto.set_drop_input_mode(
+                static_cast<proto::LayerState_DropInputMode>(layer.dropInputMode));
+    }
+    return proto;
+}
+
+proto::DisplayState TransactionProtoParser::toProto(const DisplayState& display) {
+    proto::DisplayState proto;
+    proto.set_what(display.what);
+    proto.set_id(mMapper->getDisplayId(display.token));
+
+    if (display.what & DisplayState::eLayerStackChanged) {
+        proto.set_layer_stack(display.layerStack.id);
+    }
+    if (display.what & DisplayState::eDisplayProjectionChanged) {
+        proto.set_orientation(static_cast<uint32_t>(display.orientation));
+        LayerProtoHelper::writeToProto(display.orientedDisplaySpaceRect,
+                                       proto.mutable_oriented_display_space_rect());
+        LayerProtoHelper::writeToProto(display.layerStackSpaceRect,
+                                       proto.mutable_layer_stack_space_rect());
+    }
+    if (display.what & DisplayState::eDisplaySizeChanged) {
+        proto.set_width(display.width);
+        proto.set_height(display.height);
+    }
+    if (display.what & DisplayState::eFlagsChanged) {
+        proto.set_flags(display.flags);
+    }
+    return proto;
+}
+
+proto::LayerCreationArgs TransactionProtoParser::toProto(const TracingLayerCreationArgs& args) {
+    proto::LayerCreationArgs proto;
+    proto.set_layer_id(args.layerId);
+    proto.set_name(args.name);
+    proto.set_flags(args.flags);
+    proto.set_parent_id(args.parentId);
+    proto.set_mirror_from_id(args.mirrorFromId);
+    return proto;
+}
+
+TransactionState TransactionProtoParser::fromProto(const proto::TransactionState& proto) {
+    TransactionState t;
+    t.originPid = proto.pid();
+    t.originUid = proto.uid();
+    t.frameTimelineInfo.vsyncId = proto.vsync_id();
+    t.frameTimelineInfo.inputEventId = proto.input_event_id();
+    t.postTime = proto.post_time();
+    t.id = proto.transaction_id();
+
+    int32_t layerCount = proto.layer_changes_size();
+    t.states.reserve(static_cast<size_t>(layerCount));
+    for (int i = 0; i < layerCount; i++) {
+        ComposerState s;
+        s.state.what = 0;
+        fromProto(proto.layer_changes(i), s.state);
+        t.states.add(s);
+    }
+
+    int32_t displayCount = proto.display_changes_size();
+    t.displays.reserve(static_cast<size_t>(displayCount));
+    for (int i = 0; i < displayCount; i++) {
+        t.displays.add(fromProto(proto.display_changes(i)));
+    }
+    return t;
+}
+
+void TransactionProtoParser::fromProto(const proto::LayerCreationArgs& proto,
+                                       TracingLayerCreationArgs& outArgs) {
+    outArgs.layerId = proto.layer_id();
+    outArgs.name = proto.name();
+    outArgs.flags = proto.flags();
+    outArgs.parentId = proto.parent_id();
+    outArgs.mirrorFromId = proto.mirror_from_id();
+}
+
+void TransactionProtoParser::mergeFromProto(const proto::LayerState& proto,
+                                            TracingLayerState& outState) {
+    layer_state_t state;
+    fromProto(proto, state);
+    outState.merge(state);
+
+    if (state.what & layer_state_t::eReparent) {
+        outState.parentId = static_cast<int32_t>(proto.parent_id());
+    }
+    if (state.what & layer_state_t::eRelativeLayerChanged) {
+        outState.relativeParentId = static_cast<int32_t>(proto.relative_parent_id());
+    }
+    if (state.what & layer_state_t::eInputInfoChanged) {
+        outState.inputCropId = static_cast<int32_t>(proto.window_info_handle().crop_layer_id());
+    }
+    if (state.what & layer_state_t::eBufferChanged) {
+        const proto::LayerState_BufferData& bufferProto = proto.buffer_data();
+        outState.bufferId = bufferProto.buffer_id();
+        outState.bufferWidth = bufferProto.width();
+        outState.bufferHeight = bufferProto.height();
+        outState.pixelFormat = bufferProto.pixel_format();
+        outState.bufferUsage = bufferProto.usage();
+    }
+    if (state.what & layer_state_t::eSidebandStreamChanged) {
+        outState.hasSidebandStream = proto.has_sideband_stream();
+    }
+}
+
+void TransactionProtoParser::fromProto(const proto::LayerState& proto, layer_state_t& layer) {
+    layer.layerId = (int32_t)proto.layer_id();
+    layer.what |= proto.what();
+    layer.surface = mMapper->getLayerHandle(layer.layerId);
+
+    if (proto.what() & layer_state_t::ePositionChanged) {
+        layer.x = proto.x();
+        layer.y = proto.y();
+    }
+    if (proto.what() & layer_state_t::eLayerChanged) {
+        layer.z = proto.z();
+    }
+    if (proto.what() & layer_state_t::eSizeChanged) {
+        layer.w = proto.w();
+        layer.h = proto.h();
+    }
+    if (proto.what() & layer_state_t::eLayerStackChanged) {
+        layer.layerStack.id = proto.layer_stack();
+    }
+    if (proto.what() & layer_state_t::eFlagsChanged) {
+        layer.flags = proto.flags();
+        layer.mask = proto.mask();
+    }
+    if (proto.what() & layer_state_t::eMatrixChanged) {
+        const proto::LayerState_Matrix22& matrixProto = proto.matrix();
+        layer.matrix.dsdx = matrixProto.dsdx();
+        layer.matrix.dsdy = matrixProto.dsdy();
+        layer.matrix.dtdx = matrixProto.dtdx();
+        layer.matrix.dtdy = matrixProto.dtdy();
+    }
+    if (proto.what() & layer_state_t::eCornerRadiusChanged) {
+        layer.cornerRadius = proto.corner_radius();
+    }
+    if (proto.what() & layer_state_t::eBackgroundBlurRadiusChanged) {
+        layer.backgroundBlurRadius = proto.background_blur_radius();
+    }
+
+    if (proto.what() & layer_state_t::eAlphaChanged) {
+        layer.alpha = proto.alpha();
+    }
+
+    if (proto.what() & layer_state_t::eColorChanged) {
+        const proto::LayerState_Color3& colorProto = proto.color();
+        layer.color.r = colorProto.r();
+        layer.color.g = colorProto.g();
+        layer.color.b = colorProto.b();
+    }
+    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::eTransformToDisplayInverseChanged) {
+        layer.transformToDisplayInverse = proto.transform_to_display_inverse();
+    }
+    if (proto.what() & layer_state_t::eCropChanged) {
+        LayerProtoHelper::readFromProto(proto.crop(), layer.crop);
+    }
+    if (proto.what() & layer_state_t::eBufferChanged) {
+        const proto::LayerState_BufferData& bufferProto = proto.buffer_data();
+        layer.bufferData =
+                std::move(mMapper->getGraphicData(bufferProto.buffer_id(), bufferProto.width(),
+                                                  bufferProto.height(), bufferProto.pixel_format(),
+                                                  bufferProto.usage()));
+        layer.bufferData->frameNumber = bufferProto.frame_number();
+        layer.bufferData->flags = Flags<BufferData::BufferDataChange>(bufferProto.flags());
+        layer.bufferData->cachedBuffer.id = bufferProto.cached_buffer_id();
+        layer.bufferData->acquireFence = Fence::NO_FENCE;
+    }
+
+    if (proto.what() & layer_state_t::eApiChanged) {
+        layer.api = proto.api();
+    }
+
+    if (proto.what() & layer_state_t::eColorTransformChanged) {
+        LayerProtoHelper::readFromProto(proto.color_transform(), layer.colorTransform);
+    }
+    if (proto.what() & layer_state_t::eBlurRegionsChanged) {
+        layer.blurRegions.reserve(static_cast<size_t>(proto.blur_regions_size()));
+        for (int i = 0; i < proto.blur_regions_size(); i++) {
+            android::BlurRegion region;
+            LayerProtoHelper::readFromProto(proto.blur_regions(i), region);
+            layer.blurRegions.push_back(region);
+        }
+    }
+
+    if (proto.what() & layer_state_t::eReparent) {
+        int64_t layerId = proto.parent_id();
+        if (layerId == -1) {
+            layer.parentSurfaceControlForChild = nullptr;
+        } else {
+            layer.parentSurfaceControlForChild =
+                    new SurfaceControl(SurfaceComposerClient::getDefault(),
+                                       mMapper->getLayerHandle(static_cast<int32_t>(layerId)),
+                                       nullptr, static_cast<int32_t>(layerId));
+        }
+    }
+    if (proto.what() & layer_state_t::eRelativeLayerChanged) {
+        int64_t layerId = proto.relative_parent_id();
+        if (layerId == -1) {
+            layer.relativeLayerSurfaceControl = nullptr;
+        } else {
+            layer.relativeLayerSurfaceControl =
+                    new SurfaceControl(SurfaceComposerClient::getDefault(),
+                                       mMapper->getLayerHandle(static_cast<int32_t>(layerId)),
+                                       nullptr, static_cast<int32_t>(layerId));
+        }
+        layer.z = proto.z();
+    }
+
+    if ((proto.what() & layer_state_t::eInputInfoChanged) && proto.has_window_info_handle()) {
+        gui::WindowInfo inputInfo;
+        const proto::LayerState_WindowInfo& windowInfoProto = proto.window_info_handle();
+
+        inputInfo.layoutParamsFlags =
+                static_cast<gui::WindowInfo::Flag>(windowInfoProto.layout_params_flags());
+        inputInfo.layoutParamsType =
+                static_cast<gui::WindowInfo::Type>(windowInfoProto.layout_params_type());
+        LayerProtoHelper::readFromProto(windowInfoProto.touchable_region(),
+                                        inputInfo.touchableRegion);
+        inputInfo.surfaceInset = windowInfoProto.surface_inset();
+        inputInfo.setInputConfig(gui::WindowInfo::InputConfig::NOT_FOCUSABLE,
+                                 !windowInfoProto.focusable());
+        inputInfo.setInputConfig(gui::WindowInfo::InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER,
+                                 windowInfoProto.has_wallpaper());
+        inputInfo.globalScaleFactor = windowInfoProto.global_scale_factor();
+        const proto::LayerState_Transform& transformProto = windowInfoProto.transform();
+        inputInfo.transform.set(transformProto.dsdx(), transformProto.dtdx(), transformProto.dtdy(),
+                                transformProto.dsdy());
+        inputInfo.transform.set(transformProto.tx(), transformProto.ty());
+        inputInfo.replaceTouchableRegionWithCrop =
+                windowInfoProto.replace_touchable_region_with_crop();
+        int64_t layerId = windowInfoProto.crop_layer_id();
+        inputInfo.touchableRegionCropHandle =
+                mMapper->getLayerHandle(static_cast<int32_t>(layerId));
+        layer.windowInfoHandle = sp<gui::WindowInfoHandle>::make(inputInfo);
+    }
+    if (proto.what() & layer_state_t::eBackgroundColorChanged) {
+        layer.bgColorAlpha = proto.bg_color_alpha();
+        layer.bgColorDataspace = static_cast<ui::Dataspace>(proto.bg_color_dataspace());
+        const proto::LayerState_Color3& colorProto = proto.color();
+        layer.color.r = colorProto.r();
+        layer.color.g = colorProto.g();
+        layer.color.b = colorProto.b();
+    }
+    if (proto.what() & layer_state_t::eColorSpaceAgnosticChanged) {
+        layer.colorSpaceAgnostic = proto.color_space_agnostic();
+    }
+    if (proto.what() & layer_state_t::eShadowRadiusChanged) {
+        layer.shadowRadius = proto.shadow_radius();
+    }
+    if (proto.what() & layer_state_t::eFrameRateSelectionPriority) {
+        layer.frameRateSelectionPriority = proto.frame_rate_selection_priority();
+    }
+    if (proto.what() & layer_state_t::eFrameRateChanged) {
+        layer.frameRate = proto.frame_rate();
+        layer.frameRateCompatibility = static_cast<int8_t>(proto.frame_rate_compatibility());
+        layer.changeFrameRateStrategy = static_cast<int8_t>(proto.change_frame_rate_strategy());
+    }
+    if (proto.what() & layer_state_t::eFixedTransformHintChanged) {
+        layer.fixedTransformHint =
+                static_cast<ui::Transform::RotationFlags>(proto.fixed_transform_hint());
+    }
+    if (proto.what() & layer_state_t::eAutoRefreshChanged) {
+        layer.autoRefresh = proto.auto_refresh();
+    }
+    if (proto.what() & layer_state_t::eTrustedOverlayChanged) {
+        layer.isTrustedOverlay = proto.is_trusted_overlay();
+    }
+    if (proto.what() & layer_state_t::eBufferCropChanged) {
+        LayerProtoHelper::readFromProto(proto.buffer_crop(), layer.bufferCrop);
+    }
+    if (proto.what() & layer_state_t::eDestinationFrameChanged) {
+        LayerProtoHelper::readFromProto(proto.destination_frame(), layer.destinationFrame);
+    }
+    if (proto.what() & layer_state_t::eDropInputModeChanged) {
+        layer.dropInputMode = static_cast<gui::DropInputMode>(proto.drop_input_mode());
+    }
+}
+
+DisplayState TransactionProtoParser::fromProto(const proto::DisplayState& proto) {
+    DisplayState display;
+    display.what = proto.what();
+    display.token = mMapper->getDisplayHandle(proto.id());
+
+    if (display.what & DisplayState::eLayerStackChanged) {
+        display.layerStack.id = proto.layer_stack();
+    }
+    if (display.what & DisplayState::eDisplayProjectionChanged) {
+        display.orientation = static_cast<ui::Rotation>(proto.orientation());
+        LayerProtoHelper::readFromProto(proto.oriented_display_space_rect(),
+                                        display.orientedDisplaySpaceRect);
+        LayerProtoHelper::readFromProto(proto.layer_stack_space_rect(),
+                                        display.layerStackSpaceRect);
+    }
+    if (display.what & DisplayState::eDisplaySizeChanged) {
+        display.width = proto.width();
+        display.height = proto.height();
+    }
+    if (display.what & DisplayState::eFlagsChanged) {
+        display.flags = proto.flags();
+    }
+    return display;
+}
+
+} // namespace android::surfaceflinger
diff --git a/services/surfaceflinger/Tracing/TransactionProtoParser.h b/services/surfaceflinger/Tracing/TransactionProtoParser.h
new file mode 100644
index 0000000..872a901
--- /dev/null
+++ b/services/surfaceflinger/Tracing/TransactionProtoParser.h
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#include <layerproto/TransactionProto.h>
+#include <utils/RefBase.h>
+
+#include "TransactionState.h"
+
+namespace android::surfaceflinger {
+
+struct TracingLayerCreationArgs {
+    int32_t layerId;
+    std::string name;
+    uint32_t flags = 0;
+    int32_t parentId = -1;
+    int32_t mirrorFromId = -1;
+};
+
+struct TracingLayerState : layer_state_t {
+    uint64_t bufferId;
+    uint32_t bufferHeight;
+    uint32_t bufferWidth;
+    int32_t pixelFormat;
+    uint64_t bufferUsage;
+    bool hasSidebandStream;
+    int32_t parentId;
+    int32_t relativeParentId;
+    int32_t inputCropId;
+    TracingLayerCreationArgs args;
+};
+
+// Class which exposes buffer properties from BufferData without holding on to the actual buffer
+// handle.
+class BufferDataStub : public BufferData {
+public:
+    BufferDataStub(uint64_t bufferId, uint32_t width, uint32_t height, int32_t pixelFormat,
+                   uint64_t outUsage)
+          : mBufferId(bufferId),
+            mWidth(width),
+            mHeight(height),
+            mPixelFormat(pixelFormat),
+            mOutUsage(outUsage) {}
+    bool hasBuffer() const override { return mBufferId != 0; }
+    bool hasSameBuffer(const BufferData& other) const override {
+        return getId() == other.getId() && frameNumber == other.frameNumber;
+    }
+    uint32_t getWidth() const override { return mWidth; }
+    uint32_t getHeight() const override { return mHeight; }
+    uint64_t getId() const override { return mBufferId; }
+    PixelFormat getPixelFormat() const override { return mPixelFormat; }
+    uint64_t getUsage() const override { return mOutUsage; }
+
+private:
+    uint64_t mBufferId;
+    uint32_t mWidth;
+    uint32_t mHeight;
+    int32_t mPixelFormat;
+    uint64_t mOutUsage;
+};
+
+class TransactionProtoParser {
+public:
+    // Utility class to map handles to ids and buffers to buffer properties without pulling
+    // in SurfaceFlinger dependencies.
+    class FlingerDataMapper {
+    public:
+        virtual ~FlingerDataMapper() = default;
+        virtual sp<IBinder> getLayerHandle(int32_t /* layerId */) const { return nullptr; }
+        virtual int64_t getLayerId(const sp<IBinder>& /* layerHandle */) const { return -1; }
+        virtual int64_t getLayerId(BBinder* /* layerHandle */) const { return -1; }
+        virtual sp<IBinder> getDisplayHandle(int32_t /* displayId */) const { return nullptr; }
+        virtual int32_t getDisplayId(const sp<IBinder>& /* displayHandle */) const { return -1; }
+        virtual std::shared_ptr<BufferData> getGraphicData(uint64_t bufferId, uint32_t width,
+                                                           uint32_t height, int32_t pixelFormat,
+                                                           uint64_t usage) const {
+            return std::make_shared<BufferDataStub>(bufferId, width, height, pixelFormat, usage);
+        }
+        virtual void getGraphicBufferPropertiesFromCache(client_cache_t /* cachedBuffer */,
+                                                         uint64_t* /* outBufferId */,
+                                                         uint32_t* /* outWidth */,
+                                                         uint32_t* /* outHeight */,
+                                                         int32_t* /* outPixelFormat */,
+                                                         uint64_t* /* outUsage */) const {}
+    };
+
+    TransactionProtoParser(std::unique_ptr<FlingerDataMapper> provider)
+          : mMapper(std::move(provider)) {}
+
+    proto::TransactionState toProto(const TransactionState&);
+    proto::TransactionState toProto(const std::map<int32_t /* layerId */, TracingLayerState>&);
+    proto::LayerCreationArgs toProto(const TracingLayerCreationArgs& args);
+
+    TransactionState fromProto(const proto::TransactionState&);
+    void mergeFromProto(const proto::LayerState&, TracingLayerState& outState);
+    void fromProto(const proto::LayerCreationArgs&, TracingLayerCreationArgs& outArgs);
+    std::unique_ptr<FlingerDataMapper> mMapper;
+
+private:
+    proto::LayerState toProto(const layer_state_t&);
+    proto::DisplayState toProto(const DisplayState&);
+    void fromProto(const proto::LayerState&, layer_state_t& out);
+    DisplayState fromProto(const proto::DisplayState&);
+
+};
+
+} // namespace android::surfaceflinger
diff --git a/services/surfaceflinger/Tracing/TransactionTracing.cpp b/services/surfaceflinger/Tracing/TransactionTracing.cpp
new file mode 100644
index 0000000..6381758
--- /dev/null
+++ b/services/surfaceflinger/Tracing/TransactionTracing.cpp
@@ -0,0 +1,414 @@
+/*
+ * 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.
+ */
+
+#undef LOG_TAG
+#define LOG_TAG "TransactionTracing"
+#define ATRACE_TAG ATRACE_TAG_GRAPHICS
+
+#include <android-base/stringprintf.h>
+#include <log/log.h>
+#include <utils/SystemClock.h>
+#include <utils/Trace.h>
+
+#include "ClientCache.h"
+#include "TransactionTracing.h"
+#include "renderengine/ExternalTexture.h"
+
+namespace android {
+
+// Keeps the binder address as the layer id so we can avoid holding the tracing lock in the
+// binder thread.
+class FlatDataMapper : public TransactionProtoParser::FlingerDataMapper {
+public:
+    virtual int64_t getLayerId(const sp<IBinder>& layerHandle) const {
+        if (layerHandle == nullptr) {
+            return -1;
+        }
+
+        return reinterpret_cast<int64_t>(layerHandle->localBinder());
+    }
+
+    void getGraphicBufferPropertiesFromCache(client_cache_t cachedBuffer, uint64_t* outBufferId,
+                                             uint32_t* outWidth, uint32_t* outHeight,
+                                             int32_t* outPixelFormat,
+                                             uint64_t* outUsage) const override {
+        std::shared_ptr<renderengine::ExternalTexture> buffer =
+                ClientCache::getInstance().get(cachedBuffer);
+        if (!buffer || !buffer->getBuffer()) {
+            *outBufferId = 0;
+            *outWidth = 0;
+            *outHeight = 0;
+            *outPixelFormat = 0;
+            *outUsage = 0;
+            return;
+        }
+
+        *outBufferId = buffer->getId();
+        *outWidth = buffer->getWidth();
+        *outHeight = buffer->getHeight();
+        *outPixelFormat = buffer->getPixelFormat();
+        *outUsage = buffer->getUsage();
+        return;
+    }
+};
+
+class FlingerDataMapper : public FlatDataMapper {
+    std::unordered_map<BBinder* /* layerHandle */, int32_t /* layerId */>& mLayerHandles;
+
+public:
+    FlingerDataMapper(std::unordered_map<BBinder* /* handle */, int32_t /* id */>& layerHandles)
+          : mLayerHandles(layerHandles) {}
+
+    int64_t getLayerId(const sp<IBinder>& layerHandle) const override {
+        if (layerHandle == nullptr) {
+            return -1;
+        }
+        return getLayerId(layerHandle->localBinder());
+    }
+
+    int64_t getLayerId(BBinder* localBinder) const {
+        auto it = mLayerHandles.find(localBinder);
+        if (it == mLayerHandles.end()) {
+            ALOGW("Could not find layer handle %p", localBinder);
+            return -1;
+        }
+        return it->second;
+    }
+};
+
+TransactionTracing::TransactionTracing()
+      : mProtoParser(std::make_unique<FlingerDataMapper>(mLayerHandles)),
+        mLockfreeProtoParser(std::make_unique<FlatDataMapper>()) {
+    std::scoped_lock lock(mTraceLock);
+
+    mBuffer.setSize(mBufferSizeInBytes);
+    mStartingTimestamp = systemTime();
+    {
+        std::scoped_lock lock(mMainThreadLock);
+        mThread = std::thread(&TransactionTracing::loop, this);
+    }
+}
+
+TransactionTracing::~TransactionTracing() {
+    std::thread thread;
+    {
+        std::scoped_lock lock(mMainThreadLock);
+        mDone = true;
+        mTransactionsAvailableCv.notify_all();
+        thread = std::move(mThread);
+    }
+    if (thread.joinable()) {
+        thread.join();
+    }
+
+    writeToFile();
+}
+
+status_t TransactionTracing::writeToFile(std::string filename) {
+    std::scoped_lock lock(mTraceLock);
+    proto::TransactionTraceFile fileProto = createTraceFileProto();
+    addStartingStateToProtoLocked(fileProto);
+    return mBuffer.writeToFile(fileProto, filename);
+}
+
+void TransactionTracing::setBufferSize(size_t bufferSizeInBytes) {
+    std::scoped_lock lock(mTraceLock);
+    mBufferSizeInBytes = bufferSizeInBytes;
+    mBuffer.setSize(mBufferSizeInBytes);
+}
+
+proto::TransactionTraceFile TransactionTracing::createTraceFileProto() const {
+    proto::TransactionTraceFile proto;
+    proto.set_magic_number(uint64_t(proto::TransactionTraceFile_MagicNumber_MAGIC_NUMBER_H) << 32 |
+                           proto::TransactionTraceFile_MagicNumber_MAGIC_NUMBER_L);
+    return proto;
+}
+
+void TransactionTracing::dump(std::string& result) const {
+    std::scoped_lock lock(mTraceLock);
+    base::StringAppendF(&result,
+                        "  queued transactions=%zu created layers=%zu handles=%zu states=%zu\n",
+                        mQueuedTransactions.size(), mCreatedLayers.size(), mLayerHandles.size(),
+                        mStartingStates.size());
+    mBuffer.dump(result);
+}
+
+void TransactionTracing::addQueuedTransaction(const TransactionState& transaction) {
+    proto::TransactionState* state =
+            new proto::TransactionState(mLockfreeProtoParser.toProto(transaction));
+    mTransactionQueue.push(state);
+}
+
+void TransactionTracing::addCommittedTransactions(std::vector<TransactionState>& transactions,
+                                                  int64_t vsyncId) {
+    CommittedTransactions committedTransactions;
+    committedTransactions.vsyncId = vsyncId;
+    committedTransactions.timestamp = systemTime();
+    committedTransactions.transactionIds.reserve(transactions.size());
+    for (const auto& transaction : transactions) {
+        committedTransactions.transactionIds.emplace_back(transaction.id);
+    }
+
+    mPendingTransactions.emplace_back(committedTransactions);
+    tryPushToTracingThread();
+}
+
+void TransactionTracing::loop() {
+    while (true) {
+        std::vector<CommittedTransactions> committedTransactions;
+        std::vector<int32_t> removedLayers;
+        {
+            std::unique_lock<std::mutex> lock(mMainThreadLock);
+            base::ScopedLockAssertion assumeLocked(mMainThreadLock);
+            mTransactionsAvailableCv.wait(lock, [&]() REQUIRES(mMainThreadLock) {
+                return mDone || !mCommittedTransactions.empty();
+            });
+            if (mDone) {
+                mCommittedTransactions.clear();
+                mRemovedLayers.clear();
+                break;
+            }
+
+            removedLayers = std::move(mRemovedLayers);
+            mRemovedLayers.clear();
+            committedTransactions = std::move(mCommittedTransactions);
+            mCommittedTransactions.clear();
+        } // unlock mMainThreadLock
+
+        if (!committedTransactions.empty() || !removedLayers.empty()) {
+            addEntry(committedTransactions, removedLayers);
+        }
+    }
+}
+
+void TransactionTracing::addEntry(const std::vector<CommittedTransactions>& committedTransactions,
+                                  const std::vector<int32_t>& removedLayers) {
+    ATRACE_CALL();
+    std::scoped_lock lock(mTraceLock);
+    std::vector<std::string> removedEntries;
+    proto::TransactionTraceEntry entryProto;
+
+    while (auto incomingTransaction = mTransactionQueue.pop()) {
+        auto transaction = *incomingTransaction;
+        int32_t layerCount = transaction.layer_changes_size();
+        for (int i = 0; i < layerCount; i++) {
+            auto layer = transaction.mutable_layer_changes(i);
+            layer->set_layer_id(
+                mProtoParser.mMapper->getLayerId(reinterpret_cast<BBinder*>(layer->layer_id())));
+            if ((layer->what() & layer_state_t::eReparent) && layer->parent_id() != -1) {
+                layer->set_parent_id(
+                    mProtoParser.mMapper->getLayerId(reinterpret_cast<BBinder*>(
+                        layer->parent_id())));
+            }
+
+            if ((layer->what() & layer_state_t::eRelativeLayerChanged) &&
+                layer->relative_parent_id() != -1) {
+                layer->set_relative_parent_id(
+                    mProtoParser.mMapper->getLayerId(reinterpret_cast<BBinder*>(
+                        layer->relative_parent_id())));
+            }
+
+            if (layer->has_window_info_handle() &&
+                layer->window_info_handle().crop_layer_id() != -1) {
+                auto input = layer->mutable_window_info_handle();
+                input->set_crop_layer_id(
+                        mProtoParser.mMapper->getLayerId(reinterpret_cast<BBinder*>(
+                            input->crop_layer_id())));
+            }
+        }
+        mQueuedTransactions[incomingTransaction->transaction_id()] = transaction;
+        delete incomingTransaction;
+    }
+    for (const CommittedTransactions& entry : committedTransactions) {
+        entryProto.set_elapsed_realtime_nanos(entry.timestamp);
+        entryProto.set_vsync_id(entry.vsyncId);
+        entryProto.mutable_added_layers()->Reserve(static_cast<int32_t>(mCreatedLayers.size()));
+        for (auto& newLayer : mCreatedLayers) {
+            entryProto.mutable_added_layers()->Add(std::move(newLayer));
+        }
+        entryProto.mutable_removed_layers()->Reserve(static_cast<int32_t>(removedLayers.size()));
+        for (auto& removedLayer : removedLayers) {
+            entryProto.mutable_removed_layers()->Add(removedLayer);
+        }
+        mCreatedLayers.clear();
+        entryProto.mutable_transactions()->Reserve(
+                static_cast<int32_t>(entry.transactionIds.size()));
+        for (const uint64_t& id : entry.transactionIds) {
+            auto it = mQueuedTransactions.find(id);
+            if (it != mQueuedTransactions.end()) {
+                entryProto.mutable_transactions()->Add(std::move(it->second));
+                mQueuedTransactions.erase(it);
+            } else {
+                ALOGW("Could not find transaction id %" PRIu64, id);
+            }
+        }
+
+        std::string serializedProto;
+        entryProto.SerializeToString(&serializedProto);
+        entryProto.Clear();
+        std::vector<std::string> entries = mBuffer.emplace(std::move(serializedProto));
+        removedEntries.reserve(removedEntries.size() + entries.size());
+        removedEntries.insert(removedEntries.end(), std::make_move_iterator(entries.begin()),
+                              std::make_move_iterator(entries.end()));
+
+        entryProto.mutable_removed_layer_handles()->Reserve(
+                static_cast<int32_t>(mRemovedLayerHandles.size()));
+        for (auto& handle : mRemovedLayerHandles) {
+            entryProto.mutable_removed_layer_handles()->Add(handle);
+        }
+        mRemovedLayerHandles.clear();
+    }
+
+    proto::TransactionTraceEntry removedEntryProto;
+    for (const std::string& removedEntry : removedEntries) {
+        removedEntryProto.ParseFromString(removedEntry);
+        updateStartingStateLocked(removedEntryProto);
+        removedEntryProto.Clear();
+    }
+    mTransactionsAddedToBufferCv.notify_one();
+}
+
+void TransactionTracing::flush(int64_t vsyncId) {
+    while (!mPendingTransactions.empty() || !mPendingRemovedLayers.empty()) {
+        tryPushToTracingThread();
+    }
+    std::unique_lock<std::mutex> lock(mTraceLock);
+    base::ScopedLockAssertion assumeLocked(mTraceLock);
+    mTransactionsAddedToBufferCv.wait(lock, [&]() REQUIRES(mTraceLock) {
+        proto::TransactionTraceEntry entry;
+        if (mBuffer.used() > 0) {
+            entry.ParseFromString(mBuffer.back());
+        }
+        return mBuffer.used() > 0 && entry.vsync_id() >= vsyncId;
+    });
+}
+
+void TransactionTracing::onLayerAdded(BBinder* layerHandle, int layerId, const std::string& name,
+                                      uint32_t flags, int parentId) {
+    std::scoped_lock lock(mTraceLock);
+    TracingLayerCreationArgs args{layerId, name, flags, parentId, -1 /* mirrorFromId */};
+    if (mLayerHandles.find(layerHandle) != mLayerHandles.end()) {
+        ALOGW("Duplicate handles found. %p", layerHandle);
+    }
+    mLayerHandles[layerHandle] = layerId;
+    mCreatedLayers.push_back(mProtoParser.toProto(args));
+}
+
+void TransactionTracing::onMirrorLayerAdded(BBinder* layerHandle, int layerId,
+                                            const std::string& name, int mirrorFromId) {
+    std::scoped_lock lock(mTraceLock);
+    TracingLayerCreationArgs args{layerId, name, 0 /* flags */, -1 /* parentId */, mirrorFromId};
+    if (mLayerHandles.find(layerHandle) != mLayerHandles.end()) {
+        ALOGW("Duplicate handles found. %p", layerHandle);
+    }
+    mLayerHandles[layerHandle] = layerId;
+    mCreatedLayers.emplace_back(mProtoParser.toProto(args));
+}
+
+void TransactionTracing::onLayerRemoved(int32_t layerId) {
+    mPendingRemovedLayers.emplace_back(layerId);
+    tryPushToTracingThread();
+}
+
+void TransactionTracing::onHandleRemoved(BBinder* layerHandle) {
+    std::scoped_lock lock(mTraceLock);
+    auto it = mLayerHandles.find(layerHandle);
+    if (it == mLayerHandles.end()) {
+        ALOGW("handle not found. %p", layerHandle);
+        return;
+    }
+
+    mRemovedLayerHandles.push_back(it->second);
+    mLayerHandles.erase(it);
+}
+
+void TransactionTracing::tryPushToTracingThread() {
+    // Try to acquire the lock from main thread.
+    if (mMainThreadLock.try_lock()) {
+        // We got the lock! Collect any pending transactions and continue.
+        mCommittedTransactions.insert(mCommittedTransactions.end(),
+                                      std::make_move_iterator(mPendingTransactions.begin()),
+                                      std::make_move_iterator(mPendingTransactions.end()));
+        mPendingTransactions.clear();
+        mRemovedLayers.insert(mRemovedLayers.end(), mPendingRemovedLayers.begin(),
+                              mPendingRemovedLayers.end());
+        mPendingRemovedLayers.clear();
+        mTransactionsAvailableCv.notify_one();
+        mMainThreadLock.unlock();
+    } else {
+        ALOGV("Couldn't get lock");
+    }
+}
+
+void TransactionTracing::updateStartingStateLocked(
+        const proto::TransactionTraceEntry& removedEntry) {
+    mStartingTimestamp = removedEntry.elapsed_realtime_nanos();
+    // Keep track of layer starting state so we can reconstruct the layer state as we purge
+    // transactions from the buffer.
+    for (const proto::LayerCreationArgs& addedLayer : removedEntry.added_layers()) {
+        TracingLayerState& startingState = mStartingStates[addedLayer.layer_id()];
+        startingState.layerId = addedLayer.layer_id();
+        mProtoParser.fromProto(addedLayer, startingState.args);
+    }
+
+    // Merge layer states to starting transaction state.
+    for (const proto::TransactionState& transaction : removedEntry.transactions()) {
+        for (const proto::LayerState& layerState : transaction.layer_changes()) {
+            auto it = mStartingStates.find((int32_t)layerState.layer_id());
+            if (it == mStartingStates.end()) {
+                ALOGW("Could not find layer id %d", (int32_t)layerState.layer_id());
+                continue;
+            }
+            mProtoParser.mergeFromProto(layerState, it->second);
+        }
+    }
+
+    // Clean up stale starting states since the layer has been removed and the buffer does not
+    // contain any references to the layer.
+    for (const int32_t removedLayerId : removedEntry.removed_layers()) {
+        mStartingStates.erase(removedLayerId);
+    }
+}
+
+void TransactionTracing::addStartingStateToProtoLocked(proto::TransactionTraceFile& proto) {
+    if (mStartingStates.size() == 0) {
+        return;
+    }
+
+    proto::TransactionTraceEntry* entryProto = proto.add_entry();
+    entryProto->set_elapsed_realtime_nanos(mStartingTimestamp);
+    entryProto->set_vsync_id(0);
+
+    entryProto->mutable_added_layers()->Reserve(static_cast<int32_t>(mStartingStates.size()));
+    for (auto& [layerId, state] : mStartingStates) {
+        entryProto->mutable_added_layers()->Add(mProtoParser.toProto(state.args));
+    }
+
+    proto::TransactionState transactionProto = mProtoParser.toProto(mStartingStates);
+    transactionProto.set_vsync_id(0);
+    transactionProto.set_post_time(mStartingTimestamp);
+    entryProto->mutable_transactions()->Add(std::move(transactionProto));
+}
+
+proto::TransactionTraceFile TransactionTracing::writeToProto() {
+    std::scoped_lock<std::mutex> lock(mTraceLock);
+    proto::TransactionTraceFile proto = createTraceFileProto();
+    addStartingStateToProtoLocked(proto);
+    mBuffer.writeToProto(proto);
+    return proto;
+}
+
+} // namespace android
diff --git a/services/surfaceflinger/Tracing/TransactionTracing.h b/services/surfaceflinger/Tracing/TransactionTracing.h
new file mode 100644
index 0000000..4c291f9
--- /dev/null
+++ b/services/surfaceflinger/Tracing/TransactionTracing.h
@@ -0,0 +1,128 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <android-base/thread_annotations.h>
+#include <layerproto/TransactionProto.h>
+#include <utils/Errors.h>
+#include <utils/Timers.h>
+
+#include <memory>
+#include <mutex>
+#include <thread>
+
+#include "RingBuffer.h"
+#include "LocklessStack.h"
+#include "TransactionProtoParser.h"
+
+using namespace android::surfaceflinger;
+
+namespace android {
+
+class SurfaceFlinger;
+class TransactionTracingTest;
+
+/*
+ * Records all committed transactions into a ring bufffer.
+ *
+ * Transactions come in via the binder thread. They are serialized to proto
+ * and stored in a map using the transaction id as key. Main thread will
+ * pass the list of transaction ids that are committed every vsync and notify
+ * the tracing thread. The tracing thread will then wake up and add the
+ * committed transactions to the ring buffer.
+ *
+ * When generating SF dump state, we will flush the buffer to a file which
+ * will then be included in the bugreport.
+ *
+ */
+class TransactionTracing {
+public:
+    TransactionTracing();
+    ~TransactionTracing();
+
+    void addQueuedTransaction(const TransactionState&);
+    void addCommittedTransactions(std::vector<TransactionState>& transactions, int64_t vsyncId);
+    status_t writeToFile(std::string filename = FILE_NAME);
+    void setBufferSize(size_t bufferSizeInBytes);
+    void onLayerAdded(BBinder* layerHandle, int layerId, const std::string& name, uint32_t flags,
+                      int parentId);
+    void onMirrorLayerAdded(BBinder* layerHandle, int layerId, const std::string& name,
+                            int mirrorFromId);
+    void onLayerRemoved(int layerId);
+    void onHandleRemoved(BBinder* layerHandle);
+    void dump(std::string&) const;
+    static constexpr auto CONTINUOUS_TRACING_BUFFER_SIZE = 512 * 1024;
+    static constexpr auto ACTIVE_TRACING_BUFFER_SIZE = 100 * 1024 * 1024;
+
+private:
+    friend class TransactionTracingTest;
+
+    static constexpr auto FILE_NAME = "/data/misc/wmtrace/transactions_trace.winscope";
+
+    mutable std::mutex mTraceLock;
+    RingBuffer<proto::TransactionTraceFile, proto::TransactionTraceEntry> mBuffer
+            GUARDED_BY(mTraceLock);
+    size_t mBufferSizeInBytes GUARDED_BY(mTraceLock) = CONTINUOUS_TRACING_BUFFER_SIZE;
+    std::unordered_map<uint64_t, proto::TransactionState> mQueuedTransactions
+            GUARDED_BY(mTraceLock);
+    LocklessStack<proto::TransactionState> mTransactionQueue;
+    nsecs_t mStartingTimestamp GUARDED_BY(mTraceLock);
+    std::vector<proto::LayerCreationArgs> mCreatedLayers GUARDED_BY(mTraceLock);
+    std::unordered_map<BBinder* /* layerHandle */, int32_t /* layerId */> mLayerHandles
+            GUARDED_BY(mTraceLock);
+    std::vector<int32_t /* layerId */> mRemovedLayerHandles GUARDED_BY(mTraceLock);
+    std::map<int32_t /* layerId */, TracingLayerState> mStartingStates GUARDED_BY(mTraceLock);
+    TransactionProtoParser mProtoParser GUARDED_BY(mTraceLock);
+    // Parses the transaction to proto without holding any tracing locks so we can generate proto
+    // in the binder thread without any contention.
+    TransactionProtoParser mLockfreeProtoParser;
+
+    // We do not want main thread to block so main thread will try to acquire mMainThreadLock,
+    // otherwise will push data to temporary container.
+    std::mutex mMainThreadLock;
+    std::thread mThread GUARDED_BY(mMainThreadLock);
+    bool mDone GUARDED_BY(mMainThreadLock) = false;
+    std::condition_variable mTransactionsAvailableCv;
+    std::condition_variable mTransactionsAddedToBufferCv;
+    struct CommittedTransactions {
+        std::vector<uint64_t> transactionIds;
+        int64_t vsyncId;
+        int64_t timestamp;
+    };
+    std::vector<CommittedTransactions> mCommittedTransactions GUARDED_BY(mMainThreadLock);
+    std::vector<CommittedTransactions> mPendingTransactions; // only accessed by main thread
+
+    std::vector<int32_t /* layerId */> mRemovedLayers GUARDED_BY(mMainThreadLock);
+    std::vector<int32_t /* layerId */> mPendingRemovedLayers; // only accessed by main thread
+
+    proto::TransactionTraceFile createTraceFileProto() const;
+    void loop();
+    void addEntry(const std::vector<CommittedTransactions>& committedTransactions,
+                  const std::vector<int32_t>& removedLayers) EXCLUDES(mTraceLock);
+    int32_t getLayerIdLocked(const sp<IBinder>& layerHandle) REQUIRES(mTraceLock);
+    void tryPushToTracingThread() EXCLUDES(mMainThreadLock);
+    void addStartingStateToProtoLocked(proto::TransactionTraceFile& proto) REQUIRES(mTraceLock);
+    void updateStartingStateLocked(const proto::TransactionTraceEntry& entry) REQUIRES(mTraceLock);
+
+    // TEST
+    // Wait until all the committed transactions for the specified vsync id are added to the buffer.
+    void flush(int64_t vsyncId) EXCLUDES(mMainThreadLock);
+    // Return buffer contents as trace file proto
+    proto::TransactionTraceFile writeToProto() EXCLUDES(mMainThreadLock);
+};
+
+} // namespace android
diff --git a/services/surfaceflinger/Tracing/tools/Android.bp b/services/surfaceflinger/Tracing/tools/Android.bp
new file mode 100644
index 0000000..e8fe734
--- /dev/null
+++ b/services/surfaceflinger/Tracing/tools/Android.bp
@@ -0,0 +1,55 @@
+// 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.
+
+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_binary {
+    name: "layertracegenerator",
+    defaults: [
+        "libsurfaceflinger_mocks_defaults",
+        "surfaceflinger_defaults",
+        "skia_renderengine_deps",
+    ],
+    srcs: [
+        ":libsurfaceflinger_sources",
+        ":libsurfaceflinger_mock_sources",
+        ":layertracegenerator_sources",
+        "main.cpp",
+    ],
+    static_libs: [
+        "libgtest",
+    ],
+    header_libs: [
+        "libsurfaceflinger_mocks_headers",
+    ],
+}
+
+filegroup {
+    name: "layertracegenerator_sources",
+    srcs: [
+        "LayerTraceGenerator.cpp",
+    ],
+}
+
+cc_library_headers {
+    name: "layertracegenerator_headers",
+    export_include_dirs: ["."],
+}
diff --git a/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp b/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp
new file mode 100644
index 0000000..cf44eff
--- /dev/null
+++ b/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp
@@ -0,0 +1,283 @@
+/*
+ * 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.
+ */
+
+#undef LOG_TAG
+#define LOG_TAG "LayerTraceGenerator"
+
+#include <TestableSurfaceFlinger.h>
+#include <Tracing/TransactionProtoParser.h>
+#include <binder/IPCThreadState.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <gui/LayerState.h>
+#include <log/log.h>
+#include <mock/MockEventThread.h>
+#include <renderengine/ExternalTexture.h>
+#include <renderengine/mock/FakeExternalTexture.h>
+#include <renderengine/mock/RenderEngine.h>
+#include <utils/String16.h>
+#include <string>
+
+#include "LayerTraceGenerator.h"
+
+namespace android {
+
+class Factory final : public surfaceflinger::Factory {
+public:
+    ~Factory() = default;
+
+    std::unique_ptr<HWComposer> createHWComposer(const std::string&) override { return nullptr; }
+
+    std::unique_ptr<scheduler::VsyncConfiguration> createVsyncConfiguration(
+            Fps /*currentRefreshRate*/) override {
+        return std::make_unique<scheduler::FakePhaseOffsets>();
+    }
+
+    sp<SurfaceInterceptor> createSurfaceInterceptor() override {
+        return new android::impl::SurfaceInterceptor();
+    }
+
+    sp<StartPropertySetThread> createStartPropertySetThread(
+            bool /* timestampPropertyValue */) override {
+        return nullptr;
+    }
+
+    sp<DisplayDevice> createDisplayDevice(DisplayDeviceCreationArgs& /* creationArgs */) override {
+        return nullptr;
+    }
+
+    sp<GraphicBuffer> createGraphicBuffer(uint32_t /* width */, uint32_t /* height */,
+                                          PixelFormat /* format */, uint32_t /* layerCount */,
+                                          uint64_t /* usage */,
+                                          std::string /* requestorName */) override {
+        return nullptr;
+    }
+
+    void createBufferQueue(sp<IGraphicBufferProducer>* /* outProducer */,
+                           sp<IGraphicBufferConsumer>* /* outConsumer */,
+                           bool /* consumerIsSurfaceFlinger */) override {}
+
+    sp<IGraphicBufferProducer> createMonitoredProducer(
+            const sp<IGraphicBufferProducer>& /* producer */,
+            const sp<SurfaceFlinger>& /* flinger */, const wp<Layer>& /* layer */) override {
+        return nullptr;
+    }
+
+    sp<BufferLayerConsumer> createBufferLayerConsumer(
+            const sp<IGraphicBufferConsumer>& /* consumer */,
+            renderengine::RenderEngine& /* renderEngine */, uint32_t /* textureName */,
+            Layer* /* layer */) override {
+        return nullptr;
+    }
+
+    std::unique_ptr<surfaceflinger::NativeWindowSurface> createNativeWindowSurface(
+            const sp<IGraphicBufferProducer>& /* producer */) override {
+        return nullptr;
+    }
+
+    std::unique_ptr<compositionengine::CompositionEngine> createCompositionEngine() override {
+        return compositionengine::impl::createCompositionEngine();
+    }
+
+    sp<ContainerLayer> createContainerLayer(const LayerCreationArgs& args) {
+        return sp<ContainerLayer>::make(args);
+    }
+
+    sp<BufferStateLayer> createBufferStateLayer(const LayerCreationArgs& args) {
+        return new BufferStateLayer(args);
+    }
+
+    sp<EffectLayer> createEffectLayer(const LayerCreationArgs& args) {
+        return new EffectLayer(args);
+    }
+
+    sp<BufferQueueLayer> createBufferQueueLayer(const LayerCreationArgs&) override {
+        return nullptr;
+    }
+
+    std::unique_ptr<FrameTracer> createFrameTracer() override {
+        return std::make_unique<testing::NiceMock<mock::FrameTracer>>();
+    }
+
+    std::unique_ptr<frametimeline::FrameTimeline> createFrameTimeline(
+            std::shared_ptr<TimeStats> timeStats, pid_t surfaceFlingerPid = 0) override {
+        return std::make_unique<testing::NiceMock<mock::FrameTimeline>>(timeStats,
+                                                                        surfaceFlingerPid);
+    }
+};
+
+class MockSurfaceFlinger : public SurfaceFlinger {
+public:
+    MockSurfaceFlinger(Factory& factory)
+          : SurfaceFlinger(factory, SurfaceFlinger::SkipInitialization) {}
+    std::shared_ptr<renderengine::ExternalTexture> getExternalTextureFromBufferData(
+            const BufferData& bufferData, const char* /* layerName */) const override {
+        return std::make_shared<renderengine::mock::FakeExternalTexture>(bufferData.getWidth(),
+                                                                         bufferData.getHeight(),
+                                                                         bufferData.getId(),
+                                                                         bufferData
+                                                                                 .getPixelFormat(),
+                                                                         bufferData.getUsage());
+    };
+
+    // b/220017192 migrate from transact codes to ISurfaceComposer apis
+    void setLayerTracingFlags(int32_t flags) {
+        Parcel data;
+        Parcel reply;
+        data.writeInterfaceToken(String16("android.ui.ISurfaceComposer"));
+        data.writeInt32(flags);
+        transact(1033, data, &reply, 0 /* flags */);
+    }
+
+    void startLayerTracing(int64_t traceStartTime) {
+        Parcel data;
+        Parcel reply;
+        data.writeInterfaceToken(String16("android.ui.ISurfaceComposer"));
+        data.writeInt32(1);
+        data.writeInt64(traceStartTime);
+        transact(1025, data, &reply, 0 /* flags */);
+    }
+
+    void stopLayerTracing(const char* tracePath) {
+        Parcel data;
+        Parcel reply;
+        data.writeInterfaceToken(String16("android.ui.ISurfaceComposer"));
+        data.writeInt32(2);
+        data.writeCString(tracePath);
+        transact(1025, data, &reply, 0 /* flags */);
+    }
+};
+
+class TraceGenFlingerDataMapper : public TransactionProtoParser::FlingerDataMapper {
+public:
+    std::unordered_map<int32_t /*layerId*/, sp<IBinder> /* handle */> mLayerHandles;
+    sp<IBinder> getLayerHandle(int32_t layerId) const override {
+        if (layerId == -1) {
+            ALOGE("Error: Called with layer=%d", layerId);
+            return nullptr;
+        }
+        auto it = mLayerHandles.find(layerId);
+        if (it == mLayerHandles.end()) {
+            ALOGE("Error: Could not find handle for layer=%d", layerId);
+            return nullptr;
+        }
+        return it->second;
+    }
+};
+
+bool LayerTraceGenerator::generate(const proto::TransactionTraceFile& traceFile,
+                                   const char* outputLayersTracePath) {
+    if (traceFile.entry_size() == 0) {
+        return false;
+    }
+
+    Factory mFactory;
+    sp<MockSurfaceFlinger> flinger = new MockSurfaceFlinger(mFactory);
+    TestableSurfaceFlinger mFlinger(flinger);
+    mFlinger.setupRenderEngine(
+            std::make_unique<testing::NiceMock<renderengine::mock::RenderEngine>>());
+    mock::VsyncController* mVsyncController = new testing::NiceMock<mock::VsyncController>();
+    mock::VSyncTracker* mVSyncTracker = new testing::NiceMock<mock::VSyncTracker>();
+    mock::EventThread* mEventThread = new testing::NiceMock<mock::EventThread>();
+    mock::EventThread* mSFEventThread = new testing::NiceMock<mock::EventThread>();
+    mFlinger.setupScheduler(std::unique_ptr<scheduler::VsyncController>(mVsyncController),
+                            std::unique_ptr<scheduler::VSyncTracker>(mVSyncTracker),
+                            std::unique_ptr<EventThread>(mEventThread),
+                            std::unique_ptr<EventThread>(mSFEventThread),
+                            TestableSurfaceFlinger::SchedulerCallbackImpl::kNoOp,
+                            TestableSurfaceFlinger::kOneDisplayMode, true /* useNiceMock */);
+
+    Hwc2::mock::Composer* mComposer = new testing::NiceMock<Hwc2::mock::Composer>();
+    mFlinger.setupComposer(std::unique_ptr<Hwc2::Composer>(mComposer));
+    mFlinger.mutableMaxRenderTargetSize() = 16384;
+
+    flinger->setLayerTracingFlags(LayerTracing::TRACE_INPUT | LayerTracing::TRACE_BUFFERS);
+    flinger->startLayerTracing(traceFile.entry(0).elapsed_realtime_nanos());
+    std::unique_ptr<TraceGenFlingerDataMapper> mapper =
+            std::make_unique<TraceGenFlingerDataMapper>();
+    TraceGenFlingerDataMapper* dataMapper = mapper.get();
+    TransactionProtoParser parser(std::move(mapper));
+
+    nsecs_t frameTime;
+    int64_t vsyncId;
+    ALOGD("Generating %d transactions...", traceFile.entry_size());
+    for (int i = 0; i < traceFile.entry_size(); i++) {
+        proto::TransactionTraceEntry entry = traceFile.entry(i);
+        ALOGV("    Entry %04d/%04d for time=%" PRId64 " vsyncid=%" PRId64
+              " layers +%d -%d transactions=%d",
+              i, traceFile.entry_size(), entry.elapsed_realtime_nanos(), entry.vsync_id(),
+              entry.added_layers_size(), entry.removed_layers_size(), entry.transactions_size());
+
+        for (int j = 0; j < entry.added_layers_size(); j++) {
+            // create layers
+            TracingLayerCreationArgs tracingArgs;
+            parser.fromProto(entry.added_layers(j), tracingArgs);
+
+            sp<IBinder> outHandle;
+            int32_t outLayerId;
+            LayerCreationArgs args(mFlinger.flinger(), nullptr /* client */, tracingArgs.name,
+                                   tracingArgs.flags, LayerMetadata());
+            args.sequence = std::make_optional<int32_t>(tracingArgs.layerId);
+
+            if (tracingArgs.mirrorFromId == -1) {
+                sp<IBinder> parentHandle = nullptr;
+                if ((tracingArgs.parentId != -1) &&
+                    (dataMapper->mLayerHandles.find(tracingArgs.parentId) ==
+                     dataMapper->mLayerHandles.end())) {
+                    args.addToRoot = false;
+                } else {
+                    parentHandle = dataMapper->getLayerHandle(tracingArgs.parentId);
+                }
+                mFlinger.createLayer(args, &outHandle, parentHandle, &outLayerId,
+                                     nullptr /* parentLayer */, nullptr /* outTransformHint */);
+            } else {
+                sp<IBinder> mirrorFromHandle = dataMapper->getLayerHandle(tracingArgs.mirrorFromId);
+                mFlinger.mirrorLayer(args, mirrorFromHandle, &outHandle, &outLayerId);
+            }
+            LOG_ALWAYS_FATAL_IF(outLayerId != tracingArgs.layerId,
+                                "Could not create layer expected:%d actual:%d", tracingArgs.layerId,
+                                outLayerId);
+            dataMapper->mLayerHandles[tracingArgs.layerId] = outHandle;
+        }
+
+        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);
+        }
+
+        for (int j = 0; j < entry.removed_layer_handles_size(); j++) {
+            dataMapper->mLayerHandles.erase(entry.removed_layer_handles(j));
+        }
+
+        frameTime = entry.elapsed_realtime_nanos();
+        vsyncId = entry.vsync_id();
+        mFlinger.commit(frameTime, vsyncId);
+    }
+
+    flinger->stopLayerTracing(outputLayersTracePath);
+    ALOGD("End of generating trace file. File written to %s", outputLayersTracePath);
+    dataMapper->mLayerHandles.clear();
+    return true;
+}
+
+} // namespace android
\ No newline at end of file
diff --git a/services/surfaceflinger/DisplayHardware/Hash.h b/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.h
similarity index 71%
copy from services/surfaceflinger/DisplayHardware/Hash.h
copy to services/surfaceflinger/Tracing/tools/LayerTraceGenerator.h
index a7b6c71..ee1ea6c 100644
--- a/services/surfaceflinger/DisplayHardware/Hash.h
+++ b/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.h
@@ -1,5 +1,5 @@
 /*
- * Copyright 2021 The Android Open Source Project
+ * 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.
@@ -16,12 +16,11 @@
 
 #pragma once
 
-#include <cstdint>
-#include <string_view>
+#include <Tracing/TransactionTracing.h>
 
 namespace android {
-
-// CityHash64 implementation that only hashes at most the first 16 characters of the given string.
-uint64_t cityHash64Len0To16(std::string_view sv);
-
+class LayerTraceGenerator {
+public:
+    bool generate(const proto::TransactionTraceFile&, const char* outputLayersTracePath);
+};
 } // namespace android
\ No newline at end of file
diff --git a/services/surfaceflinger/Tracing/tools/main.cpp b/services/surfaceflinger/Tracing/tools/main.cpp
new file mode 100644
index 0000000..f3cf42d
--- /dev/null
+++ b/services/surfaceflinger/Tracing/tools/main.cpp
@@ -0,0 +1,60 @@
+/*
+ * 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.
+ */
+
+#undef LOG_TAG
+#define LOG_TAG "LayerTraceGenerator"
+
+#include <fstream>
+#include <iostream>
+#include <string>
+
+#include "LayerTraceGenerator.h"
+
+using namespace android;
+
+int main(int argc, char** argv) {
+    if (argc > 3) {
+        std::cout << "Usage: " << argv[0]
+                  << " [transaction-trace-path] [output-layers-trace-path]\n";
+        return -1;
+    }
+
+    const char* transactionTracePath =
+            (argc > 1) ? argv[1] : "/data/misc/wmtrace/transactions_trace.winscope";
+    std::cout << "Parsing " << transactionTracePath << "\n";
+    std::fstream input(transactionTracePath, std::ios::in | std::ios::binary);
+    if (!input) {
+        std::cout << "Error: Could not open " << transactionTracePath;
+        return -1;
+    }
+
+    proto::TransactionTraceFile transactionTraceFile;
+    if (!transactionTraceFile.ParseFromIstream(&input)) {
+        std::cout << "Error: Failed to parse " << transactionTracePath;
+        return -1;
+    }
+
+    const char* outputLayersTracePath =
+            (argc == 3) ? argv[2] : "/data/misc/wmtrace/layers_trace.winscope";
+    ;
+    ALOGD("Generating %s...", outputLayersTracePath);
+    std::cout << "Generating " << outputLayersTracePath << "\n";
+    if (!LayerTraceGenerator().generate(transactionTraceFile, outputLayersTracePath)) {
+        std::cout << "Error: Failed to generate layers trace " << outputLayersTracePath;
+        return -1;
+    }
+    return 0;
+}
\ No newline at end of file
diff --git a/services/surfaceflinger/Tracing/tools/readme.md b/services/surfaceflinger/Tracing/tools/readme.md
new file mode 100644
index 0000000..143c14f
--- /dev/null
+++ b/services/surfaceflinger/Tracing/tools/readme.md
@@ -0,0 +1,13 @@
+### LayerTraceGenerator ###
+
+Generates layer traces from transaction traces. The tool is a custom
+surface flinger build that mocks out everything else apart from the
+front end logic. Transaction traces are written when the transaction
+is applied, along wth a timestamp and vsync id. The transactions
+are parsed from proto and applied to recreate the layer state. The
+result is then written as a layer trace.
+
+Usage:
+1. build and push to device
+2. run ./layertracegenerator [transaction-trace-path] [output-layers-trace-path]
+
diff --git a/services/surfaceflinger/Tracing/tools/run.sh b/services/surfaceflinger/Tracing/tools/run.sh
new file mode 100644
index 0000000..baa93f1
--- /dev/null
+++ b/services/surfaceflinger/Tracing/tools/run.sh
@@ -0,0 +1,11 @@
+#!/usr/bin/env bash
+
+set -ex
+
+# Build, push and run layertracegenerator
+$ANDROID_BUILD_TOP/build/soong/soong_ui.bash --make-mode layertracegenerator
+adb wait-for-device && adb push $OUT/system/bin/layertracegenerator /data/layertracegenerator
+echo "Writing transaction trace to file"
+adb shell service call SurfaceFlinger 1041 i32 0
+adb shell /data/layertracegenerator
+adb pull /data/misc/wmtrace/layers_trace.winscope
\ No newline at end of file
diff --git a/services/surfaceflinger/TransactionCallbackInvoker.cpp b/services/surfaceflinger/TransactionCallbackInvoker.cpp
index 6af69f0..e1f348f 100644
--- a/services/surfaceflinger/TransactionCallbackInvoker.cpp
+++ b/services/surfaceflinger/TransactionCallbackInvoker.cpp
@@ -24,6 +24,7 @@
 #define ATRACE_TAG ATRACE_TAG_GRAPHICS
 
 #include "TransactionCallbackInvoker.h"
+#include "BackgroundExecutor.h"
 
 #include <cinttypes>
 
@@ -49,121 +50,25 @@
     return !callbacks.empty() && callbacks.front().type == CallbackId::Type::ON_COMMIT;
 }
 
-TransactionCallbackInvoker::~TransactionCallbackInvoker() {
-    {
-        std::lock_guard lock(mMutex);
-        for (const auto& [listener, transactionStats] : mCompletedTransactions) {
-            listener->unlinkToDeath(mDeathRecipient);
-        }
-    }
-}
-
-status_t TransactionCallbackInvoker::startRegistration(const ListenerCallbacks& listenerCallbacks) {
-    std::lock_guard lock(mMutex);
-
-    auto [itr, inserted] = mRegisteringTransactions.insert(listenerCallbacks);
+void TransactionCallbackInvoker::addEmptyTransaction(const ListenerCallbacks& listenerCallbacks) {
     auto& [listener, callbackIds] = listenerCallbacks;
-
-    if (inserted) {
-        if (mCompletedTransactions.count(listener) == 0) {
-            status_t err = listener->linkToDeath(mDeathRecipient);
-            if (err != NO_ERROR) {
-                ALOGE("cannot add callback because linkToDeath failed, err: %d", err);
-                return err;
-            }
-        }
-        auto& transactionStatsDeque = mCompletedTransactions[listener];
-        transactionStatsDeque.emplace_back(callbackIds);
-    }
-
-    return NO_ERROR;
+    auto& transactionStatsDeque = mCompletedTransactions[listener];
+    transactionStatsDeque.emplace_back(callbackIds);
 }
 
-status_t TransactionCallbackInvoker::endRegistration(const ListenerCallbacks& listenerCallbacks) {
-    std::lock_guard lock(mMutex);
-
-    auto itr = mRegisteringTransactions.find(listenerCallbacks);
-    if (itr == mRegisteringTransactions.end()) {
-        ALOGE("cannot end a registration that does not exist");
-        return BAD_VALUE;
-    }
-
-    mRegisteringTransactions.erase(itr);
-
-    return NO_ERROR;
-}
-
-bool TransactionCallbackInvoker::isRegisteringTransaction(
-        const sp<IBinder>& transactionListener, const std::vector<CallbackId>& callbackIds) {
-    ListenerCallbacks listenerCallbacks(transactionListener, callbackIds);
-
-    auto itr = mRegisteringTransactions.find(listenerCallbacks);
-    return itr != mRegisteringTransactions.end();
-}
-
-status_t TransactionCallbackInvoker::registerPendingCallbackHandle(
-        const sp<CallbackHandle>& handle) {
-    std::lock_guard lock(mMutex);
-
-    // If we can't find the transaction stats something has gone wrong. The client should call
-    // startRegistration before trying to register a pending callback handle.
-    TransactionStats* transactionStats;
-    status_t err = findTransactionStats(handle->listener, handle->callbackIds, &transactionStats);
-    if (err != NO_ERROR) {
-        ALOGE("cannot find transaction stats");
-        return err;
-    }
-
-    mPendingTransactions[handle->listener][handle->callbackIds]++;
-    return NO_ERROR;
-}
-
-status_t TransactionCallbackInvoker::finalizeCallbackHandle(const sp<CallbackHandle>& handle,
-                                                            const std::vector<JankData>& jankData) {
-    auto listener = mPendingTransactions.find(handle->listener);
-    if (listener != mPendingTransactions.end()) {
-        auto& pendingCallbacks = listener->second;
-        auto pendingCallback = pendingCallbacks.find(handle->callbackIds);
-
-        if (pendingCallback != pendingCallbacks.end()) {
-            auto& pendingCount = pendingCallback->second;
-
-            // Decrease the pending count for this listener
-            if (--pendingCount == 0) {
-                pendingCallbacks.erase(pendingCallback);
-            }
-        } else {
-            ALOGW("there are more latched callbacks than there were registered callbacks");
-        }
-        if (listener->second.size() == 0) {
-            mPendingTransactions.erase(listener);
-        }
-    } else {
-        ALOGW("cannot find listener in mPendingTransactions");
-    }
-
-    status_t err = addCallbackHandle(handle, jankData);
-    if (err != NO_ERROR) {
-        ALOGE("could not add callback handle");
-        return err;
-    }
-    return NO_ERROR;
-}
-
-status_t TransactionCallbackInvoker::finalizeOnCommitCallbackHandles(
+status_t TransactionCallbackInvoker::addOnCommitCallbackHandles(
         const std::deque<sp<CallbackHandle>>& handles,
         std::deque<sp<CallbackHandle>>& outRemainingHandles) {
     if (handles.empty()) {
         return NO_ERROR;
     }
-    std::lock_guard lock(mMutex);
     const std::vector<JankData>& jankData = std::vector<JankData>();
     for (const auto& handle : handles) {
         if (!containsOnCommitCallbacks(handle->callbackIds)) {
             outRemainingHandles.push_back(handle);
             continue;
         }
-        status_t err = finalizeCallbackHandle(handle, jankData);
+        status_t err = addCallbackHandle(handle, jankData);
         if (err != NO_ERROR) {
             return err;
         }
@@ -172,14 +77,13 @@
     return NO_ERROR;
 }
 
-status_t TransactionCallbackInvoker::finalizePendingCallbackHandles(
+status_t TransactionCallbackInvoker::addCallbackHandles(
         const std::deque<sp<CallbackHandle>>& handles, const std::vector<JankData>& jankData) {
     if (handles.empty()) {
         return NO_ERROR;
     }
-    std::lock_guard lock(mMutex);
     for (const auto& handle : handles) {
-        status_t err = finalizeCallbackHandle(handle, jankData);
+        status_t err = addCallbackHandle(handle, jankData);
         if (err != NO_ERROR) {
             return err;
         }
@@ -190,12 +94,10 @@
 
 status_t TransactionCallbackInvoker::registerUnpresentedCallbackHandle(
         const sp<CallbackHandle>& handle) {
-    std::lock_guard lock(mMutex);
-
     return addCallbackHandle(handle, std::vector<JankData>());
 }
 
-status_t TransactionCallbackInvoker::findTransactionStats(
+status_t TransactionCallbackInvoker::findOrCreateTransactionStats(
         const sp<IBinder>& listener, const std::vector<CallbackId>& callbackIds,
         TransactionStats** outTransactionStats) {
     auto& transactionStatsDeque = mCompletedTransactions[listener];
@@ -208,9 +110,8 @@
             return NO_ERROR;
         }
     }
-
-    ALOGE("could not find transaction stats");
-    return BAD_VALUE;
+    *outTransactionStats = &transactionStatsDeque.emplace_back(callbackIds);
+    return NO_ERROR;
 }
 
 status_t TransactionCallbackInvoker::addCallbackHandle(const sp<CallbackHandle>& handle,
@@ -218,7 +119,8 @@
     // If we can't find the transaction stats something has gone wrong. The client should call
     // startRegistration before trying to add a callback handle.
     TransactionStats* transactionStats;
-    status_t err = findTransactionStats(handle->listener, handle->callbackIds, &transactionStats);
+    status_t err =
+            findOrCreateTransactionStats(handle->listener, handle->callbackIds, &transactionStats);
     if (err != NO_ERROR) {
         return err;
     }
@@ -229,11 +131,43 @@
     // destroyed the client side is dead and there won't be anyone to send the callback to.
     sp<IBinder> surfaceControl = handle->surfaceControl.promote();
     if (surfaceControl) {
+        sp<Fence> prevFence = nullptr;
+
+        for (const auto& futureStruct : handle->previousReleaseFences) {
+            sp<Fence> currentFence = sp<Fence>::make(dup(futureStruct.get().drawFence));
+            if (prevFence == nullptr && currentFence->getStatus() != Fence::Status::Invalid) {
+                prevFence = currentFence;
+                handle->previousReleaseFence = prevFence;
+            } else if (prevFence != nullptr) {
+                // If both fences are signaled or both are unsignaled, we need to merge
+                // them to get an accurate timestamp.
+                if (prevFence->getStatus() != Fence::Status::Invalid &&
+                    prevFence->getStatus() == currentFence->getStatus()) {
+                    char fenceName[32] = {};
+                    snprintf(fenceName, 32, "%.28s", handle->name.c_str());
+                    sp<Fence> mergedFence = Fence::merge(fenceName, prevFence, currentFence);
+                    if (mergedFence->isValid()) {
+                        handle->previousReleaseFence = mergedFence;
+                        prevFence = handle->previousReleaseFence;
+                    }
+                } else if (currentFence->getStatus() == Fence::Status::Unsignaled) {
+                    // If one fence has signaled and the other hasn't, the unsignaled
+                    // fence will approximately correspond with the correct timestamp.
+                    // There's a small race if both fences signal at about the same time
+                    // and their statuses are retrieved with unfortunate timing. However,
+                    // by this point, they will have both signaled and only the timestamp
+                    // will be slightly off; any dependencies after this point will
+                    // already have been met.
+                    handle->previousReleaseFence = currentFence;
+                }
+            }
+        }
+        handle->previousReleaseFences = {};
         FrameEventHistoryStats eventStats(handle->frameNumber,
                                           handle->gpuCompositionDoneFence->getSnapshot().fence,
                                           handle->compositorTiming, handle->refreshStartTime,
                                           handle->dequeueReadyTime);
-        transactionStats->surfaceStats.emplace_back(surfaceControl, handle->acquireTime,
+        transactionStats->surfaceStats.emplace_back(surfaceControl, handle->acquireTimeOrFence,
                                                     handle->previousReleaseFence,
                                                     handle->transformHint,
                                                     handle->currentMaxAcquiredBufferCount,
@@ -244,13 +178,10 @@
 }
 
 void TransactionCallbackInvoker::addPresentFence(const sp<Fence>& presentFence) {
-    std::lock_guard<std::mutex> lock(mMutex);
     mPresentFence = presentFence;
 }
 
-void TransactionCallbackInvoker::sendCallbacks() {
-    std::lock_guard lock(mMutex);
-
+void TransactionCallbackInvoker::sendCallbacks(bool onCommitOnly) {
     // For each listener
     auto completedTransactionsItr = mCompletedTransactions.begin();
     while (completedTransactionsItr != mCompletedTransactions.end()) {
@@ -262,28 +193,14 @@
         auto transactionStatsItr = transactionStatsDeque.begin();
         while (transactionStatsItr != transactionStatsDeque.end()) {
             auto& transactionStats = *transactionStatsItr;
-
-            // If this transaction is still registering, it is not safe to send a callback
-            // because there could be surface controls that haven't been added to
-            // transaction stats or mPendingTransactions.
-            if (isRegisteringTransaction(listener, transactionStats.callbackIds)) {
-                break;
-            }
-
-            // If we are still waiting on the callback handles for this transaction, stop
-            // here because all transaction callbacks for the same listener must come in order
-            auto pendingTransactions = mPendingTransactions.find(listener);
-            if (pendingTransactions != mPendingTransactions.end() &&
-                pendingTransactions->second.count(transactionStats.callbackIds) != 0) {
-                break;
+            if (onCommitOnly && !containsOnCommitCallbacks(transactionStats.callbackIds)) {
+                transactionStatsItr++;
+                continue;
             }
 
             // If the transaction has been latched
             if (transactionStats.latchTime >= 0 &&
                 !containsOnCommitCallbacks(transactionStats.callbackIds)) {
-                if (!mPresentFence) {
-                    break;
-                }
                 transactionStats.presentFence = mPresentFence;
             }
 
@@ -301,22 +218,13 @@
                 // keep it as an IBinder due to consistency reasons: if we
                 // interface_cast at the IPC boundary when reading a Parcel,
                 // we get pointers that compare unequal in the SF process.
-                interface_cast<ITransactionCompletedListener>(listenerStats.listener)
-                        ->onTransactionCompleted(listenerStats);
-                if (transactionStatsDeque.empty()) {
-                    listener->unlinkToDeath(mDeathRecipient);
-                    completedTransactionsItr =
-                            mCompletedTransactions.erase(completedTransactionsItr);
-                } else {
-                    completedTransactionsItr++;
-                }
-            } else {
-                completedTransactionsItr =
-                        mCompletedTransactions.erase(completedTransactionsItr);
+                BackgroundExecutor::getInstance().execute([stats = std::move(listenerStats)]() {
+                    interface_cast<ITransactionCompletedListener>(stats.listener)
+                            ->onTransactionCompleted(stats);
+                });
             }
-        } else {
-            completedTransactionsItr++;
         }
+        completedTransactionsItr++;
     }
 
     if (mPresentFence) {
diff --git a/services/surfaceflinger/TransactionCallbackInvoker.h b/services/surfaceflinger/TransactionCallbackInvoker.h
index 6f4d812..a68cd87 100644
--- a/services/surfaceflinger/TransactionCallbackInvoker.h
+++ b/services/surfaceflinger/TransactionCallbackInvoker.h
@@ -18,7 +18,9 @@
 
 #include <condition_variable>
 #include <deque>
+#include <future>
 #include <mutex>
+#include <queue>
 #include <thread>
 #include <unordered_map>
 #include <unordered_set>
@@ -27,6 +29,7 @@
 
 #include <binder/IBinder.h>
 #include <gui/ITransactionCompletedListener.h>
+#include <renderengine/RenderEngine.h>
 #include <ui/Fence.h>
 
 namespace android {
@@ -41,8 +44,10 @@
     wp<IBinder> surfaceControl;
 
     bool releasePreviousBuffer = false;
+    std::string name;
     sp<Fence> previousReleaseFence;
-    nsecs_t acquireTime = -1;
+    std::vector<std::shared_future<renderengine::RenderEngineResult>> previousReleaseFences;
+    std::variant<nsecs_t, sp<Fence>> acquireTimeOrFence = -1;
     nsecs_t latchTime = -1;
     uint32_t transformHint = 0;
     uint32_t currentMaxAcquiredBufferCount = 0;
@@ -56,79 +61,36 @@
 
 class TransactionCallbackInvoker {
 public:
-    ~TransactionCallbackInvoker();
-
-    // Adds listener and callbackIds in case there are no SurfaceControls that are supposed
-    // to be included in the callback. This functions should be call before attempting to register
-    // any callback handles.
-    status_t startRegistration(const ListenerCallbacks& listenerCallbacks);
-    // Ends the registration. After this is called, no more CallbackHandles will be registered.
-    // It is safe to send a callback if the Transaction doesn't have any Pending callback handles.
-    status_t endRegistration(const ListenerCallbacks& listenerCallbacks);
-
-    // Informs the TransactionCallbackInvoker that there is a Transaction with a CallbackHandle
-    // that needs to be latched and presented this frame. This function should be called once the
-    // layer has received the CallbackHandle so the TransactionCallbackInvoker knows not to send
-    // a callback for that Listener/Transaction pair until that CallbackHandle has been latched and
-    // presented.
-    status_t registerPendingCallbackHandle(const sp<CallbackHandle>& handle);
-    // Notifies the TransactionCallbackInvoker that a pending CallbackHandle has been presented.
-    status_t finalizePendingCallbackHandles(const std::deque<sp<CallbackHandle>>& handles,
-                                            const std::vector<JankData>& jankData);
-    status_t finalizeOnCommitCallbackHandles(const std::deque<sp<CallbackHandle>>& handles,
+    status_t addCallbackHandles(const std::deque<sp<CallbackHandle>>& handles,
+                                const std::vector<JankData>& jankData);
+    status_t addOnCommitCallbackHandles(const std::deque<sp<CallbackHandle>>& handles,
                                              std::deque<sp<CallbackHandle>>& outRemainingHandles);
 
     // Adds the Transaction CallbackHandle from a layer that does not need to be relatched and
     // presented this frame.
     status_t registerUnpresentedCallbackHandle(const sp<CallbackHandle>& handle);
+    void addEmptyTransaction(const ListenerCallbacks& listenerCallbacks);
 
     void addPresentFence(const sp<Fence>& presentFence);
 
-    void sendCallbacks();
-
-private:
-
-    bool isRegisteringTransaction(const sp<IBinder>& transactionListener,
-                                  const std::vector<CallbackId>& callbackIds) REQUIRES(mMutex);
-
-    status_t findTransactionStats(const sp<IBinder>& listener,
-                                  const std::vector<CallbackId>& callbackIds,
-                                  TransactionStats** outTransactionStats) REQUIRES(mMutex);
+    void sendCallbacks(bool onCommitOnly);
+    void clearCompletedTransactions() {
+        mCompletedTransactions.clear();
+    }
 
     status_t addCallbackHandle(const sp<CallbackHandle>& handle,
-                               const std::vector<JankData>& jankData) REQUIRES(mMutex);
+                               const std::vector<JankData>& jankData);
 
-    status_t finalizeCallbackHandle(const sp<CallbackHandle>& handle,
-                                    const std::vector<JankData>& jankData) REQUIRES(mMutex);
 
-    class CallbackDeathRecipient : public IBinder::DeathRecipient {
-    public:
-        // This function is a no-op. isBinderAlive needs a linked DeathRecipient to work.
-        // Death recipients needs a binderDied function.
-        //
-        // (isBinderAlive checks if BpBinder's mAlive is 0. mAlive is only set to 0 in sendObituary.
-        // sendObituary is only called if linkToDeath was called with a DeathRecipient.)
-        void binderDied(const wp<IBinder>& /*who*/) override {}
-    };
-    sp<CallbackDeathRecipient> mDeathRecipient =
-        new CallbackDeathRecipient();
-
-    std::mutex mMutex;
-    std::condition_variable_any mConditionVariable;
-
-    std::unordered_set<ListenerCallbacks, ListenerCallbacksHash> mRegisteringTransactions
-            GUARDED_BY(mMutex);
-
-    std::unordered_map<
-            sp<IBinder>,
-            std::unordered_map<std::vector<CallbackId>, uint32_t /*count*/, CallbackIdsHash>,
-            IListenerHash>
-            mPendingTransactions GUARDED_BY(mMutex);
+private:
+    status_t findOrCreateTransactionStats(const sp<IBinder>& listener,
+                                          const std::vector<CallbackId>& callbackIds,
+                                          TransactionStats** outTransactionStats);
 
     std::unordered_map<sp<IBinder>, std::deque<TransactionStats>, IListenerHash>
-            mCompletedTransactions GUARDED_BY(mMutex);
+        mCompletedTransactions;
 
-    sp<Fence> mPresentFence GUARDED_BY(mMutex);
+    sp<Fence> mPresentFence;
 };
 
 } // namespace android
diff --git a/services/surfaceflinger/TransactionState.h b/services/surfaceflinger/TransactionState.h
new file mode 100644
index 0000000..04ca347
--- /dev/null
+++ b/services/surfaceflinger/TransactionState.h
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <gui/LayerState.h>
+
+namespace android {
+class CountDownLatch;
+
+struct TransactionState {
+    TransactionState(const FrameTimelineInfo& frameTimelineInfo,
+                     const Vector<ComposerState>& composerStates,
+                     const Vector<DisplayState>& displayStates, uint32_t transactionFlags,
+                     const sp<IBinder>& applyToken, const InputWindowCommands& inputWindowCommands,
+                     int64_t desiredPresentTime, bool isAutoTimestamp,
+                     const client_cache_t& uncacheBuffer, int64_t postTime, uint32_t permissions,
+                     bool hasListenerCallbacks, std::vector<ListenerCallbacks> listenerCallbacks,
+                     int originPid, int originUid, uint64_t transactionId)
+          : frameTimelineInfo(frameTimelineInfo),
+            states(composerStates),
+            displays(displayStates),
+            flags(transactionFlags),
+            applyToken(applyToken),
+            inputWindowCommands(inputWindowCommands),
+            desiredPresentTime(desiredPresentTime),
+            isAutoTimestamp(isAutoTimestamp),
+            buffer(uncacheBuffer),
+            postTime(postTime),
+            permissions(permissions),
+            hasListenerCallbacks(hasListenerCallbacks),
+            listenerCallbacks(listenerCallbacks),
+            originPid(originPid),
+            originUid(originUid),
+            id(transactionId) {}
+
+    TransactionState() {}
+
+    void traverseStatesWithBuffers(std::function<void(const layer_state_t&)> visitor);
+
+    FrameTimelineInfo frameTimelineInfo;
+    Vector<ComposerState> states;
+    Vector<DisplayState> displays;
+    uint32_t flags;
+    sp<IBinder> applyToken;
+    InputWindowCommands inputWindowCommands;
+    int64_t desiredPresentTime;
+    bool isAutoTimestamp;
+    client_cache_t buffer;
+    int64_t postTime;
+    uint32_t permissions;
+    bool hasListenerCallbacks;
+    std::vector<ListenerCallbacks> listenerCallbacks;
+    int originPid;
+    int originUid;
+    uint64_t id;
+    std::shared_ptr<CountDownLatch> transactionCommittedSignal;
+};
+
+class CountDownLatch {
+public:
+    enum {
+        eSyncTransaction = 1 << 0,
+        eSyncInputWindows = 1 << 1,
+    };
+    explicit CountDownLatch(uint32_t flags) : mFlags(flags) {}
+
+    // True if there is no waiting condition after count down.
+    bool countDown(uint32_t flag) {
+        std::unique_lock<std::mutex> lock(mMutex);
+        if (mFlags == 0) {
+            return true;
+        }
+        mFlags &= ~flag;
+        if (mFlags == 0) {
+            mCountDownComplete.notify_all();
+            return true;
+        }
+        return false;
+    }
+
+    // Return true if triggered.
+    bool wait_until(const std::chrono::nanoseconds& timeout) const {
+        std::unique_lock<std::mutex> lock(mMutex);
+        const auto untilTime = std::chrono::system_clock::now() + timeout;
+        while (mFlags != 0) {
+            // Conditional variables can be woken up sporadically, so we check count
+            // to verify the wakeup was triggered by |countDown|.
+            if (std::cv_status::timeout == mCountDownComplete.wait_until(lock, untilTime)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+private:
+    uint32_t mFlags;
+    mutable std::condition_variable mCountDownComplete;
+    mutable std::mutex mMutex;
+};
+
+} // namespace android
diff --git a/services/surfaceflinger/TunnelModeEnabledReporter.h b/services/surfaceflinger/TunnelModeEnabledReporter.h
index 935502a..802d22d 100644
--- a/services/surfaceflinger/TunnelModeEnabledReporter.h
+++ b/services/surfaceflinger/TunnelModeEnabledReporter.h
@@ -22,6 +22,8 @@
 
 #include <unordered_map>
 
+#include "WpHash.h"
+
 namespace android {
 
 class Layer;
@@ -54,11 +56,6 @@
 
 private:
     mutable std::mutex mMutex;
-    struct WpHash {
-        size_t operator()(const wp<IBinder>& p) const {
-            return std::hash<IBinder*>()(p.unsafe_get());
-        }
-    };
 
     std::unordered_map<wp<IBinder>, sp<gui::ITunnelModeEnabledListener>, WpHash> mListeners
             GUARDED_BY(mMutex);
diff --git a/services/surfaceflinger/WindowInfosListenerInvoker.cpp b/services/surfaceflinger/WindowInfosListenerInvoker.cpp
index dc2aa58..30b9d8f 100644
--- a/services/surfaceflinger/WindowInfosListenerInvoker.cpp
+++ b/services/surfaceflinger/WindowInfosListenerInvoker.cpp
@@ -14,47 +14,45 @@
  * limitations under the License.
  */
 
-#include "WindowInfosListenerInvoker.h"
+#include <ftl/small_vector.h>
 #include <gui/ISurfaceComposer.h>
-#include <unordered_set>
+
 #include "SurfaceFlinger.h"
+#include "WindowInfosListenerInvoker.h"
 
 namespace android {
 
+using gui::DisplayInfo;
 using gui::IWindowInfosListener;
 using gui::WindowInfo;
 
-struct WindowInfosReportedListener : gui::BnWindowInfosReportedListener {
-    explicit WindowInfosReportedListener(std::function<void()> listenerCb)
-          : mListenerCb(listenerCb) {}
+struct WindowInfosListenerInvoker::WindowInfosReportedListener
+      : gui::BnWindowInfosReportedListener {
+    explicit WindowInfosReportedListener(WindowInfosListenerInvoker& invoker) : mInvoker(invoker) {}
 
     binder::Status onWindowInfosReported() override {
-        if (mListenerCb != nullptr) {
-            mListenerCb();
-        }
+        mInvoker.windowInfosReported();
         return binder::Status::ok();
     }
 
-    std::function<void()> mListenerCb;
+    WindowInfosListenerInvoker& mInvoker;
 };
 
-WindowInfosListenerInvoker::WindowInfosListenerInvoker(const sp<SurfaceFlinger>& sf) : mSf(sf) {
-    mWindowInfosReportedListener =
-            new WindowInfosReportedListener([&]() { windowInfosReported(); });
-}
+WindowInfosListenerInvoker::WindowInfosListenerInvoker(SurfaceFlinger& flinger)
+      : mFlinger(flinger),
+        mWindowInfosReportedListener(sp<WindowInfosReportedListener>::make(*this)) {}
 
-void WindowInfosListenerInvoker::addWindowInfosListener(
-        const sp<IWindowInfosListener>& windowInfosListener) {
-    sp<IBinder> asBinder = IInterface::asBinder(windowInfosListener);
-
+void WindowInfosListenerInvoker::addWindowInfosListener(sp<IWindowInfosListener> listener) {
+    sp<IBinder> asBinder = IInterface::asBinder(listener);
     asBinder->linkToDeath(this);
+
     std::scoped_lock lock(mListenersMutex);
-    mWindowInfosListeners.emplace(asBinder, windowInfosListener);
+    mWindowInfosListeners.try_emplace(asBinder, std::move(listener));
 }
 
 void WindowInfosListenerInvoker::removeWindowInfosListener(
-        const sp<IWindowInfosListener>& windowInfosListener) {
-    sp<IBinder> asBinder = IInterface::asBinder(windowInfosListener);
+        const sp<IWindowInfosListener>& listener) {
+    sp<IBinder> asBinder = IInterface::asBinder(listener);
 
     std::scoped_lock lock(mListenersMutex);
     asBinder->unlinkToDeath(this);
@@ -67,21 +65,20 @@
 }
 
 void WindowInfosListenerInvoker::windowInfosChanged(const std::vector<WindowInfo>& windowInfos,
+                                                    const std::vector<DisplayInfo>& displayInfos,
                                                     bool shouldSync) {
-    std::unordered_set<sp<IWindowInfosListener>, ISurfaceComposer::SpHash<IWindowInfosListener>>
-            windowInfosListeners;
-
+    ftl::SmallVector<const sp<IWindowInfosListener>, kStaticCapacity> windowInfosListeners;
     {
         std::scoped_lock lock(mListenersMutex);
         for (const auto& [_, listener] : mWindowInfosListeners) {
-            windowInfosListeners.insert(listener);
+            windowInfosListeners.push_back(listener);
         }
     }
 
     mCallbacksPending = windowInfosListeners.size();
 
     for (const auto& listener : windowInfosListeners) {
-        listener->onWindowInfosChanged(windowInfos,
+        listener->onWindowInfosChanged(windowInfos, displayInfos,
                                        shouldSync ? mWindowInfosReportedListener : nullptr);
     }
 }
@@ -89,8 +86,8 @@
 void WindowInfosListenerInvoker::windowInfosReported() {
     mCallbacksPending--;
     if (mCallbacksPending == 0) {
-        mSf->windowInfosReported();
+        mFlinger.windowInfosReported();
     }
 }
 
-} // namespace android
\ No newline at end of file
+} // namespace android
diff --git a/services/surfaceflinger/WindowInfosListenerInvoker.h b/services/surfaceflinger/WindowInfosListenerInvoker.h
index 5e5796f..d8d8d0f 100644
--- a/services/surfaceflinger/WindowInfosListenerInvoker.h
+++ b/services/surfaceflinger/WindowInfosListenerInvoker.h
@@ -20,8 +20,8 @@
 #include <android/gui/IWindowInfosListener.h>
 #include <android/gui/IWindowInfosReportedListener.h>
 #include <binder/IBinder.h>
+#include <ftl/small_map.h>
 #include <utils/Mutex.h>
-#include <unordered_map>
 
 namespace android {
 
@@ -29,29 +29,30 @@
 
 class WindowInfosListenerInvoker : public IBinder::DeathRecipient {
 public:
-    WindowInfosListenerInvoker(const sp<SurfaceFlinger>& sf);
-    void addWindowInfosListener(const sp<gui::IWindowInfosListener>& windowInfosListener);
+    explicit WindowInfosListenerInvoker(SurfaceFlinger&);
+
+    void addWindowInfosListener(sp<gui::IWindowInfosListener>);
     void removeWindowInfosListener(const sp<gui::IWindowInfosListener>& windowInfosListener);
 
-    void windowInfosChanged(const std::vector<gui::WindowInfo>& windowInfos, bool shouldSync);
+    void windowInfosChanged(const std::vector<gui::WindowInfo>&,
+                            const std::vector<gui::DisplayInfo>&, bool shouldSync);
 
 protected:
     void binderDied(const wp<IBinder>& who) override;
 
 private:
+    struct WindowInfosReportedListener;
     void windowInfosReported();
 
-    struct WpHash {
-        size_t operator()(const wp<IBinder>& p) const {
-            return std::hash<IBinder*>()(p.unsafe_get());
-        }
-    };
-
-    const sp<SurfaceFlinger> mSf;
+    SurfaceFlinger& mFlinger;
     std::mutex mListenersMutex;
-    std::unordered_map<wp<IBinder>, const sp<gui::IWindowInfosListener>, WpHash>
+
+    static constexpr size_t kStaticCapacity = 3;
+    ftl::SmallMap<wp<IBinder>, const sp<gui::IWindowInfosListener>, kStaticCapacity>
             mWindowInfosListeners GUARDED_BY(mListenersMutex);
+
     sp<gui::IWindowInfosReportedListener> mWindowInfosReportedListener;
     std::atomic<size_t> mCallbacksPending{0};
 };
-} // namespace android
\ No newline at end of file
+
+} // namespace android
diff --git a/services/surfaceflinger/DisplayHardware/Hash.h b/services/surfaceflinger/WpHash.h
similarity index 77%
rename from services/surfaceflinger/DisplayHardware/Hash.h
rename to services/surfaceflinger/WpHash.h
index a7b6c71..86777b7 100644
--- a/services/surfaceflinger/DisplayHardware/Hash.h
+++ b/services/surfaceflinger/WpHash.h
@@ -16,12 +16,10 @@
 
 #pragma once
 
-#include <cstdint>
-#include <string_view>
-
 namespace android {
 
-// CityHash64 implementation that only hashes at most the first 16 characters of the given string.
-uint64_t cityHash64Len0To16(std::string_view sv);
+struct WpHash {
+    size_t operator()(const wp<IBinder>& p) const { return std::hash<IBinder*>()(p.unsafe_get()); }
+};
 
 } // namespace android
\ No newline at end of file
diff --git a/services/surfaceflinger/fuzzer/Android.bp b/services/surfaceflinger/fuzzer/Android.bp
new file mode 100644
index 0000000..b0b6bf1
--- /dev/null
+++ b/services/surfaceflinger/fuzzer/Android.bp
@@ -0,0 +1,129 @@
+/*
+ * 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.
+ *
+ */
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_native_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_native_license"],
+}
+
+cc_defaults {
+    name: "surfaceflinger_fuzz_defaults",
+    include_dirs: [
+        "frameworks/native/services/surfaceflinger/tests/unittests",
+    ],
+    static_libs: [
+        "android.hardware.graphics.composer@2.1-resources",
+        "libgmock",
+        "libgui_mocks",
+        "libgmock_ndk",
+        "libgmock_main",
+        "libgtest_ndk_c++",
+        "libgmock_main_ndk",
+        "librenderengine_mocks",
+        "perfetto_trace_protos",
+        "libcompositionengine_mocks",
+        "perfetto_trace_protos",
+    ],
+    shared_libs: [
+        "libprotoutil",
+        "libstatssocket",
+        "libstatspull",
+        "libtimestats",
+        "libtimestats_proto",
+        "libprotobuf-cpp-full",
+        "android.hardware.graphics.mapper@2.0",
+        "android.hardware.graphics.mapper@3.0",
+        "android.hardware.graphics.mapper@4.0",
+    ],
+    srcs: [
+        ":libsurfaceflinger_sources",
+        ":libsurfaceflinger_mock_sources",
+    ],
+    defaults: [
+        "libsurfaceflinger_defaults",
+    ],
+    header_libs: [
+        "libui_fuzzableDataspaces_headers",
+        "libsurfaceflinger_headers",
+        "libui_headers",
+    ],
+    cflags: [
+        "-Wno-unused-result",
+        "-Wno-conversion",
+        "-Wno-sign-compare",
+    ],
+    fuzz_config: {
+        cc: [
+            "android-media-fuzzing-reports@google.com",
+        ],
+        componentid: 155276,
+    },
+}
+
+cc_fuzz {
+    name: "surfaceflinger_fuzzer",
+    defaults: [
+        "surfaceflinger_fuzz_defaults",
+    ],
+    srcs: [
+        "surfaceflinger_fuzzer.cpp",
+    ],
+}
+
+cc_fuzz {
+    name: "surfaceflinger_displayhardware_fuzzer",
+    defaults: [
+        "surfaceflinger_fuzz_defaults",
+    ],
+    srcs: [
+        "surfaceflinger_displayhardware_fuzzer.cpp",
+    ],
+    header_libs: [
+        "android.hardware.graphics.composer@2.4-command-buffer",
+        "android.hardware.graphics.composer@2.4-hal",
+    ],
+}
+
+cc_fuzz {
+    name: "surfaceflinger_scheduler_fuzzer",
+    defaults: [
+        "surfaceflinger_fuzz_defaults",
+    ],
+    srcs: [
+        "surfaceflinger_scheduler_fuzzer.cpp",
+    ],
+}
+
+cc_fuzz {
+    name: "surfaceflinger_layer_fuzzer",
+    defaults: [
+        "surfaceflinger_fuzz_defaults",
+    ],
+    header_libs: [
+        "libgui_headers",
+    ],
+    static_libs: [
+        "librenderengine",
+    ],
+    srcs: [
+        "surfaceflinger_layer_fuzzer.cpp",
+    ],
+}
diff --git a/services/surfaceflinger/fuzzer/README.md b/services/surfaceflinger/fuzzer/README.md
new file mode 100644
index 0000000..78a7596
--- /dev/null
+++ b/services/surfaceflinger/fuzzer/README.md
@@ -0,0 +1,95 @@
+# Fuzzers for SurfaceFlinger
+## Table of contents
++ [SurfaceFlinger](#SurfaceFlinger)
++ [DisplayHardware](#DisplayHardware)
++ [Scheduler](#Scheduler)
++ [Layer](#Layer)
+
+# <a name="SurfaceFlinger"></a> Fuzzer for SurfaceFlinger
+
+SurfaceFlinger supports the following data sources:
+1. Pixel Formats (parameter name: `defaultCompositionPixelFormat`)
+2. Data Spaces (parameter name: `defaultCompositionDataspace`)
+3. Rotations (parameter name: `internalDisplayOrientation`)
+3. Surface composer tags (parameter name: `onTransact`)
+
+You can find the possible values in the fuzzer's source code.
+
+#### Steps to run
+1. Build the fuzzer
+```
+  $ mm -j$(nproc) surfaceflinger_fuzzer
+```
+2. To run on device
+```
+  $ adb sync data
+  $ adb shell /data/fuzz/arm64/surfaceflinger_fuzzer/surfaceflinger_fuzzer
+```
+
+# <a name="DisplayHardware"></a> Fuzzer for DisplayHardware
+
+DisplayHardware supports the following parameters:
+1. Hal Capability (parameter name: `hasCapability`)
+2. Hal BlendMode (parameter name: `setBlendMode`)
+3. Hal Composition (parameter name: `setCompositionType`)
+4. Hal Display Capability (parameter name: `hasDisplayCapability`)
+5. Composition Types (parameter name: `prepareFrame`)
+6. Color Modes (parameter name: `setActiveColorMode`)
+7. Render Intents (parameter name: `setActiveColorMode`)
+8. Power Modes (parameter name: `setPowerMode`)
+9. Content Types (parameter name: `setContentType`)
+10. Data Space (parameter name: `setDataspace`)
+11. Transforms (parameter name: `setLayerTransform`)
+
+You can find the possible values in the fuzzer's source code.
+
+#### Steps to run
+1. Build the fuzzer
+```
+  $ mm -j$(nproc) surfaceflinger_displayhardware_fuzzer
+```
+2. Run on device
+```
+  $ adb sync data
+  $ adb shell /data/fuzz/arm64/surfaceflinger_displayhardware_fuzzer/surfaceflinger_displayhardware_fuzzer
+```
+
+# <a name="Scheduler"></a> Fuzzer for Scheduler
+
+Scheduler supports the following parameters:
+1. VSync Periods (parameter name: `lowFpsPeriod`)
+
+You can find the possible values in the fuzzer's source code.
+
+#### Steps to run
+1. Build the fuzzer
+```
+  $ mm -j$(nproc) surfaceflinger_scheduler_fuzzer
+```
+2. To run on device
+```
+  $ adb sync data
+  $ adb shell /data/fuzz/arm64/surfaceflinger_scheduler_fuzzer/surfaceflinger_scheduler_fuzzer
+```
+
+# <a name="Layer"></a> Fuzzer for Layer
+
+Layer supports the following parameters:
+1. Display Connection Types (parameter name: `fakeDisplay`)
+2. State Sets (parameter name: `traverseInZOrder`)
+3. State Subsets (parameter name: `prepareCompositionState`)
+4. Disconnect modes (parameter name: `disconnect`)
+5. Data Spaces (parameter name: `setDataspace`)
+
+You can find the possible values in the fuzzer's source code.
+
+#### Steps to run
+1. Build the fuzzer
+```
+  $ mm -j$(nproc) surfaceflinger_layer_fuzzer
+```
+2. Run on device
+```
+  $ adb sync data
+  $ adb shell /data/fuzz/arm64/surfaceflinger_layer_fuzzer/surfaceflinger_layer_fuzzer
+```
diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_displayhardware_fuzzer.cpp b/services/surfaceflinger/fuzzer/surfaceflinger_displayhardware_fuzzer.cpp
new file mode 100644
index 0000000..3c4ab95
--- /dev/null
+++ b/services/surfaceflinger/fuzzer/surfaceflinger_displayhardware_fuzzer.cpp
@@ -0,0 +1,659 @@
+/*
+ * 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.
+ *
+ */
+
+#include <binder/IPCThreadState.h>
+#include <binder/IServiceManager.h>
+#include <binder/ProcessState.h>
+#include <compositionengine/impl/OutputCompositionState.h>
+#include <fuzzer/FuzzedDataProvider.h>
+#include <gui/BLASTBufferQueue.h>
+#include <gui/IGraphicBufferProducer.h>
+#include <gui/IProducerListener.h>
+#include <gui/LayerDebugInfo.h>
+#include <gui/SurfaceComposerClient.h>
+#include <hidl/ServiceManagement.h>
+#include <hwbinder/ProcessState.h>
+#include <ui/DisplayIdentification.h>
+
+#include "DisplayHardware/AidlComposerHal.h"
+#include "DisplayHardware/DisplayMode.h"
+#include "DisplayHardware/FramebufferSurface.h"
+#include "DisplayHardware/HWComposer.h"
+#include "DisplayHardware/PowerAdvisor.h"
+#include "DisplayHardware/VirtualDisplaySurface.h"
+#include "SurfaceFlinger.h"
+#include "surfaceflinger_displayhardware_fuzzer_utils.h"
+
+#include <FuzzableDataspaces.h>
+
+namespace android::fuzz {
+
+using namespace android::hardware::graphics::common;
+using namespace android::hardware::graphics::composer;
+namespace aidl = aidl::android::hardware::graphics::composer3;
+namespace hal = android::hardware::graphics::composer::hal;
+using Config = hal::V2_1::Config;
+using Display = hal::V2_1::Display;
+using RenderIntent = V1_1::RenderIntent;
+using IComposerClient = hal::V2_4::IComposerClient;
+using VsyncPeriodChangeTimeline = hal::V2_4::VsyncPeriodChangeTimeline;
+using PerFrameMetadata = IComposerClient::PerFrameMetadata;
+using PerFrameMetadataBlob = IComposerClient::PerFrameMetadataBlob;
+using Vsync = IComposerClient::Vsync;
+
+static constexpr hal::Transform kTransforms[] = {hal::Transform::FLIP_H, hal::Transform::FLIP_V,
+                                                 hal::Transform::ROT_90, hal::Transform::ROT_180,
+                                                 hal::Transform::ROT_270};
+
+static constexpr aidl::Capability kCapability[] = {aidl::Capability::INVALID,
+                                                   aidl::Capability::SIDEBAND_STREAM,
+                                                   aidl::Capability::SKIP_CLIENT_COLOR_TRANSFORM,
+                                                   aidl::Capability::PRESENT_FENCE_IS_NOT_RELIABLE,
+                                                   aidl::Capability::SKIP_VALIDATE};
+
+static constexpr hal::BlendMode kBlendModes[] = {hal::BlendMode::INVALID, hal::BlendMode::NONE,
+                                                 hal::BlendMode::PREMULTIPLIED,
+                                                 hal::BlendMode::COVERAGE};
+
+static constexpr Composition kCompositions[] = {Composition::INVALID, Composition::CLIENT,
+                                                Composition::DEVICE,  Composition::SOLID_COLOR,
+                                                Composition::CURSOR,  Composition::SIDEBAND};
+
+static constexpr DisplayCapability kDisplayCapability[] =
+        {DisplayCapability::INVALID,
+         DisplayCapability::SKIP_CLIENT_COLOR_TRANSFORM,
+         DisplayCapability::DOZE,
+         DisplayCapability::BRIGHTNESS,
+         DisplayCapability::PROTECTED_CONTENTS,
+         DisplayCapability::AUTO_LOW_LATENCY_MODE};
+
+static constexpr VirtualDisplaySurface::CompositionType kCompositionTypes[] =
+        {VirtualDisplaySurface::CompositionType::Unknown,
+         VirtualDisplaySurface::CompositionType::Gpu, VirtualDisplaySurface::CompositionType::Hwc,
+         VirtualDisplaySurface::CompositionType::Mixed};
+
+static constexpr ui::RenderIntent kRenderIntents[] = {ui::RenderIntent::COLORIMETRIC,
+                                                      ui::RenderIntent::ENHANCE,
+                                                      ui::RenderIntent::TONE_MAP_COLORIMETRIC,
+                                                      ui::RenderIntent::TONE_MAP_ENHANCE};
+
+static constexpr hal::PowerMode kPowerModes[] = {hal::PowerMode::OFF, hal::PowerMode::DOZE,
+                                                 hal::PowerMode::DOZE_SUSPEND, hal::PowerMode::ON,
+                                                 hal::PowerMode::ON_SUSPEND};
+
+static constexpr hal::ContentType kContentTypes[] = {hal::ContentType::NONE,
+                                                     hal::ContentType::GRAPHICS,
+                                                     hal::ContentType::PHOTO,
+                                                     hal::ContentType::CINEMA,
+                                                     hal::ContentType::GAME};
+
+const unsigned char kInternalEdid[] =
+        "\x00\xff\xff\xff\xff\xff\xff\x00\x4c\xa3\x42\x31\x00\x00\x00\x00"
+        "\x00\x15\x01\x03\x80\x1a\x10\x78\x0a\xd3\xe5\x95\x5c\x60\x90\x27"
+        "\x19\x50\x54\x00\x00\x00\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01"
+        "\x01\x01\x01\x01\x01\x01\x9e\x1b\x00\xa0\x50\x20\x12\x30\x10\x30"
+        "\x13\x00\x05\xa3\x10\x00\x00\x19\x00\x00\x00\x0f\x00\x00\x00\x00"
+        "\x00\x00\x00\x00\x00\x23\x87\x02\x64\x00\x00\x00\x00\xfe\x00\x53"
+        "\x41\x4d\x53\x55\x4e\x47\x0a\x20\x20\x20\x20\x20\x00\x00\x00\xfe"
+        "\x00\x31\x32\x31\x41\x54\x31\x31\x2d\x38\x30\x31\x0a\x20\x00\x45";
+
+static constexpr hal::HWConfigId kActiveConfig = 0;
+
+class DisplayHardwareFuzzer {
+public:
+    DisplayHardwareFuzzer(const uint8_t* data, size_t size) : mFdp(data, size) {
+        mPhysicalDisplayId = SurfaceComposerClient::getInternalDisplayId().value();
+    };
+    void process();
+
+private:
+    void invokeComposer();
+    void invokeDisplayIdentification();
+    void invokeLayer(HWC2::Layer* layer);
+    void setSidebandStream(HWC2::Layer* layer);
+    void setCursorPosition(HWC2::Layer* layer);
+    void setBuffer(HWC2::Layer* layer);
+    void setSurfaceDamage(HWC2::Layer* layer);
+    void setDisplayFrame(HWC2::Layer* layer);
+    void setVisibleRegion(HWC2::Layer* layer);
+    void setLayerGenericMetadata(HWC2::Layer* layer);
+    void invokeFrameBufferSurface();
+    void invokeVirtualDisplaySurface();
+    void invokeAidlComposer();
+    Display createVirtualDisplay(Hwc2::AidlComposer*);
+    void validateDisplay(Hwc2::AidlComposer*, Display);
+    void presentOrValidateDisplay(Hwc2::AidlComposer*, Display);
+    void setOutputBuffer(Hwc2::AidlComposer*, Display);
+    void setLayerSidebandStream(Hwc2::AidlComposer*, Display, Hwc2::V2_4::hal::Layer);
+    void invokeComposerHal2_2(Hwc2::AidlComposer*, Display, Hwc2::V2_4::hal::Layer);
+    void invokeComposerHal2_3(Hwc2::AidlComposer*, Display, Hwc2::V2_4::hal::Layer);
+    void invokeComposerHal2_4(Hwc2::AidlComposer*, Display, Hwc2::V2_4::hal::Layer);
+    void getDisplayVsyncPeriod();
+    void setActiveModeWithConstraints();
+    void getDisplayIdentificationData();
+    void dumpHwc();
+    void getDisplayedContentSamplingAttributes(HalDisplayId);
+    void getDeviceCompositionChanges(HalDisplayId);
+    void getHdrCapabilities(HalDisplayId);
+    void getDisplayedContentSample(HalDisplayId);
+    void getSupportedContentTypes();
+    ui::Size getFuzzedSize();
+    mat4 getFuzzedMatrix();
+
+    DisplayIdGenerator<HalVirtualDisplayId> mGenerator;
+    FuzzedDataProvider mFdp;
+    PhysicalDisplayId mPhysicalDisplayId;
+    android::impl::HWComposer mHwc{std::make_unique<Hwc2::mock::Composer>()};
+};
+
+void DisplayHardwareFuzzer::validateDisplay(Hwc2::AidlComposer* composer, Display display) {
+    uint32_t outNumTypes, outNumRequests;
+    composer->validateDisplay(display, mFdp.ConsumeIntegral<nsecs_t>(), &outNumTypes,
+                              &outNumRequests);
+}
+
+void DisplayHardwareFuzzer::presentOrValidateDisplay(Hwc2::AidlComposer* composer,
+                                                     Display display) {
+    int32_t outPresentFence;
+    uint32_t outNumTypes, outNumRequests, state;
+    composer->presentOrValidateDisplay(display, mFdp.ConsumeIntegral<nsecs_t>(), &outNumTypes,
+                                       &outNumRequests, &outPresentFence, &state);
+}
+
+void DisplayHardwareFuzzer::setOutputBuffer(Hwc2::AidlComposer* composer, Display display) {
+    const native_handle_t buffer{};
+    composer->setOutputBuffer(display, &buffer, mFdp.ConsumeIntegral<int32_t>() /*releaseFence*/);
+}
+
+void DisplayHardwareFuzzer::setLayerSidebandStream(Hwc2::AidlComposer* composer, Display display,
+                                                   Hwc2::V2_4::hal::Layer outLayer) {
+    const native_handle_t stream{};
+    composer->setLayerSidebandStream(display, outLayer, &stream);
+}
+
+Display DisplayHardwareFuzzer::createVirtualDisplay(Hwc2::AidlComposer* composer) {
+    namespace types = hardware::graphics::common;
+    using types::V1_2::PixelFormat;
+    PixelFormat format{};
+    Display display;
+    composer->createVirtualDisplay(mFdp.ConsumeIntegral<uint32_t>() /*width*/,
+                                   mFdp.ConsumeIntegral<uint32_t>() /*height*/, &format, &display);
+    return display;
+}
+
+void DisplayHardwareFuzzer::getDisplayVsyncPeriod() {
+    nsecs_t outVsyncPeriod;
+    mHwc.getDisplayVsyncPeriod(mPhysicalDisplayId, &outVsyncPeriod);
+}
+
+void DisplayHardwareFuzzer::setActiveModeWithConstraints() {
+    hal::VsyncPeriodChangeTimeline outTimeline;
+    mHwc.setActiveModeWithConstraints(mPhysicalDisplayId, kActiveConfig, {} /*constraints*/,
+                                      &outTimeline);
+}
+
+void DisplayHardwareFuzzer::getDisplayIdentificationData() {
+    uint8_t outPort;
+    DisplayIdentificationData outData;
+    mHwc.getDisplayIdentificationData(kHwDisplayId, &outPort, &outData);
+}
+
+void DisplayHardwareFuzzer::dumpHwc() {
+    std::string string = mFdp.ConsumeRandomLengthString().c_str();
+    mHwc.dump(string);
+}
+
+void DisplayHardwareFuzzer::getDeviceCompositionChanges(HalDisplayId halDisplayID) {
+    std::optional<impl::HWComposer::DeviceRequestedChanges> outChanges;
+    mHwc.getDeviceCompositionChanges(halDisplayID,
+                                     mFdp.ConsumeBool() /*frameUsesClientComposition*/,
+                                     std::chrono::steady_clock::now(), FenceTime::NO_FENCE,
+                                     mFdp.ConsumeIntegral<nsecs_t>(), &outChanges);
+}
+
+void DisplayHardwareFuzzer::getDisplayedContentSamplingAttributes(HalDisplayId halDisplayID) {
+    uint8_t outComponentMask;
+    ui::Dataspace dataSpace;
+    ui::PixelFormat pixelFormat;
+    mHwc.getDisplayedContentSamplingAttributes(halDisplayID, &pixelFormat, &dataSpace,
+                                               &outComponentMask);
+}
+
+void DisplayHardwareFuzzer::getHdrCapabilities(HalDisplayId halDisplayID) {
+    HdrCapabilities outCapabilities;
+    mHwc.getHdrCapabilities(halDisplayID, &outCapabilities);
+}
+
+void DisplayHardwareFuzzer::getDisplayedContentSample(HalDisplayId halDisplayID) {
+    DisplayedFrameStats outStats;
+    mHwc.getDisplayedContentSample(halDisplayID, mFdp.ConsumeIntegral<uint64_t>() /* maxFrames*/,
+                                   mFdp.ConsumeIntegral<uint64_t>() /*timestamps*/, &outStats);
+}
+
+void DisplayHardwareFuzzer::getSupportedContentTypes() {
+    std::vector<hal::ContentType> contentType{};
+    mHwc.getSupportedContentTypes(mPhysicalDisplayId, &contentType);
+}
+
+void DisplayHardwareFuzzer::invokeAidlComposer() {
+    hardware::ProcessState::self()->startThreadPool();
+    ProcessState::self()->startThreadPool();
+
+    if (!Hwc2::AidlComposer::isDeclared("default")) {
+        return;
+    }
+
+    Hwc2::AidlComposer composer("default");
+
+    android::hardware::graphics::composer::hal::TestHWC2ComposerCallback composerCallback{};
+    composer.registerCallback(composerCallback);
+
+    Display display = createVirtualDisplay(&composer);
+
+    composer.acceptDisplayChanges(display);
+
+    Hwc2::V2_4::hal::Layer outLayer;
+    composer.createLayer(display, &outLayer);
+
+    int32_t outPresentFence;
+    composer.presentDisplay(display, &outPresentFence);
+
+    composer.setActiveConfig(display, Config{});
+
+    composer.setClientTarget(display, mFdp.ConsumeIntegral<uint32_t>(), sp<GraphicBuffer>(),
+                             mFdp.ConsumeIntegral<int32_t>(), mFdp.PickValueInArray(kDataspaces),
+                             {});
+
+    composer.setColorMode(display, mFdp.PickValueInArray(kColormodes),
+                          mFdp.PickValueInArray(kRenderIntents));
+
+    setOutputBuffer(&composer, display);
+
+    composer.setPowerMode(display, mFdp.PickValueInArray(kPowerModes));
+    composer.setVsyncEnabled(display, mFdp.ConsumeBool() ? Vsync::ENABLE : Vsync::DISABLE);
+
+    composer.setClientTargetSlotCount(display);
+
+    validateDisplay(&composer, display);
+
+    presentOrValidateDisplay(&composer, display);
+
+    composer.setCursorPosition(display, outLayer, mFdp.ConsumeIntegral<uint8_t>() /*x*/,
+                               mFdp.ConsumeIntegral<uint8_t>() /*y*/);
+
+    composer.setLayerBuffer(display, outLayer, mFdp.ConsumeIntegral<uint32_t>() /*slot*/,
+                            sp<GraphicBuffer>(), mFdp.ConsumeIntegral<int32_t>() /*acquireFence*/);
+
+    composer.setLayerSurfaceDamage(display, outLayer, {} /*damage*/);
+
+    composer.setLayerBlendMode(display, outLayer, mFdp.PickValueInArray(kBlendModes));
+
+    composer.setLayerColor(display, outLayer,
+                           {mFdp.ConsumeFloatingPoint<float>() /*red*/,
+                            mFdp.ConsumeFloatingPoint<float>() /*green*/,
+                            mFdp.ConsumeFloatingPoint<float>() /*blue*/,
+                            mFdp.ConsumeFloatingPoint<float>() /*alpha*/});
+    composer.setLayerCompositionType(display, outLayer, mFdp.PickValueInArray(kCompositions));
+    composer.setLayerDataspace(display, outLayer, mFdp.PickValueInArray(kDataspaces));
+    composer.setLayerDisplayFrame(display, outLayer, {} /*frame*/);
+    composer.setLayerPlaneAlpha(display, outLayer, mFdp.ConsumeFloatingPoint<float>());
+
+    setLayerSidebandStream(&composer, display, outLayer);
+
+    composer.setLayerSourceCrop(display, outLayer, {} /*crop*/);
+
+    composer.setLayerTransform(display, outLayer, mFdp.PickValueInArray(kTransforms));
+
+    composer.setLayerVisibleRegion(display, outLayer, std::vector<IComposerClient::Rect>{});
+    composer.setLayerZOrder(display, outLayer, mFdp.ConsumeIntegral<uint32_t>());
+
+    invokeComposerHal2_2(&composer, display, outLayer);
+    invokeComposerHal2_3(&composer, display, outLayer);
+    invokeComposerHal2_4(&composer, display, outLayer);
+
+    composer.executeCommands();
+    composer.resetCommands();
+
+    composer.destroyLayer(display, outLayer);
+    composer.destroyVirtualDisplay(display);
+}
+
+void DisplayHardwareFuzzer::invokeComposerHal2_2(Hwc2::AidlComposer* composer, Display display,
+                                                 Hwc2::V2_4::hal::Layer outLayer) {
+    const std::vector<PerFrameMetadata> perFrameMetadatas;
+    composer->setLayerPerFrameMetadata(display, outLayer, perFrameMetadatas);
+
+    composer->getPerFrameMetadataKeys(display);
+    std::vector<RenderIntent> outRenderIntents;
+
+    composer->getRenderIntents(display, mFdp.PickValueInArray(kColormodes), &outRenderIntents);
+    mat4 outMatrix;
+    composer->getDataspaceSaturationMatrix(mFdp.PickValueInArray(kDataspaces), &outMatrix);
+}
+
+void DisplayHardwareFuzzer::invokeComposerHal2_3(Hwc2::AidlComposer* composer, Display display,
+                                                 Hwc2::V2_4::hal::Layer outLayer) {
+    composer->setDisplayContentSamplingEnabled(display, mFdp.ConsumeBool() /*enabled*/,
+                                               mFdp.ConsumeIntegral<uint8_t>() /*componentMask*/,
+                                               mFdp.ConsumeIntegral<uint64_t>() /*maxFrames*/);
+
+    DisplayedFrameStats outStats;
+    composer->getDisplayedContentSample(display, mFdp.ConsumeIntegral<uint64_t>() /*maxFrames*/,
+                                        mFdp.ConsumeIntegral<uint64_t>() /*timestamp*/, &outStats);
+
+    composer->setLayerPerFrameMetadataBlobs(display, outLayer, std::vector<PerFrameMetadataBlob>{});
+
+    composer->setDisplayBrightness(display, mFdp.ConsumeFloatingPoint<float>(),
+                                   Hwc2::Composer::DisplayBrightnessOptions{
+                                           .applyImmediately = mFdp.ConsumeIntegral<bool>()});
+}
+
+void DisplayHardwareFuzzer::invokeComposerHal2_4(Hwc2::AidlComposer* composer, Display display,
+                                                 Hwc2::V2_4::hal::Layer outLayer) {
+    VsyncPeriodChangeTimeline outTimeline;
+    composer->setActiveConfigWithConstraints(display, Config{},
+                                             IComposerClient::VsyncPeriodChangeConstraints{},
+                                             &outTimeline);
+
+    composer->setAutoLowLatencyMode(display, mFdp.ConsumeBool());
+
+    composer->setContentType(display, mFdp.PickValueInArray(kContentTypes));
+
+    std::vector<uint8_t> value;
+    value.push_back(mFdp.ConsumeIntegral<uint8_t>());
+    composer->setLayerGenericMetadata(display, outLayer, mFdp.ConsumeRandomLengthString() /*key*/,
+                                      mFdp.ConsumeBool() /*mandatory*/, value);
+}
+
+ui::Size DisplayHardwareFuzzer::getFuzzedSize() {
+    ui::Size size{mFdp.ConsumeIntegral<int32_t>() /*width*/,
+                  mFdp.ConsumeIntegral<int32_t>() /*height*/};
+    return size;
+}
+
+mat4 DisplayHardwareFuzzer::getFuzzedMatrix() {
+    mat4 matrix{mFdp.ConsumeFloatingPoint<float>(), mFdp.ConsumeFloatingPoint<float>(),
+                mFdp.ConsumeFloatingPoint<float>(), mFdp.ConsumeFloatingPoint<float>(),
+                mFdp.ConsumeFloatingPoint<float>(), mFdp.ConsumeFloatingPoint<float>(),
+                mFdp.ConsumeFloatingPoint<float>(), mFdp.ConsumeFloatingPoint<float>(),
+                mFdp.ConsumeFloatingPoint<float>(), mFdp.ConsumeFloatingPoint<float>(),
+                mFdp.ConsumeFloatingPoint<float>(), mFdp.ConsumeFloatingPoint<float>(),
+                mFdp.ConsumeFloatingPoint<float>(), mFdp.ConsumeFloatingPoint<float>(),
+                mFdp.ConsumeFloatingPoint<float>(), mFdp.ConsumeFloatingPoint<float>()};
+    return matrix;
+}
+
+void DisplayHardwareFuzzer::setCursorPosition(HWC2::Layer* layer) {
+    layer->setCursorPosition(mFdp.ConsumeIntegral<int32_t>() /*x*/,
+                             mFdp.ConsumeIntegral<int32_t>() /*y*/);
+}
+
+void DisplayHardwareFuzzer::setBuffer(HWC2::Layer* layer) {
+    layer->setBuffer(mFdp.ConsumeIntegral<uint32_t>() /*slot*/, sp<GraphicBuffer>(),
+                     sp<Fence>::make());
+}
+
+void DisplayHardwareFuzzer::setSurfaceDamage(HWC2::Layer* layer) {
+    Rect rhs{mFdp.ConsumeIntegral<uint32_t>() /*width*/,
+             mFdp.ConsumeIntegral<uint32_t>() /*height*/};
+    const Region damage{rhs};
+    layer->setSurfaceDamage(damage);
+}
+
+void DisplayHardwareFuzzer::setVisibleRegion(HWC2::Layer* layer) {
+    uint32_t width = mFdp.ConsumeIntegral<uint32_t>();
+    uint32_t height = mFdp.ConsumeIntegral<uint32_t>();
+    Rect rect{width, height};
+    const Region region{rect};
+    layer->setVisibleRegion(region);
+}
+
+void DisplayHardwareFuzzer::setDisplayFrame(HWC2::Layer* layer) {
+    uint32_t width = mFdp.ConsumeIntegral<uint32_t>();
+    uint32_t height = mFdp.ConsumeIntegral<uint32_t>();
+    const Rect frame{width, height};
+    layer->setDisplayFrame(frame);
+}
+
+void DisplayHardwareFuzzer::setLayerGenericMetadata(HWC2::Layer* layer) {
+    std::vector<uint8_t> value;
+    value.push_back(mFdp.ConsumeIntegral<uint8_t>());
+    layer->setLayerGenericMetadata(mFdp.ConsumeRandomLengthString().c_str() /*name*/,
+                                   mFdp.ConsumeBool() /*mandatory*/, value);
+}
+
+void DisplayHardwareFuzzer::setSidebandStream(HWC2::Layer* layer) {
+    const native_handle_t stream{};
+    layer->setSidebandStream(&stream);
+}
+
+void DisplayHardwareFuzzer::invokeLayer(HWC2::Layer* layer) {
+    setCursorPosition(layer);
+    setBuffer(layer);
+    setSurfaceDamage(layer);
+
+    layer->setBlendMode(mFdp.PickValueInArray(kBlendModes));
+    layer->setColor({mFdp.ConsumeFloatingPoint<float>() /*red*/,
+                     mFdp.ConsumeFloatingPoint<float>() /*green*/,
+                     mFdp.ConsumeFloatingPoint<float>() /*blue*/,
+                     mFdp.ConsumeFloatingPoint<float>() /*alpha*/});
+    layer->setCompositionType(mFdp.PickValueInArray(kCompositions));
+    layer->setDataspace(mFdp.PickValueInArray(kDataspaces));
+
+    layer->setPerFrameMetadata(mFdp.ConsumeIntegral<int32_t>(), getFuzzedHdrMetadata(&mFdp));
+    setDisplayFrame(layer);
+
+    layer->setPlaneAlpha(mFdp.ConsumeFloatingPoint<float>());
+
+    setSidebandStream(layer);
+
+    layer->setSourceCrop(getFuzzedFloatRect(&mFdp));
+    layer->setTransform(mFdp.PickValueInArray(kTransforms));
+
+    setVisibleRegion(layer);
+
+    layer->setZOrder(mFdp.ConsumeIntegral<uint32_t>());
+
+    layer->setColorTransform(getFuzzedMatrix());
+
+    setLayerGenericMetadata(layer);
+}
+
+void DisplayHardwareFuzzer::invokeFrameBufferSurface() {
+    sp<IGraphicBufferProducer> bqProducer = sp<mock::GraphicBufferProducer>::make();
+    sp<IGraphicBufferConsumer> bqConsumer;
+    BufferQueue::createBufferQueue(&bqProducer, &bqConsumer);
+
+    sp<FramebufferSurface> surface =
+            new FramebufferSurface(mHwc, mPhysicalDisplayId, bqConsumer, getFuzzedSize() /*size*/,
+                                   getFuzzedSize() /*maxSize*/);
+    surface->beginFrame(mFdp.ConsumeBool());
+
+    surface->prepareFrame(mFdp.PickValueInArray(kCompositionTypes));
+    surface->advanceFrame();
+    surface->onFrameCommitted();
+    String8 result = String8(mFdp.ConsumeRandomLengthString().c_str());
+    surface->dumpAsString(result);
+    surface->resizeBuffers(getFuzzedSize());
+    surface->getClientTargetAcquireFence();
+}
+
+void DisplayHardwareFuzzer::invokeVirtualDisplaySurface() {
+    DisplayIdGenerator<HalVirtualDisplayId> mGenerator;
+    VirtualDisplayId VirtualDisplayId = mGenerator.generateId().value();
+
+    sp<SurfaceComposerClient> mClient = new SurfaceComposerClient();
+    sp<SurfaceControl> mSurfaceControl =
+            mClient->createSurface(String8("TestSurface"), 100, 100, PIXEL_FORMAT_RGBA_8888,
+                                   ISurfaceComposerClient::eFXSurfaceBufferState,
+                                   /*parent*/ nullptr);
+
+    sp<BLASTBufferQueue> mBlastBufferQueueAdapter =
+            new BLASTBufferQueue("TestBLASTBufferQueue", mSurfaceControl, 100, 100,
+                                 PIXEL_FORMAT_RGBA_8888);
+
+    sp<IGraphicBufferProducer> sink = mBlastBufferQueueAdapter->getIGraphicBufferProducer();
+    sp<IGraphicBufferProducer> bqProducer = mBlastBufferQueueAdapter->getIGraphicBufferProducer();
+    sp<IGraphicBufferConsumer> bqConsumer;
+    BufferQueue::createBufferQueue(&bqProducer, &bqConsumer);
+    BufferQueue::createBufferQueue(&sink, &bqConsumer);
+
+    sp<VirtualDisplaySurface> surface =
+            new VirtualDisplaySurface(mHwc, VirtualDisplayId, sink, bqProducer, bqConsumer,
+                                      mFdp.ConsumeRandomLengthString().c_str() /*name*/);
+
+    surface->beginFrame(mFdp.ConsumeBool());
+    surface->prepareFrame(mFdp.PickValueInArray(kCompositionTypes));
+    surface->resizeBuffers(getFuzzedSize());
+    surface->getClientTargetAcquireFence();
+    surface->advanceFrame();
+    surface->onFrameCommitted();
+    String8 result = String8(mFdp.ConsumeRandomLengthString().c_str());
+    surface->dumpAsString(result);
+}
+
+void DisplayHardwareFuzzer::invokeComposer() {
+    HalVirtualDisplayId halVirtualDisplayId = mGenerator.generateId().value();
+    HalDisplayId halDisplayID = HalDisplayId{halVirtualDisplayId};
+
+    android::hardware::graphics::composer::hal::TestHWC2ComposerCallback composerCallback{};
+    mHwc.setCallback(composerCallback);
+
+    ui::PixelFormat pixelFormat{};
+    if (!mHwc.allocateVirtualDisplay(halVirtualDisplayId, getFuzzedSize(), &pixelFormat)) {
+        return;
+    }
+
+    getDisplayIdentificationData();
+
+    mHwc.hasDisplayCapability(halDisplayID, mFdp.PickValueInArray(kDisplayCapability));
+
+    mHwc.allocatePhysicalDisplay(kHwDisplayId, mPhysicalDisplayId);
+
+    static auto hwcLayer = mHwc.createLayer(halDisplayID);
+    HWC2::Layer* layer = hwcLayer.get();
+    invokeLayer(layer);
+
+    getDeviceCompositionChanges(halDisplayID);
+
+    mHwc.setClientTarget(halDisplayID, mFdp.ConsumeIntegral<uint32_t>(), Fence::NO_FENCE,
+                         sp<GraphicBuffer>::make(), mFdp.PickValueInArray(kDataspaces));
+
+    mHwc.presentAndGetReleaseFences(halDisplayID, std::chrono::steady_clock::now(),
+                                    FenceTime::NO_FENCE);
+
+    mHwc.setPowerMode(mPhysicalDisplayId, mFdp.PickValueInArray(kPowerModes));
+
+    mHwc.setColorTransform(halDisplayID, getFuzzedMatrix());
+
+    mHwc.getPresentFence(halDisplayID);
+
+    mHwc.getLayerReleaseFence(halDisplayID, layer);
+
+    mHwc.setOutputBuffer(halVirtualDisplayId, sp<Fence>::make().get(), sp<GraphicBuffer>::make());
+
+    mHwc.clearReleaseFences(halDisplayID);
+
+    getHdrCapabilities(halDisplayID);
+
+    mHwc.getSupportedPerFrameMetadata(halDisplayID);
+
+    mHwc.getRenderIntents(halDisplayID, ui::ColorMode());
+
+    mHwc.getDataspaceSaturationMatrix(halDisplayID, ui::Dataspace());
+
+    getDisplayedContentSamplingAttributes(halDisplayID);
+
+    mHwc.setDisplayContentSamplingEnabled(halDisplayID, mFdp.ConsumeBool() /*enabled*/,
+                                          mFdp.ConsumeIntegral<uint8_t>() /*componentMask*/,
+                                          mFdp.ConsumeIntegral<uint64_t>() /*maxFrames*/);
+
+    getDisplayedContentSample(halDisplayID);
+
+    mHwc.setDisplayBrightness(mPhysicalDisplayId, mFdp.ConsumeFloatingPoint<float>(),
+                              Hwc2::Composer::DisplayBrightnessOptions{
+                                      .applyImmediately = mFdp.ConsumeIntegral<bool>()});
+
+    mHwc.onHotplug(kHwDisplayId, hal::Connection::CONNECTED);
+    mHwc.updatesDeviceProductInfoOnHotplugReconnect();
+
+    mHwc.onVsync(kHwDisplayId, mFdp.ConsumeIntegral<int64_t>());
+    mHwc.setVsyncEnabled(mPhysicalDisplayId,
+                         mFdp.ConsumeBool() ? hal::Vsync::ENABLE : hal::Vsync::DISABLE);
+
+    mHwc.isConnected(mPhysicalDisplayId);
+    mHwc.getModes(mPhysicalDisplayId);
+    mHwc.getActiveMode(mPhysicalDisplayId);
+    mHwc.getColorModes(mPhysicalDisplayId);
+    mHwc.hasCapability(mFdp.PickValueInArray(kCapability));
+
+    mHwc.setActiveColorMode(mPhysicalDisplayId, mFdp.PickValueInArray(kColormodes),
+                            mFdp.PickValueInArray(kRenderIntents));
+
+    mHwc.getDisplayConnectionType(mPhysicalDisplayId);
+    mHwc.isVsyncPeriodSwitchSupported(mPhysicalDisplayId);
+
+    getDisplayVsyncPeriod();
+
+    setActiveModeWithConstraints();
+
+    mHwc.setAutoLowLatencyMode(mPhysicalDisplayId, mFdp.ConsumeBool());
+
+    getSupportedContentTypes();
+
+    mHwc.setContentType(mPhysicalDisplayId, mFdp.PickValueInArray(kContentTypes));
+
+    dumpHwc();
+
+    mHwc.toPhysicalDisplayId(kHwDisplayId);
+    mHwc.fromPhysicalDisplayId(mPhysicalDisplayId);
+    mHwc.disconnectDisplay(halDisplayID);
+
+    static hal::HWDisplayId displayId = mFdp.ConsumeIntegral<hal::HWDisplayId>();
+    mHwc.onHotplug(displayId,
+                   mFdp.ConsumeBool() ? hal::Connection::DISCONNECTED : hal::Connection::CONNECTED);
+}
+
+template <size_t N>
+DisplayIdentificationData asDisplayIdentificationData(const unsigned char (&bytes)[N]) {
+    return DisplayIdentificationData(bytes, bytes + N - 1);
+}
+
+void DisplayHardwareFuzzer::invokeDisplayIdentification() {
+    static const DisplayIdentificationData data = asDisplayIdentificationData(kInternalEdid);
+    isEdid(data);
+    parseEdid(data);
+    parseDisplayIdentificationData(mFdp.ConsumeIntegral<uint8_t>(), data);
+    getPnpId(getVirtualDisplayId(mFdp.ConsumeIntegral<uint32_t>()));
+    getPnpId(mFdp.ConsumeIntegral<uint8_t>());
+}
+
+void DisplayHardwareFuzzer::process() {
+    invokeComposer();
+    invokeAidlComposer();
+    invokeDisplayIdentification();
+    invokeFrameBufferSurface();
+    invokeVirtualDisplaySurface();
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+    DisplayHardwareFuzzer displayHardwareFuzzer(data, size);
+    displayHardwareFuzzer.process();
+    return 0;
+}
+
+} // namespace android::fuzz
diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_displayhardware_fuzzer_utils.h b/services/surfaceflinger/fuzzer/surfaceflinger_displayhardware_fuzzer_utils.h
new file mode 100644
index 0000000..6a6e3db
--- /dev/null
+++ b/services/surfaceflinger/fuzzer/surfaceflinger_displayhardware_fuzzer_utils.h
@@ -0,0 +1,104 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <utils/Condition.h>
+#include <chrono>
+#include <vector>
+
+#include <android/hardware/graphics/composer/2.4/IComposer.h>
+#include <composer-hal/2.1/ComposerClient.h>
+#include <composer-hal/2.2/ComposerClient.h>
+#include <composer-hal/2.3/ComposerClient.h>
+#include <composer-hal/2.4/ComposerClient.h>
+
+#include "DisplayHardware/HWC2.h"
+#include "surfaceflinger_fuzzers_utils.h"
+
+namespace {
+class LayerImpl;
+class Frame;
+class DelayedEventGenerator;
+} // namespace
+
+namespace android {
+class SurfaceComposerClient;
+} // namespace android
+
+namespace android::hardware::graphics::composer::hal {
+
+using ::android::hardware::Return;
+using ::android::hardware::Void;
+using ::android::HWC2::ComposerCallback;
+
+class ComposerCallbackBridge : public IComposerCallback {
+public:
+    ComposerCallbackBridge(ComposerCallback* callback, bool vsyncSwitchingSupported)
+          : mCallback(callback), mVsyncSwitchingSupported(vsyncSwitchingSupported) {}
+
+    Return<void> onHotplug(HWDisplayId display, Connection connection) override {
+        mCallback->onComposerHalHotplug(display, connection);
+        return Void();
+    }
+
+    Return<void> onRefresh(HWDisplayId display) override {
+        mCallback->onComposerHalRefresh(display);
+        return Void();
+    }
+
+    Return<void> onVsync(HWDisplayId display, int64_t timestamp) override {
+        if (!mVsyncSwitchingSupported) {
+            mCallback->onComposerHalVsync(display, timestamp, std::nullopt);
+        }
+        return Void();
+    }
+
+    Return<void> onVsync_2_4(HWDisplayId display, int64_t timestamp,
+                             VsyncPeriodNanos vsyncPeriodNanos) override {
+        if (mVsyncSwitchingSupported) {
+            mCallback->onComposerHalVsync(display, timestamp, vsyncPeriodNanos);
+        }
+        return Void();
+    }
+
+    Return<void> onVsyncPeriodTimingChanged(HWDisplayId display,
+                                            const VsyncPeriodChangeTimeline& timeline) override {
+        mCallback->onComposerHalVsyncPeriodTimingChanged(display, timeline);
+        return Void();
+    }
+
+    Return<void> onSeamlessPossible(HWDisplayId display) override {
+        mCallback->onComposerHalSeamlessPossible(display);
+        return Void();
+    }
+
+private:
+    ComposerCallback* const mCallback;
+    const bool mVsyncSwitchingSupported;
+};
+
+struct TestHWC2ComposerCallback : public HWC2::ComposerCallback {
+    virtual ~TestHWC2ComposerCallback() = default;
+    void onComposerHalHotplug(HWDisplayId, Connection){};
+    void onComposerHalRefresh(HWDisplayId) {}
+    void onComposerHalVsync(HWDisplayId, int64_t, std::optional<VsyncPeriodNanos>) {}
+    void onComposerHalVsyncPeriodTimingChanged(HWDisplayId, const VsyncPeriodChangeTimeline&) {}
+    void onComposerHalSeamlessPossible(HWDisplayId) {}
+    void onComposerHalVsyncIdle(HWDisplayId) {}
+};
+
+} // namespace android::hardware::graphics::composer::hal
diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzer.cpp b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzer.cpp
new file mode 100644
index 0000000..f25043c
--- /dev/null
+++ b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzer.cpp
@@ -0,0 +1,259 @@
+/*
+ * 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.
+ *
+ */
+
+#include <FuzzableDataspaces.h>
+#include <binder/IServiceManager.h>
+#include <fuzzer/FuzzedDataProvider.h>
+#include <ui/DisplayStatInfo.h>
+#include "surfaceflinger_fuzzers_utils.h"
+
+namespace android::fuzz {
+
+static constexpr LatchUnsignaledConfig kLatchUnsignaledConfig[] = {
+        LatchUnsignaledConfig::Always,
+        LatchUnsignaledConfig::AutoSingleLayer,
+        LatchUnsignaledConfig::Disabled,
+};
+
+static constexpr BnSurfaceComposer::ISurfaceComposerTag kSurfaceComposerTags[]{
+        BnSurfaceComposer::BOOT_FINISHED,
+        BnSurfaceComposer::CREATE_CONNECTION,
+        BnSurfaceComposer::GET_STATIC_DISPLAY_INFO,
+        BnSurfaceComposer::CREATE_DISPLAY_EVENT_CONNECTION,
+        BnSurfaceComposer::CREATE_DISPLAY,
+        BnSurfaceComposer::DESTROY_DISPLAY,
+        BnSurfaceComposer::GET_PHYSICAL_DISPLAY_TOKEN,
+        BnSurfaceComposer::SET_TRANSACTION_STATE,
+        BnSurfaceComposer::AUTHENTICATE_SURFACE,
+        BnSurfaceComposer::GET_SUPPORTED_FRAME_TIMESTAMPS,
+        BnSurfaceComposer::GET_DISPLAY_MODES,
+        BnSurfaceComposer::GET_ACTIVE_DISPLAY_MODE,
+        BnSurfaceComposer::GET_DISPLAY_STATE,
+        BnSurfaceComposer::CAPTURE_DISPLAY,
+        BnSurfaceComposer::CAPTURE_LAYERS,
+        BnSurfaceComposer::CLEAR_ANIMATION_FRAME_STATS,
+        BnSurfaceComposer::GET_ANIMATION_FRAME_STATS,
+        BnSurfaceComposer::SET_POWER_MODE,
+        BnSurfaceComposer::GET_DISPLAY_STATS,
+        BnSurfaceComposer::GET_HDR_CAPABILITIES,
+        BnSurfaceComposer::GET_DISPLAY_COLOR_MODES,
+        BnSurfaceComposer::GET_ACTIVE_COLOR_MODE,
+        BnSurfaceComposer::SET_ACTIVE_COLOR_MODE,
+        BnSurfaceComposer::ENABLE_VSYNC_INJECTIONS,
+        BnSurfaceComposer::INJECT_VSYNC,
+        BnSurfaceComposer::GET_LAYER_DEBUG_INFO,
+        BnSurfaceComposer::GET_COMPOSITION_PREFERENCE,
+        BnSurfaceComposer::GET_COLOR_MANAGEMENT,
+        BnSurfaceComposer::GET_DISPLAYED_CONTENT_SAMPLING_ATTRIBUTES,
+        BnSurfaceComposer::SET_DISPLAY_CONTENT_SAMPLING_ENABLED,
+        BnSurfaceComposer::GET_DISPLAYED_CONTENT_SAMPLE,
+        BnSurfaceComposer::GET_PROTECTED_CONTENT_SUPPORT,
+        BnSurfaceComposer::IS_WIDE_COLOR_DISPLAY,
+        BnSurfaceComposer::GET_DISPLAY_NATIVE_PRIMARIES,
+        BnSurfaceComposer::GET_PHYSICAL_DISPLAY_IDS,
+        BnSurfaceComposer::ADD_REGION_SAMPLING_LISTENER,
+        BnSurfaceComposer::REMOVE_REGION_SAMPLING_LISTENER,
+        BnSurfaceComposer::SET_DESIRED_DISPLAY_MODE_SPECS,
+        BnSurfaceComposer::GET_DESIRED_DISPLAY_MODE_SPECS,
+        BnSurfaceComposer::GET_DISPLAY_BRIGHTNESS_SUPPORT,
+        BnSurfaceComposer::SET_DISPLAY_BRIGHTNESS,
+        BnSurfaceComposer::CAPTURE_DISPLAY_BY_ID,
+        BnSurfaceComposer::NOTIFY_POWER_BOOST,
+        BnSurfaceComposer::SET_GLOBAL_SHADOW_SETTINGS,
+        BnSurfaceComposer::GET_AUTO_LOW_LATENCY_MODE_SUPPORT,
+        BnSurfaceComposer::SET_AUTO_LOW_LATENCY_MODE,
+        BnSurfaceComposer::GET_GAME_CONTENT_TYPE_SUPPORT,
+        BnSurfaceComposer::SET_GAME_CONTENT_TYPE,
+        BnSurfaceComposer::SET_FRAME_RATE,
+        BnSurfaceComposer::ACQUIRE_FRAME_RATE_FLEXIBILITY_TOKEN,
+        BnSurfaceComposer::SET_FRAME_TIMELINE_INFO,
+        BnSurfaceComposer::ADD_TRANSACTION_TRACE_LISTENER,
+        BnSurfaceComposer::GET_GPU_CONTEXT_PRIORITY,
+        BnSurfaceComposer::GET_MAX_ACQUIRED_BUFFER_COUNT,
+        BnSurfaceComposer::GET_DYNAMIC_DISPLAY_INFO,
+        BnSurfaceComposer::ADD_FPS_LISTENER,
+        BnSurfaceComposer::REMOVE_FPS_LISTENER,
+        BnSurfaceComposer::OVERRIDE_HDR_TYPES,
+        BnSurfaceComposer::ADD_HDR_LAYER_INFO_LISTENER,
+        BnSurfaceComposer::REMOVE_HDR_LAYER_INFO_LISTENER,
+        BnSurfaceComposer::ON_PULL_ATOM,
+        BnSurfaceComposer::ADD_TUNNEL_MODE_ENABLED_LISTENER,
+        BnSurfaceComposer::REMOVE_TUNNEL_MODE_ENABLED_LISTENER,
+        BnSurfaceComposer::ADD_WINDOW_INFOS_LISTENER,
+        BnSurfaceComposer::REMOVE_WINDOW_INFOS_LISTENER,
+};
+
+static constexpr uint32_t kMinCode = 1000;
+static constexpr uint32_t kMaxCode = 1050;
+
+class SurfaceFlingerFuzzer {
+public:
+    SurfaceFlingerFuzzer(const uint8_t *data, size_t size) : mFdp(data, size) {
+        mFlinger = mTestableFlinger.flinger();
+    };
+    void process(const uint8_t *data, size_t size);
+
+private:
+    void setUp();
+    void invokeFlinger();
+    void setTransactionState();
+    void setInternalDisplayPrimaries();
+    void setDisplayStateLocked();
+    void onTransact(const uint8_t *data, size_t size);
+
+    FuzzedDataProvider mFdp;
+    TestableSurfaceFlinger mTestableFlinger;
+    sp<SurfaceFlinger> mFlinger = nullptr;
+};
+
+void SurfaceFlingerFuzzer::invokeFlinger() {
+    mFlinger->setSchedFifo(mFdp.ConsumeBool());
+    mFlinger->setSchedAttr(mFdp.ConsumeBool());
+    mFlinger->getServiceName();
+    mFlinger->hasSyncFramework = mFdp.ConsumeBool();
+    mFlinger->dispSyncPresentTimeOffset = mFdp.ConsumeIntegral<int64_t>();
+    mFlinger->useHwcForRgbToYuv = mFdp.ConsumeBool();
+    mFlinger->maxFrameBufferAcquiredBuffers = mFdp.ConsumeIntegral<int64_t>();
+    mFlinger->maxGraphicsWidth = mFdp.ConsumeIntegral<uint32_t>();
+    mFlinger->maxGraphicsHeight = mFdp.ConsumeIntegral<uint32_t>();
+    mFlinger->hasWideColorDisplay = mFdp.ConsumeBool();
+    mFlinger->useContextPriority = mFdp.ConsumeBool();
+
+    mFlinger->defaultCompositionDataspace = mFdp.PickValueInArray(kDataspaces);
+    mFlinger->defaultCompositionPixelFormat = mFdp.PickValueInArray(kPixelFormats);
+    mFlinger->wideColorGamutCompositionDataspace = mFdp.PickValueInArray(kDataspaces);
+    mFlinger->wideColorGamutCompositionPixelFormat = mFdp.PickValueInArray(kPixelFormats);
+
+    mFlinger->enableLatchUnsignaledConfig = mFdp.PickValueInArray(kLatchUnsignaledConfig);
+
+    using FrameHint = SurfaceFlinger::FrameHint;
+    mFlinger->scheduleComposite(mFdp.ConsumeBool() ? FrameHint::kActive : FrameHint::kNone);
+    mFlinger->scheduleRepaint();
+    mFlinger->scheduleSample();
+
+    uint32_t texture = mFlinger->getNewTexture();
+    mFlinger->deleteTextureAsync(texture);
+
+    sp<IBinder> handle = defaultServiceManager()->checkService(
+            String16(mFdp.ConsumeRandomLengthString().c_str()));
+    mFlinger->fromHandle(handle);
+    mFlinger->windowInfosReported();
+    mFlinger->disableExpensiveRendering();
+}
+
+void SurfaceFlingerFuzzer::setInternalDisplayPrimaries() {
+    ui::DisplayPrimaries primaries;
+    primaries.red.X = mFdp.ConsumeFloatingPoint<float>();
+    primaries.red.Y = mFdp.ConsumeFloatingPoint<float>();
+    primaries.red.Z = mFdp.ConsumeFloatingPoint<float>();
+    primaries.green.X = mFdp.ConsumeFloatingPoint<float>();
+    primaries.green.Y = mFdp.ConsumeFloatingPoint<float>();
+    primaries.green.Z = mFdp.ConsumeFloatingPoint<float>();
+    primaries.blue.X = mFdp.ConsumeFloatingPoint<float>();
+    primaries.blue.Y = mFdp.ConsumeFloatingPoint<float>();
+    primaries.blue.Z = mFdp.ConsumeFloatingPoint<float>();
+    primaries.white.X = mFdp.ConsumeFloatingPoint<float>();
+    primaries.white.Y = mFdp.ConsumeFloatingPoint<float>();
+    primaries.white.Z = mFdp.ConsumeFloatingPoint<float>();
+    mTestableFlinger.setInternalDisplayPrimaries(primaries);
+}
+
+void SurfaceFlingerFuzzer::setTransactionState() {
+    Vector<ComposerState> states;
+    Vector<DisplayState> displays;
+    ComposerState composerState;
+    composerState.state.what = layer_state_t::eLayerChanged;
+    composerState.state.surface = nullptr;
+    states.add(composerState);
+    uint32_t flags = mFdp.ConsumeIntegral<uint32_t>();
+    const sp<IBinder> applyToken = nullptr;
+    int64_t desiredPresentTime = mFdp.ConsumeIntegral<int64_t>();
+    bool isAutoTimestamp = mFdp.ConsumeBool();
+    bool hasListenerCallbacks = mFdp.ConsumeBool();
+    std::vector<ListenerCallbacks> listenerCallbacks{};
+    uint64_t transactionId = mFdp.ConsumeIntegral<uint64_t>();
+
+    mTestableFlinger.setTransactionState(FrameTimelineInfo{}, states, displays, flags, applyToken,
+                                         InputWindowCommands{}, desiredPresentTime, isAutoTimestamp,
+                                         {}, hasListenerCallbacks, listenerCallbacks,
+                                         transactionId);
+}
+
+void SurfaceFlingerFuzzer::setDisplayStateLocked() {
+    DisplayState state{};
+    mTestableFlinger.setDisplayStateLocked(state);
+}
+
+void SurfaceFlingerFuzzer::onTransact(const uint8_t *data, size_t size) {
+    Parcel fuzzedData, reply;
+    fuzzedData.writeInterfaceToken(String16("android.ui.ISurfaceComposer"));
+    fuzzedData.setData(data, size);
+    fuzzedData.setDataPosition(0);
+    uint32_t code = mFdp.ConsumeBool() ? mFdp.PickValueInArray(kSurfaceComposerTags)
+                                       : mFdp.ConsumeIntegralInRange<uint32_t>(kMinCode, kMaxCode);
+    mTestableFlinger.onTransact(code, fuzzedData, &reply, 0);
+}
+
+void SurfaceFlingerFuzzer::setUp() {
+    mTestableFlinger.setupScheduler(std::make_unique<android::mock::VsyncController>(),
+                                    std::make_unique<android::mock::VSyncTracker>(),
+                                    std::make_unique<android::mock::EventThread>(),
+                                    std::make_unique<android::mock::EventThread>());
+
+    mTestableFlinger.setupTimeStats(std::make_unique<android::mock::TimeStats>());
+
+    std::unique_ptr<android::renderengine::RenderEngine> renderEngine =
+            std::make_unique<android::renderengine::mock::RenderEngine>();
+    mTestableFlinger.setupRenderEngine(std::move(renderEngine));
+    mTestableFlinger.setupComposer(std::make_unique<android::Hwc2::mock::Composer>());
+}
+
+void SurfaceFlingerFuzzer::process(const uint8_t *data, size_t size) {
+    setUp();
+
+    invokeFlinger();
+
+    mTestableFlinger.fuzzSurfaceFlinger(data, size);
+
+    mTestableFlinger.setCreateBufferQueueFunction(
+            surfaceflinger::test::Factory::CreateBufferQueueFunction());
+    mTestableFlinger.setCreateNativeWindowSurface(
+            surfaceflinger::test::Factory::CreateNativeWindowSurfaceFunction());
+
+    setInternalDisplayPrimaries();
+
+    mTestableFlinger.enableHalVirtualDisplays(mFdp.ConsumeBool());
+
+    mTestableFlinger.commitTransactionsLocked(mFdp.ConsumeIntegral<uint32_t>());
+
+    mTestableFlinger.notifyPowerBoost(mFdp.ConsumeIntegral<int32_t>());
+
+    setDisplayStateLocked();
+
+    setTransactionState();
+    mTestableFlinger.flushTransactionQueues();
+
+    onTransact(data, size);
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
+    android::fuzz::SurfaceFlingerFuzzer surfaceFlingerFuzzer(data, size);
+    surfaceFlingerFuzzer.process(data, size);
+    return 0;
+}
+
+} // namespace android::fuzz
diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h
new file mode 100644
index 0000000..1d0f62b
--- /dev/null
+++ b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h
@@ -0,0 +1,824 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <compositionengine/Display.h>
+#include <compositionengine/LayerFECompositionState.h>
+#include <compositionengine/OutputLayer.h>
+#include <compositionengine/impl/CompositionEngine.h>
+#include <compositionengine/impl/Display.h>
+#include <compositionengine/impl/OutputLayerCompositionState.h>
+#include <gui/LayerDebugInfo.h>
+#include <gui/ScreenCaptureResults.h>
+#include <gui/SurfaceComposerClient.h>
+#include <gui/mock/GraphicBufferProducer.h>
+#include <ui/DisplayStatInfo.h>
+#include <ui/DynamicDisplayInfo.h>
+
+#include "BufferQueueLayer.h"
+#include "BufferStateLayer.h"
+#include "ContainerLayer.h"
+#include "DisplayDevice.h"
+#include "DisplayHardware/ComposerHal.h"
+#include "EffectLayer.h"
+#include "FrameTimeline/FrameTimeline.h"
+#include "FrameTracer/FrameTracer.h"
+#include "Layer.h"
+#include "NativeWindowSurface.h"
+#include "Scheduler/EventThread.h"
+#include "Scheduler/MessageQueue.h"
+#include "Scheduler/RefreshRateConfigs.h"
+#include "Scheduler/VSyncTracker.h"
+#include "Scheduler/VsyncConfiguration.h"
+#include "Scheduler/VsyncController.h"
+#include "Scheduler/VsyncModulator.h"
+#include "StartPropertySetThread.h"
+#include "SurfaceFlinger.h"
+#include "SurfaceFlingerDefaultFactory.h"
+#include "SurfaceInterceptor.h"
+#include "TimeStats/TimeStats.h"
+
+#include "renderengine/mock/RenderEngine.h"
+#include "scheduler/TimeKeeper.h"
+#include "tests/unittests/mock/DisplayHardware/MockComposer.h"
+#include "tests/unittests/mock/DisplayHardware/MockDisplayMode.h"
+#include "tests/unittests/mock/DisplayHardware/MockHWC2.h"
+#include "tests/unittests/mock/DisplayHardware/MockPowerAdvisor.h"
+#include "tests/unittests/mock/MockEventThread.h"
+#include "tests/unittests/mock/MockFrameTimeline.h"
+#include "tests/unittests/mock/MockFrameTracer.h"
+#include "tests/unittests/mock/MockNativeWindowSurface.h"
+#include "tests/unittests/mock/MockSurfaceInterceptor.h"
+#include "tests/unittests/mock/MockTimeStats.h"
+#include "tests/unittests/mock/MockVSyncTracker.h"
+#include "tests/unittests/mock/MockVsyncController.h"
+
+namespace android {
+namespace Hwc2 {
+
+class Composer;
+
+namespace types = hardware::graphics::common;
+
+namespace V2_1 = hardware::graphics::composer::V2_1;
+namespace V2_2 = hardware::graphics::composer::V2_2;
+namespace V2_3 = hardware::graphics::composer::V2_3;
+namespace V2_4 = hardware::graphics::composer::V2_4;
+
+using types::V1_0::ColorTransform;
+using types::V1_0::Transform;
+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;
+using V2_1::Display;
+using V2_1::Error;
+using V2_1::Layer;
+using V2_4::CommandReaderBase;
+using V2_4::CommandWriterBase;
+using V2_4::IComposer;
+using V2_4::IComposerCallback;
+using V2_4::IComposerClient;
+using V2_4::VsyncPeriodChangeTimeline;
+using V2_4::VsyncPeriodNanos;
+using DisplayCapability = IComposerClient::DisplayCapability;
+using PerFrameMetadata = IComposerClient::PerFrameMetadata;
+using PerFrameMetadataKey = IComposerClient::PerFrameMetadataKey;
+using PerFrameMetadataBlob = IComposerClient::PerFrameMetadataBlob;
+}; // namespace Hwc2
+
+static constexpr hal::HWDisplayId kHwDisplayId = 1000;
+
+static constexpr ui::Hdr kHdrTypes[] = {ui::Hdr::DOLBY_VISION, ui::Hdr::HDR10, ui::Hdr::HLG,
+                                        ui::Hdr::HDR10_PLUS};
+
+static constexpr ui::ColorMode kColormodes[] = {ui::ColorMode::NATIVE,
+                                                ui::ColorMode::STANDARD_BT601_625,
+                                                ui::ColorMode::STANDARD_BT601_625_UNADJUSTED,
+                                                ui::ColorMode::STANDARD_BT601_525,
+                                                ui::ColorMode::STANDARD_BT601_525_UNADJUSTED,
+                                                ui::ColorMode::STANDARD_BT709,
+                                                ui::ColorMode::DCI_P3,
+                                                ui::ColorMode::SRGB,
+                                                ui::ColorMode::ADOBE_RGB,
+                                                ui::ColorMode::DISPLAY_P3,
+                                                ui::ColorMode::BT2020,
+                                                ui::ColorMode::BT2100_PQ,
+                                                ui::ColorMode::BT2100_HLG,
+                                                ui::ColorMode::DISPLAY_BT2020};
+
+static constexpr ui::PixelFormat kPixelFormats[] = {ui::PixelFormat::RGBA_8888,
+                                                    ui::PixelFormat::RGBX_8888,
+                                                    ui::PixelFormat::RGB_888,
+                                                    ui::PixelFormat::RGB_565,
+                                                    ui::PixelFormat::BGRA_8888,
+                                                    ui::PixelFormat::YCBCR_422_SP,
+                                                    ui::PixelFormat::YCRCB_420_SP,
+                                                    ui::PixelFormat::YCBCR_422_I,
+                                                    ui::PixelFormat::RGBA_FP16,
+                                                    ui::PixelFormat::RAW16,
+                                                    ui::PixelFormat::BLOB,
+                                                    ui::PixelFormat::IMPLEMENTATION_DEFINED,
+                                                    ui::PixelFormat::YCBCR_420_888,
+                                                    ui::PixelFormat::RAW_OPAQUE,
+                                                    ui::PixelFormat::RAW10,
+                                                    ui::PixelFormat::RAW12,
+                                                    ui::PixelFormat::RGBA_1010102,
+                                                    ui::PixelFormat::Y8,
+                                                    ui::PixelFormat::Y16,
+                                                    ui::PixelFormat::YV12,
+                                                    ui::PixelFormat::DEPTH_16,
+                                                    ui::PixelFormat::DEPTH_24,
+                                                    ui::PixelFormat::DEPTH_24_STENCIL_8,
+                                                    ui::PixelFormat::DEPTH_32F,
+                                                    ui::PixelFormat::DEPTH_32F_STENCIL_8,
+                                                    ui::PixelFormat::STENCIL_8,
+                                                    ui::PixelFormat::YCBCR_P010,
+                                                    ui::PixelFormat::HSV_888};
+
+FloatRect getFuzzedFloatRect(FuzzedDataProvider *fdp) {
+    return FloatRect(fdp->ConsumeFloatingPoint<float>() /*left*/,
+                     fdp->ConsumeFloatingPoint<float>() /*right*/,
+                     fdp->ConsumeFloatingPoint<float>() /*top*/,
+                     fdp->ConsumeFloatingPoint<float>() /*bottom*/);
+}
+
+HdrMetadata getFuzzedHdrMetadata(FuzzedDataProvider *fdp) {
+    HdrMetadata hdrMetadata;
+    if (fdp->ConsumeBool()) {
+        hdrMetadata.cta8613.maxContentLightLevel = fdp->ConsumeFloatingPoint<float>();
+        hdrMetadata.cta8613.maxFrameAverageLightLevel = fdp->ConsumeFloatingPoint<float>();
+
+        hdrMetadata.validTypes |= HdrMetadata::CTA861_3;
+    } else {
+        hdrMetadata.smpte2086.displayPrimaryRed.x = fdp->ConsumeFloatingPoint<float>();
+        hdrMetadata.smpte2086.displayPrimaryRed.y = fdp->ConsumeFloatingPoint<float>();
+        hdrMetadata.smpte2086.displayPrimaryGreen.x = fdp->ConsumeFloatingPoint<float>();
+        hdrMetadata.smpte2086.displayPrimaryGreen.y = fdp->ConsumeFloatingPoint<float>();
+        hdrMetadata.smpte2086.displayPrimaryBlue.x = fdp->ConsumeFloatingPoint<float>();
+        hdrMetadata.smpte2086.displayPrimaryBlue.y = fdp->ConsumeFloatingPoint<float>();
+        hdrMetadata.smpte2086.whitePoint.x = fdp->ConsumeFloatingPoint<float>();
+        hdrMetadata.smpte2086.whitePoint.y = fdp->ConsumeFloatingPoint<float>();
+        hdrMetadata.smpte2086.minLuminance = fdp->ConsumeFloatingPoint<float>();
+        hdrMetadata.smpte2086.maxLuminance = fdp->ConsumeFloatingPoint<float>();
+
+        hdrMetadata.validTypes |= HdrMetadata::SMPTE2086;
+    }
+    return hdrMetadata;
+}
+
+class EventThread;
+
+namespace hal = android::hardware::graphics::composer::hal;
+
+struct FakePhaseOffsets : scheduler::VsyncConfiguration {
+    static constexpr nsecs_t FAKE_PHASE_OFFSET_NS = 0;
+    static constexpr auto FAKE_DURATION_OFFSET_NS = std::chrono::nanoseconds(0);
+
+    VsyncConfigSet getConfigsForRefreshRate(Fps) const override { return getCurrentConfigs(); }
+
+    VsyncConfigSet getCurrentConfigs() const override {
+        return {{FAKE_PHASE_OFFSET_NS, FAKE_PHASE_OFFSET_NS, FAKE_DURATION_OFFSET_NS,
+                 FAKE_DURATION_OFFSET_NS},
+                {FAKE_PHASE_OFFSET_NS, FAKE_PHASE_OFFSET_NS, FAKE_DURATION_OFFSET_NS,
+                 FAKE_DURATION_OFFSET_NS},
+                {FAKE_PHASE_OFFSET_NS, FAKE_PHASE_OFFSET_NS, FAKE_DURATION_OFFSET_NS,
+                 FAKE_DURATION_OFFSET_NS},
+                FAKE_DURATION_OFFSET_NS};
+    }
+
+    void reset() override {}
+    void setRefreshRateFps(Fps) override {}
+    void dump(std::string &) const override {}
+};
+
+namespace scheduler {
+
+class TestableScheduler : public Scheduler, private ICompositor {
+public:
+    TestableScheduler(const std::shared_ptr<scheduler::RefreshRateConfigs> &refreshRateConfigs,
+                      ISchedulerCallback &callback)
+          : TestableScheduler(std::make_unique<android::mock::VsyncController>(),
+                              std::make_unique<android::mock::VSyncTracker>(), refreshRateConfigs,
+                              callback) {}
+
+    TestableScheduler(std::unique_ptr<VsyncController> controller,
+                      std::unique_ptr<VSyncTracker> tracker,
+                      std::shared_ptr<RefreshRateConfigs> configs, ISchedulerCallback &callback)
+          : Scheduler(*this, callback, Feature::kContentDetection) {
+        mVsyncSchedule.emplace(VsyncSchedule(std::move(tracker), nullptr, std::move(controller)));
+        setRefreshRateConfigs(std::move(configs));
+    }
+
+    ConnectionHandle createConnection(std::unique_ptr<EventThread> eventThread) {
+        return Scheduler::createConnection(std::move(eventThread));
+    }
+
+    auto &mutablePrimaryHWVsyncEnabled() { return mPrimaryHWVsyncEnabled; }
+    auto &mutableHWVsyncAvailable() { return mHWVsyncAvailable; }
+
+    auto &mutableLayerHistory() { return mLayerHistory; }
+
+    auto refreshRateConfigs() { return holdRefreshRateConfigs(); }
+
+    void replaceTouchTimer(int64_t millis) {
+        if (mTouchTimer) {
+            mTouchTimer.reset();
+        }
+        mTouchTimer.emplace(
+                "Testable Touch timer", std::chrono::milliseconds(millis),
+                [this] { touchTimerCallback(TimerState::Reset); },
+                [this] { touchTimerCallback(TimerState::Expired); });
+        mTouchTimer->start();
+    }
+
+    bool isTouchActive() {
+        std::lock_guard<std::mutex> lock(mPolicyLock);
+        return mPolicy.touch == Scheduler::TouchState::Active;
+    }
+
+    void dispatchCachedReportedMode() {
+        std::lock_guard<std::mutex> lock(mPolicyLock);
+        return Scheduler::dispatchCachedReportedMode();
+    }
+
+    void clearCachedReportedMode() {
+        std::lock_guard<std::mutex> lock(mPolicyLock);
+        mPolicy.cachedModeChangedParams.reset();
+    }
+
+    void onNonPrimaryDisplayModeChanged(ConnectionHandle handle, DisplayModePtr mode) {
+        return Scheduler::onNonPrimaryDisplayModeChanged(handle, mode);
+    }
+
+private:
+    // ICompositor overrides:
+    bool commit(nsecs_t, int64_t, nsecs_t) override { return false; }
+    void composite(nsecs_t, int64_t) override {}
+    void sample() override {}
+
+    // MessageQueue overrides:
+    void scheduleFrame() override {}
+    void postMessage(sp<MessageHandler>&&) override {}
+};
+
+} // namespace scheduler
+
+namespace surfaceflinger::test {
+
+class Factory final : public surfaceflinger::Factory {
+public:
+    ~Factory() = default;
+
+    std::unique_ptr<HWComposer> createHWComposer(const std::string &) override { return nullptr; }
+
+    std::unique_ptr<MessageQueue> createMessageQueue(ICompositor &compositor) {
+        return std::make_unique<android::impl::MessageQueue>(compositor);
+    }
+
+    std::unique_ptr<scheduler::VsyncConfiguration> createVsyncConfiguration(
+            Fps /*currentRefreshRate*/) override {
+        return std::make_unique<FakePhaseOffsets>();
+    }
+
+    std::unique_ptr<scheduler::Scheduler> createScheduler(
+            const std::shared_ptr<scheduler::RefreshRateConfigs> &,
+            scheduler::ISchedulerCallback &) {
+        return nullptr;
+    }
+
+    sp<SurfaceInterceptor> createSurfaceInterceptor() override {
+        return new android::impl::SurfaceInterceptor();
+    }
+
+    sp<StartPropertySetThread> createStartPropertySetThread(bool timestampPropertyValue) override {
+        return new StartPropertySetThread(timestampPropertyValue);
+    }
+
+    sp<DisplayDevice> createDisplayDevice(DisplayDeviceCreationArgs &creationArgs) override {
+        return new DisplayDevice(creationArgs);
+    }
+
+    sp<GraphicBuffer> createGraphicBuffer(uint32_t width, uint32_t height, PixelFormat format,
+                                          uint32_t layerCount, uint64_t usage,
+                                          std::string requestorName) override {
+        return new GraphicBuffer(width, height, format, layerCount, usage, requestorName);
+    }
+
+    void createBufferQueue(sp<IGraphicBufferProducer> *outProducer,
+                           sp<IGraphicBufferConsumer> *outConsumer,
+                           bool consumerIsSurfaceFlinger) override {
+        if (!mCreateBufferQueue) {
+            BufferQueue::createBufferQueue(outProducer, outConsumer, consumerIsSurfaceFlinger);
+            return;
+        }
+        mCreateBufferQueue(outProducer, outConsumer, consumerIsSurfaceFlinger);
+    }
+
+    sp<IGraphicBufferProducer> createMonitoredProducer(const sp<IGraphicBufferProducer> &producer,
+                                                       const sp<SurfaceFlinger> &flinger,
+                                                       const wp<Layer> &layer) override {
+        return new MonitoredProducer(producer, flinger, layer);
+    }
+
+    sp<BufferLayerConsumer> createBufferLayerConsumer(const sp<IGraphicBufferConsumer> &consumer,
+                                                      renderengine::RenderEngine &renderEngine,
+                                                      uint32_t textureName, Layer *layer) override {
+        return new BufferLayerConsumer(consumer, renderEngine, textureName, layer);
+    }
+
+    std::unique_ptr<surfaceflinger::NativeWindowSurface> createNativeWindowSurface(
+            const sp<IGraphicBufferProducer> &producer) override {
+        if (!mCreateNativeWindowSurface) return nullptr;
+        return mCreateNativeWindowSurface(producer);
+    }
+
+    std::unique_ptr<compositionengine::CompositionEngine> createCompositionEngine() override {
+        return compositionengine::impl::createCompositionEngine();
+    }
+
+    sp<BufferQueueLayer> createBufferQueueLayer(const LayerCreationArgs &) override {
+        return nullptr;
+    }
+
+    sp<BufferStateLayer> createBufferStateLayer(const LayerCreationArgs &) override {
+        return nullptr;
+    }
+
+    sp<EffectLayer> createEffectLayer(const LayerCreationArgs &args) override {
+        return new EffectLayer(args);
+    }
+
+    sp<ContainerLayer> createContainerLayer(const LayerCreationArgs &args) override {
+        return new ContainerLayer(args);
+    }
+
+    std::unique_ptr<FrameTracer> createFrameTracer() override {
+        return std::make_unique<android::mock::FrameTracer>();
+    }
+
+    std::unique_ptr<frametimeline::FrameTimeline> createFrameTimeline(
+            std::shared_ptr<TimeStats> timeStats, pid_t surfaceFlingerPid = 0) override {
+        return std::make_unique<android::mock::FrameTimeline>(timeStats, surfaceFlingerPid);
+    }
+
+    using CreateBufferQueueFunction =
+            std::function<void(sp<IGraphicBufferProducer> * /* outProducer */,
+                               sp<IGraphicBufferConsumer> * /* outConsumer */,
+                               bool /* consumerIsSurfaceFlinger */)>;
+    CreateBufferQueueFunction mCreateBufferQueue;
+
+    using CreateNativeWindowSurfaceFunction =
+            std::function<std::unique_ptr<surfaceflinger::NativeWindowSurface>(
+                    const sp<IGraphicBufferProducer> &)>;
+    CreateNativeWindowSurfaceFunction mCreateNativeWindowSurface;
+
+    using CreateCompositionEngineFunction =
+            std::function<std::unique_ptr<compositionengine::CompositionEngine>()>;
+    CreateCompositionEngineFunction mCreateCompositionEngine;
+};
+
+} // namespace surfaceflinger::test
+
+// TODO(b/189053744) : Create a common test/mock library for surfaceflinger
+class TestableSurfaceFlinger final : private scheduler::ISchedulerCallback {
+public:
+    using HotplugEvent = SurfaceFlinger::HotplugEvent;
+
+    SurfaceFlinger *flinger() { return mFlinger.get(); }
+    scheduler::TestableScheduler *scheduler() { return mScheduler; }
+
+    // Allow reading display state without locking, as if called on the SF main thread.
+    auto onInitializeDisplays() NO_THREAD_SAFETY_ANALYSIS {
+        return mFlinger->onInitializeDisplays();
+    }
+
+    void setGlobalShadowSettings(FuzzedDataProvider *fdp) {
+        const half4 ambientColor{fdp->ConsumeFloatingPoint<float>(),
+                                 fdp->ConsumeFloatingPoint<float>(),
+                                 fdp->ConsumeFloatingPoint<float>(),
+                                 fdp->ConsumeFloatingPoint<float>()};
+        const half4 spotColor{fdp->ConsumeFloatingPoint<float>(),
+                              fdp->ConsumeFloatingPoint<float>(),
+                              fdp->ConsumeFloatingPoint<float>(),
+                              fdp->ConsumeFloatingPoint<float>()};
+        float lightPosY = fdp->ConsumeFloatingPoint<float>();
+        float lightPosZ = fdp->ConsumeFloatingPoint<float>();
+        float lightRadius = fdp->ConsumeFloatingPoint<float>();
+        mFlinger->setGlobalShadowSettings(ambientColor, spotColor, lightPosY, lightPosZ,
+                                          lightRadius);
+    }
+
+    void onPullAtom(FuzzedDataProvider *fdp) {
+        const int32_t atomId = fdp->ConsumeIntegral<uint8_t>();
+        std::string pulledData = fdp->ConsumeRandomLengthString().c_str();
+        bool success = fdp->ConsumeBool();
+        mFlinger->onPullAtom(atomId, &pulledData, &success);
+    }
+
+    void fuzzDumpsysAndDebug(FuzzedDataProvider *fdp) {
+        std::string result = fdp->ConsumeRandomLengthString().c_str();
+        mFlinger->appendSfConfigString(result);
+        result = fdp->ConsumeRandomLengthString().c_str();
+        mFlinger->listLayersLocked(result);
+
+        using DumpArgs = Vector<String16>;
+        DumpArgs dumpArgs;
+        dumpArgs.push_back(String16(fdp->ConsumeRandomLengthString().c_str()));
+        mFlinger->clearStatsLocked(dumpArgs, result);
+
+        mFlinger->dumpTimeStats(dumpArgs, fdp->ConsumeBool(), result);
+        mFlinger->logFrameStats();
+
+        result = fdp->ConsumeRandomLengthString().c_str();
+        mFlinger->dumpFrameTimeline(dumpArgs, result);
+
+        result = fdp->ConsumeRandomLengthString().c_str();
+        mFlinger->dumpStaticScreenStats(result);
+
+        result = fdp->ConsumeRandomLengthString().c_str();
+        mFlinger->dumpFrameEventsLocked(result);
+
+        result = fdp->ConsumeRandomLengthString().c_str();
+        mFlinger->dumpRawDisplayIdentificationData(dumpArgs, result);
+
+        LayersProto layersProto = mFlinger->dumpDrawingStateProto(fdp->ConsumeIntegral<uint32_t>());
+        mFlinger->dumpOffscreenLayersProto(layersProto);
+        LayersTraceProto layersTraceProto{};
+        mFlinger->dumpDisplayProto(layersTraceProto);
+
+        result = fdp->ConsumeRandomLengthString().c_str();
+        mFlinger->dumpHwc(result);
+
+        mFlinger->calculateColorMatrix(fdp->ConsumeFloatingPoint<float>());
+        mFlinger->updateColorMatrixLocked();
+        mFlinger->CheckTransactCodeCredentials(fdp->ConsumeIntegral<uint32_t>());
+
+        const CountDownLatch transactionCommittedSignal(fdp->ConsumeIntegral<uint32_t>());
+        mFlinger->waitForSynchronousTransaction(transactionCommittedSignal);
+        mFlinger->signalSynchronousTransactions(fdp->ConsumeIntegral<uint32_t>());
+    }
+
+    void getCompositionPreference() {
+        ui::Dataspace outDataspace;
+        ui::PixelFormat outPixelFormat;
+        ui::Dataspace outWideColorGamutDataspace;
+        ui::PixelFormat outWideColorGamutPixelFormat;
+        mFlinger->getCompositionPreference(&outDataspace, &outPixelFormat,
+                                           &outWideColorGamutDataspace,
+                                           &outWideColorGamutPixelFormat);
+    }
+
+    void overrideHdrTypes(sp<IBinder> &display, FuzzedDataProvider *fdp) {
+        std::vector<ui::Hdr> hdrTypes;
+        hdrTypes.push_back(fdp->PickValueInArray(kHdrTypes));
+        mFlinger->overrideHdrTypes(display, hdrTypes);
+    }
+
+    void getDisplayedContentSample(sp<IBinder> &display, FuzzedDataProvider *fdp) {
+        DisplayedFrameStats outDisplayedFrameStats;
+        mFlinger->getDisplayedContentSample(display, fdp->ConsumeIntegral<uint64_t>(),
+                                            fdp->ConsumeIntegral<uint64_t>(),
+                                            &outDisplayedFrameStats);
+    }
+
+    void getDisplayStats(sp<IBinder> &display) {
+        android::DisplayStatInfo stats;
+        mFlinger->getDisplayStats(display, &stats);
+    }
+
+    void getDisplayState(sp<IBinder> &display) {
+        ui::DisplayState displayState;
+        mFlinger->getDisplayState(display, &displayState);
+    }
+
+    void getStaticDisplayInfo(sp<IBinder> &display) {
+        ui::StaticDisplayInfo staticDisplayInfo;
+        mFlinger->getStaticDisplayInfo(display, &staticDisplayInfo);
+    }
+
+    void getDynamicDisplayInfo(sp<IBinder> &display) {
+        android::ui::DynamicDisplayInfo dynamicDisplayInfo;
+        mFlinger->getDynamicDisplayInfo(display, &dynamicDisplayInfo);
+    }
+    void getDisplayNativePrimaries(sp<IBinder> &display) {
+        android::ui::DisplayPrimaries displayPrimaries;
+        mFlinger->getDisplayNativePrimaries(display, displayPrimaries);
+    }
+
+    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);
+    }
+
+    void setVsyncConfig(FuzzedDataProvider *fdp) {
+        const scheduler::VsyncModulator::VsyncConfig vsyncConfig{};
+        mFlinger->setVsyncConfig(vsyncConfig, fdp->ConsumeIntegral<nsecs_t>());
+    }
+
+    void updateCompositorTiming(FuzzedDataProvider *fdp) {
+        std::shared_ptr<FenceTime> presentFenceTime = FenceTime::NO_FENCE;
+        mFlinger->updateCompositorTiming({}, fdp->ConsumeIntegral<nsecs_t>(), presentFenceTime);
+    }
+
+    void getCompositorTiming() {
+        CompositorTiming compositorTiming;
+        mFlinger->getCompositorTiming(&compositorTiming);
+    }
+
+    sp<IBinder> fuzzBoot(FuzzedDataProvider *fdp) {
+        mFlinger->callingThreadHasUnscopedSurfaceFlingerAccess(fdp->ConsumeBool());
+        mFlinger->createConnection();
+
+        DisplayIdGenerator<HalVirtualDisplayId> kGenerator;
+        HalVirtualDisplayId halVirtualDisplayId = kGenerator.generateId().value();
+
+        ui::Size uiSize{fdp->ConsumeIntegral<int32_t>(), fdp->ConsumeIntegral<int32_t>()};
+        ui::PixelFormat pixelFormat{};
+        mFlinger->getHwComposer().allocateVirtualDisplay(halVirtualDisplayId, uiSize, &pixelFormat);
+
+        PhysicalDisplayId physicalDisplayId = SurfaceComposerClient::getInternalDisplayId().value();
+        mFlinger->getHwComposer().allocatePhysicalDisplay(kHwDisplayId, physicalDisplayId);
+
+        sp<IBinder> display =
+                mFlinger->createDisplay(String8(fdp->ConsumeRandomLengthString().c_str()),
+                                        fdp->ConsumeBool());
+
+        onInitializeDisplays();
+        mFlinger->getPhysicalDisplayToken(physicalDisplayId);
+
+        mFlinger->mStartPropertySetThread =
+                mFlinger->getFactory().createStartPropertySetThread(fdp->ConsumeBool());
+
+        mFlinger->bootFinished();
+
+        return display;
+    }
+
+    void fuzzSurfaceFlinger(const uint8_t *data, size_t size) {
+        FuzzedDataProvider mFdp(data, size);
+
+        sp<IBinder> display = fuzzBoot(&mFdp);
+
+        sp<IGraphicBufferProducer> bufferProducer = sp<mock::GraphicBufferProducer>::make();
+        mFlinger->authenticateSurfaceTexture(bufferProducer.get());
+
+        mFlinger->createDisplayEventConnection();
+
+        getDisplayStats(display);
+        getDisplayState(display);
+        getStaticDisplayInfo(display);
+        getDynamicDisplayInfo(display);
+        getDisplayNativePrimaries(display);
+
+        mFlinger->setAutoLowLatencyMode(display, mFdp.ConsumeBool());
+        mFlinger->setGameContentType(display, mFdp.ConsumeBool());
+        mFlinger->setPowerMode(display, mFdp.ConsumeIntegral<int>());
+        mFlinger->clearAnimationFrameStats();
+
+        overrideHdrTypes(display, &mFdp);
+
+        onPullAtom(&mFdp);
+
+        mFlinger->injectVSync(mFdp.ConsumeIntegral<nsecs_t>());
+
+        getCompositionPreference();
+        getDisplayedContentSample(display, &mFdp);
+        getDesiredDisplayModeSpecs(display);
+
+        bool outSupport;
+        mFlinger->getDisplayBrightnessSupport(display, &outSupport);
+
+        mFlinger->notifyPowerBoost(mFdp.ConsumeIntegral<int32_t>());
+
+        setGlobalShadowSettings(&mFdp);
+
+        mFlinger->binderDied(display);
+        mFlinger->onFirstRef();
+
+        mFlinger->commitTransactions();
+        mFlinger->updateInputFlinger();
+        mFlinger->updateCursorAsync();
+
+        setVsyncConfig(&mFdp);
+
+        mFlinger->flushTransactionQueues(0);
+
+        mFlinger->setTransactionFlags(mFdp.ConsumeIntegral<uint32_t>());
+        mFlinger->clearTransactionFlags(mFdp.ConsumeIntegral<uint32_t>());
+        mFlinger->commitOffscreenLayers();
+
+        mFlinger->frameIsEarly(mFdp.ConsumeIntegral<nsecs_t>(), mFdp.ConsumeIntegral<int64_t>());
+        mFlinger->computeLayerBounds();
+        mFlinger->startBootAnim();
+
+        mFlinger->readPersistentProperties();
+
+        mFlinger->exceedsMaxRenderTargetSize(mFdp.ConsumeIntegral<uint32_t>(),
+                                             mFdp.ConsumeIntegral<uint32_t>());
+
+        mFlinger->getMaxAcquiredBufferCountForCurrentRefreshRate(mFdp.ConsumeIntegral<uid_t>());
+
+        mFlinger->postComposition();
+
+        getCompositorTiming();
+
+        updateCompositorTiming(&mFdp);
+
+        mFlinger->setCompositorTimingSnapped({}, mFdp.ConsumeIntegral<nsecs_t>());
+        mFlinger->postFrame();
+        mFlinger->calculateExpectedPresentTime({});
+
+        mFlinger->enableHalVirtualDisplays(mFdp.ConsumeBool());
+
+        fuzzDumpsysAndDebug(&mFdp);
+
+        mFlinger->destroyDisplay(display);
+    }
+
+    void setupRenderEngine(std::unique_ptr<renderengine::RenderEngine> renderEngine) {
+        mFlinger->mCompositionEngine->setRenderEngine(std::move(renderEngine));
+    }
+
+    void setupComposer(std::unique_ptr<Hwc2::Composer> composer) {
+        mFlinger->mCompositionEngine->setHwComposer(
+                std::make_unique<impl::HWComposer>(std::move(composer)));
+    }
+
+    void setupTimeStats(const std::shared_ptr<TimeStats> &timeStats) {
+        mFlinger->mCompositionEngine->setTimeStats(timeStats);
+    }
+
+    // The ISchedulerCallback argument can be nullptr for a no-op implementation.
+    void setupScheduler(std::unique_ptr<scheduler::VsyncController> vsyncController,
+                        std::unique_ptr<scheduler::VSyncTracker> vsyncTracker,
+                        std::unique_ptr<EventThread> appEventThread,
+                        std::unique_ptr<EventThread> sfEventThread,
+                        scheduler::ISchedulerCallback *callback = nullptr,
+                        bool hasMultipleModes = false) {
+        constexpr DisplayModeId kModeId60{0};
+        DisplayModes modes = makeModes(mock::createDisplayMode(kModeId60, 60_Hz));
+
+        if (hasMultipleModes) {
+            constexpr DisplayModeId kModeId90{1};
+            modes.try_emplace(kModeId90, mock::createDisplayMode(kModeId90, 90_Hz));
+        }
+
+        mRefreshRateConfigs = std::make_shared<scheduler::RefreshRateConfigs>(modes, kModeId60);
+        const auto fps = mRefreshRateConfigs->getActiveMode()->getFps();
+        mFlinger->mVsyncConfiguration = mFactory.createVsyncConfiguration(fps);
+        mFlinger->mVsyncModulator = sp<scheduler::VsyncModulator>::make(
+                mFlinger->mVsyncConfiguration->getCurrentConfigs());
+        mFlinger->mRefreshRateStats =
+                std::make_unique<scheduler::RefreshRateStats>(*mFlinger->mTimeStats, fps,
+                                                              hal::PowerMode::OFF);
+
+        mScheduler = new scheduler::TestableScheduler(std::move(vsyncController),
+                                                      std::move(vsyncTracker), mRefreshRateConfigs,
+                                                      *(callback ?: this));
+
+        mFlinger->mAppConnectionHandle = mScheduler->createConnection(std::move(appEventThread));
+        mFlinger->mSfConnectionHandle = mScheduler->createConnection(std::move(sfEventThread));
+        resetScheduler(mScheduler);
+    }
+
+    void resetScheduler(scheduler::Scheduler *scheduler) { mFlinger->mScheduler.reset(scheduler); }
+
+    scheduler::TestableScheduler &mutableScheduler() const { return *mScheduler; }
+
+    using CreateBufferQueueFunction = surfaceflinger::test::Factory::CreateBufferQueueFunction;
+    void setCreateBufferQueueFunction(CreateBufferQueueFunction f) {
+        mFactory.mCreateBufferQueue = f;
+    }
+
+    using CreateNativeWindowSurfaceFunction =
+            surfaceflinger::test::Factory::CreateNativeWindowSurfaceFunction;
+    void setCreateNativeWindowSurface(CreateNativeWindowSurfaceFunction f) {
+        mFactory.mCreateNativeWindowSurface = f;
+    }
+
+    void setInternalDisplayPrimaries(const ui::DisplayPrimaries &primaries) {
+        memcpy(&mFlinger->mInternalDisplayPrimaries, &primaries, sizeof(ui::DisplayPrimaries));
+    }
+
+    static auto &mutableLayerDrawingState(const sp<Layer> &layer) { return layer->mDrawingState; }
+
+    auto &mutableStateLock() { return mFlinger->mStateLock; }
+
+    static auto findOutputLayerForDisplay(const sp<Layer> &layer,
+                                          const sp<const DisplayDevice> &display) {
+        return layer->findOutputLayerForDisplay(display.get());
+    }
+
+    /* ------------------------------------------------------------------------
+     * Forwarding for functions being tested
+     */
+
+    void enableHalVirtualDisplays(bool enable) { mFlinger->enableHalVirtualDisplays(enable); }
+
+    auto commitTransactionsLocked(uint32_t transactionFlags) {
+        Mutex::Autolock lock(mFlinger->mStateLock);
+        return mFlinger->commitTransactionsLocked(transactionFlags);
+    }
+
+    auto setDisplayStateLocked(const DisplayState &s) {
+        Mutex::Autolock lock(mFlinger->mStateLock);
+        return mFlinger->setDisplayStateLocked(s);
+    }
+
+    auto notifyPowerBoost(int32_t boostId) { return mFlinger->notifyPowerBoost(boostId); }
+
+    // Allow reading display state without locking, as if called on the SF main thread.
+    auto setPowerModeInternal(const sp<DisplayDevice> &display,
+                              hal::PowerMode mode) NO_THREAD_SAFETY_ANALYSIS {
+        return mFlinger->setPowerModeInternal(display, mode);
+    }
+
+    auto &getTransactionQueue() { return mFlinger->mTransactionQueue; }
+    auto &getPendingTransactionQueue() { return mFlinger->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) {
+        return mFlinger->setTransactionState(frameTimelineInfo, states, displays, flags, applyToken,
+                                             inputWindowCommands, desiredPresentTime,
+                                             isAutoTimestamp, uncacheBuffer, hasListenerCallbacks,
+                                             listenerCallbacks, transactionId);
+    }
+
+    auto flushTransactionQueues() { return mFlinger->flushTransactionQueues(0); };
+
+    auto onTransact(uint32_t code, const Parcel &data, Parcel *reply, uint32_t flags) {
+        return mFlinger->onTransact(code, data, reply, flags);
+    }
+
+    auto getGPUContextPriority() { return mFlinger->getGPUContextPriority(); }
+
+    auto calculateMaxAcquiredBufferCount(Fps refreshRate,
+                                         std::chrono::nanoseconds presentLatency) const {
+        return SurfaceFlinger::calculateMaxAcquiredBufferCount(refreshRate, presentLatency);
+    }
+
+    /* Read-write access to private data to set up preconditions and assert
+     * post-conditions.
+     */
+
+    auto &mutableCurrentState() { return mFlinger->mCurrentState; }
+    auto &mutableDisplays() { return mFlinger->mDisplays; }
+    auto &mutableDrawingState() { return mFlinger->mDrawingState; }
+    auto &mutableInterceptor() { return mFlinger->mInterceptor; }
+
+    auto fromHandle(const sp<IBinder> &handle) { return mFlinger->fromHandle(handle); }
+
+    ~TestableSurfaceFlinger() {
+        mutableDisplays().clear();
+        mutableCurrentState().displays.clear();
+        mutableDrawingState().displays.clear();
+        mutableInterceptor().clear();
+        mFlinger->mScheduler.reset();
+        mFlinger->mCompositionEngine->setHwComposer(std::unique_ptr<HWComposer>());
+        mFlinger->mCompositionEngine->setRenderEngine(
+                std::unique_ptr<renderengine::RenderEngine>());
+    }
+
+private:
+    void setVsyncEnabled(bool) override {}
+    void requestDisplayMode(DisplayModePtr, DisplayModeEvent) override {}
+    void kernelTimerChanged(bool) override {}
+    void triggerOnFrameRateOverridesChanged() override {}
+
+    surfaceflinger::test::Factory mFactory;
+    sp<SurfaceFlinger> mFlinger = new SurfaceFlinger(mFactory, SurfaceFlinger::SkipInitialization);
+    scheduler::TestableScheduler *mScheduler = nullptr;
+    std::shared_ptr<scheduler::RefreshRateConfigs> mRefreshRateConfigs;
+};
+
+} // namespace android
diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_layer_fuzzer.cpp b/services/surfaceflinger/fuzzer/surfaceflinger_layer_fuzzer.cpp
new file mode 100644
index 0000000..46d52dd
--- /dev/null
+++ b/services/surfaceflinger/fuzzer/surfaceflinger_layer_fuzzer.cpp
@@ -0,0 +1,199 @@
+/*
+ * 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.
+ *
+ */
+#include <BufferStateLayer.h>
+#include <Client.h>
+#include <DisplayDevice.h>
+#include <EffectLayer.h>
+#include <LayerRejecter.h>
+#include <LayerRenderArea.h>
+#include <MonitoredProducer.h>
+#include <fuzzer/FuzzedDataProvider.h>
+#include <gui/IProducerListener.h>
+#include <gui/LayerDebugInfo.h>
+#include <gui/SurfaceComposerClient.h>
+#include <gui/WindowInfo.h>
+#include <renderengine/mock/FakeExternalTexture.h>
+#include <ui/DisplayStatInfo.h>
+
+#include <FuzzableDataspaces.h>
+#include <surfaceflinger_fuzzers_utils.h>
+
+namespace android::fuzzer {
+using namespace renderengine;
+
+constexpr uint16_t kRandomStringLength = 256;
+
+class LayerFuzzer {
+public:
+    LayerFuzzer(const uint8_t* data, size_t size) : mFdp(data, size){};
+    void init();
+    void invokeBufferStateLayer();
+    void invokeEffectLayer();
+    LayerCreationArgs createLayerCreationArgs(TestableSurfaceFlinger* flinger, sp<Client> client);
+    Rect getFuzzedRect();
+    FrameTimelineInfo getFuzzedFrameTimelineInfo();
+
+private:
+    FuzzedDataProvider mFdp;
+};
+
+Rect LayerFuzzer::getFuzzedRect() {
+    return Rect(mFdp.ConsumeIntegral<int32_t>() /*left*/, mFdp.ConsumeIntegral<int32_t>() /*top*/,
+                mFdp.ConsumeIntegral<int32_t>() /*right*/,
+                mFdp.ConsumeIntegral<int32_t>() /*bottom*/);
+}
+
+FrameTimelineInfo LayerFuzzer::getFuzzedFrameTimelineInfo() {
+    return FrameTimelineInfo{.vsyncId = mFdp.ConsumeIntegral<int64_t>(),
+                             .inputEventId = mFdp.ConsumeIntegral<int32_t>()};
+}
+
+LayerCreationArgs LayerFuzzer::createLayerCreationArgs(TestableSurfaceFlinger* flinger,
+                                                       sp<Client> client) {
+    flinger->setupScheduler(std::make_unique<android::mock::VsyncController>(),
+                            std::make_unique<android::mock::VSyncTracker>(),
+                            std::make_unique<android::mock::EventThread>(),
+                            std::make_unique<android::mock::EventThread>());
+
+    return LayerCreationArgs(flinger->flinger(), client,
+                             mFdp.ConsumeRandomLengthString(kRandomStringLength) /*name*/,
+                             mFdp.ConsumeIntegral<uint32_t>() /*flags*/, {} /*metadata*/);
+}
+
+void LayerFuzzer::invokeEffectLayer() {
+    TestableSurfaceFlinger flinger;
+    sp<Client> client = sp<Client>::make(flinger.flinger());
+    const LayerCreationArgs layerCreationArgs = createLayerCreationArgs(&flinger, client);
+    sp<EffectLayer> effectLayer = sp<EffectLayer>::make(layerCreationArgs);
+
+    effectLayer->setColor({(mFdp.ConsumeFloatingPointInRange<float>(0, 255) /*x*/,
+                            mFdp.ConsumeFloatingPointInRange<float>(0, 255) /*y*/,
+                            mFdp.ConsumeFloatingPointInRange<float>(0, 255) /*z*/)});
+    effectLayer->setDataspace(mFdp.PickValueInArray(kDataspaces));
+    sp<EffectLayer> parent = sp<EffectLayer>::make(layerCreationArgs);
+    effectLayer->setChildrenDrawingParent(parent);
+
+    const FrameTimelineInfo frameInfo = getFuzzedFrameTimelineInfo();
+    const int64_t postTime = mFdp.ConsumeIntegral<int64_t>();
+    effectLayer->setFrameTimelineVsyncForBufferTransaction(frameInfo, postTime);
+    effectLayer->setFrameTimelineVsyncForBufferlessTransaction(frameInfo, postTime);
+    auto surfaceFrame = effectLayer->createSurfaceFrameForTransaction(frameInfo, postTime);
+    auto surfaceFrame1 =
+            effectLayer->createSurfaceFrameForBuffer(frameInfo, postTime,
+                                                     mFdp.ConsumeRandomLengthString(
+                                                             kRandomStringLength) /*bufferName*/);
+    effectLayer->addSurfaceFramePresentedForBuffer(surfaceFrame,
+                                                   mFdp.ConsumeIntegral<int64_t>() /*acquireTime*/,
+                                                   mFdp.ConsumeIntegral<int64_t>() /*currentTime*/);
+    effectLayer->addSurfaceFrameDroppedForBuffer(surfaceFrame1);
+
+    parent.clear();
+    client.clear();
+    effectLayer.clear();
+}
+
+void LayerFuzzer::invokeBufferStateLayer() {
+    TestableSurfaceFlinger flinger;
+    sp<Client> client = sp<Client>::make(flinger.flinger());
+    sp<BufferStateLayer> layer =
+            sp<BufferStateLayer>::make(createLayerCreationArgs(&flinger, client));
+    sp<Fence> fence = sp<Fence>::make();
+    const std::shared_ptr<FenceTime> fenceTime = std::make_shared<FenceTime>(fence);
+
+    const CompositorTiming compositor = {mFdp.ConsumeIntegral<int64_t>(),
+                                         mFdp.ConsumeIntegral<int64_t>(),
+                                         mFdp.ConsumeIntegral<int64_t>()};
+    std::packaged_task<renderengine::RenderEngineResult()> renderResult([&] {
+        return renderengine::RenderEngineResult{mFdp.ConsumeIntegral<int32_t>(),
+                                                base::unique_fd(fence->get())};
+    });
+    layer->onLayerDisplayed(renderResult.get_future());
+    layer->releasePendingBuffer(mFdp.ConsumeIntegral<int64_t>());
+    layer->finalizeFrameEventHistory(fenceTime, compositor);
+    layer->onPostComposition(nullptr, fenceTime, fenceTime, compositor);
+    layer->isBufferDue(mFdp.ConsumeIntegral<int64_t>());
+
+    layer->setTransform(mFdp.ConsumeIntegral<uint32_t>());
+    layer->setTransformToDisplayInverse(mFdp.ConsumeBool());
+    layer->setCrop(getFuzzedRect());
+
+    layer->setHdrMetadata(getFuzzedHdrMetadata(&mFdp));
+    layer->setDataspace(mFdp.PickValueInArray(kDataspaces));
+    if (mFdp.ConsumeBool()) {
+        layer->setSurfaceDamageRegion(Region());
+        layer->setTransparentRegionHint(Region());
+    } else {
+        layer->setSurfaceDamageRegion(Region(getFuzzedRect()));
+        layer->setTransparentRegionHint(Region(getFuzzedRect()));
+    }
+    layer->setApi(mFdp.ConsumeIntegral<int32_t>());
+
+    native_handle_t* testHandle = native_handle_create(0, 1);
+    const bool ownsHandle = mFdp.ConsumeBool();
+    sp<NativeHandle> nativeHandle = sp<NativeHandle>::make(testHandle, ownsHandle);
+    layer->setSidebandStream(nativeHandle);
+    layer->addFrameEvent(fence, mFdp.ConsumeIntegral<int64_t>() /*postedTime*/,
+                         mFdp.ConsumeIntegral<int64_t>() /*requestedTime*/);
+    layer->computeSourceBounds(getFuzzedFloatRect(&mFdp));
+
+    layer->fenceHasSignaled();
+    layer->framePresentTimeIsCurrent(mFdp.ConsumeIntegral<int64_t>());
+    layer->onPreComposition(mFdp.ConsumeIntegral<int64_t>());
+    const std::vector<sp<CallbackHandle>> callbacks;
+    layer->setTransactionCompletedListeners(callbacks);
+
+    std::shared_ptr<renderengine::ExternalTexture> texture = std::make_shared<
+            renderengine::mock::FakeExternalTexture>(mFdp.ConsumeIntegral<uint32_t>(),
+                                                     mFdp.ConsumeIntegral<uint32_t>(),
+                                                     mFdp.ConsumeIntegral<uint64_t>(),
+                                                     static_cast<android::PixelFormat>(
+                                                             mFdp.PickValueInArray(kPixelFormats)),
+                                                     mFdp.ConsumeIntegral<uint64_t>());
+    layer->setBuffer(texture, {} /*bufferData*/, mFdp.ConsumeIntegral<nsecs_t>() /*postTime*/,
+                     mFdp.ConsumeIntegral<nsecs_t>() /*desiredTime*/,
+                     mFdp.ConsumeBool() /*isAutoTimestamp*/,
+                     {mFdp.ConsumeIntegral<nsecs_t>()} /*dequeue*/, {} /*info*/);
+
+    LayerRenderArea layerArea(*(flinger.flinger()), layer, getFuzzedRect(),
+                              {mFdp.ConsumeIntegral<int32_t>(),
+                               mFdp.ConsumeIntegral<int32_t>()} /*reqSize*/,
+                              mFdp.PickValueInArray(kDataspaces), mFdp.ConsumeBool(),
+                              getFuzzedRect(), mFdp.ConsumeBool());
+    layerArea.render([]() {} /*drawLayers*/);
+
+    if (!ownsHandle) {
+        native_handle_close(testHandle);
+        native_handle_delete(testHandle);
+    }
+    nativeHandle.clear();
+    fence.clear();
+    client.clear();
+    layer.clear();
+}
+
+void LayerFuzzer::init() {
+    invokeBufferStateLayer();
+    invokeEffectLayer();
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+    LayerFuzzer layerFuzzer(data, size);
+    layerFuzzer.init();
+    return 0;
+}
+
+} // namespace android::fuzzer
diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp
new file mode 100644
index 0000000..da60a69
--- /dev/null
+++ b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp
@@ -0,0 +1,404 @@
+/*
+ * 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.
+ *
+ */
+
+#include <ftl/enum.h>
+#include <fuzzer/FuzzedDataProvider.h>
+#include <processgroup/sched_policy.h>
+
+#include "Scheduler/DispSyncSource.h"
+#include "Scheduler/OneShotTimer.h"
+#include "Scheduler/VSyncDispatchTimerQueue.h"
+#include "Scheduler/VSyncPredictor.h"
+#include "Scheduler/VSyncReactor.h"
+
+#include "surfaceflinger_fuzzers_utils.h"
+#include "surfaceflinger_scheduler_fuzzer.h"
+
+namespace android::fuzz {
+
+using hardware::graphics::composer::hal::PowerMode;
+
+constexpr nsecs_t kVsyncPeriods[] = {(30_Hz).getPeriodNsecs(), (60_Hz).getPeriodNsecs(),
+                                     (72_Hz).getPeriodNsecs(), (90_Hz).getPeriodNsecs(),
+                                     (120_Hz).getPeriodNsecs()};
+
+constexpr auto kLayerVoteTypes = ftl::enum_range<scheduler::RefreshRateConfigs::LayerVoteType>();
+
+constexpr PowerMode kPowerModes[] = {PowerMode::ON, PowerMode::DOZE, PowerMode::OFF,
+                                     PowerMode::DOZE_SUSPEND, PowerMode::ON_SUSPEND};
+
+constexpr uint16_t kRandomStringLength = 256;
+constexpr std::chrono::duration kSyncPeriod(16ms);
+
+template <typename T>
+void dump(T* component, FuzzedDataProvider* fdp) {
+    std::string res = fdp->ConsumeRandomLengthString(kRandomStringLength);
+    component->dump(res);
+}
+
+class SchedulerFuzzer : private VSyncSource::Callback {
+public:
+    SchedulerFuzzer(const uint8_t* data, size_t size) : mFdp(data, size){};
+    void process();
+
+private:
+    void fuzzRefreshRateSelection();
+    void fuzzRefreshRateConfigs();
+    void fuzzVSyncModulator();
+    void fuzzVSyncPredictor();
+    void fuzzVSyncReactor();
+    void fuzzLayerHistory();
+    void fuzzDispSyncSource();
+    void fuzzCallbackToken(scheduler::VSyncDispatchTimerQueue* dispatch);
+    void fuzzVSyncDispatchTimerQueue();
+    void fuzzOneShotTimer();
+    void fuzzEventThread();
+    PhysicalDisplayId getPhysicalDisplayId();
+
+    FuzzedDataProvider mFdp;
+
+protected:
+    void onVSyncEvent(nsecs_t /* when */, VSyncSource::VSyncData) {}
+};
+
+PhysicalDisplayId SchedulerFuzzer::getPhysicalDisplayId() {
+    PhysicalDisplayId internalDispId = PhysicalDisplayId::fromPort(111u);
+    PhysicalDisplayId externalDispId = PhysicalDisplayId::fromPort(222u);
+    PhysicalDisplayId randomDispId = PhysicalDisplayId::fromPort(mFdp.ConsumeIntegral<uint16_t>());
+    PhysicalDisplayId dispId64Bit = PhysicalDisplayId::fromEdid(0xffu, 0xffffu, 0xffff'ffffu);
+    PhysicalDisplayId displayId = mFdp.PickValueInArray<PhysicalDisplayId>(
+            {internalDispId, externalDispId, dispId64Bit, randomDispId});
+    return displayId;
+}
+
+void SchedulerFuzzer::fuzzEventThread() {
+    const auto getVsyncPeriod = [](uid_t /* uid */) { return kSyncPeriod.count(); };
+    std::unique_ptr<android::impl::EventThread> thread = std::make_unique<
+            android::impl::EventThread>(std::move(std::make_unique<FuzzImplVSyncSource>()), nullptr,
+                                        nullptr, nullptr, getVsyncPeriod);
+
+    thread->onHotplugReceived(getPhysicalDisplayId(), mFdp.ConsumeBool());
+    sp<EventThreadConnection> connection =
+            new EventThreadConnection(thread.get(), mFdp.ConsumeIntegral<uint16_t>(), nullptr,
+                                      {} /*eventRegistration*/);
+    thread->requestNextVsync(connection);
+    thread->setVsyncRate(mFdp.ConsumeIntegral<uint32_t>() /*rate*/, connection);
+
+    thread->setDuration((std::chrono::nanoseconds)mFdp.ConsumeIntegral<uint64_t>(),
+                        (std::chrono::nanoseconds)mFdp.ConsumeIntegral<uint64_t>());
+    thread->registerDisplayEventConnection(connection);
+    thread->onScreenAcquired();
+    thread->onScreenReleased();
+    dump<android::impl::EventThread>(thread.get(), &mFdp);
+}
+
+void SchedulerFuzzer::fuzzDispSyncSource() {
+    std::unique_ptr<FuzzImplVSyncDispatch> vSyncDispatch =
+            std::make_unique<FuzzImplVSyncDispatch>();
+    std::unique_ptr<FuzzImplVSyncTracker> vSyncTracker = std::make_unique<FuzzImplVSyncTracker>();
+    std::unique_ptr<scheduler::DispSyncSource> dispSyncSource = std::make_unique<
+            scheduler::DispSyncSource>(*vSyncDispatch, *vSyncTracker,
+                                       (std::chrono::nanoseconds)
+                                               mFdp.ConsumeIntegral<uint64_t>() /*workDuration*/,
+                                       (std::chrono::nanoseconds)mFdp.ConsumeIntegral<uint64_t>()
+                                       /*readyDuration*/,
+                                       mFdp.ConsumeBool(),
+                                       mFdp.ConsumeRandomLengthString(kRandomStringLength).c_str());
+    dispSyncSource->setVSyncEnabled(true);
+    dispSyncSource->setCallback(this);
+    dispSyncSource->setDuration((std::chrono::nanoseconds)mFdp.ConsumeIntegral<uint64_t>(), 0ns);
+    dump<scheduler::DispSyncSource>(dispSyncSource.get(), &mFdp);
+}
+
+void SchedulerFuzzer::fuzzCallbackToken(scheduler::VSyncDispatchTimerQueue* dispatch) {
+    scheduler::VSyncDispatch::CallbackToken tmp = dispatch->registerCallback(
+            [&](auto, auto, auto) {
+                dispatch->schedule(tmp,
+                                   {.workDuration = mFdp.ConsumeIntegral<nsecs_t>(),
+                                    .readyDuration = mFdp.ConsumeIntegral<nsecs_t>(),
+                                    .earliestVsync = mFdp.ConsumeIntegral<nsecs_t>()});
+            },
+            "o.o");
+    dispatch->schedule(tmp,
+                       {.workDuration = mFdp.ConsumeIntegral<nsecs_t>(),
+                        .readyDuration = mFdp.ConsumeIntegral<nsecs_t>(),
+                        .earliestVsync = mFdp.ConsumeIntegral<nsecs_t>()});
+    dispatch->unregisterCallback(tmp);
+    dispatch->cancel(tmp);
+}
+
+void SchedulerFuzzer::fuzzVSyncDispatchTimerQueue() {
+    FuzzImplVSyncTracker stubTracker{mFdp.ConsumeIntegral<nsecs_t>()};
+    scheduler::VSyncDispatchTimerQueue
+            mDispatch{std::make_unique<scheduler::ControllableClock>(), stubTracker,
+                      mFdp.ConsumeIntegral<nsecs_t>() /*dispatchGroupThreshold*/,
+                      mFdp.ConsumeIntegral<nsecs_t>() /*vSyncMoveThreshold*/};
+
+    fuzzCallbackToken(&mDispatch);
+
+    dump<scheduler::VSyncDispatchTimerQueue>(&mDispatch, &mFdp);
+
+    scheduler::VSyncDispatchTimerQueueEntry entry(
+            "fuzz", [](auto, auto, auto) {},
+            mFdp.ConsumeIntegral<nsecs_t>() /*vSyncMoveThreshold*/);
+    entry.update(stubTracker, 0);
+    entry.schedule({.workDuration = mFdp.ConsumeIntegral<nsecs_t>(),
+                    .readyDuration = mFdp.ConsumeIntegral<nsecs_t>(),
+                    .earliestVsync = mFdp.ConsumeIntegral<nsecs_t>()},
+                   stubTracker, 0);
+    entry.disarm();
+    entry.ensureNotRunning();
+    entry.schedule({.workDuration = mFdp.ConsumeIntegral<nsecs_t>(),
+                    .readyDuration = mFdp.ConsumeIntegral<nsecs_t>(),
+                    .earliestVsync = mFdp.ConsumeIntegral<nsecs_t>()},
+                   stubTracker, 0);
+    auto const wakeup = entry.wakeupTime();
+    auto const ready = entry.readyTime();
+    entry.callback(entry.executing(), *wakeup, *ready);
+    entry.addPendingWorkloadUpdate({.workDuration = mFdp.ConsumeIntegral<nsecs_t>(),
+                                    .readyDuration = mFdp.ConsumeIntegral<nsecs_t>(),
+                                    .earliestVsync = mFdp.ConsumeIntegral<nsecs_t>()});
+    dump<scheduler::VSyncDispatchTimerQueueEntry>(&entry, &mFdp);
+}
+
+void SchedulerFuzzer::fuzzVSyncPredictor() {
+    uint16_t now = mFdp.ConsumeIntegral<uint16_t>();
+    uint16_t historySize = mFdp.ConsumeIntegralInRange<uint16_t>(1, UINT16_MAX);
+    uint16_t minimumSamplesForPrediction = mFdp.ConsumeIntegralInRange<uint16_t>(1, UINT16_MAX);
+    scheduler::VSyncPredictor tracker{mFdp.ConsumeIntegral<uint16_t>() /*period*/, historySize,
+                                      minimumSamplesForPrediction,
+                                      mFdp.ConsumeIntegral<uint32_t>() /*outlierTolerancePercent*/};
+    uint16_t period = mFdp.ConsumeIntegral<uint16_t>();
+    tracker.setPeriod(period);
+    for (uint16_t i = 0; i < minimumSamplesForPrediction; ++i) {
+        if (!tracker.needsMoreSamples()) {
+            break;
+        }
+        tracker.addVsyncTimestamp(now += period);
+    }
+    tracker.nextAnticipatedVSyncTimeFrom(now);
+    tracker.resetModel();
+}
+
+void SchedulerFuzzer::fuzzOneShotTimer() {
+    FakeClock* clock = new FakeClock();
+    std::unique_ptr<scheduler::OneShotTimer> idleTimer = std::make_unique<scheduler::OneShotTimer>(
+            mFdp.ConsumeRandomLengthString(kRandomStringLength) /*name*/,
+            (std::chrono::milliseconds)mFdp.ConsumeIntegral<uint8_t>() /*val*/,
+            [] {} /*resetCallback*/, [] {} /*timeoutCallback*/, std::unique_ptr<FakeClock>(clock));
+    idleTimer->start();
+    idleTimer->reset();
+    idleTimer->stop();
+}
+
+void SchedulerFuzzer::fuzzLayerHistory() {
+    TestableSurfaceFlinger flinger;
+    flinger.setupScheduler(std::make_unique<android::mock::VsyncController>(),
+                           std::make_unique<android::mock::VSyncTracker>(),
+                           std::make_unique<android::mock::EventThread>(),
+                           std::make_unique<android::mock::EventThread>());
+    flinger.setupTimeStats(std::make_unique<android::mock::TimeStats>());
+    std::unique_ptr<android::renderengine::RenderEngine> renderEngine =
+            std::make_unique<android::renderengine::mock::RenderEngine>();
+    flinger.setupRenderEngine(std::move(renderEngine));
+    flinger.setupComposer(std::make_unique<android::Hwc2::mock::Composer>());
+
+    scheduler::TestableScheduler* scheduler = flinger.scheduler();
+
+    scheduler::LayerHistory& historyV1 = scheduler->mutableLayerHistory();
+    nsecs_t time1 = systemTime();
+    nsecs_t time2 = time1;
+    uint8_t historySize = mFdp.ConsumeIntegral<uint8_t>();
+
+    sp<FuzzImplLayer> layer1 = new FuzzImplLayer(flinger.flinger());
+    sp<FuzzImplLayer> layer2 = new FuzzImplLayer(flinger.flinger());
+
+    for (int i = 0; i < historySize; ++i) {
+        historyV1.record(layer1.get(), time1, time1,
+                         scheduler::LayerHistory::LayerUpdateType::Buffer);
+        historyV1.record(layer2.get(), time2, time2,
+                         scheduler::LayerHistory::LayerUpdateType::Buffer);
+        time1 += mFdp.PickValueInArray(kVsyncPeriods);
+        time2 += mFdp.PickValueInArray(kVsyncPeriods);
+    }
+    historyV1.summarize(*scheduler->refreshRateConfigs(), time1);
+    historyV1.summarize(*scheduler->refreshRateConfigs(), time2);
+
+    scheduler->createConnection(std::make_unique<android::mock::EventThread>());
+
+    scheduler::ConnectionHandle handle;
+    scheduler->createDisplayEventConnection(handle);
+    scheduler->setDuration(handle, (std::chrono::nanoseconds)mFdp.ConsumeIntegral<uint64_t>(),
+                           (std::chrono::nanoseconds)mFdp.ConsumeIntegral<uint64_t>());
+
+    dump<scheduler::TestableScheduler>(scheduler, &mFdp);
+}
+
+void SchedulerFuzzer::fuzzVSyncReactor() {
+    std::shared_ptr<FuzzImplVSyncTracker> vSyncTracker = std::make_shared<FuzzImplVSyncTracker>();
+    scheduler::VSyncReactor reactor(std::make_unique<ClockWrapper>(
+                                            std::make_shared<FuzzImplClock>()),
+                                    *vSyncTracker, mFdp.ConsumeIntegral<uint8_t>() /*pendingLimit*/,
+                                    false);
+
+    reactor.startPeriodTransition(mFdp.ConsumeIntegral<nsecs_t>());
+    bool periodFlushed = mFdp.ConsumeBool();
+    reactor.addHwVsyncTimestamp(0, std::nullopt, &periodFlushed);
+    reactor.addHwVsyncTimestamp(mFdp.ConsumeIntegral<nsecs_t>() /*newPeriod*/, std::nullopt,
+                                &periodFlushed);
+    sp<Fence> fence = new Fence(memfd_create("fd", MFD_ALLOW_SEALING));
+    std::shared_ptr<FenceTime> ft = std::make_shared<FenceTime>(fence);
+    vSyncTracker->addVsyncTimestamp(mFdp.ConsumeIntegral<nsecs_t>());
+    FenceTime::Snapshot snap(mFdp.ConsumeIntegral<nsecs_t>());
+    ft->applyTrustedSnapshot(snap);
+    reactor.setIgnorePresentFences(mFdp.ConsumeBool());
+    reactor.addPresentFence(ft);
+    dump<scheduler::VSyncReactor>(&reactor, &mFdp);
+}
+
+void SchedulerFuzzer::fuzzVSyncModulator() {
+    enum {
+        SF_OFFSET_LATE,
+        APP_OFFSET_LATE,
+        SF_DURATION_LATE,
+        APP_DURATION_LATE,
+        SF_OFFSET_EARLY,
+        APP_OFFSET_EARLY,
+        SF_DURATION_EARLY,
+        APP_DURATION_EARLY,
+        SF_OFFSET_EARLY_GPU,
+        APP_OFFSET_EARLY_GPU,
+        SF_DURATION_EARLY_GPU,
+        APP_DURATION_EARLY_GPU,
+        HWC_MIN_WORK_DURATION,
+    };
+    using Schedule = scheduler::TransactionSchedule;
+    using nanos = std::chrono::nanoseconds;
+    using VsyncModulator = scheduler::VsyncModulator;
+    using FuzzImplVsyncModulator = scheduler::FuzzImplVsyncModulator;
+    const VsyncModulator::VsyncConfig early{SF_OFFSET_EARLY, APP_OFFSET_EARLY,
+                                            nanos(SF_DURATION_LATE), nanos(APP_DURATION_LATE)};
+    const VsyncModulator::VsyncConfig earlyGpu{SF_OFFSET_EARLY_GPU, APP_OFFSET_EARLY_GPU,
+                                               nanos(SF_DURATION_EARLY), nanos(APP_DURATION_EARLY)};
+    const VsyncModulator::VsyncConfig late{SF_OFFSET_LATE, APP_OFFSET_LATE,
+                                           nanos(SF_DURATION_EARLY_GPU),
+                                           nanos(APP_DURATION_EARLY_GPU)};
+    const VsyncModulator::VsyncConfigSet offsets = {early, earlyGpu, late,
+                                                    nanos(HWC_MIN_WORK_DURATION)};
+    sp<FuzzImplVsyncModulator> vSyncModulator =
+            sp<FuzzImplVsyncModulator>::make(offsets, scheduler::Now);
+    (void)vSyncModulator->setVsyncConfigSet(offsets);
+    (void)vSyncModulator->setTransactionSchedule(Schedule::Late);
+    const auto token = sp<BBinder>::make();
+    (void)vSyncModulator->setTransactionSchedule(Schedule::EarlyStart, token);
+    vSyncModulator->binderDied(token);
+}
+
+void SchedulerFuzzer::fuzzRefreshRateSelection() {
+    TestableSurfaceFlinger flinger;
+    flinger.setupScheduler(std::make_unique<android::mock::VsyncController>(),
+                           std::make_unique<android::mock::VSyncTracker>(),
+                           std::make_unique<android::mock::EventThread>(),
+                           std::make_unique<android::mock::EventThread>());
+
+    sp<Client> client;
+    LayerCreationArgs args(flinger.flinger(), client,
+                           mFdp.ConsumeRandomLengthString(kRandomStringLength) /*name*/,
+                           mFdp.ConsumeIntegral<uint16_t>() /*layerFlags*/, LayerMetadata());
+    sp<Layer> layer = new BufferQueueLayer(args);
+
+    layer->setFrameRateSelectionPriority(mFdp.ConsumeIntegral<int16_t>());
+}
+
+void SchedulerFuzzer::fuzzRefreshRateConfigs() {
+    using RefreshRateConfigs = scheduler::RefreshRateConfigs;
+    using LayerRequirement = RefreshRateConfigs::LayerRequirement;
+    using RefreshRateStats = scheduler::RefreshRateStats;
+
+    const uint16_t minRefreshRate = mFdp.ConsumeIntegralInRange<uint16_t>(1, UINT16_MAX >> 1);
+    const uint16_t maxRefreshRate =
+            mFdp.ConsumeIntegralInRange<uint16_t>(minRefreshRate + 1, UINT16_MAX);
+
+    const DisplayModeId modeId{mFdp.ConsumeIntegralInRange<uint8_t>(0, 10)};
+
+    DisplayModes displayModes;
+    for (uint16_t fps = minRefreshRate; fps < maxRefreshRate; ++fps) {
+        displayModes.try_emplace(modeId,
+                                 mock::createDisplayMode(modeId,
+                                                         Fps::fromValue(static_cast<float>(fps))));
+    }
+
+    RefreshRateConfigs refreshRateConfigs(displayModes, modeId);
+
+    const RefreshRateConfigs::GlobalSignals globalSignals = {.touch = false, .idle = false};
+    std::vector<LayerRequirement> layers = {{.weight = mFdp.ConsumeFloatingPoint<float>()}};
+
+    refreshRateConfigs.getBestRefreshRate(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);
+
+    refreshRateConfigs.setDisplayManagerPolicy(
+            {modeId,
+             {Fps::fromValue(mFdp.ConsumeFloatingPoint<float>()),
+              Fps::fromValue(mFdp.ConsumeFloatingPoint<float>())}});
+    refreshRateConfigs.setActiveModeId(modeId);
+
+    RefreshRateConfigs::isFractionalPairOrMultiple(Fps::fromValue(
+                                                           mFdp.ConsumeFloatingPoint<float>()),
+                                                   Fps::fromValue(
+                                                           mFdp.ConsumeFloatingPoint<float>()));
+    RefreshRateConfigs::getFrameRateDivisor(Fps::fromValue(mFdp.ConsumeFloatingPoint<float>()),
+                                            Fps::fromValue(mFdp.ConsumeFloatingPoint<float>()));
+
+    android::mock::TimeStats timeStats;
+    RefreshRateStats refreshRateStats(timeStats, Fps::fromValue(mFdp.ConsumeFloatingPoint<float>()),
+                                      PowerMode::OFF);
+
+    const auto fpsOpt = displayModes.get(modeId, [](const auto& mode) { return mode->getFps(); });
+    refreshRateStats.setRefreshRate(*fpsOpt);
+
+    refreshRateStats.setPowerMode(mFdp.PickValueInArray(kPowerModes));
+}
+
+void SchedulerFuzzer::process() {
+    fuzzRefreshRateSelection();
+    fuzzRefreshRateConfigs();
+    fuzzVSyncModulator();
+    fuzzVSyncPredictor();
+    fuzzVSyncReactor();
+    fuzzLayerHistory();
+    fuzzDispSyncSource();
+    fuzzEventThread();
+    fuzzVSyncDispatchTimerQueue();
+    fuzzOneShotTimer();
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+    SchedulerFuzzer schedulerFuzzer(data, size);
+    schedulerFuzzer.process();
+    return 0;
+}
+
+} // namespace android::fuzz
diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.h b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.h
new file mode 100644
index 0000000..1a49ead
--- /dev/null
+++ b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.h
@@ -0,0 +1,206 @@
+/*
+ * 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.
+ *
+ */
+
+/*
+    Reference for some of the classes and functions has been taken from unittests
+    present in frameworks/native/services/surfaceflinger/tests/unittests
+*/
+
+#pragma once
+
+#include <scheduler/TimeKeeper.h>
+
+#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"
+
+namespace android::fuzz {
+
+class FuzzImplClock : public android::scheduler::Clock {
+public:
+    nsecs_t now() const { return 1; }
+};
+
+class ClockWrapper : public android::scheduler::Clock {
+public:
+    ClockWrapper(std::shared_ptr<android::scheduler::Clock> const& clock) : mClock(clock) {}
+
+    nsecs_t now() const { return mClock->now(); }
+
+private:
+    std::shared_ptr<android::scheduler::Clock> const mClock;
+};
+
+} // namespace android::fuzz
+
+namespace android {
+
+using namespace std::chrono_literals;
+
+class FakeClock : public Clock {
+public:
+    virtual ~FakeClock() = default;
+    std::chrono::steady_clock::time_point now() const override { return mNow; }
+
+    void advanceTime(std::chrono::nanoseconds delta) { mNow += delta; }
+
+private:
+    std::chrono::steady_clock::time_point mNow;
+};
+
+class FuzzImplLayer : public Layer {
+public:
+    FuzzImplLayer(SurfaceFlinger* flinger, std::string name)
+          : Layer(LayerCreationArgs(flinger, nullptr, std::move(name), 0, {})) {}
+    explicit FuzzImplLayer(SurfaceFlinger* flinger) : FuzzImplLayer(flinger, "FuzzLayer") {}
+
+    const char* getType() const override { return ""; }
+
+    bool isVisible() const override { return true; }
+
+    sp<Layer> createClone() override { return nullptr; }
+};
+
+class FuzzImplVSyncSource : public VSyncSource {
+public:
+    const char* getName() const override { return "fuzz"; }
+
+    void setVSyncEnabled(bool /* enable */) override {}
+
+    void setCallback(Callback* /* callback */) override {}
+
+    void setDuration(std::chrono::nanoseconds /* workDuration */,
+                     std::chrono::nanoseconds /* readyDuration */) override {}
+
+    VSyncData getLatestVSyncData() const override { return {}; }
+
+    void dump(std::string& /* result */) const override {}
+};
+
+class FuzzImplVSyncTracker : public scheduler::VSyncTracker {
+public:
+    FuzzImplVSyncTracker(nsecs_t period) { mPeriod = period; }
+
+    FuzzImplVSyncTracker() = default;
+
+    bool addVsyncTimestamp(nsecs_t /* timestamp */) override { return true; }
+
+    nsecs_t nextAnticipatedVSyncTimeFrom(nsecs_t /* timePoint */) const override { return 1; }
+
+    nsecs_t currentPeriod() const override { return 1; }
+
+    void setPeriod(nsecs_t /* period */) override {}
+
+    void resetModel() override {}
+
+    bool needsMoreSamples() const override { return true; }
+
+    bool isVSyncInPhase(nsecs_t /* timePoint */, Fps /* frameRate */) const override {
+        return true;
+    }
+
+    nsecs_t nextVSyncTime(nsecs_t timePoint) const {
+        if (timePoint % mPeriod == 0) {
+            return timePoint;
+        }
+        return (timePoint - (timePoint % mPeriod) + mPeriod);
+    }
+
+    void dump(std::string& /* result */) const override {}
+
+protected:
+    nsecs_t mPeriod;
+};
+
+class FuzzImplVSyncDispatch : public scheduler::VSyncDispatch {
+public:
+    CallbackToken registerCallback(Callback /* callbackFn */,
+                                   std::string /* callbackName */) override {
+        return CallbackToken{};
+    }
+
+    void unregisterCallback(CallbackToken /* token */) override {}
+
+    scheduler::ScheduleResult schedule(CallbackToken /* token */,
+                                       ScheduleTiming /* scheduleTiming */) override {
+        return (scheduler::ScheduleResult)0;
+    }
+
+    scheduler::CancelResult cancel(CallbackToken /* token */) override {
+        return (scheduler::CancelResult)0;
+    }
+
+    void dump(std::string& /* result */) const override {}
+};
+
+} // namespace android
+
+namespace android::scheduler {
+
+class ControllableClock : public TimeKeeper {
+public:
+    nsecs_t now() const { return 1; };
+    void alarmAt(std::function<void()> /* callback */, nsecs_t /* time */) override {}
+    void alarmCancel() override {}
+    void dump(std::string& /* result */) const override {}
+
+    void alarmAtDefaultBehavior(std::function<void()> const& callback, nsecs_t time) {
+        mCallback = callback;
+        mNextCallbackTime = time;
+    }
+
+    nsecs_t fakeTime() const { return mCurrentTime; }
+
+    void advanceToNextCallback() {
+        mCurrentTime = mNextCallbackTime;
+        if (mCallback) {
+            mCallback();
+        }
+    }
+
+    void advanceBy(nsecs_t advancement) {
+        mCurrentTime += advancement;
+        if (mCurrentTime >= (mNextCallbackTime + mLag) && mCallback) {
+            mCallback();
+        }
+    };
+
+    void setLag(nsecs_t lag) { mLag = lag; }
+
+private:
+    std::function<void()> mCallback;
+    nsecs_t mNextCallbackTime = 0;
+    nsecs_t mCurrentTime = 0;
+    nsecs_t mLag = 0;
+};
+
+static VsyncModulator::TimePoint Now() {
+    static VsyncModulator::TimePoint now;
+    return now += VsyncModulator::MIN_EARLY_TRANSACTION_TIME;
+}
+
+class FuzzImplVsyncModulator : public VsyncModulator {
+public:
+    FuzzImplVsyncModulator(const VsyncConfigSet& config, Now now) : VsyncModulator(config, now) {}
+
+    void binderDied(const wp<IBinder>& token) { VsyncModulator::binderDied(token); }
+};
+} // namespace android::scheduler
diff --git a/services/surfaceflinger/layerproto/LayerProtoParser.cpp b/services/surfaceflinger/layerproto/LayerProtoParser.cpp
index 2841f7c..854084e 100644
--- a/services/surfaceflinger/layerproto/LayerProtoParser.cpp
+++ b/services/surfaceflinger/layerproto/LayerProtoParser.cpp
@@ -305,7 +305,7 @@
                   zOrderRelativeOf == nullptr ? "none" : zOrderRelativeOf->name.c_str());
     StringAppendF(&result, "      activeBuffer=%s,", activeBuffer.to_string().c_str());
     StringAppendF(&result, " tr=%s", bufferTransform.to_string().c_str());
-    StringAppendF(&result, " queued-frames=%d, mRefreshPending=%d,", queuedFrames, refreshPending);
+    StringAppendF(&result, " queued-frames=%d", queuedFrames);
     StringAppendF(&result, " metadata={");
     bool first = true;
     for (const auto& entry : metadata.mMap) {
diff --git a/services/surfaceflinger/layerproto/common.proto b/services/surfaceflinger/layerproto/common.proto
index 1c73a9f..a6d8d61 100644
--- a/services/surfaceflinger/layerproto/common.proto
+++ b/services/surfaceflinger/layerproto/common.proto
@@ -18,6 +18,11 @@
 option optimize_for = LITE_RUNTIME;
 package android.surfaceflinger;
 
+message RegionProto {
+    reserved 1; // Previously: uint64 id
+    repeated RectProto rect = 2;
+}
+
 message RectProto {
   int32 left   = 1;
   int32 top    = 2;
@@ -36,4 +41,51 @@
   float dsdy = 3;
   float dtdy = 4;
   int32 type = 5;
-}
\ No newline at end of file
+}
+
+message ColorProto {
+    float r = 1;
+    float g = 2;
+    float b = 3;
+    float a = 4;
+}
+
+message InputWindowInfoProto {
+    uint32 layout_params_flags = 1;
+    int32 layout_params_type = 2;
+    RectProto frame = 3;
+    RegionProto touchable_region = 4;
+
+    int32 surface_inset = 5;
+    bool visible = 6;
+    bool can_receive_keys = 7 [deprecated = true];
+    bool focusable = 8;
+    bool has_wallpaper = 9;
+
+    float global_scale_factor = 10;
+    float window_x_scale = 11 [deprecated = true];
+    float window_y_scale = 12 [deprecated = true];
+
+    int32 crop_layer_id = 13;
+    bool replace_touchable_region_with_crop = 14;
+    RectProto touchable_region_crop = 15;
+    TransformProto transform = 16;
+}
+
+message BlurRegion {
+    uint32 blur_radius = 1;
+    uint32 corner_radius_tl = 2;
+    uint32 corner_radius_tr = 3;
+    uint32 corner_radius_bl = 4;
+    float corner_radius_br = 5;
+    float alpha = 6;
+    int32 left = 7;
+    int32 top = 8;
+    int32 right = 9;
+    int32 bottom = 10;
+}
+
+message ColorTransformProto {
+    // This will be a 4x4 matrix of float values
+    repeated float val = 1;
+}
diff --git a/services/surfaceflinger/layerproto/display.proto b/services/surfaceflinger/layerproto/display.proto
index ee8830e..c8cd926 100644
--- a/services/surfaceflinger/layerproto/display.proto
+++ b/services/surfaceflinger/layerproto/display.proto
@@ -33,4 +33,6 @@
     RectProto layer_stack_space_rect = 5;
 
     TransformProto transform = 6;
+
+    bool is_virtual = 7;
 }
diff --git a/libs/ui/Size.cpp b/services/surfaceflinger/layerproto/include/layerproto/TransactionProto.h
similarity index 68%
copy from libs/ui/Size.cpp
copy to services/surfaceflinger/layerproto/include/layerproto/TransactionProto.h
index d2996d1..3e9ca52 100644
--- a/libs/ui/Size.cpp
+++ b/services/surfaceflinger/layerproto/include/layerproto/TransactionProto.h
@@ -1,5 +1,5 @@
 /*
- * Copyright 2019 The Android Open Source Project
+ * Copyright (C) 2021 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,11 +14,7 @@
  * limitations under the License.
  */
 
-#include <ui/Size.h>
-
-namespace android::ui {
-
-const Size Size::INVALID{-1, -1};
-const Size Size::EMPTY{0, 0};
-
-} // namespace android::ui
+// disable the warnings emitted from the protobuf headers. This file should be included instead of
+// directly including the generated header file
+#pragma GCC system_header
+#include <transactions.pb.h>
diff --git a/services/surfaceflinger/layerproto/layers.proto b/services/surfaceflinger/layerproto/layers.proto
index 057eabb..3598308 100644
--- a/services/surfaceflinger/layerproto/layers.proto
+++ b/services/surfaceflinger/layerproto/layers.proto
@@ -136,6 +136,8 @@
 
   // Corner radius explicitly set on layer rather than inherited
   float requested_corner_radius = 56;
+
+  RectProto destination_frame = 57;
 }
 
 message PositionProto {
@@ -143,11 +145,6 @@
   float y = 2;
 }
 
-message RegionProto {
-  reserved 1;  // Previously: uint64 id
-  repeated RectProto rect = 2;
-}
-
 message FloatRectProto {
   float left = 1;
   float top = 2;
@@ -160,13 +157,7 @@
   uint32 height = 2;
   uint32 stride = 3;
   int32 format = 4;
-}
-
-message ColorProto {
-  float r = 1;
-  float g = 2;
-  float b = 3;
-  float a = 4;
+  uint64 usage = 5;
 }
 
 message BarrierLayerProto {
@@ -176,42 +167,3 @@
   uint64 frame_number = 2;
 }
 
-message InputWindowInfoProto {
-    uint32 layout_params_flags = 1;
-    uint32 layout_params_type = 2;
-    RectProto frame = 3;
-    RegionProto touchable_region = 4;
-
-    uint32 surface_inset = 5;
-    bool visible = 6;
-    bool can_receive_keys = 7  [deprecated=true];
-    bool focusable = 8;
-    bool has_wallpaper = 9;
-
-    float global_scale_factor = 10;
-    float window_x_scale = 11 [deprecated=true];
-    float window_y_scale = 12 [deprecated=true];
-
-    uint32 crop_layer_id = 13;
-    bool replace_touchable_region_with_crop = 14;
-    RectProto touchable_region_crop = 15;
-    TransformProto transform = 16;
-}
-
-message ColorTransformProto {
-  // This will be a 4x4 matrix of float values
-  repeated float val = 1;
-}
-
-message BlurRegion {
-    uint32 blur_radius = 1;
-    uint32 corner_radius_tl = 2;
-    uint32 corner_radius_tr = 3;
-    uint32 corner_radius_bl = 4;
-    float corner_radius_br = 5;
-    float alpha = 6;
-    int32 left = 7;
-    int32 top = 8;
-    int32 right = 9;
-    int32 bottom = 10;
-}
diff --git a/services/surfaceflinger/layerproto/transactions.proto b/services/surfaceflinger/layerproto/transactions.proto
new file mode 100644
index 0000000..4f99b19
--- /dev/null
+++ b/services/surfaceflinger/layerproto/transactions.proto
@@ -0,0 +1,283 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+syntax = "proto3";
+option optimize_for = LITE_RUNTIME;
+
+import "frameworks/native/services/surfaceflinger/layerproto/common.proto";
+
+package android.surfaceflinger.proto;
+
+/* Represents a file full of surface flinger transactions.
+   Encoded, it should start with 0x54 0x4E 0x58 0x54 0x52 0x41 0x43 0x45 (.TNXTRACE), such
+   that they can be easily identified. */
+message TransactionTraceFile {
+    /* constant; MAGIC_NUMBER = (long) MAGIC_NUMBER_H << 32 | MagicNumber.MAGIC_NUMBER_L
+       (this is needed because enums have to be 32 bits and there's no nice way to put 64bit
+        constants into .proto files. */
+    enum MagicNumber {
+        INVALID = 0;
+        MAGIC_NUMBER_L = 0x54584E54; /* TNXT (little-endian ASCII) */
+        MAGIC_NUMBER_H = 0x45434152; /* RACE (little-endian ASCII) */
+    }
+
+    fixed64 magic_number = 1; /* Must be the first field, set to value in MagicNumber */
+    repeated TransactionTraceEntry entry = 2;
+}
+
+message TransactionTraceEntry {
+    int64 elapsed_realtime_nanos = 1;
+    int64 vsync_id = 2;
+    repeated TransactionState transactions = 3;
+    repeated LayerCreationArgs added_layers = 4;
+    repeated int32 removed_layers = 5;
+    repeated DisplayState added_displays = 6;
+    repeated int32 removed_displays = 7;
+    repeated int32 removed_layer_handles = 8;
+}
+
+message LayerCreationArgs {
+    int32 layer_id = 1;
+    string name = 2;
+    uint32 flags = 3;
+    int32 parent_id = 4;
+    int32 mirror_from_id = 5;
+}
+
+message TransactionState {
+    int32 pid = 1;
+    int32 uid = 2;
+    int64 vsync_id = 3;
+    int32 input_event_id = 4;
+    int64 post_time = 5;
+    uint64 transaction_id = 6;
+    repeated LayerState layer_changes = 7;
+    repeated DisplayState display_changes = 8;
+}
+
+// Keep insync with layer_state_t
+message LayerState {
+    int64 layer_id = 1;
+    // Changes are split into ChangesLsb and ChangesMsb. First 32 bits are in ChangesLsb
+    // and the next 32 bits are in ChangesMsb. This is needed because enums have to be
+    // 32 bits and there's no nice way to put 64bit constants into .proto files.
+    enum ChangesLsb {
+        eChangesLsbNone = 0;
+        ePositionChanged = 0x00000001;
+        eLayerChanged = 0x00000002;
+        eSizeChanged = 0x00000004;
+        eAlphaChanged = 0x00000008;
+
+        eMatrixChanged = 0x00000010;
+        eTransparentRegionChanged = 0x00000020;
+        eFlagsChanged = 0x00000040;
+        eLayerStackChanged = 0x00000080;
+
+        eReleaseBufferListenerChanged = 0x00000400;
+        eShadowRadiusChanged = 0x00000800;
+
+        eBufferCropChanged = 0x00002000;
+        eRelativeLayerChanged = 0x00004000;
+        eReparent = 0x00008000;
+
+        eColorChanged = 0x00010000;
+        eDestroySurface = 0x00020000;
+        eTransformChanged = 0x00040000;
+        eTransformToDisplayInverseChanged = 0x00080000;
+
+        eCropChanged = 0x00100000;
+        eBufferChanged = 0x00200000;
+        eAcquireFenceChanged = 0x00400000;
+        eDataspaceChanged = 0x00800000;
+
+        eHdrMetadataChanged = 0x01000000;
+        eSurfaceDamageRegionChanged = 0x02000000;
+        eApiChanged = 0x04000000;
+        eSidebandStreamChanged = 0x08000000;
+
+        eColorTransformChanged = 0x10000000;
+        eHasListenerCallbacksChanged = 0x20000000;
+        eInputInfoChanged = 0x40000000;
+        eCornerRadiusChanged = -2147483648; // 0x80000000; (proto stores enums as signed int)
+    };
+    enum ChangesMsb {
+        eChangesMsbNone = 0;
+        eDestinationFrameChanged = 0x1;
+        eCachedBufferChanged = 0x2;
+        eBackgroundColorChanged = 0x4;
+        eMetadataChanged = 0x8;
+        eColorSpaceAgnosticChanged = 0x10;
+        eFrameRateSelectionPriority = 0x20;
+        eFrameRateChanged = 0x40;
+        eBackgroundBlurRadiusChanged = 0x80;
+        eProducerDisconnect = 0x100;
+        eFixedTransformHintChanged = 0x200;
+        eFrameNumberChanged = 0x400;
+        eBlurRegionsChanged = 0x800;
+        eAutoRefreshChanged = 0x1000;
+        eStretchChanged = 0x2000;
+        eTrustedOverlayChanged = 0x4000;
+        eDropInputModeChanged = 0x8000;
+    };
+    uint64 what = 2;
+    float x = 3;
+    float y = 4;
+    int32 z = 5;
+    uint32 w = 6;
+    uint32 h = 7;
+    uint32 layer_stack = 8;
+
+    enum Flags {
+        eFlagsNone = 0;
+        eLayerHidden = 0x01;
+        eLayerOpaque = 0x02;
+        eLayerSkipScreenshot = 0x40;
+        eLayerSecure = 0x80;
+        eEnableBackpressure = 0x100;
+        eLayerIsDisplayDecoration = 0x200;
+    };
+    uint32 flags = 9;
+    uint32 mask = 10;
+
+    message Matrix22 {
+        float dsdx = 1;
+        float dtdx = 2;
+        float dtdy = 3;
+        float dsdy = 4;
+    };
+    Matrix22 matrix = 11;
+    float corner_radius = 12;
+    uint32 background_blur_radius = 13;
+    int64 parent_id = 14;
+    int64 relative_parent_id = 15;
+
+    float alpha = 16;
+    message Color3 {
+        float r = 1;
+        float g = 2;
+        float b = 3;
+    }
+    Color3 color = 17;
+    RegionProto transparent_region = 18;
+    uint32 transform = 19;
+    bool transform_to_display_inverse = 20;
+    RectProto crop = 21;
+
+    message BufferData {
+        uint64 buffer_id = 1;
+        uint32 width = 2;
+        uint32 height = 3;
+        uint64 frame_number = 4;
+
+        enum BufferDataChange {
+            BufferDataChangeNone = 0;
+            fenceChanged = 0x01;
+            frameNumberChanged = 0x02;
+            cachedBufferChanged = 0x04;
+        }
+        uint32 flags = 5;
+        uint64 cached_buffer_id = 6;
+
+        enum PixelFormat {
+            PIXEL_FORMAT_UNKNOWN = 0;
+            PIXEL_FORMAT_CUSTOM = -4;
+            PIXEL_FORMAT_TRANSLUCENT = -3;
+            PIXEL_FORMAT_TRANSPARENT = -2;
+            PIXEL_FORMAT_OPAQUE = -1;
+            PIXEL_FORMAT_RGBA_8888 = 1;
+            PIXEL_FORMAT_RGBX_8888 = 2;
+            PIXEL_FORMAT_RGB_888 = 3;
+            PIXEL_FORMAT_RGB_565 = 4;
+            PIXEL_FORMAT_BGRA_8888 = 5;
+            PIXEL_FORMAT_RGBA_5551 = 6;
+            PIXEL_FORMAT_RGBA_4444 = 7;
+            PIXEL_FORMAT_RGBA_FP16 = 22;
+            PIXEL_FORMAT_RGBA_1010102 = 43;
+            PIXEL_FORMAT_R_8 = 0x38;
+        }
+        PixelFormat pixel_format = 7;
+        uint64 usage = 8;
+    }
+    BufferData buffer_data = 22;
+    int32 api = 23;
+    bool has_sideband_stream = 24;
+    ColorTransformProto color_transform = 25;
+    repeated BlurRegion blur_regions = 26;
+
+    message Transform {
+        float dsdx = 1;
+        float dtdx = 2;
+        float dtdy = 3;
+        float dsdy = 4;
+        float tx = 5;
+        float ty = 6;
+    }
+    message WindowInfo {
+        uint32 layout_params_flags = 1;
+        int32 layout_params_type = 2;
+        RegionProto touchable_region = 3;
+        int32 surface_inset = 4;
+        bool focusable = 5;
+        bool has_wallpaper = 6;
+        float global_scale_factor = 7;
+        int64 crop_layer_id = 8;
+        bool replace_touchable_region_with_crop = 9;
+        RectProto touchable_region_crop = 10;
+        Transform transform = 11;
+    }
+    WindowInfo window_info_handle = 27;
+    float bg_color_alpha = 28;
+    int32 bg_color_dataspace = 29;
+    bool color_space_agnostic = 30;
+    float shadow_radius = 31;
+    int32 frame_rate_selection_priority = 32;
+    float frame_rate = 33;
+    int32 frame_rate_compatibility = 34;
+    int32 change_frame_rate_strategy = 35;
+    uint32 fixed_transform_hint = 36;
+    uint64 frame_number = 37;
+    bool auto_refresh = 38;
+    bool is_trusted_overlay = 39;
+    RectProto buffer_crop = 40;
+    RectProto destination_frame = 41;
+
+    enum DropInputMode {
+        NONE = 0;
+        ALL = 1;
+        OBSCURED = 2;
+    };
+    DropInputMode drop_input_mode = 42;
+}
+
+message DisplayState {
+    enum Changes {
+        eChangesNone = 0;
+        eSurfaceChanged = 0x01;
+        eLayerStackChanged = 0x02;
+        eDisplayProjectionChanged = 0x04;
+        eDisplaySizeChanged = 0x08;
+        eFlagsChanged = 0x10;
+    };
+    int32 id = 1;
+    uint32 what = 2;
+    uint32 flags = 3;
+    uint32 layer_stack = 4;
+    uint32 orientation = 5;
+    RectProto layer_stack_space_rect = 6;
+    RectProto oriented_display_space_rect = 7;
+    uint32 width = 8;
+    uint32 height = 9;
+}
diff --git a/services/surfaceflinger/main_surfaceflinger.cpp b/services/surfaceflinger/main_surfaceflinger.cpp
index caeff4a..ec18054 100644
--- a/services/surfaceflinger/main_surfaceflinger.cpp
+++ b/services/surfaceflinger/main_surfaceflinger.cpp
@@ -152,6 +152,11 @@
     sm->addService(String16(SurfaceFlinger::getServiceName()), flinger, false,
                    IServiceManager::DUMP_FLAG_PRIORITY_CRITICAL | IServiceManager::DUMP_FLAG_PROTO);
 
+    // publish gui::ISurfaceComposer, the new AIDL interface
+    sp<SurfaceComposerAIDL> composerAIDL = new SurfaceComposerAIDL(flinger);
+    sm->addService(String16("SurfaceFlingerAIDL"), composerAIDL, false,
+                   IServiceManager::DUMP_FLAG_PRIORITY_CRITICAL | IServiceManager::DUMP_FLAG_PROTO);
+
     startDisplayService(); // dependency on SF getting registered above
 
     if (SurfaceFlinger::setSchedFifo(true) != NO_ERROR) {
diff --git a/services/surfaceflinger/sysprop/SurfaceFlingerProperties.sysprop b/services/surfaceflinger/sysprop/SurfaceFlingerProperties.sysprop
index 78f8a2f..bcbe21a 100644
--- a/services/surfaceflinger/sysprop/SurfaceFlingerProperties.sysprop
+++ b/services/surfaceflinger/sysprop/SurfaceFlingerProperties.sysprop
@@ -414,16 +414,6 @@
     prop_name: "ro.surface_flinger.supports_background_blur"
 }
 
-# Indicates whether Scheduler should use frame rate API when adjusting the
-# display refresh rate.
-prop {
-    api_name: "use_frame_rate_api"
-    type: Boolean
-    scope: Public
-    access: Readonly
-    prop_name: "ro.surface_flinger.use_frame_rate_api"
-}
-
 # Sets the timeout used to rate limit DISPLAY_UPDATE_IMMINENT Power HAL notifications.
 # SurfaceFlinger wakeups will trigger this boost whenever they are separated by more than this
 # duration (specified in milliseconds). A value of 0 disables the rate limit, and will result in
@@ -471,4 +461,13 @@
     scope: Public
     access: Readonly
     prop_name: "ro.surface_flinger.enable_sdr_dimming"
+}
+
+# Ignores Camera layers when calculating HDR coverage information
+prop {
+    api_name: "ignore_hdr_camera_layers"
+    type: Boolean
+    scope: Public
+    access: Readonly
+    prop_name: "ro.surface_flinger.ignore_hdr_camera_layers"
 }
\ No newline at end of file
diff --git a/services/surfaceflinger/sysprop/api/SurfaceFlingerProperties-current.txt b/services/surfaceflinger/sysprop/api/SurfaceFlingerProperties-current.txt
index 9c567d6..348a462 100644
--- a/services/surfaceflinger/sysprop/api/SurfaceFlingerProperties-current.txt
+++ b/services/surfaceflinger/sysprop/api/SurfaceFlingerProperties-current.txt
@@ -69,6 +69,10 @@
     prop_name: "ro.surface_flinger.has_wide_color_display"
   }
   prop {
+    api_name: "ignore_hdr_camera_layers"
+    prop_name: "ro.surface_flinger.ignore_hdr_camera_layers"
+  }
+  prop {
     api_name: "max_frame_buffer_acquired_buffers"
     type: Long
     prop_name: "ro.surface_flinger.max_frame_buffer_acquired_buffers"
@@ -152,10 +156,6 @@
     prop_name: "ro.surface_flinger.use_context_priority"
   }
   prop {
-    api_name: "use_frame_rate_api"
-    prop_name: "ro.surface_flinger.use_frame_rate_api"
-  }
-  prop {
     api_name: "use_smart_90_for_video"
     prop_name: "ro.surface_flinger.use_smart_90_for_video"
     deprecated: true
diff --git a/services/surfaceflinger/sysprop/api/SurfaceFlingerProperties-latest.txt b/services/surfaceflinger/sysprop/api/SurfaceFlingerProperties-latest.txt
index ba60a7d..640b9fb 100644
--- a/services/surfaceflinger/sysprop/api/SurfaceFlingerProperties-latest.txt
+++ b/services/surfaceflinger/sysprop/api/SurfaceFlingerProperties-latest.txt
@@ -136,10 +136,6 @@
     prop_name: "ro.surface_flinger.use_context_priority"
   }
   prop {
-    api_name: "use_frame_rate_api"
-    prop_name: "ro.surface_flinger.use_frame_rate_api"
-  }
-  prop {
     api_name: "use_smart_90_for_video"
     prop_name: "ro.surface_flinger.use_smart_90_for_video"
     deprecated: true
diff --git a/services/surfaceflinger/tests/Android.bp b/services/surfaceflinger/tests/Android.bp
index 32ad873..ceddf27 100644
--- a/services/surfaceflinger/tests/Android.bp
+++ b/services/surfaceflinger/tests/Android.bp
@@ -26,11 +26,13 @@
     defaults: ["surfaceflinger_defaults"],
     test_suites: ["device-tests"],
     srcs: [
+        "BootDisplayMode_test.cpp",
         "BufferGenerator.cpp",
         "CommonTypes_test.cpp",
         "Credentials_test.cpp",
         "DereferenceSurfaceControl_test.cpp",
         "DisplayConfigs_test.cpp",
+        "DisplayEventReceiver_test.cpp",
         "EffectLayer_test.cpp",
         "InvalidHandles_test.cpp",
         "LayerCallback_test.cpp",
@@ -47,6 +49,7 @@
         "ReleaseBufferCallback_test.cpp",
         "ScreenCapture_test.cpp",
         "SetFrameRate_test.cpp",
+        "SetFrameRateOverride_test.cpp",
         "SetGeometry_test.cpp",
         "Stress_test.cpp",
         "SurfaceInterceptor_test.cpp",
@@ -60,7 +63,7 @@
         "android.hardware.graphics.composer@2.1",
     ],
     shared_libs: [
-        "android.hardware.graphics.common-V2-ndk",
+        "android.hardware.graphics.common-V3-ndk",
         "android.hardware.graphics.common@1.2",
         "libandroid",
         "libbase",
diff --git a/services/surfaceflinger/tests/BootDisplayMode_test.cpp b/services/surfaceflinger/tests/BootDisplayMode_test.cpp
new file mode 100644
index 0000000..abdb16d
--- /dev/null
+++ b/services/surfaceflinger/tests/BootDisplayMode_test.cpp
@@ -0,0 +1,47 @@
+/*
+ * 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.
+ */
+
+#include <thread>
+
+#include <gtest/gtest.h>
+
+#include <gui/SurfaceComposerClient.h>
+#include <private/gui/ComposerService.h>
+#include <chrono>
+
+namespace android {
+
+TEST(BootDisplayModeTest, setBootDisplayMode) {
+    sp<ISurfaceComposer> sf(ComposerService::getComposerService());
+    auto displayToken = SurfaceComposerClient::getInternalDisplayToken();
+    bool bootModeSupport = false;
+    ASSERT_NO_FATAL_FAILURE(sf->getBootDisplayModeSupport(&bootModeSupport));
+    if (bootModeSupport) {
+        ASSERT_EQ(NO_ERROR, sf->setBootDisplayMode(displayToken, 0));
+    }
+}
+
+TEST(BootDisplayModeTest, clearBootDisplayMode) {
+    sp<ISurfaceComposer> sf(ComposerService::getComposerService());
+    auto displayToken = SurfaceComposerClient::getInternalDisplayToken();
+    bool bootModeSupport = false;
+    ASSERT_NO_FATAL_FAILURE(sf->getBootDisplayModeSupport(&bootModeSupport));
+    if (bootModeSupport) {
+        ASSERT_EQ(NO_ERROR, sf->clearBootDisplayMode(displayToken));
+    }
+}
+
+} // namespace android
\ No newline at end of file
diff --git a/services/surfaceflinger/tests/Credentials_test.cpp b/services/surfaceflinger/tests/Credentials_test.cpp
index fa3f0e7..d33bc10 100644
--- a/services/surfaceflinger/tests/Credentials_test.cpp
+++ b/services/surfaceflinger/tests/Credentials_test.cpp
@@ -93,7 +93,7 @@
         ASSERT_TRUE(mBGSurfaceControl->isValid());
 
         Transaction t;
-        t.setDisplayLayerStack(mDisplay, 0);
+        t.setDisplayLayerStack(mDisplay, ui::DEFAULT_LAYER_STACK);
         ASSERT_EQ(NO_ERROR,
                   t.setLayer(mBGSurfaceControl, INT_MAX - 3).show(mBGSurfaceControl).apply());
     }
diff --git a/services/surfaceflinger/tests/DisplayEventReceiver_test.cpp b/services/surfaceflinger/tests/DisplayEventReceiver_test.cpp
new file mode 100644
index 0000000..0e54664
--- /dev/null
+++ b/services/surfaceflinger/tests/DisplayEventReceiver_test.cpp
@@ -0,0 +1,54 @@
+/*
+ * 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 <gtest/gtest.h>
+#include <gui/DisplayEventReceiver.h>
+
+namespace android {
+
+class DisplayEventReceiverTest : public ::testing::Test {
+public:
+    void SetUp() override { EXPECT_EQ(NO_ERROR, mDisplayEventReceiver.initCheck()); }
+
+    DisplayEventReceiver mDisplayEventReceiver;
+};
+
+TEST_F(DisplayEventReceiverTest, getLatestVsyncEventData) {
+    const nsecs_t now = systemTime();
+    ParcelableVsyncEventData parcelableVsyncEventData;
+    EXPECT_EQ(NO_ERROR, mDisplayEventReceiver.getLatestVsyncEventData(&parcelableVsyncEventData));
+
+    const VsyncEventData& vsyncEventData = parcelableVsyncEventData.vsync;
+    EXPECT_NE(std::numeric_limits<size_t>::max(), vsyncEventData.preferredFrameTimelineIndex);
+    EXPECT_GT(vsyncEventData.frameTimelines[0].deadlineTimestamp, now)
+            << "Deadline timestamp should be greater than frame time";
+    for (size_t i = 0; i < VsyncEventData::kFrameTimelinesLength; i++) {
+        EXPECT_NE(FrameTimelineInfo::INVALID_VSYNC_ID, vsyncEventData.frameTimelines[i].vsyncId);
+        EXPECT_GT(vsyncEventData.frameTimelines[i].expectedPresentationTime,
+                  vsyncEventData.frameTimelines[i].deadlineTimestamp)
+                << "Expected vsync timestamp should be greater than deadline";
+        if (i > 0) {
+            EXPECT_GT(vsyncEventData.frameTimelines[i].deadlineTimestamp,
+                      vsyncEventData.frameTimelines[i - 1].deadlineTimestamp)
+                    << "Deadline timestamp out of order for frame timeline " << i;
+            EXPECT_GT(vsyncEventData.frameTimelines[i].expectedPresentationTime,
+                      vsyncEventData.frameTimelines[i - 1].expectedPresentationTime)
+                    << "Expected vsync timestamp out of order for frame timeline " << i;
+        }
+    }
+}
+
+} // namespace android
\ No newline at end of file
diff --git a/services/surfaceflinger/tests/EffectLayer_test.cpp b/services/surfaceflinger/tests/EffectLayer_test.cpp
index 93656f3..9fa0452 100644
--- a/services/surfaceflinger/tests/EffectLayer_test.cpp
+++ b/services/surfaceflinger/tests/EffectLayer_test.cpp
@@ -33,7 +33,7 @@
 
         mParentLayer = createColorLayer("Parent layer", Color::RED);
         asTransaction([&](Transaction& t) {
-            t.setDisplayLayerStack(display, 0);
+            t.setDisplayLayerStack(display, ui::DEFAULT_LAYER_STACK);
             t.setLayer(mParentLayer, INT32_MAX - 2).show(mParentLayer);
             t.setFlags(mParentLayer, layer_state_t::eLayerOpaque, layer_state_t::eLayerOpaque);
         });
diff --git a/services/surfaceflinger/tests/IPC_test.cpp b/services/surfaceflinger/tests/IPC_test.cpp
index 84fea6c..ce94dab 100644
--- a/services/surfaceflinger/tests/IPC_test.cpp
+++ b/services/surfaceflinger/tests/IPC_test.cpp
@@ -159,10 +159,9 @@
                                                  {0, 0, static_cast<int32_t>(width),
                                                   static_cast<int32_t>(height)},
                                                  Color::RED);
-        transaction->setLayerStack(mSurfaceControl, 0)
+        transaction->setLayerStack(mSurfaceControl, ui::DEFAULT_LAYER_STACK)
                 .setLayer(mSurfaceControl, std::numeric_limits<int32_t>::max())
-                .setBuffer(mSurfaceControl, gb)
-                .setAcquireFence(mSurfaceControl, fence)
+                .setBuffer(mSurfaceControl, gb, fence)
                 .show(mSurfaceControl)
                 .addTransactionCompletedCallback(mCallbackHelper.function,
                                                  mCallbackHelper.getContext());
@@ -232,7 +231,7 @@
         mDisplayHeight = mode.resolution.getHeight();
 
         Transaction setupTransaction;
-        setupTransaction.setDisplayLayerStack(mPrimaryDisplay, 0);
+        setupTransaction.setDisplayLayerStack(mPrimaryDisplay, ui::DEFAULT_LAYER_STACK);
         setupTransaction.apply();
     }
 
@@ -310,10 +309,9 @@
                                              Color::RED);
 
     Transaction transaction;
-    transaction.setLayerStack(sc, 0)
+    transaction.setLayerStack(sc, ui::DEFAULT_LAYER_STACK)
             .setLayer(sc, std::numeric_limits<int32_t>::max() - 1)
-            .setBuffer(sc, gb)
-            .setAcquireFence(sc, fence)
+            .setBuffer(sc, gb, fence)
             .show(sc)
             .addTransactionCompletedCallback(helper1.function, helper1.getContext());
 
diff --git a/services/surfaceflinger/tests/LayerCallback_test.cpp b/services/surfaceflinger/tests/LayerCallback_test.cpp
index 7ff041e..8a2305b 100644
--- a/services/surfaceflinger/tests/LayerCallback_test.cpp
+++ b/services/surfaceflinger/tests/LayerCallback_test.cpp
@@ -26,6 +26,7 @@
 namespace android {
 
 using android::hardware::graphics::common::V1_1::BufferUsage;
+using SCHash = SurfaceComposerClient::SCHash;
 
 ::testing::Environment* const binderEnv =
         ::testing::AddGlobalTestEnvironment(new BinderEnvironment());
@@ -66,8 +67,7 @@
                     return err;
                 }
 
-                transaction.setBuffer(layer, buffer);
-                transaction.setAcquireFence(layer, fence);
+                transaction.setBuffer(layer, buffer, fence);
             }
 
             if (setBackgroundColor) {
@@ -103,6 +103,24 @@
         }
     }
 
+    static void waitForCommitCallback(
+            CallbackHelper& helper,
+            const std::unordered_set<sp<SurfaceControl>, SCHash>& committedSc) {
+        CallbackData callbackData;
+        ASSERT_NO_FATAL_FAILURE(helper.getCallbackData(&callbackData));
+
+        const auto& surfaceControlStats = callbackData.surfaceControlStats;
+
+        ASSERT_EQ(surfaceControlStats.size(), committedSc.size()) << "wrong number of surfaces";
+
+        for (const auto& stats : surfaceControlStats) {
+            ASSERT_NE(stats.surfaceControl, nullptr) << "returned null surface control";
+
+            const auto& expectedSc = committedSc.find(stats.surfaceControl);
+            ASSERT_NE(expectedSc, committedSc.end()) << "unexpected surface control";
+        }
+    }
+
     DisplayEventReceiver mDisplayEventReceiver;
     int mEpollFd;
 
@@ -123,11 +141,12 @@
                 continue;
             }
 
-            vsync = {event.vsync.vsyncId, event.vsync.expectedVSyncTimestamp};
+            vsync = {event.vsync.vsyncData.preferredVsyncId(),
+                     event.vsync.vsyncData.preferredExpectedPresentationTime()};
         }
 
         EXPECT_GE(vsync.vsyncId, 1);
-        EXPECT_GT(event.vsync.expectedVSyncTimestamp, systemTime());
+        EXPECT_GT(vsync.expectedPresentTime, systemTime());
 
         return vsync;
     }
@@ -1030,4 +1049,85 @@
     EXPECT_NO_FATAL_FAILURE(waitForCallback(callback, expected, true));
 }
 
+// b202394221
+TEST_F(LayerCallbackTest, EmptyBufferStateChanges) {
+    sp<SurfaceControl> bufferLayer, emptyBufferLayer;
+    ASSERT_NO_FATAL_FAILURE(bufferLayer = createBufferStateLayer());
+    ASSERT_NO_FATAL_FAILURE(emptyBufferLayer = createBufferStateLayer());
+
+    Transaction transaction;
+    CallbackHelper callback;
+    for (size_t i = 0; i < 10; i++) {
+        int err = fillTransaction(transaction, &callback, bufferLayer);
+        if (err) {
+            GTEST_SUCCEED() << "test not supported";
+            return;
+        }
+
+        ui::Size bufferSize = getBufferSize();
+
+        TransactionUtils::setFrame(transaction, bufferLayer,
+                                   Rect(0, 0, bufferSize.width, bufferSize.height),
+                                   Rect(0, 0, 32, 32));
+        transaction.setPosition(emptyBufferLayer, 1 + i, 2 + i);
+        transaction.apply();
+
+        ExpectedResult expected;
+        expected.addSurface(ExpectedResult::Transaction::PRESENTED, bufferLayer,
+                            ExpectedResult::Buffer::ACQUIRED,
+                            (i == 0) ? ExpectedResult::PreviousBuffer::NOT_RELEASED
+                                     : ExpectedResult::PreviousBuffer::RELEASED);
+        expected.addSurface(ExpectedResult::Transaction::PRESENTED, emptyBufferLayer,
+                            ExpectedResult::Buffer::NOT_ACQUIRED,
+                            ExpectedResult::PreviousBuffer::NOT_RELEASED);
+
+        EXPECT_NO_FATAL_FAILURE(waitForCallback(callback, expected));
+    }
+    ASSERT_NO_FATAL_FAILURE(callback.verifyFinalState());
+}
+
+// b202394221
+TEST_F(LayerCallbackTest, DISABLED_NonBufferLayerStateChanges) {
+    sp<SurfaceControl> layer;
+    ASSERT_NO_FATAL_FAILURE(layer = createColorLayer("ColorLayer", Color::RED));
+
+    Transaction transaction;
+    CallbackHelper callback;
+    int err = fillTransaction(transaction, &callback);
+    if (err) {
+        GTEST_SUCCEED() << "test not supported";
+        return;
+    }
+    transaction.setPosition(layer, 1, 2);
+    transaction.apply();
+
+    ExpectedResult expected;
+    EXPECT_NO_FATAL_FAILURE(waitForCallback(callback, expected, true));
+}
+
+TEST_F(LayerCallbackTest, CommitCallbackOffscreenLayer) {
+    sp<SurfaceControl> layer;
+    ASSERT_NO_FATAL_FAILURE(layer = createBufferStateLayer());
+    sp<SurfaceControl> offscreenLayer =
+            createSurface(mClient, "Offscreen Layer", 0, 0, PIXEL_FORMAT_RGBA_8888,
+                          ISurfaceComposerClient::eFXSurfaceBufferState, layer.get());
+
+    Transaction transaction;
+    CallbackHelper callback;
+    int err = fillTransaction(transaction, &callback, layer, true);
+    err |= fillTransaction(transaction, &callback, offscreenLayer, true);
+    if (err) {
+        GTEST_SUCCEED() << "test not supported";
+        return;
+    }
+
+    transaction.reparent(offscreenLayer, nullptr)
+            .addTransactionCommittedCallback(callback.function, callback.getContext());
+    transaction.apply();
+
+    std::unordered_set<sp<SurfaceControl>, SCHash> committedSc;
+    committedSc.insert(layer);
+    committedSc.insert(offscreenLayer);
+    EXPECT_NO_FATAL_FAILURE(waitForCommitCallback(callback, committedSc));
+}
 } // namespace android
diff --git a/services/surfaceflinger/tests/LayerRenderTypeTransaction_test.cpp b/services/surfaceflinger/tests/LayerRenderTypeTransaction_test.cpp
index c8eeac6..0e2bc3d 100644
--- a/services/surfaceflinger/tests/LayerRenderTypeTransaction_test.cpp
+++ b/services/surfaceflinger/tests/LayerRenderTypeTransaction_test.cpp
@@ -1353,7 +1353,7 @@
         return;
     }
 
-    Transaction().setBuffer(layer, buffer).setAcquireFence(layer, fence).apply();
+    Transaction().setBuffer(layer, buffer, fence).apply();
 
     status_t status = fence->wait(1000);
     ASSERT_NE(static_cast<status_t>(Fence::Status::Unsignaled), status);
@@ -1375,7 +1375,7 @@
 
     sp<Fence> fence = Fence::NO_FENCE;
 
-    Transaction().setBuffer(layer, buffer).setAcquireFence(layer, fence).apply();
+    Transaction().setBuffer(layer, buffer, fence).apply();
 
     auto shot = getScreenCapture();
     shot->expectColor(Rect(0, 0, 32, 32), Color::RED);
diff --git a/services/surfaceflinger/tests/LayerState_test.cpp b/services/surfaceflinger/tests/LayerState_test.cpp
index fa1a5ed..094b0ff 100644
--- a/services/surfaceflinger/tests/LayerState_test.cpp
+++ b/services/surfaceflinger/tests/LayerState_test.cpp
@@ -22,6 +22,8 @@
 #include <gui/LayerState.h>
 
 namespace android {
+using gui::DisplayCaptureArgs;
+using gui::LayerCaptureArgs;
 using gui::ScreenCaptureResults;
 
 namespace test {
@@ -40,11 +42,11 @@
     args.grayscale = true;
 
     Parcel p;
-    args.write(p);
+    args.writeToParcel(&p);
     p.setDataPosition(0);
 
     DisplayCaptureArgs args2;
-    args2.read(p);
+    args2.readFromParcel(&p);
 
     ASSERT_EQ(args.pixelFormat, args2.pixelFormat);
     ASSERT_EQ(args.sourceCrop, args2.sourceCrop);
@@ -71,11 +73,11 @@
     args.grayscale = true;
 
     Parcel p;
-    args.write(p);
+    args.writeToParcel(&p);
     p.setDataPosition(0);
 
     LayerCaptureArgs args2;
-    args2.read(p);
+    args2.readFromParcel(&p);
 
     ASSERT_EQ(args.pixelFormat, args2.pixelFormat);
     ASSERT_EQ(args.sourceCrop, args2.sourceCrop);
diff --git a/services/surfaceflinger/tests/LayerTransactionTest.h b/services/surfaceflinger/tests/LayerTransactionTest.h
index 0bc8fe7..6bd7920 100644
--- a/services/surfaceflinger/tests/LayerTransactionTest.h
+++ b/services/surfaceflinger/tests/LayerTransactionTest.h
@@ -266,7 +266,7 @@
     sp<IBinder> mDisplay;
     uint32_t mDisplayWidth;
     uint32_t mDisplayHeight;
-    uint32_t mDisplayLayerStack;
+    ui::LayerStack mDisplayLayerStack = ui::DEFAULT_LAYER_STACK;
     Rect mDisplayRect = Rect::INVALID_RECT;
 
     // leave room for ~256 layers
@@ -294,8 +294,6 @@
         // vsyncs.
         mBufferPostDelay = static_cast<int32_t>(1e6 / mode.refreshRate) * 3;
 
-        mDisplayLayerStack = 0;
-
         mBlackBgSurface =
                 createSurface(mClient, "BaseSurface", 0 /* buffer width */, 0 /* buffer height */,
                               PIXEL_FORMAT_RGBA_8888, ISurfaceComposerClient::eFXSurfaceEffect);
diff --git a/services/surfaceflinger/tests/LayerTypeAndRenderTypeTransaction_test.cpp b/services/surfaceflinger/tests/LayerTypeAndRenderTypeTransaction_test.cpp
index 25f3bb9..9cb617a 100644
--- a/services/surfaceflinger/tests/LayerTypeAndRenderTypeTransaction_test.cpp
+++ b/services/surfaceflinger/tests/LayerTypeAndRenderTypeTransaction_test.cpp
@@ -763,7 +763,8 @@
     ASSERT_NO_FATAL_FAILURE(layer = createLayer("test", 32, 32));
     ASSERT_NO_FATAL_FAILURE(fillLayerColor(layer, Color::RED, 32, 32));
 
-    Transaction().setLayerStack(layer, mDisplayLayerStack + 1).apply();
+    const auto layerStack = ui::LayerStack::fromValue(mDisplayLayerStack.id + 1);
+    Transaction().setLayerStack(layer, layerStack).apply();
     {
         SCOPED_TRACE("non-existing layer stack");
         getScreenCapture()->expectColor(mDisplayRect, Color::BLACK);
diff --git a/services/surfaceflinger/tests/LayerUpdate_test.cpp b/services/surfaceflinger/tests/LayerUpdate_test.cpp
index ee4d367..e1a7ecc 100644
--- a/services/surfaceflinger/tests/LayerUpdate_test.cpp
+++ b/services/surfaceflinger/tests/LayerUpdate_test.cpp
@@ -63,7 +63,7 @@
         TransactionUtils::fillSurfaceRGBA8(mSyncSurfaceControl, 31, 31, 31);
 
         asTransaction([&](Transaction& t) {
-            t.setDisplayLayerStack(display, 0);
+            t.setDisplayLayerStack(display, ui::DEFAULT_LAYER_STACK);
 
             t.setLayer(mBGSurfaceControl, INT32_MAX - 2).show(mBGSurfaceControl);
 
diff --git a/services/surfaceflinger/tests/MirrorLayer_test.cpp b/services/surfaceflinger/tests/MirrorLayer_test.cpp
index b7a9271..a921aa8 100644
--- a/services/surfaceflinger/tests/MirrorLayer_test.cpp
+++ b/services/surfaceflinger/tests/MirrorLayer_test.cpp
@@ -36,7 +36,7 @@
         mParentLayer = createColorLayer("Parent layer", Color::RED);
         mChildLayer = createColorLayer("Child layer", Color::GREEN, mParentLayer.get());
         asTransaction([&](Transaction& t) {
-            t.setDisplayLayerStack(display, 0);
+            t.setDisplayLayerStack(display, ui::DEFAULT_LAYER_STACK);
             t.setLayer(mParentLayer, INT32_MAX - 2).show(mParentLayer);
             t.setCrop(mChildLayer, Rect(0, 0, 400, 400)).show(mChildLayer);
             t.setPosition(mChildLayer, 50, 50);
diff --git a/services/surfaceflinger/tests/MultiDisplayLayerBounds_test.cpp b/services/surfaceflinger/tests/MultiDisplayLayerBounds_test.cpp
index 08de01c..1ed6c65 100644
--- a/services/surfaceflinger/tests/MultiDisplayLayerBounds_test.cpp
+++ b/services/surfaceflinger/tests/MultiDisplayLayerBounds_test.cpp
@@ -52,7 +52,7 @@
         mColorLayer = 0;
     }
 
-    void createDisplay(const ui::Size& layerStackSize, uint32_t layerStack) {
+    void createDisplay(const ui::Size& layerStackSize, ui::LayerStack layerStack) {
         mVirtualDisplay =
                 SurfaceComposerClient::createDisplay(String8("VirtualDisplay"), false /*secure*/);
         asTransaction([&](Transaction& t) {
@@ -63,7 +63,7 @@
         });
     }
 
-    void createColorLayer(uint32_t layerStack) {
+    void createColorLayer(ui::LayerStack layerStack) {
         mColorLayer =
                 createSurface(mClient, "ColorLayer", 0 /* buffer width */, 0 /* buffer height */,
                               PIXEL_FORMAT_RGBA_8888, ISurfaceComposerClient::eFXSurfaceEffect);
@@ -90,8 +90,9 @@
 };
 
 TEST_F(MultiDisplayLayerBoundsTest, RenderLayerInVirtualDisplay) {
-    createDisplay(mMainDisplayState.layerStackSpaceRect, 1 /* layerStack */);
-    createColorLayer(1 /* layerStack */);
+    constexpr ui::LayerStack kLayerStack{1u};
+    createDisplay(mMainDisplayState.layerStackSpaceRect, kLayerStack);
+    createColorLayer(kLayerStack);
 
     asTransaction([&](Transaction& t) { t.setPosition(mColorLayer, 10, 10); });
 
@@ -113,8 +114,8 @@
 
     // Assumption here is that the new mirrored display has the same layer stack rect as the
     // primary display that it is mirroring.
-    createDisplay(mMainDisplayState.layerStackSpaceRect, 0 /* layerStack */);
-    createColorLayer(0 /* layerStack */);
+    createDisplay(mMainDisplayState.layerStackSpaceRect, ui::DEFAULT_LAYER_STACK);
+    createColorLayer(ui::DEFAULT_LAYER_STACK);
 
     asTransaction([&](Transaction& t) { t.setPosition(mColorLayer, 10, 10); });
 
diff --git a/services/surfaceflinger/tests/RelativeZ_test.cpp b/services/surfaceflinger/tests/RelativeZ_test.cpp
index fde6e6e..50a4092 100644
--- a/services/surfaceflinger/tests/RelativeZ_test.cpp
+++ b/services/surfaceflinger/tests/RelativeZ_test.cpp
@@ -43,7 +43,7 @@
         mForegroundLayer = createColorLayer("Foreground layer", Color::GREEN);
 
         asTransaction([&](Transaction& t) {
-            t.setDisplayLayerStack(display, 0);
+            t.setDisplayLayerStack(display, ui::DEFAULT_LAYER_STACK);
             t.setLayer(mBackgroundLayer, INT32_MAX - 2).show(mBackgroundLayer);
             t.setLayer(mForegroundLayer, INT32_MAX - 1).show(mForegroundLayer);
         });
diff --git a/services/surfaceflinger/tests/ReleaseBufferCallback_test.cpp b/services/surfaceflinger/tests/ReleaseBufferCallback_test.cpp
index 309ab2f..027a15e 100644
--- a/services/surfaceflinger/tests/ReleaseBufferCallback_test.cpp
+++ b/services/surfaceflinger/tests/ReleaseBufferCallback_test.cpp
@@ -31,7 +31,7 @@
 public:
     static void function(void* callbackContext, ReleaseCallbackId callbackId,
                          const sp<Fence>& releaseFence,
-                         uint32_t /*currentMaxAcquiredBufferCount*/) {
+                         std::optional<uint32_t> /*currentMaxAcquiredBufferCount*/) {
         if (!callbackContext) {
             FAIL() << "failed to get callback context";
         }
@@ -85,9 +85,7 @@
                              sp<Fence> fence, CallbackHelper& callback, const ReleaseCallbackId& id,
                              ReleaseBufferCallbackHelper& releaseCallback) {
         Transaction t;
-        t.setFrameNumber(layer, id.framenumber);
-        t.setBuffer(layer, buffer, id, releaseCallback.getCallback());
-        t.setAcquireFence(layer, fence);
+        t.setBuffer(layer, buffer, fence, id.framenumber, releaseCallback.getCallback());
         t.addTransactionCompletedCallback(callback.function, callback.getContext());
         t.apply();
     }
@@ -302,8 +300,8 @@
     nsecs_t time = systemTime() + std::chrono::nanoseconds(100ms).count();
 
     Transaction t;
-    t.setBuffer(layer, firstBuffer, firstBufferCallbackId, releaseCallback->getCallback());
-    t.setAcquireFence(layer, Fence::NO_FENCE);
+    t.setBuffer(layer, firstBuffer, std::nullopt, firstBufferCallbackId.framenumber,
+                releaseCallback->getCallback());
     t.addTransactionCompletedCallback(transactionCallback.function,
                                       transactionCallback.getContext());
     t.setDesiredPresentTime(time);
@@ -318,8 +316,8 @@
     // Dropping frames in transaction queue emits a callback
     sp<GraphicBuffer> secondBuffer = getBuffer();
     ReleaseCallbackId secondBufferCallbackId(secondBuffer->getId(), generateFrameNumber());
-    t.setBuffer(layer, secondBuffer, secondBufferCallbackId, releaseCallback->getCallback());
-    t.setAcquireFence(layer, Fence::NO_FENCE);
+    t.setBuffer(layer, secondBuffer, std::nullopt, secondBufferCallbackId.framenumber,
+                releaseCallback->getCallback());
     t.addTransactionCompletedCallback(transactionCallback.function,
                                       transactionCallback.getContext());
     t.setDesiredPresentTime(time);
@@ -361,10 +359,8 @@
     ReleaseCallbackId secondBufferCallbackId(secondBuffer->getId(), generateFrameNumber());
 
     Transaction transaction1;
-    transaction1.setFrameNumber(layer, secondBufferCallbackId.framenumber);
-    transaction1.setBuffer(layer, secondBuffer, secondBufferCallbackId,
+    transaction1.setBuffer(layer, secondBuffer, std::nullopt, secondBufferCallbackId.framenumber,
                            releaseCallback->getCallback());
-    transaction1.setAcquireFence(layer, Fence::NO_FENCE);
     transaction1.addTransactionCompletedCallback(callback1.function, callback1.getContext());
 
     // Set a different TransactionCompletedListener to mimic a second process
@@ -389,4 +385,84 @@
     ASSERT_NO_FATAL_FAILURE(waitForReleaseBufferCallback(*releaseCallback, firstBufferCallbackId));
 }
 
+TEST_F(ReleaseBufferCallbackTest, DISABLED_SetBuffer_OverwriteBuffers) {
+    sp<SurfaceControl> layer = createBufferStateLayer();
+    ReleaseBufferCallbackHelper* releaseCallback = getReleaseBufferCallbackHelper();
+
+    sp<GraphicBuffer> firstBuffer = getBuffer();
+    ReleaseCallbackId firstBufferCallbackId(firstBuffer->getId(), generateFrameNumber());
+
+    // Create transaction with a buffer.
+    Transaction transaction;
+    transaction.setBuffer(layer, firstBuffer, std::nullopt, firstBufferCallbackId.framenumber,
+                          releaseCallback->getCallback());
+
+    sp<GraphicBuffer> secondBuffer = getBuffer();
+    ReleaseCallbackId secondBufferCallbackId(secondBuffer->getId(), generateFrameNumber());
+
+    // Call setBuffer on the same transaction with a different buffer.
+    transaction.setBuffer(layer, secondBuffer, std::nullopt, secondBufferCallbackId.framenumber,
+                          releaseCallback->getCallback());
+
+    ASSERT_NO_FATAL_FAILURE(waitForReleaseBufferCallback(*releaseCallback, firstBufferCallbackId));
+}
+
+TEST_F(ReleaseBufferCallbackTest, DISABLED_Merge_Transactions_OverwriteBuffers) {
+    sp<SurfaceControl> layer = createBufferStateLayer();
+    ReleaseBufferCallbackHelper* releaseCallback = getReleaseBufferCallbackHelper();
+
+    sp<GraphicBuffer> firstBuffer = getBuffer();
+    ReleaseCallbackId firstBufferCallbackId(firstBuffer->getId(), generateFrameNumber());
+
+    // Create transaction with a buffer.
+    Transaction transaction1;
+    transaction1.setBuffer(layer, firstBuffer, std::nullopt, firstBufferCallbackId.framenumber,
+                           releaseCallback->getCallback());
+
+    sp<GraphicBuffer> secondBuffer = getBuffer();
+    ReleaseCallbackId secondBufferCallbackId(secondBuffer->getId(), generateFrameNumber());
+
+    // Create a second transaction with a new buffer for the same layer.
+    Transaction transaction2;
+    transaction2.setBuffer(layer, secondBuffer, std::nullopt, secondBufferCallbackId.framenumber,
+                           releaseCallback->getCallback());
+
+    // merge transaction1 into transaction2 so ensure we get a proper buffer release callback.
+    transaction1.merge(std::move(transaction2));
+    ASSERT_NO_FATAL_FAILURE(waitForReleaseBufferCallback(*releaseCallback, firstBufferCallbackId));
+}
+
+TEST_F(ReleaseBufferCallbackTest, DISABLED_MergeBuffers_Different_Processes) {
+    sp<TransactionCompletedListener> firstCompletedListener = new TransactionCompletedListener();
+    sp<TransactionCompletedListener> secondCompletedListener = new TransactionCompletedListener();
+
+    TransactionCompletedListener::setInstance(firstCompletedListener);
+
+    sp<SurfaceControl> layer = createBufferStateLayer();
+    ReleaseBufferCallbackHelper* releaseCallback = getReleaseBufferCallbackHelper();
+
+    sp<GraphicBuffer> firstBuffer = getBuffer();
+    ReleaseCallbackId firstBufferCallbackId(firstBuffer->getId(), generateFrameNumber());
+
+    Transaction transaction1;
+    transaction1.setBuffer(layer, firstBuffer, std::nullopt, firstBufferCallbackId.framenumber,
+                           releaseCallback->getCallback());
+
+    // Sent a second buffer to allow the first buffer to get released.
+    sp<GraphicBuffer> secondBuffer = getBuffer();
+    ReleaseCallbackId secondBufferCallbackId(secondBuffer->getId(), generateFrameNumber());
+
+    Transaction transaction2;
+    transaction2.setBuffer(layer, secondBuffer, std::nullopt, secondBufferCallbackId.framenumber,
+                           releaseCallback->getCallback());
+
+    // Set a different TransactionCompletedListener to mimic a second process
+    TransactionCompletedListener::setInstance(secondCompletedListener);
+    Transaction().merge(std::move(transaction1)).merge(std::move(transaction2)).apply();
+
+    // Make sure we can still get the release callback even though the merge happened in a different
+    // process.
+    ASSERT_NO_FATAL_FAILURE(waitForReleaseBufferCallback(*releaseCallback, firstBufferCallbackId));
+}
+
 } // namespace android
diff --git a/services/surfaceflinger/tests/ScreenCapture_test.cpp b/services/surfaceflinger/tests/ScreenCapture_test.cpp
index 2d5b502..f9b3185 100644
--- a/services/surfaceflinger/tests/ScreenCapture_test.cpp
+++ b/services/surfaceflinger/tests/ScreenCapture_test.cpp
@@ -55,7 +55,7 @@
         TransactionUtils::fillSurfaceRGBA8(mFGSurfaceControl, 195, 63, 63);
 
         asTransaction([&](Transaction& t) {
-            t.setDisplayLayerStack(display, 0);
+            t.setDisplayLayerStack(display, ui::DEFAULT_LAYER_STACK);
 
             t.setLayer(mBGSurfaceControl, INT32_MAX - 2).show(mBGSurfaceControl);
 
@@ -556,7 +556,7 @@
     Transaction()
             .show(redLayer)
             .show(secureLayer)
-            .setLayerStack(redLayer, 0)
+            .setLayerStack(redLayer, ui::DEFAULT_LAYER_STACK)
             .setLayer(redLayer, INT32_MAX)
             .apply();
 
@@ -648,7 +648,7 @@
     Transaction()
             .show(layer)
             .hide(mFGSurfaceControl)
-            .setLayerStack(layer, 0)
+            .setLayerStack(layer, ui::DEFAULT_LAYER_STACK)
             .setLayer(layer, INT32_MAX)
             .setColor(layer, {layerColor.r / 255, layerColor.g / 255, layerColor.b / 255})
             .setCrop(layer, bounds)
@@ -695,7 +695,7 @@
             .show(layer)
             .show(childLayer)
             .hide(mFGSurfaceControl)
-            .setLayerStack(layer, 0)
+            .setLayerStack(layer, ui::DEFAULT_LAYER_STACK)
             .setLayer(layer, INT32_MAX)
             .setColor(layer, {layerColor.r / 255, layerColor.g / 255, layerColor.b / 255})
             .setColor(childLayer, {childColor.r / 255, childColor.g / 255, childColor.b / 255})
diff --git a/services/surfaceflinger/tests/SetFrameRateOverride_test.cpp b/services/surfaceflinger/tests/SetFrameRateOverride_test.cpp
new file mode 100644
index 0000000..4efec77
--- /dev/null
+++ b/services/surfaceflinger/tests/SetFrameRateOverride_test.cpp
@@ -0,0 +1,98 @@
+/*
+ * 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 <gtest/gtest.h>
+#include <gui/DisplayEventReceiver.h>
+#include <gui/ISurfaceComposer.h>
+#include <gui/SurfaceComposerClient.h>
+#include <sys/epoll.h>
+#include <algorithm>
+
+namespace android {
+namespace {
+using FrameRateOverride = DisplayEventReceiver::Event::FrameRateOverride;
+
+class SetFrameRateOverrideTest : public ::testing::Test {
+protected:
+    void SetUp() override {
+        const ISurfaceComposer::VsyncSource vsyncSource = ISurfaceComposer::eVsyncSourceApp;
+        const ISurfaceComposer::EventRegistrationFlags eventRegistration = {
+                ISurfaceComposer::EventRegistration::frameRateOverride};
+
+        mDisplayEventReceiver =
+                std::make_unique<DisplayEventReceiver>(vsyncSource, eventRegistration);
+        EXPECT_EQ(NO_ERROR, mDisplayEventReceiver->initCheck());
+
+        mEpollFd = epoll_create1(EPOLL_CLOEXEC);
+        EXPECT_GT(mEpollFd, 1);
+
+        epoll_event event;
+        event.events = EPOLLIN;
+        EXPECT_EQ(0, epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mDisplayEventReceiver->getFd(), &event));
+    }
+
+    void TearDown() override { close(mEpollFd); }
+
+    void setFrameRateAndListenEvents(uid_t uid, float frameRate) {
+        status_t ret = SurfaceComposerClient::setOverrideFrameRate(uid, frameRate);
+        ASSERT_EQ(NO_ERROR, ret);
+
+        DisplayEventReceiver::Event event;
+        bool isOverrideFlushReceived = false;
+        mFrameRateOverrides.clear();
+
+        epoll_event epollEvent;
+        while (epoll_wait(mEpollFd, &epollEvent, 1, 1000) > 0) {
+            while (mDisplayEventReceiver->getEvents(&event, 1) > 0) {
+                if (event.header.type == DisplayEventReceiver::DISPLAY_EVENT_FRAME_RATE_OVERRIDE) {
+                    mFrameRateOverrides.emplace_back(event.frameRateOverride);
+                }
+                if (event.header.type ==
+                    DisplayEventReceiver::DISPLAY_EVENT_FRAME_RATE_OVERRIDE_FLUSH) {
+                    isOverrideFlushReceived = true;
+                }
+            }
+
+            if (isOverrideFlushReceived) break;
+        }
+    }
+
+    std::unique_ptr<DisplayEventReceiver> mDisplayEventReceiver;
+    std::vector<FrameRateOverride> mFrameRateOverrides;
+
+    int mEpollFd;
+};
+
+TEST_F(SetFrameRateOverrideTest, SetFrameRateOverrideCall) {
+    uid_t uid = getuid();
+    float frameRate = 30.0f;
+    setFrameRateAndListenEvents(uid, frameRate);
+    // check if the frame rate override we set exists
+    ASSERT_TRUE(std::find_if(mFrameRateOverrides.begin(), mFrameRateOverrides.end(),
+                             [uid = uid, frameRate = frameRate](auto i) {
+                                 return uid == i.uid && frameRate == i.frameRateHz;
+                             }) != mFrameRateOverrides.end());
+
+    // test removing frame rate override
+    frameRate = 0.0f;
+    setFrameRateAndListenEvents(uid, frameRate);
+    ASSERT_TRUE(std::find_if(mFrameRateOverrides.begin(), mFrameRateOverrides.end(),
+                             [uid = uid, frameRate = frameRate](auto i) {
+                                 return uid == i.uid && frameRate == i.frameRateHz;
+                             }) == mFrameRateOverrides.end());
+}
+} // namespace
+} // namespace android
diff --git a/services/surfaceflinger/tests/SurfaceInterceptor_test.cpp b/services/surfaceflinger/tests/SurfaceInterceptor_test.cpp
index a424059..28e8b8c 100644
--- a/services/surfaceflinger/tests/SurfaceInterceptor_test.cpp
+++ b/services/surfaceflinger/tests/SurfaceInterceptor_test.cpp
@@ -19,6 +19,7 @@
 #pragma clang diagnostic ignored "-Wconversion"
 #pragma clang diagnostic ignored "-Wextra"
 
+#include <android-base/stringprintf.h>
 #include <frameworks/native/cmds/surfacereplayer/proto/src/trace.pb.h>
 #include <google/protobuf/io/zero_copy_stream_impl.h>
 #include <gtest/gtest.h>
@@ -56,10 +57,7 @@
 const String8 DISPLAY_NAME("SurfaceInterceptor Display Test");
 constexpr auto TEST_BG_SURFACE_NAME = "BG Interceptor Test Surface";
 constexpr auto TEST_FG_SURFACE_NAME = "FG Interceptor Test Surface";
-constexpr auto UNIQUE_TEST_BG_SURFACE_NAME = "BG Interceptor Test Surface#0";
-constexpr auto UNIQUE_TEST_FG_SURFACE_NAME = "FG Interceptor Test Surface#0";
 constexpr auto LAYER_NAME = "Layer Create and Delete Test";
-constexpr auto UNIQUE_LAYER_NAME = "Layer Create and Delete Test#0";
 
 constexpr auto DEFAULT_FILENAME = "/data/misc/wmtrace/transaction_trace.winscope";
 
@@ -105,11 +103,15 @@
     system("service call SurfaceFlinger 1020 i32 0 > /dev/null");
 }
 
+std::string getUniqueName(const std::string& name, const Increment& increment) {
+    return base::StringPrintf("%s#%d", name.c_str(), increment.surface_creation().id());
+}
+
 int32_t getSurfaceId(const Trace& capturedTrace, const std::string& surfaceName) {
     int32_t layerId = 0;
     for (const auto& increment : capturedTrace.increment()) {
         if (increment.increment_case() == increment.kSurfaceCreation) {
-            if (increment.surface_creation().name() == surfaceName) {
+            if (increment.surface_creation().name() == getUniqueName(surfaceName, increment)) {
                 layerId = increment.surface_creation().id();
             }
         }
@@ -283,7 +285,7 @@
     ASSERT_TRUE(mFGSurfaceControl->isValid());
 
     Transaction t;
-    t.setDisplayLayerStack(display, 0);
+    t.setDisplayLayerStack(display, ui::DEFAULT_LAYER_STACK);
     ASSERT_EQ(NO_ERROR,
               t.setLayer(mBGSurfaceControl, INT_MAX - 3)
                       .show(mBGSurfaceControl)
@@ -293,8 +295,8 @@
 }
 
 void SurfaceInterceptorTest::preProcessTrace(const Trace& trace) {
-    mBGLayerId = getSurfaceId(trace, UNIQUE_TEST_BG_SURFACE_NAME);
-    mFGLayerId = getSurfaceId(trace, UNIQUE_TEST_FG_SURFACE_NAME);
+    mBGLayerId = getSurfaceId(trace, TEST_BG_SURFACE_NAME);
+    mFGLayerId = getSurfaceId(trace, TEST_FG_SURFACE_NAME);
 }
 
 void SurfaceInterceptorTest::captureTest(TestTransactionAction action,
@@ -380,7 +382,7 @@
 }
 
 void SurfaceInterceptorTest::layerStackUpdate(Transaction& t) {
-    t.setLayerStack(mBGSurfaceControl, STACK_UPDATE);
+    t.setLayerStack(mBGSurfaceControl, ui::LayerStack::fromValue(STACK_UPDATE));
 }
 
 void SurfaceInterceptorTest::hiddenFlagUpdate(Transaction& t) {
@@ -752,9 +754,7 @@
 }
 
 bool SurfaceInterceptorTest::surfaceCreationFound(const Increment& increment, bool foundSurface) {
-    bool isMatch(increment.surface_creation().name() == UNIQUE_LAYER_NAME &&
-            increment.surface_creation().w() == SIZE_UPDATE &&
-            increment.surface_creation().h() == SIZE_UPDATE);
+    bool isMatch(increment.surface_creation().name() == getUniqueName(LAYER_NAME, increment));
     if (isMatch && !foundSurface) {
         foundSurface = true;
     } else if (isMatch && foundSurface) {
@@ -808,7 +808,7 @@
                     break;
                 case Increment::IncrementCase::kSurfaceDeletion:
                     // Find the id of created surface.
-                    targetId = getSurfaceId(trace, UNIQUE_LAYER_NAME);
+                    targetId = getSurfaceId(trace, LAYER_NAME);
                     foundIncrement = surfaceDeletionFound(increment, targetId, foundIncrement);
                     break;
                 case Increment::IncrementCase::kDisplayCreation:
diff --git a/services/surfaceflinger/tests/TransactionTestHarnesses.h b/services/surfaceflinger/tests/TransactionTestHarnesses.h
index 89f6086..60cffb1 100644
--- a/services/surfaceflinger/tests/TransactionTestHarnesses.h
+++ b/services/surfaceflinger/tests/TransactionTestHarnesses.h
@@ -65,7 +65,7 @@
 
                 SurfaceComposerClient::Transaction t;
                 t.setDisplaySurface(vDisplay, producer);
-                t.setDisplayLayerStack(vDisplay, 0);
+                t.setDisplayLayerStack(vDisplay, ui::DEFAULT_LAYER_STACK);
                 t.setDisplayProjection(vDisplay, displayState.orientation,
                                        Rect(displayState.layerStackSpaceRect), Rect(resolution));
                 t.apply();
diff --git a/services/surfaceflinger/tests/WindowInfosListener_test.cpp b/services/surfaceflinger/tests/WindowInfosListener_test.cpp
index 15b79be..bb52245 100644
--- a/services/surfaceflinger/tests/WindowInfosListener_test.cpp
+++ b/services/surfaceflinger/tests/WindowInfosListener_test.cpp
@@ -22,6 +22,7 @@
 
 namespace android {
 using Transaction = SurfaceComposerClient::Transaction;
+using gui::DisplayInfo;
 using gui::WindowInfo;
 
 class WindowInfosListenerTest : public ::testing::Test {
@@ -40,7 +41,8 @@
 
     struct SyncWindowInfosListener : public gui::WindowInfosListener {
     public:
-        void onWindowInfosChanged(const std::vector<WindowInfo>& windowInfos) override {
+        void onWindowInfosChanged(const std::vector<WindowInfo>& windowInfos,
+                                  const std::vector<DisplayInfo>&) override {
             windowInfosPromise.set_value(windowInfos);
         }
 
@@ -84,7 +86,7 @@
                                    ISurfaceComposerClient::eFXSurfaceBufferState);
 
     Transaction()
-            .setLayerStack(surfaceControl, 0)
+            .setLayerStack(surfaceControl, ui::DEFAULT_LAYER_STACK)
             .show(surfaceControl)
             .setLayer(surfaceControl, INT32_MAX - 1)
             .setInputWindowInfo(surfaceControl, windowInfo)
@@ -112,7 +114,7 @@
                                    ISurfaceComposerClient::eFXSurfaceBufferState);
     const Rect crop(0, 0, 100, 100);
     Transaction()
-            .setLayerStack(surfaceControl, 0)
+            .setLayerStack(surfaceControl, ui::DEFAULT_LAYER_STACK)
             .show(surfaceControl)
             .setLayer(surfaceControl, INT32_MAX - 1)
             .setCrop(surfaceControl, crop)
diff --git a/services/surfaceflinger/tests/fakehwc/Android.bp b/services/surfaceflinger/tests/fakehwc/Android.bp
index 2551a19..168b576 100644
--- a/services/surfaceflinger/tests/fakehwc/Android.bp
+++ b/services/surfaceflinger/tests/fakehwc/Android.bp
@@ -12,10 +12,10 @@
     defaults: ["surfaceflinger_defaults"],
     test_suites: ["device-tests"],
     srcs: [
-         "FakeComposerClient.cpp",
-         "FakeComposerService.cpp",
-         "FakeComposerUtils.cpp",
-         "SFFakeHwc_test.cpp"
+        "FakeComposerClient.cpp",
+        "FakeComposerService.cpp",
+        "FakeComposerUtils.cpp",
+        "SFFakeHwc_test.cpp",
     ],
     require_root: true,
     shared_libs: [
@@ -23,12 +23,14 @@
         "android.hardware.graphics.composer@2.2",
         "android.hardware.graphics.composer@2.3",
         "android.hardware.graphics.composer@2.4",
+        "android.hardware.graphics.composer3-V1-ndk",
         "android.hardware.graphics.mapper@2.0",
         "android.hardware.graphics.mapper@3.0",
         "android.hardware.graphics.mapper@4.0",
         "android.hardware.power@1.3",
         "libbase",
         "libbinder",
+        "libbinder_ndk",
         "libcutils",
         "libfmq",
         "libgui",
@@ -43,15 +45,18 @@
     ],
     static_libs: [
         "android.hardware.graphics.composer@2.1-resources",
+        "libaidlcommonsupport",
         "libcompositionengine",
         "libgmock",
         "libperfetto_client_experimental",
         "librenderengine",
         "libtrace_proto",
+        "libaidlcommonsupport",
     ],
     header_libs: [
         "android.hardware.graphics.composer@2.4-command-buffer",
         "android.hardware.graphics.composer@2.4-hal",
+        "android.hardware.graphics.composer3-command-buffer",
         "libsurfaceflinger_headers",
     ],
 }
diff --git a/services/surfaceflinger/tests/fakehwc/SFFakeHwc_test.cpp b/services/surfaceflinger/tests/fakehwc/SFFakeHwc_test.cpp
index 162711d..b3b4ec1 100644
--- a/services/surfaceflinger/tests/fakehwc/SFFakeHwc_test.cpp
+++ b/services/surfaceflinger/tests/fakehwc/SFFakeHwc_test.cpp
@@ -279,7 +279,7 @@
     }
 
     bool waitForHotplugEvent(Display displayId, bool connected) {
-        return waitForHotplugEvent(PhysicalDisplayId(displayId), connected);
+        return waitForHotplugEvent(physicalIdFromHwcDisplayId(displayId), connected);
     }
 
     bool waitForHotplugEvent(PhysicalDisplayId displayId, bool connected) {
@@ -305,7 +305,7 @@
     }
 
     bool waitForModeChangedEvent(Display display, int32_t modeId) {
-        PhysicalDisplayId displayId(display);
+        PhysicalDisplayId displayId = physicalIdFromHwcDisplayId(display);
         int waitCount = 20;
         while (waitCount--) {
             while (!mReceivedDisplayEvents.empty()) {
@@ -363,7 +363,7 @@
 
             {
                 TransactionScope ts(*mFakeComposerClient);
-                ts.setDisplayLayerStack(display, 0);
+                ts.setDisplayLayerStack(display, ui::DEFAULT_LAYER_STACK);
 
                 ts.setLayer(surfaceControl, INT32_MAX - 2).show(surfaceControl);
             }
@@ -426,7 +426,7 @@
 
             {
                 TransactionScope ts(*mFakeComposerClient);
-                ts.setDisplayLayerStack(display, 0);
+                ts.setDisplayLayerStack(display, ui::DEFAULT_LAYER_STACK);
 
                 ts.setLayer(surfaceControl, INT32_MAX - 2).show(surfaceControl);
             }
@@ -479,7 +479,7 @@
 
             {
                 TransactionScope ts(*mFakeComposerClient);
-                ts.setDisplayLayerStack(display, 0);
+                ts.setDisplayLayerStack(display, ui::DEFAULT_LAYER_STACK);
 
                 ts.setLayer(surfaceControl, INT32_MAX - 2).show(surfaceControl);
             }
@@ -534,7 +534,7 @@
 
             {
                 TransactionScope ts(*mFakeComposerClient);
-                ts.setDisplayLayerStack(display, 0);
+                ts.setDisplayLayerStack(display, ui::DEFAULT_LAYER_STACK);
 
                 ts.setLayer(surfaceControl, INT32_MAX - 2).show(surfaceControl);
             }
@@ -586,7 +586,7 @@
 
             {
                 TransactionScope ts(*mFakeComposerClient);
-                ts.setDisplayLayerStack(display, 0);
+                ts.setDisplayLayerStack(display, ui::DEFAULT_LAYER_STACK);
 
                 ts.setLayer(surfaceControl, INT32_MAX - 2).show(surfaceControl);
             }
@@ -651,7 +651,7 @@
 
             {
                 TransactionScope ts(*mFakeComposerClient);
-                ts.setDisplayLayerStack(display, 0);
+                ts.setDisplayLayerStack(display, ui::DEFAULT_LAYER_STACK);
 
                 ts.setLayer(surfaceControl, INT32_MAX - 2).show(surfaceControl);
             }
@@ -703,7 +703,7 @@
 
             {
                 TransactionScope ts(*mFakeComposerClient);
-                ts.setDisplayLayerStack(display, 0);
+                ts.setDisplayLayerStack(display, ui::DEFAULT_LAYER_STACK);
 
                 ts.setLayer(surfaceControl, INT32_MAX - 2).show(surfaceControl);
             }
@@ -750,7 +750,7 @@
 
             {
                 TransactionScope ts(*mFakeComposerClient);
-                ts.setDisplayLayerStack(display, 0);
+                ts.setDisplayLayerStack(display, ui::DEFAULT_LAYER_STACK);
 
                 ts.setLayer(surfaceControl, INT32_MAX - 2).show(surfaceControl);
             }
@@ -797,7 +797,7 @@
 
             {
                 TransactionScope ts(*mFakeComposerClient);
-                ts.setDisplayLayerStack(display, 0);
+                ts.setDisplayLayerStack(display, ui::DEFAULT_LAYER_STACK);
 
                 ts.setLayer(surfaceControl, INT32_MAX - 2).show(surfaceControl);
             }
@@ -1195,7 +1195,7 @@
         fillSurfaceRGBA8(mFGSurfaceControl, RED);
 
         Transaction t;
-        t.setDisplayLayerStack(display, 0);
+        t.setDisplayLayerStack(display, ui::DEFAULT_LAYER_STACK);
 
         t.setLayer(mBGSurfaceControl, INT32_MAX - 2);
         t.show(mBGSurfaceControl);
@@ -1342,7 +1342,7 @@
         ALOGD("TransactionTest::SetLayerStack");
         {
             TransactionScope ts(*sFakeComposer);
-            ts.setLayerStack(mFGSurfaceControl, 1);
+            ts.setLayerStack(mFGSurfaceControl, ui::LayerStack{1});
         }
 
         // Foreground layer should have disappeared.
diff --git a/services/surfaceflinger/tests/tracing/Android.bp b/services/surfaceflinger/tests/tracing/Android.bp
new file mode 100644
index 0000000..aa6c74e
--- /dev/null
+++ b/services/surfaceflinger/tests/tracing/Android.bp
@@ -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.
+
+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: "transactiontrace_testsuite",
+    defaults: [
+        "libsurfaceflinger_mocks_defaults",
+        "surfaceflinger_defaults",
+        "skia_renderengine_deps",
+    ],
+    test_suites: ["device-tests"],
+    sanitize: {
+        address: false,
+    },
+    srcs: [
+        ":libsurfaceflinger_sources",
+        ":libsurfaceflinger_mock_sources",
+        ":layertracegenerator_sources",
+        "TransactionTraceTestSuite.cpp",
+    ],
+    static_libs: [
+        "libc++fs",
+    ],
+    header_libs: [
+        "libsurfaceflinger_mocks_headers",
+        "layertracegenerator_headers",
+    ],
+    data: ["testdata/*"],
+}
diff --git a/services/surfaceflinger/tests/tracing/AndroidTest.xml b/services/surfaceflinger/tests/tracing/AndroidTest.xml
new file mode 100644
index 0000000..c0aeb35
--- /dev/null
+++ b/services/surfaceflinger/tests/tracing/AndroidTest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<configuration description="Config for transactiontrace_testsuite">
+    <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+        <option name="cleanup" value="true" />
+        <option name="push"
+        value="transactiontrace_testsuite->/data/local/tmp/transactiontrace_testsuite" />
+    </target_preparer>
+    <option name="test-suite-tag" value="apct" />
+    <test class="com.android.tradefed.testtype.GTest" >
+        <option name="native-test-device-path" value="/data/local/tmp" />
+        <option name="module-name" value="transactiontrace_testsuite" />
+    </test>
+</configuration>
diff --git a/services/surfaceflinger/tests/tracing/TransactionTraceTestSuite.cpp b/services/surfaceflinger/tests/tracing/TransactionTraceTestSuite.cpp
new file mode 100644
index 0000000..ac4354c
--- /dev/null
+++ b/services/surfaceflinger/tests/tracing/TransactionTraceTestSuite.cpp
@@ -0,0 +1,145 @@
+/*
+ * 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 <filesystem>
+#include <fstream>
+#include <iostream>
+#include <string>
+
+#include <LayerTraceGenerator.h>
+#include <Tracing/TransactionProtoParser.h>
+#include <layerproto/LayerProtoHeader.h>
+#include <log/log.h>
+
+using namespace android::surfaceflinger;
+
+namespace android {
+
+class TransactionTraceTestSuite : public testing::Test,
+                                  public testing::WithParamInterface<std::filesystem::path> {
+public:
+    static std::vector<std::filesystem::path> sTransactionTraces;
+    static constexpr std::string_view sTransactionTracePrefix = "transactions_trace_";
+    static constexpr std::string_view sLayersTracePrefix = "layers_trace_";
+    static constexpr std::string_view sTracePostfix = ".winscope";
+
+    proto::TransactionTraceFile mTransactionTrace;
+    LayersTraceFileProto mExpectedLayersTraceProto;
+    LayersTraceFileProto mActualLayersTraceProto;
+
+protected:
+    void SetUp() override {
+        std::filesystem::path transactionTracePath = GetParam();
+        parseTransactionTraceFromFile(transactionTracePath.c_str(), mTransactionTrace);
+
+        std::string expectedLayersFilename = std::string(sLayersTracePrefix) +
+                transactionTracePath.filename().string().substr(sTransactionTracePrefix.length());
+        std::string expectedLayersTracePath =
+                transactionTracePath.parent_path().string() + "/" + expectedLayersFilename;
+        EXPECT_TRUE(std::filesystem::exists(std::filesystem::path(expectedLayersTracePath)));
+        parseLayersTraceFromFile(expectedLayersTracePath.c_str(), mExpectedLayersTraceProto);
+        TemporaryDir temp_dir;
+        std::string actualLayersTracePath =
+                std::string(temp_dir.path) + "/" + expectedLayersFilename + "_actual";
+
+        EXPECT_TRUE(
+                LayerTraceGenerator().generate(mTransactionTrace, actualLayersTracePath.c_str()))
+                << "Failed to generate layers trace from " << transactionTracePath;
+        EXPECT_TRUE(std::filesystem::exists(std::filesystem::path(actualLayersTracePath)));
+        parseLayersTraceFromFile(actualLayersTracePath.c_str(), mActualLayersTraceProto);
+    }
+
+    void parseTransactionTraceFromFile(const char* transactionTracePath,
+                                       proto::TransactionTraceFile& outProto) {
+        ALOGD("Parsing file %s...", transactionTracePath);
+        std::fstream input(transactionTracePath, std::ios::in | std::ios::binary);
+        EXPECT_TRUE(input) << "Error could not open " << transactionTracePath;
+        EXPECT_TRUE(outProto.ParseFromIstream(&input))
+                << "Failed to parse " << transactionTracePath;
+    }
+
+    void parseLayersTraceFromFile(const char* layersTracePath, LayersTraceFileProto& outProto) {
+        ALOGD("Parsing file %s...", layersTracePath);
+        std::fstream input(layersTracePath, std::ios::in | std::ios::binary);
+        EXPECT_TRUE(input) << "Error could not open " << layersTracePath;
+        EXPECT_TRUE(outProto.ParseFromIstream(&input)) << "Failed to parse " << layersTracePath;
+    }
+};
+
+std::vector<std::filesystem::path> TransactionTraceTestSuite::sTransactionTraces{};
+
+TEST_P(TransactionTraceTestSuite, validateEndState) {
+    ASSERT_GT(mActualLayersTraceProto.entry_size(), 0);
+    ASSERT_GT(mExpectedLayersTraceProto.entry_size(), 0);
+
+    auto expectedLastEntry =
+            mExpectedLayersTraceProto.entry(mExpectedLayersTraceProto.entry_size() - 1);
+    auto actualLastEntry = mActualLayersTraceProto.entry(mActualLayersTraceProto.entry_size() - 1);
+
+    EXPECT_EQ(expectedLastEntry.layers().layers_size(), actualLastEntry.layers().layers_size());
+    for (int i = 0;
+         i < expectedLastEntry.layers().layers_size() && i < actualLastEntry.layers().layers_size();
+         i++) {
+        auto expectedLayer = expectedLastEntry.layers().layers(i);
+        auto actualLayer = actualLastEntry.layers().layers(i);
+        EXPECT_EQ(expectedLayer.id(), actualLayer.id());
+        EXPECT_EQ(expectedLayer.name(), actualLayer.name());
+        EXPECT_EQ(expectedLayer.parent(), actualLayer.parent());
+        EXPECT_EQ(expectedLayer.z(), actualLayer.z());
+        EXPECT_EQ(expectedLayer.curr_frame(), actualLayer.curr_frame());
+        ALOGV("Validating %s[%d] parent=%d z=%d frame=%" PRIu64, expectedLayer.name().c_str(),
+              expectedLayer.id(), expectedLayer.parent(), expectedLayer.z(),
+              expectedLayer.curr_frame());
+    }
+}
+
+std::string PrintToStringParamName(const ::testing::TestParamInfo<std::filesystem::path>& info) {
+    const auto& prefix = android::TransactionTraceTestSuite::sTransactionTracePrefix;
+    const auto& postfix = android::TransactionTraceTestSuite::sTracePostfix;
+
+    const auto& filename = info.param.filename().string();
+    return filename.substr(prefix.length(), filename.length() - prefix.length() - postfix.length());
+}
+
+INSTANTIATE_TEST_CASE_P(TransactionTraceTestSuites, TransactionTraceTestSuite,
+                        testing::ValuesIn(TransactionTraceTestSuite::sTransactionTraces),
+                        PrintToStringParamName);
+
+} // namespace android
+
+int main(int argc, char** argv) {
+    for (const auto& entry : std::filesystem::directory_iterator(
+                 android::base::GetExecutableDirectory() + "/testdata/")) {
+        if (!entry.is_regular_file()) {
+            continue;
+        }
+        const auto& filename = entry.path().filename().string();
+        const auto& prefix = android::TransactionTraceTestSuite::sTransactionTracePrefix;
+        if (filename.compare(0, prefix.length(), prefix)) {
+            continue;
+        }
+        const std::string& path = entry.path().string();
+        const auto& postfix = android::TransactionTraceTestSuite::sTracePostfix;
+        if (path.compare(path.length() - postfix.length(), postfix.length(), postfix)) {
+            continue;
+        }
+        android::TransactionTraceTestSuite::sTransactionTraces.push_back(path);
+    }
+    ::testing::InitGoogleTest(&argc, argv);
+    return RUN_ALL_TESTS();
+}
\ No newline at end of file
diff --git a/services/surfaceflinger/tests/tracing/readme.md b/services/surfaceflinger/tests/tracing/readme.md
new file mode 100644
index 0000000..3e80a74
--- /dev/null
+++ b/services/surfaceflinger/tests/tracing/readme.md
@@ -0,0 +1,20 @@
+### TransactionTrace Testsuite ###
+
+Hassle free way to test and validate whether a sequence of
+transactions will produce the expected front end state(s). Test
+runs through all testdata/transactions_trace_*.winscope files,
+generates layer states and checks if the states match the
+corresponding layer trace in testdata.
+
+
+#### Run Test ####
+`atest transactiontrace_testsuite`
+
+
+#### Workflow ####
+Add transaction traces that resulted in front end bugs along
+with the layer trace after fixing the issue. The layer trace
+can be generated by using the layertracegenerator tool. The
+main goal of this test suite is to add regression tests with
+minimal effort.
+
diff --git a/services/surfaceflinger/tests/tracing/testdata/layers_trace_boot.winscope b/services/surfaceflinger/tests/tracing/testdata/layers_trace_boot.winscope
new file mode 100644
index 0000000..9e4005c
--- /dev/null
+++ b/services/surfaceflinger/tests/tracing/testdata/layers_trace_boot.winscope
Binary files differ
diff --git a/services/surfaceflinger/tests/tracing/testdata/transactions_trace_boot.winscope b/services/surfaceflinger/tests/tracing/testdata/transactions_trace_boot.winscope
new file mode 100644
index 0000000..8356ae7
--- /dev/null
+++ b/services/surfaceflinger/tests/tracing/testdata/transactions_trace_boot.winscope
Binary files differ
diff --git a/services/surfaceflinger/tests/unittests/Android.bp b/services/surfaceflinger/tests/unittests/Android.bp
index 9e704c3..1eea023 100644
--- a/services/surfaceflinger/tests/unittests/Android.bp
+++ b/services/surfaceflinger/tests/unittests/Android.bp
@@ -21,9 +21,31 @@
     default_applicable_licenses: ["frameworks_native_license"],
 }
 
+filegroup {
+    name: "libsurfaceflinger_mock_sources",
+    srcs: [
+        "mock/DisplayHardware/MockComposer.cpp",
+        "mock/DisplayHardware/MockHWC2.cpp",
+        "mock/DisplayHardware/MockPowerAdvisor.cpp",
+        "mock/MockEventThread.cpp",
+        "mock/MockFrameTimeline.cpp",
+        "mock/MockFrameTracer.cpp",
+        "mock/MockNativeWindowSurface.cpp",
+        "mock/MockSurfaceInterceptor.cpp",
+        "mock/MockTimeStats.cpp",
+        "mock/MockVsyncController.cpp",
+        "mock/MockVSyncTracker.cpp",
+        "mock/system/window/MockNativeWindow.cpp",
+    ],
+}
+
 cc_test {
     name: "libsurfaceflinger_unittest",
-    defaults: ["surfaceflinger_defaults"],
+    defaults: [
+        "libsurfaceflinger_mocks_defaults",
+        "skia_renderengine_deps",
+        "surfaceflinger_defaults",
+    ],
     test_suites: ["device-tests"],
     sanitize: {
         // Using the address sanitizer not only helps uncover issues in the code
@@ -42,21 +64,24 @@
         address: true,
     },
     srcs: [
+        ":libsurfaceflinger_mock_sources",
         ":libsurfaceflinger_sources",
         "libsurfaceflinger_unittest_main.cpp",
         "CachingTest.cpp",
         "CompositionTest.cpp",
         "DispSyncSourceTest.cpp",
-        "DisplayIdentificationTest.cpp",
         "DisplayIdGeneratorTest.cpp",
         "DisplayTransactionTest.cpp",
         "DisplayDevice_GetBestColorModeTest.cpp",
         "DisplayDevice_InitiateModeChange.cpp",
+        "DisplayDevice_SetDisplayBrightnessTest.cpp",
         "DisplayDevice_SetProjectionTest.cpp",
         "EventThreadTest.cpp",
+        "FlagManagerTest.cpp",
         "FpsReporterTest.cpp",
         "FpsTest.cpp",
         "FramebufferSurfaceTest.cpp",
+        "FrameRateOverrideMappingsTest.cpp",
         "FrameTimelineTest.cpp",
         "GameModeTest.cpp",
         "HWComposerTest.cpp",
@@ -64,19 +89,21 @@
         "LayerHistoryTest.cpp",
         "LayerInfoTest.cpp",
         "LayerMetadataTest.cpp",
+        "LayerTest.cpp",
+        "LayerTestUtils.cpp",
         "MessageQueueTest.cpp",
         "SurfaceFlinger_CreateDisplayTest.cpp",
         "SurfaceFlinger_DestroyDisplayTest.cpp",
+        "SurfaceFlinger_DisplayModeSwitching.cpp",
+        "SurfaceFlinger_DisplayTransactionCommitTest.cpp",
         "SurfaceFlinger_GetDisplayNativePrimariesTest.cpp",
-        "SurfaceFlinger_HandleTransactionLockedTest.cpp",
-        "SurfaceFlinger_NotifyPowerBoostTest.cpp",
         "SurfaceFlinger_HotplugTest.cpp",
+        "SurfaceFlinger_NotifyPowerBoostTest.cpp",
         "SurfaceFlinger_OnInitializeDisplaysTest.cpp",
         "SurfaceFlinger_SetDisplayStateTest.cpp",
         "SurfaceFlinger_SetPowerModeInternalTest.cpp",
         "SurfaceFlinger_SetupNewDisplayDeviceInternalTest.cpp",
         "SchedulerTest.cpp",
-        "SchedulerUtilsTest.cpp",
         "SetFrameRateTest.cpp",
         "RefreshRateConfigsTest.cpp",
         "RefreshRateSelectionTest.cpp",
@@ -84,10 +111,11 @@
         "RegionSamplingTest.cpp",
         "TimeStatsTest.cpp",
         "FrameTracerTest.cpp",
-        "TimerTest.cpp",
         "TransactionApplicationTest.cpp",
         "TransactionFrameTracerTest.cpp",
+        "TransactionProtoParserTest.cpp",
         "TransactionSurfaceFrameTest.cpp",
+        "TransactionTracingTest.cpp",
         "TunnelModeEnabledReporterTest.cpp",
         "StrongTypingTest.cpp",
         "VSyncDispatchTimerQueueTest.cpp",
@@ -96,30 +124,26 @@
         "VSyncPredictorTest.cpp",
         "VSyncReactorTest.cpp",
         "VsyncConfigurationTest.cpp",
-        "mock/DisplayHardware/MockComposer.cpp",
-        "mock/DisplayHardware/MockHWC2.cpp",
-        "mock/DisplayHardware/MockPowerAdvisor.cpp",
-        "mock/MockEventThread.cpp",
-        "mock/MockFrameTimeline.cpp",
-        "mock/MockFrameTracer.cpp",
-        "mock/MockMessageQueue.cpp",
-        "mock/MockNativeWindowSurface.cpp",
-        "mock/MockSurfaceInterceptor.cpp",
-        "mock/MockTimeStats.cpp",
-        "mock/MockVsyncController.cpp",
-        "mock/MockVSyncTracker.cpp",
-        "mock/system/window/MockNativeWindow.cpp",
     ],
+}
+
+cc_defaults {
+    name: "libsurfaceflinger_mocks_defaults",
     static_libs: [
+        "android.hardware.common-V2-ndk",
+        "android.hardware.common.fmq-V1-ndk",
+        "android.hardware.graphics.common-V3-ndk",
         "android.hardware.graphics.composer@2.1",
         "android.hardware.graphics.composer@2.2",
         "android.hardware.graphics.composer@2.3",
         "android.hardware.graphics.composer@2.4",
+        "android.hardware.graphics.composer3-V1-ndk",
         "android.hardware.power@1.0",
         "android.hardware.power@1.1",
         "android.hardware.power@1.2",
         "android.hardware.power@1.3",
-        "android.hardware.power-V1-cpp",
+        "android.hardware.power-V2-cpp",
+        "libaidlcommonsupport",
         "libcompositionengine_mocks",
         "libcompositionengine",
         "libframetimeline",
@@ -127,12 +151,14 @@
         "libgui_mocks",
         "liblayers_proto",
         "libperfetto_client_experimental",
-        "librenderengine_mocks",
         "librenderengine",
+        "librenderengine_mocks",
+        "libscheduler",
         "libserviceutils",
         "libtimestats",
         "libtimestats_atoms_proto",
         "libtimestats_proto",
+        "libtonemap",
         "libtrace_proto",
         "perfetto_trace_protos",
     ],
@@ -145,6 +171,7 @@
         "android.hardware.graphics.common@1.2",
         "libbase",
         "libbinder",
+        "libbinder_ndk",
         "libcutils",
         "libEGL",
         "libfmq",
@@ -161,12 +188,20 @@
         "libsync",
         "libui",
         "libutils",
+        "server_configurable_flags",
     ],
     header_libs: [
+        "android.hardware.graphics.composer3-command-buffer",
         "android.hardware.graphics.composer@2.1-command-buffer",
         "android.hardware.graphics.composer@2.2-command-buffer",
         "android.hardware.graphics.composer@2.3-command-buffer",
         "android.hardware.graphics.composer@2.4-command-buffer",
+        "libscheduler_test_headers",
         "libsurfaceflinger_headers",
     ],
 }
+
+cc_library_headers {
+    name: "libsurfaceflinger_mocks_headers",
+    export_include_dirs: ["."],
+}
diff --git a/services/surfaceflinger/tests/unittests/CachingTest.cpp b/services/surfaceflinger/tests/unittests/CachingTest.cpp
index 6a7ec9b..6f85498 100644
--- a/services/surfaceflinger/tests/unittests/CachingTest.cpp
+++ b/services/surfaceflinger/tests/unittests/CachingTest.cpp
@@ -14,11 +14,6 @@
  * limitations under the License.
  */
 
-// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wconversion"
-#pragma clang diagnostic ignored "-Wextra"
-
 #undef LOG_TAG
 #define LOG_TAG "CachingTest"
 
@@ -42,7 +37,7 @@
     sp<IBinder> binder = new BBinder();
     // test getting invalid client_cache_id
     client_cache_t id;
-    uint32_t slot = mHwcSlotGenerator->getHwcCacheSlot(id);
+    int slot = mHwcSlotGenerator->getHwcCacheSlot(id);
     EXPECT_EQ(BufferQueue::INVALID_BUFFER_SLOT, slot);
 }
 
@@ -51,7 +46,7 @@
     client_cache_t id;
     id.token = binder;
     id.id = 0;
-    uint32_t slot = mHwcSlotGenerator->getHwcCacheSlot(id);
+    int slot = mHwcSlotGenerator->getHwcCacheSlot(id);
     EXPECT_EQ(BufferQueue::NUM_BUFFER_SLOTS - 1, slot);
 
     client_cache_t idB;
@@ -72,31 +67,28 @@
     std::vector<client_cache_t> ids;
     uint32_t cacheId = 0;
     // fill up cache
-    for (uint32_t i = 0; i < BufferQueue::NUM_BUFFER_SLOTS; i++) {
+    for (int i = 0; i < BufferQueue::NUM_BUFFER_SLOTS; i++) {
         client_cache_t id;
         id.token = binder;
         id.id = cacheId;
         ids.push_back(id);
 
-        uint32_t slot = mHwcSlotGenerator->getHwcCacheSlot(id);
+        int slot = mHwcSlotGenerator->getHwcCacheSlot(id);
         EXPECT_EQ(BufferQueue::NUM_BUFFER_SLOTS - (i + 1), slot);
         cacheId++;
     }
-    for (uint32_t i = 0; i < BufferQueue::NUM_BUFFER_SLOTS; i++) {
-        uint32_t slot = mHwcSlotGenerator->getHwcCacheSlot(ids[i]);
+    for (int i = 0; i < BufferQueue::NUM_BUFFER_SLOTS; i++) {
+        int slot = mHwcSlotGenerator->getHwcCacheSlot(ids[static_cast<uint32_t>(i)]);
         EXPECT_EQ(BufferQueue::NUM_BUFFER_SLOTS - (i + 1), slot);
     }
 
-    for (uint32_t i = 0; i < BufferQueue::NUM_BUFFER_SLOTS; i++) {
+    for (int i = 0; i < BufferQueue::NUM_BUFFER_SLOTS; i++) {
         client_cache_t id;
         id.token = binder;
         id.id = cacheId;
-        uint32_t slot = mHwcSlotGenerator->getHwcCacheSlot(id);
+        int slot = mHwcSlotGenerator->getHwcCacheSlot(id);
         EXPECT_EQ(BufferQueue::NUM_BUFFER_SLOTS - (i + 1), slot);
         cacheId++;
     }
 }
 } // namespace android
-
-// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic pop // ignored "-Wconversion -Wextra"
diff --git a/services/surfaceflinger/tests/unittests/CompositionTest.cpp b/services/surfaceflinger/tests/unittests/CompositionTest.cpp
index 52a36a2..15c9d19 100644
--- a/services/surfaceflinger/tests/unittests/CompositionTest.cpp
+++ b/services/surfaceflinger/tests/unittests/CompositionTest.cpp
@@ -30,6 +30,7 @@
 #include <gui/IProducerListener.h>
 #include <gui/LayerMetadata.h>
 #include <log/log.h>
+#include <renderengine/mock/FakeExternalTexture.h>
 #include <renderengine/mock/Framebuffer.h>
 #include <renderengine/mock/Image.h>
 #include <renderengine/mock/RenderEngine.h>
@@ -45,7 +46,6 @@
 #include "mock/DisplayHardware/MockComposer.h"
 #include "mock/DisplayHardware/MockPowerAdvisor.h"
 #include "mock/MockEventThread.h"
-#include "mock/MockMessageQueue.h"
 #include "mock/MockTimeStats.h"
 #include "mock/MockVsyncController.h"
 #include "mock/system/window/MockNativeWindow.h"
@@ -61,6 +61,8 @@
 using hal::PowerMode;
 using hal::Transform;
 
+using aidl::android::hardware::graphics::composer3::Capability;
+
 using testing::_;
 using testing::AtLeast;
 using testing::DoAll;
@@ -77,17 +79,22 @@
 constexpr hal::HWLayerId HWC_LAYER = 5000;
 constexpr Transform DEFAULT_TRANSFORM = static_cast<Transform>(0);
 
-constexpr PhysicalDisplayId DEFAULT_DISPLAY_ID(42);
+constexpr PhysicalDisplayId DEFAULT_DISPLAY_ID = PhysicalDisplayId::fromPort(42u);
 constexpr int DEFAULT_DISPLAY_WIDTH = 1920;
 constexpr int DEFAULT_DISPLAY_HEIGHT = 1024;
 
 constexpr int DEFAULT_TEXTURE_ID = 6000;
-constexpr int DEFAULT_LAYER_STACK = 7000;
+constexpr ui::LayerStack LAYER_STACK{7000u};
 
 constexpr int DEFAULT_DISPLAY_MAX_LUMINANCE = 500;
 
 constexpr int DEFAULT_SIDEBAND_STREAM = 51;
 
+MATCHER(IsIdentityMatrix, "") {
+    constexpr auto kIdentity = mat4();
+    return (mat4(arg) == kIdentity);
+}
+
 class CompositionTest : public testing::Test {
 public:
     CompositionTest() {
@@ -95,7 +102,6 @@
                 ::testing::UnitTest::GetInstance()->current_test_info();
         ALOGD("**** Setting up for %s.%s\n", test_info->test_case_name(), test_info->name());
 
-        mFlinger.mutableEventQueue().reset(mMessageQueue);
         setupScheduler();
 
         EXPECT_CALL(*mNativeWindow, query(NATIVE_WINDOW_WIDTH, _))
@@ -140,14 +146,10 @@
                 .WillRepeatedly(Return(FakeHwcDisplayInjector::DEFAULT_VSYNC_PERIOD));
         EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0));
 
-        constexpr ISchedulerCallback* kCallback = nullptr;
-        constexpr bool kHasMultipleConfigs = true;
         mFlinger.setupScheduler(std::move(vsyncController), std::move(vsyncTracker),
-                                std::move(eventThread), std::move(sfEventThread), kCallback,
-                                kHasMultipleConfigs);
-
-        // Layer history should be created if there are multiple configs.
-        ASSERT_TRUE(mFlinger.scheduler()->hasLayerHistory());
+                                std::move(eventThread), std::move(sfEventThread),
+                                TestableSurfaceFlinger::SchedulerCallbackImpl::kNoOp,
+                                TestableSurfaceFlinger::kTwoDisplayModes);
     }
 
     void setupForceGeometryDirty() {
@@ -157,7 +159,7 @@
         // pain)
         // mFlinger.mutableVisibleRegionsDirty() = true;
 
-        mFlinger.mutableGeometryInvalid() = true;
+        mFlinger.mutableGeometryDirty() = true;
     }
 
     template <typename Case>
@@ -169,7 +171,7 @@
     template <typename Case>
     void captureScreenComposition();
 
-    std::unordered_set<hal::Capability> mDefaultCapabilities = {hal::Capability::SIDEBAND_STREAM};
+    std::unordered_set<Capability> mDefaultCapabilities = {Capability::SIDEBAND_STREAM};
 
     bool mDisplayOff = false;
     TestableSurfaceFlinger mFlinger;
@@ -186,7 +188,6 @@
     Hwc2::mock::Composer* mComposer = nullptr;
     renderengine::mock::RenderEngine* mRenderEngine = new renderengine::mock::RenderEngine();
     mock::TimeStats* mTimeStats = new mock::TimeStats();
-    mock::MessageQueue* mMessageQueue = new mock::MessageQueue();
     Hwc2::mock::PowerAdvisor mPowerAdvisor;
 
     sp<Fence> mClientTargetAcquireFence = Fence::NO_FENCE;
@@ -202,8 +203,7 @@
     // --------------------------------------------------------------------
     // Invocation
 
-    mFlinger.onMessageReceived(MessageQueue::INVALIDATE);
-    mFlinger.onMessageReceived(MessageQueue::REFRESH);
+    mFlinger.commitAndComposite();
 
     LayerCase::cleanup(this);
 }
@@ -215,8 +215,7 @@
     // --------------------------------------------------------------------
     // Invocation
 
-    mFlinger.onMessageReceived(MessageQueue::INVALIDATE);
-    mFlinger.onMessageReceived(MessageQueue::REFRESH);
+    mFlinger.commitAndComposite();
 
     LayerCase::cleanup(this);
 }
@@ -237,24 +236,36 @@
                                                    CaptureArgs::UNSET_UID, visitor);
     };
 
-    // TODO: Eliminate expensive/real allocation if possible.
     const uint32_t usage = GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN |
             GRALLOC_USAGE_HW_RENDER | GRALLOC_USAGE_HW_TEXTURE;
-    mCaptureScreenBuffer = std::make_shared<
-            renderengine::ExternalTexture>(new GraphicBuffer(renderArea->getReqWidth(),
-                                                             renderArea->getReqHeight(),
-                                                             HAL_PIXEL_FORMAT_RGBA_8888, 1, usage,
-                                                             "screenshot"),
-                                           *mRenderEngine, true);
+    mCaptureScreenBuffer =
+            std::make_shared<renderengine::mock::FakeExternalTexture>(renderArea->getReqWidth(),
+                                                                      renderArea->getReqHeight(),
+                                                                      HAL_PIXEL_FORMAT_RGBA_8888, 1,
+                                                                      usage);
 
-    status_t result =
-            mFlinger.renderScreenImplLocked(*renderArea, traverseLayers, mCaptureScreenBuffer,
+    auto result = mFlinger.renderScreenImpl(*renderArea, traverseLayers, mCaptureScreenBuffer,
                                             forSystem, regionSampling);
-    EXPECT_EQ(NO_ERROR, result);
+    EXPECT_TRUE(result.valid());
+
+    auto& [status, drawFence] = result.get();
+
+    EXPECT_EQ(NO_ERROR, status);
+    if (drawFence.ok()) {
+        sync_wait(drawFence.get(), -1);
+    }
 
     LayerCase::cleanup(this);
 }
 
+template <class T>
+std::future<T> futureOf(T obj) {
+    std::promise<T> resultPromise;
+    std::future<T> resultFuture = resultPromise.get_future();
+    resultPromise.set_value(std::move(obj));
+    return resultFuture;
+}
+
 /* ------------------------------------------------------------------------
  * Variants for each display configuration which can be tested
  */
@@ -287,10 +298,8 @@
 
         auto ceDisplayArgs = compositionengine::DisplayCreationArgsBuilder()
                                      .setId(DEFAULT_DISPLAY_ID)
-                                     .setConnectionType(ui::DisplayConnectionType::Internal)
                                      .setPixels({DEFAULT_DISPLAY_WIDTH, DEFAULT_DISPLAY_HEIGHT})
                                      .setIsSecure(Derived::IS_SECURE)
-                                     .setLayerStackId(DEFAULT_LAYER_STACK)
                                      .setPowerAdvisor(&test->mPowerAdvisor)
                                      .setName(std::string("Injected display for ") +
                                               test_info->test_case_name() + "." + test_info->name())
@@ -309,21 +318,21 @@
                                  .setPowerMode(Derived::INIT_POWER_MODE)
                                  .inject();
         Mock::VerifyAndClear(test->mNativeWindow);
-        test->mDisplay->setLayerStack(DEFAULT_LAYER_STACK);
+        test->mDisplay->setLayerStack(LAYER_STACK);
     }
 
     template <typename Case>
     static void setupPreconditionCallExpectations(CompositionTest* test) {
         EXPECT_CALL(*test->mComposer, getDisplayCapabilities(HWC_DISPLAY, _))
-                .WillOnce(DoAll(SetArgPointee<1>(std::vector<Hwc2::DisplayCapability>({})),
+                .WillOnce(DoAll(SetArgPointee<1>(
+                                        std::vector<aidl::android::hardware::graphics::composer3::
+                                                            DisplayCapability>({})),
                                 Return(Error::NONE)));
     }
 
     template <typename Case>
     static void setupCommonCompositionCallExpectations(CompositionTest* test) {
-        EXPECT_CALL(*test->mComposer,
-                    setColorTransform(HWC_DISPLAY, _, Hwc2::ColorTransform::IDENTITY))
-                .Times(1);
+        EXPECT_CALL(*test->mComposer, setColorTransform(HWC_DISPLAY, IsIdentityMatrix())).Times(1);
         EXPECT_CALL(*test->mComposer, getDisplayRequests(HWC_DISPLAY, _, _, _)).Times(1);
         EXPECT_CALL(*test->mComposer, acceptDisplayChanges(HWC_DISPLAY)).Times(1);
         EXPECT_CALL(*test->mComposer, presentDisplay(HWC_DISPLAY, _)).Times(1);
@@ -339,16 +348,18 @@
     template <typename Case>
     static void setupCommonScreensCaptureCallExpectations(CompositionTest* test) {
         EXPECT_CALL(*test->mRenderEngine, drawLayers)
-                .WillRepeatedly([](const renderengine::DisplaySettings& displaySettings,
-                                   const std::vector<const renderengine::LayerSettings*>&,
-                                   const std::shared_ptr<renderengine::ExternalTexture>&,
-                                   const bool, base::unique_fd&&, base::unique_fd*) -> status_t {
+                .WillRepeatedly([&](const renderengine::DisplaySettings& displaySettings,
+                                    const std::vector<renderengine::LayerSettings>&,
+                                    const std::shared_ptr<renderengine::ExternalTexture>&,
+                                    const bool, base::unique_fd&&)
+                                        -> std::future<renderengine::RenderEngineResult> {
                     EXPECT_EQ(DEFAULT_DISPLAY_MAX_LUMINANCE, displaySettings.maxLuminance);
                     EXPECT_EQ(Rect(DEFAULT_DISPLAY_WIDTH, DEFAULT_DISPLAY_HEIGHT),
                               displaySettings.physicalDisplay);
                     EXPECT_EQ(Rect(DEFAULT_DISPLAY_WIDTH, DEFAULT_DISPLAY_HEIGHT),
                               displaySettings.clip);
-                    return NO_ERROR;
+                    return futureOf<renderengine::RenderEngineResult>(
+                            {NO_ERROR, base::unique_fd()});
                 });
     }
 
@@ -361,24 +372,26 @@
     }
 
     static void setupHwcCompositionCallExpectations(CompositionTest* test) {
-        EXPECT_CALL(*test->mComposer, presentOrValidateDisplay(HWC_DISPLAY, _, _, _, _)).Times(1);
+        EXPECT_CALL(*test->mComposer, presentOrValidateDisplay(HWC_DISPLAY, _, _, _, _, _))
+                .Times(1);
 
         EXPECT_CALL(*test->mDisplaySurface,
-                    prepareFrame(compositionengine::DisplaySurface::COMPOSITION_HWC))
+                    prepareFrame(compositionengine::DisplaySurface::CompositionType::Hwc))
                 .Times(1);
     }
 
     static void setupHwcClientCompositionCallExpectations(CompositionTest* test) {
-        EXPECT_CALL(*test->mComposer, presentOrValidateDisplay(HWC_DISPLAY, _, _, _, _)).Times(1);
+        EXPECT_CALL(*test->mComposer, presentOrValidateDisplay(HWC_DISPLAY, _, _, _, _, _))
+                .Times(1);
     }
 
     static void setupHwcForcedClientCompositionCallExpectations(CompositionTest* test) {
-        EXPECT_CALL(*test->mComposer, validateDisplay(HWC_DISPLAY, _, _)).Times(1);
+        EXPECT_CALL(*test->mComposer, validateDisplay(HWC_DISPLAY, _, _, _)).Times(1);
     }
 
     static void setupRECompositionCallExpectations(CompositionTest* test) {
         EXPECT_CALL(*test->mDisplaySurface,
-                    prepareFrame(compositionengine::DisplaySurface::COMPOSITION_GPU))
+                    prepareFrame(compositionengine::DisplaySurface::CompositionType::Gpu))
                 .Times(1);
         EXPECT_CALL(*test->mDisplaySurface, getClientTargetAcquireFence())
                 .WillRepeatedly(ReturnRef(test->mClientTargetAcquireFence));
@@ -388,17 +401,19 @@
                 .WillOnce(DoAll(SetArgPointee<0>(test->mNativeWindowBuffer), SetArgPointee<1>(-1),
                                 Return(0)));
         EXPECT_CALL(*test->mRenderEngine, drawLayers)
-                .WillRepeatedly([](const renderengine::DisplaySettings& displaySettings,
-                                   const std::vector<const renderengine::LayerSettings*>&,
-                                   const std::shared_ptr<renderengine::ExternalTexture>&,
-                                   const bool, base::unique_fd&&, base::unique_fd*) -> status_t {
+                .WillRepeatedly([&](const renderengine::DisplaySettings& displaySettings,
+                                    const std::vector<renderengine::LayerSettings>&,
+                                    const std::shared_ptr<renderengine::ExternalTexture>&,
+                                    const bool, base::unique_fd&&)
+                                        -> std::future<renderengine::RenderEngineResult> {
                     EXPECT_EQ(DEFAULT_DISPLAY_MAX_LUMINANCE, displaySettings.maxLuminance);
                     EXPECT_EQ(Rect(DEFAULT_DISPLAY_WIDTH, DEFAULT_DISPLAY_HEIGHT),
                               displaySettings.physicalDisplay);
                     EXPECT_EQ(Rect(DEFAULT_DISPLAY_WIDTH, DEFAULT_DISPLAY_HEIGHT),
                               displaySettings.clip);
                     EXPECT_EQ(ui::Dataspace::UNKNOWN, displaySettings.outputDataspace);
-                    return NO_ERROR;
+                    return futureOf<renderengine::RenderEngineResult>(
+                            {NO_ERROR, base::unique_fd()});
                 });
     }
 
@@ -438,9 +453,7 @@
     template <typename Case>
     static void setupCommonCompositionCallExpectations(CompositionTest* test) {
         // TODO: This seems like an unnecessary call if display is powered off.
-        EXPECT_CALL(*test->mComposer,
-                    setColorTransform(HWC_DISPLAY, _, Hwc2::ColorTransform::IDENTITY))
-                .Times(1);
+        EXPECT_CALL(*test->mComposer, setColorTransform(HWC_DISPLAY, IsIdentityMatrix())).Times(1);
 
         // TODO: This seems like an unnecessary call if display is powered off.
         Case::CompositionType::setupHwcSetCallExpectations(test);
@@ -521,15 +534,16 @@
 
     static void setupLatchedBuffer(CompositionTest* test, sp<BufferQueueLayer> layer) {
         // TODO: Eliminate the complexity of actually creating a buffer
+        layer->setSizeForTest(LayerProperties::WIDTH, LayerProperties::HEIGHT);
         status_t err =
                 layer->setDefaultBufferProperties(LayerProperties::WIDTH, LayerProperties::HEIGHT,
                                                   LayerProperties::FORMAT);
         ASSERT_EQ(NO_ERROR, err);
         Mock::VerifyAndClear(test->mRenderEngine);
 
-        EXPECT_CALL(*test->mMessageQueue, invalidate()).Times(1);
+        EXPECT_CALL(*test->mFlinger.scheduler(), scheduleFrame()).Times(1);
         enqueueBuffer(test, layer);
-        Mock::VerifyAndClearExpectations(test->mMessageQueue);
+        Mock::VerifyAndClearExpectations(test->mFlinger.scheduler());
 
         bool ignoredRecomputeVisibleRegions;
         layer->latchBuffer(ignoredRecomputeVisibleRegions, 0, 0);
@@ -607,7 +621,8 @@
             // TODO: use COLOR
             EXPECT_CALL(*test->mComposer,
                         setLayerColor(HWC_DISPLAY, HWC_LAYER,
-                                      IComposerClient::Color({0xff, 0xff, 0xff, 0xff})))
+                                      aidl::android::hardware::graphics::composer3::Color(
+                                              {1.0f, 1.0f, 1.0f, 1.0f})))
                     .Times(1);
         }
     }
@@ -622,10 +637,10 @@
 
     static void setupREBufferCompositionCommonCallExpectations(CompositionTest* test) {
         EXPECT_CALL(*test->mRenderEngine, drawLayers)
-                .WillOnce([](const renderengine::DisplaySettings& displaySettings,
-                             const std::vector<const renderengine::LayerSettings*>& layerSettings,
-                             const std::shared_ptr<renderengine::ExternalTexture>&, const bool,
-                             base::unique_fd&&, base::unique_fd*) -> status_t {
+                .WillOnce([&](const renderengine::DisplaySettings& displaySettings,
+                              const std::vector<renderengine::LayerSettings>& layerSettings,
+                              const std::shared_ptr<renderengine::ExternalTexture>&, const bool,
+                              base::unique_fd&&) -> std::future<renderengine::RenderEngineResult> {
                     EXPECT_EQ(DEFAULT_DISPLAY_MAX_LUMINANCE, displaySettings.maxLuminance);
                     EXPECT_EQ(Rect(DEFAULT_DISPLAY_WIDTH, DEFAULT_DISPLAY_HEIGHT),
                               displaySettings.physicalDisplay);
@@ -633,23 +648,26 @@
                               displaySettings.clip);
                     // screen capture adds an additional color layer as an alpha
                     // prefill, so gtet the back layer.
+                    std::future<renderengine::RenderEngineResult> resultFuture =
+                            futureOf<renderengine::RenderEngineResult>(
+                                    {NO_ERROR, base::unique_fd()});
                     if (layerSettings.empty()) {
                         ADD_FAILURE() << "layerSettings was not expected to be empty in "
                                          "setupREBufferCompositionCommonCallExpectations "
                                          "verification lambda";
-                        return NO_ERROR;
+                        return resultFuture;
                     }
-                    const renderengine::LayerSettings* layer = layerSettings.back();
-                    EXPECT_THAT(layer->source.buffer.buffer, Not(IsNull()));
-                    EXPECT_THAT(layer->source.buffer.fence, Not(IsNull()));
-                    EXPECT_EQ(DEFAULT_TEXTURE_ID, layer->source.buffer.textureName);
-                    EXPECT_EQ(false, layer->source.buffer.isY410BT2020);
-                    EXPECT_EQ(true, layer->source.buffer.usePremultipliedAlpha);
-                    EXPECT_EQ(false, layer->source.buffer.isOpaque);
-                    EXPECT_EQ(0.0, layer->geometry.roundedCornersRadius);
-                    EXPECT_EQ(ui::Dataspace::UNKNOWN, layer->sourceDataspace);
-                    EXPECT_EQ(LayerProperties::COLOR[3], layer->alpha);
-                    return NO_ERROR;
+                    const renderengine::LayerSettings layer = layerSettings.back();
+                    EXPECT_THAT(layer.source.buffer.buffer, Not(IsNull()));
+                    EXPECT_THAT(layer.source.buffer.fence, Not(IsNull()));
+                    EXPECT_EQ(DEFAULT_TEXTURE_ID, layer.source.buffer.textureName);
+                    EXPECT_EQ(false, layer.source.buffer.isY410BT2020);
+                    EXPECT_EQ(true, layer.source.buffer.usePremultipliedAlpha);
+                    EXPECT_EQ(false, layer.source.buffer.isOpaque);
+                    EXPECT_EQ(0.0, layer.geometry.roundedCornersRadius);
+                    EXPECT_EQ(ui::Dataspace::UNKNOWN, layer.sourceDataspace);
+                    EXPECT_EQ(LayerProperties::COLOR[3], layer.alpha);
+                    return resultFuture;
                 });
     }
 
@@ -671,10 +689,10 @@
 
     static void setupREColorCompositionCallExpectations(CompositionTest* test) {
         EXPECT_CALL(*test->mRenderEngine, drawLayers)
-                .WillOnce([](const renderengine::DisplaySettings& displaySettings,
-                             const std::vector<const renderengine::LayerSettings*>& layerSettings,
-                             const std::shared_ptr<renderengine::ExternalTexture>&, const bool,
-                             base::unique_fd&&, base::unique_fd*) -> status_t {
+                .WillOnce([&](const renderengine::DisplaySettings& displaySettings,
+                              const std::vector<renderengine::LayerSettings>& layerSettings,
+                              const std::shared_ptr<renderengine::ExternalTexture>&, const bool,
+                              base::unique_fd&&) -> std::future<renderengine::RenderEngineResult> {
                     EXPECT_EQ(DEFAULT_DISPLAY_MAX_LUMINANCE, displaySettings.maxLuminance);
                     EXPECT_EQ(Rect(DEFAULT_DISPLAY_WIDTH, DEFAULT_DISPLAY_HEIGHT),
                               displaySettings.physicalDisplay);
@@ -682,21 +700,24 @@
                               displaySettings.clip);
                     // screen capture adds an additional color layer as an alpha
                     // prefill, so get the back layer.
+                    std::future<renderengine::RenderEngineResult> resultFuture =
+                            futureOf<renderengine::RenderEngineResult>(
+                                    {NO_ERROR, base::unique_fd()});
                     if (layerSettings.empty()) {
                         ADD_FAILURE()
                                 << "layerSettings was not expected to be empty in "
                                    "setupREColorCompositionCallExpectations verification lambda";
-                        return NO_ERROR;
+                        return resultFuture;
                     }
-                    const renderengine::LayerSettings* layer = layerSettings.back();
-                    EXPECT_THAT(layer->source.buffer.buffer, IsNull());
+                    const renderengine::LayerSettings layer = layerSettings.back();
+                    EXPECT_THAT(layer.source.buffer.buffer, IsNull());
                     EXPECT_EQ(half3(LayerProperties::COLOR[0], LayerProperties::COLOR[1],
                                     LayerProperties::COLOR[2]),
-                              layer->source.solidColor);
-                    EXPECT_EQ(0.0, layer->geometry.roundedCornersRadius);
-                    EXPECT_EQ(ui::Dataspace::UNKNOWN, layer->sourceDataspace);
-                    EXPECT_EQ(LayerProperties::COLOR[3], layer->alpha);
-                    return NO_ERROR;
+                              layer.source.solidColor);
+                    EXPECT_EQ(0.0, layer.geometry.roundedCornersRadius);
+                    EXPECT_EQ(ui::Dataspace::UNKNOWN, layer.sourceDataspace);
+                    EXPECT_EQ(LayerProperties::COLOR[3], layer.alpha);
+                    return resultFuture;
                 });
     }
 
@@ -748,10 +769,10 @@
 
     static void setupInsecureREBufferCompositionCommonCallExpectations(CompositionTest* test) {
         EXPECT_CALL(*test->mRenderEngine, drawLayers)
-                .WillOnce([](const renderengine::DisplaySettings& displaySettings,
-                             const std::vector<const renderengine::LayerSettings*>& layerSettings,
-                             const std::shared_ptr<renderengine::ExternalTexture>&, const bool,
-                             base::unique_fd&&, base::unique_fd*) -> status_t {
+                .WillOnce([&](const renderengine::DisplaySettings& displaySettings,
+                              const std::vector<renderengine::LayerSettings>& layerSettings,
+                              const std::shared_ptr<renderengine::ExternalTexture>&, const bool,
+                              base::unique_fd&&) -> std::future<renderengine::RenderEngineResult> {
                     EXPECT_EQ(DEFAULT_DISPLAY_MAX_LUMINANCE, displaySettings.maxLuminance);
                     EXPECT_EQ(Rect(DEFAULT_DISPLAY_WIDTH, DEFAULT_DISPLAY_HEIGHT),
                               displaySettings.physicalDisplay);
@@ -759,19 +780,22 @@
                               displaySettings.clip);
                     // screen capture adds an additional color layer as an alpha
                     // prefill, so get the back layer.
+                    std::future<renderengine::RenderEngineResult> resultFuture =
+                            futureOf<renderengine::RenderEngineResult>(
+                                    {NO_ERROR, base::unique_fd()});
                     if (layerSettings.empty()) {
                         ADD_FAILURE() << "layerSettings was not expected to be empty in "
                                          "setupInsecureREBufferCompositionCommonCallExpectations "
                                          "verification lambda";
-                        return NO_ERROR;
+                        return resultFuture;
                     }
-                    const renderengine::LayerSettings* layer = layerSettings.back();
-                    EXPECT_THAT(layer->source.buffer.buffer, IsNull());
-                    EXPECT_EQ(half3(0.0f, 0.0f, 0.0f), layer->source.solidColor);
-                    EXPECT_EQ(0.0, layer->geometry.roundedCornersRadius);
-                    EXPECT_EQ(ui::Dataspace::UNKNOWN, layer->sourceDataspace);
-                    EXPECT_EQ(1.0f, layer->alpha);
-                    return NO_ERROR;
+                    const renderengine::LayerSettings layer = layerSettings.back();
+                    EXPECT_THAT(layer.source.buffer.buffer, IsNull());
+                    EXPECT_EQ(half3(0.0f, 0.0f, 0.0f), layer.source.solidColor);
+                    EXPECT_EQ(0.0, layer.geometry.roundedCornersRadius);
+                    EXPECT_EQ(ui::Dataspace::UNKNOWN, layer.sourceDataspace);
+                    EXPECT_EQ(1.0f, layer.alpha);
+                    return resultFuture;
                 });
     }
 
@@ -815,16 +839,16 @@
 struct BaseLayerVariant {
     template <typename L, typename F>
     static sp<L> createLayerWithFactory(CompositionTest* test, F factory) {
-        EXPECT_CALL(*test->mMessageQueue, postMessage(_)).Times(0);
+        EXPECT_CALL(*test->mFlinger.scheduler(), postMessage(_)).Times(0);
 
         sp<L> layer = factory();
 
         // Layer should be registered with scheduler.
-        EXPECT_EQ(1, test->mFlinger.scheduler()->layerHistorySize());
+        EXPECT_EQ(1u, test->mFlinger.scheduler()->layerHistorySize());
 
         Mock::VerifyAndClear(test->mComposer);
         Mock::VerifyAndClear(test->mRenderEngine);
-        Mock::VerifyAndClearExpectations(test->mMessageQueue);
+        Mock::VerifyAndClearExpectations(test->mFlinger.scheduler());
 
         initLayerDrawingStateAndComputeBounds(test, layer);
 
@@ -834,7 +858,7 @@
     template <typename L>
     static void initLayerDrawingStateAndComputeBounds(CompositionTest* test, sp<L> layer) {
         auto& layerDrawingState = test->mFlinger.mutableLayerDrawingState(layer);
-        layerDrawingState.layerStack = DEFAULT_LAYER_STACK;
+        layerDrawingState.layerStack = LAYER_STACK;
         layerDrawingState.width = 100;
         layerDrawingState.height = 100;
         layerDrawingState.color = half4(LayerProperties::COLOR[0], LayerProperties::COLOR[1],
@@ -864,8 +888,8 @@
         test->mFlinger.mutableDrawingState().layersSortedByZ.clear();
 
         // Layer should be unregistered with scheduler.
-        test->mFlinger.onMessageReceived(MessageQueue::INVALIDATE);
-        EXPECT_EQ(0, test->mFlinger.scheduler()->layerHistorySize());
+        test->mFlinger.commit();
+        EXPECT_EQ(0u, test->mFlinger.scheduler()->layerHistorySize());
     }
 };
 
@@ -878,7 +902,6 @@
         FlingerLayerType layer = Base::template createLayerWithFactory<EffectLayer>(test, [test]() {
             return new EffectLayer(
                     LayerCreationArgs(test->mFlinger.flinger(), sp<Client>(), "test-layer",
-                                      LayerProperties::WIDTH, LayerProperties::HEIGHT,
                                       LayerProperties::LAYER_FLAGS, LayerMetadata()));
         });
 
@@ -917,7 +940,6 @@
         FlingerLayerType layer =
                 Base::template createLayerWithFactory<BufferQueueLayer>(test, [test]() {
                     LayerCreationArgs args(test->mFlinger.flinger(), sp<Client>(), "test-layer",
-                                           LayerProperties::WIDTH, LayerProperties::HEIGHT,
                                            LayerProperties::LAYER_FLAGS, LayerMetadata());
                     args.textureName = test->mFlinger.mutableTexturePool().back();
                     return new BufferQueueLayer(args);
@@ -929,7 +951,6 @@
     }
 
     static void cleanupInjectedLayers(CompositionTest* test) {
-        EXPECT_CALL(*test->mMessageQueue, postMessage(_)).Times(1);
         Base::cleanupInjectedLayers(test);
     }
 
@@ -967,7 +988,6 @@
 
     static FlingerLayerType createLayer(CompositionTest* test) {
         LayerCreationArgs args(test->mFlinger.flinger(), sp<Client>(), "test-container-layer",
-                               LayerProperties::WIDTH, LayerProperties::HEIGHT,
                                LayerProperties::LAYER_FLAGS, LayerMetadata());
         FlingerLayerType layer = new ContainerLayer(args);
         Base::template initLayerDrawingStateAndComputeBounds(test, layer);
@@ -1015,9 +1035,10 @@
     }
 };
 
-template <IComposerClient::Composition CompositionType>
+template <aidl::android::hardware::graphics::composer3::Composition CompositionType>
 struct KeepCompositionTypeVariant {
-    static constexpr hal::Composition TYPE = CompositionType;
+    static constexpr aidl::android::hardware::graphics::composer3::Composition TYPE =
+            CompositionType;
 
     static void setupHwcSetCallExpectations(CompositionTest* test) {
         if (!test->mDisplayOff) {
@@ -1032,10 +1053,11 @@
     }
 };
 
-template <IComposerClient::Composition InitialCompositionType,
-          IComposerClient::Composition FinalCompositionType>
+template <aidl::android::hardware::graphics::composer3::Composition InitialCompositionType,
+          aidl::android::hardware::graphics::composer3::Composition FinalCompositionType>
 struct ChangeCompositionTypeVariant {
-    static constexpr hal::Composition TYPE = FinalCompositionType;
+    static constexpr aidl::android::hardware::graphics::composer3::Composition TYPE =
+            FinalCompositionType;
 
     static void setupHwcSetCallExpectations(CompositionTest* test) {
         if (!test->mDisplayOff) {
@@ -1049,8 +1071,9 @@
         EXPECT_CALL(*test->mComposer, getChangedCompositionTypes(HWC_DISPLAY, _, _))
                 .WillOnce(DoAll(SetArgPointee<1>(std::vector<Hwc2::Layer>{
                                         static_cast<Hwc2::Layer>(HWC_LAYER)}),
-                                SetArgPointee<2>(std::vector<IComposerClient::Composition>{
-                                        FinalCompositionType}),
+                                SetArgPointee<2>(
+                                        std::vector<aidl::android::hardware::graphics::composer3::
+                                                            Composition>{FinalCompositionType}),
                                 Return(Error::NONE)));
     }
 };
@@ -1244,25 +1267,28 @@
  */
 
 TEST_F(CompositionTest, HWCComposedNormalBufferLayerWithDirtyGeometry) {
-    displayRefreshCompositionDirtyGeometry<
-            CompositionCase<DefaultDisplaySetupVariant, BufferLayerVariant<DefaultLayerProperties>,
-                            KeepCompositionTypeVariant<IComposerClient::Composition::DEVICE>,
-                            HwcCompositionResultVariant>>();
+    displayRefreshCompositionDirtyGeometry<CompositionCase<
+            DefaultDisplaySetupVariant, BufferLayerVariant<DefaultLayerProperties>,
+            KeepCompositionTypeVariant<
+                    aidl::android::hardware::graphics::composer3::Composition::DEVICE>,
+            HwcCompositionResultVariant>>();
 }
 
 TEST_F(CompositionTest, HWCComposedNormalBufferLayerWithDirtyFrame) {
-    displayRefreshCompositionDirtyFrame<
-            CompositionCase<DefaultDisplaySetupVariant, BufferLayerVariant<DefaultLayerProperties>,
-                            KeepCompositionTypeVariant<IComposerClient::Composition::DEVICE>,
-                            HwcCompositionResultVariant>>();
+    displayRefreshCompositionDirtyFrame<CompositionCase<
+            DefaultDisplaySetupVariant, BufferLayerVariant<DefaultLayerProperties>,
+            KeepCompositionTypeVariant<
+                    aidl::android::hardware::graphics::composer3::Composition::DEVICE>,
+            HwcCompositionResultVariant>>();
 }
 
 TEST_F(CompositionTest, REComposedNormalBufferLayer) {
-    displayRefreshCompositionDirtyFrame<
-            CompositionCase<DefaultDisplaySetupVariant, BufferLayerVariant<DefaultLayerProperties>,
-                            ChangeCompositionTypeVariant<IComposerClient::Composition::DEVICE,
-                                                         IComposerClient::Composition::CLIENT>,
-                            RECompositionResultVariant>>();
+    displayRefreshCompositionDirtyFrame<CompositionCase<
+            DefaultDisplaySetupVariant, BufferLayerVariant<DefaultLayerProperties>,
+            ChangeCompositionTypeVariant<
+                    aidl::android::hardware::graphics::composer3::Composition::DEVICE,
+                    aidl::android::hardware::graphics::composer3::Composition::CLIENT>,
+            RECompositionResultVariant>>();
 }
 
 TEST_F(CompositionTest, captureScreenNormalBufferLayer) {
@@ -1276,25 +1302,28 @@
  */
 
 TEST_F(CompositionTest, HWCComposedEffectLayerWithDirtyGeometry) {
-    displayRefreshCompositionDirtyGeometry<
-            CompositionCase<DefaultDisplaySetupVariant, EffectLayerVariant<EffectLayerProperties>,
-                            KeepCompositionTypeVariant<IComposerClient::Composition::SOLID_COLOR>,
-                            HwcCompositionResultVariant>>();
+    displayRefreshCompositionDirtyGeometry<CompositionCase<
+            DefaultDisplaySetupVariant, EffectLayerVariant<EffectLayerProperties>,
+            KeepCompositionTypeVariant<
+                    aidl::android::hardware::graphics::composer3::Composition::SOLID_COLOR>,
+            HwcCompositionResultVariant>>();
 }
 
 TEST_F(CompositionTest, HWCComposedEffectLayerWithDirtyFrame) {
-    displayRefreshCompositionDirtyFrame<
-            CompositionCase<DefaultDisplaySetupVariant, EffectLayerVariant<EffectLayerProperties>,
-                            KeepCompositionTypeVariant<IComposerClient::Composition::SOLID_COLOR>,
-                            HwcCompositionResultVariant>>();
+    displayRefreshCompositionDirtyFrame<CompositionCase<
+            DefaultDisplaySetupVariant, EffectLayerVariant<EffectLayerProperties>,
+            KeepCompositionTypeVariant<
+                    aidl::android::hardware::graphics::composer3::Composition::SOLID_COLOR>,
+            HwcCompositionResultVariant>>();
 }
 
 TEST_F(CompositionTest, REComposedEffectLayer) {
-    displayRefreshCompositionDirtyFrame<
-            CompositionCase<DefaultDisplaySetupVariant, EffectLayerVariant<EffectLayerProperties>,
-                            ChangeCompositionTypeVariant<IComposerClient::Composition::SOLID_COLOR,
-                                                         IComposerClient::Composition::CLIENT>,
-                            RECompositionResultVariant>>();
+    displayRefreshCompositionDirtyFrame<CompositionCase<
+            DefaultDisplaySetupVariant, EffectLayerVariant<EffectLayerProperties>,
+            ChangeCompositionTypeVariant<
+                    aidl::android::hardware::graphics::composer3::Composition::SOLID_COLOR,
+                    aidl::android::hardware::graphics::composer3::Composition::CLIENT>,
+            RECompositionResultVariant>>();
 }
 
 TEST_F(CompositionTest, captureScreenEffectLayer) {
@@ -1308,25 +1337,28 @@
  */
 
 TEST_F(CompositionTest, HWCComposedSidebandBufferLayerWithDirtyGeometry) {
-    displayRefreshCompositionDirtyGeometry<
-            CompositionCase<DefaultDisplaySetupVariant, BufferLayerVariant<SidebandLayerProperties>,
-                            KeepCompositionTypeVariant<IComposerClient::Composition::SIDEBAND>,
-                            HwcCompositionResultVariant>>();
+    displayRefreshCompositionDirtyGeometry<CompositionCase<
+            DefaultDisplaySetupVariant, BufferLayerVariant<SidebandLayerProperties>,
+            KeepCompositionTypeVariant<
+                    aidl::android::hardware::graphics::composer3::Composition::SIDEBAND>,
+            HwcCompositionResultVariant>>();
 }
 
 TEST_F(CompositionTest, HWCComposedSidebandBufferLayerWithDirtyFrame) {
-    displayRefreshCompositionDirtyFrame<
-            CompositionCase<DefaultDisplaySetupVariant, BufferLayerVariant<SidebandLayerProperties>,
-                            KeepCompositionTypeVariant<IComposerClient::Composition::SIDEBAND>,
-                            HwcCompositionResultVariant>>();
+    displayRefreshCompositionDirtyFrame<CompositionCase<
+            DefaultDisplaySetupVariant, BufferLayerVariant<SidebandLayerProperties>,
+            KeepCompositionTypeVariant<
+                    aidl::android::hardware::graphics::composer3::Composition::SIDEBAND>,
+            HwcCompositionResultVariant>>();
 }
 
 TEST_F(CompositionTest, REComposedSidebandBufferLayer) {
-    displayRefreshCompositionDirtyFrame<
-            CompositionCase<DefaultDisplaySetupVariant, BufferLayerVariant<SidebandLayerProperties>,
-                            ChangeCompositionTypeVariant<IComposerClient::Composition::SIDEBAND,
-                                                         IComposerClient::Composition::CLIENT>,
-                            RECompositionResultVariant>>();
+    displayRefreshCompositionDirtyFrame<CompositionCase<
+            DefaultDisplaySetupVariant, BufferLayerVariant<SidebandLayerProperties>,
+            ChangeCompositionTypeVariant<
+                    aidl::android::hardware::graphics::composer3::Composition::SIDEBAND,
+                    aidl::android::hardware::graphics::composer3::Composition::CLIENT>,
+            RECompositionResultVariant>>();
 }
 
 TEST_F(CompositionTest, captureScreenSidebandBufferLayer) {
@@ -1340,25 +1372,28 @@
  */
 
 TEST_F(CompositionTest, HWCComposedSecureBufferLayerWithDirtyGeometry) {
-    displayRefreshCompositionDirtyGeometry<
-            CompositionCase<DefaultDisplaySetupVariant, BufferLayerVariant<SecureLayerProperties>,
-                            KeepCompositionTypeVariant<IComposerClient::Composition::DEVICE>,
-                            HwcCompositionResultVariant>>();
+    displayRefreshCompositionDirtyGeometry<CompositionCase<
+            DefaultDisplaySetupVariant, BufferLayerVariant<SecureLayerProperties>,
+            KeepCompositionTypeVariant<
+                    aidl::android::hardware::graphics::composer3::Composition::DEVICE>,
+            HwcCompositionResultVariant>>();
 }
 
 TEST_F(CompositionTest, HWCComposedSecureBufferLayerWithDirtyFrame) {
-    displayRefreshCompositionDirtyFrame<
-            CompositionCase<DefaultDisplaySetupVariant, BufferLayerVariant<SecureLayerProperties>,
-                            KeepCompositionTypeVariant<IComposerClient::Composition::DEVICE>,
-                            HwcCompositionResultVariant>>();
+    displayRefreshCompositionDirtyFrame<CompositionCase<
+            DefaultDisplaySetupVariant, BufferLayerVariant<SecureLayerProperties>,
+            KeepCompositionTypeVariant<
+                    aidl::android::hardware::graphics::composer3::Composition::DEVICE>,
+            HwcCompositionResultVariant>>();
 }
 
 TEST_F(CompositionTest, REComposedSecureBufferLayer) {
-    displayRefreshCompositionDirtyFrame<
-            CompositionCase<DefaultDisplaySetupVariant, BufferLayerVariant<SecureLayerProperties>,
-                            ChangeCompositionTypeVariant<IComposerClient::Composition::DEVICE,
-                                                         IComposerClient::Composition::CLIENT>,
-                            RECompositionResultVariant>>();
+    displayRefreshCompositionDirtyFrame<CompositionCase<
+            DefaultDisplaySetupVariant, BufferLayerVariant<SecureLayerProperties>,
+            ChangeCompositionTypeVariant<
+                    aidl::android::hardware::graphics::composer3::Composition::DEVICE,
+                    aidl::android::hardware::graphics::composer3::Composition::CLIENT>,
+            RECompositionResultVariant>>();
 }
 
 TEST_F(CompositionTest, captureScreenSecureBufferLayerOnSecureDisplay) {
@@ -1372,17 +1407,19 @@
  */
 
 TEST_F(CompositionTest, HWCComposedSecureBufferLayerOnInsecureDisplayWithDirtyGeometry) {
-    displayRefreshCompositionDirtyGeometry<
-            CompositionCase<InsecureDisplaySetupVariant, BufferLayerVariant<SecureLayerProperties>,
-                            KeepCompositionTypeVariant<IComposerClient::Composition::CLIENT>,
-                            ForcedClientCompositionResultVariant>>();
+    displayRefreshCompositionDirtyGeometry<CompositionCase<
+            InsecureDisplaySetupVariant, BufferLayerVariant<SecureLayerProperties>,
+            KeepCompositionTypeVariant<
+                    aidl::android::hardware::graphics::composer3::Composition::CLIENT>,
+            ForcedClientCompositionResultVariant>>();
 }
 
 TEST_F(CompositionTest, HWCComposedSecureBufferLayerOnInsecureDisplayWithDirtyFrame) {
-    displayRefreshCompositionDirtyFrame<
-            CompositionCase<InsecureDisplaySetupVariant, BufferLayerVariant<SecureLayerProperties>,
-                            KeepCompositionTypeVariant<IComposerClient::Composition::CLIENT>,
-                            ForcedClientCompositionResultVariant>>();
+    displayRefreshCompositionDirtyFrame<CompositionCase<
+            InsecureDisplaySetupVariant, BufferLayerVariant<SecureLayerProperties>,
+            KeepCompositionTypeVariant<
+                    aidl::android::hardware::graphics::composer3::Composition::CLIENT>,
+            ForcedClientCompositionResultVariant>>();
 }
 
 TEST_F(CompositionTest, captureScreenSecureBufferLayerOnInsecureDisplay) {
@@ -1397,22 +1434,24 @@
 
 TEST_F(CompositionTest,
        HWCComposedBufferLayerWithSecureParentLayerOnInsecureDisplayWithDirtyGeometry) {
-    displayRefreshCompositionDirtyGeometry<
-            CompositionCase<InsecureDisplaySetupVariant,
-                            ChildLayerVariant<BufferLayerVariant<ParentSecureLayerProperties>,
-                                              ContainerLayerVariant<SecureLayerProperties>>,
-                            KeepCompositionTypeVariant<IComposerClient::Composition::CLIENT>,
-                            ForcedClientCompositionResultVariant>>();
+    displayRefreshCompositionDirtyGeometry<CompositionCase<
+            InsecureDisplaySetupVariant,
+            ChildLayerVariant<BufferLayerVariant<ParentSecureLayerProperties>,
+                              ContainerLayerVariant<SecureLayerProperties>>,
+            KeepCompositionTypeVariant<
+                    aidl::android::hardware::graphics::composer3::Composition::CLIENT>,
+            ForcedClientCompositionResultVariant>>();
 }
 
 TEST_F(CompositionTest,
        HWCComposedBufferLayerWithSecureParentLayerOnInsecureDisplayWithDirtyFrame) {
-    displayRefreshCompositionDirtyFrame<
-            CompositionCase<InsecureDisplaySetupVariant,
-                            ChildLayerVariant<BufferLayerVariant<ParentSecureLayerProperties>,
-                                              ContainerLayerVariant<SecureLayerProperties>>,
-                            KeepCompositionTypeVariant<IComposerClient::Composition::CLIENT>,
-                            ForcedClientCompositionResultVariant>>();
+    displayRefreshCompositionDirtyFrame<CompositionCase<
+            InsecureDisplaySetupVariant,
+            ChildLayerVariant<BufferLayerVariant<ParentSecureLayerProperties>,
+                              ContainerLayerVariant<SecureLayerProperties>>,
+            KeepCompositionTypeVariant<
+                    aidl::android::hardware::graphics::composer3::Composition::CLIENT>,
+            ForcedClientCompositionResultVariant>>();
 }
 
 TEST_F(CompositionTest, captureScreenBufferLayerWithSecureParentLayerOnInsecureDisplay) {
@@ -1428,25 +1467,28 @@
  */
 
 TEST_F(CompositionTest, HWCComposedCursorLayerWithDirtyGeometry) {
-    displayRefreshCompositionDirtyGeometry<
-            CompositionCase<DefaultDisplaySetupVariant, BufferLayerVariant<CursorLayerProperties>,
-                            KeepCompositionTypeVariant<IComposerClient::Composition::CURSOR>,
-                            HwcCompositionResultVariant>>();
+    displayRefreshCompositionDirtyGeometry<CompositionCase<
+            DefaultDisplaySetupVariant, BufferLayerVariant<CursorLayerProperties>,
+            KeepCompositionTypeVariant<
+                    aidl::android::hardware::graphics::composer3::Composition::CURSOR>,
+            HwcCompositionResultVariant>>();
 }
 
 TEST_F(CompositionTest, HWCComposedCursorLayerWithDirtyFrame) {
-    displayRefreshCompositionDirtyFrame<
-            CompositionCase<DefaultDisplaySetupVariant, BufferLayerVariant<CursorLayerProperties>,
-                            KeepCompositionTypeVariant<IComposerClient::Composition::CURSOR>,
-                            HwcCompositionResultVariant>>();
+    displayRefreshCompositionDirtyFrame<CompositionCase<
+            DefaultDisplaySetupVariant, BufferLayerVariant<CursorLayerProperties>,
+            KeepCompositionTypeVariant<
+                    aidl::android::hardware::graphics::composer3::Composition::CURSOR>,
+            HwcCompositionResultVariant>>();
 }
 
 TEST_F(CompositionTest, REComposedCursorLayer) {
-    displayRefreshCompositionDirtyFrame<
-            CompositionCase<DefaultDisplaySetupVariant, BufferLayerVariant<CursorLayerProperties>,
-                            ChangeCompositionTypeVariant<IComposerClient::Composition::CURSOR,
-                                                         IComposerClient::Composition::CLIENT>,
-                            RECompositionResultVariant>>();
+    displayRefreshCompositionDirtyFrame<CompositionCase<
+            DefaultDisplaySetupVariant, BufferLayerVariant<CursorLayerProperties>,
+            ChangeCompositionTypeVariant<
+                    aidl::android::hardware::graphics::composer3::Composition::CURSOR,
+                    aidl::android::hardware::graphics::composer3::Composition::CLIENT>,
+            RECompositionResultVariant>>();
 }
 
 TEST_F(CompositionTest, captureScreenCursorLayer) {
@@ -1463,7 +1505,8 @@
     mDisplayOff = true;
     displayRefreshCompositionDirtyGeometry<CompositionCase<
             PoweredOffDisplaySetupVariant, BufferLayerVariant<DefaultLayerProperties>,
-            KeepCompositionTypeVariant<IComposerClient::Composition::DEVICE>,
+            KeepCompositionTypeVariant<
+                    aidl::android::hardware::graphics::composer3::Composition::DEVICE>,
             HwcCompositionResultVariant>>();
 }
 
@@ -1471,7 +1514,8 @@
     mDisplayOff = true;
     displayRefreshCompositionDirtyFrame<CompositionCase<
             PoweredOffDisplaySetupVariant, BufferLayerVariant<DefaultLayerProperties>,
-            KeepCompositionTypeVariant<IComposerClient::Composition::DEVICE>,
+            KeepCompositionTypeVariant<
+                    aidl::android::hardware::graphics::composer3::Composition::DEVICE>,
             HwcCompositionResultVariant>>();
 }
 
@@ -1479,8 +1523,9 @@
     mDisplayOff = true;
     displayRefreshCompositionDirtyFrame<CompositionCase<
             PoweredOffDisplaySetupVariant, BufferLayerVariant<DefaultLayerProperties>,
-            ChangeCompositionTypeVariant<IComposerClient::Composition::DEVICE,
-                                         IComposerClient::Composition::CLIENT>,
+            ChangeCompositionTypeVariant<
+                    aidl::android::hardware::graphics::composer3::Composition::DEVICE,
+                    aidl::android::hardware::graphics::composer3::Composition::CLIENT>,
             RECompositionResultVariant>>();
 }
 
@@ -1495,17 +1540,19 @@
  */
 
 TEST_F(CompositionTest, DebugOptionForcingClientCompositionOfBufferLayerWithDirtyGeometry) {
-    displayRefreshCompositionDirtyGeometry<
-            CompositionCase<DefaultDisplaySetupVariant, BufferLayerVariant<DefaultLayerProperties>,
-                            KeepCompositionTypeVariant<IComposerClient::Composition::CLIENT>,
-                            ForcedClientCompositionViaDebugOptionResultVariant>>();
+    displayRefreshCompositionDirtyGeometry<CompositionCase<
+            DefaultDisplaySetupVariant, BufferLayerVariant<DefaultLayerProperties>,
+            KeepCompositionTypeVariant<
+                    aidl::android::hardware::graphics::composer3::Composition::CLIENT>,
+            ForcedClientCompositionViaDebugOptionResultVariant>>();
 }
 
 TEST_F(CompositionTest, DebugOptionForcingClientCompositionOfBufferLayerWithDirtyFrame) {
-    displayRefreshCompositionDirtyFrame<
-            CompositionCase<DefaultDisplaySetupVariant, BufferLayerVariant<DefaultLayerProperties>,
-                            KeepCompositionTypeVariant<IComposerClient::Composition::CLIENT>,
-                            ForcedClientCompositionViaDebugOptionResultVariant>>();
+    displayRefreshCompositionDirtyFrame<CompositionCase<
+            DefaultDisplaySetupVariant, BufferLayerVariant<DefaultLayerProperties>,
+            KeepCompositionTypeVariant<
+                    aidl::android::hardware::graphics::composer3::Composition::CLIENT>,
+            ForcedClientCompositionViaDebugOptionResultVariant>>();
 }
 
 } // namespace
diff --git a/services/surfaceflinger/tests/unittests/DispSyncSourceTest.cpp b/services/surfaceflinger/tests/unittests/DispSyncSourceTest.cpp
index a9ad249..ec27eda 100644
--- a/services/surfaceflinger/tests/unittests/DispSyncSourceTest.cpp
+++ b/services/surfaceflinger/tests/unittests/DispSyncSourceTest.cpp
@@ -28,6 +28,7 @@
 #include "AsyncCallRecorder.h"
 #include "Scheduler/DispSyncSource.h"
 #include "Scheduler/VSyncDispatch.h"
+#include "mock/MockVSyncTracker.h"
 
 namespace android {
 namespace {
@@ -37,12 +38,11 @@
 
 class MockVSyncDispatch : public scheduler::VSyncDispatch {
 public:
-    MOCK_METHOD2(registerCallback,
-                 CallbackToken(std::function<void(nsecs_t, nsecs_t, nsecs_t)> const&, std::string));
-    MOCK_METHOD1(unregisterCallback, void(CallbackToken));
-    MOCK_METHOD2(schedule, scheduler::ScheduleResult(CallbackToken, ScheduleTiming));
-    MOCK_METHOD1(cancel, scheduler::CancelResult(CallbackToken token));
-    MOCK_CONST_METHOD1(dump, void(std::string&));
+    MOCK_METHOD(CallbackToken, registerCallback, (Callback, std::string), (override));
+    MOCK_METHOD(void, unregisterCallback, (CallbackToken), (override));
+    MOCK_METHOD(scheduler::ScheduleResult, schedule, (CallbackToken, ScheduleTiming), (override));
+    MOCK_METHOD(scheduler::CancelResult, cancel, (CallbackToken), (override));
+    MOCK_METHOD(void, dump, (std::string&), (const, override));
 
     MockVSyncDispatch() {
         ON_CALL(*this, registerCallback)
@@ -126,16 +126,16 @@
     DispSyncSourceTest();
     ~DispSyncSourceTest() override;
 
-    void createDispSync();
+    void SetUp() override;
     void createDispSyncSource();
 
-    void onVSyncEvent(nsecs_t when, nsecs_t expectedVSyncTimestamp,
-                      nsecs_t deadlineTimestamp) override;
+    void onVSyncEvent(nsecs_t when, VSyncSource::VSyncData) override;
 
     std::unique_ptr<MockVSyncDispatch> mVSyncDispatch;
+    std::unique_ptr<mock::VSyncTracker> mVSyncTracker;
     std::unique_ptr<scheduler::DispSyncSource> mDispSyncSource;
 
-    AsyncCallRecorder<void (*)(nsecs_t, nsecs_t, nsecs_t)> mVSyncEventCallRecorder;
+    AsyncCallRecorder<void (*)(nsecs_t, VSyncSource::VSyncData)> mVSyncEventCallRecorder;
 
     static constexpr std::chrono::nanoseconds mWorkDuration = 20ms;
     static constexpr std::chrono::nanoseconds mReadyDuration = 10ms;
@@ -156,21 +156,21 @@
     ALOGD("**** Tearing down after %s.%s\n", test_info->test_case_name(), test_info->name());
 }
 
-void DispSyncSourceTest::onVSyncEvent(nsecs_t when, nsecs_t expectedVSyncTimestamp,
-                                      nsecs_t deadlineTimestamp) {
-    ALOGD("onVSyncEvent: %" PRId64, when);
-
-    mVSyncEventCallRecorder.recordCall(when, expectedVSyncTimestamp, deadlineTimestamp);
+void DispSyncSourceTest::SetUp() {
+    mVSyncDispatch = std::make_unique<MockVSyncDispatch>();
+    mVSyncTracker = std::make_unique<mock::VSyncTracker>();
 }
 
-void DispSyncSourceTest::createDispSync() {
-    mVSyncDispatch = std::make_unique<MockVSyncDispatch>();
+void DispSyncSourceTest::onVSyncEvent(nsecs_t when, VSyncSource::VSyncData vsyncData) {
+    ALOGD("onVSyncEvent: %" PRId64, when);
+
+    mVSyncEventCallRecorder.recordCall(when, vsyncData);
 }
 
 void DispSyncSourceTest::createDispSyncSource() {
-    mDispSyncSource =
-            std::make_unique<scheduler::DispSyncSource>(*mVSyncDispatch, mWorkDuration,
-                                                        mReadyDuration, true, mName.c_str());
+    mDispSyncSource = std::make_unique<scheduler::DispSyncSource>(*mVSyncDispatch, *mVSyncTracker,
+                                                                  mWorkDuration, mReadyDuration,
+                                                                  true, mName.c_str());
     mDispSyncSource->setCallback(this);
 }
 
@@ -179,13 +179,10 @@
  */
 
 TEST_F(DispSyncSourceTest, createDispSync) {
-    createDispSync();
     EXPECT_TRUE(mVSyncDispatch);
 }
 
 TEST_F(DispSyncSourceTest, createDispSyncSource) {
-    createDispSync();
-
     InSequence seq;
     EXPECT_CALL(*mVSyncDispatch, registerCallback(_, mName)).WillOnce(Return(mFakeToken));
     EXPECT_CALL(*mVSyncDispatch, cancel(mFakeToken))
@@ -197,8 +194,6 @@
 }
 
 TEST_F(DispSyncSourceTest, noCallbackAfterInit) {
-    createDispSync();
-
     InSequence seq;
     EXPECT_CALL(*mVSyncDispatch, registerCallback(_, mName)).Times(1);
     EXPECT_CALL(*mVSyncDispatch, cancel(_)).Times(1);
@@ -213,8 +208,6 @@
 }
 
 TEST_F(DispSyncSourceTest, waitForCallbacks) {
-    createDispSync();
-
     InSequence seq;
     EXPECT_CALL(*mVSyncDispatch, registerCallback(_, mName)).Times(1);
     EXPECT_CALL(*mVSyncDispatch,
@@ -234,14 +227,14 @@
         mVSyncDispatch->triggerCallbacks();
         const auto callbackData = mVSyncEventCallRecorder.waitForCall();
         ASSERT_TRUE(callbackData.has_value());
-        const auto [when, expectedVSyncTimestamp, deadlineTimestamp] = callbackData.value();
-        EXPECT_EQ(when, expectedVSyncTimestamp - mWorkDuration.count() - mReadyDuration.count());
+        const auto [when, vsyncData] = callbackData.value();
+        EXPECT_EQ(when,
+                  vsyncData.expectedPresentationTime - mWorkDuration.count() -
+                          mReadyDuration.count());
     }
 }
 
 TEST_F(DispSyncSourceTest, waitForCallbacksWithDurationChange) {
-    createDispSync();
-
     InSequence seq;
     EXPECT_CALL(*mVSyncDispatch, registerCallback(_, mName)).Times(1);
     EXPECT_CALL(*mVSyncDispatch,
@@ -266,8 +259,10 @@
         mVSyncDispatch->triggerCallbacks();
         const auto callbackData = mVSyncEventCallRecorder.waitForCall();
         ASSERT_TRUE(callbackData.has_value());
-        const auto [when, expectedVSyncTimestamp, deadlineTimestamp] = callbackData.value();
-        EXPECT_EQ(when, expectedVSyncTimestamp - mWorkDuration.count() - mReadyDuration.count());
+        const auto [when, vsyncData] = callbackData.value();
+        EXPECT_EQ(when,
+                  vsyncData.expectedPresentationTime - mWorkDuration.count() -
+                          mReadyDuration.count());
     }
 
     const auto newDuration = mWorkDuration / 2;
@@ -287,13 +282,35 @@
         mVSyncDispatch->triggerCallbacks();
         const auto callbackData = mVSyncEventCallRecorder.waitForCall();
         ASSERT_TRUE(callbackData.has_value());
-        const auto [when, expectedVSyncTimestamp, deadlineTimestamp] = callbackData.value();
-        EXPECT_EQ(when, expectedVSyncTimestamp - newDuration.count());
+        const auto [when, vsyncData] = callbackData.value();
+        EXPECT_EQ(when, vsyncData.expectedPresentationTime - newDuration.count());
     }
 
     EXPECT_CALL(*mVSyncDispatch, cancel(_)).Times(1);
     EXPECT_CALL(*mVSyncDispatch, unregisterCallback(_)).Times(1);
 }
 
+TEST_F(DispSyncSourceTest, getLatestVsyncData) {
+    const nsecs_t now = systemTime();
+    const nsecs_t vsyncInternalDuration = mWorkDuration.count() + mReadyDuration.count();
+    EXPECT_CALL(*mVSyncTracker, nextAnticipatedVSyncTimeFrom(_))
+            .WillOnce(Return(now + vsyncInternalDuration + 1));
+    {
+        InSequence seq;
+        EXPECT_CALL(*mVSyncDispatch, registerCallback(_, mName)).Times(1);
+        EXPECT_CALL(*mVSyncDispatch, cancel(_)).Times(1);
+        EXPECT_CALL(*mVSyncDispatch, unregisterCallback(_)).Times(1);
+    }
+
+    createDispSyncSource();
+    EXPECT_TRUE(mDispSyncSource);
+
+    const auto vsyncData = mDispSyncSource->getLatestVSyncData();
+    ASSERT_GT(vsyncData.deadlineTimestamp, now);
+    ASSERT_GT(vsyncData.expectedPresentationTime, vsyncData.deadlineTimestamp);
+    EXPECT_EQ(vsyncData.deadlineTimestamp,
+              vsyncData.expectedPresentationTime - vsyncInternalDuration);
+}
+
 } // namespace
 } // namespace android
diff --git a/services/surfaceflinger/tests/unittests/DisplayDevice_InitiateModeChange.cpp b/services/surfaceflinger/tests/unittests/DisplayDevice_InitiateModeChange.cpp
index d4cfbbb..93af225 100644
--- a/services/surfaceflinger/tests/unittests/DisplayDevice_InitiateModeChange.cpp
+++ b/services/surfaceflinger/tests/unittests/DisplayDevice_InitiateModeChange.cpp
@@ -29,7 +29,7 @@
 
 class InitiateModeChangeTest : public DisplayTransactionTest {
 public:
-    using Event = scheduler::RefreshRateConfigEvent;
+    using Event = scheduler::DisplayModeEvent;
 
     void SetUp() override {
         injectFakeBufferQueueFactory();
@@ -44,68 +44,42 @@
         mFlinger.onComposerHalHotplug(PrimaryDisplayVariant::HWC_DISPLAY_ID, Connection::CONNECTED);
 
         mDisplay = PrimaryDisplayVariant::makeFakeExistingDisplayInjector(this)
-                           .setSupportedModes({kDisplayMode60, kDisplayMode90, kDisplayMode120})
-                           .setActiveMode(kDisplayModeId60)
+                           .setDisplayModes(makeModes(kMode60, kMode90, kMode120), kModeId60)
                            .inject();
     }
 
 protected:
     sp<DisplayDevice> mDisplay;
 
-    const DisplayModeId kDisplayModeId60 = DisplayModeId(0);
-    const DisplayModePtr kDisplayMode60 =
-            DisplayMode::Builder(hal::HWConfigId(kDisplayModeId60.value()))
-                    .setId(kDisplayModeId60)
-                    .setPhysicalDisplayId(PrimaryDisplayVariant::DISPLAY_ID::get())
-                    .setVsyncPeriod(int32_t(16'666'667))
-                    .setGroup(0)
-                    .setHeight(1000)
-                    .setWidth(1000)
-                    .build();
+    static constexpr DisplayModeId kModeId60{0};
+    static constexpr DisplayModeId kModeId90{1};
+    static constexpr DisplayModeId kModeId120{2};
 
-    const DisplayModeId kDisplayModeId90 = DisplayModeId(1);
-    const DisplayModePtr kDisplayMode90 =
-            DisplayMode::Builder(hal::HWConfigId(kDisplayModeId90.value()))
-                    .setId(kDisplayModeId90)
-                    .setPhysicalDisplayId(PrimaryDisplayVariant::DISPLAY_ID::get())
-                    .setVsyncPeriod(int32_t(11'111'111))
-                    .setGroup(0)
-                    .setHeight(1000)
-                    .setWidth(1000)
-                    .build();
-
-    const DisplayModeId kDisplayModeId120 = DisplayModeId(2);
-    const DisplayModePtr kDisplayMode120 =
-            DisplayMode::Builder(hal::HWConfigId(kDisplayModeId120.value()))
-                    .setId(kDisplayModeId120)
-                    .setPhysicalDisplayId(PrimaryDisplayVariant::DISPLAY_ID::get())
-                    .setVsyncPeriod(int32_t(8'333'333))
-                    .setGroup(0)
-                    .setHeight(1000)
-                    .setWidth(1000)
-                    .build();
+    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);
 };
 
 TEST_F(InitiateModeChangeTest, setDesiredActiveMode_setCurrentMode) {
-    EXPECT_FALSE(mDisplay->setDesiredActiveMode({kDisplayMode60, Event::None}));
+    EXPECT_FALSE(mDisplay->setDesiredActiveMode({kMode60, Event::None}));
     EXPECT_EQ(std::nullopt, mDisplay->getDesiredActiveMode());
 }
 
 TEST_F(InitiateModeChangeTest, setDesiredActiveMode_setNewMode) {
-    EXPECT_TRUE(mDisplay->setDesiredActiveMode({kDisplayMode90, Event::None}));
+    EXPECT_TRUE(mDisplay->setDesiredActiveMode({kMode90, Event::None}));
     ASSERT_NE(std::nullopt, mDisplay->getDesiredActiveMode());
-    EXPECT_EQ(kDisplayMode90, mDisplay->getDesiredActiveMode()->mode);
+    EXPECT_EQ(kMode90, mDisplay->getDesiredActiveMode()->mode);
     EXPECT_EQ(Event::None, mDisplay->getDesiredActiveMode()->event);
 
     // Setting another mode should be cached but return false
-    EXPECT_FALSE(mDisplay->setDesiredActiveMode({kDisplayMode120, Event::None}));
+    EXPECT_FALSE(mDisplay->setDesiredActiveMode({kMode120, Event::None}));
     ASSERT_NE(std::nullopt, mDisplay->getDesiredActiveMode());
-    EXPECT_EQ(kDisplayMode120, mDisplay->getDesiredActiveMode()->mode);
+    EXPECT_EQ(kMode120, mDisplay->getDesiredActiveMode()->mode);
     EXPECT_EQ(Event::None, mDisplay->getDesiredActiveMode()->event);
 }
 
 TEST_F(InitiateModeChangeTest, clearDesiredActiveModeState) {
-    EXPECT_TRUE(mDisplay->setDesiredActiveMode({kDisplayMode90, Event::None}));
+    EXPECT_TRUE(mDisplay->setDesiredActiveMode({kMode90, Event::None}));
     ASSERT_NE(std::nullopt, mDisplay->getDesiredActiveMode());
 
     mDisplay->clearDesiredActiveModeState();
@@ -113,9 +87,9 @@
 }
 
 TEST_F(InitiateModeChangeTest, initiateModeChange) NO_THREAD_SAFETY_ANALYSIS {
-    EXPECT_TRUE(mDisplay->setDesiredActiveMode({kDisplayMode90, Event::None}));
+    EXPECT_TRUE(mDisplay->setDesiredActiveMode({kMode90, Event::None}));
     ASSERT_NE(std::nullopt, mDisplay->getDesiredActiveMode());
-    EXPECT_EQ(kDisplayMode90, mDisplay->getDesiredActiveMode()->mode);
+    EXPECT_EQ(kMode90, mDisplay->getDesiredActiveMode()->mode);
     EXPECT_EQ(Event::None, mDisplay->getDesiredActiveMode()->event);
 
     hal::VsyncPeriodChangeConstraints constraints{
@@ -126,7 +100,7 @@
     EXPECT_EQ(OK,
               mDisplay->initiateModeChange(*mDisplay->getDesiredActiveMode(), constraints,
                                            &timeline));
-    EXPECT_EQ(kDisplayMode90, mDisplay->getUpcomingActiveMode().mode);
+    EXPECT_EQ(kMode90, mDisplay->getUpcomingActiveMode().mode);
     EXPECT_EQ(Event::None, mDisplay->getUpcomingActiveMode().event);
 
     mDisplay->clearDesiredActiveModeState();
@@ -135,9 +109,9 @@
 
 TEST_F(InitiateModeChangeTest, getUpcomingActiveMode_desiredActiveModeChanged)
 NO_THREAD_SAFETY_ANALYSIS {
-    EXPECT_TRUE(mDisplay->setDesiredActiveMode({kDisplayMode90, Event::None}));
+    EXPECT_TRUE(mDisplay->setDesiredActiveMode({kMode90, Event::None}));
     ASSERT_NE(std::nullopt, mDisplay->getDesiredActiveMode());
-    EXPECT_EQ(kDisplayMode90, mDisplay->getDesiredActiveMode()->mode);
+    EXPECT_EQ(kMode90, mDisplay->getDesiredActiveMode()->mode);
     EXPECT_EQ(Event::None, mDisplay->getDesiredActiveMode()->event);
 
     hal::VsyncPeriodChangeConstraints constraints{
@@ -148,21 +122,21 @@
     EXPECT_EQ(OK,
               mDisplay->initiateModeChange(*mDisplay->getDesiredActiveMode(), constraints,
                                            &timeline));
-    EXPECT_EQ(kDisplayMode90, mDisplay->getUpcomingActiveMode().mode);
+    EXPECT_EQ(kMode90, mDisplay->getUpcomingActiveMode().mode);
     EXPECT_EQ(Event::None, mDisplay->getUpcomingActiveMode().event);
 
-    EXPECT_FALSE(mDisplay->setDesiredActiveMode({kDisplayMode120, Event::None}));
+    EXPECT_FALSE(mDisplay->setDesiredActiveMode({kMode120, Event::None}));
     ASSERT_NE(std::nullopt, mDisplay->getDesiredActiveMode());
-    EXPECT_EQ(kDisplayMode120, mDisplay->getDesiredActiveMode()->mode);
+    EXPECT_EQ(kMode120, mDisplay->getDesiredActiveMode()->mode);
     EXPECT_EQ(Event::None, mDisplay->getDesiredActiveMode()->event);
 
-    EXPECT_EQ(kDisplayMode90, mDisplay->getUpcomingActiveMode().mode);
+    EXPECT_EQ(kMode90, mDisplay->getUpcomingActiveMode().mode);
     EXPECT_EQ(Event::None, mDisplay->getUpcomingActiveMode().event);
 
     EXPECT_EQ(OK,
               mDisplay->initiateModeChange(*mDisplay->getDesiredActiveMode(), constraints,
                                            &timeline));
-    EXPECT_EQ(kDisplayMode120, mDisplay->getUpcomingActiveMode().mode);
+    EXPECT_EQ(kMode120, mDisplay->getUpcomingActiveMode().mode);
     EXPECT_EQ(Event::None, mDisplay->getUpcomingActiveMode().event);
 
     mDisplay->clearDesiredActiveModeState();
diff --git a/services/surfaceflinger/tests/unittests/DisplayDevice_SetDisplayBrightnessTest.cpp b/services/surfaceflinger/tests/unittests/DisplayDevice_SetDisplayBrightnessTest.cpp
new file mode 100644
index 0000000..73c60e1
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/DisplayDevice_SetDisplayBrightnessTest.cpp
@@ -0,0 +1,99 @@
+/*
+ * 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.
+ */
+
+#undef LOG_TAG
+#define LOG_TAG "LibSurfaceFlingerUnittests"
+
+#include "DisplayTransactionTestHelpers.h"
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+namespace android {
+namespace {
+
+using hal::RenderIntent;
+
+using FakeDisplayDeviceInjector = TestableSurfaceFlinger::FakeDisplayDeviceInjector;
+
+class SetDisplayBrightnessTest : public DisplayTransactionTest {
+public:
+    sp<DisplayDevice> getDisplayDevice() { return injectDefaultInternalDisplay({}); }
+};
+
+TEST_F(SetDisplayBrightnessTest, persistDisplayBrightnessNoComposite) {
+    MainThreadScopedGuard fakeMainThreadGuard(SF_MAIN_THREAD);
+    sp<DisplayDevice> displayDevice = getDisplayDevice();
+
+    EXPECT_EQ(std::nullopt, displayDevice->getStagedBrightness());
+
+    constexpr float kDisplayBrightness = 0.5f;
+    displayDevice->stageBrightness(kDisplayBrightness);
+
+    EXPECT_EQ(0.5f, displayDevice->getStagedBrightness());
+
+    displayDevice->persistBrightness(false);
+
+    EXPECT_EQ(std::nullopt, displayDevice->getStagedBrightness());
+    EXPECT_EQ(std::nullopt, displayDevice->getCompositionDisplay()->getState().displayBrightness);
+}
+
+TEST_F(SetDisplayBrightnessTest, persistDisplayBrightnessWithComposite) {
+    MainThreadScopedGuard fakeMainThreadGuard(SF_MAIN_THREAD);
+    sp<DisplayDevice> displayDevice = getDisplayDevice();
+
+    EXPECT_EQ(std::nullopt, displayDevice->getStagedBrightness());
+
+    constexpr float kDisplayBrightness = 0.5f;
+    displayDevice->stageBrightness(kDisplayBrightness);
+
+    EXPECT_EQ(0.5f, displayDevice->getStagedBrightness());
+
+    displayDevice->persistBrightness(true);
+
+    EXPECT_EQ(std::nullopt, displayDevice->getStagedBrightness());
+    EXPECT_EQ(kDisplayBrightness,
+              displayDevice->getCompositionDisplay()->getState().displayBrightness);
+}
+
+TEST_F(SetDisplayBrightnessTest, persistDisplayBrightnessWithCompositeShortCircuitsOnNoOp) {
+    MainThreadScopedGuard fakeMainThreadGuard(SF_MAIN_THREAD);
+    sp<DisplayDevice> displayDevice = getDisplayDevice();
+
+    EXPECT_EQ(std::nullopt, displayDevice->getStagedBrightness());
+
+    constexpr float kDisplayBrightness = 0.5f;
+    displayDevice->stageBrightness(kDisplayBrightness);
+
+    EXPECT_EQ(0.5f, displayDevice->getStagedBrightness());
+
+    displayDevice->persistBrightness(true);
+
+    EXPECT_EQ(std::nullopt, displayDevice->getStagedBrightness());
+    EXPECT_EQ(kDisplayBrightness,
+              displayDevice->getCompositionDisplay()->getState().displayBrightness);
+    displayDevice->getCompositionDisplay()->editState().displayBrightness = std::nullopt;
+
+    displayDevice->stageBrightness(kDisplayBrightness);
+    EXPECT_EQ(0.5f, displayDevice->getStagedBrightness());
+    displayDevice->persistBrightness(true);
+
+    EXPECT_EQ(std::nullopt, displayDevice->getStagedBrightness());
+    EXPECT_EQ(std::nullopt, displayDevice->getCompositionDisplay()->getState().displayBrightness);
+}
+
+} // namespace
+} // namespace android
diff --git a/services/surfaceflinger/tests/unittests/DisplayDevice_SetProjectionTest.cpp b/services/surfaceflinger/tests/unittests/DisplayDevice_SetProjectionTest.cpp
index 9fe30f8..3d24ecb 100644
--- a/services/surfaceflinger/tests/unittests/DisplayDevice_SetProjectionTest.cpp
+++ b/services/surfaceflinger/tests/unittests/DisplayDevice_SetProjectionTest.cpp
@@ -84,10 +84,10 @@
         EXPECT_EQ(ui::Transform(TRANSFORM_FLAGS_ROT_0, mHardwareDisplaySize.width,
                                 mHardwareDisplaySize.height),
                   compositionState.transform);
-        EXPECT_EQ(ui::ROTATION_0, compositionState.displaySpace.orientation);
-        EXPECT_EQ(Rect(mHardwareDisplaySize), compositionState.displaySpace.content);
-        EXPECT_EQ(Rect(mHardwareDisplaySize), compositionState.orientedDisplaySpace.content);
-        EXPECT_EQ(Rect(mHardwareDisplaySize), compositionState.layerStackSpace.content);
+        EXPECT_EQ(ui::ROTATION_0, compositionState.displaySpace.getOrientation());
+        EXPECT_EQ(Rect(mHardwareDisplaySize), compositionState.displaySpace.getContent());
+        EXPECT_EQ(Rect(mHardwareDisplaySize), compositionState.orientedDisplaySpace.getContent());
+        EXPECT_EQ(Rect(mHardwareDisplaySize), compositionState.layerStackSpace.getContent());
         EXPECT_EQ(false, compositionState.needsFiltering);
     }
 
@@ -96,13 +96,14 @@
         EXPECT_EQ(ui::Transform(TRANSFORM_FLAGS_ROT_90, mHardwareDisplaySize.width,
                                 mHardwareDisplaySize.height),
                   compositionState.transform);
-        EXPECT_EQ(ui::ROTATION_90, compositionState.displaySpace.orientation);
-        EXPECT_EQ(Rect(mHardwareDisplaySize), compositionState.displaySpace.content);
+        EXPECT_EQ(ui::ROTATION_90, compositionState.displaySpace.getOrientation());
+        EXPECT_EQ(Rect(mHardwareDisplaySize), compositionState.displaySpace.getContent());
         // For 90, the orientedDisplaySpaceRect and layerStackSpaceRect have the hardware display
         // size width and height swapped
         EXPECT_EQ(Rect(swapWH(mHardwareDisplaySize)),
-                  compositionState.orientedDisplaySpace.content);
-        EXPECT_EQ(Rect(swapWH(mHardwareDisplaySize)), compositionState.layerStackSpace.content);
+                  compositionState.orientedDisplaySpace.getContent());
+        EXPECT_EQ(Rect(swapWH(mHardwareDisplaySize)),
+                  compositionState.layerStackSpace.getContent());
         EXPECT_EQ(false, compositionState.needsFiltering);
     }
 
@@ -111,9 +112,9 @@
         EXPECT_EQ(ui::Transform(TRANSFORM_FLAGS_ROT_180, mHardwareDisplaySize.width,
                                 mHardwareDisplaySize.height),
                   compositionState.transform);
-        EXPECT_EQ(ui::ROTATION_180, compositionState.displaySpace.orientation);
-        EXPECT_EQ(Rect(mHardwareDisplaySize), compositionState.orientedDisplaySpace.content);
-        EXPECT_EQ(Rect(mHardwareDisplaySize), compositionState.layerStackSpace.content);
+        EXPECT_EQ(ui::ROTATION_180, compositionState.displaySpace.getOrientation());
+        EXPECT_EQ(Rect(mHardwareDisplaySize), compositionState.orientedDisplaySpace.getContent());
+        EXPECT_EQ(Rect(mHardwareDisplaySize), compositionState.layerStackSpace.getContent());
         EXPECT_EQ(false, compositionState.needsFiltering);
     }
 
@@ -122,13 +123,14 @@
         EXPECT_EQ(ui::Transform(TRANSFORM_FLAGS_ROT_270, mHardwareDisplaySize.width,
                                 mHardwareDisplaySize.height),
                   compositionState.transform);
-        EXPECT_EQ(ui::ROTATION_270, compositionState.displaySpace.orientation);
-        EXPECT_EQ(Rect(mHardwareDisplaySize), compositionState.displaySpace.content);
+        EXPECT_EQ(ui::ROTATION_270, compositionState.displaySpace.getOrientation());
+        EXPECT_EQ(Rect(mHardwareDisplaySize), compositionState.displaySpace.getContent());
         // For 270, the orientedDisplaySpaceRect and layerStackSpaceRect have the hardware display
         // size width and height swapped
         EXPECT_EQ(Rect(swapWH(mHardwareDisplaySize)),
-                  compositionState.orientedDisplaySpace.content);
-        EXPECT_EQ(Rect(swapWH(mHardwareDisplaySize)), compositionState.layerStackSpace.content);
+                  compositionState.orientedDisplaySpace.getContent());
+        EXPECT_EQ(Rect(swapWH(mHardwareDisplaySize)),
+                  compositionState.layerStackSpace.getContent());
         EXPECT_EQ(false, compositionState.needsFiltering);
     }
 
diff --git a/services/surfaceflinger/tests/unittests/DisplayIdentificationTestHelpers.h b/services/surfaceflinger/tests/unittests/DisplayIdentificationTestHelpers.h
new file mode 100644
index 0000000..975bc12
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/DisplayIdentificationTestHelpers.h
@@ -0,0 +1,79 @@
+/*
+ * 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 <ui/DisplayIdentification.h>
+
+namespace android {
+
+template <size_t N>
+DisplayIdentificationData asDisplayIdentificationData(const unsigned char (&bytes)[N]) {
+    return DisplayIdentificationData(bytes, bytes + N - 1);
+}
+
+inline const DisplayIdentificationData& getInternalEdid() {
+    static constexpr unsigned char kInternalEdid[] =
+            "\x00\xff\xff\xff\xff\xff\xff\x00\x4c\xa3\x42\x31\x00\x00\x00\x00"
+            "\x00\x15\x01\x03\x80\x1a\x10\x78\x0a\xd3\xe5\x95\x5c\x60\x90\x27"
+            "\x19\x50\x54\x00\x00\x00\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01"
+            "\x01\x01\x01\x01\x01\x01\x9e\x1b\x00\xa0\x50\x20\x12\x30\x10\x30"
+            "\x13\x00\x05\xa3\x10\x00\x00\x19\x00\x00\x00\x0f\x00\x00\x00\x00"
+            "\x00\x00\x00\x00\x00\x23\x87\x02\x64\x00\x00\x00\x00\xfe\x00\x53"
+            "\x41\x4d\x53\x55\x4e\x47\x0a\x20\x20\x20\x20\x20\x00\x00\x00\xfe"
+            "\x00\x31\x32\x31\x41\x54\x31\x31\x2d\x38\x30\x31\x0a\x20\x00\x45";
+    static const DisplayIdentificationData data = asDisplayIdentificationData(kInternalEdid);
+    return data;
+}
+
+inline const DisplayIdentificationData& getExternalEdid() {
+    static constexpr unsigned char kExternalEdid[] =
+            "\x00\xff\xff\xff\xff\xff\xff\x00\x22\xf0\x6c\x28\x01\x01\x01\x01"
+            "\x02\x16\x01\x04\xb5\x40\x28\x78\xe2\x8d\x85\xad\x4f\x35\xb1\x25"
+            "\x0e\x50\x54\x00\x00\x00\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01"
+            "\x01\x01\x01\x01\x01\x01\xe2\x68\x00\xa0\xa0\x40\x2e\x60\x30\x20"
+            "\x36\x00\x81\x90\x21\x00\x00\x1a\xbc\x1b\x00\xa0\x50\x20\x17\x30"
+            "\x30\x20\x36\x00\x81\x90\x21\x00\x00\x1a\x00\x00\x00\xfc\x00\x48"
+            "\x50\x20\x5a\x52\x33\x30\x77\x0a\x20\x20\x20\x20\x00\x00\x00\xff"
+            "\x00\x43\x4e\x34\x32\x30\x32\x31\x33\x37\x51\x0a\x20\x20\x00\x71";
+    static const DisplayIdentificationData data = asDisplayIdentificationData(kExternalEdid);
+    return data;
+}
+
+inline const DisplayIdentificationData& getExternalEedid() {
+    // Extended EDID with timing extension.
+    static constexpr unsigned char kExternalEedid[] =
+            "\x00\xff\xff\xff\xff\xff\xff\x00\x4c\x2d\xfe\x08\x00\x00\x00\x00"
+            "\x29\x15\x01\x03\x80\x10\x09\x78\x0a\xee\x91\xa3\x54\x4c\x99\x26"
+            "\x0f\x50\x54\xbd\xef\x80\x71\x4f\x81\xc0\x81\x00\x81\x80\x95\x00"
+            "\xa9\xc0\xb3\x00\x01\x01\x02\x3a\x80\x18\x71\x38\x2d\x40\x58\x2c"
+            "\x45\x00\xa0\x5a\x00\x00\x00\x1e\x66\x21\x56\xaa\x51\x00\x1e\x30"
+            "\x46\x8f\x33\x00\xa0\x5a\x00\x00\x00\x1e\x00\x00\x00\xfd\x00\x18"
+            "\x4b\x0f\x51\x17\x00\x0a\x20\x20\x20\x20\x20\x20\x00\x00\x00\xfc"
+            "\x00\x53\x41\x4d\x53\x55\x4e\x47\x0a\x20\x20\x20\x20\x20\x01\x1d"
+            "\x02\x03\x1f\xf1\x47\x90\x04\x05\x03\x20\x22\x07\x23\x09\x07\x07"
+            "\x83\x01\x00\x00\xe2\x00\x0f\x67\x03\x0c\x00\x20\x00\xb8\x2d\x01"
+            "\x1d\x80\x18\x71\x1c\x16\x20\x58\x2c\x25\x00\xa0\x5a\x00\x00\x00"
+            "\x9e\x01\x1d\x00\x72\x51\xd0\x1e\x20\x6e\x28\x55\x00\xa0\x5a\x00"
+            "\x00\x00\x1e\x8c\x0a\xd0\x8a\x20\xe0\x2d\x10\x10\x3e\x96\x00\xa0"
+            "\x5a\x00\x00\x00\x18\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+            "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+            "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xc6";
+    static const DisplayIdentificationData data = asDisplayIdentificationData(kExternalEedid);
+    return data;
+}
+
+} // namespace android
\ No newline at end of file
diff --git a/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp b/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp
index cc24323..2425862 100644
--- a/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp
+++ b/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp
@@ -38,7 +38,6 @@
 
     // Default to no wide color display support configured
     mFlinger.mutableHasWideColorDisplay() = false;
-    mFlinger.mutableUseColorManagement() = false;
     mFlinger.mutableDisplayColorSetting() = DisplayColorSetting::kUnmanaged;
 
     mFlinger.setCreateBufferQueueFunction([](auto, auto, auto) {
@@ -51,7 +50,6 @@
     });
 
     injectMockScheduler();
-    mFlinger.mutableEventQueue().reset(mMessageQueue);
     mFlinger.setupRenderEngine(std::unique_ptr<renderengine::RenderEngine>(mRenderEngine));
     mFlinger.mutableInterceptor() = mSurfaceInterceptor;
 
@@ -62,6 +60,7 @@
     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());
+    mFlinger.resetScheduler(nullptr);
 }
 
 void DisplayTransactionTest::injectMockScheduler() {
@@ -78,7 +77,8 @@
     mFlinger.setupScheduler(std::unique_ptr<scheduler::VsyncController>(mVsyncController),
                             std::unique_ptr<scheduler::VSyncTracker>(mVSyncTracker),
                             std::unique_ptr<EventThread>(mEventThread),
-                            std::unique_ptr<EventThread>(mSFEventThread), &mSchedulerCallback);
+                            std::unique_ptr<EventThread>(mSFEventThread),
+                            TestableSurfaceFlinger::SchedulerCallbackImpl::kMock);
 }
 
 void DisplayTransactionTest::injectMockComposer(int virtualDisplayCount) {
@@ -122,7 +122,7 @@
 
 sp<DisplayDevice> DisplayTransactionTest::injectDefaultInternalDisplay(
         std::function<void(FakeDisplayDeviceInjector&)> injectExtra) {
-    constexpr PhysicalDisplayId DEFAULT_DISPLAY_ID(777);
+    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;
@@ -139,20 +139,18 @@
     EXPECT_CALL(*mNativeWindow, perform(NATIVE_WINDOW_SET_USAGE64));
     EXPECT_CALL(*mNativeWindow, perform(NATIVE_WINDOW_API_DISCONNECT)).Times(AnyNumber());
 
-    constexpr auto kConnectionType = ui::DisplayConnectionType::Internal;
-    constexpr bool kIsPrimary = true;
-
     auto compositionDisplay =
             compositionengine::impl::createDisplay(mFlinger.getCompositionEngine(),
                                                    compositionengine::DisplayCreationArgsBuilder()
                                                            .setId(DEFAULT_DISPLAY_ID)
-                                                           .setConnectionType(kConnectionType)
                                                            .setPixels({DEFAULT_DISPLAY_WIDTH,
                                                                        DEFAULT_DISPLAY_HEIGHT})
                                                            .setPowerAdvisor(&mPowerAdvisor)
                                                            .build());
 
-    auto injector = FakeDisplayDeviceInjector(mFlinger, compositionDisplay, kConnectionType,
+    constexpr bool kIsPrimary = true;
+    auto injector = FakeDisplayDeviceInjector(mFlinger, compositionDisplay,
+                                              ui::DisplayConnectionType::Internal,
                                               DEFAULT_DISPLAY_HWC_DISPLAY_ID, kIsPrimary);
 
     injector.setNativeWindow(mNativeWindow);
diff --git a/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h b/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h
index de058a4..565c244 100644
--- a/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h
+++ b/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h
@@ -22,7 +22,7 @@
 #pragma clang diagnostic ignored "-Wextra"
 
 #include <type_traits>
-#include "DisplayIdentificationTest.h"
+#include "DisplayIdentificationTestHelpers.h"
 
 #include <binder/IPCThreadState.h>
 #include <compositionengine/Display.h>
@@ -45,11 +45,10 @@
 #include "TestableScheduler.h"
 #include "TestableSurfaceFlinger.h"
 #include "mock/DisplayHardware/MockComposer.h"
+#include "mock/DisplayHardware/MockDisplayMode.h"
 #include "mock/DisplayHardware/MockPowerAdvisor.h"
 #include "mock/MockEventThread.h"
-#include "mock/MockMessageQueue.h"
 #include "mock/MockNativeWindowSurface.h"
-#include "mock/MockSchedulerCallback.h"
 #include "mock/MockSurfaceInterceptor.h"
 #include "mock/MockVsyncController.h"
 #include "mock/system/window/MockNativeWindow.h"
@@ -67,9 +66,9 @@
 using testing::Return;
 using testing::SetArgPointee;
 
+using aidl::android::hardware::graphics::composer3::DisplayCapability;
 using hal::ColorMode;
 using hal::Connection;
-using hal::DisplayCapability;
 using hal::DisplayType;
 using hal::Error;
 using hal::Hdr;
@@ -118,12 +117,10 @@
     // to keep a reference to them for use in setting up call expectations.
     renderengine::mock::RenderEngine* mRenderEngine = new renderengine::mock::RenderEngine();
     Hwc2::mock::Composer* mComposer = nullptr;
-    mock::MessageQueue* mMessageQueue = new mock::MessageQueue();
     sp<mock::SurfaceInterceptor> mSurfaceInterceptor = new mock::SurfaceInterceptor;
 
     mock::VsyncController* mVsyncController = new mock::VsyncController;
     mock::VSyncTracker* mVSyncTracker = new mock::VSyncTracker;
-    mock::SchedulerCallback mSchedulerCallback;
     mock::EventThread* mEventThread = new mock::EventThread;
     mock::EventThread* mSFEventThread = new mock::EventThread;
 
@@ -233,15 +230,15 @@
 //     2) HalVirtualDisplayIdType<...> for hard-coded ID of virtual display backed by HWC.
 //     3) GpuVirtualDisplayIdType for virtual display without HWC backing.
 template <typename DisplayIdType, int width, int height, Critical critical, Async async,
-          Secure secure, Primary primary, int grallocUsage>
+          Secure secure, Primary primary, int grallocUsage, int displayFlags>
 struct DisplayVariant {
     using DISPLAY_ID = DisplayIdGetter<DisplayIdType>;
     using CONNECTION_TYPE = DisplayConnectionTypeGetter<DisplayIdType>;
     using HWC_DISPLAY_ID_OPT = HwcDisplayIdGetter<DisplayIdType>;
 
-    // The display width and height
     static constexpr int WIDTH = width;
     static constexpr int HEIGHT = height;
+    static constexpr ui::Size RESOLUTION{WIDTH, HEIGHT};
 
     static constexpr int GRALLOC_USAGE = grallocUsage;
 
@@ -261,17 +258,14 @@
     // Whether the display is primary
     static constexpr Primary PRIMARY = primary;
 
+    static constexpr int DISPLAY_FLAGS = displayFlags;
+
     static auto makeFakeExistingDisplayInjector(DisplayTransactionTest* test) {
         auto ceDisplayArgs = compositionengine::DisplayCreationArgsBuilder();
         ceDisplayArgs.setId(DISPLAY_ID::get())
-                .setPixels({WIDTH, HEIGHT})
+                .setPixels(RESOLUTION)
                 .setPowerAdvisor(&test->mPowerAdvisor);
 
-        const auto connectionType = CONNECTION_TYPE::value;
-        if (connectionType) {
-            ceDisplayArgs.setConnectionType(*connectionType);
-        }
-
         auto compositionDisplay =
                 compositionengine::impl::createDisplay(test->mFlinger.getCompositionEngine(),
                                                        ceDisplayArgs.build());
@@ -279,7 +273,7 @@
         auto injector =
                 TestableSurfaceFlinger::FakeDisplayDeviceInjector(test->mFlinger,
                                                                   compositionDisplay,
-                                                                  connectionType,
+                                                                  CONNECTION_TYPE::value,
                                                                   HWC_DISPLAY_ID_OPT::value,
                                                                   static_cast<bool>(PRIMARY));
 
@@ -364,8 +358,7 @@
         TestableSurfaceFlinger::FakeHwcDisplayInjector(displayId, HWC_DISPLAY_TYPE,
                                                        static_cast<bool>(DisplayVariant::PRIMARY))
                 .setHwcDisplayId(HWC_DISPLAY_ID)
-                .setWidth(DisplayVariant::WIDTH)
-                .setHeight(DisplayVariant::HEIGHT)
+                .setResolution(DisplayVariant::RESOLUTION)
                 .setActiveConfig(HWC_ACTIVE_CONFIG_ID)
                 .setPowerMode(INIT_POWER_MODE)
                 .inject(&test->mFlinger, test->mComposer);
@@ -388,8 +381,7 @@
 
         auto ceDisplayArgs = compositionengine::DisplayCreationArgsBuilder()
                                      .setId(DisplayVariant::DISPLAY_ID::get())
-                                     .setConnectionType(PhysicalDisplay::CONNECTION_TYPE)
-                                     .setPixels({DisplayVariant::WIDTH, DisplayVariant::HEIGHT})
+                                     .setPixels(DisplayVariant::RESOLUTION)
                                      .setIsSecure(static_cast<bool>(DisplayVariant::SECURE))
                                      .setPowerAdvisor(&test->mPowerAdvisor)
                                      .setName(std::string("Injected display for ") +
@@ -475,16 +467,19 @@
 constexpr uint32_t GRALLOC_USAGE_PHYSICAL_DISPLAY =
         GRALLOC_USAGE_HW_RENDER | GRALLOC_USAGE_HW_COMPOSER | GRALLOC_USAGE_HW_FB;
 
+constexpr int PHYSICAL_DISPLAY_FLAGS = 0x1;
+
 template <typename PhysicalDisplay, int width, int height, Critical critical>
 struct PhysicalDisplayVariant
       : DisplayVariant<PhysicalDisplayIdType<PhysicalDisplay>, width, height, critical,
                        Async::FALSE, Secure::TRUE, PhysicalDisplay::PRIMARY,
-                       GRALLOC_USAGE_PHYSICAL_DISPLAY>,
-        HwcDisplayVariant<PhysicalDisplay::HWC_DISPLAY_ID, DisplayType::PHYSICAL,
-                          DisplayVariant<PhysicalDisplayIdType<PhysicalDisplay>, width, height,
-                                         critical, Async::FALSE, Secure::TRUE,
-                                         PhysicalDisplay::PRIMARY, GRALLOC_USAGE_PHYSICAL_DISPLAY>,
-                          PhysicalDisplay> {};
+                       GRALLOC_USAGE_PHYSICAL_DISPLAY, PHYSICAL_DISPLAY_FLAGS>,
+        HwcDisplayVariant<
+                PhysicalDisplay::HWC_DISPLAY_ID, DisplayType::PHYSICAL,
+                DisplayVariant<PhysicalDisplayIdType<PhysicalDisplay>, width, height, critical,
+                               Async::FALSE, Secure::TRUE, PhysicalDisplay::PRIMARY,
+                               GRALLOC_USAGE_PHYSICAL_DISPLAY, PHYSICAL_DISPLAY_FLAGS>,
+                PhysicalDisplay> {};
 
 template <bool hasIdentificationData>
 struct PrimaryDisplay {
@@ -526,13 +521,16 @@
 // A virtual display not supported by the HWC.
 constexpr uint32_t GRALLOC_USAGE_NONHWC_VIRTUAL_DISPLAY = 0;
 
+constexpr int VIRTUAL_DISPLAY_FLAGS = 0x0;
+
 template <int width, int height, Secure secure>
 struct NonHwcVirtualDisplayVariant
       : DisplayVariant<GpuVirtualDisplayIdType, width, height, Critical::FALSE, Async::TRUE, secure,
-                       Primary::FALSE, GRALLOC_USAGE_NONHWC_VIRTUAL_DISPLAY> {
-    using Base =
-            DisplayVariant<GpuVirtualDisplayIdType, width, height, Critical::FALSE, Async::TRUE,
-                           secure, Primary::FALSE, GRALLOC_USAGE_NONHWC_VIRTUAL_DISPLAY>;
+                       Primary::FALSE, GRALLOC_USAGE_NONHWC_VIRTUAL_DISPLAY,
+                       VIRTUAL_DISPLAY_FLAGS> {
+    using Base = DisplayVariant<GpuVirtualDisplayIdType, width, height, Critical::FALSE,
+                                Async::TRUE, secure, Primary::FALSE,
+                                GRALLOC_USAGE_NONHWC_VIRTUAL_DISPLAY, VIRTUAL_DISPLAY_FLAGS>;
 
     static void injectHwcDisplay(DisplayTransactionTest*) {}
 
@@ -543,7 +541,7 @@
 
         auto ceDisplayArgs = compositionengine::DisplayCreationArgsBuilder()
                                      .setId(Base::DISPLAY_ID::get())
-                                     .setPixels({Base::WIDTH, Base::HEIGHT})
+                                     .setPixels(Base::RESOLUTION)
                                      .setIsSecure(static_cast<bool>(Base::SECURE))
                                      .setPowerAdvisor(&test->mPowerAdvisor)
                                      .setName(std::string("Injected display for ") +
@@ -575,13 +573,16 @@
 template <int width, int height, Secure secure>
 struct HwcVirtualDisplayVariant
       : DisplayVariant<HalVirtualDisplayIdType<42>, width, height, Critical::FALSE, Async::TRUE,
-                       secure, Primary::FALSE, GRALLOC_USAGE_HWC_VIRTUAL_DISPLAY>,
-        HwcDisplayVariant<HWC_VIRTUAL_DISPLAY_HWC_DISPLAY_ID, DisplayType::VIRTUAL,
-                          DisplayVariant<HalVirtualDisplayIdType<42>, width, height,
-                                         Critical::FALSE, Async::TRUE, secure, Primary::FALSE,
-                                         GRALLOC_USAGE_HWC_VIRTUAL_DISPLAY>> {
+                       secure, Primary::FALSE, GRALLOC_USAGE_HWC_VIRTUAL_DISPLAY,
+                       VIRTUAL_DISPLAY_FLAGS>,
+        HwcDisplayVariant<
+                HWC_VIRTUAL_DISPLAY_HWC_DISPLAY_ID, DisplayType::VIRTUAL,
+                DisplayVariant<HalVirtualDisplayIdType<42>, width, height, Critical::FALSE,
+                               Async::TRUE, secure, Primary::FALSE,
+                               GRALLOC_USAGE_HWC_VIRTUAL_DISPLAY, VIRTUAL_DISPLAY_FLAGS>> {
     using Base = DisplayVariant<HalVirtualDisplayIdType<42>, width, height, Critical::FALSE,
-                                Async::TRUE, secure, Primary::FALSE, GRALLOC_USAGE_HW_COMPOSER>;
+                                Async::TRUE, secure, Primary::FALSE, GRALLOC_USAGE_HW_COMPOSER,
+                                VIRTUAL_DISPLAY_FLAGS>;
     using Self = HwcVirtualDisplayVariant<width, height, secure>;
 
     static std::shared_ptr<compositionengine::Display> injectCompositionDisplay(
@@ -592,7 +593,7 @@
         const auto displayId = Base::DISPLAY_ID::get();
         auto ceDisplayArgs = compositionengine::DisplayCreationArgsBuilder()
                                      .setId(displayId)
-                                     .setPixels({Base::WIDTH, Base::HEIGHT})
+                                     .setPixels(Base::RESOLUTION)
                                      .setIsSecure(static_cast<bool>(Base::SECURE))
                                      .setPowerAdvisor(&test->mPowerAdvisor)
                                      .setName(std::string("Injected display for ") +
@@ -671,7 +672,6 @@
     static constexpr bool WIDE_COLOR_SUPPORTED = false;
 
     static void injectConfigChange(DisplayTransactionTest* test) {
-        test->mFlinger.mutableUseColorManagement() = true;
         test->mFlinger.mutableHasWideColorDisplay() = true;
     }
 
@@ -690,7 +690,6 @@
 
     static void injectConfigChange(DisplayTransactionTest* test) {
         test->mFlinger.mutableHasWideColorDisplay() = false;
-        test->mFlinger.mutableUseColorManagement() = false;
         test->mFlinger.mutableDisplayColorSetting() = DisplayColorSetting::kUnmanaged;
     }
 
@@ -737,6 +736,12 @@
              HdrNotSupportedVariant<SimpleHwcVirtualDisplayVariant>,
              NoPerFrameMetadataSupportVariant<SimpleHwcVirtualDisplayVariant>>;
 
+inline DisplayModePtr createDisplayMode(DisplayModeId modeId, Fps refreshRate, int32_t group = 0,
+                                        ui::Size resolution = ui::Size(1920, 1080)) {
+    return mock::createDisplayMode(modeId, refreshRate, group, resolution,
+                                   PrimaryDisplayVariant::DISPLAY_ID::get());
+}
+
 } // namespace android
 
 // TODO(b/129481165): remove the #pragma below and fix conversion issues
diff --git a/services/surfaceflinger/tests/unittests/EventThreadTest.cpp b/services/surfaceflinger/tests/unittests/EventThreadTest.cpp
index 4ff7592..14d8f98 100644
--- a/services/surfaceflinger/tests/unittests/EventThreadTest.cpp
+++ b/services/surfaceflinger/tests/unittests/EventThreadTest.cpp
@@ -28,6 +28,7 @@
 
 #include "AsyncCallRecorder.h"
 #include "DisplayHardware/DisplayMode.h"
+#include "FrameTimeline.h"
 #include "Scheduler/EventThread.h"
 
 using namespace std::chrono_literals;
@@ -41,10 +42,13 @@
 
 namespace {
 
-constexpr PhysicalDisplayId INTERNAL_DISPLAY_ID(111);
-constexpr PhysicalDisplayId EXTERNAL_DISPLAY_ID(222);
-constexpr PhysicalDisplayId DISPLAY_ID_64BIT(0xabcd12349876fedcULL);
+constexpr PhysicalDisplayId INTERNAL_DISPLAY_ID = PhysicalDisplayId::fromPort(111u);
+constexpr PhysicalDisplayId EXTERNAL_DISPLAY_ID = PhysicalDisplayId::fromPort(222u);
+constexpr PhysicalDisplayId DISPLAY_ID_64BIT =
+        PhysicalDisplayId::fromEdid(0xffu, 0xffffu, 0xffff'ffffu);
+
 constexpr std::chrono::duration VSYNC_PERIOD(16ms);
+
 class MockVSyncSource : public VSyncSource {
 public:
     const char* getName() const override { return "test"; }
@@ -55,6 +59,7 @@
                  void(std::chrono::nanoseconds workDuration,
                       std::chrono::nanoseconds readyDuration));
     MOCK_METHOD1(pauseVsyncCallback, void(bool));
+    MOCK_METHOD(VSyncSource::VSyncData, getLatestVSyncData, (), (const, override));
     MOCK_CONST_METHOD1(dump, void(std::string&));
 };
 
@@ -93,6 +98,8 @@
                                               ConnectionEventRecorder& connectionEventRecorder,
                                               nsecs_t expectedTimestamp, unsigned expectedCount);
     void expectVsyncEventReceivedByConnection(nsecs_t expectedTimestamp, unsigned expectedCount);
+    void expectVsyncEventFrameTimelinesCorrect(nsecs_t expectedTimestamp,
+                                               VSyncSource::VSyncData preferredVsyncData);
     void expectHotplugEventReceivedByConnection(PhysicalDisplayId expectedDisplayId,
                                                 bool expectedConnected);
     void expectConfigChangedEventReceivedByConnection(PhysicalDisplayId expectedDisplayId,
@@ -117,6 +124,7 @@
     std::unique_ptr<impl::EventThread> mThread;
     sp<MockEventThreadConnection> mConnection;
     sp<MockEventThreadConnection> mThrottledConnection;
+    std::unique_ptr<frametimeline::impl::TokenManager> mTokenManager;
 
     static constexpr uid_t mConnectionUid = 443;
     static constexpr uid_t mThrottledConnectionUid = 177;
@@ -170,8 +178,8 @@
         return VSYNC_PERIOD.count();
     };
 
-    mThread = std::make_unique<impl::EventThread>(std::move(source),
-                                                  /*tokenManager=*/nullptr,
+    mTokenManager = std::make_unique<frametimeline::impl::TokenManager>();
+    mThread = std::make_unique<impl::EventThread>(std::move(source), mTokenManager.get(),
                                                   mInterceptVSyncCallRecorder.getInvocable(),
                                                   throttleVsync, getVsyncPeriod);
 
@@ -244,6 +252,46 @@
                                          expectedCount);
 }
 
+void EventThreadTest::expectVsyncEventFrameTimelinesCorrect(
+        nsecs_t expectedTimestamp, VSyncSource::VSyncData preferredVsyncData) {
+    auto args = mConnectionEventCallRecorder.waitForCall();
+    ASSERT_TRUE(args.has_value()) << " did not receive an event for timestamp "
+                                  << expectedTimestamp;
+    const auto& event = std::get<0>(args.value());
+    for (int i = 0; i < VsyncEventData::kFrameTimelinesLength; i++) {
+        auto prediction = mTokenManager->getPredictionsForToken(
+                event.vsync.vsyncData.frameTimelines[i].vsyncId);
+        EXPECT_TRUE(prediction.has_value());
+        EXPECT_EQ(prediction.value().endTime,
+                  event.vsync.vsyncData.frameTimelines[i].deadlineTimestamp)
+                << "Deadline timestamp does not match cached value";
+        EXPECT_EQ(prediction.value().presentTime,
+                  event.vsync.vsyncData.frameTimelines[i].expectedPresentationTime)
+                << "Expected vsync.vsyncData timestamp does not match cached value";
+
+        if (i > 0) {
+            EXPECT_GT(event.vsync.vsyncData.frameTimelines[i].deadlineTimestamp,
+                      event.vsync.vsyncData.frameTimelines[i - 1].deadlineTimestamp)
+                    << "Deadline timestamp out of order for frame timeline " << i;
+            EXPECT_GT(event.vsync.vsyncData.frameTimelines[i].expectedPresentationTime,
+                      event.vsync.vsyncData.frameTimelines[i - 1].expectedPresentationTime)
+                    << "Expected vsync.vsyncData timestamp out of order for frame timeline " << i;
+        }
+
+        // Vsync ID order lines up with registration into test token manager.
+        EXPECT_EQ(i, event.vsync.vsyncData.frameTimelines[i].vsyncId)
+                << "Vsync ID incorrect for frame timeline " << i;
+        if (i == event.vsync.vsyncData.preferredFrameTimelineIndex) {
+            EXPECT_EQ(event.vsync.vsyncData.frameTimelines[i].deadlineTimestamp,
+                      preferredVsyncData.deadlineTimestamp)
+                    << "Preferred deadline timestamp incorrect" << i;
+            EXPECT_EQ(event.vsync.vsyncData.frameTimelines[i].expectedPresentationTime,
+                      preferredVsyncData.expectedPresentationTime)
+                    << "Preferred expected vsync.vsyncData timestamp incorrect" << i;
+        }
+    }
+}
+
 void EventThreadTest::expectHotplugEventReceivedByConnection(PhysicalDisplayId expectedDisplayId,
                                                              bool expectedConnected) {
     auto args = mConnectionEventCallRecorder.waitForCall();
@@ -287,6 +335,8 @@
 
 namespace {
 
+using namespace testing;
+
 /* ------------------------------------------------------------------------
  * Test cases
  */
@@ -323,7 +373,7 @@
 
     // Use the received callback to signal a first vsync event.
     // The interceptor should receive the event, as well as the connection.
-    mCallback->onVSyncEvent(123, 456, 789);
+    mCallback->onVSyncEvent(123, {456, 789});
     expectInterceptCallReceived(123);
     expectThrottleVsyncReceived(456, mConnectionUid);
     expectVsyncEventReceivedByConnection(123, 1u);
@@ -331,7 +381,7 @@
     // Use the received callback to signal a second vsync event.
     // The interceptor should receive the event, but the connection should
     // not as it was only interested in the first.
-    mCallback->onVSyncEvent(456, 123, 0);
+    mCallback->onVSyncEvent(456, {123, 0});
     expectInterceptCallReceived(456);
     EXPECT_FALSE(mThrottleVsyncCallRecorder.waitForUnexpectedCall().has_value());
     EXPECT_FALSE(mConnectionEventCallRecorder.waitForUnexpectedCall().has_value());
@@ -341,6 +391,66 @@
     expectVSyncSetEnabledCallReceived(false);
 }
 
+TEST_F(EventThreadTest, requestNextVsyncEventFrameTimelinesCorrect) {
+    // Signal that we want the next vsync event to be posted to the connection
+    mThread->requestNextVsync(mConnection);
+
+    expectVSyncSetEnabledCallReceived(true);
+
+    // Use the received callback to signal a vsync event.
+    // The interceptor should receive the event, as well as the connection.
+    VSyncSource::VSyncData vsyncData = {456, 789};
+    mCallback->onVSyncEvent(123, vsyncData);
+    expectInterceptCallReceived(123);
+    expectVsyncEventFrameTimelinesCorrect(123, vsyncData);
+}
+
+TEST_F(EventThreadTest, getLatestVsyncEventData) {
+    const nsecs_t now = systemTime();
+    const nsecs_t preferredDeadline = now + 10000000;
+    const nsecs_t preferredExpectedPresentationTime = now + 20000000;
+    const VSyncSource::VSyncData preferredData = {preferredExpectedPresentationTime,
+                                                  preferredDeadline};
+    EXPECT_CALL(*mVSyncSource, getLatestVSyncData()).WillOnce(Return(preferredData));
+
+    VsyncEventData vsyncEventData = mThread->getLatestVsyncEventData(mConnection);
+    EXPECT_GT(vsyncEventData.frameTimelines[0].deadlineTimestamp, now)
+            << "Deadline timestamp should be greater than frame time";
+    for (size_t i = 0; i < VsyncEventData::kFrameTimelinesLength; i++) {
+        auto prediction =
+                mTokenManager->getPredictionsForToken(vsyncEventData.frameTimelines[i].vsyncId);
+        EXPECT_TRUE(prediction.has_value());
+        EXPECT_EQ(prediction.value().endTime, vsyncEventData.frameTimelines[i].deadlineTimestamp)
+                << "Deadline timestamp does not match cached value";
+        EXPECT_EQ(prediction.value().presentTime,
+                  vsyncEventData.frameTimelines[i].expectedPresentationTime)
+                << "Expected vsync timestamp does not match cached value";
+        EXPECT_GT(vsyncEventData.frameTimelines[i].expectedPresentationTime,
+                  vsyncEventData.frameTimelines[i].deadlineTimestamp)
+                << "Expected vsync timestamp should be greater than deadline";
+
+        if (i > 0) {
+            EXPECT_GT(vsyncEventData.frameTimelines[i].deadlineTimestamp,
+                      vsyncEventData.frameTimelines[i - 1].deadlineTimestamp)
+                    << "Deadline timestamp out of order for frame timeline " << i;
+            EXPECT_GT(vsyncEventData.frameTimelines[i].expectedPresentationTime,
+                      vsyncEventData.frameTimelines[i - 1].expectedPresentationTime)
+                    << "Expected vsync timestamp out of order for frame timeline " << i;
+        }
+
+        // Vsync ID order lines up with registration into test token manager.
+        EXPECT_EQ(i, vsyncEventData.frameTimelines[i].vsyncId)
+                << "Vsync ID incorrect for frame timeline " << i;
+        if (i == vsyncEventData.preferredFrameTimelineIndex) {
+            EXPECT_EQ(vsyncEventData.frameTimelines[i].deadlineTimestamp, preferredDeadline)
+                    << "Preferred deadline timestamp incorrect" << i;
+            EXPECT_EQ(vsyncEventData.frameTimelines[i].expectedPresentationTime,
+                      preferredExpectedPresentationTime)
+                    << "Preferred expected vsync timestamp incorrect" << i;
+        }
+    }
+}
+
 TEST_F(EventThreadTest, setVsyncRateZeroPostsNoVSyncEventsToThatConnection) {
     // Create a first connection, register it, and request a vsync rate of zero.
     ConnectionEventRecorder firstConnectionEventRecorder{0};
@@ -363,7 +473,7 @@
     // Send a vsync event. EventThread should then make a call to the
     // interceptor, and the second connection. The first connection should not
     // get the event.
-    mCallback->onVSyncEvent(123, 456, 0);
+    mCallback->onVSyncEvent(123, {456, 0});
     expectInterceptCallReceived(123);
     EXPECT_FALSE(firstConnectionEventRecorder.waitForUnexpectedCall().has_value());
     expectVsyncEventReceivedByConnection("secondConnection", secondConnectionEventRecorder, 123,
@@ -378,19 +488,19 @@
 
     // Send a vsync event. EventThread should then make a call to the
     // interceptor, and the connection.
-    mCallback->onVSyncEvent(123, 456, 789);
+    mCallback->onVSyncEvent(123, {456, 789});
     expectInterceptCallReceived(123);
     expectThrottleVsyncReceived(456, mConnectionUid);
     expectVsyncEventReceivedByConnection(123, 1u);
 
     // A second event should go to the same places.
-    mCallback->onVSyncEvent(456, 123, 0);
+    mCallback->onVSyncEvent(456, {123, 0});
     expectInterceptCallReceived(456);
     expectThrottleVsyncReceived(123, mConnectionUid);
     expectVsyncEventReceivedByConnection(456, 2u);
 
     // A third event should go to the same places.
-    mCallback->onVSyncEvent(789, 777, 111);
+    mCallback->onVSyncEvent(789, {777, 111});
     expectInterceptCallReceived(789);
     expectThrottleVsyncReceived(777, mConnectionUid);
     expectVsyncEventReceivedByConnection(789, 3u);
@@ -403,25 +513,25 @@
     expectVSyncSetEnabledCallReceived(true);
 
     // The first event will be seen by the interceptor, and not the connection.
-    mCallback->onVSyncEvent(123, 456, 789);
+    mCallback->onVSyncEvent(123, {456, 789});
     expectInterceptCallReceived(123);
     EXPECT_FALSE(mConnectionEventCallRecorder.waitForUnexpectedCall().has_value());
     EXPECT_FALSE(mThrottleVsyncCallRecorder.waitForUnexpectedCall().has_value());
 
     // The second event will be seen by the interceptor and the connection.
-    mCallback->onVSyncEvent(456, 123, 0);
+    mCallback->onVSyncEvent(456, {123, 0});
     expectInterceptCallReceived(456);
     expectVsyncEventReceivedByConnection(456, 2u);
     EXPECT_FALSE(mThrottleVsyncCallRecorder.waitForUnexpectedCall().has_value());
 
     // The third event will be seen by the interceptor, and not the connection.
-    mCallback->onVSyncEvent(789, 777, 744);
+    mCallback->onVSyncEvent(789, {777, 744});
     expectInterceptCallReceived(789);
     EXPECT_FALSE(mConnectionEventCallRecorder.waitForUnexpectedCall().has_value());
     EXPECT_FALSE(mThrottleVsyncCallRecorder.waitForUnexpectedCall().has_value());
 
     // The fourth event will be seen by the interceptor and the connection.
-    mCallback->onVSyncEvent(101112, 7847, 86);
+    mCallback->onVSyncEvent(101112, {7847, 86});
     expectInterceptCallReceived(101112);
     expectVsyncEventReceivedByConnection(101112, 4u);
 }
@@ -436,7 +546,7 @@
     mConnection = nullptr;
 
     // The first event will be seen by the interceptor, and not the connection.
-    mCallback->onVSyncEvent(123, 456, 789);
+    mCallback->onVSyncEvent(123, {456, 789});
     expectInterceptCallReceived(123);
     EXPECT_FALSE(mConnectionEventCallRecorder.waitForUnexpectedCall().has_value());
 
@@ -454,13 +564,13 @@
 
     // The first event will be seen by the interceptor, and by the connection,
     // which then returns an error.
-    mCallback->onVSyncEvent(123, 456, 789);
+    mCallback->onVSyncEvent(123, {456, 789});
     expectInterceptCallReceived(123);
     expectVsyncEventReceivedByConnection("errorConnection", errorConnectionEventRecorder, 123, 1u);
 
     // A subsequent event will be seen by the interceptor and not by the
     // connection.
-    mCallback->onVSyncEvent(456, 123, 0);
+    mCallback->onVSyncEvent(456, {123, 0});
     expectInterceptCallReceived(456);
     EXPECT_FALSE(errorConnectionEventRecorder.waitForUnexpectedCall().has_value());
 
@@ -485,7 +595,7 @@
 
     // The first event will be seen by the interceptor, and by the connection,
     // which then returns an error.
-    mCallback->onVSyncEvent(123, 456, 789);
+    mCallback->onVSyncEvent(123, {456, 789});
     expectInterceptCallReceived(123);
     expectVsyncEventReceivedByConnection("errorConnection", errorConnectionEventRecorder, 123, 1u);
     expectVsyncEventReceivedByConnection("successConnection", secondConnectionEventRecorder, 123,
@@ -503,13 +613,13 @@
 
     // The first event will be seen by the interceptor, and by the connection,
     // which then returns an non-fatal error.
-    mCallback->onVSyncEvent(123, 456, 789);
+    mCallback->onVSyncEvent(123, {456, 789});
     expectInterceptCallReceived(123);
     expectVsyncEventReceivedByConnection("errorConnection", errorConnectionEventRecorder, 123, 1u);
 
     // A subsequent event will be seen by the interceptor, and by the connection,
     // which still then returns an non-fatal error.
-    mCallback->onVSyncEvent(456, 123, 0);
+    mCallback->onVSyncEvent(456, {123, 0});
     expectInterceptCallReceived(456);
     expectVsyncEventReceivedByConnection("errorConnection", errorConnectionEventRecorder, 456, 2u);
 
@@ -633,7 +743,7 @@
 
     // Use the received callback to signal a first vsync event.
     // The interceptor should receive the event, but not the connection.
-    mCallback->onVSyncEvent(123, 456, 789);
+    mCallback->onVSyncEvent(123, {456, 789});
     expectInterceptCallReceived(123);
     expectThrottleVsyncReceived(456, mThrottledConnectionUid);
     mThrottledConnectionEventCallRecorder.waitForUnexpectedCall();
@@ -641,7 +751,7 @@
     // Use the received callback to signal a second vsync event.
     // The interceptor should receive the event, but the connection should
     // not as it was only interested in the first.
-    mCallback->onVSyncEvent(456, 123, 0);
+    mCallback->onVSyncEvent(456, {123, 0});
     expectInterceptCallReceived(456);
     expectThrottleVsyncReceived(123, mThrottledConnectionUid);
     EXPECT_FALSE(mConnectionEventCallRecorder.waitForUnexpectedCall().has_value());
diff --git a/services/surfaceflinger/tests/unittests/FlagManagerTest.cpp b/services/surfaceflinger/tests/unittests/FlagManagerTest.cpp
new file mode 100644
index 0000000..0905cd1
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/FlagManagerTest.cpp
@@ -0,0 +1,143 @@
+/*
+ * 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.
+ */
+
+#include <cstdint>
+#undef LOG_TAG
+#define LOG_TAG "FlagManagerTest"
+
+#include "FlagManager.h"
+
+#include <android-base/properties.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <log/log.h>
+#include <server_configurable_flags/get_flags.h>
+#include <optional>
+
+namespace android {
+
+using testing::Return;
+
+class MockFlagManager : public FlagManager {
+public:
+    MockFlagManager() = default;
+    ~MockFlagManager() = default;
+
+    MOCK_METHOD(std::string, getServerConfigurableFlag, (const std::string& experimentFlagName),
+                (const, override));
+};
+
+class FlagManagerTest : public testing::Test {
+public:
+    FlagManagerTest();
+    ~FlagManagerTest() override;
+    std::unique_ptr<MockFlagManager> mFlagManager;
+
+    template <typename T>
+    T getValue(const std::string& experimentFlagName, std::optional<T> systemPropertyOpt,
+               T defaultValue);
+};
+
+FlagManagerTest::FlagManagerTest() {
+    const ::testing::TestInfo* const test_info =
+            ::testing::UnitTest::GetInstance()->current_test_info();
+    ALOGD("**** Setting up for %s.%s\n", test_info->test_case_name(), test_info->name());
+    mFlagManager = std::make_unique<MockFlagManager>();
+}
+
+FlagManagerTest::~FlagManagerTest() {
+    const ::testing::TestInfo* const test_info =
+            ::testing::UnitTest::GetInstance()->current_test_info();
+    ALOGD("**** Tearing down after %s.%s\n", test_info->test_case_name(), test_info->name());
+}
+
+template <typename T>
+T FlagManagerTest::getValue(const std::string& experimentFlagName,
+                            std::optional<T> systemPropertyOpt, T defaultValue) {
+    return mFlagManager->getValue(experimentFlagName, systemPropertyOpt, defaultValue);
+}
+
+namespace {
+TEST_F(FlagManagerTest, getValue_bool_default) {
+    EXPECT_CALL(*mFlagManager, getServerConfigurableFlag).Times(1).WillOnce(Return(""));
+    const bool defaultValue = false;
+    std::optional<bool> systemPropertyValue = std::nullopt;
+    const bool result = FlagManagerTest::getValue("test_flag", systemPropertyValue, defaultValue);
+    ASSERT_EQ(result, defaultValue);
+}
+
+TEST_F(FlagManagerTest, getValue_bool_sysprop) {
+    const bool defaultValue = false;
+    std::optional<bool> systemPropertyValue = std::make_optional(true);
+    const bool result = FlagManagerTest::getValue("test_flag", systemPropertyValue, defaultValue);
+    ASSERT_EQ(result, true);
+}
+
+TEST_F(FlagManagerTest, getValue_bool_experiment) {
+    EXPECT_CALL(*mFlagManager, getServerConfigurableFlag).Times(1).WillOnce(Return("1"));
+    const bool defaultValue = false;
+    std::optional<bool> systemPropertyValue = std::nullopt;
+    const bool result = FlagManagerTest::getValue("test_flag", systemPropertyValue, defaultValue);
+    ASSERT_EQ(result, true);
+}
+
+TEST_F(FlagManagerTest, getValue_int32_default) {
+    EXPECT_CALL(*mFlagManager, getServerConfigurableFlag).Times(1).WillOnce(Return(""));
+    int32_t defaultValue = 30;
+    std::optional<int32_t> systemPropertyValue = std::nullopt;
+    int32_t result = FlagManagerTest::getValue("test_flag", systemPropertyValue, defaultValue);
+    ASSERT_EQ(result, defaultValue);
+}
+
+TEST_F(FlagManagerTest, getValue_int32_sysprop) {
+    int32_t defaultValue = 30;
+    std::optional<int32_t> systemPropertyValue = std::make_optional(10);
+    int32_t result = FlagManagerTest::getValue("test_flag", systemPropertyValue, defaultValue);
+    ASSERT_EQ(result, 10);
+}
+
+TEST_F(FlagManagerTest, getValue_int32_experiment) {
+    EXPECT_CALL(*mFlagManager, getServerConfigurableFlag).Times(1).WillOnce(Return("50"));
+    std::int32_t defaultValue = 30;
+    std::optional<std::int32_t> systemPropertyValue = std::nullopt;
+    std::int32_t result = FlagManagerTest::getValue("test_flag", systemPropertyValue, defaultValue);
+    ASSERT_EQ(result, 50);
+}
+
+TEST_F(FlagManagerTest, getValue_int64_default) {
+    EXPECT_CALL(*mFlagManager, getServerConfigurableFlag).Times(1).WillOnce(Return(""));
+    int64_t defaultValue = 30;
+    std::optional<int64_t> systemPropertyValue = std::nullopt;
+    int64_t result = getValue("flag_name", systemPropertyValue, defaultValue);
+    ASSERT_EQ(result, defaultValue);
+}
+
+TEST_F(FlagManagerTest, getValue_int64_sysprop) {
+    int64_t defaultValue = 30;
+    std::optional<int64_t> systemPropertyValue = std::make_optional(10);
+    int64_t result = getValue("flag_name", systemPropertyValue, defaultValue);
+    ASSERT_EQ(result, 10);
+}
+
+TEST_F(FlagManagerTest, getValue_int64_experiment) {
+    EXPECT_CALL(*mFlagManager, getServerConfigurableFlag).Times(1).WillOnce(Return("50"));
+    int64_t defaultValue = 30;
+    std::optional<int64_t> systemPropertyValue = std::nullopt;
+    int64_t result = getValue("flag_name", systemPropertyValue, defaultValue);
+    ASSERT_EQ(result, 50);
+}
+} // namespace
+} // namespace android
diff --git a/services/surfaceflinger/tests/unittests/FpsOps.h b/services/surfaceflinger/tests/unittests/FpsOps.h
new file mode 100644
index 0000000..7c737dc
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/FpsOps.h
@@ -0,0 +1,49 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <scheduler/Fps.h>
+
+namespace android {
+
+// Pull Fps operators into its namespace to enable ADL for EXPECT_EQ, EXPECT_LT, etc.
+
+inline bool operator==(Fps lhs, Fps rhs) {
+    return fps_approx_ops::operator==(lhs, rhs);
+}
+
+inline bool operator<(Fps lhs, Fps rhs) {
+    return fps_approx_ops::operator<(lhs, rhs);
+}
+
+inline bool operator!=(Fps lhs, Fps rhs) {
+    return fps_approx_ops::operator!=(lhs, rhs);
+}
+
+inline bool operator>(Fps lhs, Fps rhs) {
+    return fps_approx_ops::operator>(lhs, rhs);
+}
+
+inline bool operator<=(Fps lhs, Fps rhs) {
+    return fps_approx_ops::operator<=(lhs, rhs);
+}
+
+inline bool operator>=(Fps lhs, Fps rhs) {
+    return fps_approx_ops::operator>=(lhs, rhs);
+}
+
+} // namespace android
diff --git a/services/surfaceflinger/tests/unittests/FpsReporterTest.cpp b/services/surfaceflinger/tests/unittests/FpsReporterTest.cpp
index 010c675..bb1f432 100644
--- a/services/surfaceflinger/tests/unittests/FpsReporterTest.cpp
+++ b/services/surfaceflinger/tests/unittests/FpsReporterTest.cpp
@@ -17,6 +17,8 @@
 #undef LOG_TAG
 #define LOG_TAG "FpsReporterTest"
 
+#include <chrono>
+
 #include <android/gui/BnFpsListener.h>
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
@@ -36,6 +38,8 @@
 
 namespace android {
 
+using namespace std::chrono_literals;
+
 using testing::_;
 using testing::DoAll;
 using testing::Mock;
@@ -114,8 +118,7 @@
 
 sp<BufferStateLayer> FpsReporterTest::createBufferStateLayer(LayerMetadata metadata = {}) {
     sp<Client> client;
-    LayerCreationArgs args(mFlinger.flinger(), client, "buffer-state-layer", WIDTH, HEIGHT,
-                           LAYER_FLAGS, metadata);
+    LayerCreationArgs args(mFlinger.flinger(), client, "buffer-state-layer", LAYER_FLAGS, metadata);
     return new BufferStateLayer(args);
 }
 
diff --git a/services/surfaceflinger/tests/unittests/FpsTest.cpp b/services/surfaceflinger/tests/unittests/FpsTest.cpp
index db732cf..2193c9d 100644
--- a/services/surfaceflinger/tests/unittests/FpsTest.cpp
+++ b/services/surfaceflinger/tests/unittests/FpsTest.cpp
@@ -14,85 +14,68 @@
  * limitations under the License.
  */
 
-#include "Fps.h"
-
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
 
+#include <scheduler/Fps.h>
+
+#include "FpsOps.h"
+
 namespace android {
 
 TEST(FpsTest, construct) {
-    Fps fpsDefault;
-    EXPECT_FALSE(fpsDefault.isValid());
+    EXPECT_FALSE(Fps().isValid());
 
-    Fps fps1(60.0f);
-    EXPECT_TRUE(fps1.isValid());
-    Fps fps2 = Fps::fromPeriodNsecs(static_cast<nsecs_t>(1e9f / 60.0f));
-    EXPECT_TRUE(fps2.isValid());
-    EXPECT_TRUE(fps1.equalsWithMargin(fps2));
+    EXPECT_FALSE((0_Hz).isValid());
+    EXPECT_TRUE((120_Hz).isValid());
+    EXPECT_TRUE((0.5_Hz).isValid());
+
+    EXPECT_FALSE(Fps::fromPeriodNsecs(0).isValid());
+
+    const Fps fps = Fps::fromPeriodNsecs(16'666'667);
+    EXPECT_TRUE(fps.isValid());
+    EXPECT_EQ(fps, 60_Hz);
 }
 
 TEST(FpsTest, compare) {
-    constexpr float kEpsilon = 1e-4f;
-    const Fps::EqualsInBuckets equalsInBuckets;
-    const Fps::EqualsWithMargin equalsWithMargin;
+    EXPECT_EQ(60_Hz, 60_Hz);
+    EXPECT_EQ(60_Hz, 59.9999_Hz);
+    EXPECT_EQ(60_Hz, 60.0001_Hz);
 
-    EXPECT_TRUE(Fps(60.0f).equalsWithMargin(Fps(60.f)));
-    EXPECT_TRUE(Fps(60.0f).equalsWithMargin(Fps(60.f - kEpsilon)));
-    EXPECT_TRUE(Fps(60.0f).equalsWithMargin(Fps(60.f + kEpsilon)));
+    EXPECT_LE(60_Hz, 60_Hz);
+    EXPECT_LE(60_Hz, 59.9999_Hz);
+    EXPECT_LE(60_Hz, 60.0001_Hz);
 
-    EXPECT_TRUE(equalsInBuckets(Fps(60.0f), Fps(60.0f)));
-    EXPECT_TRUE(equalsInBuckets(Fps(60.0f), Fps(60.0f - kEpsilon)));
-    EXPECT_TRUE(equalsInBuckets(Fps(60.0f), Fps(60.0f + kEpsilon)));
+    EXPECT_GE(60_Hz, 60_Hz);
+    EXPECT_GE(60_Hz, 59.9999_Hz);
+    EXPECT_GE(60_Hz, 60.0001_Hz);
 
-    EXPECT_TRUE(equalsWithMargin(Fps(60.0f), Fps(60.0f)));
-    EXPECT_TRUE(equalsWithMargin(Fps(60.0f), Fps(60.0f - kEpsilon)));
-    EXPECT_TRUE(equalsWithMargin(Fps(60.0f), Fps(60.0f + kEpsilon)));
-
-    EXPECT_TRUE(Fps(60.0f).lessThanOrEqualWithMargin(Fps(60.f + kEpsilon)));
-    EXPECT_TRUE(Fps(60.0f).lessThanOrEqualWithMargin(Fps(60.f)));
-    EXPECT_TRUE(Fps(60.0f).lessThanOrEqualWithMargin(Fps(60.f - kEpsilon)));
-
-    EXPECT_TRUE(Fps(60.0f).greaterThanOrEqualWithMargin(Fps(60.f + kEpsilon)));
-    EXPECT_TRUE(Fps(60.0f).greaterThanOrEqualWithMargin(Fps(60.f)));
-    EXPECT_TRUE(Fps(60.0f).greaterThanOrEqualWithMargin(Fps(60.f - kEpsilon)));
-
-    // Fps with difference of 1 should be different
-    EXPECT_FALSE(Fps(60.0f).equalsWithMargin(Fps(61.f)));
-    EXPECT_TRUE(Fps(60.0f).lessThanWithMargin(Fps(61.f)));
-    EXPECT_TRUE(Fps(60.0f).greaterThanWithMargin(Fps(59.f)));
+    // Fps with difference of 1 should be different.
+    EXPECT_NE(60_Hz, 61_Hz);
+    EXPECT_LT(60_Hz, 61_Hz);
+    EXPECT_GT(60_Hz, 59_Hz);
 
     // These are common refresh rates which should be different.
-    EXPECT_FALSE(Fps(60.0f).equalsWithMargin(Fps(59.94f)));
-    EXPECT_TRUE(Fps(60.0f).greaterThanWithMargin(Fps(59.94f)));
-    EXPECT_FALSE(equalsInBuckets(Fps(60.0f), Fps(59.94f)));
-    EXPECT_FALSE(equalsWithMargin(Fps(60.0f), Fps(59.94f)));
-    EXPECT_NE(std::hash<Fps>()(Fps(60.0f)), std::hash<Fps>()(Fps(59.94f)));
-
-    EXPECT_FALSE(Fps(30.0f).equalsWithMargin(Fps(29.97f)));
-    EXPECT_TRUE(Fps(30.0f).greaterThanWithMargin(Fps(29.97f)));
-    EXPECT_FALSE(equalsInBuckets(Fps(30.0f), Fps(29.97f)));
-    EXPECT_FALSE(equalsWithMargin(Fps(30.0f), Fps(29.97f)));
-    EXPECT_NE(std::hash<Fps>()(Fps(30.0f)), std::hash<Fps>()(Fps(29.97f)));
+    EXPECT_NE(60_Hz, 59.94_Hz);
+    EXPECT_GT(60_Hz, 59.94_Hz);
+    EXPECT_NE(30_Hz, 29.97_Hz);
+    EXPECT_GT(30_Hz, 29.97_Hz);
 }
 
 TEST(FpsTest, getIntValue) {
-    EXPECT_EQ(30, Fps(30.1f).getIntValue());
-    EXPECT_EQ(31, Fps(30.9f).getIntValue());
-    EXPECT_EQ(31, Fps(30.5f).getIntValue());
+    EXPECT_EQ(30, (30.1_Hz).getIntValue());
+    EXPECT_EQ(31, (30.9_Hz).getIntValue());
+    EXPECT_EQ(31, (30.5_Hz).getIntValue());
 }
 
-TEST(FpsTest, equalsInBucketsImpliesEqualHashes) {
-    constexpr float kStep = 1e-4f;
-    const Fps::EqualsInBuckets equals;
-    for (float fps = 30.0f; fps < 31.0f; fps += kStep) {
-        const Fps left(fps);
-        const Fps right(fps + kStep);
-        if (equals(left, right)) {
-            ASSERT_EQ(std::hash<Fps>()(left), std::hash<Fps>()(right))
-                    << "left= " << left << " right=" << right;
-        }
-    }
+TEST(FpsTest, range) {
+    const auto fps = Fps::fromPeriodNsecs(16'666'665);
+
+    EXPECT_TRUE((FpsRange{60.000004_Hz, 60.000004_Hz}.includes(fps)));
+    EXPECT_TRUE((FpsRange{59_Hz, 60.1_Hz}.includes(fps)));
+    EXPECT_FALSE((FpsRange{75_Hz, 90_Hz}.includes(fps)));
+    EXPECT_FALSE((FpsRange{60.0011_Hz, 90_Hz}.includes(fps)));
+    EXPECT_FALSE((FpsRange{50_Hz, 59.998_Hz}.includes(fps)));
 }
 
 } // namespace android
diff --git a/services/surfaceflinger/tests/unittests/FrameRateOverrideMappingsTest.cpp b/services/surfaceflinger/tests/unittests/FrameRateOverrideMappingsTest.cpp
new file mode 100644
index 0000000..a581c7a
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/FrameRateOverrideMappingsTest.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.
+ */
+
+#undef LOG_TAG
+#define LOG_TAG "FrameRateOverrideMappingsTest"
+
+#include <gtest/gtest.h>
+#include <unordered_map>
+
+#include "Scheduler/FrameRateOverrideMappings.h"
+
+namespace android::scheduler {
+
+using FrameRateOverride = DisplayEventReceiver::Event::FrameRateOverride;
+using UidToFrameRateOverride = std::map<uid_t, Fps>;
+
+class FrameRateOverrideMappingsTest : public testing::Test {
+protected:
+    FrameRateOverrideMappings mFrameRateOverrideMappings;
+    UidToFrameRateOverride mFrameRateOverrideByContent;
+};
+
+namespace {
+TEST_F(FrameRateOverrideMappingsTest, testUpdateFrameRateOverridesByContent) {
+    mFrameRateOverrideByContent.clear();
+    mFrameRateOverrideByContent.emplace(0, 30.0_Hz);
+    mFrameRateOverrideByContent.emplace(1, 60.0_Hz);
+    ASSERT_TRUE(mFrameRateOverrideMappings.updateFrameRateOverridesByContent(
+            mFrameRateOverrideByContent));
+
+    ASSERT_TRUE(isApproxEqual(30.0_Hz,
+                              *mFrameRateOverrideMappings.getFrameRateOverrideForUid(
+                                      0, /*supportsFrameRateOverrideByContent*/ true)));
+    ASSERT_TRUE(isApproxEqual(60.0_Hz,
+                              *mFrameRateOverrideMappings.getFrameRateOverrideForUid(
+                                      1, /*supportsFrameRateOverrideByContent*/ true)));
+    ASSERT_EQ(std::nullopt,
+              mFrameRateOverrideMappings
+                      .getFrameRateOverrideForUid(1, /*supportsFrameRateOverrideByContent*/ false));
+    ASSERT_EQ(std::nullopt,
+              mFrameRateOverrideMappings
+                      .getFrameRateOverrideForUid(3, /*supportsFrameRateOverrideByContent*/ true));
+    ASSERT_EQ(std::nullopt,
+              mFrameRateOverrideMappings
+                      .getFrameRateOverrideForUid(3, /*supportsFrameRateOverrideByContent*/ false));
+}
+
+TEST_F(FrameRateOverrideMappingsTest, testSetGameModeRefreshRateForUid) {
+    mFrameRateOverrideMappings.setGameModeRefreshRateForUid({1, 30.0f});
+    mFrameRateOverrideMappings.setGameModeRefreshRateForUid({2, 90.0f});
+
+    ASSERT_TRUE(isApproxEqual(30.0_Hz,
+                              *mFrameRateOverrideMappings.getFrameRateOverrideForUid(
+                                      1, /*supportsFrameRateOverrideByContent*/ true)));
+    ASSERT_TRUE(isApproxEqual(90.0_Hz,
+                              *mFrameRateOverrideMappings.getFrameRateOverrideForUid(
+                                      2, /*supportsFrameRateOverrideByContent*/ false)));
+    ASSERT_EQ(std::nullopt,
+              mFrameRateOverrideMappings
+                      .getFrameRateOverrideForUid(0, /*supportsFrameRateOverrideByContent*/ true));
+    ASSERT_EQ(std::nullopt,
+              mFrameRateOverrideMappings
+                      .getFrameRateOverrideForUid(0, /*supportsFrameRateOverrideByContent*/ false));
+}
+
+TEST_F(FrameRateOverrideMappingsTest, testSetPreferredRefreshRateForUid) {
+    mFrameRateOverrideMappings.setPreferredRefreshRateForUid({0, 60.0f});
+    mFrameRateOverrideMappings.setPreferredRefreshRateForUid({2, 120.0f});
+
+    ASSERT_TRUE(isApproxEqual(60.0_Hz,
+                              *mFrameRateOverrideMappings.getFrameRateOverrideForUid(
+                                      0, /*supportsFrameRateOverrideByContent*/ true)));
+    ASSERT_TRUE(isApproxEqual(120.0_Hz,
+                              *mFrameRateOverrideMappings.getFrameRateOverrideForUid(
+                                      2, /*supportsFrameRateOverrideByContent*/ false)));
+    ASSERT_EQ(std::nullopt,
+              mFrameRateOverrideMappings
+                      .getFrameRateOverrideForUid(1, /*supportsFrameRateOverrideByContent*/ true));
+    ASSERT_EQ(std::nullopt,
+              mFrameRateOverrideMappings
+                      .getFrameRateOverrideForUid(1, /*supportsFrameRateOverrideByContent*/ false));
+}
+
+TEST_F(FrameRateOverrideMappingsTest, testGetFrameRateOverrideForUidMixed) {
+    mFrameRateOverrideByContent.clear();
+    mFrameRateOverrideByContent.emplace(0, 30.0_Hz);
+    mFrameRateOverrideByContent.emplace(1, 60.0_Hz);
+    mFrameRateOverrideByContent.emplace(2, 45.0_Hz);
+    mFrameRateOverrideByContent.emplace(5, 120.0_Hz);
+    ASSERT_TRUE(mFrameRateOverrideMappings.updateFrameRateOverridesByContent(
+            mFrameRateOverrideByContent));
+
+    std::vector<FrameRateOverride> allFrameRateOverrides;
+    ASSERT_EQ(allFrameRateOverrides,
+              mFrameRateOverrideMappings.getAllFrameRateOverrides(
+                      /*supportsFrameRateOverrideByContent*/ false));
+    allFrameRateOverrides = {{0, 30.0f}, {1, 60.0f}, {2, 45.0f}, {5, 120.0f}};
+    ASSERT_EQ(allFrameRateOverrides,
+              mFrameRateOverrideMappings.getAllFrameRateOverrides(
+                      /*supportsFrameRateOverrideByContent*/ true));
+
+    mFrameRateOverrideMappings.setGameModeRefreshRateForUid({1, 30.0f});
+    mFrameRateOverrideMappings.setGameModeRefreshRateForUid({2, 90.0f});
+    mFrameRateOverrideMappings.setGameModeRefreshRateForUid({4, 120.0f});
+
+    allFrameRateOverrides.clear();
+    allFrameRateOverrides = {{1, 30.0f}, {2, 90.0f}, {4, 120.0f}};
+    ASSERT_EQ(allFrameRateOverrides,
+              mFrameRateOverrideMappings.getAllFrameRateOverrides(
+                      /*supportsFrameRateOverrideByContent*/ false));
+    allFrameRateOverrides.clear();
+    allFrameRateOverrides = {{1, 30.0f}, {2, 90.0f}, {4, 120.0f}, {0, 30.0f}, {5, 120.0f}};
+    ASSERT_EQ(allFrameRateOverrides,
+              mFrameRateOverrideMappings.getAllFrameRateOverrides(
+                      /*supportsFrameRateOverrideByContent*/ true));
+
+    mFrameRateOverrideMappings.setPreferredRefreshRateForUid({0, 60.0f});
+    mFrameRateOverrideMappings.setPreferredRefreshRateForUid({2, 120.0f});
+    mFrameRateOverrideMappings.setPreferredRefreshRateForUid({3, 30.0f});
+
+    allFrameRateOverrides.clear();
+    allFrameRateOverrides = {{0, 60.0f}, {2, 120.0f}, {3, 30.0f}, {1, 30.0f}, {4, 120.0f}};
+    ASSERT_EQ(allFrameRateOverrides,
+              mFrameRateOverrideMappings.getAllFrameRateOverrides(
+                      /*supportsFrameRateOverrideByContent*/ false));
+    allFrameRateOverrides.clear();
+    allFrameRateOverrides = {{0, 60.0f}, {2, 120.0f}, {3, 30.0f},
+                             {1, 30.0f}, {4, 120.0f}, {5, 120.0f}};
+    ASSERT_EQ(allFrameRateOverrides,
+              mFrameRateOverrideMappings.getAllFrameRateOverrides(
+                      /*supportsFrameRateOverrideByContent*/ true));
+
+    ASSERT_TRUE(isApproxEqual(60.0_Hz,
+                              *mFrameRateOverrideMappings.getFrameRateOverrideForUid(
+                                      0, /*supportsFrameRateOverrideByContent*/ true)));
+    ASSERT_TRUE(isApproxEqual(30.0_Hz,
+                              *mFrameRateOverrideMappings.getFrameRateOverrideForUid(
+                                      1, /*supportsFrameRateOverrideByContent*/ true)));
+    ASSERT_TRUE(isApproxEqual(120.0_Hz,
+                              *mFrameRateOverrideMappings.getFrameRateOverrideForUid(
+                                      2, /*supportsFrameRateOverrideByContent*/ true)));
+    ASSERT_TRUE(isApproxEqual(30.0_Hz,
+                              *mFrameRateOverrideMappings.getFrameRateOverrideForUid(
+                                      3, /*supportsFrameRateOverrideByContent*/ true)));
+    ASSERT_TRUE(isApproxEqual(120.0_Hz,
+                              *mFrameRateOverrideMappings.getFrameRateOverrideForUid(
+                                      4, /*supportsFrameRateOverrideByContent*/ true)));
+    ASSERT_TRUE(isApproxEqual(120.0_Hz,
+                              *mFrameRateOverrideMappings.getFrameRateOverrideForUid(
+                                      5, /*supportsFrameRateOverrideByContent*/ true)));
+
+    ASSERT_TRUE(isApproxEqual(60.0_Hz,
+                              *mFrameRateOverrideMappings.getFrameRateOverrideForUid(
+                                      0, /*supportsFrameRateOverrideByContent*/ false)));
+    ASSERT_TRUE(isApproxEqual(30.0_Hz,
+                              *mFrameRateOverrideMappings.getFrameRateOverrideForUid(
+                                      1, /*supportsFrameRateOverrideByContent*/ false)));
+    ASSERT_TRUE(isApproxEqual(120.0_Hz,
+                              *mFrameRateOverrideMappings.getFrameRateOverrideForUid(
+                                      2, /*supportsFrameRateOverrideByContent*/ false)));
+    ASSERT_TRUE(isApproxEqual(30.0_Hz,
+                              *mFrameRateOverrideMappings.getFrameRateOverrideForUid(
+                                      3, /*supportsFrameRateOverrideByContent*/ false)));
+    ASSERT_TRUE(isApproxEqual(120.0_Hz,
+                              *mFrameRateOverrideMappings.getFrameRateOverrideForUid(
+                                      4, /*supportsFrameRateOverrideByContent*/ false)));
+    ASSERT_EQ(std::nullopt,
+              mFrameRateOverrideMappings
+                      .getFrameRateOverrideForUid(5, /*supportsFrameRateOverrideByContent*/ false));
+}
+} // namespace
+} // namespace android::scheduler
diff --git a/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp b/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp
index 97b60e0..f1efa92 100644
--- a/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp
+++ b/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp
@@ -66,9 +66,10 @@
     }
 
     void SetUp() override {
+        constexpr bool kUseBootTimeClock = true;
         mTimeStats = std::make_shared<mock::TimeStats>();
         mFrameTimeline = std::make_unique<impl::FrameTimeline>(mTimeStats, kSurfaceFlingerPid,
-                                                               kTestThresholds);
+                                                               kTestThresholds, !kUseBootTimeClock);
         mFrameTimeline->registerDataSource();
         mTokenManager = &mFrameTimeline->mTokenManager;
         mTraceCookieCounter = &mFrameTimeline->mTraceCookieCounter;
@@ -169,13 +170,14 @@
 
 static const std::string sLayerNameOne = "layer1";
 static const std::string sLayerNameTwo = "layer2";
-static constexpr const uid_t sUidOne = 0;
-static constexpr pid_t sPidOne = 10;
-static constexpr pid_t sPidTwo = 20;
-static constexpr int32_t sInputEventId = 5;
-static constexpr int32_t sLayerIdOne = 1;
-static constexpr int32_t sLayerIdTwo = 2;
-static constexpr int32_t sGameMode = 0;
+
+constexpr const uid_t sUidOne = 0;
+constexpr pid_t sPidOne = 10;
+constexpr pid_t sPidTwo = 20;
+constexpr int32_t sInputEventId = 5;
+constexpr int32_t sLayerIdOne = 1;
+constexpr int32_t sLayerIdTwo = 2;
+constexpr GameMode sGameMode = GameMode::Unsupported;
 
 TEST_F(FrameTimelineTest, tokenManagerRemovesStalePredictions) {
     int64_t token1 = mTokenManager->generateTokenForPredictions({0, 0, 0});
@@ -559,7 +561,7 @@
 }
 
 TEST_F(FrameTimelineTest, presentFenceSignaled_reportsAppMiss) {
-    Fps refreshRate = Fps(11.0);
+    Fps refreshRate = 11_Hz;
     EXPECT_CALL(*mTimeStats,
                 incrementJankyFrames(TimeStats::JankyFramesInfo{refreshRate, std::nullopt, sUidOne,
                                                                 sLayerNameOne, sGameMode,
@@ -1278,7 +1280,7 @@
     validateTraceEvent(actualSurfaceFrameEnd2, protoPresentedSurfaceFrameActualEnd);
 }
 
-TEST_F(FrameTimelineTest, traceSurfaceFrame_predictionExpiredDoesNotTraceExpectedTimeline) {
+TEST_F(FrameTimelineTest, traceSurfaceFrame_predictionExpiredIsAppMissedDeadline) {
     auto tracingSession = getTracingSessionForTest();
     auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
 
@@ -1311,7 +1313,7 @@
             createProtoActualSurfaceFrameStart(traceCookie + 1, surfaceFrameToken,
                                                displayFrameToken, sPidOne, sLayerNameOne,
                                                FrameTimelineEvent::PRESENT_UNSPECIFIED, false,
-                                               false, FrameTimelineEvent::JANK_UNKNOWN,
+                                               false, FrameTimelineEvent::JANK_APP_DEADLINE_MISSED,
                                                FrameTimelineEvent::PREDICTION_EXPIRED, true);
     auto protoActualSurfaceFrameEnd = createProtoFrameEnd(traceCookie + 1);
 
diff --git a/services/surfaceflinger/tests/unittests/GameModeTest.cpp b/services/surfaceflinger/tests/unittests/GameModeTest.cpp
index 3fa1a2c..981ca1d 100644
--- a/services/surfaceflinger/tests/unittests/GameModeTest.cpp
+++ b/services/surfaceflinger/tests/unittests/GameModeTest.cpp
@@ -53,7 +53,7 @@
 
     sp<BufferStateLayer> createBufferStateLayer() {
         sp<Client> client;
-        LayerCreationArgs args(mFlinger.flinger(), client, "buffer-state-layer", 100, 100, 0,
+        LayerCreationArgs args(mFlinger.flinger(), client, "buffer-state-layer", 0,
                                LayerMetadata());
         return new BufferStateLayer(args);
     }
@@ -91,8 +91,8 @@
     }
 
     // Mocks the behavior of applying a transaction from WMShell
-    void setGameModeMetadata(sp<Layer> layer, int gameMode) {
-        mLayerMetadata.setInt32(METADATA_GAME_MODE, gameMode);
+    void setGameModeMetadata(sp<Layer> layer, GameMode gameMode) {
+        mLayerMetadata.setInt32(METADATA_GAME_MODE, static_cast<int32_t>(gameMode));
         layer->setMetadata(mLayerMetadata);
         layer->setGameModeForTree(gameMode);
     }
@@ -109,51 +109,52 @@
     sp<BufferStateLayer> childLayer2 = createBufferStateLayer();
     rootLayer->addChild(childLayer1);
     rootLayer->addChild(childLayer2);
-    rootLayer->setGameModeForTree(/*gameMode*/ 2);
+    rootLayer->setGameModeForTree(GameMode::Performance);
 
-    EXPECT_EQ(rootLayer->getGameMode(), 2);
-    EXPECT_EQ(childLayer1->getGameMode(), 2);
-    EXPECT_EQ(childLayer2->getGameMode(), 2);
+    EXPECT_EQ(rootLayer->getGameMode(), GameMode::Performance);
+    EXPECT_EQ(childLayer1->getGameMode(), GameMode::Performance);
+    EXPECT_EQ(childLayer2->getGameMode(), GameMode::Performance);
 }
 
 TEST_F(GameModeTest, AddChildAppliesGameModeFromParent) {
     sp<BufferStateLayer> rootLayer = createBufferStateLayer();
     sp<BufferStateLayer> childLayer = createBufferStateLayer();
-    rootLayer->setGameModeForTree(/*gameMode*/ 2);
+    rootLayer->setGameModeForTree(GameMode::Performance);
     rootLayer->addChild(childLayer);
 
-    EXPECT_EQ(rootLayer->getGameMode(), 2);
-    EXPECT_EQ(childLayer->getGameMode(), 2);
+    EXPECT_EQ(rootLayer->getGameMode(), GameMode::Performance);
+    EXPECT_EQ(childLayer->getGameMode(), GameMode::Performance);
 }
 
 TEST_F(GameModeTest, RemoveChildResetsGameMode) {
     sp<BufferStateLayer> rootLayer = createBufferStateLayer();
     sp<BufferStateLayer> childLayer = createBufferStateLayer();
-    rootLayer->setGameModeForTree(/*gameMode*/ 2);
+    rootLayer->setGameModeForTree(GameMode::Performance);
     rootLayer->addChild(childLayer);
 
-    EXPECT_EQ(rootLayer->getGameMode(), 2);
-    EXPECT_EQ(childLayer->getGameMode(), 2);
+    EXPECT_EQ(rootLayer->getGameMode(), GameMode::Performance);
+    EXPECT_EQ(childLayer->getGameMode(), GameMode::Performance);
 
     rootLayer->removeChild(childLayer);
-    EXPECT_EQ(childLayer->getGameMode(), 0);
+    EXPECT_EQ(childLayer->getGameMode(), GameMode::Unsupported);
 }
 
 TEST_F(GameModeTest, ReparentingDoesNotOverrideMetadata) {
     sp<BufferStateLayer> rootLayer = createBufferStateLayer();
     sp<BufferStateLayer> childLayer1 = createBufferStateLayer();
     sp<BufferStateLayer> childLayer2 = createBufferStateLayer();
-    rootLayer->setGameModeForTree(/*gameMode*/ 1);
+    rootLayer->setGameModeForTree(GameMode::Standard);
     rootLayer->addChild(childLayer1);
 
-    setGameModeMetadata(childLayer2, /*gameMode*/ 2);
+    setGameModeMetadata(childLayer2, GameMode::Performance);
     rootLayer->addChild(childLayer2);
 
-    EXPECT_EQ(rootLayer->getGameMode(), 1);
-    EXPECT_EQ(childLayer1->getGameMode(), 1);
-    EXPECT_EQ(childLayer2->getGameMode(), 2);
+    EXPECT_EQ(rootLayer->getGameMode(), GameMode::Standard);
+    EXPECT_EQ(childLayer1->getGameMode(), GameMode::Standard);
+    EXPECT_EQ(childLayer2->getGameMode(), GameMode::Performance);
 
     rootLayer->removeChild(childLayer2);
-    EXPECT_EQ(childLayer2->getGameMode(), 2);
+    EXPECT_EQ(childLayer2->getGameMode(), GameMode::Performance);
 }
-} // namespace android
\ No newline at end of file
+
+} // namespace android
diff --git a/services/surfaceflinger/tests/unittests/HWComposerTest.cpp b/services/surfaceflinger/tests/unittests/HWComposerTest.cpp
index 655baf8..5241604 100644
--- a/services/surfaceflinger/tests/unittests/HWComposerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/HWComposerTest.cpp
@@ -36,6 +36,7 @@
 #include "DisplayHardware/DisplayMode.h"
 #include "DisplayHardware/HWComposer.h"
 #include "DisplayHardware/Hal.h"
+#include "DisplayIdentificationTestHelpers.h"
 #include "mock/DisplayHardware/MockComposer.h"
 #include "mock/DisplayHardware/MockHWC2.h"
 
@@ -47,6 +48,7 @@
 
 namespace V2_1 = hardware::graphics::composer::V2_1;
 namespace V2_4 = hardware::graphics::composer::V2_4;
+namespace aidl = aidl::android::hardware::graphics::composer3;
 
 using Hwc2::Config;
 
@@ -57,6 +59,29 @@
 using ::testing::SetArgPointee;
 using ::testing::StrictMock;
 
+TEST(HWComposerTest, isHeadless) {
+    Hwc2::mock::Composer* mHal = new StrictMock<Hwc2::mock::Composer>();
+    impl::HWComposer hwc{std::unique_ptr<Hwc2::Composer>(mHal)};
+    ASSERT_TRUE(hwc.isHeadless());
+
+    const hal::HWDisplayId hwcId = 1;
+
+    EXPECT_CALL(*mHal, getDisplayIdentificationData(_, _, _))
+            .WillOnce(DoAll(SetArgPointee<2>(getExternalEdid()),
+                            Return(hardware::graphics::composer::V2_1::Error::NONE)));
+
+    EXPECT_CALL(*mHal, setVsyncEnabled(_, _));
+    EXPECT_CALL(*mHal, setClientTargetSlotCount(_));
+
+    auto info = hwc.onHotplug(hwcId, hal::Connection::CONNECTED);
+    ASSERT_TRUE(info);
+    auto displayId = info->id;
+    ASSERT_FALSE(hwc.isHeadless());
+
+    hwc.disconnectDisplay(displayId);
+    ASSERT_TRUE(hwc.isHeadless());
+}
+
 struct MockHWC2ComposerCallback final : StrictMock<HWC2::ComposerCallback> {
     MOCK_METHOD2(onComposerHalHotplug, void(hal::HWDisplayId, hal::Connection));
     MOCK_METHOD1(onComposerHalRefresh, void(hal::HWDisplayId));
@@ -65,6 +90,7 @@
     MOCK_METHOD2(onComposerHalVsyncPeriodTimingChanged,
                  void(hal::HWDisplayId, const hal::VsyncPeriodChangeTimeline&));
     MOCK_METHOD1(onComposerHalSeamlessPossible, void(hal::HWDisplayId));
+    MOCK_METHOD1(onComposerHalVsyncIdle, void(hal::HWDisplayId));
 };
 
 struct HWComposerSetCallbackTest : testing::Test {
@@ -78,7 +104,7 @@
     const std::string kMetadata2Name = "com.example.metadata.2";
     constexpr bool kMetadata2Mandatory = true;
 
-    EXPECT_CALL(*mHal, getCapabilities()).WillOnce(Return(std::vector<hal::Capability>{}));
+    EXPECT_CALL(*mHal, getCapabilities()).WillOnce(Return(std::vector<aidl::Capability>{}));
     EXPECT_CALL(*mHal, getLayerGenericMetadataKeys(_))
             .WillOnce(DoAll(SetArgPointee<0>(std::vector<hal::LayerGenericMetadataKey>{
                                     {kMetadata1Name, kMetadata1Mandatory},
@@ -86,10 +112,9 @@
                             }),
                             Return(hardware::graphics::composer::V2_4::Error::NONE)));
     EXPECT_CALL(*mHal, registerCallback(_));
-    EXPECT_CALL(*mHal, isVsyncPeriodSwitchSupported()).WillOnce(Return(false));
 
     impl::HWComposer hwc{std::unique_ptr<Hwc2::Composer>(mHal)};
-    hwc.setCallback(&mCallback);
+    hwc.setCallback(mCallback);
 
     const auto& supported = hwc.getSupportedLayerGenericMetadata();
     EXPECT_EQ(2u, supported.size());
@@ -100,14 +125,13 @@
 }
 
 TEST_F(HWComposerSetCallbackTest, handlesUnsupportedCallToGetLayerGenericMetadataKeys) {
-    EXPECT_CALL(*mHal, getCapabilities()).WillOnce(Return(std::vector<hal::Capability>{}));
+    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, registerCallback(_));
-    EXPECT_CALL(*mHal, isVsyncPeriodSwitchSupported()).WillOnce(Return(false));
 
     impl::HWComposer hwc{std::unique_ptr<Hwc2::Composer>(mHal)};
-    hwc.setCallback(&mCallback);
+    hwc.setCallback(mCallback);
 
     const auto& supported = hwc.getSupportedLayerGenericMetadata();
     EXPECT_EQ(0u, supported.size());
@@ -117,7 +141,7 @@
     static constexpr hal::HWDisplayId kDisplayId = static_cast<hal::HWDisplayId>(1001);
     static constexpr hal::HWLayerId kLayerId = static_cast<hal::HWLayerId>(1002);
 
-    HWComposerLayerTest(const std::unordered_set<hal::Capability>& capabilities)
+    HWComposerLayerTest(const std::unordered_set<aidl::Capability>& capabilities)
           : mCapabilies(capabilities) {
         EXPECT_CALL(mDisplay, getId()).WillRepeatedly(Return(kDisplayId));
     }
@@ -128,7 +152,7 @@
     }
 
     std::unique_ptr<Hwc2::mock::Composer> mHal{new StrictMock<Hwc2::mock::Composer>()};
-    const std::unordered_set<hal::Capability> mCapabilies;
+    const std::unordered_set<aidl::Capability> mCapabilies;
     StrictMock<HWC2::mock::Display> mDisplay;
     HWC2::impl::Layer mLayer{*mHal, mCapabilies, mDisplay, kLayerId};
 };
diff --git a/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp b/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp
index 02ec7fc..17511cd 100644
--- a/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp
@@ -26,10 +26,12 @@
 #include <gtest/gtest.h>
 #include <log/log.h>
 
+#include "FpsOps.h"
 #include "Scheduler/LayerHistory.h"
 #include "Scheduler/LayerInfo.h"
 #include "TestableScheduler.h"
 #include "TestableSurfaceFlinger.h"
+#include "mock/DisplayHardware/MockDisplayMode.h"
 #include "mock/MockLayer.h"
 #include "mock/MockSchedulerCallback.h"
 
@@ -37,9 +39,11 @@
 using testing::Return;
 using testing::ReturnRef;
 
-namespace android {
+namespace android::scheduler {
 
-namespace scheduler {
+using MockLayer = android::mock::MockLayer;
+
+using android::mock::createDisplayMode;
 
 class LayerHistoryTest : public testing::Test {
 protected:
@@ -50,56 +54,63 @@
     static constexpr auto REFRESH_RATE_AVERAGE_HISTORY_DURATION =
             LayerInfo::RefreshRateHistory::HISTORY_DURATION;
 
-    static constexpr Fps LO_FPS{30.f};
+    static constexpr Fps LO_FPS = 30_Hz;
     static constexpr auto LO_FPS_PERIOD = LO_FPS.getPeriodNsecs();
 
-    static constexpr Fps HI_FPS{90.f};
+    static constexpr Fps HI_FPS = 90_Hz;
     static constexpr auto HI_FPS_PERIOD = HI_FPS.getPeriodNsecs();
 
     LayerHistoryTest() { mFlinger.resetScheduler(mScheduler); }
 
-    void SetUp() override { ASSERT_TRUE(mScheduler->hasLayerHistory()); }
-
-    LayerHistory& history() { return *mScheduler->mutableLayerHistory(); }
-    const LayerHistory& history() const { return *mScheduler->mutableLayerHistory(); }
+    LayerHistory& history() { return mScheduler->mutableLayerHistory(); }
+    const LayerHistory& history() const { return mScheduler->mutableLayerHistory(); }
 
     LayerHistory::Summary summarizeLayerHistory(nsecs_t now) {
-        return history().summarize(*mScheduler->refreshRateConfigs(), now);
+        // 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);
+        std::sort(summary.begin(), summary.end(),
+                  [](const RefreshRateConfigs::LayerRequirement& a,
+                     const RefreshRateConfigs::LayerRequirement& b) -> bool {
+                      return a.name < b.name;
+                  });
+        return summary;
     }
 
     size_t layerCount() const { return mScheduler->layerHistorySize(); }
-    size_t activeLayerCount() const NO_THREAD_SAFETY_ANALYSIS { return history().mActiveLayersEnd; }
+    size_t activeLayerCount() const NO_THREAD_SAFETY_ANALYSIS {
+        return history().mActiveLayerInfos.size();
+    }
 
     auto frequentLayerCount(nsecs_t now) const NO_THREAD_SAFETY_ANALYSIS {
-        const auto& infos = history().mLayerInfos;
-        return std::count_if(infos.begin(),
-                             infos.begin() + static_cast<long>(history().mActiveLayersEnd),
-                             [now](const auto& pair) { return pair.second->isFrequent(now); });
+        const auto& infos = history().mActiveLayerInfos;
+        return std::count_if(infos.begin(), infos.end(), [now](const auto& pair) {
+            return pair.second.second->isFrequent(now);
+        });
     }
 
     auto animatingLayerCount(nsecs_t now) const NO_THREAD_SAFETY_ANALYSIS {
-        const auto& infos = history().mLayerInfos;
-        return std::count_if(infos.begin(),
-                             infos.begin() + static_cast<long>(history().mActiveLayersEnd),
-                             [now](const auto& pair) { return pair.second->isAnimating(now); });
+        const auto& infos = history().mActiveLayerInfos;
+        return std::count_if(infos.begin(), infos.end(), [now](const auto& pair) {
+            return pair.second.second->isAnimating(now);
+        });
     }
 
     void setDefaultLayerVote(Layer* layer,
                              LayerHistory::LayerVoteType vote) NO_THREAD_SAFETY_ANALYSIS {
-        for (auto& [layerUnsafe, info] : history().mLayerInfos) {
-            if (layerUnsafe == layer) {
-                info->setDefaultLayerVote(vote);
-                return;
-            }
+        auto [found, layerPair] = history().findLayer(layer->getSequence());
+        if (found != LayerHistory::LayerStatus::NotFound) {
+            layerPair->second->setDefaultLayerVote(vote);
         }
     }
 
-    auto createLayer() { return sp<mock::MockLayer>(new mock::MockLayer(mFlinger.flinger())); }
+    auto createLayer() { return sp<MockLayer>::make(mFlinger.flinger()); }
     auto createLayer(std::string name) {
-        return sp<mock::MockLayer>(new mock::MockLayer(mFlinger.flinger(), std::move(name)));
+        return sp<MockLayer>::make(mFlinger.flinger(), std::move(name));
     }
 
-    void recordFramesAndExpect(const sp<mock::MockLayer>& layer, nsecs_t& time, Fps frameRate,
+    void recordFramesAndExpect(const sp<MockLayer>& layer, nsecs_t& time, Fps frameRate,
                                Fps desiredRefreshRate, int numFrames) {
         LayerHistory::Summary summary;
         for (int i = 0; i < numFrames; i++) {
@@ -111,24 +122,15 @@
 
         ASSERT_EQ(1, summary.size());
         ASSERT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
-        ASSERT_TRUE(desiredRefreshRate.equalsWithMargin(summary[0].desiredRefreshRate))
-                << "Frame rate is " << frameRate;
+        ASSERT_EQ(desiredRefreshRate, summary[0].desiredRefreshRate);
     }
 
-    std::shared_ptr<RefreshRateConfigs> mConfigs = std::make_shared<
-            RefreshRateConfigs>(DisplayModes{DisplayMode::Builder(0)
-                                                     .setId(DisplayModeId(0))
-                                                     .setPhysicalDisplayId(PhysicalDisplayId(0))
-                                                     .setVsyncPeriod(int32_t(LO_FPS_PERIOD))
-                                                     .setGroup(0)
-                                                     .build(),
-                                             DisplayMode::Builder(1)
-                                                     .setId(DisplayModeId(1))
-                                                     .setPhysicalDisplayId(PhysicalDisplayId(0))
-                                                     .setVsyncPeriod(int32_t(HI_FPS_PERIOD))
-                                                     .setGroup(0)
-                                                     .build()},
-                                DisplayModeId(0));
+    std::shared_ptr<RefreshRateConfigs> mConfigs =
+            std::make_shared<RefreshRateConfigs>(makeModes(createDisplayMode(DisplayModeId(0),
+                                                                             LO_FPS),
+                                                           createDisplayMode(DisplayModeId(1),
+                                                                             HI_FPS)),
+                                                 DisplayModeId(0));
 
     mock::SchedulerCallback mSchedulerCallback;
 
@@ -144,10 +146,12 @@
     EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
     EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
 
+    // history().registerLayer(layer, LayerHistory::LayerVoteType::Max);
+
     EXPECT_EQ(1, layerCount());
     EXPECT_EQ(0, activeLayerCount());
 
-    const nsecs_t time = systemTime();
+    nsecs_t time = systemTime();
 
     // No layers returned if no layers are active.
     EXPECT_TRUE(summarizeLayerHistory(time).empty());
@@ -159,6 +163,7 @@
         ASSERT_EQ(1, summarizeLayerHistory(time).size());
         EXPECT_EQ(LayerHistory::LayerVoteType::Max, summarizeLayerHistory(time)[0].vote);
         EXPECT_EQ(1, activeLayerCount());
+        time += LO_FPS_PERIOD;
     }
 
     // Max is returned since we have enough history but there is no timestamp votes.
@@ -167,6 +172,7 @@
         ASSERT_EQ(1, summarizeLayerHistory(time).size());
         EXPECT_EQ(LayerHistory::LayerVoteType::Max, summarizeLayerHistory(time)[0].vote);
         EXPECT_EQ(1, activeLayerCount());
+        time += LO_FPS_PERIOD;
     }
 }
 
@@ -211,7 +217,7 @@
 
     ASSERT_EQ(1, summarizeLayerHistory(time).size());
     EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summarizeLayerHistory(time)[0].vote);
-    EXPECT_TRUE(LO_FPS.equalsWithMargin(summarizeLayerHistory(time)[0].desiredRefreshRate));
+    EXPECT_EQ(LO_FPS, summarizeLayerHistory(time)[0].desiredRefreshRate);
     EXPECT_EQ(1, activeLayerCount());
     EXPECT_EQ(1, frequentLayerCount(time));
 }
@@ -304,7 +310,7 @@
     EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
     EXPECT_CALL(*layer, getFrameRateForLayerTree())
             .WillRepeatedly(
-                    Return(Layer::FrameRate(Fps(73.4f), Layer::FrameRateCompatibility::Default)));
+                    Return(Layer::FrameRate(73.4_Hz, Layer::FrameRateCompatibility::Default)));
 
     EXPECT_EQ(1, layerCount());
     EXPECT_EQ(0, activeLayerCount());
@@ -317,7 +323,7 @@
 
     ASSERT_EQ(1, summarizeLayerHistory(time).size());
     EXPECT_EQ(LayerHistory::LayerVoteType::ExplicitDefault, summarizeLayerHistory(time)[0].vote);
-    EXPECT_TRUE(Fps(73.4f).equalsWithMargin(summarizeLayerHistory(time)[0].desiredRefreshRate));
+    EXPECT_EQ(73.4_Hz, summarizeLayerHistory(time)[0].desiredRefreshRate);
     EXPECT_EQ(1, activeLayerCount());
     EXPECT_EQ(1, frequentLayerCount(time));
 
@@ -326,7 +332,7 @@
     time += MAX_ACTIVE_LAYER_PERIOD_NS.count();
     ASSERT_EQ(1, summarizeLayerHistory(time).size());
     EXPECT_EQ(LayerHistory::LayerVoteType::ExplicitDefault, summarizeLayerHistory(time)[0].vote);
-    EXPECT_TRUE(Fps(73.4f).equalsWithMargin(summarizeLayerHistory(time)[0].desiredRefreshRate));
+    EXPECT_EQ(73.4_Hz, summarizeLayerHistory(time)[0].desiredRefreshRate);
     EXPECT_EQ(1, activeLayerCount());
     EXPECT_EQ(0, frequentLayerCount(time));
 }
@@ -336,7 +342,7 @@
     EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
     EXPECT_CALL(*layer, getFrameRateForLayerTree())
             .WillRepeatedly(Return(
-                    Layer::FrameRate(Fps(73.4f), Layer::FrameRateCompatibility::ExactOrMultiple)));
+                    Layer::FrameRate(73.4_Hz, Layer::FrameRateCompatibility::ExactOrMultiple)));
 
     EXPECT_EQ(1, layerCount());
     EXPECT_EQ(0, activeLayerCount());
@@ -350,7 +356,7 @@
     ASSERT_EQ(1, summarizeLayerHistory(time).size());
     EXPECT_EQ(LayerHistory::LayerVoteType::ExplicitExactOrMultiple,
               summarizeLayerHistory(time)[0].vote);
-    EXPECT_TRUE(Fps(73.4f).equalsWithMargin(summarizeLayerHistory(time)[0].desiredRefreshRate));
+    EXPECT_EQ(73.4_Hz, summarizeLayerHistory(time)[0].desiredRefreshRate);
     EXPECT_EQ(1, activeLayerCount());
     EXPECT_EQ(1, frequentLayerCount(time));
 
@@ -360,15 +366,15 @@
     ASSERT_EQ(1, summarizeLayerHistory(time).size());
     EXPECT_EQ(LayerHistory::LayerVoteType::ExplicitExactOrMultiple,
               summarizeLayerHistory(time)[0].vote);
-    EXPECT_TRUE(Fps(73.4f).equalsWithMargin(summarizeLayerHistory(time)[0].desiredRefreshRate));
+    EXPECT_EQ(73.4_Hz, summarizeLayerHistory(time)[0].desiredRefreshRate);
     EXPECT_EQ(1, activeLayerCount());
     EXPECT_EQ(0, frequentLayerCount(time));
 }
 
 TEST_F(LayerHistoryTest, multipleLayers) {
-    auto layer1 = createLayer();
-    auto layer2 = createLayer();
-    auto layer3 = createLayer();
+    auto layer1 = createLayer("A");
+    auto layer2 = createLayer("B");
+    auto layer3 = createLayer("C");
 
     EXPECT_CALL(*layer1, isVisible()).WillRepeatedly(Return(true));
     EXPECT_CALL(*layer1, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
@@ -412,7 +418,7 @@
     ASSERT_EQ(2, summary.size());
     EXPECT_EQ(LayerHistory::LayerVoteType::Min, summary[0].vote);
     ASSERT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[1].vote);
-    EXPECT_TRUE(HI_FPS.equalsWithMargin(summarizeLayerHistory(time)[1].desiredRefreshRate));
+    EXPECT_EQ(HI_FPS, summarizeLayerHistory(time)[1].desiredRefreshRate);
 
     EXPECT_EQ(2, activeLayerCount());
     EXPECT_EQ(1, frequentLayerCount(time));
@@ -427,7 +433,7 @@
 
     ASSERT_EQ(1, summary.size());
     EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
-    EXPECT_TRUE(LO_FPS.equalsWithMargin(summary[0].desiredRefreshRate));
+    EXPECT_EQ(LO_FPS, summary[0].desiredRefreshRate);
     EXPECT_EQ(1, activeLayerCount());
     EXPECT_EQ(1, frequentLayerCount(time));
 
@@ -446,7 +452,7 @@
 
     ASSERT_EQ(2, summary.size());
     EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
-    EXPECT_TRUE(LO_FPS.equalsWithMargin(summary[0].desiredRefreshRate));
+    EXPECT_EQ(LO_FPS, summary[0].desiredRefreshRate);
     EXPECT_EQ(LayerHistory::LayerVoteType::Max, summary[1].vote);
     EXPECT_EQ(2, activeLayerCount());
     EXPECT_EQ(2, frequentLayerCount(time));
@@ -456,9 +462,9 @@
     summary = summarizeLayerHistory(time);
     ASSERT_EQ(2, summary.size());
     EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
-    EXPECT_TRUE(LO_FPS.equalsWithMargin(summary[0].desiredRefreshRate));
+    EXPECT_EQ(LO_FPS, summary[0].desiredRefreshRate);
     EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[1].vote);
-    EXPECT_TRUE(HI_FPS.equalsWithMargin(summary[1].desiredRefreshRate));
+    EXPECT_EQ(HI_FPS, summary[1].desiredRefreshRate);
     EXPECT_EQ(2, activeLayerCount());
     EXPECT_EQ(2, frequentLayerCount(time));
 
@@ -468,9 +474,9 @@
     ASSERT_EQ(2, summary.size());
     EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
     EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
-    EXPECT_TRUE(LO_FPS.equalsWithMargin(summary[0].desiredRefreshRate));
+    EXPECT_EQ(LO_FPS, summary[0].desiredRefreshRate);
     EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[1].vote);
-    EXPECT_TRUE(HI_FPS.equalsWithMargin(summary[1].desiredRefreshRate));
+    EXPECT_EQ(HI_FPS, summary[1].desiredRefreshRate);
     EXPECT_EQ(2, layerCount());
     EXPECT_EQ(2, activeLayerCount());
     EXPECT_EQ(2, frequentLayerCount(time));
@@ -485,7 +491,7 @@
 
     ASSERT_EQ(1, summary.size());
     EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
-    EXPECT_TRUE(LO_FPS.equalsWithMargin(summary[0].desiredRefreshRate));
+    EXPECT_EQ(LO_FPS, summary[0].desiredRefreshRate);
     EXPECT_EQ(1, activeLayerCount());
     EXPECT_EQ(1, frequentLayerCount(time));
 
@@ -506,7 +512,7 @@
 
     ASSERT_EQ(1, summary.size());
     EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
-    EXPECT_TRUE(HI_FPS.equalsWithMargin(summary[0].desiredRefreshRate));
+    EXPECT_EQ(HI_FPS, summary[0].desiredRefreshRate);
     EXPECT_EQ(1, layerCount());
     EXPECT_EQ(1, activeLayerCount());
     EXPECT_EQ(1, frequentLayerCount(time));
@@ -583,12 +589,12 @@
     EXPECT_CALL(*explicitVisiblelayer, isVisible()).WillRepeatedly(Return(true));
     EXPECT_CALL(*explicitVisiblelayer, getFrameRateForLayerTree())
             .WillRepeatedly(Return(
-                    Layer::FrameRate(Fps(60.0f), Layer::FrameRateCompatibility::ExactOrMultiple)));
+                    Layer::FrameRate(60_Hz, Layer::FrameRateCompatibility::ExactOrMultiple)));
 
     EXPECT_CALL(*explicitInvisiblelayer, isVisible()).WillRepeatedly(Return(false));
     EXPECT_CALL(*explicitInvisiblelayer, getFrameRateForLayerTree())
             .WillRepeatedly(Return(
-                    Layer::FrameRate(Fps(90.0f), Layer::FrameRateCompatibility::ExactOrMultiple)));
+                    Layer::FrameRate(90_Hz, Layer::FrameRateCompatibility::ExactOrMultiple)));
 
     nsecs_t time = systemTime();
 
@@ -601,7 +607,7 @@
     ASSERT_EQ(1, summarizeLayerHistory(time).size());
     EXPECT_EQ(LayerHistory::LayerVoteType::ExplicitExactOrMultiple,
               summarizeLayerHistory(time)[0].vote);
-    EXPECT_TRUE(Fps(60.0f).equalsWithMargin(summarizeLayerHistory(time)[0].desiredRefreshRate));
+    EXPECT_EQ(60_Hz, summarizeLayerHistory(time)[0].desiredRefreshRate);
     EXPECT_EQ(2, activeLayerCount());
     EXPECT_EQ(2, frequentLayerCount(time));
 }
@@ -652,6 +658,29 @@
     EXPECT_EQ(1, animatingLayerCount(time));
 }
 
+TEST_F(LayerHistoryTest, getFramerate) {
+    auto layer = createLayer();
+
+    EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
+    EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
+
+    nsecs_t time = systemTime();
+
+    EXPECT_EQ(1, layerCount());
+    EXPECT_EQ(0, activeLayerCount());
+    EXPECT_EQ(0, frequentLayerCount(time));
+    EXPECT_EQ(0, animatingLayerCount(time));
+
+    // layer is active but infrequent.
+    for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
+        history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
+        time += MAX_FREQUENT_LAYER_PERIOD_NS.count();
+    }
+
+    float expectedFramerate = 1e9f / MAX_FREQUENT_LAYER_PERIOD_NS.count();
+    EXPECT_FLOAT_EQ(expectedFramerate, history().getLayerFramerate(time, layer->getSequence()));
+}
+
 TEST_F(LayerHistoryTest, heuristicLayer60Hz) {
     const auto layer = createLayer();
     EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
@@ -659,7 +688,7 @@
 
     nsecs_t time = systemTime();
     for (float fps = 54.0f; fps < 65.0f; fps += 0.1f) {
-        recordFramesAndExpect(layer, time, Fps(fps), Fps(60.0f), PRESENT_TIME_HISTORY_SIZE);
+        recordFramesAndExpect(layer, time, Fps::fromValue(fps), 60_Hz, PRESENT_TIME_HISTORY_SIZE);
     }
 }
 
@@ -669,13 +698,13 @@
     EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
 
     nsecs_t time = systemTime();
-    recordFramesAndExpect(layer, time, Fps(60.0f), Fps(60.0f), PRESENT_TIME_HISTORY_SIZE);
+    recordFramesAndExpect(layer, time, 60_Hz, 60_Hz, PRESENT_TIME_HISTORY_SIZE);
 
-    recordFramesAndExpect(layer, time, Fps(60.0f), Fps(60.0f), PRESENT_TIME_HISTORY_SIZE);
-    recordFramesAndExpect(layer, time, Fps(30.0f), Fps(60.0f), PRESENT_TIME_HISTORY_SIZE);
-    recordFramesAndExpect(layer, time, Fps(30.0f), Fps(30.0f), PRESENT_TIME_HISTORY_SIZE);
-    recordFramesAndExpect(layer, time, Fps(60.0f), Fps(30.0f), PRESENT_TIME_HISTORY_SIZE);
-    recordFramesAndExpect(layer, time, Fps(60.0f), Fps(60.0f), PRESENT_TIME_HISTORY_SIZE);
+    recordFramesAndExpect(layer, time, 60_Hz, 60_Hz, PRESENT_TIME_HISTORY_SIZE);
+    recordFramesAndExpect(layer, time, 30_Hz, 60_Hz, PRESENT_TIME_HISTORY_SIZE);
+    recordFramesAndExpect(layer, time, 30_Hz, 30_Hz, PRESENT_TIME_HISTORY_SIZE);
+    recordFramesAndExpect(layer, time, 60_Hz, 30_Hz, PRESENT_TIME_HISTORY_SIZE);
+    recordFramesAndExpect(layer, time, 60_Hz, 60_Hz, PRESENT_TIME_HISTORY_SIZE);
 }
 
 TEST_F(LayerHistoryTest, heuristicLayerNotOscillating) {
@@ -685,11 +714,11 @@
 
     nsecs_t time = systemTime();
 
-    recordFramesAndExpect(layer, time, Fps(27.10f), Fps(30.0f), PRESENT_TIME_HISTORY_SIZE);
-    recordFramesAndExpect(layer, time, Fps(26.90f), Fps(30.0f), PRESENT_TIME_HISTORY_SIZE);
-    recordFramesAndExpect(layer, time, Fps(26.00f), Fps(24.0f), PRESENT_TIME_HISTORY_SIZE);
-    recordFramesAndExpect(layer, time, Fps(26.90f), Fps(24.0f), PRESENT_TIME_HISTORY_SIZE);
-    recordFramesAndExpect(layer, time, Fps(27.10f), Fps(30.0f), PRESENT_TIME_HISTORY_SIZE);
+    recordFramesAndExpect(layer, time, 27.1_Hz, 30_Hz, PRESENT_TIME_HISTORY_SIZE);
+    recordFramesAndExpect(layer, time, 26.9_Hz, 30_Hz, PRESENT_TIME_HISTORY_SIZE);
+    recordFramesAndExpect(layer, time, 26_Hz, 24_Hz, PRESENT_TIME_HISTORY_SIZE);
+    recordFramesAndExpect(layer, time, 26.9_Hz, 24_Hz, PRESENT_TIME_HISTORY_SIZE);
+    recordFramesAndExpect(layer, time, 27.1_Hz, 30_Hz, PRESENT_TIME_HISTORY_SIZE);
 }
 
 class LayerHistoryTestParameterized : public LayerHistoryTest,
@@ -740,7 +769,7 @@
 
             bool max = false;
             bool min = false;
-            Fps heuristic{0.0};
+            Fps heuristic;
             for (const auto& layer : summarizeLayerHistory(time)) {
                 if (layer.vote == LayerHistory::LayerVoteType::Heuristic) {
                     heuristic = layer.desiredRefreshRate;
@@ -752,7 +781,7 @@
             }
 
             if (infrequentLayerUpdates > FREQUENT_LAYER_WINDOW_SIZE) {
-                EXPECT_TRUE(Fps(24.0f).equalsWithMargin(heuristic));
+                EXPECT_EQ(24_Hz, heuristic);
                 EXPECT_FALSE(max);
                 if (summarizeLayerHistory(time).size() == 2) {
                     EXPECT_TRUE(min);
@@ -766,8 +795,7 @@
                         ::testing::Values(1s, 2s, 3s, 4s, 5s));
 
 } // namespace
-} // namespace scheduler
-} // namespace android
+} // namespace android::scheduler
 
 // TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic pop // ignored "-Wextra"
\ No newline at end of file
+#pragma clang diagnostic pop // ignored "-Wextra"
diff --git a/services/surfaceflinger/tests/unittests/LayerInfoTest.cpp b/services/surfaceflinger/tests/unittests/LayerInfoTest.cpp
index d6ce5e2..5c2d2e1 100644
--- a/services/surfaceflinger/tests/unittests/LayerInfoTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerInfoTest.cpp
@@ -19,7 +19,9 @@
 
 #include <gtest/gtest.h>
 
-#include "Fps.h"
+#include <scheduler/Fps.h>
+
+#include "FpsOps.h"
 #include "Scheduler/LayerHistory.h"
 #include "Scheduler/LayerInfo.h"
 
@@ -47,7 +49,7 @@
 
 TEST_F(LayerInfoTest, prefersPresentTime) {
     std::deque<FrameTimeData> frameTimes;
-    constexpr auto kExpectedFps = Fps(50.0f);
+    constexpr auto kExpectedFps = 50_Hz;
     constexpr auto kPeriod = kExpectedFps.getPeriodNsecs();
     constexpr int kNumFrames = 10;
     for (int i = 1; i <= kNumFrames; i++) {
@@ -58,14 +60,12 @@
     setFrameTimes(frameTimes);
     const auto averageFrameTime = calculateAverageFrameTime();
     ASSERT_TRUE(averageFrameTime.has_value());
-    const auto averageFps = Fps::fromPeriodNsecs(*averageFrameTime);
-    ASSERT_TRUE(kExpectedFps.equalsWithMargin(averageFps))
-            << "Expected " << averageFps << " to be equal to " << kExpectedFps;
+    ASSERT_EQ(kExpectedFps, Fps::fromPeriodNsecs(*averageFrameTime));
 }
 
 TEST_F(LayerInfoTest, fallbacksToQueueTimeIfNoPresentTime) {
     std::deque<FrameTimeData> frameTimes;
-    constexpr auto kExpectedFps = Fps(50.0f);
+    constexpr auto kExpectedFps = 50_Hz;
     constexpr auto kPeriod = kExpectedFps.getPeriodNsecs();
     constexpr int kNumFrames = 10;
     for (int i = 1; i <= kNumFrames; i++) {
@@ -74,17 +74,15 @@
                                            .pendingModeChange = false});
     }
     setFrameTimes(frameTimes);
-    setLastRefreshRate(Fps(20.0f)); // Set to some valid value
+    setLastRefreshRate(20_Hz); // Set to some valid value.
     const auto averageFrameTime = calculateAverageFrameTime();
     ASSERT_TRUE(averageFrameTime.has_value());
-    const auto averageFps = Fps::fromPeriodNsecs(*averageFrameTime);
-    ASSERT_TRUE(kExpectedFps.equalsWithMargin(averageFps))
-            << "Expected " << averageFps << " to be equal to " << kExpectedFps;
+    ASSERT_EQ(kExpectedFps, Fps::fromPeriodNsecs(*averageFrameTime));
 }
 
 TEST_F(LayerInfoTest, returnsNulloptIfThereWasConfigChange) {
     std::deque<FrameTimeData> frameTimesWithoutConfigChange;
-    const auto period = Fps(50.0f).getPeriodNsecs();
+    const auto period = (50_Hz).getPeriodNsecs();
     constexpr int kNumFrames = 10;
     for (int i = 1; i <= kNumFrames; i++) {
         frameTimesWithoutConfigChange.push_back(FrameTimeData{.presentTime = period * i,
@@ -124,9 +122,9 @@
 // Make sure that this doesn't influence the calculated average FPS.
 TEST_F(LayerInfoTest, ignoresSmallPeriods) {
     std::deque<FrameTimeData> frameTimes;
-    constexpr auto kExpectedFps = Fps(50.0f);
+    constexpr auto kExpectedFps = 50_Hz;
     constexpr auto kExpectedPeriod = kExpectedFps.getPeriodNsecs();
-    constexpr auto kSmallPeriod = Fps(250.0f).getPeriodNsecs();
+    constexpr auto kSmallPeriod = (250_Hz).getPeriodNsecs();
     constexpr int kNumIterations = 10;
     for (int i = 1; i <= kNumIterations; i++) {
         frameTimes.push_back(FrameTimeData{.presentTime = kExpectedPeriod * i,
@@ -141,18 +139,16 @@
     setFrameTimes(frameTimes);
     const auto averageFrameTime = calculateAverageFrameTime();
     ASSERT_TRUE(averageFrameTime.has_value());
-    const auto averageFps = Fps::fromPeriodNsecs(*averageFrameTime);
-    ASSERT_TRUE(kExpectedFps.equalsWithMargin(averageFps))
-            << "Expected " << averageFps << " to be equal to " << kExpectedFps;
+    ASSERT_EQ(kExpectedFps, Fps::fromPeriodNsecs(*averageFrameTime));
 }
 
 // There may be a big period of time between two frames. Make sure that
 // this doesn't influence the calculated average FPS.
 TEST_F(LayerInfoTest, ignoresLargePeriods) {
     std::deque<FrameTimeData> frameTimes;
-    constexpr auto kExpectedFps = Fps(50.0f);
+    constexpr auto kExpectedFps = 50_Hz;
     constexpr auto kExpectedPeriod = kExpectedFps.getPeriodNsecs();
-    constexpr auto kLargePeriod = Fps(9.0f).getPeriodNsecs();
+    constexpr auto kLargePeriod = (9_Hz).getPeriodNsecs();
 
     auto record = [&](nsecs_t time) {
         frameTimes.push_back(
@@ -172,9 +168,7 @@
     setFrameTimes(frameTimes);
     const auto averageFrameTime = calculateAverageFrameTime();
     ASSERT_TRUE(averageFrameTime.has_value());
-    const auto averageFps = Fps::fromPeriodNsecs(*averageFrameTime);
-    ASSERT_TRUE(kExpectedFps.equalsWithMargin(averageFps))
-            << "Expected " << averageFps << " to be equal to " << kExpectedFps;
+    ASSERT_EQ(kExpectedFps, Fps::fromPeriodNsecs(*averageFrameTime));
 }
 
 } // namespace
diff --git a/services/surfaceflinger/tests/unittests/LayerTest.cpp b/services/surfaceflinger/tests/unittests/LayerTest.cpp
new file mode 100644
index 0000000..4974f90
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/LayerTest.cpp
@@ -0,0 +1,87 @@
+/*
+ * 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.
+ */
+
+#undef LOG_TAG
+#define LOG_TAG "LibSurfaceFlingerUnittests"
+
+#include <EffectLayer.h>
+#include <gtest/gtest.h>
+#include <ui/FloatRect.h>
+#include <ui/Transform.h>
+#include <limits>
+
+#include "LayerTestUtils.h"
+#include "TestableSurfaceFlinger.h"
+
+namespace android {
+namespace {
+
+class LayerTest : public BaseLayerTest {
+protected:
+    static constexpr const float MIN_FLOAT = std::numeric_limits<float>::min();
+    static constexpr const float MAX_FLOAT = std::numeric_limits<float>::max();
+    static constexpr const FloatRect LARGE_FLOAT_RECT{MIN_FLOAT, MIN_FLOAT, MAX_FLOAT, MAX_FLOAT};
+};
+
+INSTANTIATE_TEST_SUITE_P(PerLayerType, LayerTest,
+                         testing::Values(std::make_shared<BufferStateLayerFactory>(),
+                                         std::make_shared<EffectLayerFactory>()),
+                         PrintToStringParamName);
+
+TEST_P(LayerTest, layerVisibleByDefault) {
+    sp<Layer> layer = GetParam()->createLayer(mFlinger);
+    layer->updateGeometry();
+    layer->computeBounds(LARGE_FLOAT_RECT, ui::Transform(), 0.f);
+    ASSERT_FALSE(layer->isHiddenByPolicy());
+}
+
+TEST_P(LayerTest, hideLayerWithZeroMatrix) {
+    sp<Layer> layer = GetParam()->createLayer(mFlinger);
+
+    layer_state_t::matrix22_t matrix{0, 0, 0, 0};
+    layer->setMatrix(matrix);
+    layer->updateGeometry();
+    layer->computeBounds(LARGE_FLOAT_RECT, ui::Transform(), 0.f);
+
+    ASSERT_TRUE(layer->isHiddenByPolicy());
+}
+
+TEST_P(LayerTest, hideLayerWithInfMatrix) {
+    sp<Layer> layer = GetParam()->createLayer(mFlinger);
+
+    constexpr const float INF = std::numeric_limits<float>::infinity();
+    layer_state_t::matrix22_t matrix{INF, 0, 0, INF};
+    layer->setMatrix(matrix);
+    layer->updateGeometry();
+    layer->computeBounds(LARGE_FLOAT_RECT, ui::Transform(), 0.f);
+
+    ASSERT_TRUE(layer->isHiddenByPolicy());
+}
+
+TEST_P(LayerTest, hideLayerWithNanMatrix) {
+    sp<Layer> layer = GetParam()->createLayer(mFlinger);
+
+    constexpr const float QUIET_NAN = std::numeric_limits<float>::quiet_NaN();
+    layer_state_t::matrix22_t matrix{QUIET_NAN, 0, 0, QUIET_NAN};
+    layer->setMatrix(matrix);
+    layer->updateGeometry();
+    layer->computeBounds(LARGE_FLOAT_RECT, ui::Transform(), 0.f);
+
+    ASSERT_TRUE(layer->isHiddenByPolicy());
+}
+
+} // namespace
+} // namespace android
diff --git a/services/surfaceflinger/tests/unittests/LayerTestUtils.cpp b/services/surfaceflinger/tests/unittests/LayerTestUtils.cpp
new file mode 100644
index 0000000..5a2c147
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/LayerTestUtils.cpp
@@ -0,0 +1,77 @@
+/*
+ * 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 "LayerTestUtils.h"
+
+#include "mock/MockEventThread.h"
+
+namespace android {
+
+using testing::_;
+using testing::Return;
+
+using FakeHwcDisplayInjector = TestableSurfaceFlinger::FakeHwcDisplayInjector;
+
+sp<Layer> BufferStateLayerFactory::createLayer(TestableSurfaceFlinger& flinger) {
+    sp<Client> client;
+    LayerCreationArgs args(flinger.flinger(), client, "buffer-state-layer", LAYER_FLAGS,
+                           LayerMetadata());
+    return new BufferStateLayer(args);
+}
+
+sp<Layer> EffectLayerFactory::createLayer(TestableSurfaceFlinger& flinger) {
+    sp<Client> client;
+    LayerCreationArgs args(flinger.flinger(), client, "color-layer", LAYER_FLAGS, LayerMetadata());
+    return new EffectLayer(args);
+}
+
+std::string PrintToStringParamName(
+        const ::testing::TestParamInfo<std::shared_ptr<LayerFactory>>& info) {
+    return info.param->name();
+}
+
+BaseLayerTest::BaseLayerTest() {
+    setupScheduler();
+}
+
+void BaseLayerTest::setupScheduler() {
+    auto eventThread = std::make_unique<mock::EventThread>();
+    auto sfEventThread = std::make_unique<mock::EventThread>();
+
+    EXPECT_CALL(*eventThread, registerDisplayEventConnection(_));
+    EXPECT_CALL(*eventThread, createEventConnection(_, _))
+            .WillOnce(Return(new EventThreadConnection(eventThread.get(), /*callingUid=*/0,
+                                                       ResyncCallback())));
+
+    EXPECT_CALL(*sfEventThread, registerDisplayEventConnection(_));
+    EXPECT_CALL(*sfEventThread, createEventConnection(_, _))
+            .WillOnce(Return(new EventThreadConnection(sfEventThread.get(), /*callingUid=*/0,
+                                                       ResyncCallback())));
+
+    auto vsyncController = std::make_unique<mock::VsyncController>();
+    auto vsyncTracker = std::make_unique<mock::VSyncTracker>();
+
+    EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0));
+    EXPECT_CALL(*vsyncTracker, currentPeriod())
+            .WillRepeatedly(Return(FakeHwcDisplayInjector::DEFAULT_VSYNC_PERIOD));
+    EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0));
+    mFlinger.setupScheduler(std::move(vsyncController), std::move(vsyncTracker),
+                            std::move(eventThread), std::move(sfEventThread),
+                            TestableSurfaceFlinger::SchedulerCallbackImpl::kNoOp,
+                            TestableSurfaceFlinger::kTwoDisplayModes);
+}
+
+} // namespace android
diff --git a/services/surfaceflinger/tests/unittests/LayerTestUtils.h b/services/surfaceflinger/tests/unittests/LayerTestUtils.h
new file mode 100644
index 0000000..fc9b6a2
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/LayerTestUtils.h
@@ -0,0 +1,73 @@
+/*
+ * 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 <memory>
+
+#include <gtest/gtest.h>
+
+// TODO(b/129481165): remove the #pragma below and fix conversion issues
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wconversion"
+#include "BufferStateLayer.h"
+#include "EffectLayer.h"
+#include "Layer.h"
+// TODO(b/129481165): remove the #pragma below and fix conversion issues
+#pragma clang diagnostic pop // ignored "-Wconversion"
+
+#include "TestableSurfaceFlinger.h"
+
+namespace android {
+
+class LayerFactory {
+public:
+    virtual ~LayerFactory() = default;
+
+    virtual std::string name() = 0;
+    virtual sp<Layer> createLayer(TestableSurfaceFlinger& flinger) = 0;
+
+protected:
+    static constexpr uint32_t WIDTH = 100;
+    static constexpr uint32_t HEIGHT = 100;
+    static constexpr uint32_t LAYER_FLAGS = 0;
+};
+
+class BufferStateLayerFactory : public LayerFactory {
+public:
+    std::string name() override { return "BufferStateLayer"; }
+    sp<Layer> createLayer(TestableSurfaceFlinger& flinger) override;
+};
+
+class EffectLayerFactory : public LayerFactory {
+public:
+    std::string name() override { return "EffectLayer"; }
+    sp<Layer> createLayer(TestableSurfaceFlinger& flinger) override;
+};
+
+std::string PrintToStringParamName(
+        const ::testing::TestParamInfo<std::shared_ptr<LayerFactory>>& info);
+
+class BaseLayerTest : public ::testing::TestWithParam<std::shared_ptr<LayerFactory>> {
+protected:
+    BaseLayerTest();
+
+    void setupScheduler();
+
+    TestableSurfaceFlinger mFlinger;
+};
+
+} // namespace android
diff --git a/services/surfaceflinger/tests/unittests/MessageQueueTest.cpp b/services/surfaceflinger/tests/unittests/MessageQueueTest.cpp
index dbd51fe..e0aa0b1 100644
--- a/services/surfaceflinger/tests/unittests/MessageQueueTest.cpp
+++ b/services/surfaceflinger/tests/unittests/MessageQueueTest.cpp
@@ -31,62 +31,50 @@
 
 using CallbackToken = scheduler::VSyncDispatch::CallbackToken;
 
+struct NoOpCompositor final : ICompositor {
+    bool commit(nsecs_t, int64_t, nsecs_t) override { return false; }
+    void composite(nsecs_t, int64_t) override {}
+    void sample() override {}
+} gNoOpCompositor;
+
 class TestableMessageQueue : public impl::MessageQueue {
-public:
-    class MockHandler : public MessageQueue::Handler {
-    public:
-        explicit MockHandler(MessageQueue& queue) : MessageQueue::Handler(queue) {}
-        ~MockHandler() override = default;
-        MOCK_METHOD2(dispatchInvalidate, void(int64_t vsyncId, nsecs_t expectedVSyncTimestamp));
+    struct MockHandler : MessageQueue::Handler {
+        using MessageQueue::Handler::Handler;
+
+        MOCK_METHOD(void, dispatchFrame, (int64_t, nsecs_t), (override));
     };
 
-    TestableMessageQueue() = default;
-    ~TestableMessageQueue() override = default;
+    explicit TestableMessageQueue(sp<MockHandler> handler)
+          : impl::MessageQueue(gNoOpCompositor, handler), mHandler(std::move(handler)) {}
 
-    void initHandler(const sp<MockHandler>& handler) { mHandler = handler; }
+public:
+    TestableMessageQueue() : TestableMessageQueue(sp<MockHandler>::make(*this)) {}
 
-    void triggerVsyncCallback(nsecs_t vsyncTime, nsecs_t targetWakeupTime, nsecs_t readyTime) {
-        vsyncCallback(vsyncTime, targetWakeupTime, readyTime);
-    }
+    using impl::MessageQueue::vsyncCallback;
+
+    const sp<MockHandler> mHandler;
 };
 
-class MockVSyncDispatch : public scheduler::VSyncDispatch {
-public:
-    MockVSyncDispatch() = default;
-    ~MockVSyncDispatch() override = default;
-
-    MOCK_METHOD2(registerCallback,
-                 CallbackToken(std::function<void(nsecs_t, nsecs_t, nsecs_t)> const&, std::string));
-    MOCK_METHOD1(unregisterCallback, void(CallbackToken));
-    MOCK_METHOD2(schedule, scheduler::ScheduleResult(CallbackToken, ScheduleTiming));
-    MOCK_METHOD1(cancel, scheduler::CancelResult(CallbackToken token));
-    MOCK_CONST_METHOD1(dump, void(std::string&));
+struct MockVSyncDispatch : scheduler::VSyncDispatch {
+    MOCK_METHOD(CallbackToken, registerCallback, (Callback, std::string), (override));
+    MOCK_METHOD(void, unregisterCallback, (CallbackToken), (override));
+    MOCK_METHOD(scheduler::ScheduleResult, schedule, (CallbackToken, ScheduleTiming), (override));
+    MOCK_METHOD(scheduler::CancelResult, cancel, (CallbackToken token), (override));
+    MOCK_METHOD(void, dump, (std::string&), (const, override));
 };
 
-class MockTokenManager : public frametimeline::TokenManager {
-public:
-    MockTokenManager() = default;
-    ~MockTokenManager() override = default;
-
+struct MockTokenManager : frametimeline::TokenManager {
     MOCK_METHOD1(generateTokenForPredictions, int64_t(frametimeline::TimelineItem&& prediction));
     MOCK_CONST_METHOD1(getPredictionsForToken, std::optional<frametimeline::TimelineItem>(int64_t));
 };
 
-class MessageQueueTest : public testing::Test {
-public:
-    MessageQueueTest() = default;
-    ~MessageQueueTest() override = default;
-
+struct MessageQueueTest : testing::Test {
     void SetUp() override {
-        EXPECT_NO_FATAL_FAILURE(mEventQueue.initHandler(mHandler));
-
         EXPECT_CALL(mVSyncDispatch, registerCallback(_, "sf")).WillOnce(Return(mCallbackToken));
         EXPECT_NO_FATAL_FAILURE(mEventQueue.initVsync(mVSyncDispatch, mTokenManager, mDuration));
         EXPECT_CALL(mVSyncDispatch, unregisterCallback(mCallbackToken)).Times(1);
     }
 
-    sp<TestableMessageQueue::MockHandler> mHandler =
-            new TestableMessageQueue::MockHandler(mEventQueue);
     MockVSyncDispatch mVSyncDispatch;
     MockTokenManager mTokenManager;
     TestableMessageQueue mEventQueue;
@@ -100,45 +88,49 @@
 /* ------------------------------------------------------------------------
  * Test cases
  */
-TEST_F(MessageQueueTest, invalidate) {
+TEST_F(MessageQueueTest, commit) {
     const auto timing = scheduler::VSyncDispatch::ScheduleTiming{.workDuration = mDuration.count(),
                                                                  .readyDuration = 0,
                                                                  .earliestVsync = 0};
-    EXPECT_FALSE(mEventQueue.nextExpectedInvalidate().has_value());
+    EXPECT_FALSE(mEventQueue.getScheduledFrameTime());
 
     EXPECT_CALL(mVSyncDispatch, schedule(mCallbackToken, timing)).WillOnce(Return(1234));
-    EXPECT_NO_FATAL_FAILURE(mEventQueue.invalidate());
-    EXPECT_TRUE(mEventQueue.nextExpectedInvalidate().has_value());
-    EXPECT_EQ(1234, mEventQueue.nextExpectedInvalidate().value().time_since_epoch().count());
+    EXPECT_NO_FATAL_FAILURE(mEventQueue.scheduleFrame());
+
+    ASSERT_TRUE(mEventQueue.getScheduledFrameTime());
+    EXPECT_EQ(1234, mEventQueue.getScheduledFrameTime()->time_since_epoch().count());
 }
 
-TEST_F(MessageQueueTest, invalidateTwice) {
+TEST_F(MessageQueueTest, commitTwice) {
     InSequence s;
     const auto timing = scheduler::VSyncDispatch::ScheduleTiming{.workDuration = mDuration.count(),
                                                                  .readyDuration = 0,
                                                                  .earliestVsync = 0};
 
     EXPECT_CALL(mVSyncDispatch, schedule(mCallbackToken, timing)).WillOnce(Return(1234));
-    EXPECT_NO_FATAL_FAILURE(mEventQueue.invalidate());
-    EXPECT_TRUE(mEventQueue.nextExpectedInvalidate().has_value());
-    EXPECT_EQ(1234, mEventQueue.nextExpectedInvalidate().value().time_since_epoch().count());
+    EXPECT_NO_FATAL_FAILURE(mEventQueue.scheduleFrame());
+
+    ASSERT_TRUE(mEventQueue.getScheduledFrameTime());
+    EXPECT_EQ(1234, mEventQueue.getScheduledFrameTime()->time_since_epoch().count());
 
     EXPECT_CALL(mVSyncDispatch, schedule(mCallbackToken, timing)).WillOnce(Return(4567));
-    EXPECT_NO_FATAL_FAILURE(mEventQueue.invalidate());
-    EXPECT_TRUE(mEventQueue.nextExpectedInvalidate().has_value());
-    EXPECT_EQ(4567, mEventQueue.nextExpectedInvalidate().value().time_since_epoch().count());
+    EXPECT_NO_FATAL_FAILURE(mEventQueue.scheduleFrame());
+
+    ASSERT_TRUE(mEventQueue.getScheduledFrameTime());
+    EXPECT_EQ(4567, mEventQueue.getScheduledFrameTime()->time_since_epoch().count());
 }
 
-TEST_F(MessageQueueTest, invalidateTwiceWithCallback) {
+TEST_F(MessageQueueTest, commitTwiceWithCallback) {
     InSequence s;
     const auto timing = scheduler::VSyncDispatch::ScheduleTiming{.workDuration = mDuration.count(),
                                                                  .readyDuration = 0,
                                                                  .earliestVsync = 0};
 
     EXPECT_CALL(mVSyncDispatch, schedule(mCallbackToken, timing)).WillOnce(Return(1234));
-    EXPECT_NO_FATAL_FAILURE(mEventQueue.invalidate());
-    EXPECT_TRUE(mEventQueue.nextExpectedInvalidate().has_value());
-    EXPECT_EQ(1234, mEventQueue.nextExpectedInvalidate().value().time_since_epoch().count());
+    EXPECT_NO_FATAL_FAILURE(mEventQueue.scheduleFrame());
+
+    ASSERT_TRUE(mEventQueue.getScheduledFrameTime());
+    EXPECT_EQ(1234, mEventQueue.getScheduledFrameTime()->time_since_epoch().count());
 
     const auto startTime = 100;
     const auto endTime = startTime + mDuration.count();
@@ -148,10 +140,10 @@
                 generateTokenForPredictions(
                         frametimeline::TimelineItem(startTime, endTime, presentTime)))
             .WillOnce(Return(vsyncId));
-    EXPECT_CALL(*mHandler, dispatchInvalidate(vsyncId, presentTime)).Times(1);
-    EXPECT_NO_FATAL_FAILURE(mEventQueue.triggerVsyncCallback(presentTime, startTime, endTime));
+    EXPECT_CALL(*mEventQueue.mHandler, dispatchFrame(vsyncId, presentTime)).Times(1);
+    EXPECT_NO_FATAL_FAILURE(mEventQueue.vsyncCallback(presentTime, startTime, endTime));
 
-    EXPECT_FALSE(mEventQueue.nextExpectedInvalidate().has_value());
+    EXPECT_FALSE(mEventQueue.getScheduledFrameTime());
 
     const auto timingAfterCallback =
             scheduler::VSyncDispatch::ScheduleTiming{.workDuration = mDuration.count(),
@@ -159,10 +151,10 @@
                                                      .earliestVsync = presentTime};
 
     EXPECT_CALL(mVSyncDispatch, schedule(mCallbackToken, timingAfterCallback)).WillOnce(Return(0));
-    EXPECT_NO_FATAL_FAILURE(mEventQueue.invalidate());
+    EXPECT_NO_FATAL_FAILURE(mEventQueue.scheduleFrame());
 }
 
-TEST_F(MessageQueueTest, invalidateWithDurationChange) {
+TEST_F(MessageQueueTest, commitWithDurationChange) {
     EXPECT_NO_FATAL_FAILURE(mEventQueue.setDuration(mDifferentDuration));
 
     const auto timing =
@@ -171,7 +163,7 @@
                                                      .earliestVsync = 0};
 
     EXPECT_CALL(mVSyncDispatch, schedule(mCallbackToken, timing)).WillOnce(Return(0));
-    EXPECT_NO_FATAL_FAILURE(mEventQueue.invalidate());
+    EXPECT_NO_FATAL_FAILURE(mEventQueue.scheduleFrame());
 }
 
 } // namespace
diff --git a/services/surfaceflinger/tests/unittests/OneShotTimerTest.cpp b/services/surfaceflinger/tests/unittests/OneShotTimerTest.cpp
index 6916764..597e5e7 100644
--- a/services/surfaceflinger/tests/unittests/OneShotTimerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/OneShotTimerTest.cpp
@@ -72,7 +72,8 @@
     mIdleTimer->stop();
 }
 
-TEST_F(OneShotTimerTest, resetTest) {
+// TODO(b/186417847) This test is flaky. Reenable once fixed.
+TEST_F(OneShotTimerTest, DISABLED_resetTest) {
     fake::FakeClock* clock = new fake::FakeClock();
     mIdleTimer = std::make_unique<scheduler::OneShotTimer>("TestTimer", 1ms,
                                                            mResetTimerCallback.getInvocable(),
@@ -94,7 +95,8 @@
     EXPECT_FALSE(mResetTimerCallback.waitForUnexpectedCall().has_value());
 }
 
-TEST_F(OneShotTimerTest, resetBackToBackTest) {
+// TODO(b/186417847) This test is flaky. Reenable once fixed.
+TEST_F(OneShotTimerTest, DISABLED_resetBackToBackTest) {
     fake::FakeClock* clock = new fake::FakeClock();
     mIdleTimer = std::make_unique<scheduler::OneShotTimer>("TestTimer", 1ms,
                                                            mResetTimerCallback.getInvocable(),
@@ -144,7 +146,8 @@
     EXPECT_FALSE(mResetTimerCallback.waitForUnexpectedCall().has_value());
 }
 
-TEST_F(OneShotTimerTest, idleTimerIdlesTest) {
+// TODO(b/186417847) This test is flaky. Reenable once fixed.
+TEST_F(OneShotTimerTest, DISABLED_idleTimerIdlesTest) {
     fake::FakeClock* clock = new fake::FakeClock();
     mIdleTimer = std::make_unique<scheduler::OneShotTimer>("TestTimer", 1ms,
                                                            mResetTimerCallback.getInvocable(),
@@ -169,7 +172,8 @@
     EXPECT_FALSE(mResetTimerCallback.waitForUnexpectedCall().has_value());
 }
 
-TEST_F(OneShotTimerTest, timeoutCallbackExecutionTest) {
+// TODO(b/186417847) This test is flaky. Reenable once fixed.
+TEST_F(OneShotTimerTest, DISABLED_timeoutCallbackExecutionTest) {
     fake::FakeClock* clock = new fake::FakeClock();
     mIdleTimer = std::make_unique<scheduler::OneShotTimer>("TestTimer", 1ms,
                                                            mResetTimerCallback.getInvocable(),
diff --git a/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp b/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp
index 77b75ab..fcde532 100644
--- a/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp
+++ b/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp
@@ -14,173 +14,127 @@
  * limitations under the License.
  */
 
-// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wextra"
-
 #undef LOG_TAG
 #define LOG_TAG "SchedulerUnittests"
 
+#include <ftl/enum.h>
 #include <gmock/gmock.h>
 #include <log/log.h>
-#include <thread>
-
 #include <ui/Size.h>
 
-#include "../../Scheduler/RefreshRateConfigs.h"
 #include "DisplayHardware/HWC2.h"
+#include "FpsOps.h"
 #include "Scheduler/RefreshRateConfigs.h"
+#include "mock/DisplayHardware/MockDisplayMode.h"
 
 using namespace std::chrono_literals;
 
-namespace android {
-
-namespace scheduler {
+namespace android::scheduler {
 
 namespace hal = android::hardware::graphics::composer::hal;
 
-using RefreshRate = RefreshRateConfigs::RefreshRate;
 using LayerVoteType = RefreshRateConfigs::LayerVoteType;
 using LayerRequirement = RefreshRateConfigs::LayerRequirement;
 
+using mock::createDisplayMode;
+
+struct TestableRefreshRateConfigs : RefreshRateConfigs {
+    using RefreshRateConfigs::RefreshRateConfigs;
+
+    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();
+    }
+
+    const std::vector<Fps>& knownFrameRates() const { return mKnownFrameRates; }
+
+    using RefreshRateConfigs::GetBestRefreshRateCache;
+    auto& mutableGetBestRefreshRateCache() { return mGetBestRefreshRateCache; }
+
+    auto getBestRefreshRateAndSignals(const std::vector<LayerRequirement>& layers,
+                                      GlobalSignals signals) const {
+        return RefreshRateConfigs::getBestRefreshRate(layers, signals);
+    }
+
+    DisplayModePtr getBestRefreshRate(const std::vector<LayerRequirement>& layers = {},
+                                      GlobalSignals signals = {}) const {
+        return getBestRefreshRateAndSignals(layers, signals).first;
+    }
+};
+
 class RefreshRateConfigsTest : public testing::Test {
 protected:
-    using GetBestRefreshRateInvocation = RefreshRateConfigs::GetBestRefreshRateInvocation;
-
     RefreshRateConfigsTest();
     ~RefreshRateConfigsTest();
 
-    RefreshRate createRefreshRate(DisplayModePtr displayMode) {
-        return {displayMode, RefreshRate::ConstructorTag(0)};
-    }
+    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};
 
-    Fps findClosestKnownFrameRate(const RefreshRateConfigs& refreshRateConfigs, Fps frameRate) {
-        return refreshRateConfigs.findClosestKnownFrameRate(frameRate);
-    }
+    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);
 
-    std::vector<Fps> getKnownFrameRate(const RefreshRateConfigs& refreshRateConfigs) {
-        return refreshRateConfigs.mKnownFrameRates;
-    }
+    // 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);
 
-    RefreshRate getMinRefreshRateByPolicy(const RefreshRateConfigs& refreshRateConfigs) {
-        std::lock_guard lock(refreshRateConfigs.mLock);
-        return refreshRateConfigs.getMinRefreshRateByPolicyLocked();
-    }
-
-    RefreshRate getMinSupportedRefreshRate(const RefreshRateConfigs& refreshRateConfigs) {
-        std::lock_guard lock(refreshRateConfigs.mLock);
-        return *refreshRateConfigs.mMinSupportedRefreshRate;
-    }
-
-    RefreshRate getMaxSupportedRefreshRate(const RefreshRateConfigs& refreshRateConfigs) {
-        std::lock_guard lock(refreshRateConfigs.mLock);
-        return *refreshRateConfigs.mMaxSupportedRefreshRate;
-    }
-
-    void setLastBestRefreshRateInvocation(RefreshRateConfigs& refreshRateConfigs,
-                                          const GetBestRefreshRateInvocation& invocation) {
-        std::lock_guard lock(refreshRateConfigs.mLock);
-        refreshRateConfigs.lastBestRefreshRateInvocation.emplace(
-                GetBestRefreshRateInvocation(invocation));
-    }
-
-    std::optional<GetBestRefreshRateInvocation> getLastBestRefreshRateInvocation(
-            const RefreshRateConfigs& refreshRateConfigs) {
-        std::lock_guard lock(refreshRateConfigs.mLock);
-        return refreshRateConfigs.lastBestRefreshRateInvocation;
-    }
-
-    // Test config IDs
-    static inline const DisplayModeId HWC_CONFIG_ID_60 = DisplayModeId(0);
-    static inline const DisplayModeId HWC_CONFIG_ID_90 = DisplayModeId(1);
-    static inline const DisplayModeId HWC_CONFIG_ID_72 = DisplayModeId(2);
-    static inline const DisplayModeId HWC_CONFIG_ID_120 = DisplayModeId(3);
-    static inline const DisplayModeId HWC_CONFIG_ID_30 = DisplayModeId(4);
-    static inline const DisplayModeId HWC_CONFIG_ID_25 = DisplayModeId(5);
-    static inline const DisplayModeId HWC_CONFIG_ID_50 = DisplayModeId(6);
-    static inline const DisplayModeId HWC_CONFIG_ID_24 = DisplayModeId(7);
-    static inline const DisplayModeId HWC_CONFIG_ID_24_FRAC = DisplayModeId(8);
-    static inline const DisplayModeId HWC_CONFIG_ID_30_FRAC = DisplayModeId(9);
-    static inline const DisplayModeId HWC_CONFIG_ID_60_FRAC = DisplayModeId(10);
-
-    // Test configs
-    DisplayModePtr mConfig60 = createDisplayMode(HWC_CONFIG_ID_60, 0, Fps(60.0f).getPeriodNsecs());
-    DisplayModePtr mConfig60Frac =
-            createDisplayMode(HWC_CONFIG_ID_60_FRAC, 0, Fps(59.94f).getPeriodNsecs());
-    DisplayModePtr mConfig90 = createDisplayMode(HWC_CONFIG_ID_90, 0, Fps(90.0f).getPeriodNsecs());
-    DisplayModePtr mConfig90DifferentGroup =
-            createDisplayMode(HWC_CONFIG_ID_90, 1, Fps(90.0f).getPeriodNsecs());
-    DisplayModePtr mConfig90DifferentResolution =
-            createDisplayMode(HWC_CONFIG_ID_90, 0, Fps(90.0f).getPeriodNsecs(), ui::Size(111, 222));
-    DisplayModePtr mConfig72 = createDisplayMode(HWC_CONFIG_ID_72, 0, Fps(72.0f).getPeriodNsecs());
-    DisplayModePtr mConfig72DifferentGroup =
-            createDisplayMode(HWC_CONFIG_ID_72, 1, Fps(72.0f).getPeriodNsecs());
-    DisplayModePtr mConfig120 =
-            createDisplayMode(HWC_CONFIG_ID_120, 0, Fps(120.0f).getPeriodNsecs());
-    DisplayModePtr mConfig120DifferentGroup =
-            createDisplayMode(HWC_CONFIG_ID_120, 1, Fps(120.0f).getPeriodNsecs());
-    DisplayModePtr mConfig30 = createDisplayMode(HWC_CONFIG_ID_30, 0, Fps(30.0f).getPeriodNsecs());
-    DisplayModePtr mConfig30DifferentGroup =
-            createDisplayMode(HWC_CONFIG_ID_30, 1, Fps(30.0f).getPeriodNsecs());
-    DisplayModePtr mConfig30Frac =
-            createDisplayMode(HWC_CONFIG_ID_30_FRAC, 0, Fps(29.97f).getPeriodNsecs());
-    DisplayModePtr mConfig25 = createDisplayMode(HWC_CONFIG_ID_25, 0, Fps(25.0f).getPeriodNsecs());
-    DisplayModePtr mConfig25DifferentGroup =
-            createDisplayMode(HWC_CONFIG_ID_25, 1, Fps(25.0f).getPeriodNsecs());
-    DisplayModePtr mConfig50 = createDisplayMode(HWC_CONFIG_ID_50, 0, Fps(50.0f).getPeriodNsecs());
-    DisplayModePtr mConfig24 = createDisplayMode(HWC_CONFIG_ID_24, 0, Fps(24.0f).getPeriodNsecs());
-    DisplayModePtr mConfig24Frac =
-            createDisplayMode(HWC_CONFIG_ID_24_FRAC, 0, Fps(23.976f).getPeriodNsecs());
-
-    // Test device configurations
-    // The positions of the configs in the arrays below MUST match their IDs. For example,
-    // the first config should always be 60Hz, the second 90Hz etc.
-    DisplayModes m60OnlyConfigDevice = {mConfig60};
-    DisplayModes m60_90Device = {mConfig60, mConfig90};
-    DisplayModes m60_90DeviceWithDifferentGroups = {mConfig60, mConfig90DifferentGroup};
-    DisplayModes m60_90DeviceWithDifferentResolutions = {mConfig60, mConfig90DifferentResolution};
-    DisplayModes m60_72_90Device = {mConfig60, mConfig90, mConfig72};
-    DisplayModes m60_90_72_120Device = {mConfig60, mConfig90, mConfig72, mConfig120};
-    DisplayModes m30_60_72_90_120Device = {mConfig60, mConfig90, mConfig72, mConfig120, mConfig30};
-    DisplayModes m30_60Device = {mConfig60, mConfig90DifferentGroup, mConfig72DifferentGroup,
-                                 mConfig120DifferentGroup, mConfig30};
-    DisplayModes m30_60_72_90Device = {mConfig60, mConfig90, mConfig72, mConfig120DifferentGroup,
-                                       mConfig30};
-    DisplayModes m30_60_90Device = {mConfig60, mConfig90, mConfig72DifferentGroup,
-                                    mConfig120DifferentGroup, mConfig30};
-    DisplayModes m25_30_50_60Device = {mConfig60,
-                                       mConfig90,
-                                       mConfig72DifferentGroup,
-                                       mConfig120DifferentGroup,
-                                       mConfig30DifferentGroup,
-                                       mConfig25DifferentGroup,
-                                       mConfig50};
-    DisplayModes m60_120Device = {mConfig60, mConfig120};
+    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.
-    DisplayModes m24_25_30_50_60WithFracDevice = {mConfig24, mConfig24Frac, mConfig25,
-                                                  mConfig30, mConfig30Frac, mConfig50,
-                                                  mConfig60, mConfig60Frac};
-
-    // Expected RefreshRate objects
-    RefreshRate mExpected60Config = {mConfig60, RefreshRate::ConstructorTag(0)};
-    RefreshRate mExpectedAlmost60Config = {createDisplayMode(HWC_CONFIG_ID_60, 0, 16666665),
-                                           RefreshRate::ConstructorTag(0)};
-    RefreshRate mExpected90Config = {mConfig90, RefreshRate::ConstructorTag(0)};
-    RefreshRate mExpected90DifferentGroupConfig = {mConfig90DifferentGroup,
-                                                   RefreshRate::ConstructorTag(0)};
-    RefreshRate mExpected90DifferentResolutionConfig = {mConfig90DifferentResolution,
-                                                        RefreshRate::ConstructorTag(0)};
-    RefreshRate mExpected72Config = {mConfig72, RefreshRate::ConstructorTag(0)};
-    RefreshRate mExpected30Config = {mConfig30, RefreshRate::ConstructorTag(0)};
-    RefreshRate mExpected120Config = {mConfig120, RefreshRate::ConstructorTag(0)};
-
-    DisplayModePtr createDisplayMode(DisplayModeId modeId, int32_t group, int64_t vsyncPeriod,
-                                     ui::Size resolution = ui::Size());
+    static inline const DisplayModes kModes_24_25_30_50_60_Frac =
+            makeModes(kMode24, kMode24Frac, kMode25, kMode30, kMode30Frac, kMode50, kMode60,
+                      kMode60Frac);
 };
 
-using Builder = DisplayMode::Builder;
-
 RefreshRateConfigsTest::RefreshRateConfigsTest() {
     const ::testing::TestInfo* const test_info =
             ::testing::UnitTest::GetInstance()->current_test_info();
@@ -193,1415 +147,1160 @@
     ALOGD("**** Tearing down after %s.%s\n", test_info->test_case_name(), test_info->name());
 }
 
-DisplayModePtr RefreshRateConfigsTest::createDisplayMode(DisplayModeId modeId, int32_t group,
-                                                         int64_t vsyncPeriod, ui::Size resolution) {
-    return DisplayMode::Builder(hal::HWConfigId(modeId.value()))
-            .setId(modeId)
-            .setPhysicalDisplayId(PhysicalDisplayId(0))
-            .setVsyncPeriod(int32_t(vsyncPeriod))
-            .setGroup(group)
-            .setHeight(resolution.height)
-            .setWidth(resolution.width)
-            .build();
-}
-
 namespace {
-/* ------------------------------------------------------------------------
- * Test cases
- */
-TEST_F(RefreshRateConfigsTest, oneDeviceConfig_SwitchingSupported) {
-    auto refreshRateConfigs =
-            std::make_unique<RefreshRateConfigs>(m60OnlyConfigDevice,
-                                                 /*currentConfigId=*/HWC_CONFIG_ID_60);
+
+TEST_F(RefreshRateConfigsTest, oneMode_canSwitch) {
+    RefreshRateConfigs configs(kModes_60, kModeId60);
+    EXPECT_FALSE(configs.canSwitch());
 }
 
 TEST_F(RefreshRateConfigsTest, invalidPolicy) {
-    auto refreshRateConfigs =
-            std::make_unique<RefreshRateConfigs>(m60OnlyConfigDevice,
-                                                 /*currentConfigId=*/HWC_CONFIG_ID_60);
-    ASSERT_LT(refreshRateConfigs->setDisplayManagerPolicy({DisplayModeId(10), {Fps(60), Fps(60)}}),
-              0);
-    ASSERT_LT(refreshRateConfigs->setDisplayManagerPolicy({HWC_CONFIG_ID_60, {Fps(20), Fps(40)}}),
-              0);
+    RefreshRateConfigs configs(kModes_60, kModeId60);
+    EXPECT_LT(configs.setDisplayManagerPolicy({DisplayModeId(10), {60_Hz, 60_Hz}}), 0);
+    EXPECT_LT(configs.setDisplayManagerPolicy({kModeId60, {20_Hz, 40_Hz}}), 0);
 }
 
-TEST_F(RefreshRateConfigsTest, twoDeviceConfigs_storesFullRefreshRateMap) {
-    auto refreshRateConfigs =
-            std::make_unique<RefreshRateConfigs>(m60_90Device,
-                                                 /*currentConfigId=*/HWC_CONFIG_ID_60);
+TEST_F(RefreshRateConfigsTest, twoModes_storesFullRefreshRateMap) {
+    TestableRefreshRateConfigs configs(kModes_60_90, kModeId60);
 
-    const auto& minRate = getMinSupportedRefreshRate(*refreshRateConfigs);
-    const auto& performanceRate = getMaxSupportedRefreshRate(*refreshRateConfigs);
+    const auto minRate = configs.getMinSupportedRefreshRate();
+    const auto performanceRate = configs.getMaxSupportedRefreshRate();
 
-    ASSERT_EQ(mExpected60Config, minRate);
-    ASSERT_EQ(mExpected90Config, performanceRate);
+    EXPECT_EQ(kMode60, minRate);
+    EXPECT_EQ(kMode90, performanceRate);
 
-    const auto& minRateByPolicy = getMinRefreshRateByPolicy(*refreshRateConfigs);
-    const auto& performanceRateByPolicy = refreshRateConfigs->getMaxRefreshRateByPolicy();
-    ASSERT_EQ(minRateByPolicy, minRate);
-    ASSERT_EQ(performanceRateByPolicy, performanceRate);
+    const auto minRateByPolicy = configs.getMinRefreshRateByPolicy();
+    const auto performanceRateByPolicy = configs.getMaxRefreshRateByPolicy();
+
+    EXPECT_EQ(minRateByPolicy, minRate);
+    EXPECT_EQ(performanceRateByPolicy, performanceRate);
 }
 
-TEST_F(RefreshRateConfigsTest, twoDeviceConfigs_storesFullRefreshRateMap_differentGroups) {
-    auto refreshRateConfigs =
-            std::make_unique<RefreshRateConfigs>(m60_90DeviceWithDifferentGroups,
-                                                 /*currentConfigId=*/HWC_CONFIG_ID_60);
+TEST_F(RefreshRateConfigsTest, twoModes_storesFullRefreshRateMap_differentGroups) {
+    TestableRefreshRateConfigs configs(kModes_60_90_G1, kModeId60);
 
-    const auto& minRate = getMinRefreshRateByPolicy(*refreshRateConfigs);
-    const auto& performanceRate = getMaxSupportedRefreshRate(*refreshRateConfigs);
-    const auto& minRate60 = getMinRefreshRateByPolicy(*refreshRateConfigs);
-    const auto& performanceRate60 = refreshRateConfigs->getMaxRefreshRateByPolicy();
+    const auto minRate = configs.getMinRefreshRateByPolicy();
+    const auto performanceRate = configs.getMaxSupportedRefreshRate();
+    const auto minRate60 = configs.getMinRefreshRateByPolicy();
+    const auto performanceRate60 = configs.getMaxRefreshRateByPolicy();
 
-    ASSERT_EQ(mExpected60Config, minRate);
-    ASSERT_EQ(mExpected60Config, minRate60);
-    ASSERT_EQ(mExpected60Config, performanceRate60);
+    EXPECT_EQ(kMode60, minRate);
+    EXPECT_EQ(kMode60, minRate60);
+    EXPECT_EQ(kMode60, performanceRate60);
 
-    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy({HWC_CONFIG_ID_90, {Fps(60), Fps(90)}}),
-              0);
-    refreshRateConfigs->setCurrentModeId(HWC_CONFIG_ID_90);
+    EXPECT_GE(configs.setDisplayManagerPolicy({kModeId90, {60_Hz, 90_Hz}}), 0);
+    configs.setActiveModeId(kModeId90);
 
-    const auto& minRate90 = getMinRefreshRateByPolicy(*refreshRateConfigs);
-    const auto& performanceRate90 = refreshRateConfigs->getMaxRefreshRateByPolicy();
+    const auto minRate90 = configs.getMinRefreshRateByPolicy();
+    const auto performanceRate90 = configs.getMaxRefreshRateByPolicy();
 
-    ASSERT_EQ(mExpected90DifferentGroupConfig, performanceRate);
-    ASSERT_EQ(mExpected90DifferentGroupConfig, minRate90);
-    ASSERT_EQ(mExpected90DifferentGroupConfig, performanceRate90);
+    EXPECT_EQ(kMode90_G1, performanceRate);
+    EXPECT_EQ(kMode90_G1, minRate90);
+    EXPECT_EQ(kMode90_G1, performanceRate90);
 }
 
-TEST_F(RefreshRateConfigsTest, twoDeviceConfigs_storesFullRefreshRateMap_differentResolutions) {
-    auto refreshRateConfigs =
-            std::make_unique<RefreshRateConfigs>(m60_90DeviceWithDifferentResolutions,
-                                                 /*currentConfigId=*/HWC_CONFIG_ID_60);
+TEST_F(RefreshRateConfigsTest, twoModes_storesFullRefreshRateMap_differentResolutions) {
+    TestableRefreshRateConfigs configs(kModes_60_90_4K, kModeId60);
 
-    const auto& minRate = getMinRefreshRateByPolicy(*refreshRateConfigs);
-    const auto& performanceRate = getMaxSupportedRefreshRate(*refreshRateConfigs);
-    const auto& minRate60 = getMinRefreshRateByPolicy(*refreshRateConfigs);
-    const auto& performanceRate60 = refreshRateConfigs->getMaxRefreshRateByPolicy();
+    const auto minRate = configs.getMinRefreshRateByPolicy();
+    const auto performanceRate = configs.getMaxSupportedRefreshRate();
+    const auto minRate60 = configs.getMinRefreshRateByPolicy();
+    const auto performanceRate60 = configs.getMaxRefreshRateByPolicy();
 
-    ASSERT_EQ(mExpected60Config, minRate);
-    ASSERT_EQ(mExpected60Config, minRate60);
-    ASSERT_EQ(mExpected60Config, performanceRate60);
+    EXPECT_EQ(kMode60, minRate);
+    EXPECT_EQ(kMode60, minRate60);
+    EXPECT_EQ(kMode60, performanceRate60);
 
-    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy({HWC_CONFIG_ID_90, {Fps(60), Fps(90)}}),
-              0);
-    refreshRateConfigs->setCurrentModeId(HWC_CONFIG_ID_90);
+    EXPECT_GE(configs.setDisplayManagerPolicy({kModeId90, {60_Hz, 90_Hz}}), 0);
+    configs.setActiveModeId(kModeId90);
 
-    const auto& minRate90 = getMinRefreshRateByPolicy(*refreshRateConfigs);
-    const auto& performanceRate90 = refreshRateConfigs->getMaxRefreshRateByPolicy();
+    const auto minRate90 = configs.getMinRefreshRateByPolicy();
+    const auto performanceRate90 = configs.getMaxRefreshRateByPolicy();
 
-    ASSERT_EQ(mExpected90DifferentResolutionConfig, performanceRate);
-    ASSERT_EQ(mExpected90DifferentResolutionConfig, minRate90);
-    ASSERT_EQ(mExpected90DifferentResolutionConfig, performanceRate90);
+    EXPECT_EQ(kMode90_4K, performanceRate);
+    EXPECT_EQ(kMode90_4K, minRate90);
+    EXPECT_EQ(kMode90_4K, performanceRate90);
 }
 
-TEST_F(RefreshRateConfigsTest, twoDeviceConfigs_policyChange) {
-    auto refreshRateConfigs =
-            std::make_unique<RefreshRateConfigs>(m60_90Device,
-                                                 /*currentConfigId=*/HWC_CONFIG_ID_60);
+TEST_F(RefreshRateConfigsTest, twoModes_policyChange) {
+    TestableRefreshRateConfigs configs(kModes_60_90, kModeId60);
 
-    auto minRate = getMinRefreshRateByPolicy(*refreshRateConfigs);
-    auto performanceRate = refreshRateConfigs->getMaxRefreshRateByPolicy();
+    const auto minRate = configs.getMinRefreshRateByPolicy();
+    const auto performanceRate = configs.getMaxRefreshRateByPolicy();
 
-    ASSERT_EQ(mExpected60Config, minRate);
-    ASSERT_EQ(mExpected90Config, performanceRate);
+    EXPECT_EQ(kMode60, minRate);
+    EXPECT_EQ(kMode90, performanceRate);
 
-    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy({HWC_CONFIG_ID_60, {Fps(60), Fps(60)}}),
-              0);
+    EXPECT_GE(configs.setDisplayManagerPolicy({kModeId60, {60_Hz, 60_Hz}}), 0);
 
-    auto minRate60 = getMinRefreshRateByPolicy(*refreshRateConfigs);
-    auto performanceRate60 = refreshRateConfigs->getMaxRefreshRateByPolicy();
-    ASSERT_EQ(mExpected60Config, minRate60);
-    ASSERT_EQ(mExpected60Config, performanceRate60);
+    const auto minRate60 = configs.getMinRefreshRateByPolicy();
+    const auto performanceRate60 = configs.getMaxRefreshRateByPolicy();
+
+    EXPECT_EQ(kMode60, minRate60);
+    EXPECT_EQ(kMode60, performanceRate60);
 }
 
-TEST_F(RefreshRateConfigsTest, twoDeviceConfigs_getCurrentRefreshRate) {
-    auto refreshRateConfigs =
-            std::make_unique<RefreshRateConfigs>(m60_90Device,
-                                                 /*currentConfigId=*/HWC_CONFIG_ID_60);
+TEST_F(RefreshRateConfigsTest, twoModes_getActiveMode) {
+    TestableRefreshRateConfigs configs(kModes_60_90, kModeId60);
     {
-        auto current = refreshRateConfigs->getCurrentRefreshRate();
-        EXPECT_EQ(current.getModeId(), HWC_CONFIG_ID_60);
+        const auto mode = configs.getActiveMode();
+        EXPECT_EQ(mode->getId(), kModeId60);
     }
 
-    refreshRateConfigs->setCurrentModeId(HWC_CONFIG_ID_90);
+    configs.setActiveModeId(kModeId90);
     {
-        auto current = refreshRateConfigs->getCurrentRefreshRate();
-        EXPECT_EQ(current.getModeId(), HWC_CONFIG_ID_90);
+        const auto mode = configs.getActiveMode();
+        EXPECT_EQ(mode->getId(), kModeId90);
     }
 
-    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy({HWC_CONFIG_ID_90, {Fps(90), Fps(90)}}),
-              0);
+    EXPECT_GE(configs.setDisplayManagerPolicy({kModeId90, {90_Hz, 90_Hz}}), 0);
     {
-        auto current = refreshRateConfigs->getCurrentRefreshRate();
-        EXPECT_EQ(current.getModeId(), HWC_CONFIG_ID_90);
+        const auto mode = configs.getActiveMode();
+        EXPECT_EQ(mode->getId(), kModeId90);
     }
 }
 
 TEST_F(RefreshRateConfigsTest, getBestRefreshRate_noLayers) {
-    auto refreshRateConfigs =
-            std::make_unique<RefreshRateConfigs>(m60_72_90Device, /*currentConfigId=*/
-                                                 HWC_CONFIG_ID_72);
+    {
+        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.
-    auto layers = std::vector<LayerRequirement>{};
-    EXPECT_EQ(mExpected90Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
+        // If there are no layers we select the default frame rate, which is the max of the primary
+        // range.
+        EXPECT_EQ(kMode90, configs.getBestRefreshRate());
 
-    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy({HWC_CONFIG_ID_60, {Fps(60), Fps(60)}}),
-              0);
-    EXPECT_EQ(mExpected60Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
+        EXPECT_EQ(configs.setDisplayManagerPolicy({kModeId60, {60_Hz, 60_Hz}}), NO_ERROR);
+        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(configs.setDisplayManagerPolicy({kModeId90, kAllowGroupSwitching, {0_Hz, 90_Hz}}),
+                  NO_ERROR);
+        EXPECT_EQ(kMode90_G1, configs.getBestRefreshRate());
+    }
 }
 
 TEST_F(RefreshRateConfigsTest, getBestRefreshRate_60_90) {
-    auto refreshRateConfigs =
-            std::make_unique<RefreshRateConfigs>(m60_90Device,
-                                                 /*currentConfigId=*/HWC_CONFIG_ID_60);
+    TestableRefreshRateConfigs configs(kModes_60_90, kModeId60);
 
-    auto layers = std::vector<LayerRequirement>{LayerRequirement{.weight = 1.0f}};
+    std::vector<LayerRequirement> layers = {{.weight = 1.f}};
     auto& lr = layers[0];
 
     lr.vote = LayerVoteType::Min;
     lr.name = "Min";
-    EXPECT_EQ(mExpected60Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
+    EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers));
 
     lr.vote = LayerVoteType::Max;
     lr.name = "Max";
-    EXPECT_EQ(mExpected90Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
+    EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
 
-    lr.desiredRefreshRate = Fps(90.0f);
+    lr.desiredRefreshRate = 90_Hz;
     lr.vote = LayerVoteType::Heuristic;
     lr.name = "90Hz Heuristic";
-    EXPECT_EQ(mExpected90Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
+    EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
 
-    lr.desiredRefreshRate = Fps(60.0f);
+    lr.desiredRefreshRate = 60_Hz;
     lr.name = "60Hz Heuristic";
-    EXPECT_EQ(mExpected60Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
+    EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers));
 
-    lr.desiredRefreshRate = Fps(45.0f);
+    lr.desiredRefreshRate = 45_Hz;
     lr.name = "45Hz Heuristic";
-    EXPECT_EQ(mExpected90Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
+    EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
 
-    lr.desiredRefreshRate = Fps(30.0f);
+    lr.desiredRefreshRate = 30_Hz;
     lr.name = "30Hz Heuristic";
-    EXPECT_EQ(mExpected60Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
+    EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers));
 
-    lr.desiredRefreshRate = Fps(24.0f);
+    lr.desiredRefreshRate = 24_Hz;
     lr.name = "24Hz Heuristic";
-    EXPECT_EQ(mExpected60Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
+    EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers));
 
     lr.name = "";
-    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy({HWC_CONFIG_ID_60, {Fps(60), Fps(60)}}),
-              0);
+    EXPECT_GE(configs.setDisplayManagerPolicy({kModeId60, {60_Hz, 60_Hz}}), 0);
 
     lr.vote = LayerVoteType::Min;
-    EXPECT_EQ(mExpected60Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
+    EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers));
 
     lr.vote = LayerVoteType::Max;
-    EXPECT_EQ(mExpected60Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
+    EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers));
 
-    lr.desiredRefreshRate = Fps(90.0f);
+    lr.desiredRefreshRate = 90_Hz;
     lr.vote = LayerVoteType::Heuristic;
-    EXPECT_EQ(mExpected60Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
+    EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers));
 
-    lr.desiredRefreshRate = Fps(60.0f);
-    EXPECT_EQ(mExpected60Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
+    lr.desiredRefreshRate = 60_Hz;
+    EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers));
 
-    lr.desiredRefreshRate = Fps(45.0f);
-    EXPECT_EQ(mExpected60Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
+    lr.desiredRefreshRate = 45_Hz;
+    EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers));
 
-    lr.desiredRefreshRate = Fps(30.0f);
-    EXPECT_EQ(mExpected60Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
+    lr.desiredRefreshRate = 30_Hz;
+    EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers));
 
-    lr.desiredRefreshRate = Fps(24.0f);
-    EXPECT_EQ(mExpected60Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
+    lr.desiredRefreshRate = 24_Hz;
+    EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers));
 
-    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy(
-                      {HWC_CONFIG_ID_90, {Fps(90.0f), Fps(90.0f)}}),
-              0);
+    EXPECT_GE(configs.setDisplayManagerPolicy({kModeId90, {90_Hz, 90_Hz}}), 0);
 
     lr.vote = LayerVoteType::Min;
-    EXPECT_EQ(mExpected90Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
+    EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
 
     lr.vote = LayerVoteType::Max;
-    EXPECT_EQ(mExpected90Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
+    EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
 
-    lr.desiredRefreshRate = Fps(90.0f);
+    lr.desiredRefreshRate = 90_Hz;
     lr.vote = LayerVoteType::Heuristic;
-    EXPECT_EQ(mExpected90Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
+    EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
 
-    lr.desiredRefreshRate = Fps(60.0f);
-    EXPECT_EQ(mExpected90Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
+    lr.desiredRefreshRate = 60_Hz;
+    EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
 
-    lr.desiredRefreshRate = Fps(45.0f);
-    EXPECT_EQ(mExpected90Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
+    lr.desiredRefreshRate = 45_Hz;
+    EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
 
-    lr.desiredRefreshRate = Fps(30.0f);
-    EXPECT_EQ(mExpected90Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
+    lr.desiredRefreshRate = 30_Hz;
+    EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
 
-    lr.desiredRefreshRate = Fps(24.0f);
-    EXPECT_EQ(mExpected90Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
+    lr.desiredRefreshRate = 24_Hz;
+    EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
 
-    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy(
-                      {HWC_CONFIG_ID_60, {Fps(0.0f), Fps(120.0f)}}),
-              0);
+    EXPECT_GE(configs.setDisplayManagerPolicy({kModeId60, {0_Hz, 120_Hz}}), 0);
     lr.vote = LayerVoteType::Min;
-    EXPECT_EQ(mExpected60Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
+    EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers));
 
     lr.vote = LayerVoteType::Max;
-    EXPECT_EQ(mExpected90Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
+    EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
 
-    lr.desiredRefreshRate = Fps(90.0f);
+    lr.desiredRefreshRate = 90_Hz;
     lr.vote = LayerVoteType::Heuristic;
-    EXPECT_EQ(mExpected90Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
+    EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
 
-    lr.desiredRefreshRate = Fps(60.0f);
-    EXPECT_EQ(mExpected60Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
+    lr.desiredRefreshRate = 60_Hz;
+    EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers));
 
-    lr.desiredRefreshRate = Fps(45.0f);
-    EXPECT_EQ(mExpected90Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
+    lr.desiredRefreshRate = 45_Hz;
+    EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
 
-    lr.desiredRefreshRate = Fps(30.0f);
-    EXPECT_EQ(mExpected60Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
+    lr.desiredRefreshRate = 30_Hz;
+    EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers));
 
-    lr.desiredRefreshRate = Fps(24.0f);
-    EXPECT_EQ(mExpected60Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
+    lr.desiredRefreshRate = 24_Hz;
+    EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers));
 }
 
 TEST_F(RefreshRateConfigsTest, getBestRefreshRate_multipleThreshold_60_90) {
-    RefreshRateConfigs::Config config = {.frameRateMultipleThreshold = 90};
-    auto refreshRateConfigs =
-            std::make_unique<RefreshRateConfigs>(m60_90Device,
-                                                 /*currentConfigId=*/HWC_CONFIG_ID_60, config);
+    TestableRefreshRateConfigs configs(kModes_60_90, kModeId60, {.frameRateMultipleThreshold = 90});
 
-    auto layers = std::vector<LayerRequirement>{LayerRequirement{.weight = 1.0f}};
+    std::vector<LayerRequirement> layers = {{.weight = 1.f}};
     auto& lr = layers[0];
 
     lr.vote = LayerVoteType::Min;
     lr.name = "Min";
-    EXPECT_EQ(mExpected60Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
+    EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers));
 
     lr.vote = LayerVoteType::Max;
     lr.name = "Max";
-    EXPECT_EQ(mExpected90Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
+    EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
 
-    lr.desiredRefreshRate = Fps(90.0f);
+    lr.desiredRefreshRate = 90_Hz;
     lr.vote = LayerVoteType::Heuristic;
     lr.name = "90Hz Heuristic";
-    EXPECT_EQ(mExpected90Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
+    EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
 
-    lr.desiredRefreshRate = Fps(60.0f);
+    lr.desiredRefreshRate = 60_Hz;
     lr.name = "60Hz Heuristic";
-    EXPECT_EQ(mExpected60Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
+    EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers));
 
-    lr.desiredRefreshRate = Fps(45.0f);
+    lr.desiredRefreshRate = 45_Hz;
     lr.name = "45Hz Heuristic";
-    EXPECT_EQ(mExpected90Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
+    EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
 
-    lr.desiredRefreshRate = Fps(30.0f);
+    lr.desiredRefreshRate = 30_Hz;
     lr.name = "30Hz Heuristic";
-    EXPECT_EQ(mExpected60Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
+    EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers));
 
-    lr.desiredRefreshRate = Fps(24.0f);
+    lr.desiredRefreshRate = 24_Hz;
     lr.name = "24Hz Heuristic";
-    EXPECT_EQ(mExpected60Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
+    EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers));
 }
 
 TEST_F(RefreshRateConfigsTest, getBestRefreshRate_60_72_90) {
-    auto refreshRateConfigs =
-            std::make_unique<RefreshRateConfigs>(m60_72_90Device,
-                                                 /*currentConfigId=*/HWC_CONFIG_ID_60);
+    TestableRefreshRateConfigs configs(kModes_60_72_90, kModeId60);
 
-    auto layers = std::vector<LayerRequirement>{LayerRequirement{.weight = 1.0f}};
+    std::vector<LayerRequirement> layers = {{.weight = 1.f}};
     auto& lr = layers[0];
 
     lr.vote = LayerVoteType::Min;
-    EXPECT_EQ(mExpected60Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
+    EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers));
 
     lr.vote = LayerVoteType::Max;
-    EXPECT_EQ(mExpected90Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
+    EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
 
-    lr.desiredRefreshRate = Fps(90.0f);
+    lr.desiredRefreshRate = 90_Hz;
     lr.vote = LayerVoteType::Heuristic;
-    EXPECT_EQ(mExpected90Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
+    EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
 
-    lr.desiredRefreshRate = Fps(60.0f);
-    EXPECT_EQ(mExpected60Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
+    lr.desiredRefreshRate = 60_Hz;
+    EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers));
 
-    lr.desiredRefreshRate = Fps(45.0f);
-    EXPECT_EQ(mExpected90Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
+    lr.desiredRefreshRate = 45_Hz;
+    EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
 
-    lr.desiredRefreshRate = Fps(30.0f);
-    EXPECT_EQ(mExpected60Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
+    lr.desiredRefreshRate = 30_Hz;
+    EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers));
 
-    lr.desiredRefreshRate = Fps(24.0f);
-    EXPECT_EQ(mExpected72Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
+    lr.desiredRefreshRate = 24_Hz;
+    EXPECT_EQ(kMode72, configs.getBestRefreshRate(layers));
 }
 
 TEST_F(RefreshRateConfigsTest, getBestRefreshRate_30_60_72_90_120) {
-    auto refreshRateConfigs =
-            std::make_unique<RefreshRateConfigs>(m30_60_72_90_120Device,
-                                                 /*currentConfigId=*/HWC_CONFIG_ID_60);
+    TestableRefreshRateConfigs configs(kModes_30_60_72_90_120, kModeId60);
 
-    auto layers = std::vector<LayerRequirement>{LayerRequirement{.weight = 1.0f},
-                                                LayerRequirement{.weight = 1.0f}};
+    std::vector<LayerRequirement> layers = {{.weight = 1.f}, {.weight = 1.f}};
     auto& lr1 = layers[0];
     auto& lr2 = layers[1];
 
-    lr1.desiredRefreshRate = Fps(24.0f);
+    lr1.desiredRefreshRate = 24_Hz;
     lr1.vote = LayerVoteType::Heuristic;
-    lr2.desiredRefreshRate = Fps(60.0f);
+    lr2.desiredRefreshRate = 60_Hz;
     lr2.vote = LayerVoteType::Heuristic;
-    EXPECT_EQ(mExpected120Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
+    EXPECT_EQ(kMode120, configs.getBestRefreshRate(layers));
 
-    lr1.desiredRefreshRate = Fps(24.0f);
+    lr1.desiredRefreshRate = 24_Hz;
     lr1.vote = LayerVoteType::Heuristic;
-    lr2.desiredRefreshRate = Fps(48.0f);
+    lr2.desiredRefreshRate = 48_Hz;
     lr2.vote = LayerVoteType::Heuristic;
-    EXPECT_EQ(mExpected72Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
+    EXPECT_EQ(kMode72, configs.getBestRefreshRate(layers));
 
-    lr1.desiredRefreshRate = Fps(24.0f);
+    lr1.desiredRefreshRate = 24_Hz;
     lr1.vote = LayerVoteType::Heuristic;
-    lr2.desiredRefreshRate = Fps(48.0f);
+    lr2.desiredRefreshRate = 48_Hz;
     lr2.vote = LayerVoteType::Heuristic;
-    EXPECT_EQ(mExpected72Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
+    EXPECT_EQ(kMode72, configs.getBestRefreshRate(layers));
 }
 
 TEST_F(RefreshRateConfigsTest, getBestRefreshRate_30_60_90_120_DifferentTypes) {
-    auto refreshRateConfigs =
-            std::make_unique<RefreshRateConfigs>(m30_60_72_90_120Device,
-                                                 /*currentConfigId=*/HWC_CONFIG_ID_60);
+    TestableRefreshRateConfigs configs(kModes_30_60_72_90_120, kModeId60);
 
-    auto layers = std::vector<LayerRequirement>{LayerRequirement{.weight = 1.0f},
-                                                LayerRequirement{.weight = 1.0f}};
+    std::vector<LayerRequirement> layers = {{.weight = 1.f}, {.weight = 1.f}};
     auto& lr1 = layers[0];
     auto& lr2 = layers[1];
 
-    lr1.desiredRefreshRate = Fps(24.0f);
+    lr1.desiredRefreshRate = 24_Hz;
     lr1.vote = LayerVoteType::ExplicitDefault;
     lr1.name = "24Hz ExplicitDefault";
-    lr2.desiredRefreshRate = Fps(60.0f);
+    lr2.desiredRefreshRate = 60_Hz;
     lr2.vote = LayerVoteType::Heuristic;
     lr2.name = "60Hz Heuristic";
-    EXPECT_EQ(mExpected120Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
+    EXPECT_EQ(kMode120, configs.getBestRefreshRate(layers));
 
-    lr1.desiredRefreshRate = Fps(24.0f);
+    lr1.desiredRefreshRate = 24_Hz;
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
     lr1.name = "24Hz ExplicitExactOrMultiple";
-    lr2.desiredRefreshRate = Fps(60.0f);
+    lr2.desiredRefreshRate = 60_Hz;
     lr2.vote = LayerVoteType::Heuristic;
     lr2.name = "60Hz Heuristic";
-    EXPECT_EQ(mExpected120Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
+    EXPECT_EQ(kMode120, configs.getBestRefreshRate(layers));
 
-    lr1.desiredRefreshRate = Fps(24.0f);
+    lr1.desiredRefreshRate = 24_Hz;
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
     lr1.name = "24Hz ExplicitExactOrMultiple";
-    lr2.desiredRefreshRate = Fps(60.0f);
+    lr2.desiredRefreshRate = 60_Hz;
     lr2.vote = LayerVoteType::ExplicitDefault;
     lr2.name = "60Hz ExplicitDefault";
-    EXPECT_EQ(mExpected120Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
+    EXPECT_EQ(kMode120, configs.getBestRefreshRate(layers));
 
-    lr1.desiredRefreshRate = Fps(24.0f);
+    lr1.desiredRefreshRate = 24_Hz;
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
     lr1.name = "24Hz ExplicitExactOrMultiple";
-    lr2.desiredRefreshRate = Fps(90.0f);
+    lr2.desiredRefreshRate = 90_Hz;
     lr2.vote = LayerVoteType::Heuristic;
     lr2.name = "90Hz Heuristic";
-    EXPECT_EQ(mExpected90Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
+    EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
 
-    lr1.desiredRefreshRate = Fps(24.0f);
+    lr1.desiredRefreshRate = 24_Hz;
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
     lr1.name = "24Hz ExplicitExactOrMultiple";
-    lr2.desiredRefreshRate = Fps(90.0f);
+    lr2.desiredRefreshRate = 90_Hz;
     lr2.vote = LayerVoteType::ExplicitDefault;
     lr2.name = "90Hz Heuristic";
-    EXPECT_EQ(mExpected72Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
+    EXPECT_EQ(kMode72, configs.getBestRefreshRate(layers));
 
-    lr1.desiredRefreshRate = Fps(24.0f);
+    lr1.desiredRefreshRate = 24_Hz;
     lr1.vote = LayerVoteType::ExplicitDefault;
     lr1.name = "24Hz ExplicitDefault";
-    lr2.desiredRefreshRate = Fps(90.0f);
+    lr2.desiredRefreshRate = 90_Hz;
     lr2.vote = LayerVoteType::Heuristic;
     lr2.name = "90Hz Heuristic";
-    EXPECT_EQ(mExpected90Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
+    EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
 
-    lr1.desiredRefreshRate = Fps(24.0f);
+    lr1.desiredRefreshRate = 24_Hz;
     lr1.vote = LayerVoteType::Heuristic;
     lr1.name = "24Hz Heuristic";
-    lr2.desiredRefreshRate = Fps(90.0f);
+    lr2.desiredRefreshRate = 90_Hz;
     lr2.vote = LayerVoteType::ExplicitDefault;
     lr2.name = "90Hz ExplicitDefault";
-    EXPECT_EQ(mExpected72Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
+    EXPECT_EQ(kMode72, configs.getBestRefreshRate(layers));
 
-    lr1.desiredRefreshRate = Fps(24.0f);
+    lr1.desiredRefreshRate = 24_Hz;
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
     lr1.name = "24Hz ExplicitExactOrMultiple";
-    lr2.desiredRefreshRate = Fps(90.0f);
+    lr2.desiredRefreshRate = 90_Hz;
     lr2.vote = LayerVoteType::ExplicitDefault;
     lr2.name = "90Hz ExplicitDefault";
-    EXPECT_EQ(mExpected72Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
+    EXPECT_EQ(kMode72, configs.getBestRefreshRate(layers));
 
-    lr1.desiredRefreshRate = Fps(24.0f);
+    lr1.desiredRefreshRate = 24_Hz;
     lr1.vote = LayerVoteType::ExplicitDefault;
     lr1.name = "24Hz ExplicitDefault";
-    lr2.desiredRefreshRate = Fps(90.0f);
+    lr2.desiredRefreshRate = 90_Hz;
     lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
     lr2.name = "90Hz ExplicitExactOrMultiple";
-    EXPECT_EQ(mExpected90Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
+    EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
 }
 
 TEST_F(RefreshRateConfigsTest, getBestRefreshRate_30_60_90_120_DifferentTypes_multipleThreshold) {
-    RefreshRateConfigs::Config config = {.frameRateMultipleThreshold = 120};
-    auto refreshRateConfigs =
-            std::make_unique<RefreshRateConfigs>(m30_60_72_90_120Device,
-                                                 /*currentConfigId=*/HWC_CONFIG_ID_60, config);
+    TestableRefreshRateConfigs configs(kModes_30_60_72_90_120, kModeId60,
+                                       {.frameRateMultipleThreshold = 120});
 
-    auto layers = std::vector<LayerRequirement>{LayerRequirement{.weight = 1.0f},
-                                                LayerRequirement{.weight = 1.0f}};
+    std::vector<LayerRequirement> layers = {{.weight = 1.f}, {.weight = 1.f}};
     auto& lr1 = layers[0];
     auto& lr2 = layers[1];
 
-    lr1.desiredRefreshRate = Fps(24.0f);
+    lr1.desiredRefreshRate = 24_Hz;
     lr1.vote = LayerVoteType::ExplicitDefault;
     lr1.name = "24Hz ExplicitDefault";
-    lr2.desiredRefreshRate = Fps(60.0f);
+    lr2.desiredRefreshRate = 60_Hz;
     lr2.vote = LayerVoteType::Heuristic;
     lr2.name = "60Hz Heuristic";
-    EXPECT_EQ(mExpected120Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
+    EXPECT_EQ(kMode120, configs.getBestRefreshRate(layers));
 
-    lr1.desiredRefreshRate = Fps(24.0f);
+    lr1.desiredRefreshRate = 24_Hz;
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
     lr1.name = "24Hz ExplicitExactOrMultiple";
-    lr2.desiredRefreshRate = Fps(60.0f);
+    lr2.desiredRefreshRate = 60_Hz;
     lr2.vote = LayerVoteType::Heuristic;
     lr2.name = "60Hz Heuristic";
-    EXPECT_EQ(mExpected60Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
+    EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers));
 
-    lr1.desiredRefreshRate = Fps(24.0f);
+    lr1.desiredRefreshRate = 24_Hz;
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
     lr1.name = "24Hz ExplicitExactOrMultiple";
-    lr2.desiredRefreshRate = Fps(60.0f);
+    lr2.desiredRefreshRate = 60_Hz;
     lr2.vote = LayerVoteType::ExplicitDefault;
     lr2.name = "60Hz ExplicitDefault";
-    EXPECT_EQ(mExpected72Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
+    EXPECT_EQ(kMode72, configs.getBestRefreshRate(layers));
 
-    lr1.desiredRefreshRate = Fps(24.0f);
+    lr1.desiredRefreshRate = 24_Hz;
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
     lr1.name = "24Hz ExplicitExactOrMultiple";
-    lr2.desiredRefreshRate = Fps(90.0f);
+    lr2.desiredRefreshRate = 90_Hz;
     lr2.vote = LayerVoteType::Heuristic;
     lr2.name = "90Hz Heuristic";
-    EXPECT_EQ(mExpected90Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
+    EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
 
-    lr1.desiredRefreshRate = Fps(24.0f);
+    lr1.desiredRefreshRate = 24_Hz;
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
     lr1.name = "24Hz ExplicitExactOrMultiple";
-    lr2.desiredRefreshRate = Fps(90.0f);
+    lr2.desiredRefreshRate = 90_Hz;
     lr2.vote = LayerVoteType::ExplicitDefault;
     lr2.name = "90Hz Heuristic";
-    EXPECT_EQ(mExpected72Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
+    EXPECT_EQ(kMode72, configs.getBestRefreshRate(layers));
 
-    lr1.desiredRefreshRate = Fps(24.0f);
+    lr1.desiredRefreshRate = 24_Hz;
     lr1.vote = LayerVoteType::ExplicitDefault;
     lr1.name = "24Hz ExplicitDefault";
-    lr2.desiredRefreshRate = Fps(90.0f);
+    lr2.desiredRefreshRate = 90_Hz;
     lr2.vote = LayerVoteType::Heuristic;
     lr2.name = "90Hz Heuristic";
-    EXPECT_EQ(mExpected90Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
+    EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
 
-    lr1.desiredRefreshRate = Fps(24.0f);
+    lr1.desiredRefreshRate = 24_Hz;
     lr1.vote = LayerVoteType::Heuristic;
     lr1.name = "24Hz Heuristic";
-    lr2.desiredRefreshRate = Fps(90.0f);
+    lr2.desiredRefreshRate = 90_Hz;
     lr2.vote = LayerVoteType::ExplicitDefault;
     lr2.name = "90Hz ExplicitDefault";
-    EXPECT_EQ(mExpected72Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
+    EXPECT_EQ(kMode72, configs.getBestRefreshRate(layers));
 
-    lr1.desiredRefreshRate = Fps(24.0f);
+    lr1.desiredRefreshRate = 24_Hz;
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
     lr1.name = "24Hz ExplicitExactOrMultiple";
-    lr2.desiredRefreshRate = Fps(90.0f);
+    lr2.desiredRefreshRate = 90_Hz;
     lr2.vote = LayerVoteType::ExplicitDefault;
     lr2.name = "90Hz ExplicitDefault";
-    EXPECT_EQ(mExpected72Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
+    EXPECT_EQ(kMode72, configs.getBestRefreshRate(layers));
 
-    lr1.desiredRefreshRate = Fps(24.0f);
+    lr1.desiredRefreshRate = 24_Hz;
     lr1.vote = LayerVoteType::ExplicitDefault;
     lr1.name = "24Hz ExplicitDefault";
-    lr2.desiredRefreshRate = Fps(90.0f);
+    lr2.desiredRefreshRate = 90_Hz;
     lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
     lr2.name = "90Hz ExplicitExactOrMultiple";
-    EXPECT_EQ(mExpected90Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
+    EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
 }
 
 TEST_F(RefreshRateConfigsTest, getBestRefreshRate_30_60) {
-    auto refreshRateConfigs =
-            std::make_unique<RefreshRateConfigs>(m30_60Device,
-                                                 /*currentConfigId=*/HWC_CONFIG_ID_60);
+    TestableRefreshRateConfigs configs(kModes_30_60, kModeId60);
 
-    auto layers = std::vector<LayerRequirement>{LayerRequirement{.weight = 1.0f}};
+    std::vector<LayerRequirement> layers = {{.weight = 1.f}};
     auto& lr = layers[0];
 
     lr.vote = LayerVoteType::Min;
-    EXPECT_EQ(mExpected30Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
+    EXPECT_EQ(kMode30, configs.getBestRefreshRate(layers));
 
     lr.vote = LayerVoteType::Max;
-    EXPECT_EQ(mExpected60Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
+    EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers));
 
-    lr.desiredRefreshRate = Fps(90.0f);
+    lr.desiredRefreshRate = 90_Hz;
     lr.vote = LayerVoteType::Heuristic;
-    EXPECT_EQ(mExpected60Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
+    EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers));
 
-    lr.desiredRefreshRate = Fps(60.0f);
-    EXPECT_EQ(mExpected60Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
+    lr.desiredRefreshRate = 60_Hz;
+    EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers));
 
-    lr.desiredRefreshRate = Fps(45.0f);
-    EXPECT_EQ(mExpected60Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
+    lr.desiredRefreshRate = 45_Hz;
+    EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers));
 
-    lr.desiredRefreshRate = Fps(30.0f);
-    EXPECT_EQ(mExpected30Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
+    lr.desiredRefreshRate = 30_Hz;
+    EXPECT_EQ(kMode30, configs.getBestRefreshRate(layers));
 
-    lr.desiredRefreshRate = Fps(24.0f);
-    EXPECT_EQ(mExpected60Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
+    lr.desiredRefreshRate = 24_Hz;
+    EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers));
 }
 
 TEST_F(RefreshRateConfigsTest, getBestRefreshRate_30_60_72_90) {
-    auto refreshRateConfigs =
-            std::make_unique<RefreshRateConfigs>(m30_60_72_90Device,
-                                                 /*currentConfigId=*/HWC_CONFIG_ID_60);
+    TestableRefreshRateConfigs configs(kModes_30_60_72_90, kModeId60);
 
-    auto layers = std::vector<LayerRequirement>{LayerRequirement{.weight = 1.0f}};
+    std::vector<LayerRequirement> layers = {{.weight = 1.f}};
     auto& lr = layers[0];
 
     lr.vote = LayerVoteType::Min;
     lr.name = "Min";
-    EXPECT_EQ(mExpected30Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
+    EXPECT_EQ(kMode30, configs.getBestRefreshRate(layers));
 
     lr.vote = LayerVoteType::Max;
     lr.name = "Max";
-    EXPECT_EQ(mExpected90Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
+    EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
 
-    lr.desiredRefreshRate = Fps(90.0f);
+    lr.desiredRefreshRate = 90_Hz;
     lr.vote = LayerVoteType::Heuristic;
     lr.name = "90Hz Heuristic";
-    EXPECT_EQ(mExpected90Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
+    EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
 
-    lr.desiredRefreshRate = Fps(60.0f);
+    lr.desiredRefreshRate = 60_Hz;
     lr.name = "60Hz Heuristic";
-    EXPECT_EQ(mExpected60Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
-    EXPECT_EQ(mExpected90Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = true, .idle = false}));
+    EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers));
+    EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers, {.touch = true}));
 
-    lr.desiredRefreshRate = Fps(45.0f);
+    lr.desiredRefreshRate = 45_Hz;
     lr.name = "45Hz Heuristic";
-    EXPECT_EQ(mExpected90Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
-    EXPECT_EQ(mExpected90Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = true, .idle = false}));
+    EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
+    EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers, {.touch = true}));
 
-    lr.desiredRefreshRate = Fps(30.0f);
+    lr.desiredRefreshRate = 30_Hz;
     lr.name = "30Hz Heuristic";
-    EXPECT_EQ(mExpected30Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
-    EXPECT_EQ(mExpected90Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = true, .idle = false}));
+    EXPECT_EQ(kMode30, configs.getBestRefreshRate(layers));
+    EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers, {.touch = true}));
 
-    lr.desiredRefreshRate = Fps(24.0f);
+    lr.desiredRefreshRate = 24_Hz;
     lr.name = "24Hz Heuristic";
-    EXPECT_EQ(mExpected72Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
-    EXPECT_EQ(mExpected90Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = true, .idle = false}));
+    EXPECT_EQ(kMode72, configs.getBestRefreshRate(layers));
+    EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers, {.touch = true}));
 
-    lr.desiredRefreshRate = Fps(24.0f);
+    lr.desiredRefreshRate = 24_Hz;
     lr.vote = LayerVoteType::ExplicitExactOrMultiple;
     lr.name = "24Hz ExplicitExactOrMultiple";
-    EXPECT_EQ(mExpected72Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
-    EXPECT_EQ(mExpected90Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = true, .idle = false}));
+    EXPECT_EQ(kMode72, configs.getBestRefreshRate(layers));
+    EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers, {.touch = true}));
 }
 
 TEST_F(RefreshRateConfigsTest, getBestRefreshRate_PriorityTest) {
-    auto refreshRateConfigs =
-            std::make_unique<RefreshRateConfigs>(m30_60_90Device,
-                                                 /*currentConfigId=*/HWC_CONFIG_ID_60);
+    TestableRefreshRateConfigs configs(kModes_30_60_90, kModeId60);
 
-    auto layers = std::vector<LayerRequirement>{LayerRequirement{.weight = 1.0f},
-                                                LayerRequirement{.weight = 1.0f}};
+    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(mExpected90Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
+    EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
 
     lr1.vote = LayerVoteType::Min;
     lr2.vote = LayerVoteType::Heuristic;
-    lr2.desiredRefreshRate = Fps(24.0f);
-    EXPECT_EQ(mExpected60Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
+    lr2.desiredRefreshRate = 24_Hz;
+    EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers));
 
     lr1.vote = LayerVoteType::Min;
     lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
-    lr2.desiredRefreshRate = Fps(24.0f);
-    EXPECT_EQ(mExpected60Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
+    lr2.desiredRefreshRate = 24_Hz;
+    EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers));
 
     lr1.vote = LayerVoteType::Max;
     lr2.vote = LayerVoteType::Heuristic;
-    lr2.desiredRefreshRate = Fps(60.0f);
-    EXPECT_EQ(mExpected90Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
+    lr2.desiredRefreshRate = 60_Hz;
+    EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
 
     lr1.vote = LayerVoteType::Max;
     lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
-    lr2.desiredRefreshRate = Fps(60.0f);
-    EXPECT_EQ(mExpected90Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
+    lr2.desiredRefreshRate = 60_Hz;
+    EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
 
     lr1.vote = LayerVoteType::Heuristic;
-    lr1.desiredRefreshRate = Fps(15.0f);
+    lr1.desiredRefreshRate = 15_Hz;
     lr2.vote = LayerVoteType::Heuristic;
-    lr2.desiredRefreshRate = Fps(45.0f);
-    EXPECT_EQ(mExpected90Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
+    lr2.desiredRefreshRate = 45_Hz;
+    EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
 
     lr1.vote = LayerVoteType::Heuristic;
-    lr1.desiredRefreshRate = Fps(30.0f);
+    lr1.desiredRefreshRate = 30_Hz;
     lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
-    lr2.desiredRefreshRate = Fps(45.0f);
-    EXPECT_EQ(mExpected90Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
+    lr2.desiredRefreshRate = 45_Hz;
+    EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
 }
 
 TEST_F(RefreshRateConfigsTest, getBestRefreshRate_24FpsVideo) {
-    auto refreshRateConfigs =
-            std::make_unique<RefreshRateConfigs>(m60_90Device,
-                                                 /*currentConfigId=*/HWC_CONFIG_ID_60);
+    TestableRefreshRateConfigs configs(kModes_60_90, kModeId60);
 
-    auto layers = std::vector<LayerRequirement>{LayerRequirement{.weight = 1.0f}};
+    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(fps);
-        const auto& refreshRate =
-                refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false});
-        EXPECT_EQ(mExpected60Config, refreshRate) << fps << "Hz chooses " << refreshRate.getName();
+        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) {
-    RefreshRateConfigs::Config config = {.frameRateMultipleThreshold = 120};
-    auto refreshRateConfigs =
-            std::make_unique<RefreshRateConfigs>(m60_120Device,
-                                                 /*currentConfigId=*/HWC_CONFIG_ID_60, config);
+    TestableRefreshRateConfigs configs(kModes_60_120, kModeId60,
+                                       {.frameRateMultipleThreshold = 120});
 
-    auto layers = std::vector<LayerRequirement>{LayerRequirement{.weight = 1.0f}};
+    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(fps);
-        const auto& refreshRate =
-                refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false});
-        EXPECT_EQ(mExpected60Config, refreshRate) << fps << "Hz chooses " << refreshRate.getName();
+        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, twoDeviceConfigs_getBestRefreshRate_Explicit) {
-    auto refreshRateConfigs =
-            std::make_unique<RefreshRateConfigs>(m60_90Device,
-                                                 /*currentConfigId=*/HWC_CONFIG_ID_60);
+TEST_F(RefreshRateConfigsTest, twoModes_getBestRefreshRate_Explicit) {
+    TestableRefreshRateConfigs configs(kModes_60_90, kModeId60);
 
-    auto layers = std::vector<LayerRequirement>{LayerRequirement{.weight = 1.0f},
-                                                LayerRequirement{.weight = 1.0f}};
+    std::vector<LayerRequirement> layers = {{.weight = 1.f}, {.weight = 1.f}};
     auto& lr1 = layers[0];
     auto& lr2 = layers[1];
 
     lr1.vote = LayerVoteType::Heuristic;
-    lr1.desiredRefreshRate = Fps(60.0f);
+    lr1.desiredRefreshRate = 60_Hz;
     lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
-    lr2.desiredRefreshRate = Fps(90.0f);
-    EXPECT_EQ(mExpected90Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
+    lr2.desiredRefreshRate = 90_Hz;
+    EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
 
     lr1.vote = LayerVoteType::ExplicitDefault;
-    lr1.desiredRefreshRate = Fps(90.0f);
+    lr1.desiredRefreshRate = 90_Hz;
     lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
-    lr2.desiredRefreshRate = Fps(60.0f);
-    EXPECT_EQ(mExpected60Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
+    lr2.desiredRefreshRate = 60_Hz;
+    EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers));
 
     lr1.vote = LayerVoteType::Heuristic;
-    lr1.desiredRefreshRate = Fps(90.0f);
+    lr1.desiredRefreshRate = 90_Hz;
     lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
-    lr2.desiredRefreshRate = Fps(60.0f);
-    EXPECT_EQ(mExpected90Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
-}
-
-TEST_F(RefreshRateConfigsTest, testInPolicy) {
-    ASSERT_TRUE(mExpectedAlmost60Config.inPolicy(Fps(60.000004f), Fps(60.000004f)));
-    ASSERT_TRUE(mExpectedAlmost60Config.inPolicy(Fps(59.0f), Fps(60.1f)));
-    ASSERT_FALSE(mExpectedAlmost60Config.inPolicy(Fps(75.0f), Fps(90.0f)));
-    ASSERT_FALSE(mExpectedAlmost60Config.inPolicy(Fps(60.0011f), Fps(90.0f)));
-    ASSERT_FALSE(mExpectedAlmost60Config.inPolicy(Fps(50.0f), Fps(59.998f)));
+    lr2.desiredRefreshRate = 60_Hz;
+    EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
 }
 
 TEST_F(RefreshRateConfigsTest, getBestRefreshRate_75HzContent) {
-    auto refreshRateConfigs =
-            std::make_unique<RefreshRateConfigs>(m60_90Device,
-                                                 /*currentConfigId=*/HWC_CONFIG_ID_60);
+    TestableRefreshRateConfigs configs(kModes_60_90, kModeId60);
 
-    auto layers = std::vector<LayerRequirement>{LayerRequirement{.weight = 1.0f}};
+    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(fps);
-        const auto& refreshRate =
-                refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false});
-        EXPECT_EQ(mExpected90Config, refreshRate) << fps << "Hz chooses " << refreshRate.getName();
+        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) {
-    auto refreshRateConfigs =
-            std::make_unique<RefreshRateConfigs>(m60_90Device,
-                                                 /*currentConfigId=*/HWC_CONFIG_ID_60);
+    TestableRefreshRateConfigs configs(kModes_60_90, kModeId60);
 
-    auto layers = std::vector<LayerRequirement>{LayerRequirement{.weight = 1.0f},
-                                                LayerRequirement{.weight = 1.0f}};
+    std::vector<LayerRequirement> layers = {{.weight = 1.f}, {.weight = 1.f}};
     auto& lr1 = layers[0];
     auto& lr2 = layers[1];
 
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
-    lr1.desiredRefreshRate = Fps(60.0f);
+    lr1.desiredRefreshRate = 60_Hz;
     lr1.name = "60Hz ExplicitExactOrMultiple";
     lr2.vote = LayerVoteType::Heuristic;
-    lr2.desiredRefreshRate = Fps(90.0f);
+    lr2.desiredRefreshRate = 90_Hz;
     lr2.name = "90Hz Heuristic";
-    EXPECT_EQ(mExpected90Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
+    EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
 
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
-    lr1.desiredRefreshRate = Fps(60.0f);
+    lr1.desiredRefreshRate = 60_Hz;
     lr1.name = "60Hz ExplicitExactOrMultiple";
     lr2.vote = LayerVoteType::ExplicitDefault;
-    lr2.desiredRefreshRate = Fps(90.0f);
+    lr2.desiredRefreshRate = 90_Hz;
     lr2.name = "90Hz ExplicitDefault";
-    EXPECT_EQ(mExpected60Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
+    EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers));
 
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
-    lr1.desiredRefreshRate = Fps(60.0f);
+    lr1.desiredRefreshRate = 60_Hz;
     lr1.name = "60Hz ExplicitExactOrMultiple";
     lr2.vote = LayerVoteType::Max;
     lr2.name = "Max";
-    EXPECT_EQ(mExpected90Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
+    EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
 
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
-    lr1.desiredRefreshRate = Fps(30.0f);
+    lr1.desiredRefreshRate = 30_Hz;
     lr1.name = "30Hz ExplicitExactOrMultiple";
     lr2.vote = LayerVoteType::Heuristic;
-    lr2.desiredRefreshRate = Fps(90.0f);
+    lr2.desiredRefreshRate = 90_Hz;
     lr2.name = "90Hz Heuristic";
-    EXPECT_EQ(mExpected90Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
+    EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
 
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
-    lr1.desiredRefreshRate = Fps(30.0f);
+    lr1.desiredRefreshRate = 30_Hz;
     lr1.name = "30Hz ExplicitExactOrMultiple";
     lr2.vote = LayerVoteType::Max;
     lr2.name = "Max";
-    EXPECT_EQ(mExpected90Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
+    EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
 }
 
 TEST_F(RefreshRateConfigsTest, scrollWhileWatching60fps_60_90) {
-    auto refreshRateConfigs =
-            std::make_unique<RefreshRateConfigs>(m60_90Device,
-                                                 /*currentConfigId=*/HWC_CONFIG_ID_60);
+    TestableRefreshRateConfigs configs(kModes_60_90, kModeId60);
 
-    auto layers = std::vector<LayerRequirement>{LayerRequirement{.weight = 1.0f},
-                                                LayerRequirement{.weight = 1.0f}};
+    std::vector<LayerRequirement> layers = {{.weight = 1.f}, {.weight = 1.f}};
     auto& lr1 = layers[0];
     auto& lr2 = layers[1];
 
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
-    lr1.desiredRefreshRate = Fps(60.0f);
+    lr1.desiredRefreshRate = 60_Hz;
     lr1.name = "60Hz ExplicitExactOrMultiple";
     lr2.vote = LayerVoteType::NoVote;
     lr2.name = "NoVote";
-    EXPECT_EQ(mExpected60Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
+    EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers));
 
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
-    lr1.desiredRefreshRate = Fps(60.0f);
+    lr1.desiredRefreshRate = 60_Hz;
     lr1.name = "60Hz ExplicitExactOrMultiple";
     lr2.vote = LayerVoteType::NoVote;
     lr2.name = "NoVote";
-    EXPECT_EQ(mExpected90Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = true, .idle = false}));
+    EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers, {.touch = true}));
 
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
-    lr1.desiredRefreshRate = Fps(60.0f);
+    lr1.desiredRefreshRate = 60_Hz;
     lr1.name = "60Hz ExplicitExactOrMultiple";
     lr2.vote = LayerVoteType::Max;
     lr2.name = "Max";
-    EXPECT_EQ(mExpected90Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = true, .idle = false}));
+    EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers, {.touch = true}));
 
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
-    lr1.desiredRefreshRate = Fps(60.0f);
+    lr1.desiredRefreshRate = 60_Hz;
     lr1.name = "60Hz ExplicitExactOrMultiple";
     lr2.vote = LayerVoteType::Max;
     lr2.name = "Max";
-    EXPECT_EQ(mExpected90Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
+    EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
 
     // The other layer starts to provide buffers
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
-    lr1.desiredRefreshRate = Fps(60.0f);
+    lr1.desiredRefreshRate = 60_Hz;
     lr1.name = "60Hz ExplicitExactOrMultiple";
     lr2.vote = LayerVoteType::Heuristic;
-    lr2.desiredRefreshRate = Fps(90.0f);
+    lr2.desiredRefreshRate = 90_Hz;
     lr2.name = "90Hz Heuristic";
-    EXPECT_EQ(mExpected90Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
+    EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
 }
 
 TEST_F(RefreshRateConfigsTest, touchConsidered) {
-    RefreshRateConfigs::GlobalSignals consideredSignals;
-    auto refreshRateConfigs =
-            std::make_unique<RefreshRateConfigs>(m60_90Device,
-                                                 /*currentConfigId=*/HWC_CONFIG_ID_60);
+    RefreshRateConfigs configs(kModes_60_90, kModeId60);
 
-    refreshRateConfigs->getBestRefreshRate({}, {.touch = false, .idle = false}, &consideredSignals);
-    EXPECT_EQ(false, consideredSignals.touch);
+    auto [_, signals] = configs.getBestRefreshRate({}, {});
+    EXPECT_FALSE(signals.touch);
 
-    refreshRateConfigs->getBestRefreshRate({}, {.touch = true, .idle = false}, &consideredSignals);
-    EXPECT_EQ(true, consideredSignals.touch);
+    std::tie(std::ignore, signals) = configs.getBestRefreshRate({}, {.touch = true});
+    EXPECT_TRUE(signals.touch);
 
-    auto layers = std::vector<LayerRequirement>{LayerRequirement{.weight = 1.0f},
-                                                LayerRequirement{.weight = 1.0f}};
+    std::vector<LayerRequirement> layers = {{.weight = 1.f}, {.weight = 1.f}};
     auto& lr1 = layers[0];
     auto& lr2 = layers[1];
 
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
-    lr1.desiredRefreshRate = Fps(60.0f);
+    lr1.desiredRefreshRate = 60_Hz;
     lr1.name = "60Hz ExplicitExactOrMultiple";
     lr2.vote = LayerVoteType::Heuristic;
-    lr2.desiredRefreshRate = Fps(60.0f);
+    lr2.desiredRefreshRate = 60_Hz;
     lr2.name = "60Hz Heuristic";
-    refreshRateConfigs->getBestRefreshRate(layers, {.touch = true, .idle = false},
-                                           &consideredSignals);
-    EXPECT_EQ(true, consideredSignals.touch);
+    std::tie(std::ignore, signals) = configs.getBestRefreshRate(layers, {.touch = true});
+    EXPECT_TRUE(signals.touch);
 
     lr1.vote = LayerVoteType::ExplicitDefault;
-    lr1.desiredRefreshRate = Fps(60.0f);
+    lr1.desiredRefreshRate = 60_Hz;
     lr1.name = "60Hz ExplicitExactOrMultiple";
     lr2.vote = LayerVoteType::Heuristic;
-    lr2.desiredRefreshRate = Fps(60.0f);
+    lr2.desiredRefreshRate = 60_Hz;
     lr2.name = "60Hz Heuristic";
-    refreshRateConfigs->getBestRefreshRate(layers, {.touch = true, .idle = false},
-                                           &consideredSignals);
-    EXPECT_EQ(false, consideredSignals.touch);
+    std::tie(std::ignore, signals) = configs.getBestRefreshRate(layers, {.touch = true});
+    EXPECT_FALSE(signals.touch);
 
     lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
-    lr1.desiredRefreshRate = Fps(60.0f);
+    lr1.desiredRefreshRate = 60_Hz;
     lr1.name = "60Hz ExplicitExactOrMultiple";
     lr2.vote = LayerVoteType::Heuristic;
-    lr2.desiredRefreshRate = Fps(60.0f);
+    lr2.desiredRefreshRate = 60_Hz;
     lr2.name = "60Hz Heuristic";
-    refreshRateConfigs->getBestRefreshRate(layers, {.touch = true, .idle = false},
-                                           &consideredSignals);
-    EXPECT_EQ(true, consideredSignals.touch);
+    std::tie(std::ignore, signals) = configs.getBestRefreshRate(layers, {.touch = true});
+    EXPECT_TRUE(signals.touch);
 
     lr1.vote = LayerVoteType::ExplicitDefault;
-    lr1.desiredRefreshRate = Fps(60.0f);
+    lr1.desiredRefreshRate = 60_Hz;
     lr1.name = "60Hz ExplicitExactOrMultiple";
     lr2.vote = LayerVoteType::Heuristic;
-    lr2.desiredRefreshRate = Fps(60.0f);
+    lr2.desiredRefreshRate = 60_Hz;
     lr2.name = "60Hz Heuristic";
-    refreshRateConfigs->getBestRefreshRate(layers, {.touch = true, .idle = false},
-                                           &consideredSignals);
-    EXPECT_EQ(false, consideredSignals.touch);
+    std::tie(std::ignore, signals) = configs.getBestRefreshRate(layers, {.touch = true});
+    EXPECT_FALSE(signals.touch);
 }
 
 TEST_F(RefreshRateConfigsTest, getBestRefreshRate_ExplicitDefault) {
-    auto refreshRateConfigs =
-            std::make_unique<RefreshRateConfigs>(m60_90_72_120Device, /*currentConfigId=*/
-                                                 HWC_CONFIG_ID_60);
+    TestableRefreshRateConfigs configs(kModes_60_90_72_120, kModeId60);
 
-    auto layers = std::vector<LayerRequirement>{LayerRequirement{.weight = 1.0f}};
+    std::vector<LayerRequirement> layers = {{.weight = 1.f}};
     auto& lr = layers[0];
 
     // Prepare a table with the vote and the expected refresh rate
-    const std::vector<std::pair<float, float>> testCases = {
-            {130, 120}, {120, 120}, {119, 120}, {110, 120},
+    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, 90},  {90, 90},   {89, 90},
+            {100_Hz, 90_Hz},  {90_Hz, 90_Hz},   {89_Hz, 90_Hz},
 
-            {80, 72},   {73, 72},   {72, 72},   {71, 72},   {70, 72},
+            {80_Hz, 72_Hz},   {73_Hz, 72_Hz},   {72_Hz, 72_Hz},   {71_Hz, 72_Hz},   {70_Hz, 72_Hz},
 
-            {65, 60},   {60, 60},   {59, 60},   {58, 60},
+            {65_Hz, 60_Hz},   {60_Hz, 60_Hz},   {59_Hz, 60_Hz},   {58_Hz, 60_Hz},
 
-            {55, 90},   {50, 90},   {45, 90},
+            {55_Hz, 90_Hz},   {50_Hz, 90_Hz},   {45_Hz, 90_Hz},
 
-            {42, 120},  {40, 120},  {39, 120},
+            {42_Hz, 120_Hz},  {40_Hz, 120_Hz},  {39_Hz, 120_Hz},
 
-            {37, 72},   {36, 72},   {35, 72},
+            {37_Hz, 72_Hz},   {36_Hz, 72_Hz},   {35_Hz, 72_Hz},
 
-            {30, 60},
+            {30_Hz, 60_Hz},
     };
 
-    for (const auto& test : testCases) {
+    for (auto [desired, expected] : testCases) {
         lr.vote = LayerVoteType::ExplicitDefault;
-        lr.desiredRefreshRate = Fps(test.first);
+        lr.desiredRefreshRate = desired;
 
         std::stringstream ss;
-        ss << "ExplicitDefault " << test.first << " fps";
+        ss << "ExplicitDefault " << desired;
         lr.name = ss.str();
 
-        const auto& refreshRate =
-                refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false});
-        EXPECT_TRUE(refreshRate.getFps().equalsWithMargin(Fps(test.second)))
-                << "Expecting " << test.first << "fps => " << test.second << "Hz"
-                << " but it was " << refreshRate.getFps();
+        EXPECT_EQ(expected, configs.getBestRefreshRate(layers)->getFps());
     }
 }
 
 TEST_F(RefreshRateConfigsTest,
        getBestRefreshRate_ExplicitExactOrMultiple_WithFractionalRefreshRates) {
-    auto layers = std::vector<LayerRequirement>{LayerRequirement{.weight = 1.0f}};
+    std::vector<LayerRequirement> layers = {{.weight = 1.f}};
     auto& lr = layers[0];
 
     // Test that 23.976 will choose 24 if 23.976 is not supported
     {
-        android::DisplayModes modes = {mConfig24,     mConfig25, mConfig30,
-                                       mConfig30Frac, mConfig60, mConfig60Frac};
-        auto refreshRateConfigs =
-                std::make_unique<RefreshRateConfigs>(modes, /*currentConfigId=*/HWC_CONFIG_ID_60);
+        TestableRefreshRateConfigs configs(makeModes(kMode24, kMode25, kMode30, kMode30Frac,
+                                                     kMode60, kMode60Frac),
+                                           kModeId60);
 
         lr.vote = LayerVoteType::ExplicitExactOrMultiple;
-        lr.desiredRefreshRate = Fps(23.976f);
-        lr.name = "ExplicitExactOrMultiple 23.976 fps";
-        EXPECT_EQ(HWC_CONFIG_ID_24,
-                  refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false})
-                          .getModeId());
+        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
     {
-        android::DisplayModes modes = {mConfig24Frac, mConfig25, mConfig30,
-                                       mConfig30Frac, mConfig60, mConfig60Frac};
-        auto refreshRateConfigs =
-                std::make_unique<RefreshRateConfigs>(modes, /*currentConfigId=*/HWC_CONFIG_ID_60);
-        lr.desiredRefreshRate = Fps(24.f);
-        lr.name = "ExplicitExactOrMultiple 24 fps";
-        EXPECT_EQ(HWC_CONFIG_ID_24_FRAC,
-                  refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false})
-                          .getModeId());
+        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
     {
-        android::DisplayModes modes = {mConfig24, mConfig24Frac, mConfig25,
-                                       mConfig30, mConfig60,     mConfig60Frac};
-        auto refreshRateConfigs =
-                std::make_unique<RefreshRateConfigs>(modes, /*currentConfigId=*/HWC_CONFIG_ID_60);
-        lr.desiredRefreshRate = Fps(29.97f);
-        lr.name = "ExplicitExactOrMultiple 29.97f fps";
-        EXPECT_EQ(HWC_CONFIG_ID_60_FRAC,
-                  refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false})
-                          .getModeId());
+        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) {
-    auto layers = std::vector<LayerRequirement>{LayerRequirement{.weight = 1.0f}};
+    std::vector<LayerRequirement> layers = {{.weight = 1.f}};
     auto& lr = layers[0];
 
     // Test that voting for supported refresh rate will select this refresh rate
     {
-        auto refreshRateConfigs =
-                std::make_unique<RefreshRateConfigs>(m24_25_30_50_60WithFracDevice,
-                                                     /*currentConfigId=*/HWC_CONFIG_ID_60);
+        TestableRefreshRateConfigs configs(kModes_24_25_30_50_60_Frac, kModeId60);
 
-        for (auto desiredRefreshRate : {23.976f, 24.f, 25.f, 29.97f, 30.f, 50.f, 59.94f, 60.f}) {
+        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 = Fps(desiredRefreshRate);
+            lr.desiredRefreshRate = desired;
             std::stringstream ss;
-            ss << "ExplicitExact " << desiredRefreshRate << " fps";
+            ss << "ExplicitExact " << desired;
             lr.name = ss.str();
 
-            auto selecteRefreshRate =
-                    refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false});
-
-            EXPECT_TRUE(selecteRefreshRate.getFps().equalsWithMargin(lr.desiredRefreshRate))
-                    << "Expecting " << lr.desiredRefreshRate << " but it was "
-                    << selecteRefreshRate.getFps();
+            EXPECT_EQ(lr.desiredRefreshRate, configs.getBestRefreshRate(layers)->getFps());
         }
     }
 
     // Test that 23.976 will choose 24 if 23.976 is not supported
     {
-        android::DisplayModes modes = {mConfig24,     mConfig25, mConfig30,
-                                       mConfig30Frac, mConfig60, mConfig60Frac};
-        auto refreshRateConfigs =
-                std::make_unique<RefreshRateConfigs>(modes, /*currentConfigId=*/HWC_CONFIG_ID_60);
+        TestableRefreshRateConfigs configs(makeModes(kMode24, kMode25, kMode30, kMode30Frac,
+                                                     kMode60, kMode60Frac),
+                                           kModeId60);
+
         lr.vote = LayerVoteType::ExplicitExact;
-        lr.desiredRefreshRate = Fps(23.976f);
-        lr.name = "ExplicitExact 23.976 fps";
-        EXPECT_EQ(HWC_CONFIG_ID_24,
-                  refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false})
-                          .getModeId());
+        lr.desiredRefreshRate = 23.976_Hz;
+        lr.name = "ExplicitExact 23.976 Hz";
+        EXPECT_EQ(kModeId24, configs.getBestRefreshRate(layers)->getId());
     }
 
     // Test that 24 will choose 23.976 if 24 is not supported
     {
-        android::DisplayModes modes = {mConfig24Frac, mConfig25, mConfig30,
-                                       mConfig30Frac, mConfig60, mConfig60Frac};
-        auto refreshRateConfigs =
-                std::make_unique<RefreshRateConfigs>(modes, /*currentConfigId=*/HWC_CONFIG_ID_60);
-        lr.desiredRefreshRate = Fps(24.f);
-        lr.name = "ExplicitExact 24 fps";
-        EXPECT_EQ(HWC_CONFIG_ID_24_FRAC,
-                  refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false})
-                          .getModeId());
+        TestableRefreshRateConfigs configs(makeModes(kMode24Frac, kMode25, kMode30, kMode30Frac,
+                                                     kMode60, kMode60Frac),
+                                           kModeId60);
+
+        lr.desiredRefreshRate = 24_Hz;
+        lr.name = "ExplicitExact 24 Hz";
+        EXPECT_EQ(kModeId24Frac, configs.getBestRefreshRate(layers)->getId());
     }
 }
 
 TEST_F(RefreshRateConfigsTest,
        getBestRefreshRate_withDisplayManagerRequestingSingleRate_ignoresTouchFlag) {
-    auto refreshRateConfigs =
-            std::make_unique<RefreshRateConfigs>(m60_90Device,
-                                                 /*currentConfigId=*/HWC_CONFIG_ID_90);
+    RefreshRateConfigs configs(kModes_60_90, kModeId90);
 
-    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy(
-                      {HWC_CONFIG_ID_90, {Fps(90.f), Fps(90.f)}, {Fps(60.f), Fps(90.f)}}),
-              0);
+    EXPECT_GE(configs.setDisplayManagerPolicy({kModeId90, {90_Hz, 90_Hz}, {60_Hz, 90_Hz}}), 0);
 
-    auto layers = std::vector<LayerRequirement>{LayerRequirement{.weight = 1.0f}};
+    std::vector<LayerRequirement> layers = {{.weight = 1.f}};
     auto& lr = layers[0];
 
-    RefreshRateConfigs::GlobalSignals consideredSignals;
     lr.vote = LayerVoteType::ExplicitDefault;
-    lr.desiredRefreshRate = Fps(60.0f);
+    lr.desiredRefreshRate = 60_Hz;
     lr.name = "60Hz ExplicitDefault";
     lr.focused = true;
-    EXPECT_EQ(mExpected60Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = true, .idle = true},
-                                                     &consideredSignals));
-    EXPECT_EQ(false, consideredSignals.touch);
+
+    const auto [mode, signals] = configs.getBestRefreshRate(layers, {.touch = true, .idle = true});
+
+    EXPECT_EQ(mode, kMode60);
+    EXPECT_FALSE(signals.touch);
 }
 
 TEST_F(RefreshRateConfigsTest,
        getBestRefreshRate_withDisplayManagerRequestingSingleRate_ignoresIdleFlag) {
-    auto refreshRateConfigs =
-            std::make_unique<RefreshRateConfigs>(m60_90Device,
-                                                 /*currentConfigId=*/HWC_CONFIG_ID_60);
+    TestableRefreshRateConfigs configs(kModes_60_90, kModeId60);
 
-    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy(
-                      {HWC_CONFIG_ID_60, {Fps(60.f), Fps(60.f)}, {Fps(60.f), Fps(90.f)}}),
-              0);
+    EXPECT_GE(configs.setDisplayManagerPolicy({kModeId60, {60_Hz, 60_Hz}, {60_Hz, 90_Hz}}), 0);
 
-    auto layers = std::vector<LayerRequirement>{LayerRequirement{.weight = 1.0f}};
+    std::vector<LayerRequirement> layers = {{.weight = 1.f}};
     auto& lr = layers[0];
 
     lr.vote = LayerVoteType::ExplicitDefault;
-    lr.desiredRefreshRate = Fps(90.0f);
+    lr.desiredRefreshRate = 90_Hz;
     lr.name = "90Hz ExplicitDefault";
     lr.focused = true;
-    EXPECT_EQ(mExpected90Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = true}));
+    EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers, {.idle = true}));
 }
 
 TEST_F(RefreshRateConfigsTest,
        getBestRefreshRate_withDisplayManagerRequestingSingleRate_onlySwitchesRatesForExplicitFocusedLayers) {
-    auto refreshRateConfigs =
-            std::make_unique<RefreshRateConfigs>(m60_90Device,
-                                                 /*currentConfigId=*/HWC_CONFIG_ID_90);
+    TestableRefreshRateConfigs configs(kModes_60_90, kModeId90);
 
-    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy(
-                      {HWC_CONFIG_ID_90, {Fps(90.f), Fps(90.f)}, {Fps(60.f), Fps(90.f)}}),
-              0);
+    EXPECT_GE(configs.setDisplayManagerPolicy({kModeId90, {90_Hz, 90_Hz}, {60_Hz, 90_Hz}}), 0);
 
-    RefreshRateConfigs::GlobalSignals consideredSignals;
-    EXPECT_EQ(mExpected90Config,
-              refreshRateConfigs->getBestRefreshRate({}, {.touch = false, .idle = false},
-                                                     &consideredSignals));
-    EXPECT_EQ(false, consideredSignals.touch);
+    const auto [mode, signals] = configs.getBestRefreshRateAndSignals({}, {});
+    EXPECT_EQ(mode, kMode90);
+    EXPECT_FALSE(signals.touch);
 
-    auto layers = std::vector<LayerRequirement>{LayerRequirement{.weight = 1.0f}};
+    std::vector<LayerRequirement> layers = {{.weight = 1.f}};
     auto& lr = layers[0];
 
     lr.vote = LayerVoteType::ExplicitExactOrMultiple;
-    lr.desiredRefreshRate = Fps(60.0f);
+    lr.desiredRefreshRate = 60_Hz;
     lr.name = "60Hz ExplicitExactOrMultiple";
     lr.focused = false;
-    EXPECT_EQ(mExpected90Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
+    EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
 
     lr.focused = true;
-    EXPECT_EQ(mExpected90Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
+    EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
 
     lr.vote = LayerVoteType::ExplicitDefault;
-    lr.desiredRefreshRate = Fps(60.0f);
+    lr.desiredRefreshRate = 60_Hz;
     lr.name = "60Hz ExplicitDefault";
     lr.focused = false;
-    EXPECT_EQ(mExpected90Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
+    EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
 
     lr.focused = true;
-    EXPECT_EQ(mExpected60Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
+    EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers));
 
     lr.vote = LayerVoteType::Heuristic;
-    lr.desiredRefreshRate = Fps(60.0f);
+    lr.desiredRefreshRate = 60_Hz;
     lr.name = "60Hz Heuristic";
     lr.focused = false;
-    EXPECT_EQ(mExpected90Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
+    EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
 
     lr.focused = true;
-    EXPECT_EQ(mExpected90Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
+    EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
 
     lr.vote = LayerVoteType::Max;
-    lr.desiredRefreshRate = Fps(60.0f);
+    lr.desiredRefreshRate = 60_Hz;
     lr.name = "60Hz Max";
     lr.focused = false;
-    EXPECT_EQ(mExpected90Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
+    EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
 
     lr.focused = true;
-    EXPECT_EQ(mExpected90Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
+    EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
 
     lr.vote = LayerVoteType::Min;
-    lr.desiredRefreshRate = Fps(60.0f);
+    lr.desiredRefreshRate = 60_Hz;
     lr.name = "60Hz Min";
     lr.focused = false;
-    EXPECT_EQ(mExpected90Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
+    EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
 
     lr.focused = true;
-    EXPECT_EQ(mExpected90Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
+    EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
 }
 
 TEST_F(RefreshRateConfigsTest, groupSwitchingNotAllowed) {
-    auto refreshRateConfigs =
-            std::make_unique<RefreshRateConfigs>(m60_90DeviceWithDifferentGroups,
-                                                 /*currentConfigId=*/HWC_CONFIG_ID_60);
+    TestableRefreshRateConfigs configs(kModes_60_90_G1, kModeId60);
 
     // The default policy doesn't allow group switching. Verify that no
     // group switches are performed.
-    auto layers = std::vector<LayerRequirement>{LayerRequirement{.weight = 1.0f}};
+    std::vector<LayerRequirement> layers = {{.weight = 1.f}};
     auto& layer = layers[0];
     layer.vote = LayerVoteType::ExplicitDefault;
-    layer.desiredRefreshRate = Fps(90.0f);
+    layer.desiredRefreshRate = 90_Hz;
     layer.seamlessness = Seamlessness::SeamedAndSeamless;
     layer.name = "90Hz ExplicitDefault";
     layer.focused = true;
 
-    ASSERT_EQ(HWC_CONFIG_ID_60,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false})
-                      .getModeId());
+    EXPECT_EQ(kModeId60, configs.getBestRefreshRate(layers)->getId());
 }
 
 TEST_F(RefreshRateConfigsTest, groupSwitchingWithOneLayer) {
-    auto refreshRateConfigs =
-            std::make_unique<RefreshRateConfigs>(m60_90DeviceWithDifferentGroups,
-                                                 /*currentConfigId=*/HWC_CONFIG_ID_60);
-    RefreshRateConfigs::Policy policy;
-    policy.defaultMode = refreshRateConfigs->getCurrentPolicy().defaultMode;
-    policy.allowGroupSwitching = true;
-    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy(policy), 0);
+    TestableRefreshRateConfigs configs(kModes_60_90_G1, kModeId60);
 
-    auto layers = std::vector<LayerRequirement>{LayerRequirement{.weight = 1.0f}};
+    RefreshRateConfigs::Policy policy;
+    policy.defaultMode = configs.getCurrentPolicy().defaultMode;
+    policy.allowGroupSwitching = true;
+    EXPECT_GE(configs.setDisplayManagerPolicy(policy), 0);
+
+    std::vector<LayerRequirement> layers = {{.weight = 1.f}};
     auto& layer = layers[0];
     layer.vote = LayerVoteType::ExplicitDefault;
-    layer.desiredRefreshRate = Fps(90.0f);
+    layer.desiredRefreshRate = 90_Hz;
     layer.seamlessness = Seamlessness::SeamedAndSeamless;
     layer.name = "90Hz ExplicitDefault";
     layer.focused = true;
-    ASSERT_EQ(HWC_CONFIG_ID_90,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false})
-                      .getModeId());
+    EXPECT_EQ(kModeId90, configs.getBestRefreshRate(layers)->getId());
 }
 
 TEST_F(RefreshRateConfigsTest, groupSwitchingWithOneLayerOnlySeamless) {
-    auto refreshRateConfigs =
-            std::make_unique<RefreshRateConfigs>(m60_90DeviceWithDifferentGroups,
-                                                 /*currentConfigId=*/HWC_CONFIG_ID_60);
+    TestableRefreshRateConfigs configs(kModes_60_90_G1, kModeId60);
+
     RefreshRateConfigs::Policy policy;
-    policy.defaultMode = refreshRateConfigs->getCurrentPolicy().defaultMode;
+    policy.defaultMode = configs.getCurrentPolicy().defaultMode;
     policy.allowGroupSwitching = true;
-    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy(policy), 0);
+    EXPECT_GE(configs.setDisplayManagerPolicy(policy), 0);
 
     // Verify that we won't change the group if seamless switch is required.
-    auto layers = std::vector<LayerRequirement>{LayerRequirement{.weight = 1.0f}};
+    std::vector<LayerRequirement> layers = {{.weight = 1.f}};
     auto& layer = layers[0];
     layer.vote = LayerVoteType::ExplicitDefault;
-    layer.desiredRefreshRate = Fps(90.0f);
+    layer.desiredRefreshRate = 90_Hz;
     layer.seamlessness = Seamlessness::OnlySeamless;
     layer.name = "90Hz ExplicitDefault";
     layer.focused = true;
-    ASSERT_EQ(HWC_CONFIG_ID_60,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false})
-                      .getModeId());
+    EXPECT_EQ(kModeId60, configs.getBestRefreshRate(layers)->getId());
 }
 
 TEST_F(RefreshRateConfigsTest, groupSwitchingWithOneLayerOnlySeamlessDefaultFps) {
-    auto refreshRateConfigs =
-            std::make_unique<RefreshRateConfigs>(m60_90DeviceWithDifferentGroups,
-                                                 /*currentConfigId=*/HWC_CONFIG_ID_60);
-    RefreshRateConfigs::Policy policy;
-    policy.defaultMode = refreshRateConfigs->getCurrentPolicy().defaultMode;
-    policy.allowGroupSwitching = true;
-    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy(policy), 0);
+    TestableRefreshRateConfigs configs(kModes_60_90_G1, kModeId60);
 
-    refreshRateConfigs->setCurrentModeId(HWC_CONFIG_ID_90);
+    RefreshRateConfigs::Policy policy;
+    policy.defaultMode = configs.getCurrentPolicy().defaultMode;
+    policy.allowGroupSwitching = true;
+    EXPECT_GE(configs.setDisplayManagerPolicy(policy), 0);
+
+    configs.setActiveModeId(kModeId90);
 
     // Verify that we won't do a seamless switch if we request the same mode as the default
-    auto layers = std::vector<LayerRequirement>{LayerRequirement{.weight = 1.0f}};
+    std::vector<LayerRequirement> layers = {{.weight = 1.f}};
     auto& layer = layers[0];
     layer.vote = LayerVoteType::ExplicitDefault;
-    layer.desiredRefreshRate = Fps(60.0f);
+    layer.desiredRefreshRate = 60_Hz;
     layer.seamlessness = Seamlessness::OnlySeamless;
     layer.name = "60Hz ExplicitDefault";
     layer.focused = true;
-    ASSERT_EQ(HWC_CONFIG_ID_90,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false})
-                      .getModeId());
+    EXPECT_EQ(kModeId90, configs.getBestRefreshRate(layers)->getId());
 }
 
 TEST_F(RefreshRateConfigsTest, groupSwitchingWithOneLayerDefaultSeamlessness) {
-    auto refreshRateConfigs =
-            std::make_unique<RefreshRateConfigs>(m60_90DeviceWithDifferentGroups,
-                                                 /*currentConfigId=*/HWC_CONFIG_ID_60);
-    RefreshRateConfigs::Policy policy;
-    policy.defaultMode = refreshRateConfigs->getCurrentPolicy().defaultMode;
-    policy.allowGroupSwitching = true;
-    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy(policy), 0);
+    TestableRefreshRateConfigs configs(kModes_60_90_G1, kModeId60);
 
-    refreshRateConfigs->setCurrentModeId(HWC_CONFIG_ID_90);
+    RefreshRateConfigs::Policy policy;
+    policy.defaultMode = configs.getCurrentPolicy().defaultMode;
+    policy.allowGroupSwitching = true;
+    EXPECT_GE(configs.setDisplayManagerPolicy(policy), 0);
+
+    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.
 
-    auto layers = std::vector<LayerRequirement>{LayerRequirement{.weight = 1.0f}};
+    std::vector<LayerRequirement> layers = {{.weight = 1.f}};
     auto& layer = layers[0];
     layer.vote = LayerVoteType::ExplicitDefault;
-    layer.desiredRefreshRate = Fps(60.0f);
+    layer.desiredRefreshRate = 60_Hz;
     layer.seamlessness = Seamlessness::Default;
     layer.name = "60Hz ExplicitDefault";
     layer.focused = true;
 
-    ASSERT_EQ(HWC_CONFIG_ID_60,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false})
-                      .getModeId());
+    EXPECT_EQ(kModeId60, configs.getBestRefreshRate(layers)->getId());
 }
 
 TEST_F(RefreshRateConfigsTest, groupSwitchingWithTwoLayersOnlySeamlessAndSeamed) {
-    auto refreshRateConfigs =
-            std::make_unique<RefreshRateConfigs>(m60_90DeviceWithDifferentGroups,
-                                                 /*currentConfigId=*/HWC_CONFIG_ID_60);
-    RefreshRateConfigs::Policy policy;
-    policy.defaultMode = refreshRateConfigs->getCurrentPolicy().defaultMode;
-    policy.allowGroupSwitching = true;
-    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy(policy), 0);
+    TestableRefreshRateConfigs configs(kModes_60_90_G1, kModeId60);
 
-    refreshRateConfigs->setCurrentModeId(HWC_CONFIG_ID_90);
+    RefreshRateConfigs::Policy policy;
+    policy.defaultMode = configs.getCurrentPolicy().defaultMode;
+    policy.allowGroupSwitching = true;
+    EXPECT_GE(configs.setDisplayManagerPolicy(policy), 0);
+
+    configs.setActiveModeId(kModeId90);
 
     // If there's a layer with seamlessness=SeamedAndSeamless, another layer with
     // seamlessness=OnlySeamless can't change the mode group.
-    auto layers = std::vector<LayerRequirement>{LayerRequirement{.weight = 1.0f}};
+    std::vector<LayerRequirement> layers = {{.weight = 1.f}};
     layers[0].vote = LayerVoteType::ExplicitDefault;
-    layers[0].desiredRefreshRate = Fps(60.0f);
+    layers[0].desiredRefreshRate = 60_Hz;
     layers[0].seamlessness = Seamlessness::OnlySeamless;
     layers[0].name = "60Hz ExplicitDefault";
     layers[0].focused = true;
@@ -1609,25 +1308,22 @@
     layers.push_back(LayerRequirement{.weight = 0.5f});
     layers[1].vote = LayerVoteType::ExplicitDefault;
     layers[1].seamlessness = Seamlessness::SeamedAndSeamless;
-    layers[1].desiredRefreshRate = Fps(90.0f);
+    layers[1].desiredRefreshRate = 90_Hz;
     layers[1].name = "90Hz ExplicitDefault";
     layers[1].focused = false;
 
-    ASSERT_EQ(HWC_CONFIG_ID_90,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false})
-                      .getModeId());
+    EXPECT_EQ(kModeId90, configs.getBestRefreshRate(layers)->getId());
 }
 
 TEST_F(RefreshRateConfigsTest, groupSwitchingWithTwoLayersDefaultFocusedAndSeamed) {
-    auto refreshRateConfigs =
-            std::make_unique<RefreshRateConfigs>(m60_90DeviceWithDifferentGroups,
-                                                 /*currentConfigId=*/HWC_CONFIG_ID_60);
-    RefreshRateConfigs::Policy policy;
-    policy.defaultMode = refreshRateConfigs->getCurrentPolicy().defaultMode;
-    policy.allowGroupSwitching = true;
-    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy(policy), 0);
+    TestableRefreshRateConfigs configs(kModes_60_90_G1, kModeId60);
 
-    refreshRateConfigs->setCurrentModeId(HWC_CONFIG_ID_90);
+    RefreshRateConfigs::Policy policy;
+    policy.defaultMode = configs.getCurrentPolicy().defaultMode;
+    policy.allowGroupSwitching = true;
+    EXPECT_GE(configs.setDisplayManagerPolicy(policy), 0);
+
+    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
@@ -1635,571 +1331,444 @@
     // 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.
-    auto layers = std::vector<LayerRequirement>{LayerRequirement{.weight = 1.0f}};
+    std::vector<LayerRequirement> layers = {{.weight = 1.f}};
     layers[0].seamlessness = Seamlessness::Default;
-    layers[0].desiredRefreshRate = Fps(60.0f);
+    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 = Fps(90.0f);
+    layers[1].desiredRefreshRate = 90_Hz;
     layers[1].focused = true;
     layers[1].vote = LayerVoteType::ExplicitDefault;
     layers[1].name = "90Hz ExplicitDefault";
 
-    ASSERT_EQ(HWC_CONFIG_ID_90,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false})
-                      .getModeId());
+    EXPECT_EQ(kModeId90, configs.getBestRefreshRate(layers)->getId());
 }
 
 TEST_F(RefreshRateConfigsTest, groupSwitchingWithTwoLayersDefaultNotFocusedAndSeamed) {
-    auto refreshRateConfigs =
-            std::make_unique<RefreshRateConfigs>(m60_90DeviceWithDifferentGroups,
-                                                 /*currentConfigId=*/HWC_CONFIG_ID_60);
-    RefreshRateConfigs::Policy policy;
-    policy.defaultMode = refreshRateConfigs->getCurrentPolicy().defaultMode;
-    policy.allowGroupSwitching = true;
-    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy(policy), 0);
+    TestableRefreshRateConfigs configs(kModes_60_90_G1, kModeId60);
 
-    refreshRateConfigs->setCurrentModeId(HWC_CONFIG_ID_90);
+    RefreshRateConfigs::Policy policy;
+    policy.defaultMode = configs.getCurrentPolicy().defaultMode;
+    policy.allowGroupSwitching = true;
+    EXPECT_GE(configs.setDisplayManagerPolicy(policy), 0);
+
+    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.
-    auto layers = std::vector<LayerRequirement>{LayerRequirement{.weight = 1.0f}};
+    std::vector<LayerRequirement> layers = {{.weight = 1.f}};
     layers[0].seamlessness = Seamlessness::Default;
-    layers[0].desiredRefreshRate = Fps(60.0f);
+    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 = Fps(90.0f);
+    layers[1].desiredRefreshRate = 90_Hz;
     layers[1].focused = false;
     layers[1].vote = LayerVoteType::ExplicitDefault;
     layers[1].name = "90Hz ExplicitDefault";
 
-    ASSERT_EQ(HWC_CONFIG_ID_60,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false})
-                      .getModeId());
+    EXPECT_EQ(kModeId60, configs.getBestRefreshRate(layers)->getId());
 }
 
 TEST_F(RefreshRateConfigsTest, nonSeamlessVotePrefersSeamlessSwitches) {
-    auto refreshRateConfigs =
-            std::make_unique<RefreshRateConfigs>(m30_60Device,
-                                                 /*currentConfigId=*/HWC_CONFIG_ID_60);
+    TestableRefreshRateConfigs configs(kModes_30_60, kModeId60);
 
     // Allow group switching.
     RefreshRateConfigs::Policy policy;
-    policy.defaultMode = refreshRateConfigs->getCurrentPolicy().defaultMode;
+    policy.defaultMode = configs.getCurrentPolicy().defaultMode;
     policy.allowGroupSwitching = true;
-    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy(policy), 0);
+    EXPECT_GE(configs.setDisplayManagerPolicy(policy), 0);
 
-    auto layers = std::vector<LayerRequirement>{LayerRequirement{.weight = 1.0f}};
+    std::vector<LayerRequirement> layers = {{.weight = 1.f}};
     auto& layer = layers[0];
     layer.vote = LayerVoteType::ExplicitExactOrMultiple;
-    layer.desiredRefreshRate = Fps(60.0f);
+    layer.desiredRefreshRate = 60_Hz;
     layer.seamlessness = Seamlessness::SeamedAndSeamless;
     layer.name = "60Hz ExplicitExactOrMultiple";
     layer.focused = true;
 
-    ASSERT_EQ(HWC_CONFIG_ID_60,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false})
-                      .getModeId());
+    EXPECT_EQ(kModeId60, configs.getBestRefreshRate(layers)->getId());
 
-    refreshRateConfigs->setCurrentModeId(HWC_CONFIG_ID_120);
-    ASSERT_EQ(HWC_CONFIG_ID_120,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false})
-                      .getModeId());
+    configs.setActiveModeId(kModeId120);
+    EXPECT_EQ(kModeId120, configs.getBestRefreshRate(layers)->getId());
 }
 
 TEST_F(RefreshRateConfigsTest, nonSeamlessExactAndSeamlessMultipleLayers) {
-    auto refreshRateConfigs =
-            std::make_unique<RefreshRateConfigs>(m25_30_50_60Device,
-                                                 /*currentConfigId=*/HWC_CONFIG_ID_60);
+    TestableRefreshRateConfigs configs(kModes_25_30_50_60, kModeId60);
 
     // Allow group switching.
     RefreshRateConfigs::Policy policy;
-    policy.defaultMode = refreshRateConfigs->getCurrentPolicy().defaultMode;
+    policy.defaultMode = configs.getCurrentPolicy().defaultMode;
     policy.allowGroupSwitching = true;
-    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy(policy), 0);
+    EXPECT_GE(configs.setDisplayManagerPolicy(policy), 0);
 
-    auto layers = std::vector<
-            LayerRequirement>{LayerRequirement{.name = "60Hz ExplicitDefault",
-                                               .vote = LayerVoteType::ExplicitDefault,
-                                               .desiredRefreshRate = Fps(60.0f),
-                                               .seamlessness = Seamlessness::SeamedAndSeamless,
-                                               .weight = 0.5f,
-                                               .focused = false},
-                              LayerRequirement{.name = "25Hz ExplicitExactOrMultiple",
-                                               .vote = LayerVoteType::ExplicitExactOrMultiple,
-                                               .desiredRefreshRate = Fps(25.0f),
-                                               .seamlessness = Seamlessness::OnlySeamless,
-                                               .weight = 1.0f,
-                                               .focused = true}};
+    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}};
 
-    ASSERT_EQ(HWC_CONFIG_ID_50,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false})
-                      .getModeId());
+    EXPECT_EQ(kModeId50, configs.getBestRefreshRate(layers)->getId());
 
     auto& seamedLayer = layers[0];
-    seamedLayer.name = "30Hz ExplicitDefault", seamedLayer.desiredRefreshRate = Fps(30.0f);
-    refreshRateConfigs->setCurrentModeId(HWC_CONFIG_ID_30);
+    seamedLayer.desiredRefreshRate = 30_Hz;
+    seamedLayer.name = "30Hz ExplicitDefault";
+    configs.setActiveModeId(kModeId30);
 
-    ASSERT_EQ(HWC_CONFIG_ID_25,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false})
-                      .getModeId());
+    EXPECT_EQ(kModeId25, configs.getBestRefreshRate(layers)->getId());
 }
 
 TEST_F(RefreshRateConfigsTest, minLayersDontTrigerSeamedSwitch) {
-    auto refreshRateConfigs =
-            std::make_unique<RefreshRateConfigs>(m60_90DeviceWithDifferentGroups,
-                                                 /*currentConfigId=*/HWC_CONFIG_ID_90);
+    TestableRefreshRateConfigs configs(kModes_60_90_G1, kModeId90);
 
     // Allow group switching.
     RefreshRateConfigs::Policy policy;
-    policy.defaultMode = refreshRateConfigs->getCurrentPolicy().defaultMode;
+    policy.defaultMode = configs.getCurrentPolicy().defaultMode;
     policy.allowGroupSwitching = true;
-    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy(policy), 0);
+    EXPECT_GE(configs.setDisplayManagerPolicy(policy), 0);
 
-    auto layers = std::vector<LayerRequirement>{LayerRequirement{.name = "Min",
-                                                                 .vote = LayerVoteType::Min,
-                                                                 .weight = 1.f,
-                                                                 .focused = true}};
+    std::vector<LayerRequirement> layers = {
+            {.name = "Min", .vote = LayerVoteType::Min, .weight = 1.f, .focused = true}};
 
-    ASSERT_EQ(HWC_CONFIG_ID_90,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false})
-                      .getModeId());
+    EXPECT_EQ(kModeId90, configs.getBestRefreshRate(layers)->getId());
 }
 
 TEST_F(RefreshRateConfigsTest, primaryVsAppRequestPolicy) {
-    auto refreshRateConfigs =
-            std::make_unique<RefreshRateConfigs>(m30_60_90Device,
-                                                 /*currentConfigId=*/HWC_CONFIG_ID_60);
+    TestableRefreshRateConfigs configs(kModes_30_60_90, kModeId60);
 
-    auto layers = std::vector<LayerRequirement>{LayerRequirement{.weight = 1.0f}};
+    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, bool touchActive = false,
-                            bool focused = true) -> DisplayModeId {
+    auto getFrameRate = [&](LayerVoteType voteType, Fps fps, Args args = {}) -> DisplayModeId {
         layers[0].vote = voteType;
         layers[0].desiredRefreshRate = fps;
-        layers[0].focused = focused;
-        return refreshRateConfigs->getBestRefreshRate(layers, {.touch = touchActive, .idle = false})
-                .getModeId();
+        layers[0].focused = args.focused;
+        return configs.getBestRefreshRate(layers, {.touch = args.touch})->getId();
     };
 
-    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy(
-                      {HWC_CONFIG_ID_60, {Fps(30.f), Fps(60.f)}, {Fps(30.f), Fps(90.f)}}),
-              0);
-    EXPECT_EQ(HWC_CONFIG_ID_60,
-              refreshRateConfigs->getBestRefreshRate({}, {.touch = false, .idle = false})
-                      .getModeId());
-    EXPECT_EQ(HWC_CONFIG_ID_60, getFrameRate(LayerVoteType::NoVote, Fps(90.f)));
-    EXPECT_EQ(HWC_CONFIG_ID_30, getFrameRate(LayerVoteType::Min, Fps(90.f)));
-    EXPECT_EQ(HWC_CONFIG_ID_60, getFrameRate(LayerVoteType::Max, Fps(90.f)));
-    EXPECT_EQ(HWC_CONFIG_ID_60, getFrameRate(LayerVoteType::Heuristic, Fps(90.f)));
-    EXPECT_EQ(HWC_CONFIG_ID_90, getFrameRate(LayerVoteType::ExplicitDefault, Fps(90.f)));
-    EXPECT_EQ(HWC_CONFIG_ID_60, getFrameRate(LayerVoteType::ExplicitExactOrMultiple, Fps(90.f)));
+    EXPECT_GE(configs.setDisplayManagerPolicy({kModeId60, {30_Hz, 60_Hz}, {30_Hz, 90_Hz}}), 0);
 
-    // Layers not focused are not allowed to override primary config
-    EXPECT_EQ(HWC_CONFIG_ID_60,
-              getFrameRate(LayerVoteType::ExplicitDefault, Fps(90.f), /*touch=*/false,
-                           /*focused=*/false));
-    EXPECT_EQ(HWC_CONFIG_ID_60,
-              getFrameRate(LayerVoteType::ExplicitExactOrMultiple, Fps(90.f), /*touch=*/false,
-                           /*focused=*/false));
+    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(HWC_CONFIG_ID_60, getFrameRate(LayerVoteType::Max, Fps(90.f), /*touch=*/true));
+    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(HWC_CONFIG_ID_90,
-              getFrameRate(LayerVoteType::ExplicitDefault, Fps(90.f), /*touch=*/true));
-    EXPECT_EQ(HWC_CONFIG_ID_60,
-              getFrameRate(LayerVoteType::ExplicitExactOrMultiple, Fps(90.f), /*touch=*/true));
+    EXPECT_EQ(kModeId90, getFrameRate(LayerVoteType::ExplicitDefault, 90_Hz, {.touch = true}));
+    EXPECT_EQ(kModeId60,
+              getFrameRate(LayerVoteType::ExplicitExactOrMultiple, 90_Hz, {.touch = true}));
 
-    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy(
-                      {HWC_CONFIG_ID_60, {Fps(60.f), Fps(60.f)}, {Fps(60.f), Fps(60.f)}}),
-              0);
-    EXPECT_EQ(HWC_CONFIG_ID_60, getFrameRate(LayerVoteType::NoVote, Fps(90.f)));
-    EXPECT_EQ(HWC_CONFIG_ID_60, getFrameRate(LayerVoteType::Min, Fps(90.f)));
-    EXPECT_EQ(HWC_CONFIG_ID_60, getFrameRate(LayerVoteType::Max, Fps(90.f)));
-    EXPECT_EQ(HWC_CONFIG_ID_60, getFrameRate(LayerVoteType::Heuristic, Fps(90.f)));
-    EXPECT_EQ(HWC_CONFIG_ID_60, getFrameRate(LayerVoteType::ExplicitDefault, Fps(90.f)));
-    EXPECT_EQ(HWC_CONFIG_ID_60, getFrameRate(LayerVoteType::ExplicitExactOrMultiple, Fps(90.f)));
+    EXPECT_GE(configs.setDisplayManagerPolicy({kModeId60, {60_Hz, 60_Hz}, {60_Hz, 60_Hz}}), 0);
+
+    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) {
-    auto refreshRateConfigs =
-            std::make_unique<RefreshRateConfigs>(m60_90Device,
-                                                 /*currentConfigId=*/HWC_CONFIG_ID_60);
+    TestableRefreshRateConfigs configs(kModes_60_90, kModeId60);
 
-    auto layers = std::vector<LayerRequirement>{LayerRequirement{.weight = 1.0f}};
+    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 = Fps(90.f);
-        RefreshRateConfigs::GlobalSignals consideredSignals;
-        const auto configId =
-                refreshRateConfigs
-                        ->getBestRefreshRate(layers, {.touch = touchActive, .idle = true},
-                                             &consideredSignals)
-                        .getModeId();
-        // Refresh rate will be chosen by either touch state or idle state
-        EXPECT_EQ(!touchActive, consideredSignals.idle);
-        return configId;
+        layers[0].desiredRefreshRate = 90_Hz;
+
+        const auto [refreshRate, signals] =
+                configs.getBestRefreshRateAndSignals(layers, {.touch = touchActive, .idle = true});
+
+        // Refresh rate will be chosen by either touch state or idle state.
+        EXPECT_EQ(!touchActive, signals.idle);
+        return refreshRate->getId();
     };
 
-    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy(
-                      {HWC_CONFIG_ID_60, {Fps(60.f), Fps(90.f)}, {Fps(60.f), Fps(90.f)}}),
-              0);
+    EXPECT_GE(configs.setDisplayManagerPolicy({kModeId60, {60_Hz, 90_Hz}, {60_Hz, 90_Hz}}), 0);
 
     // Idle should be lower priority than touch boost.
-    EXPECT_EQ(HWC_CONFIG_ID_90, getIdleFrameRate(LayerVoteType::NoVote, /*touchActive=*/true));
-    EXPECT_EQ(HWC_CONFIG_ID_90, getIdleFrameRate(LayerVoteType::Min, /*touchActive=*/true));
-    EXPECT_EQ(HWC_CONFIG_ID_90, getIdleFrameRate(LayerVoteType::Max, /*touchActive=*/true));
-    EXPECT_EQ(HWC_CONFIG_ID_90, getIdleFrameRate(LayerVoteType::Heuristic, /*touchActive=*/true));
-    EXPECT_EQ(HWC_CONFIG_ID_90,
-              getIdleFrameRate(LayerVoteType::ExplicitDefault, /*touchActive=*/true));
-    EXPECT_EQ(HWC_CONFIG_ID_90,
-              getIdleFrameRate(LayerVoteType::ExplicitExactOrMultiple, /*touchActive=*/true));
+    {
+        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(HWC_CONFIG_ID_90,
-              refreshRateConfigs->getBestRefreshRate({}, {.touch = true, .idle = true})
-                      .getModeId());
+    EXPECT_EQ(kModeId90, configs.getBestRefreshRate({}, {.touch = true, .idle = true})->getId());
 
     // Idle should be higher precedence than other layer frame rate considerations.
-    refreshRateConfigs->setCurrentModeId(HWC_CONFIG_ID_90);
-    EXPECT_EQ(HWC_CONFIG_ID_60, getIdleFrameRate(LayerVoteType::NoVote, /*touchActive=*/false));
-    EXPECT_EQ(HWC_CONFIG_ID_60, getIdleFrameRate(LayerVoteType::Min, /*touchActive=*/false));
-    EXPECT_EQ(HWC_CONFIG_ID_60, getIdleFrameRate(LayerVoteType::Max, /*touchActive=*/false));
-    EXPECT_EQ(HWC_CONFIG_ID_60, getIdleFrameRate(LayerVoteType::Heuristic, /*touchActive=*/false));
-    EXPECT_EQ(HWC_CONFIG_ID_60,
-              getIdleFrameRate(LayerVoteType::ExplicitDefault, /*touchActive=*/false));
-    EXPECT_EQ(HWC_CONFIG_ID_60,
-              getIdleFrameRate(LayerVoteType::ExplicitExactOrMultiple, /*touchActive=*/false));
+    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(HWC_CONFIG_ID_60,
-              refreshRateConfigs->getBestRefreshRate({}, {.touch = false, .idle = true})
-                      .getModeId());
+    EXPECT_EQ(kModeId60, configs.getBestRefreshRate({}, {.idle = true})->getId());
 }
 
 TEST_F(RefreshRateConfigsTest, findClosestKnownFrameRate) {
-    auto refreshRateConfigs =
-            std::make_unique<RefreshRateConfigs>(m60_90Device,
-                                                 /*currentConfigId=*/HWC_CONFIG_ID_60);
+    TestableRefreshRateConfigs configs(kModes_60_90, kModeId60);
 
     for (float fps = 1.0f; fps <= 120.0f; fps += 0.1f) {
-        const auto knownFrameRate = findClosestKnownFrameRate(*refreshRateConfigs, Fps(fps));
-        Fps expectedFrameRate;
-        if (fps < 26.91f) {
-            expectedFrameRate = Fps(24.0f);
-        } else if (fps < 37.51f) {
-            expectedFrameRate = Fps(30.0f);
-        } else if (fps < 52.51f) {
-            expectedFrameRate = Fps(45.0f);
-        } else if (fps < 66.01f) {
-            expectedFrameRate = Fps(60.0f);
-        } else if (fps < 81.01f) {
-            expectedFrameRate = Fps(72.0f);
-        } else {
-            expectedFrameRate = Fps(90.0f);
-        }
-        EXPECT_TRUE(expectedFrameRate.equalsWithMargin(knownFrameRate))
-                << "findClosestKnownFrameRate(" << fps << ") = " << knownFrameRate;
+        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) {
-    auto refreshRateConfigs =
-            std::make_unique<RefreshRateConfigs>(m60_90Device,
-                                                 /*currentConfigId=*/HWC_CONFIG_ID_60);
+    TestableRefreshRateConfigs configs(kModes_60_90, kModeId60);
 
-    struct ExpectedRate {
-        Fps rate;
-        const RefreshRate& expected;
+    struct Expectation {
+        Fps fps;
+        DisplayModePtr mode;
     };
 
-    /* clang-format off */
-    std::vector<ExpectedRate> knownFrameRatesExpectations = {
-        {Fps(24.0f), mExpected60Config},
-        {Fps(30.0f), mExpected60Config},
-        {Fps(45.0f), mExpected90Config},
-        {Fps(60.0f), mExpected60Config},
-        {Fps(72.0f), mExpected90Config},
-        {Fps(90.0f), mExpected90Config},
+    const std::initializer_list<Expectation> knownFrameRatesExpectations = {
+            {24_Hz, kMode60}, {30_Hz, kMode60}, {45_Hz, kMode90},
+            {60_Hz, kMode60}, {72_Hz, kMode90}, {90_Hz, kMode90},
     };
-    /* clang-format on */
 
     // Make sure the test tests all the known frame rate
-    const auto knownFrameRateList = getKnownFrameRate(*refreshRateConfigs);
-    const auto equal =
-            std::equal(knownFrameRateList.begin(), knownFrameRateList.end(),
-                       knownFrameRatesExpectations.begin(),
-                       [](Fps a, const ExpectedRate& b) { return a.equalsWithMargin(b.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);
 
-    auto layers = std::vector<LayerRequirement>{LayerRequirement{.weight = 1.0f}};
+    std::vector<LayerRequirement> layers = {{.weight = 1.f}};
     auto& layer = layers[0];
     layer.vote = LayerVoteType::Heuristic;
-    for (const auto& expectedRate : knownFrameRatesExpectations) {
-        layer.desiredRefreshRate = expectedRate.rate;
-        const auto& refreshRate =
-                refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false});
-        EXPECT_EQ(expectedRate.expected, refreshRate);
+
+    for (const auto& [fps, mode] : knownFrameRatesExpectations) {
+        layer.desiredRefreshRate = fps;
+        EXPECT_EQ(mode, configs.getBestRefreshRate(layers));
     }
 }
 
 TEST_F(RefreshRateConfigsTest, getBestRefreshRate_ExplicitExact) {
-    auto refreshRateConfigs =
-            std::make_unique<RefreshRateConfigs>(m30_60_72_90_120Device,
-                                                 /*currentConfigId=*/HWC_CONFIG_ID_60);
+    TestableRefreshRateConfigs configs(kModes_30_60_72_90_120, kModeId60);
 
-    auto layers = std::vector<LayerRequirement>{LayerRequirement{.weight = 1.0f},
-                                                LayerRequirement{.weight = 0.5f}};
+    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 = Fps(60);
+    explicitExactOrMultipleLayer.desiredRefreshRate = 60_Hz;
 
     explicitExactLayer.vote = LayerVoteType::ExplicitExact;
     explicitExactLayer.name = "ExplicitExact";
-    explicitExactLayer.desiredRefreshRate = Fps(30);
+    explicitExactLayer.desiredRefreshRate = 30_Hz;
 
-    EXPECT_EQ(mExpected30Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
-    EXPECT_EQ(mExpected30Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = true, .idle = false}));
+    EXPECT_EQ(kMode30, configs.getBestRefreshRate(layers));
+    EXPECT_EQ(kMode30, configs.getBestRefreshRate(layers, {.touch = true}));
 
-    explicitExactOrMultipleLayer.desiredRefreshRate = Fps(120);
-    explicitExactLayer.desiredRefreshRate = Fps(60);
-    EXPECT_EQ(mExpected60Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
+    explicitExactOrMultipleLayer.desiredRefreshRate = 120_Hz;
+    explicitExactLayer.desiredRefreshRate = 60_Hz;
+    EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers));
 
-    explicitExactLayer.desiredRefreshRate = Fps(72);
-    EXPECT_EQ(mExpected72Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
+    explicitExactLayer.desiredRefreshRate = 72_Hz;
+    EXPECT_EQ(kMode72, configs.getBestRefreshRate(layers));
 
-    explicitExactLayer.desiredRefreshRate = Fps(90);
-    EXPECT_EQ(mExpected90Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
+    explicitExactLayer.desiredRefreshRate = 90_Hz;
+    EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
 
-    explicitExactLayer.desiredRefreshRate = Fps(120);
-    EXPECT_EQ(mExpected120Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
+    explicitExactLayer.desiredRefreshRate = 120_Hz;
+    EXPECT_EQ(kMode120, configs.getBestRefreshRate(layers));
 }
 
 TEST_F(RefreshRateConfigsTest, getBestRefreshRate_ExplicitExactEnableFrameRateOverride) {
-    RefreshRateConfigs::Config config = {.enableFrameRateOverride = true};
-    auto refreshRateConfigs =
-            std::make_unique<RefreshRateConfigs>(m30_60_72_90_120Device,
-                                                 /*currentConfigId=*/HWC_CONFIG_ID_60, config);
+    TestableRefreshRateConfigs configs(kModes_30_60_72_90_120, kModeId60,
+                                       {.enableFrameRateOverride = true});
 
-    auto layers = std::vector<LayerRequirement>{LayerRequirement{.weight = 1.0f},
-                                                LayerRequirement{.weight = 0.5f}};
+    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 = Fps(60);
+    explicitExactOrMultipleLayer.desiredRefreshRate = 60_Hz;
 
     explicitExactLayer.vote = LayerVoteType::ExplicitExact;
     explicitExactLayer.name = "ExplicitExact";
-    explicitExactLayer.desiredRefreshRate = Fps(30);
+    explicitExactLayer.desiredRefreshRate = 30_Hz;
 
-    EXPECT_EQ(mExpected60Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
-    EXPECT_EQ(mExpected120Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = true, .idle = false}));
+    EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers));
+    EXPECT_EQ(kMode120, configs.getBestRefreshRate(layers, {.touch = true}));
 
-    explicitExactOrMultipleLayer.desiredRefreshRate = Fps(120);
-    explicitExactLayer.desiredRefreshRate = Fps(60);
-    EXPECT_EQ(mExpected120Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
+    explicitExactOrMultipleLayer.desiredRefreshRate = 120_Hz;
+    explicitExactLayer.desiredRefreshRate = 60_Hz;
+    EXPECT_EQ(kMode120, configs.getBestRefreshRate(layers));
 
-    explicitExactLayer.desiredRefreshRate = Fps(72);
-    EXPECT_EQ(mExpected72Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
+    explicitExactLayer.desiredRefreshRate = 72_Hz;
+    EXPECT_EQ(kMode72, configs.getBestRefreshRate(layers));
 
-    explicitExactLayer.desiredRefreshRate = Fps(90);
-    EXPECT_EQ(mExpected90Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
+    explicitExactLayer.desiredRefreshRate = 90_Hz;
+    EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
 
-    explicitExactLayer.desiredRefreshRate = Fps(120);
-    EXPECT_EQ(mExpected120Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
+    explicitExactLayer.desiredRefreshRate = 120_Hz;
+    EXPECT_EQ(kMode120, configs.getBestRefreshRate(layers));
 }
 
-TEST_F(RefreshRateConfigsTest, getBestRefreshRate_ReadsCached) {
+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(kMode90, GlobalSignals{.touch = true});
 
-    auto refreshRateConfigs =
-            std::make_unique<RefreshRateConfigs>(m30_60_72_90_120Device,
-                                                 /*currentConfigId=*/HWC_CONFIG_ID_60);
+    configs.mutableGetBestRefreshRateCache() = {args, result};
 
-    setLastBestRefreshRateInvocation(*refreshRateConfigs,
-                                     GetBestRefreshRateInvocation{.layerRequirements = std::vector<
-                                                                          LayerRequirement>(),
-                                                                  .globalSignals = {.touch = true,
-                                                                                    .idle = true},
-                                                                  .outSignalsConsidered =
-                                                                          {.touch = true,
-                                                                           .idle = false},
-                                                                  .resultingBestRefreshRate =
-                                                                          createRefreshRate(
-                                                                                  mConfig90)});
-
-    EXPECT_EQ(createRefreshRate(mConfig90),
-              refreshRateConfigs->getBestRefreshRate(std::vector<LayerRequirement>(),
-                                                     {.touch = true, .idle = true}));
-
-    const GlobalSignals cachedSignalsConsidered{.touch = true, .idle = false};
-    setLastBestRefreshRateInvocation(*refreshRateConfigs,
-                                     GetBestRefreshRateInvocation{.layerRequirements = std::vector<
-                                                                          LayerRequirement>(),
-                                                                  .globalSignals = {.touch = true,
-                                                                                    .idle = true},
-                                                                  .outSignalsConsidered =
-                                                                          cachedSignalsConsidered,
-                                                                  .resultingBestRefreshRate =
-                                                                          createRefreshRate(
-                                                                                  mConfig30)});
-
-    GlobalSignals signalsConsidered;
-    EXPECT_EQ(createRefreshRate(mConfig30),
-              refreshRateConfigs->getBestRefreshRate(std::vector<LayerRequirement>(),
-                                                     {.touch = true, .idle = true},
-                                                     &signalsConsidered));
-
-    EXPECT_EQ(cachedSignalsConsidered, signalsConsidered);
+    EXPECT_EQ(result, configs.getBestRefreshRateAndSignals(args.first, args.second));
 }
 
 TEST_F(RefreshRateConfigsTest, getBestRefreshRate_WritesCache) {
-    using GlobalSignals = RefreshRateConfigs::GlobalSignals;
+    TestableRefreshRateConfigs configs(kModes_30_60_72_90_120, kModeId60);
 
-    auto refreshRateConfigs =
-            std::make_unique<RefreshRateConfigs>(m30_60_72_90_120Device,
-                                                 /*currentConfigId=*/HWC_CONFIG_ID_60);
-    ASSERT_FALSE(getLastBestRefreshRateInvocation(*refreshRateConfigs).has_value());
+    EXPECT_FALSE(configs.mutableGetBestRefreshRateCache());
 
-    GlobalSignals globalSignals{.touch = true, .idle = true};
-    auto layers = std::vector<LayerRequirement>{LayerRequirement{.weight = 1.0f},
-                                                LayerRequirement{.weight = 0.5f}};
-    const auto lastResult =
-            refreshRateConfigs->getBestRefreshRate(layers, globalSignals,
-                                                   /* outSignalsConsidered */ nullptr);
+    std::vector<LayerRequirement> layers = {{.weight = 1.f}, {.weight = 0.5f}};
+    RefreshRateConfigs::GlobalSignals globalSignals{.touch = true, .idle = true};
 
-    const auto lastInvocation = getLastBestRefreshRateInvocation(*refreshRateConfigs);
+    const auto result = configs.getBestRefreshRateAndSignals(layers, globalSignals);
 
-    ASSERT_TRUE(lastInvocation.has_value());
-    ASSERT_EQ(layers, lastInvocation->layerRequirements);
-    ASSERT_EQ(globalSignals, lastInvocation->globalSignals);
-    ASSERT_EQ(lastResult, lastInvocation->resultingBestRefreshRate);
+    const auto& cache = configs.mutableGetBestRefreshRateCache();
+    ASSERT_TRUE(cache);
 
-    // outSignalsConsidered needs to be populated even tho earlier we gave nullptr
-    // to getBestRefreshRate()
-    GlobalSignals detaultSignals;
-    ASSERT_FALSE(detaultSignals == lastInvocation->outSignalsConsidered);
+    EXPECT_EQ(cache->arguments, std::make_pair(layers, globalSignals));
+    EXPECT_EQ(cache->result, result);
 }
 
 TEST_F(RefreshRateConfigsTest, getBestRefreshRate_ExplicitExactTouchBoost) {
-    RefreshRateConfigs::Config config = {.enableFrameRateOverride = true};
-    auto refreshRateConfigs =
-            std::make_unique<RefreshRateConfigs>(m60_120Device,
-                                                 /*currentConfigId=*/HWC_CONFIG_ID_60, config);
+    TestableRefreshRateConfigs configs(kModes_60_120, kModeId60, {.enableFrameRateOverride = true});
 
-    auto layers = std::vector<LayerRequirement>{LayerRequirement{.weight = 1.0f},
-                                                LayerRequirement{.weight = 0.5f}};
+    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 = Fps(60);
+    explicitExactOrMultipleLayer.desiredRefreshRate = 60_Hz;
 
     explicitExactLayer.vote = LayerVoteType::ExplicitExact;
     explicitExactLayer.name = "ExplicitExact";
-    explicitExactLayer.desiredRefreshRate = Fps(30);
+    explicitExactLayer.desiredRefreshRate = 30_Hz;
 
-    EXPECT_EQ(mExpected60Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
-    EXPECT_EQ(mExpected120Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = true, .idle = false}));
+    EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers));
+    EXPECT_EQ(kMode120, configs.getBestRefreshRate(layers, {.touch = true}));
 
     explicitExactOrMultipleLayer.vote = LayerVoteType::NoVote;
 
-    EXPECT_EQ(mExpected60Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
-    EXPECT_EQ(mExpected60Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = true, .idle = false}));
+    EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers));
+    EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers, {.touch = true}));
 }
 
 TEST_F(RefreshRateConfigsTest, getBestRefreshRate_FractionalRefreshRates_ExactAndDefault) {
-    RefreshRateConfigs::Config config = {.enableFrameRateOverride = true};
-    auto refreshRateConfigs =
-            std::make_unique<RefreshRateConfigs>(m24_25_30_50_60WithFracDevice,
-                                                 /*currentConfigId=*/HWC_CONFIG_ID_60, config);
+    TestableRefreshRateConfigs configs(kModes_24_25_30_50_60_Frac, kModeId60,
+                                       {.enableFrameRateOverride = true});
 
-    auto layers = std::vector<LayerRequirement>{LayerRequirement{.weight = 0.5f},
-                                                LayerRequirement{.weight = 0.5f}};
+    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 = Fps(60);
+    explicitExactOrMultipleLayer.desiredRefreshRate = 60_Hz;
 
     explicitDefaultLayer.vote = LayerVoteType::ExplicitDefault;
     explicitDefaultLayer.name = "ExplicitDefault";
-    explicitDefaultLayer.desiredRefreshRate = Fps(59.94f);
+    explicitDefaultLayer.desiredRefreshRate = 59.94_Hz;
 
-    EXPECT_EQ(mExpected60Config,
-              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
+    EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers));
 }
 
 // b/190578904
-TEST_F(RefreshRateConfigsTest, getBestRefreshRate_deviceWithCloseRefreshRates) {
+TEST_F(RefreshRateConfigsTest, getBestRefreshRate_withCloseRefreshRates) {
     constexpr int kMinRefreshRate = 10;
     constexpr int kMaxRefreshRate = 240;
 
     DisplayModes displayModes;
     for (int fps = kMinRefreshRate; fps < kMaxRefreshRate; fps++) {
-        constexpr int32_t kGroup = 0;
-        const auto refreshRate = Fps(static_cast<float>(fps));
-        displayModes.push_back(
-                createDisplayMode(DisplayModeId(fps), kGroup, refreshRate.getPeriodNsecs()));
+        const DisplayModeId modeId(fps);
+        displayModes.try_emplace(modeId,
+                                 createDisplayMode(modeId,
+                                                   Fps::fromValue(static_cast<float>(fps))));
     }
 
-    const RefreshRateConfigs::GlobalSignals globalSignals = {.touch = false, .idle = false};
-    auto refreshRateConfigs =
-            std::make_unique<RefreshRateConfigs>(displayModes,
-                                                 /*currentConfigId=*/displayModes[0]->getId());
+    const TestableRefreshRateConfigs configs(std::move(displayModes),
+                                             DisplayModeId(kMinRefreshRate));
 
-    auto layers = std::vector<LayerRequirement>{LayerRequirement{.weight = 1.0f}};
+    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(),
-                  refreshRateConfigs->getBestRefreshRate(layers, globalSignals)
-                          .getFps()
-                          .getIntValue())
-                << "Failed for " << RefreshRateConfigs::layerVoteTypeString(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(static_cast<float>(fps));
+        const auto refreshRate = Fps::fromValue(static_cast<float>(fps));
         testRefreshRate(refreshRate, LayerVoteType::Heuristic);
         testRefreshRate(refreshRate, LayerVoteType::ExplicitDefault);
         testRefreshRate(refreshRate, LayerVoteType::ExplicitExactOrMultiple);
@@ -2209,308 +1778,257 @@
 
 // b/190578904
 TEST_F(RefreshRateConfigsTest, getBestRefreshRate_conflictingVotes) {
-    const DisplayModes displayModes = {
-            createDisplayMode(DisplayModeId(0), 0, Fps(43.0f).getPeriodNsecs()),
-            createDisplayMode(DisplayModeId(1), 0, Fps(53.0f).getPeriodNsecs()),
-            createDisplayMode(DisplayModeId(2), 0, Fps(55.0f).getPeriodNsecs()),
-            createDisplayMode(DisplayModeId(3), 0, Fps(60.0f).getPeriodNsecs()),
-    };
+    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};
-    auto refreshRateConfigs =
-            std::make_unique<RefreshRateConfigs>(displayModes,
-                                                 /*currentConfigId=*/displayModes[0]->getId());
+    const TestableRefreshRateConfigs configs(std::move(displayModes), kActiveModeId);
 
-    const auto layers = std::vector<LayerRequirement>{
-            LayerRequirement{
+    const std::vector<LayerRequirement> layers = {
+            {
                     .vote = LayerVoteType::ExplicitDefault,
-                    .desiredRefreshRate = Fps(43.0f),
+                    .desiredRefreshRate = 43_Hz,
                     .seamlessness = Seamlessness::SeamedAndSeamless,
                     .weight = 0.41f,
             },
-            LayerRequirement{
+            {
                     .vote = LayerVoteType::ExplicitExactOrMultiple,
-                    .desiredRefreshRate = Fps(53.0f),
+                    .desiredRefreshRate = 53_Hz,
                     .seamlessness = Seamlessness::SeamedAndSeamless,
                     .weight = 0.41f,
             },
     };
 
-    EXPECT_EQ(53,
-              refreshRateConfigs->getBestRefreshRate(layers, globalSignals).getFps().getIntValue());
+    EXPECT_EQ(53_Hz, configs.getBestRefreshRate(layers, globalSignals)->getFps());
 }
 
-TEST_F(RefreshRateConfigsTest, testComparisonOperator) {
-    EXPECT_TRUE(mExpected60Config < mExpected90Config);
-    EXPECT_FALSE(mExpected60Config < mExpected60Config);
-    EXPECT_FALSE(mExpected90Config < mExpected90Config);
+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 = scheduler::RefreshRateConfigs::KernelIdleTimerAction;
+    using KernelIdleTimerAction = RefreshRateConfigs::KernelIdleTimerAction;
 
-    auto refreshRateConfigs =
-            std::make_unique<RefreshRateConfigs>(m60_90Device,
-                                                 /*currentConfigId=*/HWC_CONFIG_ID_90);
+    RefreshRateConfigs configs(kModes_60_90, kModeId90);
+
     // SetPolicy(60, 90), current 90Hz => TurnOn.
-    EXPECT_EQ(KernelIdleTimerAction::TurnOn, refreshRateConfigs->getIdleTimerAction());
+    EXPECT_EQ(KernelIdleTimerAction::TurnOn, configs.getIdleTimerAction());
 
     // SetPolicy(60, 90), current 60Hz => TurnOn.
-    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy({HWC_CONFIG_ID_60, {Fps(60), Fps(90)}}),
-              0);
-    EXPECT_EQ(KernelIdleTimerAction::TurnOn, refreshRateConfigs->getIdleTimerAction());
+    EXPECT_GE(configs.setDisplayManagerPolicy({kModeId60, {60_Hz, 90_Hz}}), 0);
+    EXPECT_EQ(KernelIdleTimerAction::TurnOn, configs.getIdleTimerAction());
 
     // SetPolicy(60, 60), current 60Hz => TurnOff
-    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy({HWC_CONFIG_ID_60, {Fps(60), Fps(60)}}),
-              0);
-    EXPECT_EQ(KernelIdleTimerAction::TurnOff, refreshRateConfigs->getIdleTimerAction());
+    EXPECT_GE(configs.setDisplayManagerPolicy({kModeId60, {60_Hz, 60_Hz}}), 0);
+    EXPECT_EQ(KernelIdleTimerAction::TurnOff, configs.getIdleTimerAction());
 
     // SetPolicy(90, 90), current 90Hz => TurnOff.
-    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy({HWC_CONFIG_ID_90, {Fps(90), Fps(90)}}),
-              0);
-    EXPECT_EQ(KernelIdleTimerAction::TurnOff, refreshRateConfigs->getIdleTimerAction());
+    EXPECT_GE(configs.setDisplayManagerPolicy({kModeId90, {90_Hz, 90_Hz}}), 0);
+    EXPECT_EQ(KernelIdleTimerAction::TurnOff, configs.getIdleTimerAction());
 }
 
 TEST_F(RefreshRateConfigsTest, testKernelIdleTimerActionFor120Hz) {
-    using KernelIdleTimerAction = scheduler::RefreshRateConfigs::KernelIdleTimerAction;
+    using KernelIdleTimerAction = RefreshRateConfigs::KernelIdleTimerAction;
 
-    // Tests with 120Hz
-    auto refreshRateConfigs =
-            std::make_unique<RefreshRateConfigs>(m60_120Device,
-                                                 /*currentConfigId=*/HWC_CONFIG_ID_120);
+    RefreshRateConfigs configs(kModes_60_120, kModeId120);
+
     // SetPolicy(0, 60), current 60Hz => TurnOn.
-    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy({HWC_CONFIG_ID_60, {Fps(0), Fps(60)}}),
-              0);
-    EXPECT_EQ(KernelIdleTimerAction::TurnOn, refreshRateConfigs->getIdleTimerAction());
+    EXPECT_GE(configs.setDisplayManagerPolicy({kModeId60, {0_Hz, 60_Hz}}), 0);
+    EXPECT_EQ(KernelIdleTimerAction::TurnOn, configs.getIdleTimerAction());
 
     // SetPolicy(60, 60), current 60Hz => TurnOff.
-    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy({HWC_CONFIG_ID_60, {Fps(60), Fps(60)}}),
-              0);
-    EXPECT_EQ(KernelIdleTimerAction::TurnOff, refreshRateConfigs->getIdleTimerAction());
+    EXPECT_GE(configs.setDisplayManagerPolicy({kModeId60, {60_Hz, 60_Hz}}), 0);
+    EXPECT_EQ(KernelIdleTimerAction::TurnOff, configs.getIdleTimerAction());
 
     // SetPolicy(60, 120), current 60Hz => TurnOn.
-    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy({HWC_CONFIG_ID_60, {Fps(60), Fps(120)}}),
-              0);
-    EXPECT_EQ(KernelIdleTimerAction::TurnOn, refreshRateConfigs->getIdleTimerAction());
+    EXPECT_GE(configs.setDisplayManagerPolicy({kModeId60, {60_Hz, 120_Hz}}), 0);
+    EXPECT_EQ(KernelIdleTimerAction::TurnOn, configs.getIdleTimerAction());
 
     // SetPolicy(120, 120), current 120Hz => TurnOff.
-    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy(
-                      {HWC_CONFIG_ID_120, {Fps(120), Fps(120)}}),
-              0);
-    EXPECT_EQ(KernelIdleTimerAction::TurnOff, refreshRateConfigs->getIdleTimerAction());
+    EXPECT_GE(configs.setDisplayManagerPolicy({kModeId120, {120_Hz, 120_Hz}}), 0);
+    EXPECT_EQ(KernelIdleTimerAction::TurnOff, configs.getIdleTimerAction());
 }
 
-TEST_F(RefreshRateConfigsTest, getFrameRateDivider) {
-    auto refreshRateConfigs =
-            std::make_unique<RefreshRateConfigs>(m30_60_72_90_120Device,
-                                                 /*currentConfigId=*/HWC_CONFIG_ID_30);
+TEST_F(RefreshRateConfigsTest, getFrameRateDivisor) {
+    RefreshRateConfigs configs(kModes_30_60_72_90_120, kModeId30);
 
-    const auto frameRate = Fps(30.f);
-    Fps displayRefreshRate = refreshRateConfigs->getCurrentRefreshRate().getFps();
-    EXPECT_EQ(1, RefreshRateConfigs::getFrameRateDivider(displayRefreshRate, frameRate));
+    const auto frameRate = 30_Hz;
+    Fps displayRefreshRate = configs.getActiveMode()->getFps();
+    EXPECT_EQ(1, RefreshRateConfigs::getFrameRateDivisor(displayRefreshRate, frameRate));
 
-    refreshRateConfigs->setCurrentModeId(HWC_CONFIG_ID_60);
-    displayRefreshRate = refreshRateConfigs->getCurrentRefreshRate().getFps();
-    EXPECT_EQ(2, RefreshRateConfigs::getFrameRateDivider(displayRefreshRate, frameRate));
+    configs.setActiveModeId(kModeId60);
+    displayRefreshRate = configs.getActiveMode()->getFps();
+    EXPECT_EQ(2, RefreshRateConfigs::getFrameRateDivisor(displayRefreshRate, frameRate));
 
-    refreshRateConfigs->setCurrentModeId(HWC_CONFIG_ID_72);
-    displayRefreshRate = refreshRateConfigs->getCurrentRefreshRate().getFps();
-    EXPECT_EQ(0, RefreshRateConfigs::getFrameRateDivider(displayRefreshRate, frameRate));
+    configs.setActiveModeId(kModeId72);
+    displayRefreshRate = configs.getActiveMode()->getFps();
+    EXPECT_EQ(0, RefreshRateConfigs::getFrameRateDivisor(displayRefreshRate, frameRate));
 
-    refreshRateConfigs->setCurrentModeId(HWC_CONFIG_ID_90);
-    displayRefreshRate = refreshRateConfigs->getCurrentRefreshRate().getFps();
-    EXPECT_EQ(3, RefreshRateConfigs::getFrameRateDivider(displayRefreshRate, frameRate));
+    configs.setActiveModeId(kModeId90);
+    displayRefreshRate = configs.getActiveMode()->getFps();
+    EXPECT_EQ(3, RefreshRateConfigs::getFrameRateDivisor(displayRefreshRate, frameRate));
 
-    refreshRateConfigs->setCurrentModeId(HWC_CONFIG_ID_120);
-    displayRefreshRate = refreshRateConfigs->getCurrentRefreshRate().getFps();
-    EXPECT_EQ(4, RefreshRateConfigs::getFrameRateDivider(displayRefreshRate, frameRate));
+    configs.setActiveModeId(kModeId120);
+    displayRefreshRate = configs.getActiveMode()->getFps();
+    EXPECT_EQ(4, RefreshRateConfigs::getFrameRateDivisor(displayRefreshRate, frameRate));
 
-    refreshRateConfigs->setCurrentModeId(HWC_CONFIG_ID_90);
-    displayRefreshRate = refreshRateConfigs->getCurrentRefreshRate().getFps();
-    EXPECT_EQ(4, RefreshRateConfigs::getFrameRateDivider(displayRefreshRate, Fps(22.5f)));
+    configs.setActiveModeId(kModeId90);
+    displayRefreshRate = configs.getActiveMode()->getFps();
+    EXPECT_EQ(4, RefreshRateConfigs::getFrameRateDivisor(displayRefreshRate, 22.5_Hz));
 
-    EXPECT_EQ(0, RefreshRateConfigs::getFrameRateDivider(Fps(24.f), Fps(25.f)));
-    EXPECT_EQ(0, RefreshRateConfigs::getFrameRateDivider(Fps(24.f), Fps(23.976f)));
-    EXPECT_EQ(0, RefreshRateConfigs::getFrameRateDivider(Fps(30.f), Fps(29.97f)));
-    EXPECT_EQ(0, RefreshRateConfigs::getFrameRateDivider(Fps(60.f), Fps(59.94f)));
+    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(Fps(23.976f), Fps(24.f)));
-    EXPECT_TRUE(RefreshRateConfigs::isFractionalPairOrMultiple(Fps(24.f), Fps(23.976f)));
+    EXPECT_TRUE(RefreshRateConfigs::isFractionalPairOrMultiple(23.976_Hz, 24_Hz));
+    EXPECT_TRUE(RefreshRateConfigs::isFractionalPairOrMultiple(24_Hz, 23.976_Hz));
 
-    EXPECT_TRUE(RefreshRateConfigs::isFractionalPairOrMultiple(Fps(29.97f), Fps(30.f)));
-    EXPECT_TRUE(RefreshRateConfigs::isFractionalPairOrMultiple(Fps(30.f), Fps(29.97f)));
+    EXPECT_TRUE(RefreshRateConfigs::isFractionalPairOrMultiple(29.97_Hz, 30_Hz));
+    EXPECT_TRUE(RefreshRateConfigs::isFractionalPairOrMultiple(30_Hz, 29.97_Hz));
 
-    EXPECT_TRUE(RefreshRateConfigs::isFractionalPairOrMultiple(Fps(59.94f), Fps(60.f)));
-    EXPECT_TRUE(RefreshRateConfigs::isFractionalPairOrMultiple(Fps(60.f), Fps(59.94f)));
+    EXPECT_TRUE(RefreshRateConfigs::isFractionalPairOrMultiple(59.94_Hz, 60_Hz));
+    EXPECT_TRUE(RefreshRateConfigs::isFractionalPairOrMultiple(60_Hz, 59.94_Hz));
 
-    EXPECT_TRUE(RefreshRateConfigs::isFractionalPairOrMultiple(Fps(29.97f), Fps(60.f)));
-    EXPECT_TRUE(RefreshRateConfigs::isFractionalPairOrMultiple(Fps(60.f), Fps(29.97f)));
+    EXPECT_TRUE(RefreshRateConfigs::isFractionalPairOrMultiple(29.97_Hz, 60_Hz));
+    EXPECT_TRUE(RefreshRateConfigs::isFractionalPairOrMultiple(60_Hz, 29.97_Hz));
 
-    EXPECT_TRUE(RefreshRateConfigs::isFractionalPairOrMultiple(Fps(59.94f), Fps(30.f)));
-    EXPECT_TRUE(RefreshRateConfigs::isFractionalPairOrMultiple(Fps(30.f), Fps(59.94f)));
+    EXPECT_TRUE(RefreshRateConfigs::isFractionalPairOrMultiple(59.94_Hz, 30_Hz));
+    EXPECT_TRUE(RefreshRateConfigs::isFractionalPairOrMultiple(30_Hz, 59.94_Hz));
 
-    const std::vector<float> refreshRates = {23.976f, 24.f, 25.f, 29.97f, 30.f, 50.f, 59.94f, 60.f};
+    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(Fps(refreshRate), Fps(refreshRate)));
+        EXPECT_FALSE(RefreshRateConfigs::isFractionalPairOrMultiple(refreshRate, refreshRate));
     }
 
-    EXPECT_FALSE(RefreshRateConfigs::isFractionalPairOrMultiple(Fps(24.f), Fps(25.f)));
-    EXPECT_FALSE(RefreshRateConfigs::isFractionalPairOrMultiple(Fps(23.978f), Fps(25.f)));
-    EXPECT_FALSE(RefreshRateConfigs::isFractionalPairOrMultiple(Fps(29.97f), Fps(59.94f)));
+    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) {
-    auto refreshRateConfigs =
-            std::make_unique<RefreshRateConfigs>(m30_60_72_90_120Device, /*currentConfigId=*/
-                                                 HWC_CONFIG_ID_120);
+    RefreshRateConfigs configs(kModes_30_60_72_90_120, kModeId120);
 
-    auto layers = std::vector<LayerRequirement>{};
-    ASSERT_TRUE(refreshRateConfigs->getFrameRateOverrides(layers, Fps(120.0f), /*touch=*/false)
-                        .empty());
+    EXPECT_TRUE(configs.getFrameRateOverrides({}, 120_Hz, {}).empty());
 }
 
 TEST_F(RefreshRateConfigsTest, getFrameRateOverrides_60on120) {
-    RefreshRateConfigs::Config config = {.enableFrameRateOverride = true};
-    auto refreshRateConfigs =
-            std::make_unique<RefreshRateConfigs>(m30_60_72_90_120Device, /*currentConfigId=*/
-                                                 HWC_CONFIG_ID_120, config);
+    RefreshRateConfigs configs(kModes_30_60_72_90_120, kModeId120,
+                               {.enableFrameRateOverride = true});
 
-    auto layers = std::vector<LayerRequirement>{LayerRequirement{.weight = 1.0f}};
+    std::vector<LayerRequirement> layers = {{.weight = 1.f}};
     layers[0].name = "Test layer";
     layers[0].ownerUid = 1234;
-    layers[0].desiredRefreshRate = Fps(60.0f);
+    layers[0].desiredRefreshRate = 60_Hz;
     layers[0].vote = LayerVoteType::ExplicitDefault;
-    auto frameRateOverrides =
-            refreshRateConfigs->getFrameRateOverrides(layers, Fps(120.0f), /*touch=*/false);
-    ASSERT_EQ(1, frameRateOverrides.size());
-    ASSERT_EQ(1, frameRateOverrides.count(1234));
-    ASSERT_EQ(60.0f, frameRateOverrides.at(1234).getValue());
+
+    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 =
-            refreshRateConfigs->getFrameRateOverrides(layers, Fps(120.0f), /*touch=*/false);
-    ASSERT_EQ(1, frameRateOverrides.size());
-    ASSERT_EQ(1, frameRateOverrides.count(1234));
-    ASSERT_EQ(60.0f, frameRateOverrides.at(1234).getValue());
+    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 =
-            refreshRateConfigs->getFrameRateOverrides(layers, Fps(120.0f), /*touch=*/false);
-    ASSERT_TRUE(frameRateOverrides.empty());
+    frameRateOverrides = configs.getFrameRateOverrides(layers, 120_Hz, {});
+    EXPECT_TRUE(frameRateOverrides.empty());
 
     layers[0].vote = LayerVoteType::Min;
-    frameRateOverrides =
-            refreshRateConfigs->getFrameRateOverrides(layers, Fps(120.0f), /*touch=*/false);
-    ASSERT_TRUE(frameRateOverrides.empty());
+    frameRateOverrides = configs.getFrameRateOverrides(layers, 120_Hz, {});
+    EXPECT_TRUE(frameRateOverrides.empty());
 
     layers[0].vote = LayerVoteType::Max;
-    frameRateOverrides =
-            refreshRateConfigs->getFrameRateOverrides(layers, Fps(120.0f), /*touch=*/false);
-    ASSERT_TRUE(frameRateOverrides.empty());
+    frameRateOverrides = configs.getFrameRateOverrides(layers, 120_Hz, {});
+    EXPECT_TRUE(frameRateOverrides.empty());
 
     layers[0].vote = LayerVoteType::Heuristic;
-    frameRateOverrides =
-            refreshRateConfigs->getFrameRateOverrides(layers, Fps(120.0f), /*touch=*/false);
-    ASSERT_TRUE(frameRateOverrides.empty());
+    frameRateOverrides = configs.getFrameRateOverrides(layers, 120_Hz, {});
+    EXPECT_TRUE(frameRateOverrides.empty());
 }
 
 TEST_F(RefreshRateConfigsTest, getFrameRateOverrides_twoUids) {
-    RefreshRateConfigs::Config config = {.enableFrameRateOverride = true};
-    auto refreshRateConfigs =
-            std::make_unique<RefreshRateConfigs>(m30_60_72_90_120Device, /*currentConfigId=*/
-                                                 HWC_CONFIG_ID_120, config);
+    RefreshRateConfigs configs(kModes_30_60_72_90_120, kModeId120,
+                               {.enableFrameRateOverride = true});
 
-    auto layers = std::vector<LayerRequirement>{
-            LayerRequirement{.ownerUid = 1234, .weight = 1.0f},
-            LayerRequirement{.ownerUid = 5678, .weight = 1.0f},
-    };
+    std::vector<LayerRequirement> layers = {{.ownerUid = 1234, .weight = 1.f},
+                                            {.ownerUid = 5678, .weight = 1.f}};
 
     layers[0].name = "Test layer 1234";
-    layers[0].desiredRefreshRate = Fps(60.0f);
+    layers[0].desiredRefreshRate = 60_Hz;
     layers[0].vote = LayerVoteType::ExplicitDefault;
 
     layers[1].name = "Test layer 5678";
-    layers[1].desiredRefreshRate = Fps(30.0f);
+    layers[1].desiredRefreshRate = 30_Hz;
     layers[1].vote = LayerVoteType::ExplicitDefault;
-    auto frameRateOverrides =
-            refreshRateConfigs->getFrameRateOverrides(layers, Fps(120.0f), /*touch=*/false);
+    auto frameRateOverrides = configs.getFrameRateOverrides(layers, 120_Hz, {});
 
-    ASSERT_EQ(2, frameRateOverrides.size());
-    ASSERT_EQ(1, frameRateOverrides.count(1234));
-    ASSERT_EQ(60.0f, frameRateOverrides.at(1234).getValue());
-    ASSERT_EQ(1, frameRateOverrides.count(5678));
-    ASSERT_EQ(30.0f, frameRateOverrides.at(5678).getValue());
+    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 =
-            refreshRateConfigs->getFrameRateOverrides(layers, Fps(120.0f), /*touch=*/false);
-    ASSERT_EQ(1, frameRateOverrides.size());
-    ASSERT_EQ(1, frameRateOverrides.count(1234));
-    ASSERT_EQ(60.0f, frameRateOverrides.at(1234).getValue());
+    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 =
-            refreshRateConfigs->getFrameRateOverrides(layers, Fps(120.0f), /*touch=*/false);
-    ASSERT_TRUE(frameRateOverrides.empty());
+    frameRateOverrides = configs.getFrameRateOverrides(layers, 120_Hz, {});
+    EXPECT_TRUE(frameRateOverrides.empty());
 }
 
 TEST_F(RefreshRateConfigsTest, getFrameRateOverrides_touch) {
-    RefreshRateConfigs::Config config = {.enableFrameRateOverride = true};
-    auto refreshRateConfigs =
-            std::make_unique<RefreshRateConfigs>(m30_60_72_90_120Device, /*currentConfigId=*/
-                                                 HWC_CONFIG_ID_120, config);
+    RefreshRateConfigs configs(kModes_30_60_72_90_120, kModeId120,
+                               {.enableFrameRateOverride = true});
 
-    auto layers = std::vector<LayerRequirement>{
-            LayerRequirement{.ownerUid = 1234, .weight = 1.0f},
-    };
-
+    std::vector<LayerRequirement> layers = {{.ownerUid = 1234, .weight = 1.f}};
     layers[0].name = "Test layer";
-    layers[0].desiredRefreshRate = Fps(60.0f);
+    layers[0].desiredRefreshRate = 60_Hz;
     layers[0].vote = LayerVoteType::ExplicitDefault;
 
-    auto frameRateOverrides =
-            refreshRateConfigs->getFrameRateOverrides(layers, Fps(120.0f), /*touch=*/false);
-    ASSERT_EQ(1, frameRateOverrides.size());
-    ASSERT_EQ(1, frameRateOverrides.count(1234));
-    ASSERT_EQ(60.0f, frameRateOverrides.at(1234).getValue());
+    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 =
-            refreshRateConfigs->getFrameRateOverrides(layers, Fps(120.0f), /*touch=*/true);
-    ASSERT_EQ(1, frameRateOverrides.size());
-    ASSERT_EQ(1, frameRateOverrides.count(1234));
-    ASSERT_EQ(60.0f, frameRateOverrides.at(1234).getValue());
+    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 =
-            refreshRateConfigs->getFrameRateOverrides(layers, Fps(120.0f), /*touch=*/false);
-    ASSERT_EQ(1, frameRateOverrides.size());
-    ASSERT_EQ(1, frameRateOverrides.count(1234));
-    ASSERT_EQ(60.0f, frameRateOverrides.at(1234).getValue());
+    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 =
-            refreshRateConfigs->getFrameRateOverrides(layers, Fps(120.0f), /*touch=*/true);
-    ASSERT_EQ(1, frameRateOverrides.size());
-    ASSERT_EQ(1, frameRateOverrides.count(1234));
-    ASSERT_EQ(60.0f, frameRateOverrides.at(1234).getValue());
+    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 =
-            refreshRateConfigs->getFrameRateOverrides(layers, Fps(120.0f), /*touch=*/false);
-    ASSERT_EQ(1, frameRateOverrides.size());
-    ASSERT_EQ(1, frameRateOverrides.count(1234));
-    ASSERT_EQ(60.0f, frameRateOverrides.at(1234).getValue());
+    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 =
-            refreshRateConfigs->getFrameRateOverrides(layers, Fps(120.0f), /*touch=*/true);
-    ASSERT_TRUE(frameRateOverrides.empty());
+    frameRateOverrides = configs.getFrameRateOverrides(layers, 120_Hz, {.touch = true});
+    EXPECT_TRUE(frameRateOverrides.empty());
 }
 
 } // namespace
-} // namespace scheduler
-} // namespace android
-
-// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic pop // ignored "-Wextra"
+} // namespace android::scheduler
diff --git a/services/surfaceflinger/tests/unittests/RefreshRateSelectionTest.cpp b/services/surfaceflinger/tests/unittests/RefreshRateSelectionTest.cpp
index 35033ea..1e6e336 100644
--- a/services/surfaceflinger/tests/unittests/RefreshRateSelectionTest.cpp
+++ b/services/surfaceflinger/tests/unittests/RefreshRateSelectionTest.cpp
@@ -21,7 +21,6 @@
 #include <gtest/gtest.h>
 #include <gui/LayerMetadata.h>
 
-#include "BufferQueueLayer.h"
 #include "BufferStateLayer.h"
 #include "EffectLayer.h"
 #include "Layer.h"
@@ -60,7 +59,6 @@
     static constexpr int32_t PRIORITY_UNSET = -1;
 
     void setupScheduler();
-    sp<BufferQueueLayer> createBufferQueueLayer();
     sp<BufferStateLayer> createBufferStateLayer();
     sp<EffectLayer> createEffectLayer();
 
@@ -90,24 +88,17 @@
     ALOGD("**** Tearing down after %s.%s\n", test_info->test_case_name(), test_info->name());
 }
 
-sp<BufferQueueLayer> RefreshRateSelectionTest::createBufferQueueLayer() {
-    sp<Client> client;
-    LayerCreationArgs args(mFlinger.flinger(), client, "buffer-queue-layer", WIDTH, HEIGHT,
-                           LAYER_FLAGS, LayerMetadata());
-    return new BufferQueueLayer(args);
-}
 
 sp<BufferStateLayer> RefreshRateSelectionTest::createBufferStateLayer() {
     sp<Client> client;
-    LayerCreationArgs args(mFlinger.flinger(), client, "buffer-queue-layer", WIDTH, HEIGHT,
-                           LAYER_FLAGS, LayerMetadata());
+    LayerCreationArgs args(mFlinger.flinger(), client, "buffer-queue-layer", LAYER_FLAGS,
+                           LayerMetadata());
     return new BufferStateLayer(args);
 }
 
 sp<EffectLayer> RefreshRateSelectionTest::createEffectLayer() {
     sp<Client> client;
-    LayerCreationArgs args(mFlinger.flinger(), client, "color-layer", WIDTH, HEIGHT, LAYER_FLAGS,
-                           LayerMetadata());
+    LayerCreationArgs args(mFlinger.flinger(), client, "color-layer", LAYER_FLAGS, LayerMetadata());
     return new EffectLayer(args);
 }
 
@@ -149,46 +140,6 @@
 /* ------------------------------------------------------------------------
  * Test cases
  */
-TEST_F(RefreshRateSelectionTest, testPriorityOnBufferQueueLayers) {
-    mParent = createBufferQueueLayer();
-    mChild = createBufferQueueLayer();
-    setParent(mChild.get(), mParent.get());
-    mGrandChild = createBufferQueueLayer();
-    setParent(mGrandChild.get(), mChild.get());
-
-    ASSERT_EQ(PRIORITY_UNSET, mParent->getFrameRateSelectionPriority());
-    ASSERT_EQ(PRIORITY_UNSET, mChild->getFrameRateSelectionPriority());
-    ASSERT_EQ(PRIORITY_UNSET, mGrandChild->getFrameRateSelectionPriority());
-
-    // Child has its own priority.
-    mGrandChild->setFrameRateSelectionPriority(1);
-    commitTransaction(mGrandChild.get());
-    ASSERT_EQ(PRIORITY_UNSET, mParent->getFrameRateSelectionPriority());
-    ASSERT_EQ(PRIORITY_UNSET, mChild->getFrameRateSelectionPriority());
-    ASSERT_EQ(1, mGrandChild->getFrameRateSelectionPriority());
-
-    // Child inherits from his parent.
-    mChild->setFrameRateSelectionPriority(1);
-    commitTransaction(mChild.get());
-    mGrandChild->setFrameRateSelectionPriority(PRIORITY_UNSET);
-    commitTransaction(mGrandChild.get());
-
-    ASSERT_EQ(PRIORITY_UNSET, mParent->getFrameRateSelectionPriority());
-    ASSERT_EQ(1, mChild->getFrameRateSelectionPriority());
-    ASSERT_EQ(1, mGrandChild->getFrameRateSelectionPriority());
-
-    // Grandchild inherits from his grand parent.
-    mParent->setFrameRateSelectionPriority(1);
-    commitTransaction(mParent.get());
-    mChild->setFrameRateSelectionPriority(PRIORITY_UNSET);
-    commitTransaction(mChild.get());
-    mGrandChild->setFrameRateSelectionPriority(PRIORITY_UNSET);
-    commitTransaction(mGrandChild.get());
-    ASSERT_EQ(1, mParent->getFrameRateSelectionPriority());
-    ASSERT_EQ(1, mChild->getFrameRateSelectionPriority());
-    ASSERT_EQ(1, mGrandChild->getFrameRateSelectionPriority());
-}
-
 TEST_F(RefreshRateSelectionTest, testPriorityOnBufferStateLayers) {
     mParent = createBufferStateLayer();
     mChild = createBufferStateLayer();
diff --git a/services/surfaceflinger/tests/unittests/RefreshRateStatsTest.cpp b/services/surfaceflinger/tests/unittests/RefreshRateStatsTest.cpp
index 12b155b..e2e3d7b 100644
--- a/services/surfaceflinger/tests/unittests/RefreshRateStatsTest.cpp
+++ b/services/surfaceflinger/tests/unittests/RefreshRateStatsTest.cpp
@@ -14,10 +14,6 @@
  * limitations under the License.
  */
 
-// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wextra"
-
 #undef LOG_TAG
 #define LOG_TAG "SchedulerUnittests"
 
@@ -25,8 +21,6 @@
 #include <log/log.h>
 #include <thread>
 
-#include "DisplayHardware/DisplayMode.h"
-#include "Scheduler/RefreshRateConfigs.h"
 #include "Scheduler/RefreshRateStats.h"
 #include "mock/MockTimeStats.h"
 
@@ -35,34 +29,19 @@
 using testing::_;
 using testing::AtLeast;
 
-namespace android {
-namespace scheduler {
+namespace android::scheduler {
 
 class RefreshRateStatsTest : public testing::Test {
 protected:
-    static inline const auto CONFIG_ID_0 = DisplayModeId(0);
-    static inline const auto CONFIG_ID_1 = DisplayModeId(1);
-    static inline const auto CONFIG_GROUP_0 = 0;
-    static constexpr int64_t VSYNC_90 = 11111111;
-    static constexpr int64_t VSYNC_60 = 16666667;
-
     RefreshRateStatsTest();
     ~RefreshRateStatsTest();
 
-    void init(const DisplayModes& configs) {
-        mRefreshRateConfigs =
-                std::make_unique<RefreshRateConfigs>(configs, /*currentConfig=*/CONFIG_ID_0);
-
-        const auto currFps = mRefreshRateConfigs->getRefreshRateFromModeId(CONFIG_ID_0).getFps();
-        mRefreshRateStats = std::make_unique<RefreshRateStats>(mTimeStats, currFps,
-                                                               /*currentPowerMode=*/PowerMode::OFF);
+    void resetStats(Fps fps) {
+        mRefreshRateStats = std::make_unique<RefreshRateStats>(mTimeStats, fps, PowerMode::OFF);
     }
 
     mock::TimeStats mTimeStats;
-    std::unique_ptr<RefreshRateConfigs> mRefreshRateConfigs;
     std::unique_ptr<RefreshRateStats> mRefreshRateStats;
-
-    DisplayModePtr createDisplayMode(DisplayModeId modeId, int32_t group, int64_t vsyncPeriod);
 };
 
 RefreshRateStatsTest::RefreshRateStatsTest() {
@@ -77,147 +56,132 @@
     ALOGD("**** Tearing down after %s.%s\n", test_info->test_case_name(), test_info->name());
 }
 
-DisplayModePtr RefreshRateStatsTest::createDisplayMode(DisplayModeId modeId, int32_t group,
-                                                       int64_t vsyncPeriod) {
-    return DisplayMode::Builder(static_cast<hal::HWConfigId>(modeId.value()))
-            .setId(modeId)
-            .setPhysicalDisplayId(PhysicalDisplayId(0))
-            .setVsyncPeriod(static_cast<int32_t>(vsyncPeriod))
-            .setGroup(group)
-            .build();
-}
-
 namespace {
-/* ------------------------------------------------------------------------
- * Test cases
- */
-TEST_F(RefreshRateStatsTest, oneConfigTest) {
-    init({createDisplayMode(CONFIG_ID_0, CONFIG_GROUP_0, VSYNC_90)});
+
+TEST_F(RefreshRateStatsTest, oneMode) {
+    resetStats(90_Hz);
 
     EXPECT_CALL(mTimeStats, recordRefreshRate(0, _)).Times(AtLeast(1));
     EXPECT_CALL(mTimeStats, recordRefreshRate(90, _)).Times(AtLeast(1));
 
-    std::unordered_map<std::string, int64_t> times = mRefreshRateStats->getTotalTimes();
-    ASSERT_EQ(1, times.size());
-    EXPECT_NE(0u, times.count("ScreenOff"));
-    // Setting up tests on mobile harness can be flaky with time passing, so testing for
-    // exact time changes can result in flaxy numbers. To avoid that remember old
-    // numbers to make sure the correct values are increasing in the next test.
-    auto screenOff = times["ScreenOff"];
+    auto times = mRefreshRateStats->getTotalTimes();
+    ASSERT_TRUE(times.contains("ScreenOff"));
+    EXPECT_EQ(1u, times.size());
 
     // Screen is off by default.
+    std::chrono::milliseconds screenOff = times.get("ScreenOff")->get();
     std::this_thread::sleep_for(std::chrono::milliseconds(2));
     times = mRefreshRateStats->getTotalTimes();
-    EXPECT_LT(screenOff, times["ScreenOff"]);
-    EXPECT_EQ(0u, times.count("90.00fps"));
 
-    const auto config0Fps = mRefreshRateConfigs->getRefreshRateFromModeId(CONFIG_ID_0).getFps();
-    mRefreshRateStats->setRefreshRate(config0Fps);
+    EXPECT_LT(screenOff, times.get("ScreenOff")->get());
+    EXPECT_FALSE(times.contains("90.00 Hz"));
+
+    mRefreshRateStats->setRefreshRate(90_Hz);
     mRefreshRateStats->setPowerMode(PowerMode::ON);
-    screenOff = mRefreshRateStats->getTotalTimes()["ScreenOff"];
+    screenOff = mRefreshRateStats->getTotalTimes().get("ScreenOff")->get();
     std::this_thread::sleep_for(std::chrono::milliseconds(2));
     times = mRefreshRateStats->getTotalTimes();
-    EXPECT_EQ(screenOff, times["ScreenOff"]);
-    ASSERT_EQ(1u, times.count("90.00fps"));
-    EXPECT_LT(0, times["90.00fps"]);
+
+    EXPECT_EQ(screenOff, times.get("ScreenOff")->get());
+    ASSERT_TRUE(times.contains("90.00 Hz"));
+    EXPECT_LT(0ms, times.get("90.00 Hz")->get());
 
     mRefreshRateStats->setPowerMode(PowerMode::DOZE);
-    auto ninety = mRefreshRateStats->getTotalTimes()["90.00fps"];
+    const auto ninety = mRefreshRateStats->getTotalTimes().get("90.00 Hz")->get();
     std::this_thread::sleep_for(std::chrono::milliseconds(2));
     times = mRefreshRateStats->getTotalTimes();
-    EXPECT_LT(screenOff, times["ScreenOff"]);
-    EXPECT_EQ(ninety, times["90.00fps"]);
 
-    mRefreshRateStats->setRefreshRate(config0Fps);
-    screenOff = mRefreshRateStats->getTotalTimes()["ScreenOff"];
+    EXPECT_LT(screenOff, times.get("ScreenOff")->get());
+    EXPECT_EQ(ninety, times.get("90.00 Hz")->get());
+
+    mRefreshRateStats->setRefreshRate(90_Hz);
+    screenOff = mRefreshRateStats->getTotalTimes().get("ScreenOff")->get();
     std::this_thread::sleep_for(std::chrono::milliseconds(2));
     times = mRefreshRateStats->getTotalTimes();
-    // Because the power mode is not PowerMode::ON, switching the config
-    // does not update refresh rates that come from the config.
-    EXPECT_LT(screenOff, times["ScreenOff"]);
-    EXPECT_EQ(ninety, times["90.00fps"]);
+
+    // Stats are not updated while the screen is off.
+    EXPECT_LT(screenOff, times.get("ScreenOff")->get());
+    EXPECT_EQ(ninety, times.get("90.00 Hz")->get());
 }
 
-TEST_F(RefreshRateStatsTest, twoConfigsTest) {
-    init({createDisplayMode(CONFIG_ID_0, CONFIG_GROUP_0, VSYNC_90),
-          createDisplayMode(CONFIG_ID_1, CONFIG_GROUP_0, VSYNC_60)});
+TEST_F(RefreshRateStatsTest, twoModes) {
+    resetStats(90_Hz);
 
     EXPECT_CALL(mTimeStats, recordRefreshRate(0, _)).Times(AtLeast(1));
     EXPECT_CALL(mTimeStats, recordRefreshRate(60, _)).Times(AtLeast(1));
     EXPECT_CALL(mTimeStats, recordRefreshRate(90, _)).Times(AtLeast(1));
 
-    std::unordered_map<std::string, int64_t> times = mRefreshRateStats->getTotalTimes();
-    ASSERT_EQ(1, times.size());
-    EXPECT_NE(0u, times.count("ScreenOff"));
-    // Setting up tests on mobile harness can be flaky with time passing, so testing for
-    // exact time changes can result in flaxy numbers. To avoid that remember old
-    // numbers to make sure the correct values are increasing in the next test.
-    auto screenOff = times["ScreenOff"];
+    auto times = mRefreshRateStats->getTotalTimes();
+    ASSERT_TRUE(times.contains("ScreenOff"));
+    EXPECT_EQ(1u, times.size());
 
     // Screen is off by default.
+    std::chrono::milliseconds screenOff = times.get("ScreenOff")->get();
     std::this_thread::sleep_for(std::chrono::milliseconds(2));
     times = mRefreshRateStats->getTotalTimes();
-    EXPECT_LT(screenOff, times["ScreenOff"]);
 
-    const auto config0Fps = mRefreshRateConfigs->getRefreshRateFromModeId(CONFIG_ID_0).getFps();
-    const auto config1Fps = mRefreshRateConfigs->getRefreshRateFromModeId(CONFIG_ID_1).getFps();
-    mRefreshRateStats->setRefreshRate(config0Fps);
+    EXPECT_LT(screenOff, times.get("ScreenOff")->get());
+    EXPECT_FALSE(times.contains("60.00 Hz"));
+    EXPECT_FALSE(times.contains("90.00 Hz"));
+
+    mRefreshRateStats->setRefreshRate(90_Hz);
     mRefreshRateStats->setPowerMode(PowerMode::ON);
-    screenOff = mRefreshRateStats->getTotalTimes()["ScreenOff"];
+    screenOff = mRefreshRateStats->getTotalTimes().get("ScreenOff")->get();
     std::this_thread::sleep_for(std::chrono::milliseconds(2));
     times = mRefreshRateStats->getTotalTimes();
-    EXPECT_EQ(screenOff, times["ScreenOff"]);
-    ASSERT_EQ(1u, times.count("90.00fps"));
-    EXPECT_LT(0, times["90.00fps"]);
 
-    // When power mode is normal, time for configs updates.
-    mRefreshRateStats->setRefreshRate(config1Fps);
-    auto ninety = mRefreshRateStats->getTotalTimes()["90.00fps"];
+    EXPECT_EQ(screenOff, times.get("ScreenOff")->get());
+    ASSERT_TRUE(times.contains("90.00 Hz"));
+    EXPECT_LT(0ms, times.get("90.00 Hz")->get());
+
+    mRefreshRateStats->setRefreshRate(60_Hz);
+    auto ninety = mRefreshRateStats->getTotalTimes().get("90.00 Hz")->get();
     std::this_thread::sleep_for(std::chrono::milliseconds(2));
     times = mRefreshRateStats->getTotalTimes();
-    EXPECT_EQ(screenOff, times["ScreenOff"]);
-    EXPECT_EQ(ninety, times["90.00fps"]);
-    ASSERT_EQ(1u, times.count("60.00fps"));
-    EXPECT_LT(0, times["60.00fps"]);
 
-    mRefreshRateStats->setRefreshRate(config0Fps);
-    auto sixty = mRefreshRateStats->getTotalTimes()["60.00fps"];
+    EXPECT_EQ(screenOff, times.get("ScreenOff")->get());
+    EXPECT_EQ(ninety, times.get("90.00 Hz")->get());
+    ASSERT_TRUE(times.contains("60.00 Hz"));
+    EXPECT_LT(0ms, times.get("60.00 Hz")->get());
+
+    mRefreshRateStats->setRefreshRate(90_Hz);
+    auto sixty = mRefreshRateStats->getTotalTimes().get("60.00 Hz")->get();
     std::this_thread::sleep_for(std::chrono::milliseconds(2));
     times = mRefreshRateStats->getTotalTimes();
-    EXPECT_EQ(screenOff, times["ScreenOff"]);
-    EXPECT_LT(ninety, times["90.00fps"]);
-    EXPECT_EQ(sixty, times["60.00fps"]);
 
-    mRefreshRateStats->setRefreshRate(config1Fps);
-    ninety = mRefreshRateStats->getTotalTimes()["90.00fps"];
+    EXPECT_EQ(screenOff, times.get("ScreenOff")->get());
+    EXPECT_LT(ninety, times.get("90.00 Hz")->get());
+    EXPECT_EQ(sixty, times.get("60.00 Hz")->get());
+
+    mRefreshRateStats->setRefreshRate(60_Hz);
+    ninety = mRefreshRateStats->getTotalTimes().get("90.00 Hz")->get();
     std::this_thread::sleep_for(std::chrono::milliseconds(2));
     times = mRefreshRateStats->getTotalTimes();
-    EXPECT_EQ(screenOff, times["ScreenOff"]);
-    EXPECT_EQ(ninety, times["90.00fps"]);
-    EXPECT_LT(sixty, times["60.00fps"]);
 
-    // Because the power mode is not PowerMode::ON, switching the config
-    // does not update refresh rates that come from the config.
+    EXPECT_EQ(screenOff, times.get("ScreenOff")->get());
+    EXPECT_EQ(ninety, times.get("90.00 Hz")->get());
+    EXPECT_LT(sixty, times.get("60.00 Hz")->get());
+
+    // Stats are not updated while the screen is off.
     mRefreshRateStats->setPowerMode(PowerMode::DOZE);
-    mRefreshRateStats->setRefreshRate(config0Fps);
-    sixty = mRefreshRateStats->getTotalTimes()["60.00fps"];
+    mRefreshRateStats->setRefreshRate(90_Hz);
+    sixty = mRefreshRateStats->getTotalTimes().get("60.00 Hz")->get();
     std::this_thread::sleep_for(std::chrono::milliseconds(2));
     times = mRefreshRateStats->getTotalTimes();
-    EXPECT_LT(screenOff, times["ScreenOff"]);
-    EXPECT_EQ(ninety, times["90.00fps"]);
-    EXPECT_EQ(sixty, times["60.00fps"]);
 
-    mRefreshRateStats->setRefreshRate(config1Fps);
-    screenOff = mRefreshRateStats->getTotalTimes()["ScreenOff"];
+    EXPECT_LT(screenOff, times.get("ScreenOff")->get());
+    EXPECT_EQ(ninety, times.get("90.00 Hz")->get());
+    EXPECT_EQ(sixty, times.get("60.00 Hz")->get());
+
+    mRefreshRateStats->setRefreshRate(60_Hz);
+    screenOff = mRefreshRateStats->getTotalTimes().get("ScreenOff")->get();
     std::this_thread::sleep_for(std::chrono::milliseconds(2));
     times = mRefreshRateStats->getTotalTimes();
-    EXPECT_LT(screenOff, times["ScreenOff"]);
-    EXPECT_EQ(ninety, times["90.00fps"]);
-    EXPECT_EQ(sixty, times["60.00fps"]);
+
+    EXPECT_LT(screenOff, times.get("ScreenOff")->get());
+    EXPECT_EQ(ninety, times.get("90.00 Hz")->get());
+    EXPECT_EQ(sixty, times.get("60.00 Hz")->get());
 }
-} // namespace
-} // namespace scheduler
-} // namespace android
 
-// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic pop // ignored "-Wextra"
+} // namespace
+} // namespace android::scheduler
diff --git a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
index 5713c2f..aab2795 100644
--- a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
@@ -24,17 +24,24 @@
 #include "Scheduler/RefreshRateConfigs.h"
 #include "TestableScheduler.h"
 #include "TestableSurfaceFlinger.h"
+#include "mock/DisplayHardware/MockDisplayMode.h"
 #include "mock/MockEventThread.h"
 #include "mock/MockLayer.h"
 #include "mock/MockSchedulerCallback.h"
 
+namespace android::scheduler {
+
+using android::mock::createDisplayMode;
+
 using testing::_;
 using testing::Return;
 
-namespace android {
 namespace {
 
-constexpr PhysicalDisplayId PHYSICAL_DISPLAY_ID(999);
+using MockEventThread = android::mock::EventThread;
+using MockLayer = android::mock::MockLayer;
+
+constexpr PhysicalDisplayId PHYSICAL_DISPLAY_ID = PhysicalDisplayId::fromPort(255u);
 
 class SchedulerTest : public testing::Test {
 protected:
@@ -44,49 +51,31 @@
               : EventThreadConnection(eventThread, /*callingUid=*/0, ResyncCallback()) {}
         ~MockEventThreadConnection() = default;
 
-        MOCK_METHOD1(stealReceiveChannel, status_t(gui::BitTube* outChannel));
-        MOCK_METHOD1(setVsyncRate, status_t(uint32_t count));
-        MOCK_METHOD0(requestNextVsync, void());
+        MOCK_METHOD1(stealReceiveChannel, binder::Status(gui::BitTube* outChannel));
+        MOCK_METHOD1(setVsyncRate, binder::Status(int count));
+        MOCK_METHOD0(requestNextVsync, binder::Status());
     };
 
     SchedulerTest();
 
-    const DisplayModePtr mode60 = DisplayMode::Builder(0)
-                                          .setId(DisplayModeId(0))
-                                          .setPhysicalDisplayId(PhysicalDisplayId(0))
-                                          .setVsyncPeriod(Fps(60.f).getPeriodNsecs())
-                                          .setGroup(0)
-                                          .build();
-    const DisplayModePtr mode120 = DisplayMode::Builder(1)
-                                           .setId(DisplayModeId(1))
-                                           .setPhysicalDisplayId(PhysicalDisplayId(0))
-                                           .setVsyncPeriod(Fps(120.f).getPeriodNsecs())
-                                           .setGroup(0)
-                                           .build();
+    static inline const DisplayModePtr kMode60 = createDisplayMode(DisplayModeId(0), 60_Hz);
+    static inline const DisplayModePtr kMode120 = createDisplayMode(DisplayModeId(1), 120_Hz);
 
-    std::shared_ptr<scheduler::RefreshRateConfigs> mConfigs =
-            std::make_shared<scheduler::RefreshRateConfigs>(DisplayModes{mode60}, mode60->getId());
+    std::shared_ptr<RefreshRateConfigs> mConfigs =
+            std::make_shared<RefreshRateConfigs>(makeModes(kMode60), kMode60->getId());
 
     mock::SchedulerCallback mSchedulerCallback;
-
-    // The scheduler should initially disable VSYNC.
-    struct ExpectDisableVsync {
-        ExpectDisableVsync(mock::SchedulerCallback& callback) {
-            EXPECT_CALL(callback, setVsyncEnabled(false)).Times(1);
-        }
-    } mExpectDisableVsync{mSchedulerCallback};
-
     TestableScheduler* mScheduler = new TestableScheduler{mConfigs, mSchedulerCallback};
 
-    Scheduler::ConnectionHandle mConnectionHandle;
-    mock::EventThread* mEventThread;
+    ConnectionHandle mConnectionHandle;
+    MockEventThread* mEventThread;
     sp<MockEventThreadConnection> mEventThreadConnection;
 
     TestableSurfaceFlinger mFlinger;
 };
 
 SchedulerTest::SchedulerTest() {
-    auto eventThread = std::make_unique<mock::EventThread>();
+    auto eventThread = std::make_unique<MockEventThread>();
     mEventThread = eventThread.get();
     EXPECT_CALL(*mEventThread, registerDisplayEventConnection(_)).WillOnce(Return(0));
 
@@ -106,7 +95,7 @@
 } // namespace
 
 TEST_F(SchedulerTest, invalidConnectionHandle) {
-    Scheduler::ConnectionHandle handle;
+    ConnectionHandle handle;
 
     const sp<IDisplayEventConnection> connection = mScheduler->createDisplayEventConnection(handle);
 
@@ -163,12 +152,12 @@
 
 TEST_F(SchedulerTest, chooseRefreshRateForContentIsNoopWhenModeSwitchingIsNotSupported) {
     // The layer is registered at creation time and deregistered at destruction time.
-    sp<mock::MockLayer> layer = sp<mock::MockLayer>::make(mFlinger.flinger());
+    sp<MockLayer> layer = sp<MockLayer>::make(mFlinger.flinger());
 
     // recordLayerHistory should be a noop
-    ASSERT_EQ(static_cast<size_t>(0), mScheduler->getNumActiveLayers());
+    ASSERT_EQ(0u, mScheduler->getNumActiveLayers());
     mScheduler->recordLayerHistory(layer.get(), 0, LayerHistory::LayerUpdateType::Buffer);
-    ASSERT_EQ(static_cast<size_t>(0), mScheduler->getNumActiveLayers());
+    ASSERT_EQ(0u, mScheduler->getNumActiveLayers());
 
     constexpr bool kPowerStateNormal = true;
     mScheduler->setDisplayPowerState(kPowerStateNormal);
@@ -176,30 +165,28 @@
     constexpr uint32_t kDisplayArea = 999'999;
     mScheduler->onActiveDisplayAreaChanged(kDisplayArea);
 
-    EXPECT_CALL(mSchedulerCallback, changeRefreshRate(_, _)).Times(0);
+    EXPECT_CALL(mSchedulerCallback, requestDisplayMode(_, _)).Times(0);
     mScheduler->chooseRefreshRateForContent();
 }
 
 TEST_F(SchedulerTest, updateDisplayModes) {
-    ASSERT_EQ(static_cast<size_t>(0), mScheduler->layerHistorySize());
-    sp<mock::MockLayer> layer = sp<mock::MockLayer>::make(mFlinger.flinger());
-    ASSERT_EQ(static_cast<size_t>(1), mScheduler->layerHistorySize());
+    ASSERT_EQ(0u, mScheduler->layerHistorySize());
+    sp<MockLayer> layer = sp<MockLayer>::make(mFlinger.flinger());
+    ASSERT_EQ(1u, mScheduler->layerHistorySize());
 
     mScheduler->setRefreshRateConfigs(
-            std::make_shared<scheduler::RefreshRateConfigs>(DisplayModes{mode60, mode120},
-                                                            mode60->getId()));
+            std::make_shared<RefreshRateConfigs>(makeModes(kMode60, kMode120), kMode60->getId()));
 
-    ASSERT_EQ(static_cast<size_t>(0), mScheduler->getNumActiveLayers());
+    ASSERT_EQ(0u, mScheduler->getNumActiveLayers());
     mScheduler->recordLayerHistory(layer.get(), 0, LayerHistory::LayerUpdateType::Buffer);
-    ASSERT_EQ(static_cast<size_t>(1), mScheduler->getNumActiveLayers());
+    ASSERT_EQ(1u, mScheduler->getNumActiveLayers());
 }
 
-TEST_F(SchedulerTest, testDispatchCachedReportedMode) {
-    // If the optional fields are cleared, the function should return before
-    // onModeChange is called.
-    mScheduler->clearOptionalFieldsInFeatures();
-    EXPECT_NO_FATAL_FAILURE(mScheduler->dispatchCachedReportedMode());
+TEST_F(SchedulerTest, dispatchCachedReportedMode) {
+    mScheduler->clearCachedReportedMode();
+
     EXPECT_CALL(*mEventThread, onModeChanged(_)).Times(0);
+    EXPECT_NO_FATAL_FAILURE(mScheduler->dispatchCachedReportedMode());
 }
 
 TEST_F(SchedulerTest, onNonPrimaryDisplayModeChanged_invalidParameters) {
@@ -211,31 +198,31 @@
 
     // If the handle is incorrect, the function should return before
     // onModeChange is called.
-    Scheduler::ConnectionHandle invalidHandle = {.id = 123};
+    ConnectionHandle invalidHandle = {.id = 123};
     EXPECT_NO_FATAL_FAILURE(mScheduler->onNonPrimaryDisplayModeChanged(invalidHandle, mode));
     EXPECT_CALL(*mEventThread, onModeChanged(_)).Times(0);
 }
 
 TEST_F(SchedulerTest, calculateMaxAcquiredBufferCount) {
-    EXPECT_EQ(1, mFlinger.calculateMaxAcquiredBufferCount(Fps(60), 30ms));
-    EXPECT_EQ(2, mFlinger.calculateMaxAcquiredBufferCount(Fps(90), 30ms));
-    EXPECT_EQ(3, mFlinger.calculateMaxAcquiredBufferCount(Fps(120), 30ms));
+    EXPECT_EQ(1, mFlinger.calculateMaxAcquiredBufferCount(60_Hz, 30ms));
+    EXPECT_EQ(2, mFlinger.calculateMaxAcquiredBufferCount(90_Hz, 30ms));
+    EXPECT_EQ(3, mFlinger.calculateMaxAcquiredBufferCount(120_Hz, 30ms));
 
-    EXPECT_EQ(2, mFlinger.calculateMaxAcquiredBufferCount(Fps(60), 40ms));
+    EXPECT_EQ(2, mFlinger.calculateMaxAcquiredBufferCount(60_Hz, 40ms));
 
-    EXPECT_EQ(1, mFlinger.calculateMaxAcquiredBufferCount(Fps(60), 10ms));
+    EXPECT_EQ(1, mFlinger.calculateMaxAcquiredBufferCount(60_Hz, 10ms));
 }
 
 MATCHER(Is120Hz, "") {
-    return arg.getFps().equalsWithMargin(Fps(120.f));
+    return isApproxEqual(arg->getFps(), 120_Hz);
 }
 
 TEST_F(SchedulerTest, chooseRefreshRateForContentSelectsMaxRefreshRate) {
     mScheduler->setRefreshRateConfigs(
-            std::make_shared<scheduler::RefreshRateConfigs>(DisplayModes{mode60, mode120},
-                                                            mode60->getId()));
+            std::make_shared<RefreshRateConfigs>(makeModes(kMode60, kMode120), kMode60->getId()));
 
-    sp<mock::MockLayer> layer = sp<mock::MockLayer>::make(mFlinger.flinger());
+    const sp<MockLayer> layer = sp<MockLayer>::make(mFlinger.flinger());
+    EXPECT_CALL(*layer, isVisible()).WillOnce(Return(true));
 
     mScheduler->recordLayerHistory(layer.get(), 0, LayerHistory::LayerUpdateType::Buffer);
 
@@ -245,8 +232,12 @@
     constexpr uint32_t kDisplayArea = 999'999;
     mScheduler->onActiveDisplayAreaChanged(kDisplayArea);
 
-    EXPECT_CALL(mSchedulerCallback, changeRefreshRate(Is120Hz(), _)).Times(1);
+    EXPECT_CALL(mSchedulerCallback, requestDisplayMode(Is120Hz(), _)).Times(1);
+    mScheduler->chooseRefreshRateForContent();
+
+    // No-op if layer requirements have not changed.
+    EXPECT_CALL(mSchedulerCallback, requestDisplayMode(_, _)).Times(0);
     mScheduler->chooseRefreshRateForContent();
 }
 
-} // namespace android
+} // namespace android::scheduler
diff --git a/services/surfaceflinger/tests/unittests/SchedulerUtilsTest.cpp b/services/surfaceflinger/tests/unittests/SchedulerUtilsTest.cpp
deleted file mode 100644
index 5f6a715..0000000
--- a/services/surfaceflinger/tests/unittests/SchedulerUtilsTest.cpp
+++ /dev/null
@@ -1,137 +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.
- */
-
-// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wconversion"
-
-#undef LOG_TAG
-#define LOG_TAG "SchedulerUnittests"
-
-#include <gmock/gmock.h>
-#include <gtest/gtest.h>
-
-#include <array>
-
-#include "Scheduler/SchedulerUtils.h"
-
-namespace android {
-namespace scheduler {
-
-class SchedulerUtilsTest : public testing::Test {
-public:
-    SchedulerUtilsTest() = default;
-    ~SchedulerUtilsTest() override = default;
-};
-
-namespace {
-TEST_F(SchedulerUtilsTest, calculate_mean) {
-    std::array<int64_t, 30> testArray{};
-    // Calling the function on empty array returns 0.
-    EXPECT_EQ(0, calculate_mean(testArray));
-
-    testArray[0] = 33;
-    EXPECT_EQ(1, calculate_mean(testArray));
-    testArray[1] = 33;
-    testArray[2] = 33;
-    EXPECT_EQ(3, calculate_mean(testArray));
-    testArray[3] = 42;
-    EXPECT_EQ(4, calculate_mean(testArray));
-    testArray[4] = 33;
-    EXPECT_EQ(5, calculate_mean(testArray));
-    testArray[5] = 42;
-    EXPECT_EQ(7, calculate_mean(testArray));
-    for (int i = 6; i < 30; i++) {
-        testArray[i] = 33;
-    }
-    EXPECT_EQ(33, calculate_mean(testArray));
-}
-
-TEST_F(SchedulerUtilsTest, calculate_median) {
-    std::vector<int64_t> testVector;
-    // Calling the function on empty vector returns 0.
-    EXPECT_EQ(0, calculate_median(&testVector));
-
-    testVector.push_back(33);
-    EXPECT_EQ(33, calculate_median(&testVector));
-    testVector.push_back(33);
-    testVector.push_back(33);
-    EXPECT_EQ(33, calculate_median(&testVector));
-    testVector.push_back(42);
-    EXPECT_EQ(33, calculate_median(&testVector));
-    testVector.push_back(33);
-    EXPECT_EQ(33, calculate_median(&testVector));
-    testVector.push_back(42);
-    EXPECT_EQ(33, calculate_median(&testVector));
-    testVector.push_back(42);
-    EXPECT_EQ(33, calculate_median(&testVector));
-    testVector.push_back(42);
-    EXPECT_EQ(42, calculate_median(&testVector));
-    testVector.push_back(60);
-    EXPECT_EQ(42, calculate_median(&testVector));
-    testVector.push_back(60);
-    EXPECT_EQ(42, calculate_median(&testVector));
-    testVector.push_back(33);
-    EXPECT_EQ(42, calculate_median(&testVector));
-    testVector.push_back(33);
-    EXPECT_EQ(42, calculate_median(&testVector));
-    testVector.push_back(33);
-    EXPECT_EQ(33, calculate_median(&testVector));
-}
-
-TEST_F(SchedulerUtilsTest, calculate_mode) {
-    std::vector<int64_t> testVector;
-    // Calling the function on empty vector returns 0.
-    EXPECT_EQ(0, calculate_mode(testVector));
-
-    testVector.push_back(33);
-    EXPECT_EQ(33, calculate_mode(testVector));
-    testVector.push_back(33);
-    testVector.push_back(33);
-    EXPECT_EQ(33, calculate_mode(testVector));
-    testVector.push_back(42);
-    EXPECT_EQ(33, calculate_mode(testVector));
-    testVector.push_back(33);
-    EXPECT_EQ(33, calculate_mode(testVector));
-    testVector.push_back(42);
-    EXPECT_EQ(33, calculate_mode(testVector));
-    testVector.push_back(42);
-    EXPECT_EQ(33, calculate_mode(testVector));
-    testVector.push_back(42);
-    EXPECT_EQ(33, calculate_mode(testVector));
-    testVector.push_back(60);
-    EXPECT_EQ(33, calculate_mode(testVector));
-    testVector.push_back(60);
-    EXPECT_EQ(33, calculate_mode(testVector));
-    testVector.push_back(33);
-    // 5 occurences of 33.
-    EXPECT_EQ(33, calculate_mode(testVector));
-    testVector.push_back(42);
-    // 5 occurences of 33, 5 occurences of 42. We choose the first one.
-    EXPECT_EQ(33, calculate_mode(testVector));
-    testVector.push_back(42);
-    // 5 occurences of 33, 6 occurences of 42.
-    EXPECT_EQ(42, calculate_mode(testVector));
-    testVector.push_back(42);
-    // 5 occurences of 33, 7 occurences of 42.
-    EXPECT_EQ(42, calculate_mode(testVector));
-}
-
-} // namespace
-} // namespace scheduler
-} // namespace android
-// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic pop // ignored "-Wconversion"
diff --git a/services/surfaceflinger/tests/unittests/SetFrameRateTest.cpp b/services/surfaceflinger/tests/unittests/SetFrameRateTest.cpp
index a4e9d20..825f145 100644
--- a/services/surfaceflinger/tests/unittests/SetFrameRateTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SetFrameRateTest.cpp
@@ -24,108 +24,48 @@
 // TODO(b/129481165): remove the #pragma below and fix conversion issues
 #pragma clang diagnostic push
 #pragma clang diagnostic ignored "-Wconversion"
-#include "BufferQueueLayer.h"
 #include "BufferStateLayer.h"
 #include "EffectLayer.h"
 #include "Layer.h"
 // TODO(b/129481165): remove the #pragma below and fix conversion issues
 #pragma clang diagnostic pop // ignored "-Wconversion"
+#include "FpsOps.h"
+#include "LayerTestUtils.h"
 #include "TestableSurfaceFlinger.h"
 #include "mock/DisplayHardware/MockComposer.h"
-#include "mock/MockEventThread.h"
-#include "mock/MockMessageQueue.h"
 #include "mock/MockVsyncController.h"
 
 namespace android {
 
-using testing::_;
 using testing::DoAll;
 using testing::Mock;
-using testing::Return;
 using testing::SetArgPointee;
 
 using android::Hwc2::IComposer;
 using android::Hwc2::IComposerClient;
 
-using FakeHwcDisplayInjector = TestableSurfaceFlinger::FakeHwcDisplayInjector;
+using scheduler::LayerHistory;
 
 using FrameRate = Layer::FrameRate;
 using FrameRateCompatibility = Layer::FrameRateCompatibility;
 
-class LayerFactory {
-public:
-    virtual ~LayerFactory() = default;
-
-    virtual std::string name() = 0;
-    virtual sp<Layer> createLayer(TestableSurfaceFlinger& flinger) = 0;
-
-protected:
-    static constexpr uint32_t WIDTH = 100;
-    static constexpr uint32_t HEIGHT = 100;
-    static constexpr uint32_t LAYER_FLAGS = 0;
-};
-
-class BufferQueueLayerFactory : public LayerFactory {
-public:
-    std::string name() override { return "BufferQueueLayer"; }
-    sp<Layer> createLayer(TestableSurfaceFlinger& flinger) override {
-        sp<Client> client;
-        LayerCreationArgs args(flinger.flinger(), client, "buffer-queue-layer", WIDTH, HEIGHT,
-                               LAYER_FLAGS, LayerMetadata());
-        return new BufferQueueLayer(args);
-    }
-};
-
-class BufferStateLayerFactory : public LayerFactory {
-public:
-    std::string name() override { return "BufferStateLayer"; }
-    sp<Layer> createLayer(TestableSurfaceFlinger& flinger) override {
-        sp<Client> client;
-        LayerCreationArgs args(flinger.flinger(), client, "buffer-state-layer", WIDTH, HEIGHT,
-                               LAYER_FLAGS, LayerMetadata());
-        return new BufferStateLayer(args);
-    }
-};
-
-class EffectLayerFactory : public LayerFactory {
-public:
-    std::string name() override { return "EffectLayer"; }
-    sp<Layer> createLayer(TestableSurfaceFlinger& flinger) override {
-        sp<Client> client;
-        LayerCreationArgs args(flinger.flinger(), client, "color-layer", WIDTH, HEIGHT, LAYER_FLAGS,
-                               LayerMetadata());
-        return new EffectLayer(args);
-    }
-};
-
-std::string PrintToStringParamName(
-        const ::testing::TestParamInfo<std::shared_ptr<LayerFactory>>& info) {
-    return info.param->name();
-}
-
 /**
  * This class tests the behaviour of Layer::SetFrameRate and Layer::GetFrameRate
  */
-class SetFrameRateTest : public ::testing::TestWithParam<std::shared_ptr<LayerFactory>> {
+class SetFrameRateTest : public BaseLayerTest {
 protected:
-    const FrameRate FRAME_RATE_VOTE1 = FrameRate(Fps(67.f), FrameRateCompatibility::Default);
-    const FrameRate FRAME_RATE_VOTE2 =
-            FrameRate(Fps(14.f), FrameRateCompatibility::ExactOrMultiple);
-    const FrameRate FRAME_RATE_VOTE3 = FrameRate(Fps(99.f), FrameRateCompatibility::NoVote);
-    const FrameRate FRAME_RATE_TREE = FrameRate(Fps(0.f), FrameRateCompatibility::NoVote);
-    const FrameRate FRAME_RATE_NO_VOTE = FrameRate(Fps(0.f), FrameRateCompatibility::Default);
+    const FrameRate FRAME_RATE_VOTE1 = FrameRate(67_Hz, FrameRateCompatibility::Default);
+    const FrameRate FRAME_RATE_VOTE2 = FrameRate(14_Hz, FrameRateCompatibility::ExactOrMultiple);
+    const FrameRate FRAME_RATE_VOTE3 = FrameRate(99_Hz, FrameRateCompatibility::NoVote);
+    const FrameRate FRAME_RATE_TREE = FrameRate(Fps(), FrameRateCompatibility::NoVote);
+    const FrameRate FRAME_RATE_NO_VOTE = FrameRate(Fps(), FrameRateCompatibility::Default);
 
     SetFrameRateTest();
 
-    void setupScheduler();
-
     void addChild(sp<Layer> layer, sp<Layer> child);
     void removeChild(sp<Layer> layer, sp<Layer> child);
     void commitTransaction();
 
-    TestableSurfaceFlinger mFlinger;
-    mock::MessageQueue* mMessageQueue = new mock::MessageQueue();
-
     std::vector<sp<Layer>> mLayers;
 };
 
@@ -134,12 +74,7 @@
             ::testing::UnitTest::GetInstance()->current_test_info();
     ALOGD("**** Setting up for %s.%s\n", test_info->test_case_name(), test_info->name());
 
-    mFlinger.mutableUseFrameRateApi() = true;
-
-    setupScheduler();
-
     mFlinger.setupComposer(std::make_unique<Hwc2::mock::Composer>());
-    mFlinger.mutableEventQueue().reset(mMessageQueue);
 }
 
 void SetFrameRateTest::addChild(sp<Layer> layer, sp<Layer> child) {
@@ -157,38 +92,10 @@
     }
 }
 
-void SetFrameRateTest::setupScheduler() {
-    auto eventThread = std::make_unique<mock::EventThread>();
-    auto sfEventThread = std::make_unique<mock::EventThread>();
-
-    EXPECT_CALL(*eventThread, registerDisplayEventConnection(_));
-    EXPECT_CALL(*eventThread, createEventConnection(_, _))
-            .WillOnce(Return(new EventThreadConnection(eventThread.get(), /*callingUid=*/0,
-                                                       ResyncCallback())));
-
-    EXPECT_CALL(*sfEventThread, registerDisplayEventConnection(_));
-    EXPECT_CALL(*sfEventThread, createEventConnection(_, _))
-            .WillOnce(Return(new EventThreadConnection(sfEventThread.get(), /*callingUid=*/0,
-                                                       ResyncCallback())));
-
-    auto vsyncController = std::make_unique<mock::VsyncController>();
-    auto vsyncTracker = std::make_unique<mock::VSyncTracker>();
-
-    EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0));
-    EXPECT_CALL(*vsyncTracker, currentPeriod())
-            .WillRepeatedly(Return(FakeHwcDisplayInjector::DEFAULT_VSYNC_PERIOD));
-    EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0));
-    mFlinger.setupScheduler(std::move(vsyncController), std::move(vsyncTracker),
-                            std::move(eventThread), std::move(sfEventThread), /*callback*/ nullptr,
-                            /*hasMultipleModes*/ true);
-}
-
 namespace {
-/* ------------------------------------------------------------------------
- * Test cases
- */
+
 TEST_P(SetFrameRateTest, SetAndGet) {
-    EXPECT_CALL(*mMessageQueue, invalidate()).Times(1);
+    EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(1);
 
     const auto& layerFactory = GetParam();
 
@@ -199,7 +106,7 @@
 }
 
 TEST_P(SetFrameRateTest, SetAndGetParent) {
-    EXPECT_CALL(*mMessageQueue, invalidate()).Times(1);
+    EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(1);
 
     const auto& layerFactory = GetParam();
 
@@ -224,7 +131,7 @@
 }
 
 TEST_P(SetFrameRateTest, SetAndGetParentAllVote) {
-    EXPECT_CALL(*mMessageQueue, invalidate()).Times(1);
+    EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(1);
 
     const auto& layerFactory = GetParam();
 
@@ -263,7 +170,7 @@
 }
 
 TEST_P(SetFrameRateTest, SetAndGetChild) {
-    EXPECT_CALL(*mMessageQueue, invalidate()).Times(1);
+    EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(1);
 
     const auto& layerFactory = GetParam();
 
@@ -288,7 +195,7 @@
 }
 
 TEST_P(SetFrameRateTest, SetAndGetChildAllVote) {
-    EXPECT_CALL(*mMessageQueue, invalidate()).Times(1);
+    EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(1);
 
     const auto& layerFactory = GetParam();
 
@@ -327,7 +234,7 @@
 }
 
 TEST_P(SetFrameRateTest, SetAndGetChildAddAfterVote) {
-    EXPECT_CALL(*mMessageQueue, invalidate()).Times(1);
+    EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(1);
 
     const auto& layerFactory = GetParam();
 
@@ -357,7 +264,7 @@
 }
 
 TEST_P(SetFrameRateTest, SetAndGetChildRemoveAfterVote) {
-    EXPECT_CALL(*mMessageQueue, invalidate()).Times(1);
+    EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(1);
 
     const auto& layerFactory = GetParam();
 
@@ -388,7 +295,7 @@
 }
 
 TEST_P(SetFrameRateTest, SetAndGetParentNotInTree) {
-    EXPECT_CALL(*mMessageQueue, invalidate()).Times(1);
+    EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(1);
 
     const auto& layerFactory = GetParam();
 
@@ -417,8 +324,7 @@
 }
 
 INSTANTIATE_TEST_SUITE_P(PerLayerType, SetFrameRateTest,
-                         testing::Values(std::make_shared<BufferQueueLayerFactory>(),
-                                         std::make_shared<BufferStateLayerFactory>(),
+                         testing::Values(std::make_shared<BufferStateLayerFactory>(),
                                          std::make_shared<EffectLayerFactory>()),
                          PrintToStringParamName);
 
@@ -471,24 +377,20 @@
     parent->setFrameRate(FRAME_RATE_VOTE1);
     commitTransaction();
 
-    mFlinger.mutableScheduler()
-            .mutableLayerHistory()
-            ->record(parent.get(), 0, 0, LayerHistory::LayerUpdateType::Buffer);
-    mFlinger.mutableScheduler()
-            .mutableLayerHistory()
-            ->record(child.get(), 0, 0, LayerHistory::LayerUpdateType::Buffer);
+    auto& history = mFlinger.mutableScheduler().mutableLayerHistory();
+    history.record(parent.get(), 0, 0, LayerHistory::LayerUpdateType::Buffer);
+    history.record(child.get(), 0, 0, LayerHistory::LayerUpdateType::Buffer);
 
-    const auto layerHistorySummary =
-            mFlinger.mutableScheduler()
-                    .mutableLayerHistory()
-                    ->summarize(*mFlinger.mutableScheduler().refreshRateConfigs(), 0);
-    ASSERT_EQ(2u, layerHistorySummary.size());
-    EXPECT_TRUE(FRAME_RATE_VOTE1.rate.equalsWithMargin(layerHistorySummary[0].desiredRefreshRate));
-    EXPECT_TRUE(FRAME_RATE_VOTE1.rate.equalsWithMargin(layerHistorySummary[1].desiredRefreshRate));
+    const auto configs = mFlinger.mutableScheduler().refreshRateConfigs();
+    const auto summary = history.summarize(*configs, 0);
+
+    ASSERT_EQ(2u, summary.size());
+    EXPECT_EQ(FRAME_RATE_VOTE1.rate, summary[0].desiredRefreshRate);
+    EXPECT_EQ(FRAME_RATE_VOTE1.rate, summary[1].desiredRefreshRate);
 }
 
 TEST_P(SetFrameRateTest, addChildForParentWithTreeVote) {
-    EXPECT_CALL(*mMessageQueue, invalidate()).Times(1);
+    EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(1);
 
     const auto& layerFactory = GetParam();
 
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_CreateDisplayTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_CreateDisplayTest.cpp
index 2362a31..f7d34ac 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_CreateDisplayTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_CreateDisplayTest.cpp
@@ -51,8 +51,8 @@
     // --------------------------------------------------------------------
     // Cleanup conditions
 
-    // Destroying the display invalidates the display state.
-    EXPECT_CALL(*mMessageQueue, invalidate()).Times(1);
+    // Creating the display commits a display transaction.
+    EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(1);
 }
 
 TEST_F(CreateDisplayTest, createDisplaySetsCurrentStateForSecureDisplay) {
@@ -86,9 +86,9 @@
     // --------------------------------------------------------------------
     // Cleanup conditions
 
-    // Destroying the display invalidates the display state.
-    EXPECT_CALL(*mMessageQueue, invalidate()).Times(1);
+    // Creating the display commits a display transaction.
+    EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(1);
 }
 
 } // namespace
-} // namespace android
\ No newline at end of file
+} // namespace android
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DestroyDisplayTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DestroyDisplayTest.cpp
index e2be074..c7e61c9 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DestroyDisplayTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DestroyDisplayTest.cpp
@@ -40,8 +40,8 @@
     // The call should notify the interceptor that a display was created.
     EXPECT_CALL(*mSurfaceInterceptor, saveDisplayDeletion(_)).Times(1);
 
-    // Destroying the display invalidates the display state.
-    EXPECT_CALL(*mMessageQueue, invalidate()).Times(1);
+    // Destroying the display commits a display transaction.
+    EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(1);
 
     // --------------------------------------------------------------------
     // Invocation
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp
new file mode 100644
index 0000000..32d57b5
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp
@@ -0,0 +1,270 @@
+/*
+ * 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.
+ */
+
+#include "mock/MockEventThread.h"
+#undef LOG_TAG
+#define LOG_TAG "LibSurfaceFlingerUnittests"
+
+#include "DisplayTransactionTestHelpers.h"
+
+#include <scheduler/Fps.h>
+
+namespace android {
+namespace {
+
+using android::hardware::graphics::composer::V2_4::Error;
+using android::hardware::graphics::composer::V2_4::VsyncPeriodChangeTimeline;
+
+class DisplayModeSwitchingTest : public DisplayTransactionTest {
+public:
+    void SetUp() override {
+        injectFakeBufferQueueFactory();
+        injectFakeNativeWindowSurfaceFactory();
+
+        PrimaryDisplayVariant::setupHwcHotplugCallExpectations(this);
+        PrimaryDisplayVariant::setupFramebufferConsumerBufferQueueCallExpectations(this);
+        PrimaryDisplayVariant::setupFramebufferProducerBufferQueueCallExpectations(this);
+        PrimaryDisplayVariant::setupNativeWindowSurfaceCreationCallExpectations(this);
+        PrimaryDisplayVariant::setupHwcGetActiveConfigCallExpectations(this);
+
+        mFlinger.onComposerHalHotplug(PrimaryDisplayVariant::HWC_DISPLAY_ID, Connection::CONNECTED);
+
+        {
+            DisplayModes modes = makeModes(kMode60, kMode90, kMode120, kMode90_4K);
+            auto configs = std::make_shared<scheduler::RefreshRateConfigs>(modes, kModeId60);
+
+            mDisplay = PrimaryDisplayVariant::makeFakeExistingDisplayInjector(this)
+                               .setDisplayModes(std::move(modes), kModeId60, std::move(configs))
+                               .inject();
+        }
+
+        setupScheduler(mDisplay->holdRefreshRateConfigs());
+
+        // isVsyncPeriodSwitchSupported should return true, otherwise the SF's HWC proxy
+        // will call setActiveConfig instead of setActiveConfigWithConstraints.
+        ON_CALL(*mComposer, isSupported(Hwc2::Composer::OptionalFeature::RefreshRateSwitching))
+                .WillByDefault(Return(true));
+    }
+
+protected:
+    void setupScheduler(std::shared_ptr<scheduler::RefreshRateConfigs>);
+    void testChangeRefreshRate(bool isDisplayActive, bool isRefreshRequired);
+
+    sp<DisplayDevice> mDisplay;
+    mock::EventThread* mAppEventThread;
+
+    static constexpr DisplayModeId kModeId60{0};
+    static constexpr DisplayModeId kModeId90{1};
+    static constexpr DisplayModeId kModeId120{2};
+    static constexpr DisplayModeId kModeId90_4K{3};
+
+    static inline const DisplayModePtr kMode60 = createDisplayMode(kModeId60, 60_Hz, 0);
+    static inline const DisplayModePtr kMode90 = createDisplayMode(kModeId90, 90_Hz, 1);
+    static inline const DisplayModePtr kMode120 = createDisplayMode(kModeId120, 120_Hz, 2);
+
+    static constexpr ui::Size kResolution4K{3840, 2160};
+    static inline const DisplayModePtr kMode90_4K =
+            createDisplayMode(kModeId90_4K, 90_Hz, 3, kResolution4K);
+};
+
+void DisplayModeSwitchingTest::setupScheduler(
+        std::shared_ptr<scheduler::RefreshRateConfigs> configs) {
+    auto eventThread = std::make_unique<mock::EventThread>();
+    mAppEventThread = eventThread.get();
+    auto sfEventThread = std::make_unique<mock::EventThread>();
+
+    EXPECT_CALL(*eventThread, registerDisplayEventConnection(_));
+    EXPECT_CALL(*eventThread, createEventConnection(_, _))
+            .WillOnce(Return(new EventThreadConnection(eventThread.get(), /*callingUid=*/0,
+                                                       ResyncCallback())));
+
+    EXPECT_CALL(*sfEventThread, registerDisplayEventConnection(_));
+    EXPECT_CALL(*sfEventThread, createEventConnection(_, _))
+            .WillOnce(Return(new EventThreadConnection(sfEventThread.get(), /*callingUid=*/0,
+                                                       ResyncCallback())));
+
+    auto vsyncController = std::make_unique<mock::VsyncController>();
+    auto vsyncTracker = std::make_unique<mock::VSyncTracker>();
+
+    EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0));
+    EXPECT_CALL(*vsyncTracker, currentPeriod())
+            .WillRepeatedly(
+                    Return(TestableSurfaceFlinger::FakeHwcDisplayInjector::DEFAULT_VSYNC_PERIOD));
+    EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0));
+    mFlinger.setupScheduler(std::move(vsyncController), std::move(vsyncTracker),
+                            std::move(eventThread), std::move(sfEventThread),
+                            TestableSurfaceFlinger::SchedulerCallbackImpl::kNoOp,
+                            std::move(configs));
+}
+
+TEST_F(DisplayModeSwitchingTest, changeRefreshRate_OnActiveDisplay_WithRefreshRequired) {
+    ASSERT_FALSE(mDisplay->getDesiredActiveMode().has_value());
+    ASSERT_EQ(mDisplay->getActiveMode()->getId(), kModeId60);
+
+    mFlinger.onActiveDisplayChanged(mDisplay);
+
+    mFlinger.setDesiredDisplayModeSpecs(mDisplay->getDisplayToken().promote(), kModeId90.value(),
+                                        false, 0.f, 120.f, 0.f, 120.f);
+
+    ASSERT_TRUE(mDisplay->getDesiredActiveMode().has_value());
+    ASSERT_EQ(mDisplay->getDesiredActiveMode()->mode->getId(), kModeId90);
+    ASSERT_EQ(mDisplay->getActiveMode()->getId(), kModeId60);
+
+    // Verify that next commit will call setActiveConfigWithConstraints in HWC
+    const VsyncPeriodChangeTimeline timeline{.refreshRequired = true};
+    EXPECT_CALL(*mComposer,
+                setActiveConfigWithConstraints(PrimaryDisplayVariant::HWC_DISPLAY_ID,
+                                               hal::HWConfigId(kModeId90.value()), _, _))
+            .WillOnce(DoAll(SetArgPointee<3>(timeline), Return(Error::NONE)));
+
+    mFlinger.commit();
+
+    Mock::VerifyAndClearExpectations(mComposer);
+    ASSERT_TRUE(mDisplay->getDesiredActiveMode().has_value());
+    ASSERT_EQ(mDisplay->getActiveMode()->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));
+    mFlinger.commit();
+    Mock::VerifyAndClearExpectations(mAppEventThread);
+
+    ASSERT_FALSE(mDisplay->getDesiredActiveMode().has_value());
+    ASSERT_EQ(mDisplay->getActiveMode()->getId(), kModeId90);
+}
+
+TEST_F(DisplayModeSwitchingTest, changeRefreshRate_OnActiveDisplay_WithoutRefreshRequired) {
+    ASSERT_FALSE(mDisplay->getDesiredActiveMode().has_value());
+
+    mFlinger.onActiveDisplayChanged(mDisplay);
+
+    mFlinger.setDesiredDisplayModeSpecs(mDisplay->getDisplayToken().promote(), kModeId90.value(),
+                                        true, 0.f, 120.f, 0.f, 120.f);
+
+    ASSERT_TRUE(mDisplay->getDesiredActiveMode().has_value());
+    ASSERT_EQ(mDisplay->getDesiredActiveMode()->mode->getId(), kModeId90);
+    ASSERT_EQ(mDisplay->getActiveMode()->getId(), kModeId60);
+
+    // Verify that next commit will call setActiveConfigWithConstraints in HWC
+    // and complete the mode change.
+    const VsyncPeriodChangeTimeline timeline{.refreshRequired = false};
+    EXPECT_CALL(*mComposer,
+                setActiveConfigWithConstraints(PrimaryDisplayVariant::HWC_DISPLAY_ID,
+                                               hal::HWConfigId(kModeId90.value()), _, _))
+            .WillOnce(DoAll(SetArgPointee<3>(timeline), Return(Error::NONE)));
+
+    EXPECT_CALL(*mAppEventThread, onModeChanged(kMode90));
+
+    mFlinger.commit();
+
+    ASSERT_FALSE(mDisplay->getDesiredActiveMode().has_value());
+    ASSERT_EQ(mDisplay->getActiveMode()->getId(), kModeId90);
+}
+
+TEST_F(DisplayModeSwitchingTest, twoConsecutiveSetDesiredDisplayModeSpecs) {
+    // Test that if we call setDesiredDisplayModeSpecs while a previous mode change
+    // is still being processed the later call will be respected.
+
+    ASSERT_FALSE(mDisplay->getDesiredActiveMode().has_value());
+    ASSERT_EQ(mDisplay->getActiveMode()->getId(), kModeId60);
+
+    mFlinger.onActiveDisplayChanged(mDisplay);
+
+    mFlinger.setDesiredDisplayModeSpecs(mDisplay->getDisplayToken().promote(), kModeId90.value(),
+                                        false, 0.f, 120.f, 0.f, 120.f);
+
+    const VsyncPeriodChangeTimeline timeline{.refreshRequired = true};
+    EXPECT_CALL(*mComposer,
+                setActiveConfigWithConstraints(PrimaryDisplayVariant::HWC_DISPLAY_ID,
+                                               hal::HWConfigId(kModeId90.value()), _, _))
+            .WillOnce(DoAll(SetArgPointee<3>(timeline), Return(Error::NONE)));
+
+    mFlinger.commit();
+
+    mFlinger.setDesiredDisplayModeSpecs(mDisplay->getDisplayToken().promote(), kModeId120.value(),
+                                        false, 0.f, 180.f, 0.f, 180.f);
+
+    ASSERT_TRUE(mDisplay->getDesiredActiveMode().has_value());
+    ASSERT_EQ(mDisplay->getDesiredActiveMode()->mode->getId(), kModeId120);
+
+    EXPECT_CALL(*mComposer,
+                setActiveConfigWithConstraints(PrimaryDisplayVariant::HWC_DISPLAY_ID,
+                                               hal::HWConfigId(kModeId120.value()), _, _))
+            .WillOnce(DoAll(SetArgPointee<3>(timeline), Return(Error::NONE)));
+
+    mFlinger.commit();
+
+    ASSERT_TRUE(mDisplay->getDesiredActiveMode().has_value());
+    ASSERT_EQ(mDisplay->getDesiredActiveMode()->mode->getId(), kModeId120);
+
+    mFlinger.commit();
+
+    ASSERT_FALSE(mDisplay->getDesiredActiveMode().has_value());
+    ASSERT_EQ(mDisplay->getActiveMode()->getId(), kModeId120);
+}
+
+TEST_F(DisplayModeSwitchingTest, changeResolution_OnActiveDisplay_WithoutRefreshRequired) {
+    ASSERT_FALSE(mDisplay->getDesiredActiveMode().has_value());
+    ASSERT_EQ(mDisplay->getActiveMode()->getId(), kModeId60);
+
+    mFlinger.onActiveDisplayChanged(mDisplay);
+
+    mFlinger.setDesiredDisplayModeSpecs(mDisplay->getDisplayToken().promote(), kModeId90_4K.value(),
+                                        false, 0.f, 120.f, 0.f, 120.f);
+
+    ASSERT_TRUE(mDisplay->getDesiredActiveMode().has_value());
+    ASSERT_EQ(mDisplay->getDesiredActiveMode()->mode->getId(), kModeId90_4K);
+    ASSERT_EQ(mDisplay->getActiveMode()->getId(), kModeId60);
+
+    // Verify that next commit will call setActiveConfigWithConstraints in HWC
+    // and complete the mode change.
+    const VsyncPeriodChangeTimeline timeline{.refreshRequired = false};
+    EXPECT_CALL(*mComposer,
+                setActiveConfigWithConstraints(PrimaryDisplayVariant::HWC_DISPLAY_ID,
+                                               hal::HWConfigId(kModeId90_4K.value()), _, _))
+            .WillOnce(DoAll(SetArgPointee<3>(timeline), Return(Error::NONE)));
+
+    EXPECT_CALL(*mAppEventThread, onHotplugReceived(mDisplay->getPhysicalId(), true));
+
+    // Misc expecations. We don't need to enforce these method calls, but since the helper methods
+    // already set expectations we should add new ones here, otherwise the test will fail.
+    EXPECT_CALL(*mConsumer,
+                setDefaultBufferSize(static_cast<uint32_t>(kResolution4K.getWidth()),
+                                     static_cast<uint32_t>(kResolution4K.getHeight())))
+            .WillOnce(Return(NO_ERROR));
+    EXPECT_CALL(*mConsumer, consumerConnect(_, false)).WillOnce(Return(NO_ERROR));
+    EXPECT_CALL(*mComposer, setClientTargetSlotCount(_)).WillOnce(Return(hal::Error::NONE));
+
+    // Create a new native surface to be used by the recreated display.
+    mNativeWindowSurface = nullptr;
+    injectFakeNativeWindowSurfaceFactory();
+    PrimaryDisplayVariant::setupNativeWindowSurfaceCreationCallExpectations(this);
+
+    const auto displayToken = mDisplay->getDisplayToken().promote();
+
+    mFlinger.commit();
+
+    // The DisplayDevice will be destroyed and recreated,
+    // so we need to update with the new instance.
+    mDisplay = mFlinger.getDisplay(displayToken);
+
+    ASSERT_FALSE(mDisplay->getDesiredActiveMode().has_value());
+    ASSERT_EQ(mDisplay->getActiveMode()->getId(), kModeId90_4K);
+}
+
+} // namespace
+} // namespace android
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_HandleTransactionLockedTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayTransactionCommitTest.cpp
similarity index 74%
rename from services/surfaceflinger/tests/unittests/SurfaceFlinger_HandleTransactionLockedTest.cpp
rename to services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayTransactionCommitTest.cpp
index b713334..6959ee3 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_HandleTransactionLockedTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayTransactionCommitTest.cpp
@@ -22,8 +22,7 @@
 namespace android {
 namespace {
 
-class HandleTransactionLockedTest : public DisplayTransactionTest {
-public:
+struct DisplayTransactionCommitTest : DisplayTransactionTest {
     template <typename Case>
     void setupCommonPreconditions();
 
@@ -55,7 +54,7 @@
 };
 
 template <typename Case>
-void HandleTransactionLockedTest::setupCommonPreconditions() {
+void DisplayTransactionCommitTest::setupCommonPreconditions() {
     // Wide color displays support is configured appropriately
     Case::WideColorSupport::injectConfigChange(this);
 
@@ -68,7 +67,7 @@
 }
 
 template <typename Case, bool connected>
-void HandleTransactionLockedTest::expectHotplugReceived(mock::EventThread* eventThread) {
+void DisplayTransactionCommitTest::expectHotplugReceived(mock::EventThread* eventThread) {
     const auto convert = [](auto physicalDisplayId) {
         return std::make_optional(DisplayId{physicalDisplayId});
     };
@@ -79,7 +78,7 @@
 }
 
 template <typename Case>
-void HandleTransactionLockedTest::setupCommonCallExpectationsForConnectProcessing() {
+void DisplayTransactionCommitTest::setupCommonCallExpectationsForConnectProcessing() {
     Case::Display::setupHwcHotplugCallExpectations(this);
 
     Case::Display::setupFramebufferConsumerBufferQueueCallExpectations(this);
@@ -97,7 +96,7 @@
 }
 
 template <typename Case>
-void HandleTransactionLockedTest::setupCommonCallExpectationsForDisconnectProcessing() {
+void DisplayTransactionCommitTest::setupCommonCallExpectationsForDisconnectProcessing() {
     EXPECT_CALL(*mSurfaceInterceptor, saveDisplayDeletion(_)).Times(1);
 
     expectHotplugReceived<Case, false>(mEventThread);
@@ -105,7 +104,7 @@
 }
 
 template <typename Case>
-void HandleTransactionLockedTest::verifyDisplayIsConnected(const sp<IBinder>& displayToken) {
+void DisplayTransactionCommitTest::verifyDisplayIsConnected(const sp<IBinder>& displayToken) {
     // The display device should have been set up in the list of displays.
     ASSERT_TRUE(hasDisplayDevice(displayToken));
     const auto& device = getDisplayDevice(displayToken);
@@ -137,7 +136,7 @@
 }
 
 template <typename Case>
-void HandleTransactionLockedTest::verifyPhysicalDisplayIsConnected() {
+void DisplayTransactionCommitTest::verifyPhysicalDisplayIsConnected() {
     // HWComposer should have an entry for the display
     EXPECT_TRUE(hasPhysicalHwcDisplay(Case::Display::HWC_DISPLAY_ID));
 
@@ -150,14 +149,14 @@
     verifyDisplayIsConnected<Case>(displayToken);
 }
 
-void HandleTransactionLockedTest::verifyDisplayIsNotConnected(const sp<IBinder>& displayToken) {
+void DisplayTransactionCommitTest::verifyDisplayIsNotConnected(const sp<IBinder>& displayToken) {
     EXPECT_FALSE(hasDisplayDevice(displayToken));
     EXPECT_FALSE(hasCurrentDisplayState(displayToken));
     EXPECT_FALSE(hasDrawingDisplayState(displayToken));
 }
 
 template <typename Case>
-void HandleTransactionLockedTest::processesHotplugConnectCommon() {
+void DisplayTransactionCommitTest::processesHotplugConnectCommon() {
     // --------------------------------------------------------------------
     // Preconditions
 
@@ -174,7 +173,7 @@
     // --------------------------------------------------------------------
     // Invocation
 
-    mFlinger.handleTransactionLocked(eDisplayTransactionNeeded);
+    mFlinger.commitTransactionsLocked(eDisplayTransactionNeeded);
 
     // --------------------------------------------------------------------
     // Postconditions
@@ -191,7 +190,7 @@
 }
 
 template <typename Case>
-void HandleTransactionLockedTest::ignoresHotplugConnectCommon() {
+void DisplayTransactionCommitTest::ignoresHotplugConnectCommon() {
     // --------------------------------------------------------------------
     // Preconditions
 
@@ -203,7 +202,7 @@
     // --------------------------------------------------------------------
     // Invocation
 
-    mFlinger.handleTransactionLocked(eDisplayTransactionNeeded);
+    mFlinger.commitTransactionsLocked(eDisplayTransactionNeeded);
 
     // --------------------------------------------------------------------
     // Postconditions
@@ -213,7 +212,7 @@
 }
 
 template <typename Case>
-void HandleTransactionLockedTest::processesHotplugDisconnectCommon() {
+void DisplayTransactionCommitTest::processesHotplugDisconnectCommon() {
     // --------------------------------------------------------------------
     // Preconditions
 
@@ -238,7 +237,7 @@
     // --------------------------------------------------------------------
     // Invocation
 
-    mFlinger.handleTransactionLocked(eDisplayTransactionNeeded);
+    mFlinger.commitTransactionsLocked(eDisplayTransactionNeeded);
 
     // --------------------------------------------------------------------
     // Postconditions
@@ -255,18 +254,18 @@
     verifyDisplayIsNotConnected(existing.token());
 }
 
-TEST_F(HandleTransactionLockedTest, processesHotplugConnectPrimaryDisplay) {
+TEST_F(DisplayTransactionCommitTest, processesHotplugConnectPrimaryDisplay) {
     processesHotplugConnectCommon<SimplePrimaryDisplayCase>();
 }
 
-TEST_F(HandleTransactionLockedTest, processesHotplugConnectExternalDisplay) {
+TEST_F(DisplayTransactionCommitTest, processesHotplugConnectExternalDisplay) {
     // Inject a primary display.
     PrimaryDisplayVariant::injectHwcDisplay(this);
 
     processesHotplugConnectCommon<SimpleExternalDisplayCase>();
 }
 
-TEST_F(HandleTransactionLockedTest, ignoresHotplugConnectIfPrimaryAndExternalAlreadyConnected) {
+TEST_F(DisplayTransactionCommitTest, ignoresHotplugConnectIfPrimaryAndExternalAlreadyConnected) {
     // Inject both a primary and external display.
     PrimaryDisplayVariant::injectHwcDisplay(this);
     ExternalDisplayVariant::injectHwcDisplay(this);
@@ -281,108 +280,119 @@
     ignoresHotplugConnectCommon<SimpleTertiaryDisplayCase>();
 }
 
-TEST_F(HandleTransactionLockedTest, processesHotplugDisconnectPrimaryDisplay) {
-    processesHotplugDisconnectCommon<SimplePrimaryDisplayCase>();
+TEST_F(DisplayTransactionCommitTest, processesHotplugDisconnectPrimaryDisplay) {
+    EXPECT_EXIT(processesHotplugDisconnectCommon<SimplePrimaryDisplayCase>(),
+                testing::KilledBySignal(SIGABRT), "Primary display cannot be disconnected.");
 }
 
-TEST_F(HandleTransactionLockedTest, processesHotplugDisconnectExternalDisplay) {
+TEST_F(DisplayTransactionCommitTest, processesHotplugDisconnectExternalDisplay) {
     processesHotplugDisconnectCommon<SimpleExternalDisplayCase>();
 }
 
-TEST_F(HandleTransactionLockedTest, processesHotplugConnectThenDisconnectPrimary) {
-    using Case = SimplePrimaryDisplayCase;
+TEST_F(DisplayTransactionCommitTest, processesHotplugConnectThenDisconnectPrimary) {
+    EXPECT_EXIT(
+            [this] {
+                using Case = SimplePrimaryDisplayCase;
 
-    // --------------------------------------------------------------------
-    // Preconditions
+                // --------------------------------------------------------------------
+                // Preconditions
 
-    setupCommonPreconditions<Case>();
+                setupCommonPreconditions<Case>();
 
-    // A hotplug connect event is enqueued for a display
-    Case::Display::injectPendingHotplugEvent(this, Connection::CONNECTED);
-    // A hotplug disconnect event is also enqueued for the same display
-    Case::Display::injectPendingHotplugEvent(this, Connection::DISCONNECTED);
+                // A hotplug connect event is enqueued for a display
+                Case::Display::injectPendingHotplugEvent(this, Connection::CONNECTED);
+                // A hotplug disconnect event is also enqueued for the same display
+                Case::Display::injectPendingHotplugEvent(this, Connection::DISCONNECTED);
 
-    // --------------------------------------------------------------------
-    // Call Expectations
+                // --------------------------------------------------------------------
+                // Call Expectations
 
-    setupCommonCallExpectationsForConnectProcessing<Case>();
-    setupCommonCallExpectationsForDisconnectProcessing<Case>();
+                setupCommonCallExpectationsForConnectProcessing<Case>();
+                setupCommonCallExpectationsForDisconnectProcessing<Case>();
 
-    EXPECT_CALL(*mComposer,
-                setVsyncEnabled(Case::Display::HWC_DISPLAY_ID, IComposerClient::Vsync::DISABLE))
-            .WillOnce(Return(Error::NONE));
-    EXPECT_CALL(*mConsumer, consumerDisconnect()).WillOnce(Return(NO_ERROR));
+                EXPECT_CALL(*mComposer,
+                            setVsyncEnabled(Case::Display::HWC_DISPLAY_ID,
+                                            IComposerClient::Vsync::DISABLE))
+                        .WillOnce(Return(Error::NONE));
+                EXPECT_CALL(*mConsumer, consumerDisconnect()).WillOnce(Return(NO_ERROR));
 
-    // --------------------------------------------------------------------
-    // Invocation
+                // --------------------------------------------------------------------
+                // Invocation
 
-    mFlinger.handleTransactionLocked(eDisplayTransactionNeeded);
+                mFlinger.commitTransactionsLocked(eDisplayTransactionNeeded);
 
-    // --------------------------------------------------------------------
-    // Postconditions
+                // --------------------------------------------------------------------
+                // Postconditions
 
-    // HWComposer should not have an entry for the display
-    EXPECT_FALSE(hasPhysicalHwcDisplay(Case::Display::HWC_DISPLAY_ID));
+                // HWComposer should not have an entry for the display
+                EXPECT_FALSE(hasPhysicalHwcDisplay(Case::Display::HWC_DISPLAY_ID));
 
-    // SF should not have a display token.
-    const auto displayId = Case::Display::DISPLAY_ID::get();
-    ASSERT_TRUE(PhysicalDisplayId::tryCast(displayId));
-    ASSERT_TRUE(mFlinger.mutablePhysicalDisplayTokens().count(displayId) == 0);
+                // SF should not have a display token.
+                const auto displayId = Case::Display::DISPLAY_ID::get();
+                ASSERT_TRUE(PhysicalDisplayId::tryCast(displayId));
+                ASSERT_TRUE(mFlinger.mutablePhysicalDisplayTokens().count(displayId) == 0);
+            }(),
+            testing::KilledBySignal(SIGABRT), "Primary display cannot be disconnected.");
 }
 
-TEST_F(HandleTransactionLockedTest, processesHotplugDisconnectThenConnectPrimary) {
-    using Case = SimplePrimaryDisplayCase;
+TEST_F(DisplayTransactionCommitTest, processesHotplugDisconnectThenConnectPrimary) {
+    EXPECT_EXIT(
+            [this] {
+                using Case = SimplePrimaryDisplayCase;
 
-    // --------------------------------------------------------------------
-    // Preconditions
+                // --------------------------------------------------------------------
+                // Preconditions
 
-    setupCommonPreconditions<Case>();
+                setupCommonPreconditions<Case>();
 
-    // The display is already completely set up.
-    Case::Display::injectHwcDisplay(this);
-    auto existing = Case::Display::makeFakeExistingDisplayInjector(this);
-    existing.inject();
+                // The display is already completely set up.
+                Case::Display::injectHwcDisplay(this);
+                auto existing = Case::Display::makeFakeExistingDisplayInjector(this);
+                existing.inject();
 
-    // A hotplug disconnect event is enqueued for a display
-    Case::Display::injectPendingHotplugEvent(this, Connection::DISCONNECTED);
-    // A hotplug connect event is also enqueued for the same display
-    Case::Display::injectPendingHotplugEvent(this, Connection::CONNECTED);
+                // A hotplug disconnect event is enqueued for a display
+                Case::Display::injectPendingHotplugEvent(this, Connection::DISCONNECTED);
+                // A hotplug connect event is also enqueued for the same display
+                Case::Display::injectPendingHotplugEvent(this, Connection::CONNECTED);
 
-    // --------------------------------------------------------------------
-    // Call Expectations
+                // --------------------------------------------------------------------
+                // Call Expectations
 
-    setupCommonCallExpectationsForConnectProcessing<Case>();
-    setupCommonCallExpectationsForDisconnectProcessing<Case>();
+                setupCommonCallExpectationsForConnectProcessing<Case>();
+                setupCommonCallExpectationsForDisconnectProcessing<Case>();
 
-    // --------------------------------------------------------------------
-    // Invocation
+                // --------------------------------------------------------------------
+                // Invocation
 
-    mFlinger.handleTransactionLocked(eDisplayTransactionNeeded);
+                mFlinger.commitTransactionsLocked(eDisplayTransactionNeeded);
 
-    // --------------------------------------------------------------------
-    // Postconditions
+                // --------------------------------------------------------------------
+                // Postconditions
 
-    // The existing token should have been removed
-    verifyDisplayIsNotConnected(existing.token());
-    const auto displayId = Case::Display::DISPLAY_ID::get();
-    ASSERT_TRUE(PhysicalDisplayId::tryCast(displayId));
-    ASSERT_TRUE(mFlinger.mutablePhysicalDisplayTokens().count(displayId) == 1);
-    EXPECT_NE(existing.token(), mFlinger.mutablePhysicalDisplayTokens()[displayId]);
+                // The existing token should have been removed
+                verifyDisplayIsNotConnected(existing.token());
+                const auto displayId = Case::Display::DISPLAY_ID::get();
+                ASSERT_TRUE(PhysicalDisplayId::tryCast(displayId));
+                ASSERT_TRUE(mFlinger.mutablePhysicalDisplayTokens().count(displayId) == 1);
+                EXPECT_NE(existing.token(), mFlinger.mutablePhysicalDisplayTokens()[displayId]);
 
-    // A new display should be connected in its place
+                // A new display should be connected in its place
 
-    verifyPhysicalDisplayIsConnected<Case>();
+                verifyPhysicalDisplayIsConnected<Case>();
 
-    // --------------------------------------------------------------------
-    // Cleanup conditions
+                // --------------------------------------------------------------------
+                // Cleanup conditions
 
-    EXPECT_CALL(*mComposer,
-                setVsyncEnabled(Case::Display::HWC_DISPLAY_ID, IComposerClient::Vsync::DISABLE))
-            .WillOnce(Return(Error::NONE));
-    EXPECT_CALL(*mConsumer, consumerDisconnect()).WillOnce(Return(NO_ERROR));
+                EXPECT_CALL(*mComposer,
+                            setVsyncEnabled(Case::Display::HWC_DISPLAY_ID,
+                                            IComposerClient::Vsync::DISABLE))
+                        .WillOnce(Return(Error::NONE));
+                EXPECT_CALL(*mConsumer, consumerDisconnect()).WillOnce(Return(NO_ERROR));
+            }(),
+            testing::KilledBySignal(SIGABRT), "Primary display cannot be disconnected.");
 }
 
-TEST_F(HandleTransactionLockedTest, processesVirtualDisplayAdded) {
+TEST_F(DisplayTransactionCommitTest, processesVirtualDisplayAdded) {
     using Case = HwcVirtualDisplayCase;
 
     // --------------------------------------------------------------------
@@ -433,7 +443,7 @@
     // --------------------------------------------------------------------
     // Invocation
 
-    mFlinger.handleTransactionLocked(eDisplayTransactionNeeded);
+    mFlinger.commitTransactionsLocked(eDisplayTransactionNeeded);
 
     // --------------------------------------------------------------------
     // Postconditions
@@ -453,7 +463,7 @@
     mFlinger.mutableDrawingState().displays.removeItem(displayToken);
 }
 
-TEST_F(HandleTransactionLockedTest, processesVirtualDisplayAddedWithNoSurface) {
+TEST_F(DisplayTransactionCommitTest, processesVirtualDisplayAddedWithNoSurface) {
     using Case = HwcVirtualDisplayCase;
 
     // --------------------------------------------------------------------
@@ -479,7 +489,7 @@
     // --------------------------------------------------------------------
     // Invocation
 
-    mFlinger.handleTransactionLocked(eDisplayTransactionNeeded);
+    mFlinger.commitTransactionsLocked(eDisplayTransactionNeeded);
 
     // --------------------------------------------------------------------
     // Postconditions
@@ -493,7 +503,7 @@
     EXPECT_EQ(static_cast<bool>(Case::Display::VIRTUAL), draw.isVirtual());
 }
 
-TEST_F(HandleTransactionLockedTest, processesVirtualDisplayRemoval) {
+TEST_F(DisplayTransactionCommitTest, processesVirtualDisplayRemoval) {
     using Case = HwcVirtualDisplayCase;
 
     // --------------------------------------------------------------------
@@ -511,7 +521,7 @@
     // --------------------------------------------------------------------
     // Invocation
 
-    mFlinger.handleTransactionLocked(eDisplayTransactionNeeded);
+    mFlinger.commitTransactionsLocked(eDisplayTransactionNeeded);
 
     // --------------------------------------------------------------------
     // Postconditions
@@ -520,11 +530,11 @@
     verifyDisplayIsNotConnected(existing.token());
 }
 
-TEST_F(HandleTransactionLockedTest, processesDisplayLayerStackChanges) {
+TEST_F(DisplayTransactionCommitTest, processesDisplayLayerStackChanges) {
     using Case = NonHwcVirtualDisplayCase;
 
-    constexpr uint32_t oldLayerStack = 0u;
-    constexpr uint32_t newLayerStack = 123u;
+    constexpr ui::LayerStack oldLayerStack = ui::DEFAULT_LAYER_STACK;
+    constexpr ui::LayerStack newLayerStack{123u};
 
     // --------------------------------------------------------------------
     // Preconditions
@@ -540,7 +550,7 @@
     // --------------------------------------------------------------------
     // Invocation
 
-    mFlinger.handleTransactionLocked(eDisplayTransactionNeeded);
+    mFlinger.commitTransactionsLocked(eDisplayTransactionNeeded);
 
     // --------------------------------------------------------------------
     // Postconditions
@@ -548,7 +558,7 @@
     EXPECT_EQ(newLayerStack, display.mutableDisplayDevice()->getLayerStack());
 }
 
-TEST_F(HandleTransactionLockedTest, processesDisplayTransformChanges) {
+TEST_F(DisplayTransactionCommitTest, processesDisplayTransformChanges) {
     using Case = NonHwcVirtualDisplayCase;
 
     constexpr ui::Rotation oldTransform = ui::ROTATION_0;
@@ -568,7 +578,7 @@
     // --------------------------------------------------------------------
     // Invocation
 
-    mFlinger.handleTransactionLocked(eDisplayTransactionNeeded);
+    mFlinger.commitTransactionsLocked(eDisplayTransactionNeeded);
 
     // --------------------------------------------------------------------
     // Postconditions
@@ -576,7 +586,7 @@
     EXPECT_EQ(newTransform, display.mutableDisplayDevice()->getOrientation());
 }
 
-TEST_F(HandleTransactionLockedTest, processesDisplayLayerStackRectChanges) {
+TEST_F(DisplayTransactionCommitTest, processesDisplayLayerStackRectChanges) {
     using Case = NonHwcVirtualDisplayCase;
 
     const Rect oldLayerStackRect(0, 0, 0, 0);
@@ -596,7 +606,7 @@
     // --------------------------------------------------------------------
     // Invocation
 
-    mFlinger.handleTransactionLocked(eDisplayTransactionNeeded);
+    mFlinger.commitTransactionsLocked(eDisplayTransactionNeeded);
 
     // --------------------------------------------------------------------
     // Postconditions
@@ -604,7 +614,7 @@
     EXPECT_EQ(newLayerStackRect, display.mutableDisplayDevice()->getLayerStackSpaceRect());
 }
 
-TEST_F(HandleTransactionLockedTest, processesDisplayRectChanges) {
+TEST_F(DisplayTransactionCommitTest, processesDisplayRectChanges) {
     using Case = NonHwcVirtualDisplayCase;
 
     const Rect oldDisplayRect(0, 0);
@@ -624,7 +634,7 @@
     // --------------------------------------------------------------------
     // Invocation
 
-    mFlinger.handleTransactionLocked(eDisplayTransactionNeeded);
+    mFlinger.commitTransactionsLocked(eDisplayTransactionNeeded);
 
     // --------------------------------------------------------------------
     // Postconditions
@@ -632,7 +642,7 @@
     EXPECT_EQ(newDisplayRect, display.mutableDisplayDevice()->getOrientedDisplaySpaceRect());
 }
 
-TEST_F(HandleTransactionLockedTest, processesDisplayWidthChanges) {
+TEST_F(DisplayTransactionCommitTest, processesDisplayWidthChanges) {
     using Case = NonHwcVirtualDisplayCase;
 
     constexpr int oldWidth = 0;
@@ -674,10 +684,10 @@
     // --------------------------------------------------------------------
     // Invocation
 
-    mFlinger.handleTransactionLocked(eDisplayTransactionNeeded);
+    mFlinger.commitTransactionsLocked(eDisplayTransactionNeeded);
 }
 
-TEST_F(HandleTransactionLockedTest, processesDisplayHeightChanges) {
+TEST_F(DisplayTransactionCommitTest, processesDisplayHeightChanges) {
     using Case = NonHwcVirtualDisplayCase;
 
     constexpr int oldWidth = 0;
@@ -719,10 +729,10 @@
     // --------------------------------------------------------------------
     // Invocation
 
-    mFlinger.handleTransactionLocked(eDisplayTransactionNeeded);
+    mFlinger.commitTransactionsLocked(eDisplayTransactionNeeded);
 }
 
-TEST_F(HandleTransactionLockedTest, processesDisplaySizeDisplayRectAndLayerStackRectChanges) {
+TEST_F(DisplayTransactionCommitTest, processesDisplaySizeDisplayRectAndLayerStackRectChanges) {
     using Case = NonHwcVirtualDisplayCase;
 
     constexpr uint32_t kOldWidth = 567;
@@ -769,7 +779,7 @@
     // --------------------------------------------------------------------
     // Invocation
 
-    mFlinger.handleTransactionLocked(eDisplayTransactionNeeded);
+    mFlinger.commitTransactionsLocked(eDisplayTransactionNeeded);
 
     EXPECT_EQ(display.mutableDisplayDevice()->getBounds(), kNewSize);
     EXPECT_EQ(display.mutableDisplayDevice()->getWidth(), kNewWidth);
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_HotplugTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_HotplugTest.cpp
index bd89397..c9a2b00 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_HotplugTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_HotplugTest.cpp
@@ -38,9 +38,8 @@
     // --------------------------------------------------------------------
     // Call Expectations
 
-    // We expect invalidate() to be invoked once to trigger display transaction
-    // processing.
-    EXPECT_CALL(*mMessageQueue, invalidate()).Times(1);
+    // We expect a scheduled commit for the display transaction.
+    EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(1);
 
     // --------------------------------------------------------------------
     // Invocation
@@ -86,9 +85,8 @@
     // --------------------------------------------------------------------
     // Call Expectations
 
-    // We expect invalidate() to be invoked once to trigger display transaction
-    // processing.
-    EXPECT_CALL(*mMessageQueue, invalidate()).Times(1);
+    // We expect a scheduled commit for the display transaction.
+    EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(1);
 
     // --------------------------------------------------------------------
     // Invocation
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_NotifyPowerBoostTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_NotifyPowerBoostTest.cpp
index 69e0501..ec7e8a7 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_NotifyPowerBoostTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_NotifyPowerBoostTest.cpp
@@ -17,6 +17,9 @@
 #undef LOG_TAG
 #define LOG_TAG "LibSurfaceFlingerUnittests"
 
+#include <chrono>
+#include <thread>
+
 #include "DisplayTransactionTestHelpers.h"
 
 #include <android/hardware/power/Boost.h>
@@ -27,6 +30,8 @@
 using android::hardware::power::Boost;
 
 TEST_F(DisplayTransactionTest, notifyPowerBoostNotifiesTouchEvent) {
+    using namespace std::chrono_literals;
+
     mFlinger.scheduler()->replaceTouchTimer(100);
     std::this_thread::sleep_for(10ms);                  // wait for callback to be triggered
     EXPECT_TRUE(mFlinger.scheduler()->isTouchActive()); // Starting timer activates touch
@@ -47,4 +52,4 @@
 }
 
 } // namespace
-} // namespace android
\ No newline at end of file
+} // namespace android
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_OnInitializeDisplaysTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_OnInitializeDisplaysTest.cpp
index ef8b149..37cf05e 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_OnInitializeDisplaysTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_OnInitializeDisplaysTest.cpp
@@ -46,9 +46,8 @@
     // We expect a call to get the active display config.
     Case::Display::setupHwcGetActiveConfigCallExpectations(this);
 
-    // We expect invalidate() to be invoked once to trigger display transaction
-    // processing.
-    EXPECT_CALL(*mMessageQueue, invalidate()).Times(1);
+    // We expect a scheduled commit for the display transaction.
+    EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(1);
 
     EXPECT_CALL(*mVSyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0));
 
@@ -63,15 +62,11 @@
     // The primary display should have a current state
     ASSERT_TRUE(hasCurrentDisplayState(primaryDisplay.token()));
     const auto& primaryDisplayState = getCurrentDisplayState(primaryDisplay.token());
-    // The layer stack state should be set to zero
-    EXPECT_EQ(0u, primaryDisplayState.layerStack);
-    // The orientation state should be set to zero
+
+    // The primary display state should be reset
+    EXPECT_EQ(ui::DEFAULT_LAYER_STACK, primaryDisplayState.layerStack);
     EXPECT_EQ(ui::ROTATION_0, primaryDisplayState.orientation);
-
-    // The orientedDisplaySpaceRect state should be set to INVALID
     EXPECT_EQ(Rect::INVALID_RECT, primaryDisplayState.orientedDisplaySpaceRect);
-
-    // The layerStackSpaceRect state should be set to INVALID
     EXPECT_EQ(Rect::INVALID_RECT, primaryDisplayState.layerStackSpaceRect);
 
     // The width and height should both be zero
@@ -99,4 +94,4 @@
 }
 
 } // namespace
-} // namespace android
\ No newline at end of file
+} // namespace android
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetDisplayStateTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetDisplayStateTest.cpp
index fc40818..7d9e22b 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetDisplayStateTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetDisplayStateTest.cpp
@@ -25,6 +25,8 @@
 namespace android {
 namespace {
 
+constexpr ui::LayerStack LAYER_STACK{456u};
+
 class SetDisplayStateLockedTest : public DisplayTransactionTest {};
 
 TEST_F(SetDisplayStateLockedTest, setDisplayStateLockedDoesNothingWithUnknownDisplay) {
@@ -38,7 +40,7 @@
     DisplayState state;
     state.what = DisplayState::eLayerStackChanged;
     state.token = displayToken;
-    state.layerStack = 456;
+    state.layerStack = LAYER_STACK;
 
     // --------------------------------------------------------------------
     // Invocation
@@ -167,13 +169,13 @@
     display.inject();
 
     // The display has a layer stack set
-    display.mutableCurrentDisplayState().layerStack = 456u;
+    display.mutableCurrentDisplayState().layerStack = LAYER_STACK;
 
     // The incoming request sets the same layer stack
     DisplayState state;
     state.what = DisplayState::eLayerStackChanged;
     state.token = display.token();
-    state.layerStack = 456u;
+    state.layerStack = LAYER_STACK;
 
     // --------------------------------------------------------------------
     // Invocation
@@ -187,7 +189,7 @@
     EXPECT_EQ(0u, flags);
 
     // The current display state is unchanged
-    EXPECT_EQ(456u, display.getCurrentDisplayState().layerStack);
+    EXPECT_EQ(LAYER_STACK, display.getCurrentDisplayState().layerStack);
 }
 
 TEST_F(SetDisplayStateLockedTest, setDisplayStateLockedRequestsUpdateIfLayerStackChanged) {
@@ -201,13 +203,13 @@
     display.inject();
 
     // The display has a layer stack set
-    display.mutableCurrentDisplayState().layerStack = 654u;
+    display.mutableCurrentDisplayState().layerStack = ui::LayerStack{LAYER_STACK.id + 1};
 
     // The incoming request sets a different layer stack
     DisplayState state;
     state.what = DisplayState::eLayerStackChanged;
     state.token = display.token();
-    state.layerStack = 456u;
+    state.layerStack = LAYER_STACK;
 
     // --------------------------------------------------------------------
     // Invocation
@@ -221,7 +223,7 @@
     EXPECT_EQ(eDisplayTransactionNeeded, flags);
 
     // The desired display state has been set to the new value.
-    EXPECT_EQ(456u, display.getCurrentDisplayState().layerStack);
+    EXPECT_EQ(LAYER_STACK, display.getCurrentDisplayState().layerStack);
 }
 
 TEST_F(SetDisplayStateLockedTest, setDisplayStateLockedDoesNothingIfFlagsNotChanged) {
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetPowerModeInternalTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetPowerModeInternalTest.cpp
index eea1002..7948e60 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetPowerModeInternalTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetPowerModeInternalTest.cpp
@@ -61,7 +61,7 @@
 struct EventThreadBaseSupportedVariant {
     static void setupVsyncAndEventThreadNoCallExpectations(DisplayTransactionTest* test) {
         // The callback should not be notified to toggle VSYNC.
-        EXPECT_CALL(test->mSchedulerCallback, setVsyncEnabled(_)).Times(0);
+        EXPECT_CALL(test->mFlinger.mockSchedulerCallback(), setVsyncEnabled(_)).Times(0);
 
         // The event thread should not be notified.
         EXPECT_CALL(*test->mEventThread, onScreenReleased()).Times(0);
@@ -88,7 +88,7 @@
 struct EventThreadIsSupportedVariant : public EventThreadBaseSupportedVariant {
     static void setupAcquireAndEnableVsyncCallExpectations(DisplayTransactionTest* test) {
         // The callback should be notified to enable VSYNC.
-        EXPECT_CALL(test->mSchedulerCallback, setVsyncEnabled(true)).Times(1);
+        EXPECT_CALL(test->mFlinger.mockSchedulerCallback(), setVsyncEnabled(true)).Times(1);
 
         // The event thread should be notified that the screen was acquired.
         EXPECT_CALL(*test->mEventThread, onScreenAcquired()).Times(1);
@@ -96,7 +96,7 @@
 
     static void setupReleaseAndDisableVsyncCallExpectations(DisplayTransactionTest* test) {
         // The callback should be notified to disable VSYNC.
-        EXPECT_CALL(test->mSchedulerCallback, setVsyncEnabled(false)).Times(1);
+        EXPECT_CALL(test->mFlinger.mockSchedulerCallback(), setVsyncEnabled(false)).Times(1);
 
         // The event thread should not be notified that the screen was released.
         EXPECT_CALL(*test->mEventThread, onScreenReleased()).Times(1);
@@ -273,7 +273,7 @@
     }
 
     static void setupRepaintEverythingCallExpectations(DisplayTransactionTest* test) {
-        EXPECT_CALL(*test->mMessageQueue, invalidate()).Times(1);
+        EXPECT_CALL(*test->mFlinger.scheduler(), scheduleFrame()).Times(1);
     }
 
     static void setupSurfaceInterceptorCallExpectations(DisplayTransactionTest* test,
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetupNewDisplayDeviceInternalTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetupNewDisplayDeviceInternalTest.cpp
index e32c4bf..a0e078b 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetupNewDisplayDeviceInternalTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetupNewDisplayDeviceInternalTest.cpp
@@ -34,7 +34,6 @@
     static constexpr bool WIDE_COLOR_SUPPORTED = true;
 
     static void injectConfigChange(DisplayTransactionTest* test) {
-        test->mFlinger.mutableUseColorManagement() = true;
         test->mFlinger.mutableHasWideColorDisplay() = true;
         test->mFlinger.mutableDisplayColorSetting() = DisplayColorSetting::kUnmanaged;
     }
@@ -241,22 +240,21 @@
         ASSERT_TRUE(hwcDisplayId);
         mFlinger.getHwComposer().allocatePhysicalDisplay(*hwcDisplayId, *displayId);
         DisplayModePtr activeMode = DisplayMode::Builder(Case::Display::HWC_ACTIVE_CONFIG_ID)
-                                            .setWidth(Case::Display::WIDTH)
-                                            .setHeight(Case::Display::HEIGHT)
+                                            .setResolution(Case::Display::RESOLUTION)
                                             .setVsyncPeriod(DEFAULT_VSYNC_PERIOD)
                                             .setDpiX(DEFAULT_DPI)
                                             .setDpiY(DEFAULT_DPI)
                                             .setGroup(0)
                                             .build();
-        DisplayModes modes{activeMode};
         state.physical = {.id = *displayId,
                           .type = *connectionType,
                           .hwcDisplayId = *hwcDisplayId,
-                          .supportedModes = modes,
-                          .activeMode = activeMode};
+                          .supportedModes = makeModes(activeMode),
+                          .activeMode = std::move(activeMode)};
     }
 
     state.isSecure = static_cast<bool>(Case::Display::SECURE);
+    state.flags = Case::Display::DISPLAY_FLAGS;
 
     auto device = mFlinger.setupNewDisplayDeviceInternal(displayToken, compositionDisplay, state,
                                                          displaySurface, producer);
@@ -270,8 +268,7 @@
     EXPECT_EQ(static_cast<bool>(Case::Display::VIRTUAL), device->isVirtual());
     EXPECT_EQ(static_cast<bool>(Case::Display::SECURE), device->isSecure());
     EXPECT_EQ(static_cast<bool>(Case::Display::PRIMARY), device->isPrimary());
-    EXPECT_EQ(Case::Display::WIDTH, device->getWidth());
-    EXPECT_EQ(Case::Display::HEIGHT, device->getHeight());
+    EXPECT_EQ(Case::Display::RESOLUTION, device->getSize());
     EXPECT_EQ(Case::WideColorSupport::WIDE_COLOR_SUPPORTED, device->hasWideColorGamut());
     EXPECT_EQ(Case::HdrSupport::HDR10_PLUS_SUPPORTED, device->hasHDR10PlusSupport());
     EXPECT_EQ(Case::HdrSupport::HDR10_SUPPORTED, device->hasHDR10Support());
@@ -279,6 +276,8 @@
     EXPECT_EQ(Case::HdrSupport::HDR_DOLBY_VISION_SUPPORTED, device->hasDolbyVisionSupport());
     EXPECT_EQ(Case::PerFrameMetadataSupport::PER_FRAME_METADATA_KEYS,
               device->getSupportedPerFrameMetadata());
+    EXPECT_EQ(Case::Display::DISPLAY_FLAGS & DisplayDevice::eReceivesInput,
+              device->receivesInput());
 
     if constexpr (Case::Display::CONNECTION_TYPE::value) {
         EXPECT_EQ(1, device->getSupportedModes().size());
@@ -292,7 +291,9 @@
 }
 
 TEST_F(SetupNewDisplayDeviceInternalTest, createSimpleExternalDisplay) {
-    setupNewDisplayDeviceInternalTest<SimpleExternalDisplayCase>();
+    // External displays must be secondary, as the primary display cannot be disconnected.
+    EXPECT_EXIT(setupNewDisplayDeviceInternalTest<SimpleExternalDisplayCase>(),
+                testing::KilledBySignal(SIGABRT), "Missing primary display");
 }
 
 TEST_F(SetupNewDisplayDeviceInternalTest, createNonHwcVirtualDisplay) {
diff --git a/services/surfaceflinger/tests/unittests/TestableScheduler.h b/services/surfaceflinger/tests/unittests/TestableScheduler.h
index 1d21bd4..4708572 100644
--- a/services/surfaceflinger/tests/unittests/TestableScheduler.h
+++ b/services/surfaceflinger/tests/unittests/TestableScheduler.h
@@ -28,23 +28,30 @@
 #include "mock/MockVSyncTracker.h"
 #include "mock/MockVsyncController.h"
 
-namespace android {
+namespace android::scheduler {
 
-class TestableScheduler : public Scheduler {
+class TestableScheduler : public Scheduler, private ICompositor {
 public:
-    TestableScheduler(const std::shared_ptr<scheduler::RefreshRateConfigs>& refreshRateConfigs,
-                      ISchedulerCallback& callback)
+    TestableScheduler(std::shared_ptr<RefreshRateConfigs> configs, ISchedulerCallback& callback)
           : TestableScheduler(std::make_unique<mock::VsyncController>(),
-                              std::make_unique<mock::VSyncTracker>(), refreshRateConfigs,
+                              std::make_unique<mock::VSyncTracker>(), std::move(configs),
                               callback) {}
 
-    TestableScheduler(std::unique_ptr<scheduler::VsyncController> vsyncController,
-                      std::unique_ptr<scheduler::VSyncTracker> vsyncTracker,
-                      const std::shared_ptr<scheduler::RefreshRateConfigs>& refreshRateConfigs,
-                      ISchedulerCallback& callback)
-          : Scheduler({std::move(vsyncController), std::move(vsyncTracker), nullptr},
-                      refreshRateConfigs, callback, createLayerHistory(),
-                      {.useContentDetection = true}) {}
+    TestableScheduler(std::unique_ptr<VsyncController> controller,
+                      std::unique_ptr<VSyncTracker> tracker,
+                      std::shared_ptr<RefreshRateConfigs> configs, ISchedulerCallback& callback)
+          : Scheduler(*this, callback, Feature::kContentDetection) {
+        mVsyncSchedule.emplace(VsyncSchedule(std::move(tracker), nullptr, std::move(controller)));
+        setRefreshRateConfigs(std::move(configs));
+
+        ON_CALL(*this, postMessage).WillByDefault([](sp<MessageHandler>&& handler) {
+            // Execute task to prevent broken promise exception on destruction.
+            handler->handleMessage(Message());
+        });
+    }
+
+    MOCK_METHOD(void, scheduleFrame, (), (override));
+    MOCK_METHOD(void, postMessage, (sp<MessageHandler>&&), (override));
 
     // Used to inject mock event thread.
     ConnectionHandle createConnection(std::unique_ptr<EventThread> eventThread) {
@@ -58,20 +65,16 @@
     auto& mutablePrimaryHWVsyncEnabled() { return mPrimaryHWVsyncEnabled; }
     auto& mutableHWVsyncAvailable() { return mHWVsyncAvailable; }
 
-    bool hasLayerHistory() const { return static_cast<bool>(mLayerHistory); }
-
-    auto* mutableLayerHistory() { return mLayerHistory.get(); }
+    auto& mutableLayerHistory() { return mLayerHistory; }
 
     size_t layerHistorySize() NO_THREAD_SAFETY_ANALYSIS {
-        if (!mLayerHistory) return 0;
-        return mutableLayerHistory()->mLayerInfos.size();
+        return mLayerHistory.mActiveLayerInfos.size() + mLayerHistory.mInactiveLayerInfos.size();
     }
 
     auto refreshRateConfigs() { return holdRefreshRateConfigs(); }
 
     size_t getNumActiveLayers() NO_THREAD_SAFETY_ANALYSIS {
-        if (!mLayerHistory) return 0;
-        return mutableLayerHistory()->mActiveLayersEnd;
+        return mLayerHistory.mActiveLayerInfos.size();
     }
 
     void replaceTouchTimer(int64_t millis) {
@@ -86,32 +89,29 @@
     }
 
     bool isTouchActive() {
-        std::lock_guard<std::mutex> lock(mFeatureStateLock);
-        return mFeatures.touch == Scheduler::TouchState::Active;
+        std::lock_guard<std::mutex> lock(mPolicyLock);
+        return mPolicy.touch == Scheduler::TouchState::Active;
     }
 
     void dispatchCachedReportedMode() {
-        std::lock_guard<std::mutex> lock(mFeatureStateLock);
+        std::lock_guard<std::mutex> lock(mPolicyLock);
         return Scheduler::dispatchCachedReportedMode();
     }
 
-    void clearOptionalFieldsInFeatures() {
-        std::lock_guard<std::mutex> lock(mFeatureStateLock);
-        mFeatures.cachedModeChangedParams.reset();
+    void clearCachedReportedMode() {
+        std::lock_guard<std::mutex> lock(mPolicyLock);
+        mPolicy.cachedModeChangedParams.reset();
     }
 
     void onNonPrimaryDisplayModeChanged(ConnectionHandle handle, DisplayModePtr mode) {
         return Scheduler::onNonPrimaryDisplayModeChanged(handle, mode);
     }
 
-    ~TestableScheduler() {
-        // All these pointer and container clears help ensure that GMock does
-        // not report a leaked object, since the Scheduler instance may
-        // still be referenced by something despite our best efforts to destroy
-        // it after each test is done.
-        mVsyncSchedule.controller.reset();
-        mConnections.clear();
-    }
+private:
+    // ICompositor overrides:
+    bool commit(nsecs_t, int64_t, nsecs_t) override { return false; }
+    void composite(nsecs_t, int64_t) override {}
+    void sample() override {}
 };
 
-} // namespace android
+} // namespace android::scheduler
diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
index a23361e..bf2465f 100644
--- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
+++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
@@ -16,6 +16,9 @@
 
 #pragma once
 
+#include <algorithm>
+#include <variant>
+
 #include <compositionengine/Display.h>
 #include <compositionengine/LayerFECompositionState.h>
 #include <compositionengine/OutputLayer.h>
@@ -42,8 +45,10 @@
 #include "SurfaceInterceptor.h"
 #include "TestableScheduler.h"
 #include "mock/DisplayHardware/MockComposer.h"
+#include "mock/DisplayHardware/MockDisplayMode.h"
 #include "mock/MockFrameTimeline.h"
 #include "mock/MockFrameTracer.h"
+#include "mock/MockSchedulerCallback.h"
 
 namespace android {
 
@@ -73,20 +78,11 @@
         return nullptr;
     }
 
-    std::unique_ptr<MessageQueue> createMessageQueue() override {
-        return std::make_unique<android::impl::MessageQueue>();
-    }
-
     std::unique_ptr<scheduler::VsyncConfiguration> createVsyncConfiguration(
             Fps /*currentRefreshRate*/) override {
         return std::make_unique<scheduler::FakePhaseOffsets>();
     }
 
-    std::unique_ptr<Scheduler> createScheduler(
-            const std::shared_ptr<scheduler::RefreshRateConfigs>&, ISchedulerCallback&) override {
-        return nullptr;
-    }
-
     sp<SurfaceInterceptor> createSurfaceInterceptor() override {
         return new android::impl::SurfaceInterceptor();
     }
@@ -178,12 +174,19 @@
 
 } // namespace surfaceflinger::test
 
-class TestableSurfaceFlinger final : private ISchedulerCallback {
+class TestableSurfaceFlinger {
 public:
     using HotplugEvent = SurfaceFlinger::HotplugEvent;
 
+    TestableSurfaceFlinger(sp<SurfaceFlinger> flinger = nullptr) : mFlinger(flinger) {
+        if (!mFlinger) {
+            mFlinger = sp<SurfaceFlinger>::make(mFactory, SurfaceFlinger::SkipInitialization);
+        }
+        mFlinger->mAnimationTransactionTimeout = ms2ns(10);
+    }
+
     SurfaceFlinger* flinger() { return mFlinger.get(); }
-    TestableScheduler* scheduler() { return mScheduler; }
+    scheduler::TestableScheduler* scheduler() { return mScheduler; }
 
     // Extend this as needed for accessing SurfaceFlinger private (and public)
     // functions.
@@ -201,49 +204,78 @@
         mFlinger->mCompositionEngine->setTimeStats(timeStats);
     }
 
-    // The ISchedulerCallback argument can be nullptr for a no-op implementation.
+    enum class SchedulerCallbackImpl { kNoOp, kMock };
+
+    static constexpr struct OneDisplayMode {
+    } kOneDisplayMode;
+
+    static constexpr struct TwoDisplayModes {
+    } kTwoDisplayModes;
+
+    using RefreshRateConfigsPtr = std::shared_ptr<scheduler::RefreshRateConfigs>;
+
+    using DisplayModesVariant =
+            std::variant<OneDisplayMode, TwoDisplayModes, RefreshRateConfigsPtr>;
+
     void setupScheduler(std::unique_ptr<scheduler::VsyncController> vsyncController,
                         std::unique_ptr<scheduler::VSyncTracker> vsyncTracker,
                         std::unique_ptr<EventThread> appEventThread,
                         std::unique_ptr<EventThread> sfEventThread,
-                        ISchedulerCallback* callback = nullptr, bool hasMultipleModes = false) {
-        DisplayModes modes{DisplayMode::Builder(0)
-                                   .setId(DisplayModeId(0))
-                                   .setPhysicalDisplayId(PhysicalDisplayId(0))
-                                   .setVsyncPeriod(16'666'667)
-                                   .setGroup(0)
-                                   .build()};
+                        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));
+        } else {
+            constexpr DisplayModeId kModeId60{0};
+            DisplayModes modes = makeModes(mock::createDisplayMode(kModeId60, 60_Hz));
 
-        if (hasMultipleModes) {
-            modes.emplace_back(DisplayMode::Builder(1)
-                                       .setId(DisplayModeId(1))
-                                       .setPhysicalDisplayId(PhysicalDisplayId(0))
-                                       .setVsyncPeriod(11'111'111)
-                                       .setGroup(0)
-                                       .build());
+            if (std::holds_alternative<TwoDisplayModes>(modesVariant)) {
+                constexpr DisplayModeId kModeId90{1};
+                modes.try_emplace(kModeId90, mock::createDisplayMode(kModeId90, 90_Hz));
+            }
+
+            configs = std::make_shared<scheduler::RefreshRateConfigs>(modes, kModeId60);
         }
 
-        const auto currMode = DisplayModeId(0);
-        mRefreshRateConfigs = std::make_shared<scheduler::RefreshRateConfigs>(modes, currMode);
-        const auto currFps = mRefreshRateConfigs->getCurrentRefreshRate().getFps();
-        mFlinger->mVsyncConfiguration = mFactory.createVsyncConfiguration(currFps);
+        const auto fps = configs->getActiveMode()->getFps();
+        mFlinger->mVsyncConfiguration = mFactory.createVsyncConfiguration(fps);
         mFlinger->mVsyncModulator = sp<scheduler::VsyncModulator>::make(
                 mFlinger->mVsyncConfiguration->getCurrentConfigs());
-        mFlinger->mRefreshRateStats =
-                std::make_unique<scheduler::RefreshRateStats>(*mFlinger->mTimeStats, currFps,
-                                                              /*powerMode=*/hal::PowerMode::OFF);
 
-        mScheduler = new TestableScheduler(std::move(vsyncController), std::move(vsyncTracker),
-                                           mRefreshRateConfigs, *(callback ?: this));
+        mFlinger->mRefreshRateStats =
+                std::make_unique<scheduler::RefreshRateStats>(*mFlinger->mTimeStats, fps,
+                                                              hal::PowerMode::OFF);
+
+        using Callback = scheduler::ISchedulerCallback;
+        Callback& callback = callbackImpl == SchedulerCallbackImpl::kNoOp
+                ? static_cast<Callback&>(mNoOpSchedulerCallback)
+                : static_cast<Callback&>(mSchedulerCallback);
+
+        if (useNiceMock) {
+            mScheduler =
+                    new testing::NiceMock<scheduler::TestableScheduler>(std::move(vsyncController),
+                                                                        std::move(vsyncTracker),
+                                                                        std::move(configs),
+                                                                        callback);
+        } else {
+            mScheduler = new scheduler::TestableScheduler(std::move(vsyncController),
+                                                          std::move(vsyncTracker),
+                                                          std::move(configs), callback);
+        }
 
         mFlinger->mAppConnectionHandle = mScheduler->createConnection(std::move(appEventThread));
         mFlinger->mSfConnectionHandle = mScheduler->createConnection(std::move(sfEventThread));
         resetScheduler(mScheduler);
     }
 
-    void resetScheduler(Scheduler* scheduler) { mFlinger->mScheduler.reset(scheduler); }
+    void resetScheduler(scheduler::Scheduler* scheduler) { mFlinger->mScheduler.reset(scheduler); }
 
-    TestableScheduler& mutableScheduler() const { return *mScheduler; }
+    scheduler::TestableScheduler& mutableScheduler() { return *mScheduler; }
+    scheduler::mock::SchedulerCallback& mockSchedulerCallback() { return mSchedulerCallback; }
+
+    auto& mutableVsyncModulator() { return mFlinger->mVsyncModulator; }
 
     using CreateBufferQueueFunction = surfaceflinger::test::Factory::CreateBufferQueueFunction;
     void setCreateBufferQueueFunction(CreateBufferQueueFunction f) {
@@ -276,7 +308,8 @@
         layer->editCompositionState()->sidebandStream = sidebandStream;
     }
 
-    void setLayerCompositionType(const sp<Layer>& layer, hal::Composition type) {
+    void setLayerCompositionType(const sp<Layer>& layer,
+                                 aidl::android::hardware::graphics::composer3::Composition type) {
         auto outputLayer = findOutputLayerForDisplay(layer, mFlinger->getDefaultDisplayDevice());
         LOG_ALWAYS_FATAL_IF(!outputLayer);
         auto& state = outputLayer->editState();
@@ -295,6 +328,14 @@
     /* ------------------------------------------------------------------------
      * Forwarding for functions being tested
      */
+    nsecs_t commit() {
+        const nsecs_t now = systemTime();
+        const nsecs_t expectedVsyncTime = now + 10'000'000;
+        mFlinger->commit(now, kVsyncId, expectedVsyncTime);
+        return now;
+    }
+
+    void commitAndComposite() { mFlinger->composite(commit(), kVsyncId); }
 
     auto createDisplay(const String8& displayName, bool secure) {
         return mFlinger->createDisplay(displayName, secure);
@@ -304,6 +345,11 @@
         return mFlinger->destroyDisplay(displayToken);
     }
 
+    auto getDisplay(const sp<IBinder>& displayToken) {
+        Mutex::Autolock lock(mFlinger->mStateLock);
+        return mFlinger->getDisplayDeviceLocked(displayToken);
+    }
+
     void enableHalVirtualDisplays(bool enable) { mFlinger->enableHalVirtualDisplays(enable); }
 
     auto setupNewDisplayDeviceInternal(
@@ -316,9 +362,9 @@
                                                        dispSurface, producer);
     }
 
-    auto handleTransactionLocked(uint32_t transactionFlags) {
-        Mutex::Autolock _l(mFlinger->mStateLock);
-        return mFlinger->handleTransactionLocked(transactionFlags);
+    auto commitTransactionsLocked(uint32_t transactionFlags) {
+        Mutex::Autolock lock(mFlinger->mStateLock);
+        return mFlinger->commitTransactionsLocked(transactionFlags);
     }
 
     void onComposerHalHotplug(hal::HWDisplayId hwcDisplayId, hal::Connection connection) {
@@ -326,7 +372,7 @@
     }
 
     auto setDisplayStateLocked(const DisplayState& s) {
-        Mutex::Autolock _l(mFlinger->mStateLock);
+        Mutex::Autolock lock(mFlinger->mStateLock);
         return mFlinger->setDisplayStateLocked(s);
     }
 
@@ -337,22 +383,23 @@
 
     auto notifyPowerBoost(int32_t boostId) { return mFlinger->notifyPowerBoost(boostId); }
 
+    auto setDisplayBrightness(const sp<IBinder>& display,
+                              const gui::DisplayBrightness& brightness) {
+        return mFlinger->setDisplayBrightness(display, brightness);
+    }
+
     // Allow reading display state without locking, as if called on the SF main thread.
     auto setPowerModeInternal(const sp<DisplayDevice>& display,
                               hal::PowerMode mode) NO_THREAD_SAFETY_ANALYSIS {
         return mFlinger->setPowerModeInternal(display, mode);
     }
 
-    auto onMessageReceived(int32_t what) {
-        return mFlinger->onMessageReceived(what, /*vsyncId=*/0, systemTime());
-    }
-
-    auto renderScreenImplLocked(const RenderArea& renderArea,
+    auto renderScreenImpl(const RenderArea& renderArea,
                                 SurfaceFlinger::TraverseLayersFunction traverseLayers,
                                 const std::shared_ptr<renderengine::ExternalTexture>& buffer,
                                 bool forSystem, bool regionSampling) {
         ScreenCaptureResults captureResults;
-        return mFlinger->renderScreenImplLocked(renderArea, traverseLayers, buffer, forSystem,
+        return mFlinger->renderScreenImpl(renderArea, traverseLayers, buffer, forSystem,
                                                 regionSampling, false /* grayscale */,
                                                 captureResults);
     }
@@ -369,6 +416,7 @@
 
     auto& getTransactionQueue() { return mFlinger->mTransactionQueue; }
     auto& getPendingTransactionQueue() { return mFlinger->mPendingTransactionQueues; }
+    auto& getTransactionCommittedSignals() { return mFlinger->mTransactionCommittedSignals; }
 
     auto setTransactionState(
             const FrameTimelineInfo& frameTimelineInfo, const Vector<ComposerState>& states,
@@ -382,7 +430,7 @@
                                              listenerCallbacks, transactionId);
     }
 
-    auto flushTransactionQueues() { return mFlinger->flushTransactionQueues(); };
+    auto flushTransactionQueues() { return mFlinger->flushTransactionQueues(0); };
 
     auto onTransact(uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) {
         return mFlinger->onTransact(code, data, reply, flags);
@@ -395,6 +443,38 @@
         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);
+    }
+
+    void onActiveDisplayChanged(const sp<DisplayDevice>& activeDisplay) {
+        Mutex::Autolock lock(mFlinger->mStateLock);
+        mFlinger->onActiveDisplayChangedLocked(activeDisplay);
+    }
+
+    auto commit(nsecs_t frameTime, int64_t vsyncId) {
+        const nsecs_t expectedVsyncTime = frameTime + 10'000'000;
+        mFlinger->commit(frameTime, vsyncId, expectedVsyncTime);
+    }
+
+    auto createLayer(LayerCreationArgs& args, sp<IBinder>* outHandle,
+                     const sp<IBinder>& parentHandle, int32_t* outLayerId,
+                     const sp<Layer>& parentLayer, uint32_t* outTransformHint) {
+        return mFlinger->createLayer(args, outHandle, parentHandle, outLayerId, parentLayer,
+                                     outTransformHint);
+    }
+
+    auto mirrorLayer(const LayerCreationArgs& args, const sp<IBinder>& mirrorFromHandle,
+                     sp<IBinder>* outHandle, int32_t* outLayerId) {
+        return mFlinger->mirrorLayer(args, mirrorFromHandle, outHandle, outLayerId);
+    }
+
     /* ------------------------------------------------------------------------
      * Read-only access to private data to assert post-conditions.
      */
@@ -413,20 +493,22 @@
         return static_cast<mock::FrameTracer*>(mFlinger->mFrameTracer.get());
     }
 
+    nsecs_t getAnimationTransactionTimeout() const {
+        return mFlinger->mAnimationTransactionTimeout;
+    }
+
     /* ------------------------------------------------------------------------
      * Read-write access to private data to set up preconditions and assert
      * post-conditions.
      */
 
     auto& mutableHasWideColorDisplay() { return SurfaceFlinger::hasWideColorDisplay; }
-    auto& mutableUseColorManagement() { return SurfaceFlinger::useColorManagement; }
 
     auto& mutableCurrentState() { return mFlinger->mCurrentState; }
     auto& mutableDisplayColorSetting() { return mFlinger->mDisplayColorSetting; }
     auto& mutableDisplays() { return mFlinger->mDisplays; }
     auto& mutableDrawingState() { return mFlinger->mDrawingState; }
-    auto& mutableEventQueue() { return mFlinger->mEventQueue; }
-    auto& mutableGeometryInvalid() { return mFlinger->mGeometryInvalid; }
+    auto& mutableGeometryDirty() { return mFlinger->mGeometryDirty; }
     auto& mutableInterceptor() { return mFlinger->mInterceptor; }
     auto& mutableMainThreadId() { return mFlinger->mMainThreadId; }
     auto& mutablePendingHotplugEvents() { return mFlinger->mPendingHotplugEvents; }
@@ -439,9 +521,7 @@
 
     auto& mutableHwcDisplayData() { return getHwComposer().mDisplayData; }
     auto& mutableHwcPhysicalDisplayIdMap() { return getHwComposer().mPhysicalDisplayIdMap; }
-    auto& mutableInternalHwcDisplayId() { return getHwComposer().mInternalHwcDisplayId; }
-    auto& mutableExternalHwcDisplayId() { return getHwComposer().mExternalHwcDisplayId; }
-    auto& mutableUseFrameRateApi() { return mFlinger->useFrameRateApi; }
+    auto& mutablePrimaryHwcDisplayId() { return getHwComposer().mPrimaryHwcDisplayId; }
     auto& mutableActiveDisplayToken() { return mFlinger->mActiveDisplayToken; }
 
     auto fromHandle(const sp<IBinder>& handle) {
@@ -456,7 +536,6 @@
         mutableDisplays().clear();
         mutableCurrentState().displays.clear();
         mutableDrawingState().displays.clear();
-        mutableEventQueue().reset();
         mutableInterceptor().clear();
         mFlinger->mScheduler.reset();
         mFlinger->mCompositionEngine->setHwComposer(std::unique_ptr<HWComposer>());
@@ -469,9 +548,11 @@
      * preconditions and assert post-conditions.
      */
     struct HWC2Display : public HWC2::impl::Display {
-        HWC2Display(Hwc2::Composer& composer,
-                    const std::unordered_set<hal::Capability>& capabilities, hal::HWDisplayId id,
-                    hal::DisplayType type)
+        HWC2Display(
+                Hwc2::Composer& composer,
+                const std::unordered_set<aidl::android::hardware::graphics::composer3::Capability>&
+                        capabilities,
+                hal::HWDisplayId id, hal::DisplayType type)
               : HWC2::impl::Display(composer, capabilities, id, type) {}
         ~HWC2Display() {
             // Prevents a call to disable vsyncs.
@@ -485,9 +566,8 @@
     class FakeHwcDisplayInjector {
     public:
         static constexpr hal::HWDisplayId DEFAULT_HWC_DISPLAY_ID = 1000;
-        static constexpr int32_t DEFAULT_WIDTH = 1920;
-        static constexpr int32_t DEFAULT_HEIGHT = 1280;
-        static constexpr int32_t DEFAULT_VSYNC_PERIOD = 16'666'666;
+        static constexpr ui::Size DEFAULT_RESOLUTION{1920, 1280};
+        static constexpr int32_t DEFAULT_VSYNC_PERIOD = 16'666'667;
         static constexpr int32_t DEFAULT_CONFIG_GROUP = 7;
         static constexpr int32_t DEFAULT_DPI = 320;
         static constexpr hal::HWConfigId DEFAULT_ACTIVE_CONFIG = 0;
@@ -502,17 +582,12 @@
             return *this;
         }
 
-        auto& setWidth(int32_t width) {
-            mWidth = width;
+        auto& setResolution(ui::Size resolution) {
+            mResolution = resolution;
             return *this;
         }
 
-        auto& setHeight(int32_t height) {
-            mHeight = height;
-            return *this;
-        }
-
-        auto& setVsyncPeriod(int32_t vsyncPeriod) {
+        auto& setVsyncPeriod(nsecs_t vsyncPeriod) {
             mVsyncPeriod = vsyncPeriod;
             return *this;
         }
@@ -532,7 +607,9 @@
             return *this;
         }
 
-        auto& setCapabilities(const std::unordered_set<hal::Capability>* capabilities) {
+        auto& setCapabilities(
+                const std::unordered_set<aidl::android::hardware::graphics::composer3::Capability>*
+                        capabilities) {
             mCapabilities = capabilities;
             return *this;
         }
@@ -548,7 +625,9 @@
             using ::testing::Return;
             using ::testing::SetArgPointee;
 
-            static const std::unordered_set<hal::Capability> defaultCapabilities;
+            static const std::unordered_set<
+                    aidl::android::hardware::graphics::composer3::Capability>
+                    defaultCapabilities;
             if (mCapabilities == nullptr) mCapabilities = &defaultCapabilities;
 
             // Caution - Make sure that any values passed by reference here do
@@ -569,18 +648,20 @@
 
             EXPECT_CALL(*composer,
                         getDisplayAttribute(mHwcDisplayId, mActiveConfig, hal::Attribute::WIDTH, _))
-                    .WillRepeatedly(DoAll(SetArgPointee<3>(mWidth), Return(hal::Error::NONE)));
+                    .WillRepeatedly(DoAll(SetArgPointee<3>(mResolution.getWidth()),
+                                          Return(hal::Error::NONE)));
 
             EXPECT_CALL(*composer,
                         getDisplayAttribute(mHwcDisplayId, mActiveConfig, hal::Attribute::HEIGHT,
                                             _))
-                    .WillRepeatedly(DoAll(SetArgPointee<3>(mHeight), Return(hal::Error::NONE)));
+                    .WillRepeatedly(DoAll(SetArgPointee<3>(mResolution.getHeight()),
+                                          Return(hal::Error::NONE)));
 
             EXPECT_CALL(*composer,
                         getDisplayAttribute(mHwcDisplayId, mActiveConfig,
                                             hal::Attribute::VSYNC_PERIOD, _))
-                    .WillRepeatedly(
-                            DoAll(SetArgPointee<3>(mVsyncPeriod), Return(hal::Error::NONE)));
+                    .WillRepeatedly(DoAll(SetArgPointee<3>(static_cast<int32_t>(mVsyncPeriod)),
+                                          Return(hal::Error::NONE)));
 
             EXPECT_CALL(*composer,
                         getDisplayAttribute(mHwcDisplayId, mActiveConfig, hal::Attribute::DPI_X, _))
@@ -601,13 +682,12 @@
                 LOG_ALWAYS_FATAL_IF(!physicalId);
                 flinger->mutableHwcPhysicalDisplayIdMap().emplace(mHwcDisplayId, *physicalId);
                 if (mIsPrimary) {
-                    flinger->mutableInternalHwcDisplayId() = mHwcDisplayId;
+                    flinger->mutablePrimaryHwcDisplayId() = mHwcDisplayId;
                 } else {
-                    // If there is an external HWC display there should always be an internal ID
+                    // If there is an external HWC display, there should always be a primary ID
                     // as well. Set it to some arbitrary value.
-                    auto& internalId = flinger->mutableInternalHwcDisplayId();
-                    if (!internalId) internalId = mHwcDisplayId - 1;
-                    flinger->mutableExternalHwcDisplayId() = mHwcDisplayId;
+                    auto& primaryId = flinger->mutablePrimaryHwcDisplayId();
+                    if (!primaryId) primaryId = mHwcDisplayId - 1;
                 }
             }
         }
@@ -618,46 +698,29 @@
         const bool mIsPrimary;
 
         hal::HWDisplayId mHwcDisplayId = DEFAULT_HWC_DISPLAY_ID;
-        int32_t mWidth = DEFAULT_WIDTH;
-        int32_t mHeight = DEFAULT_HEIGHT;
-        int32_t mVsyncPeriod = DEFAULT_VSYNC_PERIOD;
+        ui::Size mResolution = DEFAULT_RESOLUTION;
+        nsecs_t mVsyncPeriod = DEFAULT_VSYNC_PERIOD;
         int32_t mDpiX = DEFAULT_DPI;
         int32_t mDpiY = DEFAULT_DPI;
         int32_t mConfigGroup = DEFAULT_CONFIG_GROUP;
         hal::HWConfigId mActiveConfig = DEFAULT_ACTIVE_CONFIG;
         hal::PowerMode mPowerMode = DEFAULT_POWER_MODE;
-        const std::unordered_set<hal::Capability>* mCapabilities = nullptr;
+        const std::unordered_set<aidl::android::hardware::graphics::composer3::Capability>*
+                mCapabilities = nullptr;
     };
 
     class FakeDisplayDeviceInjector {
     public:
         FakeDisplayDeviceInjector(TestableSurfaceFlinger& flinger,
-                                  std::shared_ptr<compositionengine::Display> compositionDisplay,
+                                  std::shared_ptr<compositionengine::Display> display,
                                   std::optional<ui::DisplayConnectionType> connectionType,
                                   std::optional<hal::HWDisplayId> hwcDisplayId, bool isPrimary)
               : mFlinger(flinger),
                 mCreationArgs(flinger.mFlinger.get(), flinger.mFlinger->getHwComposer(),
-                              mDisplayToken, compositionDisplay),
+                              mDisplayToken, display),
                 mHwcDisplayId(hwcDisplayId) {
             mCreationArgs.connectionType = connectionType;
             mCreationArgs.isPrimary = isPrimary;
-
-            mActiveModeId = DisplayModeId(0);
-            DisplayModePtr activeMode =
-                    DisplayMode::Builder(FakeHwcDisplayInjector::DEFAULT_ACTIVE_CONFIG)
-                            .setId(mActiveModeId)
-                            .setPhysicalDisplayId(PhysicalDisplayId(0))
-                            .setWidth(FakeHwcDisplayInjector::DEFAULT_WIDTH)
-                            .setHeight(FakeHwcDisplayInjector::DEFAULT_HEIGHT)
-                            .setVsyncPeriod(FakeHwcDisplayInjector::DEFAULT_VSYNC_PERIOD)
-                            .setDpiX(FakeHwcDisplayInjector::DEFAULT_DPI)
-                            .setDpiY(FakeHwcDisplayInjector::DEFAULT_DPI)
-                            .setGroup(0)
-                            .build();
-
-            DisplayModes modes{activeMode};
-            mCreationArgs.supportedModes = modes;
-            mCreationArgs.refreshRateConfigs = flinger.mRefreshRateConfigs;
         }
 
         sp<IBinder> token() const { return mDisplayToken; }
@@ -680,13 +743,16 @@
 
         auto& mutableDisplayDevice() { return mFlinger.mutableDisplays()[mDisplayToken]; }
 
-        auto& setActiveMode(DisplayModeId mode) {
-            mActiveModeId = mode;
-            return *this;
-        }
-
-        auto& setSupportedModes(DisplayModes mode) {
-            mCreationArgs.supportedModes = mode;
+        // If `configs` is nullptr, the injector creates RefreshRateConfigs from the `modes`.
+        // Otherwise, it uses `configs`, 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) {
+            mCreationArgs.supportedModes = std::move(modes);
+            mCreationArgs.activeModeId = activeModeId;
+            mCreationArgs.refreshRateConfigs = std::move(configs);
             return *this;
         }
 
@@ -730,22 +796,59 @@
         sp<DisplayDevice> inject() NO_THREAD_SAFETY_ANALYSIS {
             const auto displayId = mCreationArgs.compositionDisplay->getDisplayId();
 
+            auto& modes = mCreationArgs.supportedModes;
+            auto& activeModeId = mCreationArgs.activeModeId;
+
+            if (displayId && !mCreationArgs.refreshRateConfigs) {
+                if (const auto physicalId = PhysicalDisplayId::tryCast(*displayId)) {
+                    if (modes.empty()) {
+                        constexpr DisplayModeId kModeId{0};
+                        DisplayModePtr mode =
+                                DisplayMode::Builder(FakeHwcDisplayInjector::DEFAULT_ACTIVE_CONFIG)
+                                        .setId(kModeId)
+                                        .setPhysicalDisplayId(*physicalId)
+                                        .setResolution(FakeHwcDisplayInjector::DEFAULT_RESOLUTION)
+                                        .setVsyncPeriod(
+                                                FakeHwcDisplayInjector::DEFAULT_VSYNC_PERIOD)
+                                        .setDpiX(FakeHwcDisplayInjector::DEFAULT_DPI)
+                                        .setDpiY(FakeHwcDisplayInjector::DEFAULT_DPI)
+                                        .setGroup(FakeHwcDisplayInjector::DEFAULT_CONFIG_GROUP)
+                                        .build();
+
+                        modes = ftl::init::map(kModeId, std::move(mode));
+                        activeModeId = kModeId;
+                    }
+
+                    mCreationArgs.refreshRateConfigs =
+                            std::make_shared<scheduler::RefreshRateConfigs>(modes, activeModeId);
+                }
+            }
+
             DisplayDeviceState state;
             if (const auto type = mCreationArgs.connectionType) {
                 LOG_ALWAYS_FATAL_IF(!displayId);
                 const auto physicalId = PhysicalDisplayId::tryCast(*displayId);
                 LOG_ALWAYS_FATAL_IF(!physicalId);
                 LOG_ALWAYS_FATAL_IF(!mHwcDisplayId);
-                state.physical = {.id = *physicalId, .type = *type, .hwcDisplayId = *mHwcDisplayId};
+
+                const auto activeMode = modes.get(activeModeId);
+                LOG_ALWAYS_FATAL_IF(!activeMode);
+
+                state.physical = {.id = *physicalId,
+                                  .type = *type,
+                                  .hwcDisplayId = *mHwcDisplayId,
+                                  .deviceProductInfo = {},
+                                  .supportedModes = modes,
+                                  .activeMode = activeMode->get()};
             }
 
             state.isSecure = mCreationArgs.isSecure;
 
-            sp<DisplayDevice> device = new DisplayDevice(mCreationArgs);
-            if (!device->isVirtual()) {
-                device->setActiveMode(mActiveModeId);
+            sp<DisplayDevice> display = sp<DisplayDevice>::make(mCreationArgs);
+            if (!display->isVirtual()) {
+                display->setActiveMode(activeModeId);
             }
-            mFlinger.mutableDisplays().emplace(mDisplayToken, device);
+            mFlinger.mutableDisplays().emplace(mDisplayToken, display);
             mFlinger.mutableCurrentState().displays.add(mDisplayToken, state);
             mFlinger.mutableDrawingState().displays.add(mDisplayToken, state);
 
@@ -753,7 +856,7 @@
                 mFlinger.mutablePhysicalDisplayTokens()[physical->id] = mDisplayToken;
             }
 
-            return device;
+            return display;
         }
 
     private:
@@ -761,20 +864,16 @@
         sp<BBinder> mDisplayToken = new BBinder();
         DisplayDeviceCreationArgs mCreationArgs;
         const std::optional<hal::HWDisplayId> mHwcDisplayId;
-        DisplayModeId mActiveModeId;
     };
 
 private:
-    void setVsyncEnabled(bool) override {}
-    void changeRefreshRate(const Scheduler::RefreshRate&, Scheduler::ModeEvent) override {}
-    void repaintEverythingForHWC() override {}
-    void kernelTimerChanged(bool) override {}
-    void triggerOnFrameRateOverridesChanged() {}
+    constexpr static int64_t kVsyncId = 123;
 
     surfaceflinger::test::Factory mFactory;
-    sp<SurfaceFlinger> mFlinger = new SurfaceFlinger(mFactory, SurfaceFlinger::SkipInitialization);
-    TestableScheduler* mScheduler = nullptr;
-    std::shared_ptr<scheduler::RefreshRateConfigs> mRefreshRateConfigs;
+    sp<SurfaceFlinger> mFlinger;
+    scheduler::mock::SchedulerCallback mSchedulerCallback;
+    scheduler::mock::NoOpSchedulerCallback mNoOpSchedulerCallback;
+    scheduler::TestableScheduler* mScheduler = nullptr;
 };
 
 } // namespace android
diff --git a/services/surfaceflinger/tests/unittests/TimeStatsTest.cpp b/services/surfaceflinger/tests/unittests/TimeStatsTest.cpp
index 317cdf1..0ef8456 100644
--- a/services/surfaceflinger/tests/unittests/TimeStatsTest.cpp
+++ b/services/surfaceflinger/tests/unittests/TimeStatsTest.cpp
@@ -64,17 +64,15 @@
 #define LAYER_ID_0            0
 #define LAYER_ID_1            1
 #define UID_0                 123
-#define REFRESH_RATE_0        61
-#define RENDER_RATE_0         31
 #define REFRESH_RATE_BUCKET_0 60
 #define RENDER_RATE_BUCKET_0  30
 #define LAYER_ID_INVALID      -1
 #define NUM_LAYERS            1
 #define NUM_LAYERS_INVALID    "INVALID"
 
-const constexpr Fps kRefreshRate0 = Fps(static_cast<float>(REFRESH_RATE_0));
-const constexpr Fps kRenderRate0 = Fps(static_cast<float>(RENDER_RATE_0));
-static constexpr int32_t kGameMode = TimeStatsHelper::GameModeUnsupported;
+constexpr Fps kRefreshRate0 = 61_Hz;
+constexpr Fps kRenderRate0 = 31_Hz;
+constexpr GameMode kGameMode = GameMode::Unsupported;
 
 enum InputCommand : int32_t {
     ENABLE                 = 0,
@@ -147,14 +145,14 @@
     std::string inputCommand(InputCommand cmd, bool useProto);
 
     void setTimeStamp(TimeStamp type, int32_t id, uint64_t frameNumber, nsecs_t ts,
-                      TimeStats::SetFrameRateVote frameRateVote, int32_t gameMode);
+                      TimeStats::SetFrameRateVote, GameMode);
 
     int32_t genRandomInt32(int32_t begin, int32_t end);
 
     template <size_t N>
     void insertTimeRecord(const TimeStamp (&sequence)[N], int32_t id, uint64_t frameNumber,
                           nsecs_t ts, TimeStats::SetFrameRateVote frameRateVote = {},
-                          int32_t gameMode = kGameMode) {
+                          GameMode gameMode = kGameMode) {
         for (size_t i = 0; i < N; i++, ts += 1000000) {
             setTimeStamp(sequence[i], id, frameNumber, ts, frameRateVote, gameMode);
         }
@@ -205,7 +203,7 @@
 }
 
 void TimeStatsTest::setTimeStamp(TimeStamp type, int32_t id, uint64_t frameNumber, nsecs_t ts,
-                                 TimeStats::SetFrameRateVote frameRateVote, int32_t gameMode) {
+                                 TimeStats::SetFrameRateVote frameRateVote, GameMode gameMode) {
     switch (type) {
         case TimeStamp::POST:
             ASSERT_NO_FATAL_FAILURE(mTimeStats->setPostTime(id, frameNumber, genLayerName(id),
@@ -1170,8 +1168,7 @@
     constexpr nsecs_t APP_DEADLINE_DELTA_3MS = 3'000'000;
     EXPECT_TRUE(inputCommand(InputCommand::ENABLE, FMT_STRING).empty());
 
-    insertTimeRecord(NORMAL_SEQUENCE, LAYER_ID_0, 1, 1000000, {},
-                     TimeStatsHelper::GameModeStandard);
+    insertTimeRecord(NORMAL_SEQUENCE, LAYER_ID_0, 1, 1000000, {}, GameMode::Standard);
     for (size_t i = 0; i < LATE_ACQUIRE_FRAMES; i++) {
         mTimeStats->incrementLatchSkipped(LAYER_ID_0, TimeStats::LatchSkipReason::LateAcquire);
     }
@@ -1184,42 +1181,39 @@
                     TimeStats::SetFrameRateVote::FrameRateCompatibility::ExactOrMultiple,
             .seamlessness = TimeStats::SetFrameRateVote::Seamlessness::NotRequired,
     };
-    insertTimeRecord(NORMAL_SEQUENCE, LAYER_ID_0, 2, 2000000, frameRate60,
-                     TimeStatsHelper::GameModeStandard);
+    insertTimeRecord(NORMAL_SEQUENCE, LAYER_ID_0, 2, 2000000, frameRate60, GameMode::Standard);
 
-    mTimeStats->incrementJankyFrames(
-            {kRefreshRate0, kRenderRate0, UID_0, genLayerName(LAYER_ID_0),
-             TimeStatsHelper::GameModeStandard, JankType::SurfaceFlingerCpuDeadlineMissed,
-             DISPLAY_DEADLINE_DELTA, DISPLAY_PRESENT_JITTER, APP_DEADLINE_DELTA_3MS});
-    mTimeStats->incrementJankyFrames(
-            {kRefreshRate0, kRenderRate0, UID_0, genLayerName(LAYER_ID_0),
-             TimeStatsHelper::GameModeStandard, JankType::SurfaceFlingerGpuDeadlineMissed,
-             DISPLAY_DEADLINE_DELTA, DISPLAY_PRESENT_JITTER, APP_DEADLINE_DELTA_3MS});
     mTimeStats->incrementJankyFrames({kRefreshRate0, kRenderRate0, UID_0, genLayerName(LAYER_ID_0),
-                                      TimeStatsHelper::GameModeStandard, JankType::DisplayHAL,
+                                      GameMode::Standard, JankType::SurfaceFlingerCpuDeadlineMissed,
                                       DISPLAY_DEADLINE_DELTA, DISPLAY_PRESENT_JITTER,
                                       APP_DEADLINE_DELTA_3MS});
     mTimeStats->incrementJankyFrames({kRefreshRate0, kRenderRate0, UID_0, genLayerName(LAYER_ID_0),
-                                      TimeStatsHelper::GameModeStandard,
-                                      JankType::AppDeadlineMissed, DISPLAY_DEADLINE_DELTA,
+                                      GameMode::Standard, JankType::SurfaceFlingerGpuDeadlineMissed,
+                                      DISPLAY_DEADLINE_DELTA, DISPLAY_PRESENT_JITTER,
+                                      APP_DEADLINE_DELTA_3MS});
+    mTimeStats->incrementJankyFrames({kRefreshRate0, kRenderRate0, UID_0, genLayerName(LAYER_ID_0),
+                                      GameMode::Standard, JankType::DisplayHAL,
+                                      DISPLAY_DEADLINE_DELTA, DISPLAY_PRESENT_JITTER,
+                                      APP_DEADLINE_DELTA_3MS});
+    mTimeStats->incrementJankyFrames({kRefreshRate0, kRenderRate0, UID_0, genLayerName(LAYER_ID_0),
+                                      GameMode::Standard, JankType::AppDeadlineMissed,
+                                      DISPLAY_DEADLINE_DELTA, DISPLAY_PRESENT_JITTER,
+                                      APP_DEADLINE_DELTA_3MS});
+    mTimeStats->incrementJankyFrames({kRefreshRate0, kRenderRate0, UID_0, genLayerName(LAYER_ID_0),
+                                      GameMode::Standard, JankType::SurfaceFlingerScheduling,
+                                      DISPLAY_DEADLINE_DELTA, DISPLAY_PRESENT_JITTER,
+                                      APP_DEADLINE_DELTA_2MS});
+    mTimeStats->incrementJankyFrames({kRefreshRate0, kRenderRate0, UID_0, genLayerName(LAYER_ID_0),
+                                      GameMode::Standard, JankType::PredictionError,
+                                      DISPLAY_DEADLINE_DELTA, DISPLAY_PRESENT_JITTER,
+                                      APP_DEADLINE_DELTA_2MS});
+    mTimeStats->incrementJankyFrames(
+            {kRefreshRate0, kRenderRate0, UID_0, genLayerName(LAYER_ID_0), GameMode::Standard,
+             JankType::AppDeadlineMissed | JankType::BufferStuffing, DISPLAY_DEADLINE_DELTA,
+             APP_DEADLINE_DELTA_2MS, APP_DEADLINE_DELTA_2MS});
+    mTimeStats->incrementJankyFrames({kRefreshRate0, kRenderRate0, UID_0, genLayerName(LAYER_ID_0),
+                                      GameMode::Standard, JankType::None, DISPLAY_DEADLINE_DELTA,
                                       DISPLAY_PRESENT_JITTER, APP_DEADLINE_DELTA_3MS});
-    mTimeStats->incrementJankyFrames({kRefreshRate0, kRenderRate0, UID_0, genLayerName(LAYER_ID_0),
-                                      TimeStatsHelper::GameModeStandard,
-                                      JankType::SurfaceFlingerScheduling, DISPLAY_DEADLINE_DELTA,
-                                      DISPLAY_PRESENT_JITTER, APP_DEADLINE_DELTA_2MS});
-    mTimeStats->incrementJankyFrames({kRefreshRate0, kRenderRate0, UID_0, genLayerName(LAYER_ID_0),
-                                      TimeStatsHelper::GameModeStandard, JankType::PredictionError,
-                                      DISPLAY_DEADLINE_DELTA, DISPLAY_PRESENT_JITTER,
-                                      APP_DEADLINE_DELTA_2MS});
-    mTimeStats->incrementJankyFrames({kRefreshRate0, kRenderRate0, UID_0, genLayerName(LAYER_ID_0),
-                                      TimeStatsHelper::GameModeStandard,
-                                      JankType::AppDeadlineMissed | JankType::BufferStuffing,
-                                      DISPLAY_DEADLINE_DELTA, APP_DEADLINE_DELTA_2MS,
-                                      APP_DEADLINE_DELTA_2MS});
-    mTimeStats->incrementJankyFrames({kRefreshRate0, kRenderRate0, UID_0, genLayerName(LAYER_ID_0),
-                                      TimeStatsHelper::GameModeStandard, JankType::None,
-                                      DISPLAY_DEADLINE_DELTA, DISPLAY_PRESENT_JITTER,
-                                      APP_DEADLINE_DELTA_3MS});
 
     std::string pulledData;
     EXPECT_TRUE(mTimeStats->onPullAtom(10063 /*SURFACEFLINGER_STATS_LAYER_INFO*/, &pulledData));
@@ -1295,22 +1289,18 @@
     constexpr size_t BAD_DESIRED_PRESENT_FRAMES = 3;
     EXPECT_TRUE(inputCommand(InputCommand::ENABLE, FMT_STRING).empty());
 
-    insertTimeRecord(NORMAL_SEQUENCE, LAYER_ID_0, 1, 1000000, {},
-                     TimeStatsHelper::GameModeStandard);
+    insertTimeRecord(NORMAL_SEQUENCE, LAYER_ID_0, 1, 1000000, {}, GameMode::Standard);
     for (size_t i = 0; i < LATE_ACQUIRE_FRAMES; i++) {
         mTimeStats->incrementLatchSkipped(LAYER_ID_0, TimeStats::LatchSkipReason::LateAcquire);
     }
     for (size_t i = 0; i < BAD_DESIRED_PRESENT_FRAMES; i++) {
         mTimeStats->incrementBadDesiredPresent(LAYER_ID_0);
     }
-    insertTimeRecord(NORMAL_SEQUENCE, LAYER_ID_0, 2, 2000000, {},
-                     TimeStatsHelper::GameModeStandard);
 
-    insertTimeRecord(NORMAL_SEQUENCE, LAYER_ID_0, 3, 3000000, {},
-                     TimeStatsHelper::GameModePerformance);
-
-    insertTimeRecord(NORMAL_SEQUENCE, LAYER_ID_0, 4, 4000000, {}, TimeStatsHelper::GameModeBattery);
-    insertTimeRecord(NORMAL_SEQUENCE, LAYER_ID_0, 5, 4000000, {}, TimeStatsHelper::GameModeBattery);
+    insertTimeRecord(NORMAL_SEQUENCE, LAYER_ID_0, 2, 2000000, {}, GameMode::Standard);
+    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);
 
     std::string pulledData;
     EXPECT_TRUE(mTimeStats->onPullAtom(10063 /*SURFACEFLINGER_STATS_LAYER_INFO*/, &pulledData));
@@ -1498,14 +1488,14 @@
         EXPECT_THAT(result, HasSubstr(expectedResult)) << "failed for " << fps;
     };
 
-    verifyRefreshRateBucket(Fps(91.f), 90);
-    verifyRefreshRateBucket(Fps(89.f), 90);
+    verifyRefreshRateBucket(91_Hz, 90);
+    verifyRefreshRateBucket(89_Hz, 90);
 
-    verifyRefreshRateBucket(Fps(61.f), 60);
-    verifyRefreshRateBucket(Fps(59.f), 60);
+    verifyRefreshRateBucket(61_Hz, 60);
+    verifyRefreshRateBucket(59_Hz, 60);
 
-    verifyRefreshRateBucket(Fps(31.f), 30);
-    verifyRefreshRateBucket(Fps(29.f), 30);
+    verifyRefreshRateBucket(31_Hz, 30);
+    verifyRefreshRateBucket(29_Hz, 30);
 }
 
 } // namespace
diff --git a/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp b/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp
index 1a50427..ded7531 100644
--- a/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp
+++ b/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp
@@ -24,12 +24,11 @@
 #include <gtest/gtest.h>
 #include <gui/SurfaceComposerClient.h>
 #include <log/log.h>
+#include <ui/MockFence.h>
 #include <utils/String8.h>
 
-#include "TestableScheduler.h"
 #include "TestableSurfaceFlinger.h"
 #include "mock/MockEventThread.h"
-#include "mock/MockMessageQueue.h"
 #include "mock/MockVsyncController.h"
 
 namespace android {
@@ -46,7 +45,6 @@
                 ::testing::UnitTest::GetInstance()->current_test_info();
         ALOGD("**** Setting up for %s.%s\n", test_info->test_case_name(), test_info->name());
 
-        mFlinger.mutableEventQueue().reset(mMessageQueue);
         setupScheduler();
     }
 
@@ -80,12 +78,8 @@
                                 std::move(eventThread), std::move(sfEventThread));
     }
 
-    TestableScheduler* mScheduler;
     TestableSurfaceFlinger mFlinger;
 
-    std::unique_ptr<mock::EventThread> mEventThread = std::make_unique<mock::EventThread>();
-
-    mock::MessageQueue* mMessageQueue = new mock::MessageQueue();
     mock::VsyncController* mVsyncController = new mock::VsyncController();
     mock::VSyncTracker* mVSyncTracker = new mock::VSyncTracker();
 
@@ -103,7 +97,7 @@
         static_assert(0xffffffffffffffff == static_cast<uint64_t>(-1));
     };
 
-    void checkEqual(TransactionInfo info, SurfaceFlinger::TransactionState state) {
+    void checkEqual(TransactionInfo info, TransactionState state) {
         EXPECT_EQ(0u, info.states.size());
         EXPECT_EQ(0u, state.states.size());
 
@@ -126,8 +120,7 @@
 
     void NotPlacedOnTransactionQueue(uint32_t flags, bool syncInputWindows) {
         ASSERT_EQ(0u, mFlinger.getTransactionQueue().size());
-        // called in SurfaceFlinger::signalTransaction
-        EXPECT_CALL(*mMessageQueue, invalidate()).Times(1);
+        EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(1);
         TransactionInfo transaction;
         setupSingle(transaction, flags, syncInputWindows,
                     /*desiredPresentTime*/ systemTime(), /*isAutoTimestamp*/ true,
@@ -146,9 +139,9 @@
         // completed.  If this is animation, it should not time out waiting.
         nsecs_t returnedTime = systemTime();
         if (flags & ISurfaceComposer::eSynchronous || syncInputWindows) {
-            EXPECT_GE(returnedTime, applicationTime + s2ns(5));
+            EXPECT_GE(returnedTime, applicationTime + mFlinger.getAnimationTransactionTimeout());
         } else {
-            EXPECT_LE(returnedTime, applicationTime + s2ns(5));
+            EXPECT_LE(returnedTime, applicationTime + mFlinger.getAnimationTransactionTimeout());
         }
         // Each transaction should have been placed on the transaction queue
         auto transactionQueue = mFlinger.getTransactionQueue();
@@ -157,8 +150,7 @@
 
     void PlaceOnTransactionQueue(uint32_t flags, bool syncInputWindows) {
         ASSERT_EQ(0u, mFlinger.getTransactionQueue().size());
-        // called in SurfaceFlinger::signalTransaction
-        EXPECT_CALL(*mMessageQueue, invalidate()).Times(1);
+        EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(1);
 
         // first check will see desired present time has not passed,
         // but afterwards it will look like the desired present time has passed
@@ -176,9 +168,11 @@
 
         nsecs_t returnedTime = systemTime();
         if ((flags & ISurfaceComposer::eSynchronous) || syncInputWindows) {
-            EXPECT_GE(systemTime(), applicationSentTime + s2ns(5));
+            EXPECT_GE(systemTime(),
+                      applicationSentTime + mFlinger.getAnimationTransactionTimeout());
         } else {
-            EXPECT_LE(returnedTime, applicationSentTime + s2ns(5));
+            EXPECT_LE(returnedTime,
+                      applicationSentTime + mFlinger.getAnimationTransactionTimeout());
         }
         // This transaction should have been placed on the transaction queue
         auto transactionQueue = mFlinger.getTransactionQueue();
@@ -187,12 +181,11 @@
 
     void BlockedByPriorTransaction(uint32_t flags, bool syncInputWindows) {
         ASSERT_EQ(0u, mFlinger.getTransactionQueue().size());
-        // called in SurfaceFlinger::signalTransaction
         nsecs_t time = systemTime();
         if (!syncInputWindows) {
-            EXPECT_CALL(*mMessageQueue, invalidate()).Times(2);
+            EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(2);
         } else {
-            EXPECT_CALL(*mMessageQueue, invalidate()).Times(1);
+            EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(1);
         }
         // transaction that should go on the pending thread
         TransactionInfo transactionA;
@@ -217,7 +210,7 @@
         // This thread should not have been blocked by the above transaction
         // (5s is the timeout period that applyTransactionState waits for SF to
         // commit the transaction)
-        EXPECT_LE(systemTime(), applicationSentTime + s2ns(5));
+        EXPECT_LE(systemTime(), applicationSentTime + mFlinger.getAnimationTransactionTimeout());
         // transaction that would goes to pending transaciton queue.
         mFlinger.flushTransactionQueues();
 
@@ -233,11 +226,13 @@
         // if this is an animation, this thread should be blocked for 5s
         // in setTransactionState waiting for transactionA to flush.  Otherwise,
         // the transaction should be placed on the pending queue
-        if (flags & (ISurfaceComposer::eAnimation | ISurfaceComposer::eSynchronous) ||
+        if (flags & (ISurfaceComposer::eSynchronous) ||
             syncInputWindows) {
-            EXPECT_GE(systemTime(), applicationSentTime + s2ns(5));
+            EXPECT_GE(systemTime(),
+                      applicationSentTime + mFlinger.getAnimationTransactionTimeout());
         } else {
-            EXPECT_LE(systemTime(), applicationSentTime + s2ns(5));
+            EXPECT_LE(systemTime(),
+                      applicationSentTime + mFlinger.getAnimationTransactionTimeout());
         }
 
         // transaction that would goes to pending transaciton queue.
@@ -255,8 +250,7 @@
 
 TEST_F(TransactionApplicationTest, Flush_RemovesFromQueue) {
     ASSERT_EQ(0u, mFlinger.getTransactionQueue().size());
-    // called in SurfaceFlinger::signalTransaction
-    EXPECT_CALL(*mMessageQueue, invalidate()).Times(1);
+    EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(1);
 
     TransactionInfo transactionA; // transaction to go on pending queue
     setupSingle(transactionA, /*flags*/ 0, /*syncInputWindows*/ false,
@@ -294,10 +288,6 @@
     NotPlacedOnTransactionQueue(ISurfaceComposer::eSynchronous, /*syncInputWindows*/ false);
 }
 
-TEST_F(TransactionApplicationTest, NotPlacedOnTransactionQueue_Animation) {
-    NotPlacedOnTransactionQueue(ISurfaceComposer::eAnimation, /*syncInputWindows*/ false);
-}
-
 TEST_F(TransactionApplicationTest, NotPlacedOnTransactionQueue_SyncInputWindows) {
     NotPlacedOnTransactionQueue(/*flags*/ 0, /*syncInputWindows*/ true);
 }
@@ -306,10 +296,6 @@
     PlaceOnTransactionQueue(ISurfaceComposer::eSynchronous, /*syncInputWindows*/ false);
 }
 
-TEST_F(TransactionApplicationTest, PlaceOnTransactionQueue_Animation) {
-    PlaceOnTransactionQueue(ISurfaceComposer::eAnimation, /*syncInputWindows*/ false);
-}
-
 TEST_F(TransactionApplicationTest, PlaceOnTransactionQueue_SyncInputWindows) {
     PlaceOnTransactionQueue(/*flags*/ 0, /*syncInputWindows*/ true);
 }
@@ -331,4 +317,769 @@
     auto ret = mFlinger.fromHandle(badHandle);
     EXPECT_EQ(nullptr, ret.promote().get());
 }
+
+class LatchUnsignaledTest : public TransactionApplicationTest {
+public:
+    void TearDown() override {
+        // Clear all transaction queues to release all transactions we sent
+        // in the tests. Otherwise, gmock complains about memory leaks.
+        mFlinger.getTransactionQueue().clear();
+        mFlinger.getPendingTransactionQueue().clear();
+        mFlinger.getTransactionCommittedSignals().clear();
+        mFlinger.commitTransactionsLocked(eTransactionMask);
+        mFlinger.mutableCurrentState().layersSortedByZ.clear();
+        mFlinger.mutableDrawingState().layersSortedByZ.clear();
+    }
+
+    static sp<Fence> fence(Fence::Status status) {
+        const auto fence = sp<mock::MockFence>::make();
+        EXPECT_CALL(*fence, getStatus()).WillRepeatedly(Return(status));
+        return fence;
+    }
+
+    ComposerState createComposerState(int layerId, sp<Fence> fence, uint64_t what) {
+        ComposerState state;
+        state.state.bufferData = std::make_shared<BufferData>();
+        state.state.bufferData->acquireFence = std::move(fence);
+        state.state.layerId = layerId;
+        state.state.surface =
+                sp<BufferStateLayer>::make(
+                        LayerCreationArgs(mFlinger.flinger(), nullptr, "TestLayer", 0, {}))
+                        ->getHandle();
+        state.state.bufferData->flags = BufferData::BufferDataChange::fenceChanged;
+
+        state.state.what = what;
+        if (what & layer_state_t::eCropChanged) {
+            state.state.crop = Rect(1, 2, 3, 4);
+        }
+        return state;
+    }
+
+    TransactionInfo createTransactionInfo(const sp<IBinder>& applyToken,
+                                          const std::vector<ComposerState>& states) {
+        TransactionInfo transaction;
+        const uint32_t kFlags = ISurfaceComposer::eSynchronous;
+        const bool kSyncInputWindows = false;
+        const nsecs_t kDesiredPresentTime = systemTime();
+        const bool kIsAutoTimestamp = true;
+        const auto kFrameTimelineInfo = FrameTimelineInfo{};
+
+        setupSingle(transaction, kFlags, kSyncInputWindows, kDesiredPresentTime, kIsAutoTimestamp,
+                    kFrameTimelineInfo);
+        transaction.applyToken = applyToken;
+        for (const auto& state : states) {
+            transaction.states.push_back(state);
+        }
+
+        return transaction;
+    }
+
+    void setTransactionStates(const std::vector<TransactionInfo>& transactions,
+                              size_t expectedTransactionsApplied,
+                              size_t expectedTransactionsPending) {
+        EXPECT_EQ(0u, mFlinger.getTransactionQueue().size());
+        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);
+        }
+        mFlinger.flushTransactionQueues();
+        EXPECT_EQ(0u, mFlinger.getTransactionQueue().size());
+        EXPECT_EQ(expectedTransactionsPending, mFlinger.getPendingTransactionQueue().size());
+        EXPECT_EQ(expectedTransactionsApplied, mFlinger.getTransactionCommittedSignals().size());
+    }
+};
+
+class LatchUnsignaledAutoSingleLayerTest : public LatchUnsignaledTest {
+public:
+    void SetUp() override {
+        LatchUnsignaledTest::SetUp();
+        SurfaceFlinger::enableLatchUnsignaledConfig = LatchUnsignaledConfig::AutoSingleLayer;
+    }
+};
+
+TEST_F(LatchUnsignaledAutoSingleLayerTest, Flush_RemovesSingleSignaledFromTheQueue) {
+    const sp<IBinder> kApplyToken =
+            IInterface::asBinder(TransactionCompletedListener::getIInstance());
+    const auto kLayerId = 1;
+    const auto kExpectedTransactionsApplied = 1u;
+    const auto kExpectedTransactionsPending = 0u;
+
+    const auto signaledTransaction =
+            createTransactionInfo(kApplyToken,
+                                  {createComposerState(kLayerId, fence(Fence::Status::Signaled),
+                                                       layer_state_t::eBufferChanged)});
+    setTransactionStates({signaledTransaction}, kExpectedTransactionsApplied,
+                         kExpectedTransactionsPending);
+}
+
+TEST_F(LatchUnsignaledAutoSingleLayerTest, Flush_RemovesSingleUnSignaledFromTheQueue) {
+    const sp<IBinder> kApplyToken =
+            IInterface::asBinder(TransactionCompletedListener::getIInstance());
+    const auto kLayerId = 1;
+    const auto kExpectedTransactionsApplied = 1u;
+    const auto kExpectedTransactionsPending = 0u;
+
+    const auto unsignaledTransaction =
+            createTransactionInfo(kApplyToken,
+                                  {
+                                          createComposerState(kLayerId,
+                                                              fence(Fence::Status::Unsignaled),
+                                                              layer_state_t::eBufferChanged),
+                                  });
+    setTransactionStates({unsignaledTransaction}, kExpectedTransactionsApplied,
+                         kExpectedTransactionsPending);
+}
+
+TEST_F(LatchUnsignaledAutoSingleLayerTest, Flush_KeepsUnSignaledInTheQueue_NonBufferCropChange) {
+    const sp<IBinder> kApplyToken =
+            IInterface::asBinder(TransactionCompletedListener::getIInstance());
+    const auto kLayerId = 1;
+    const auto kExpectedTransactionsApplied = 0u;
+    const auto kExpectedTransactionsPending = 1u;
+
+    const auto unsignaledTransaction =
+            createTransactionInfo(kApplyToken,
+                                  {
+                                          createComposerState(kLayerId,
+                                                              fence(Fence::Status::Unsignaled),
+                                                              layer_state_t::eCropChanged),
+                                  });
+    setTransactionStates({unsignaledTransaction}, kExpectedTransactionsApplied,
+                         kExpectedTransactionsPending);
+}
+
+TEST_F(LatchUnsignaledAutoSingleLayerTest, Flush_KeepsUnSignaledInTheQueue_NonBufferChangeClubed) {
+    const sp<IBinder> kApplyToken =
+            IInterface::asBinder(TransactionCompletedListener::getIInstance());
+    const auto kLayerId = 1;
+    const auto kExpectedTransactionsApplied = 0u;
+    const auto kExpectedTransactionsPending = 1u;
+
+    const auto unsignaledTransaction =
+            createTransactionInfo(kApplyToken,
+                                  {
+                                          createComposerState(kLayerId,
+                                                              fence(Fence::Status::Unsignaled),
+                                                              layer_state_t::eCropChanged |
+                                                                      layer_state_t::
+                                                                              eBufferChanged),
+                                  });
+    setTransactionStates({unsignaledTransaction}, kExpectedTransactionsApplied,
+                         kExpectedTransactionsPending);
+}
+
+TEST_F(LatchUnsignaledAutoSingleLayerTest, Flush_KeepsInTheQueueSameApplyTokenMultiState) {
+    const sp<IBinder> kApplyToken =
+            IInterface::asBinder(TransactionCompletedListener::getIInstance());
+    const auto kLayerId = 1;
+    const auto kExpectedTransactionsApplied = 0u;
+    const auto kExpectedTransactionsPending = 1u;
+
+    const auto mixedTransaction =
+            createTransactionInfo(kApplyToken,
+                                  {
+                                          createComposerState(kLayerId,
+                                                              fence(Fence::Status::Unsignaled),
+                                                              layer_state_t::eBufferChanged),
+                                          createComposerState(kLayerId,
+                                                              fence(Fence::Status::Signaled),
+                                                              layer_state_t::eBufferChanged),
+                                  });
+    setTransactionStates({mixedTransaction}, kExpectedTransactionsApplied,
+                         kExpectedTransactionsPending);
+}
+
+TEST_F(LatchUnsignaledAutoSingleLayerTest, Flush_KeepsInTheQueue_MultipleStateTransaction) {
+    const sp<IBinder> kApplyToken =
+            IInterface::asBinder(TransactionCompletedListener::getIInstance());
+    const auto kLayerId1 = 1;
+    const auto kLayerId2 = 2;
+    const auto kExpectedTransactionsApplied = 0u;
+    const auto kExpectedTransactionsPending = 1u;
+
+    const auto mixedTransaction =
+            createTransactionInfo(kApplyToken,
+                                  {
+                                          createComposerState(kLayerId1,
+                                                              fence(Fence::Status::Unsignaled),
+                                                              layer_state_t::eBufferChanged),
+                                          createComposerState(kLayerId2,
+                                                              fence(Fence::Status::Signaled),
+                                                              layer_state_t::eBufferChanged),
+                                  });
+    setTransactionStates({mixedTransaction}, kExpectedTransactionsApplied,
+                         kExpectedTransactionsPending);
+}
+
+TEST_F(LatchUnsignaledAutoSingleLayerTest, Flush_RemovesSignaledFromTheQueue) {
+    const sp<IBinder> kApplyToken =
+            IInterface::asBinder(TransactionCompletedListener::getIInstance());
+    const auto kLayerId1 = 1;
+    const auto kLayerId2 = 2;
+    const auto kExpectedTransactionsApplied = 2u;
+    const auto kExpectedTransactionsPending = 0u;
+
+    const auto signaledTransaction =
+            createTransactionInfo(kApplyToken,
+                                  {
+                                          createComposerState(kLayerId1,
+                                                              fence(Fence::Status::Signaled),
+                                                              layer_state_t::eBufferChanged),
+                                  });
+    const auto signaledTransaction2 =
+            createTransactionInfo(kApplyToken,
+                                  {
+                                          createComposerState(kLayerId2,
+                                                              fence(Fence::Status::Signaled),
+                                                              layer_state_t::eBufferChanged),
+                                  });
+    setTransactionStates({signaledTransaction, signaledTransaction2}, kExpectedTransactionsApplied,
+                         kExpectedTransactionsPending);
+}
+
+TEST_F(LatchUnsignaledAutoSingleLayerTest,
+       UnsignaledNotAppliedWhenThereAreSignaled_UnsignaledFirst) {
+    const sp<IBinder> kApplyToken1 =
+            IInterface::asBinder(TransactionCompletedListener::getIInstance());
+    const sp<IBinder> kApplyToken2 = sp<BBinder>::make();
+    const sp<IBinder> kApplyToken3 = sp<BBinder>::make();
+    const auto kLayerId1 = 1;
+    const auto kLayerId2 = 2;
+    const auto kExpectedTransactionsApplied = 2u;
+    const auto kExpectedTransactionsPending = 1u;
+
+    const auto unsignaledTransaction =
+            createTransactionInfo(kApplyToken1,
+                                  {
+                                          createComposerState(kLayerId1,
+                                                              fence(Fence::Status::Unsignaled),
+                                                              layer_state_t::eBufferChanged),
+                                  });
+
+    const auto signaledTransaction =
+            createTransactionInfo(kApplyToken2,
+                                  {
+                                          createComposerState(kLayerId2,
+                                                              fence(Fence::Status::Signaled),
+                                                              layer_state_t::eBufferChanged),
+                                  });
+    const auto signaledTransaction2 =
+            createTransactionInfo(kApplyToken3,
+                                  {
+                                          createComposerState(kLayerId2,
+                                                              fence(Fence::Status::Signaled),
+                                                              layer_state_t::eBufferChanged),
+                                  });
+
+    setTransactionStates({unsignaledTransaction, signaledTransaction, signaledTransaction2},
+                         kExpectedTransactionsApplied, kExpectedTransactionsPending);
+}
+
+TEST_F(LatchUnsignaledAutoSingleLayerTest, UnsignaledNotAppliedWhenThereAreSignaled_SignaledFirst) {
+    const sp<IBinder> kApplyToken1 =
+            IInterface::asBinder(TransactionCompletedListener::getIInstance());
+    const sp<IBinder> kApplyToken2 = sp<BBinder>::make();
+    const sp<IBinder> kApplyToken3 = sp<BBinder>::make();
+    const auto kLayerId1 = 1;
+    const auto kLayerId2 = 2;
+    const auto kExpectedTransactionsApplied = 2u;
+    const auto kExpectedTransactionsPending = 1u;
+
+    const auto signaledTransaction =
+            createTransactionInfo(kApplyToken1,
+                                  {
+                                          createComposerState(kLayerId1,
+                                                              fence(Fence::Status::Signaled),
+                                                              layer_state_t::eBufferChanged),
+                                  });
+    const auto signaledTransaction2 =
+            createTransactionInfo(kApplyToken2,
+                                  {
+                                          createComposerState(kLayerId1,
+                                                              fence(Fence::Status::Signaled),
+                                                              layer_state_t::eBufferChanged),
+                                  });
+    const auto unsignaledTransaction =
+            createTransactionInfo(kApplyToken3,
+                                  {
+                                          createComposerState(kLayerId2,
+                                                              fence(Fence::Status::Unsignaled),
+                                                              layer_state_t::eBufferChanged),
+                                  });
+
+    setTransactionStates({signaledTransaction, signaledTransaction2, unsignaledTransaction},
+                         kExpectedTransactionsApplied, kExpectedTransactionsPending);
+}
+
+TEST_F(LatchUnsignaledAutoSingleLayerTest, Flush_KeepsTransactionInTheQueueSameApplyToken) {
+    const sp<IBinder> kApplyToken =
+            IInterface::asBinder(TransactionCompletedListener::getIInstance());
+    const auto kLayerId1 = 1;
+    const auto kLayerId2 = 2;
+    const auto kExpectedTransactionsApplied = 1u;
+    const auto kExpectedTransactionsPending = 1u;
+
+    const auto unsignaledTransaction =
+            createTransactionInfo(kApplyToken,
+                                  {
+                                          createComposerState(kLayerId1,
+                                                              fence(Fence::Status::Unsignaled),
+                                                              layer_state_t::eBufferChanged),
+                                  });
+    const auto signaledTransaction =
+            createTransactionInfo(kApplyToken,
+                                  {
+                                          createComposerState(kLayerId2,
+                                                              fence(Fence::Status::Signaled),
+                                                              layer_state_t::eBufferChanged),
+                                  });
+    setTransactionStates({unsignaledTransaction, signaledTransaction}, kExpectedTransactionsApplied,
+                         kExpectedTransactionsPending);
+}
+
+TEST_F(LatchUnsignaledAutoSingleLayerTest, Flush_KeepsTransactionInTheQueue) {
+    const sp<IBinder> kApplyToken1 =
+            IInterface::asBinder(TransactionCompletedListener::getIInstance());
+    const sp<IBinder> kApplyToken2 = sp<BBinder>::make();
+    const auto kLayerId1 = 1;
+    const auto kLayerId2 = 2;
+    const auto kExpectedTransactionsApplied = 1u;
+    const auto kExpectedTransactionsPending = 1u;
+
+    const auto unsignaledTransaction =
+            createTransactionInfo(kApplyToken1,
+                                  {
+                                          createComposerState(kLayerId1,
+                                                              fence(Fence::Status::Unsignaled),
+                                                              layer_state_t::eBufferChanged),
+                                  });
+    const auto unsignaledTransaction2 =
+            createTransactionInfo(kApplyToken2,
+                                  {
+                                          createComposerState(kLayerId2,
+                                                              fence(Fence::Status::Unsignaled),
+                                                              layer_state_t::eBufferChanged),
+                                  });
+    setTransactionStates({unsignaledTransaction, unsignaledTransaction2},
+                         kExpectedTransactionsApplied, kExpectedTransactionsPending);
+}
+
+TEST_F(LatchUnsignaledAutoSingleLayerTest, DontLatchUnsignaledWhenEarlyOffset) {
+    const sp<IBinder> kApplyToken =
+            IInterface::asBinder(TransactionCompletedListener::getIInstance());
+    const auto kLayerId = 1;
+    const auto kExpectedTransactionsApplied = 0u;
+    const auto kExpectedTransactionsPending = 1u;
+
+    const auto unsignaledTransaction =
+            createTransactionInfo(kApplyToken,
+                                  {
+                                          createComposerState(kLayerId,
+                                                              fence(Fence::Status::Unsignaled),
+                                                              layer_state_t::eBufferChanged),
+                                  });
+
+    // Get VsyncModulator out of the default config
+    static_cast<void>(mFlinger.mutableVsyncModulator()->onRefreshRateChangeInitiated());
+
+    setTransactionStates({unsignaledTransaction}, kExpectedTransactionsApplied,
+                         kExpectedTransactionsPending);
+}
+
+class LatchUnsignaledDisabledTest : public LatchUnsignaledTest {
+public:
+    void SetUp() override {
+        LatchUnsignaledTest::SetUp();
+        SurfaceFlinger::enableLatchUnsignaledConfig = LatchUnsignaledConfig::Disabled;
+    }
+};
+
+TEST_F(LatchUnsignaledDisabledTest, Flush_RemovesSignaledFromTheQueue) {
+    const sp<IBinder> kApplyToken =
+            IInterface::asBinder(TransactionCompletedListener::getIInstance());
+    const auto kLayerId = 1;
+    const auto kExpectedTransactionsApplied = 1u;
+    const auto kExpectedTransactionsPending = 0u;
+
+    const auto signaledTransaction =
+            createTransactionInfo(kApplyToken,
+                                  {createComposerState(kLayerId, fence(Fence::Status::Signaled),
+                                                       layer_state_t::eBufferChanged)});
+    setTransactionStates({signaledTransaction}, kExpectedTransactionsApplied,
+                         kExpectedTransactionsPending);
+}
+
+TEST_F(LatchUnsignaledDisabledTest, Flush_KeepsInTheQueue) {
+    const sp<IBinder> kApplyToken =
+            IInterface::asBinder(TransactionCompletedListener::getIInstance());
+    const auto kLayerId = 1;
+    const auto kExpectedTransactionsApplied = 0u;
+    const auto kExpectedTransactionsPending = 1u;
+
+    const auto unsignaledTransaction =
+            createTransactionInfo(kApplyToken,
+                                  {
+                                          createComposerState(kLayerId,
+                                                              fence(Fence::Status::Unsignaled),
+                                                              layer_state_t::eBufferChanged),
+                                  });
+    setTransactionStates({unsignaledTransaction}, kExpectedTransactionsApplied,
+                         kExpectedTransactionsPending);
+}
+
+TEST_F(LatchUnsignaledDisabledTest, Flush_KeepsInTheQueueSameLayerId) {
+    const sp<IBinder> kApplyToken =
+            IInterface::asBinder(TransactionCompletedListener::getIInstance());
+    const auto kLayerId = 1;
+    const auto kExpectedTransactionsApplied = 0u;
+    const auto kExpectedTransactionsPending = 1u;
+
+    const auto unsignaledTransaction =
+            createTransactionInfo(kApplyToken,
+                                  {
+                                          createComposerState(kLayerId,
+                                                              fence(Fence::Status::Unsignaled),
+                                                              layer_state_t::eBufferChanged),
+                                          createComposerState(kLayerId,
+                                                              fence(Fence::Status::Unsignaled),
+                                                              layer_state_t::eBufferChanged),
+                                  });
+    setTransactionStates({unsignaledTransaction}, kExpectedTransactionsApplied,
+                         kExpectedTransactionsPending);
+}
+
+TEST_F(LatchUnsignaledDisabledTest, Flush_KeepsInTheQueueDifferentLayerId) {
+    const sp<IBinder> kApplyToken =
+            IInterface::asBinder(TransactionCompletedListener::getIInstance());
+    const auto kLayerId1 = 1;
+    const auto kLayerId2 = 2;
+    const auto kExpectedTransactionsApplied = 0u;
+    const auto kExpectedTransactionsPending = 1u;
+
+    const auto unsignaledTransaction =
+            createTransactionInfo(kApplyToken,
+                                  {
+                                          createComposerState(kLayerId1,
+                                                              fence(Fence::Status::Unsignaled),
+                                                              layer_state_t::eBufferChanged),
+                                          createComposerState(kLayerId2,
+                                                              fence(Fence::Status::Unsignaled),
+                                                              layer_state_t::eBufferChanged),
+                                  });
+    setTransactionStates({unsignaledTransaction}, kExpectedTransactionsApplied,
+                         kExpectedTransactionsPending);
+}
+
+TEST_F(LatchUnsignaledDisabledTest, Flush_RemovesSignaledFromTheQueue_MultipleLayers) {
+    const sp<IBinder> kApplyToken =
+            IInterface::asBinder(TransactionCompletedListener::getIInstance());
+    const auto kLayerId1 = 1;
+    const auto kLayerId2 = 2;
+    const auto kExpectedTransactionsApplied = 2u;
+    const auto kExpectedTransactionsPending = 0u;
+
+    const auto signaledTransaction =
+            createTransactionInfo(kApplyToken,
+                                  {
+                                          createComposerState(kLayerId1,
+                                                              fence(Fence::Status::Signaled),
+                                                              layer_state_t::eBufferChanged),
+                                  });
+    const auto signaledTransaction2 =
+            createTransactionInfo(kApplyToken,
+                                  {
+                                          createComposerState(kLayerId2,
+                                                              fence(Fence::Status::Signaled),
+                                                              layer_state_t::eBufferChanged),
+                                  });
+    setTransactionStates({signaledTransaction, signaledTransaction2}, kExpectedTransactionsApplied,
+                         kExpectedTransactionsPending);
+}
+
+TEST_F(LatchUnsignaledDisabledTest, Flush_KeepInTheQueueDifferentApplyToken) {
+    const sp<IBinder> kApplyToken1 =
+            IInterface::asBinder(TransactionCompletedListener::getIInstance());
+    const sp<IBinder> kApplyToken2 = sp<BBinder>::make();
+    const auto kLayerId1 = 1;
+    const auto kLayerId2 = 2;
+    const auto kExpectedTransactionsApplied = 1u;
+    const auto kExpectedTransactionsPending = 1u;
+
+    const auto unsignaledTransaction =
+            createTransactionInfo(kApplyToken1,
+                                  {
+                                          createComposerState(kLayerId1,
+                                                              fence(Fence::Status::Unsignaled),
+                                                              layer_state_t::eBufferChanged),
+                                  });
+    const auto signaledTransaction =
+            createTransactionInfo(kApplyToken2,
+                                  {
+                                          createComposerState(kLayerId2,
+                                                              fence(Fence::Status::Signaled),
+                                                              layer_state_t::eBufferChanged),
+                                  });
+    setTransactionStates({unsignaledTransaction, signaledTransaction}, kExpectedTransactionsApplied,
+                         kExpectedTransactionsPending);
+}
+
+TEST_F(LatchUnsignaledDisabledTest, Flush_KeepInTheQueueSameApplyToken) {
+    const sp<IBinder> kApplyToken =
+            IInterface::asBinder(TransactionCompletedListener::getIInstance());
+    const auto kLayerId1 = 1;
+    const auto kLayerId2 = 2;
+    const auto kExpectedTransactionsApplied = 1u;
+    const auto kExpectedTransactionsPending = 1u;
+
+    const auto signaledTransaction =
+            createTransactionInfo(kApplyToken,
+                                  {
+                                          createComposerState(kLayerId1,
+                                                              fence(Fence::Status::Signaled),
+                                                              layer_state_t::eBufferChanged),
+                                  });
+    const auto unsignaledTransaction =
+            createTransactionInfo(kApplyToken,
+                                  {
+                                          createComposerState(kLayerId2,
+                                                              fence(Fence::Status::Unsignaled),
+                                                              layer_state_t::eBufferChanged),
+                                  });
+    setTransactionStates({signaledTransaction, unsignaledTransaction}, kExpectedTransactionsApplied,
+                         kExpectedTransactionsPending);
+}
+
+TEST_F(LatchUnsignaledDisabledTest, Flush_KeepInTheUnsignaledTheQueue) {
+    const sp<IBinder> kApplyToken =
+            IInterface::asBinder(TransactionCompletedListener::getIInstance());
+    const auto kLayerId1 = 1;
+    const auto kLayerId2 = 2;
+    const auto kExpectedTransactionsApplied = 0u;
+    const auto kExpectedTransactionsPending = 1u;
+
+    const auto unsignaledTransaction =
+            createTransactionInfo(kApplyToken,
+                                  {
+                                          createComposerState(kLayerId1,
+                                                              fence(Fence::Status::Unsignaled),
+                                                              layer_state_t::eBufferChanged),
+                                  });
+    const auto unsignaledTransaction2 =
+            createTransactionInfo(kApplyToken,
+                                  {
+                                          createComposerState(kLayerId2,
+                                                              fence(Fence::Status::Unsignaled),
+                                                              layer_state_t::eBufferChanged),
+                                  });
+    setTransactionStates({unsignaledTransaction, unsignaledTransaction2},
+                         kExpectedTransactionsApplied, kExpectedTransactionsPending);
+}
+
+class LatchUnsignaledAlwaysTest : public LatchUnsignaledTest {
+public:
+    void SetUp() override {
+        LatchUnsignaledTest::SetUp();
+        SurfaceFlinger::enableLatchUnsignaledConfig = LatchUnsignaledConfig::Always;
+    }
+};
+
+TEST_F(LatchUnsignaledAlwaysTest, Flush_RemovesSignaledFromTheQueue) {
+    const sp<IBinder> kApplyToken =
+            IInterface::asBinder(TransactionCompletedListener::getIInstance());
+    const auto kLayerId = 1;
+    const auto kExpectedTransactionsApplied = 1u;
+    const auto kExpectedTransactionsPending = 0u;
+
+    const auto signaledTransaction =
+            createTransactionInfo(kApplyToken,
+                                  {createComposerState(kLayerId, fence(Fence::Status::Signaled),
+                                                       layer_state_t::eBufferChanged)});
+    setTransactionStates({signaledTransaction}, kExpectedTransactionsApplied,
+                         kExpectedTransactionsPending);
+}
+
+TEST_F(LatchUnsignaledAlwaysTest, Flush_RemovesFromTheQueue) {
+    const sp<IBinder> kApplyToken =
+            IInterface::asBinder(TransactionCompletedListener::getIInstance());
+    const auto kLayerId = 1;
+    const auto kExpectedTransactionsApplied = 1u;
+    const auto kExpectedTransactionsPending = 0u;
+
+    const auto unsignaledTransaction =
+            createTransactionInfo(kApplyToken,
+                                  {createComposerState(kLayerId, fence(Fence::Status::Unsignaled),
+                                                       layer_state_t::eBufferChanged)});
+    setTransactionStates({unsignaledTransaction}, kExpectedTransactionsApplied,
+                         kExpectedTransactionsPending);
+}
+
+TEST_F(LatchUnsignaledAlwaysTest, Flush_RemovesFromTheQueueSameLayerId) {
+    const sp<IBinder> kApplyToken =
+            IInterface::asBinder(TransactionCompletedListener::getIInstance());
+    const auto kLayerId = 1;
+    const auto kExpectedTransactionsApplied = 1u;
+    const auto kExpectedTransactionsPending = 0u;
+
+    const auto mixedTransaction =
+            createTransactionInfo(kApplyToken,
+                                  {createComposerState(kLayerId, fence(Fence::Status::Unsignaled),
+                                                       layer_state_t::eBufferChanged),
+                                   createComposerState(kLayerId, fence(Fence::Status::Signaled),
+                                                       layer_state_t::eBufferChanged)});
+    setTransactionStates({mixedTransaction}, kExpectedTransactionsApplied,
+                         kExpectedTransactionsPending);
+}
+
+TEST_F(LatchUnsignaledAlwaysTest, Flush_RemovesFromTheQueueDifferentLayerId) {
+    const sp<IBinder> kApplyToken =
+            IInterface::asBinder(TransactionCompletedListener::getIInstance());
+    const auto kLayerId1 = 1;
+    const auto kLayerId2 = 2;
+    const auto kExpectedTransactionsApplied = 1u;
+    const auto kExpectedTransactionsPending = 0u;
+
+    const auto mixedTransaction =
+            createTransactionInfo(kApplyToken,
+                                  {createComposerState(kLayerId1, fence(Fence::Status::Unsignaled),
+                                                       layer_state_t::eBufferChanged),
+                                   createComposerState(kLayerId2, fence(Fence::Status::Signaled),
+                                                       layer_state_t::eBufferChanged)});
+    setTransactionStates({mixedTransaction}, kExpectedTransactionsApplied,
+                         kExpectedTransactionsPending);
+}
+
+TEST_F(LatchUnsignaledAlwaysTest, Flush_RemovesSignaledFromTheQueue_MultipleLayers) {
+    const sp<IBinder> kApplyToken =
+            IInterface::asBinder(TransactionCompletedListener::getIInstance());
+    const auto kLayerId1 = 1;
+    const auto kLayerId2 = 2;
+    const auto kExpectedTransactionsApplied = 2u;
+    const auto kExpectedTransactionsPending = 0u;
+
+    const auto signaledTransaction =
+            createTransactionInfo(kApplyToken,
+                                  {
+                                          createComposerState(kLayerId1,
+                                                              fence(Fence::Status::Signaled),
+                                                              layer_state_t::eBufferChanged),
+                                  });
+    const auto signaledTransaction2 =
+            createTransactionInfo(kApplyToken,
+                                  {
+                                          createComposerState(kLayerId2,
+                                                              fence(Fence::Status::Signaled),
+                                                              layer_state_t::eBufferChanged),
+                                  });
+    setTransactionStates({signaledTransaction, signaledTransaction2}, kExpectedTransactionsApplied,
+                         kExpectedTransactionsPending);
+}
+
+TEST_F(LatchUnsignaledAlwaysTest, Flush_RemovesFromTheQueueDifferentApplyToken) {
+    const sp<IBinder> kApplyToken1 =
+            IInterface::asBinder(TransactionCompletedListener::getIInstance());
+    const sp<IBinder> kApplyToken2 = sp<BBinder>::make();
+    const auto kLayerId1 = 1;
+    const auto kLayerId2 = 2;
+    const auto kExpectedTransactionsApplied = 2u;
+    const auto kExpectedTransactionsPending = 0u;
+
+    const auto signaledTransaction =
+            createTransactionInfo(kApplyToken1,
+                                  {
+                                          createComposerState(kLayerId1,
+                                                              fence(Fence::Status::Signaled),
+                                                              layer_state_t::eBufferChanged),
+                                  });
+    const auto unsignaledTransaction =
+            createTransactionInfo(kApplyToken2,
+                                  {
+                                          createComposerState(kLayerId2,
+                                                              fence(Fence::Status::Unsignaled),
+                                                              layer_state_t::eBufferChanged),
+                                  });
+    setTransactionStates({signaledTransaction, unsignaledTransaction}, kExpectedTransactionsApplied,
+                         kExpectedTransactionsPending);
+}
+
+TEST_F(LatchUnsignaledAlwaysTest, Flush_RemovesUnsignaledFromTheQueueSameApplyToken) {
+    const sp<IBinder> kApplyToken =
+            IInterface::asBinder(TransactionCompletedListener::getIInstance());
+    const auto kLayerId1 = 1;
+    const auto kLayerId2 = 2;
+    const auto kExpectedTransactionsApplied = 2u;
+    const auto kExpectedTransactionsPending = 0u;
+
+    const auto unsignaledTransaction =
+            createTransactionInfo(kApplyToken,
+                                  {
+                                          createComposerState(kLayerId1,
+                                                              fence(Fence::Status::Unsignaled),
+                                                              layer_state_t::eBufferChanged),
+                                  });
+    const auto signaledTransaction =
+            createTransactionInfo(kApplyToken,
+                                  {
+                                          createComposerState(kLayerId2,
+                                                              fence(Fence::Status::Signaled),
+                                                              layer_state_t::eBufferChanged),
+                                  });
+    setTransactionStates({unsignaledTransaction, signaledTransaction}, kExpectedTransactionsApplied,
+                         kExpectedTransactionsPending);
+}
+
+TEST_F(LatchUnsignaledAlwaysTest, Flush_RemovesUnsignaledFromTheQueue) {
+    const sp<IBinder> kApplyToken1 =
+            IInterface::asBinder(TransactionCompletedListener::getIInstance());
+    const sp<IBinder> kApplyToken2 = sp<BBinder>::make();
+    const auto kLayerId1 = 1;
+    const auto kLayerId2 = 2;
+    const auto kExpectedTransactionsApplied = 2u;
+    const auto kExpectedTransactionsPending = 0u;
+
+    const auto unsignaledTransaction =
+            createTransactionInfo(kApplyToken1,
+                                  {
+                                          createComposerState(kLayerId1,
+                                                              fence(Fence::Status::Unsignaled),
+                                                              layer_state_t::eBufferChanged),
+                                  });
+    const auto unsignaledTransaction2 =
+            createTransactionInfo(kApplyToken2,
+                                  {
+                                          createComposerState(kLayerId2,
+                                                              fence(Fence::Status::Unsignaled),
+                                                              layer_state_t::eBufferChanged),
+                                  });
+    setTransactionStates({unsignaledTransaction, unsignaledTransaction2},
+                         kExpectedTransactionsApplied, kExpectedTransactionsPending);
+}
+
+TEST_F(LatchUnsignaledAlwaysTest, LatchUnsignaledWhenEarlyOffset) {
+    const sp<IBinder> kApplyToken =
+            IInterface::asBinder(TransactionCompletedListener::getIInstance());
+    const auto kLayerId = 1;
+    const auto kExpectedTransactionsApplied = 1u;
+    const auto kExpectedTransactionsPending = 0u;
+
+    const auto unsignaledTransaction =
+            createTransactionInfo(kApplyToken,
+                                  {
+                                          createComposerState(kLayerId,
+                                                              fence(Fence::Status::Unsignaled),
+                                                              layer_state_t::eBufferChanged),
+                                  });
+
+    // Get VsyncModulator out of the default config
+    static_cast<void>(mFlinger.mutableVsyncModulator()->onRefreshRateChangeInitiated());
+
+    setTransactionStates({unsignaledTransaction}, kExpectedTransactionsApplied,
+                         kExpectedTransactionsPending);
+}
+
 } // namespace android
diff --git a/services/surfaceflinger/tests/unittests/TransactionFrameTracerTest.cpp b/services/surfaceflinger/tests/unittests/TransactionFrameTracerTest.cpp
index a749ece..5364630 100644
--- a/services/surfaceflinger/tests/unittests/TransactionFrameTracerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/TransactionFrameTracerTest.cpp
@@ -22,6 +22,7 @@
 #include <gui/SurfaceComposerClient.h>
 #include <log/log.h>
 #include <renderengine/ExternalTexture.h>
+#include <renderengine/mock/FakeExternalTexture.h>
 #include <renderengine/mock/RenderEngine.h>
 #include <utils/String8.h>
 
@@ -46,6 +47,7 @@
         ALOGD("**** Setting up for %s.%s\n", test_info->test_case_name(), test_info->name());
         setupScheduler();
         mFlinger.setupComposer(std::make_unique<Hwc2::mock::Composer>());
+        mFlinger.setupRenderEngine(std::unique_ptr<renderengine::RenderEngine>(mRenderEngine));
     }
 
     ~TransactionFrameTracerTest() {
@@ -56,7 +58,7 @@
 
     sp<BufferStateLayer> createBufferStateLayer() {
         sp<Client> client;
-        LayerCreationArgs args(mFlinger.flinger(), client, "buffer-state-layer", 100, 100, 0,
+        LayerCreationArgs args(mFlinger.flinger(), client, "buffer-state-layer", 0,
                                LayerMetadata());
         return new BufferStateLayer(args);
     }
@@ -92,21 +94,16 @@
     }
 
     TestableSurfaceFlinger mFlinger;
-    renderengine::mock::RenderEngine mRenderEngine;
+    renderengine::mock::RenderEngine* mRenderEngine = new renderengine::mock::RenderEngine();
 
     FenceToFenceTimeMap fenceFactory;
-    client_cache_t mClientCache;
 
     void BLASTTransactionSendsFrameTracerEvents() {
         sp<BufferStateLayer> layer = createBufferStateLayer();
 
         sp<Fence> fence(new Fence());
-        const auto buffer = std::make_shared<
-                renderengine::ExternalTexture>(new GraphicBuffer(1, 1, HAL_PIXEL_FORMAT_RGBA_8888,
-                                                                 1, 0),
-                                               mRenderEngine, false);
         int32_t layerId = layer->getSequence();
-        uint64_t bufferId = buffer->getBuffer()->getId();
+        uint64_t bufferId = 42;
         uint64_t frameNumber = 5;
         nsecs_t dequeueTime = 10;
         nsecs_t postTime = 20;
@@ -117,9 +114,17 @@
         EXPECT_CALL(*mFlinger.getFrameTracer(),
                     traceTimestamp(layerId, bufferId, frameNumber, postTime,
                                    FrameTracer::FrameEvent::QUEUE, /*duration*/ 0));
-        layer->setBuffer(buffer, fence, postTime, /*desiredPresentTime*/ 30, false, mClientCache,
-                         frameNumber, dequeueTime, FrameTimelineInfo{},
-                         nullptr /* releaseBufferCallback */, nullptr /* releaseBufferEndpoint*/);
+        BufferData bufferData;
+        bufferData.acquireFence = fence;
+        bufferData.frameNumber = frameNumber;
+        bufferData.flags |= BufferData::BufferDataChange::fenceChanged;
+        bufferData.flags |= BufferData::BufferDataChange::frameNumberChanged;
+        std::shared_ptr<renderengine::ExternalTexture> externalTexture = std::make_shared<
+                renderengine::mock::FakeExternalTexture>(1U /*width*/, 1U /*height*/, bufferId,
+                                                         HAL_PIXEL_FORMAT_RGBA_8888,
+                                                         0ULL /*usage*/);
+        layer->setBuffer(externalTexture, bufferData, postTime, /*desiredPresentTime*/ 30, false,
+                         dequeueTime, FrameTimelineInfo{});
 
         commitTransaction(layer.get());
         bool computeVisisbleRegions;
diff --git a/services/surfaceflinger/tests/unittests/TransactionProtoParserTest.cpp b/services/surfaceflinger/tests/unittests/TransactionProtoParserTest.cpp
new file mode 100644
index 0000000..f5e3b77
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/TransactionProtoParserTest.cpp
@@ -0,0 +1,116 @@
+/*
+ * 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.
+ */
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <limits> // std::numeric_limits
+
+#include <gui/SurfaceComposerClient.h>
+
+#include "Tracing/TransactionProtoParser.h"
+
+using namespace android::surfaceflinger;
+
+namespace android {
+
+TEST(TransactionProtoParserTest, parse) {
+    const sp<IBinder> layerHandle = new BBinder();
+    const sp<IBinder> displayHandle = new BBinder();
+    TransactionState t1;
+    t1.originPid = 1;
+    t1.originUid = 2;
+    t1.frameTimelineInfo.vsyncId = 3;
+    t1.frameTimelineInfo.inputEventId = 4;
+    t1.postTime = 5;
+
+    layer_state_t layer;
+    layer.layerId = 6;
+    layer.what = std::numeric_limits<uint64_t>::max();
+    layer.what &= ~static_cast<uint64_t>(layer_state_t::eBufferChanged);
+    layer.x = 7;
+    layer.matrix.dsdx = 15;
+
+    size_t layerCount = 2;
+    t1.states.reserve(layerCount);
+    for (uint32_t i = 0; i < layerCount; i++) {
+        ComposerState s;
+        if (i == 1) {
+            layer.parentSurfaceControlForChild =
+                    new SurfaceControl(SurfaceComposerClient::getDefault(), layerHandle, nullptr,
+                                       42);
+        }
+        s.state = layer;
+        t1.states.add(s);
+    }
+
+    size_t displayCount = 2;
+    t1.displays.reserve(displayCount);
+    for (uint32_t i = 0; i < displayCount; i++) {
+        DisplayState display;
+        display.what = std::numeric_limits<uint32_t>::max();
+        if (i == 0) {
+            display.token = displayHandle;
+        } else {
+            display.token = nullptr;
+        }
+        display.width = 85;
+        t1.displays.add(display);
+    }
+
+    class TestMapper : public TransactionProtoParser::FlingerDataMapper {
+    public:
+        sp<IBinder> layerHandle;
+        sp<IBinder> displayHandle;
+
+        TestMapper(sp<IBinder> layerHandle, sp<IBinder> displayHandle)
+              : layerHandle(layerHandle), displayHandle(displayHandle) {}
+
+        sp<IBinder> getLayerHandle(int32_t id) const override {
+            return (id == 42) ? layerHandle : nullptr;
+        }
+        int64_t getLayerId(const sp<IBinder>& handle) const override {
+            return (handle == layerHandle) ? 42 : -1;
+        }
+        sp<IBinder> getDisplayHandle(int32_t id) const {
+            return (id == 43) ? displayHandle : nullptr;
+        }
+        int32_t getDisplayId(const sp<IBinder>& handle) const {
+            return (handle == displayHandle) ? 43 : -1;
+        }
+    };
+
+    TransactionProtoParser parser(std::make_unique<TestMapper>(layerHandle, displayHandle));
+
+    proto::TransactionState proto = parser.toProto(t1);
+    TransactionState t2 = parser.fromProto(proto);
+
+    ASSERT_EQ(t1.originPid, t2.originPid);
+    ASSERT_EQ(t1.originUid, t2.originUid);
+    ASSERT_EQ(t1.frameTimelineInfo.vsyncId, t2.frameTimelineInfo.vsyncId);
+    ASSERT_EQ(t1.frameTimelineInfo.inputEventId, t2.frameTimelineInfo.inputEventId);
+    ASSERT_EQ(t1.postTime, t2.postTime);
+    ASSERT_EQ(t1.states.size(), t2.states.size());
+    ASSERT_EQ(t1.states[0].state.x, t2.states[0].state.x);
+    ASSERT_EQ(t1.states[0].state.matrix.dsdx, t2.states[0].state.matrix.dsdx);
+    ASSERT_EQ(t1.states[1].state.parentSurfaceControlForChild->getHandle(),
+              t2.states[1].state.parentSurfaceControlForChild->getHandle());
+
+    ASSERT_EQ(t1.displays.size(), t2.displays.size());
+    ASSERT_EQ(t1.displays[1].width, t2.displays[1].width);
+    ASSERT_EQ(t1.displays[0].token, t2.displays[0].token);
+}
+
+} // namespace android
diff --git a/services/surfaceflinger/tests/unittests/TransactionSurfaceFrameTest.cpp b/services/surfaceflinger/tests/unittests/TransactionSurfaceFrameTest.cpp
index 2a7921f..5bb4c92 100644
--- a/services/surfaceflinger/tests/unittests/TransactionSurfaceFrameTest.cpp
+++ b/services/surfaceflinger/tests/unittests/TransactionSurfaceFrameTest.cpp
@@ -22,6 +22,7 @@
 #include <gui/SurfaceComposerClient.h>
 #include <log/log.h>
 #include <renderengine/ExternalTexture.h>
+#include <renderengine/mock/FakeExternalTexture.h>
 #include <renderengine/mock/RenderEngine.h>
 #include <utils/String8.h>
 
@@ -46,6 +47,7 @@
         ALOGD("**** Setting up for %s.%s\n", test_info->test_case_name(), test_info->name());
         setupScheduler();
         mFlinger.setupComposer(std::make_unique<Hwc2::mock::Composer>());
+        mFlinger.setupRenderEngine(std::unique_ptr<renderengine::RenderEngine>(mRenderEngine));
     }
 
     ~TransactionSurfaceFrameTest() {
@@ -56,7 +58,7 @@
 
     sp<BufferStateLayer> createBufferStateLayer() {
         sp<Client> client;
-        LayerCreationArgs args(mFlinger.flinger(), client, "buffer-state-layer", 100, 100, 0,
+        LayerCreationArgs args(mFlinger.flinger(), client, "buffer-state-layer", 0,
                                LayerMetadata());
         return new BufferStateLayer(args);
     }
@@ -92,10 +94,9 @@
     }
 
     TestableSurfaceFlinger mFlinger;
-    renderengine::mock::RenderEngine mRenderEngine;
+    renderengine::mock::RenderEngine* mRenderEngine = new renderengine::mock::RenderEngine();
 
     FenceToFenceTimeMap fenceFactory;
-    client_cache_t mClientCache;
 
     void PresentedSurfaceFrameForBufferlessTransaction() {
         sp<BufferStateLayer> layer = createBufferStateLayer();
@@ -114,13 +115,18 @@
         sp<BufferStateLayer> layer = createBufferStateLayer();
         sp<Fence> fence(new Fence());
         auto acquireFence = fenceFactory.createFenceTimeForTest(fence);
-        const auto buffer = std::make_shared<
-                renderengine::ExternalTexture>(new GraphicBuffer(1, 1, HAL_PIXEL_FORMAT_RGBA_8888,
-                                                                 1, 0),
-                                               mRenderEngine, false);
-        layer->setBuffer(buffer, fence, 10, 20, false, mClientCache, 1, std::nullopt,
-                         {/*vsyncId*/ 1, /*inputEventId*/ 0}, nullptr /* releaseBufferCallback */,
-                         nullptr /* releaseBufferEndpoint */);
+        BufferData bufferData;
+        bufferData.acquireFence = fence;
+        bufferData.frameNumber = 1;
+        bufferData.flags |= BufferData::BufferDataChange::fenceChanged;
+        bufferData.flags |= BufferData::BufferDataChange::frameNumberChanged;
+        std::shared_ptr<renderengine::ExternalTexture> externalTexture = std::make_shared<
+                renderengine::mock::FakeExternalTexture>(1U /*width*/, 1U /*height*/,
+                                                         1ULL /* bufferId */,
+                                                         HAL_PIXEL_FORMAT_RGBA_8888,
+                                                         0ULL /*usage*/);
+        layer->setBuffer(externalTexture, bufferData, 10, 20, false, std::nullopt,
+                         {/*vsyncId*/ 1, /*inputEventId*/ 0});
         acquireFence->signalForTest(12);
 
         commitTransaction(layer.get());
@@ -143,27 +149,36 @@
 
         sp<Fence> fence1(new Fence());
         auto acquireFence1 = fenceFactory.createFenceTimeForTest(fence1);
-        const auto buffer1 = std::make_shared<
-                renderengine::ExternalTexture>(new GraphicBuffer(1, 1, HAL_PIXEL_FORMAT_RGBA_8888,
-                                                                 1, 0),
-                                               mRenderEngine, false);
-        layer->setBuffer(buffer1, fence1, 10, 20, false, mClientCache, 1, std::nullopt,
-                         {/*vsyncId*/ 1, /*inputEventId*/ 0}, nullptr /* releaseBufferCallback */,
-                         nullptr /* releaseBufferEndpoint */);
+        BufferData bufferData;
+        bufferData.acquireFence = fence1;
+        bufferData.frameNumber = 1;
+        bufferData.flags |= BufferData::BufferDataChange::fenceChanged;
+        bufferData.flags |= BufferData::BufferDataChange::frameNumberChanged;
+        std::shared_ptr<renderengine::ExternalTexture> externalTexture1 = std::make_shared<
+                renderengine::mock::FakeExternalTexture>(1U /*width*/, 1U /*height*/,
+                                                         1ULL /* bufferId */,
+                                                         HAL_PIXEL_FORMAT_RGBA_8888,
+                                                         0ULL /*usage*/);
+        layer->setBuffer(externalTexture1, bufferData, 10, 20, false, std::nullopt,
+                         {/*vsyncId*/ 1, /*inputEventId*/ 0});
         EXPECT_EQ(0u, layer->mDrawingState.bufferlessSurfaceFramesTX.size());
         ASSERT_NE(nullptr, layer->mDrawingState.bufferSurfaceFrameTX);
         const auto droppedSurfaceFrame = layer->mDrawingState.bufferSurfaceFrameTX;
 
         sp<Fence> fence2(new Fence());
         auto acquireFence2 = fenceFactory.createFenceTimeForTest(fence2);
-        const auto buffer2 = std::make_shared<
-                renderengine::ExternalTexture>(new GraphicBuffer(1, 1, HAL_PIXEL_FORMAT_RGBA_8888,
-                                                                 1, 0),
-                                               mRenderEngine, false);
         nsecs_t start = systemTime();
-        layer->setBuffer(buffer2, fence2, 10, 20, false, mClientCache, 1, std::nullopt,
-                         {/*vsyncId*/ 1, /*inputEventId*/ 0}, nullptr /* releaseBufferCallback */,
-                         nullptr /* releaseBufferEndpoint */);
+        bufferData.acquireFence = fence2;
+        bufferData.frameNumber = 1;
+        bufferData.flags |= BufferData::BufferDataChange::fenceChanged;
+        bufferData.flags |= BufferData::BufferDataChange::frameNumberChanged;
+        std::shared_ptr<renderengine::ExternalTexture> externalTexture2 = std::make_shared<
+                renderengine::mock::FakeExternalTexture>(1U /*width*/, 1U /*height*/,
+                                                         2ULL /* bufferId */,
+                                                         HAL_PIXEL_FORMAT_RGBA_8888,
+                                                         0ULL /*usage*/);
+        layer->setBuffer(externalTexture2, bufferData, 10, 20, false, std::nullopt,
+                         {/*vsyncId*/ 1, /*inputEventId*/ 0});
         nsecs_t end = systemTime();
         acquireFence2->signalForTest(12);
 
@@ -198,13 +213,18 @@
 
         sp<Fence> fence(new Fence());
         auto acquireFence = fenceFactory.createFenceTimeForTest(fence);
-        const auto buffer = std::make_shared<
-                renderengine::ExternalTexture>(new GraphicBuffer(1, 1, HAL_PIXEL_FORMAT_RGBA_8888,
-                                                                 1, 0),
-                                               mRenderEngine, false);
-        layer->setBuffer(buffer, fence, 10, 20, false, mClientCache, 1, std::nullopt,
-                         {/*vsyncId*/ 1, /*inputEventId*/ 0}, nullptr /* releaseBufferCallback */,
-                         nullptr /* releaseBufferEndpoint */);
+        BufferData bufferData;
+        bufferData.acquireFence = fence;
+        bufferData.frameNumber = 1;
+        bufferData.flags |= BufferData::BufferDataChange::fenceChanged;
+        bufferData.flags |= BufferData::BufferDataChange::frameNumberChanged;
+        std::shared_ptr<renderengine::ExternalTexture> externalTexture = std::make_shared<
+                renderengine::mock::FakeExternalTexture>(1U /*width*/, 1U /*height*/,
+                                                         1ULL /* bufferId */,
+                                                         HAL_PIXEL_FORMAT_RGBA_8888,
+                                                         0ULL /*usage*/);
+        layer->setBuffer(externalTexture, bufferData, 10, 20, false, std::nullopt,
+                         {/*vsyncId*/ 1, /*inputEventId*/ 0});
         acquireFence->signalForTest(12);
 
         EXPECT_EQ(0u, layer->mDrawingState.bufferlessSurfaceFramesTX.size());
@@ -227,13 +247,18 @@
         sp<BufferStateLayer> layer = createBufferStateLayer();
         sp<Fence> fence(new Fence());
         auto acquireFence = fenceFactory.createFenceTimeForTest(fence);
-        const auto buffer = std::make_shared<
-                renderengine::ExternalTexture>(new GraphicBuffer(1, 1, HAL_PIXEL_FORMAT_RGBA_8888,
-                                                                 1, 0),
-                                               mRenderEngine, false);
-        layer->setBuffer(buffer, fence, 10, 20, false, mClientCache, 1, std::nullopt,
-                         {/*vsyncId*/ 1, /*inputEventId*/ 0}, nullptr /* releaseBufferCallback */,
-                         nullptr /* releaseBufferEndpoint */);
+        BufferData bufferData;
+        bufferData.acquireFence = fence;
+        bufferData.frameNumber = 1;
+        bufferData.flags |= BufferData::BufferDataChange::fenceChanged;
+        bufferData.flags |= BufferData::BufferDataChange::frameNumberChanged;
+        std::shared_ptr<renderengine::ExternalTexture> externalTexture = std::make_shared<
+                renderengine::mock::FakeExternalTexture>(1U /*width*/, 1U /*height*/,
+                                                         1ULL /* bufferId */,
+                                                         HAL_PIXEL_FORMAT_RGBA_8888,
+                                                         0ULL /*usage*/);
+        layer->setBuffer(externalTexture, bufferData, 10, 20, false, std::nullopt,
+                         {/*vsyncId*/ 1, /*inputEventId*/ 0});
         EXPECT_EQ(0u, layer->mDrawingState.bufferlessSurfaceFramesTX.size());
         ASSERT_NE(nullptr, layer->mDrawingState.bufferSurfaceFrameTX);
 
@@ -260,13 +285,18 @@
 
         sp<Fence> fence(new Fence());
         auto acquireFence = fenceFactory.createFenceTimeForTest(fence);
-        const auto buffer = std::make_shared<
-                renderengine::ExternalTexture>(new GraphicBuffer(1, 1, HAL_PIXEL_FORMAT_RGBA_8888,
-                                                                 1, 0),
-                                               mRenderEngine, false);
-        layer->setBuffer(buffer, fence, 10, 20, false, mClientCache, 1, std::nullopt,
-                         {/*vsyncId*/ 3, /*inputEventId*/ 0}, nullptr /* releaseBufferCallback */,
-                         nullptr /* releaseBufferEndpoint */);
+        BufferData bufferData;
+        bufferData.acquireFence = fence;
+        bufferData.frameNumber = 1;
+        bufferData.flags |= BufferData::BufferDataChange::fenceChanged;
+        bufferData.flags |= BufferData::BufferDataChange::frameNumberChanged;
+        std::shared_ptr<renderengine::ExternalTexture> externalTexture = std::make_shared<
+                renderengine::mock::FakeExternalTexture>(1U /*width*/, 1U /*height*/,
+                                                         1ULL /* bufferId */,
+                                                         HAL_PIXEL_FORMAT_RGBA_8888,
+                                                         0ULL /*usage*/);
+        layer->setBuffer(externalTexture, bufferData, 10, 20, false, std::nullopt,
+                         {/*vsyncId*/ 3, /*inputEventId*/ 0});
         EXPECT_EQ(2u, layer->mDrawingState.bufferlessSurfaceFramesTX.size());
         ASSERT_NE(nullptr, layer->mDrawingState.bufferSurfaceFrameTX);
         const auto bufferSurfaceFrameTX = layer->mDrawingState.bufferSurfaceFrameTX;
@@ -299,25 +329,34 @@
 
         sp<Fence> fence1(new Fence());
         auto acquireFence1 = fenceFactory.createFenceTimeForTest(fence1);
-        const auto buffer1 = std::make_shared<
-                renderengine::ExternalTexture>(new GraphicBuffer(1, 1, HAL_PIXEL_FORMAT_RGBA_8888,
-                                                                 1, 0),
-                                               mRenderEngine, false);
-        layer->setBuffer(buffer1, fence1, 10, 20, false, mClientCache, 1, std::nullopt,
-                         {/*vsyncId*/ 1, /*inputEventId*/ 0}, nullptr /* releaseBufferCallback */,
-                         nullptr /* releaseBufferEndpoint */);
+        BufferData bufferData;
+        bufferData.acquireFence = fence1;
+        bufferData.frameNumber = 1;
+        bufferData.flags |= BufferData::BufferDataChange::fenceChanged;
+        bufferData.flags |= BufferData::BufferDataChange::frameNumberChanged;
+        std::shared_ptr<renderengine::ExternalTexture> externalTexture1 = std::make_shared<
+                renderengine::mock::FakeExternalTexture>(1U /*width*/, 1U /*height*/,
+                                                         1ULL /* bufferId */,
+                                                         HAL_PIXEL_FORMAT_RGBA_8888,
+                                                         0ULL /*usage*/);
+        layer->setBuffer(externalTexture1, bufferData, 10, 20, false, std::nullopt,
+                         {/*vsyncId*/ 1, /*inputEventId*/ 0});
         ASSERT_NE(nullptr, layer->mDrawingState.bufferSurfaceFrameTX);
         const auto droppedSurfaceFrame = layer->mDrawingState.bufferSurfaceFrameTX;
 
         sp<Fence> fence2(new Fence());
         auto acquireFence2 = fenceFactory.createFenceTimeForTest(fence2);
-        const auto buffer2 = std::make_shared<
-                renderengine::ExternalTexture>(new GraphicBuffer(1, 1, HAL_PIXEL_FORMAT_RGBA_8888,
-                                                                 1, 0),
-                                               mRenderEngine, false);
-        layer->setBuffer(buffer2, fence2, 10, 20, false, mClientCache, 1, std::nullopt,
-                         {/*vsyncId*/ 1, /*inputEventId*/ 0}, nullptr /* releaseBufferCallback */,
-                         nullptr /* releaseBufferEndpoint */);
+        bufferData.acquireFence = fence2;
+        bufferData.frameNumber = 1;
+        bufferData.flags |= BufferData::BufferDataChange::fenceChanged;
+        bufferData.flags |= BufferData::BufferDataChange::frameNumberChanged;
+        std::shared_ptr<renderengine::ExternalTexture> externalTexture2 = std::make_shared<
+                renderengine::mock::FakeExternalTexture>(1U /*width*/, 1U /*height*/,
+                                                         1ULL /* bufferId */,
+                                                         HAL_PIXEL_FORMAT_RGBA_8888,
+                                                         0ULL /*usage*/);
+        layer->setBuffer(externalTexture2, bufferData, 10, 20, false, std::nullopt,
+                         {/*vsyncId*/ 1, /*inputEventId*/ 0});
         acquireFence2->signalForTest(12);
 
         ASSERT_NE(nullptr, layer->mDrawingState.bufferSurfaceFrameTX);
@@ -330,7 +369,7 @@
         // Both the droppedSurfaceFrame and presentedSurfaceFrame should be in
         // pendingJankClassifications.
         EXPECT_EQ(2u, layer->mPendingJankClassifications.size());
-        presentedSurfaceFrame->onPresent(20, JankType::None, Fps::fromPeriodNsecs(11),
+        presentedSurfaceFrame->onPresent(20, JankType::None, 90_Hz,
                                          /*displayDeadlineDelta*/ 0, /*displayPresentDelta*/ 0);
         layer->releasePendingBuffer(25);
 
@@ -342,27 +381,36 @@
 
         sp<Fence> fence1(new Fence());
         auto acquireFence1 = fenceFactory.createFenceTimeForTest(fence1);
-        const auto buffer1 = std::make_shared<
-                renderengine::ExternalTexture>(new GraphicBuffer(1, 1, HAL_PIXEL_FORMAT_RGBA_8888,
-                                                                 1, 0),
-                                               mRenderEngine, false);
-        layer->setBuffer(buffer1, fence1, 10, 20, false, mClientCache, 1, std::nullopt,
-                         {/*vsyncId*/ 1, /*inputEventId*/ 0}, nullptr /* releaseBufferCallback */,
-                         nullptr /* releaseBufferEndpoint */);
+        BufferData bufferData;
+        bufferData.acquireFence = fence1;
+        bufferData.frameNumber = 1;
+        bufferData.flags |= BufferData::BufferDataChange::fenceChanged;
+        bufferData.flags |= BufferData::BufferDataChange::frameNumberChanged;
+        std::shared_ptr<renderengine::ExternalTexture> externalTexture1 = std::make_shared<
+                renderengine::mock::FakeExternalTexture>(1U /*width*/, 1U /*height*/,
+                                                         1ULL /* bufferId */,
+                                                         HAL_PIXEL_FORMAT_RGBA_8888,
+                                                         0ULL /*usage*/);
+        layer->setBuffer(externalTexture1, bufferData, 10, 20, false, std::nullopt,
+                         {/*vsyncId*/ 1, /*inputEventId*/ 0});
         EXPECT_EQ(0u, layer->mDrawingState.bufferlessSurfaceFramesTX.size());
         ASSERT_NE(nullptr, layer->mDrawingState.bufferSurfaceFrameTX);
         const auto droppedSurfaceFrame1 = layer->mDrawingState.bufferSurfaceFrameTX;
 
         sp<Fence> fence2(new Fence());
         auto acquireFence2 = fenceFactory.createFenceTimeForTest(fence2);
-        const auto buffer2 = std::make_shared<
-                renderengine::ExternalTexture>(new GraphicBuffer(1, 1, HAL_PIXEL_FORMAT_RGBA_8888,
-                                                                 1, 0),
-                                               mRenderEngine, false);
         auto dropStartTime1 = systemTime();
-        layer->setBuffer(buffer2, fence2, 10, 20, false, mClientCache, 1, std::nullopt,
-                         {/*vsyncId*/ FrameTimelineInfo::INVALID_VSYNC_ID, /*inputEventId*/ 0},
-                         nullptr /* releaseBufferCallback */, nullptr /* releaseBufferEndpoint */);
+        bufferData.acquireFence = fence2;
+        bufferData.frameNumber = 1;
+        bufferData.flags |= BufferData::BufferDataChange::fenceChanged;
+        bufferData.flags |= BufferData::BufferDataChange::frameNumberChanged;
+        std::shared_ptr<renderengine::ExternalTexture> externalTexture2 = std::make_shared<
+                renderengine::mock::FakeExternalTexture>(1U /*width*/, 1U /*height*/,
+                                                         1ULL /* bufferId */,
+                                                         HAL_PIXEL_FORMAT_RGBA_8888,
+                                                         0ULL /*usage*/);
+        layer->setBuffer(externalTexture2, bufferData, 10, 20, false, std::nullopt,
+                         {/*vsyncId*/ FrameTimelineInfo::INVALID_VSYNC_ID, /*inputEventId*/ 0});
         auto dropEndTime1 = systemTime();
         EXPECT_EQ(0u, layer->mDrawingState.bufferlessSurfaceFramesTX.size());
         ASSERT_NE(nullptr, layer->mDrawingState.bufferSurfaceFrameTX);
@@ -370,14 +418,18 @@
 
         sp<Fence> fence3(new Fence());
         auto acquireFence3 = fenceFactory.createFenceTimeForTest(fence3);
-        const auto buffer3 = std::make_shared<
-                renderengine::ExternalTexture>(new GraphicBuffer(1, 1, HAL_PIXEL_FORMAT_RGBA_8888,
-                                                                 1, 0),
-                                               mRenderEngine, false);
         auto dropStartTime2 = systemTime();
-        layer->setBuffer(buffer3, fence3, 10, 20, false, mClientCache, 1, std::nullopt,
-                         {/*vsyncId*/ 2, /*inputEventId*/ 0}, nullptr /* releaseBufferCallback */,
-                         nullptr /* releaseBufferEndpoint */);
+        bufferData.acquireFence = fence3;
+        bufferData.frameNumber = 1;
+        bufferData.flags |= BufferData::BufferDataChange::fenceChanged;
+        bufferData.flags |= BufferData::BufferDataChange::frameNumberChanged;
+        std::shared_ptr<renderengine::ExternalTexture> externalTexture3 = std::make_shared<
+                renderengine::mock::FakeExternalTexture>(1U /*width*/, 1U /*height*/,
+                                                         1ULL /* bufferId */,
+                                                         HAL_PIXEL_FORMAT_RGBA_8888,
+                                                         0ULL /*usage*/);
+        layer->setBuffer(externalTexture3, bufferData, 10, 20, false, std::nullopt,
+                         {/*vsyncId*/ 2, /*inputEventId*/ 0});
         auto dropEndTime2 = systemTime();
         acquireFence3->signalForTest(12);
 
@@ -413,16 +465,19 @@
         uint32_t surfaceFramesPendingClassification = 0;
         std::vector<std::shared_ptr<frametimeline::SurfaceFrame>> bufferlessSurfaceFrames;
         for (int i = 0; i < 10; i += 2) {
-            sp<Fence> fence1(new Fence());
-            const auto buffer1 = std::make_shared<
-                    renderengine::ExternalTexture>(new GraphicBuffer(1, 1,
-                                                                     HAL_PIXEL_FORMAT_RGBA_8888, 1,
-                                                                     0),
-                                                   mRenderEngine, false);
-            layer->setBuffer(buffer1, fence1, 10, 20, false, mClientCache, 1, std::nullopt,
-                             {/*vsyncId*/ 1, /*inputEventId*/ 0},
-                             nullptr /* releaseBufferCallback */,
-                             nullptr /* releaseBufferEndpoint */);
+            sp<Fence> fence(new Fence());
+            BufferData bufferData;
+            bufferData.acquireFence = fence;
+            bufferData.frameNumber = 1;
+            bufferData.flags |= BufferData::BufferDataChange::fenceChanged;
+            bufferData.flags |= BufferData::BufferDataChange::frameNumberChanged;
+            std::shared_ptr<renderengine::ExternalTexture> externalTexture = std::make_shared<
+                    renderengine::mock::FakeExternalTexture>(1U /*width*/, 1U /*height*/,
+                                                             1ULL /* bufferId */,
+                                                             HAL_PIXEL_FORMAT_RGBA_8888,
+                                                             0ULL /*usage*/);
+            layer->setBuffer(externalTexture, bufferData, 10, 20, false, std::nullopt,
+                             {/*vsyncId*/ 1, /*inputEventId*/ 0});
             layer->setFrameTimelineVsyncForBufferlessTransaction({/*vsyncId*/ 2,
                                                                   /*inputEventId*/ 0},
                                                                  10);
@@ -444,10 +499,10 @@
         // BufferlessSurfaceFrames are immediately set to presented and added to the DisplayFrame.
         // Since we don't have access to DisplayFrame here, trigger an onPresent directly.
         for (auto& surfaceFrame : bufferlessSurfaceFrames) {
-            surfaceFrame->onPresent(20, JankType::None, Fps::fromPeriodNsecs(11),
+            surfaceFrame->onPresent(20, JankType::None, 90_Hz,
                                     /*displayDeadlineDelta*/ 0, /*displayPresentDelta*/ 0);
         }
-        presentedBufferSurfaceFrame->onPresent(20, JankType::None, Fps::fromPeriodNsecs(11),
+        presentedBufferSurfaceFrame->onPresent(20, JankType::None, 90_Hz,
                                                /*displayDeadlineDelta*/ 0,
                                                /*displayPresentDelta*/ 0);
 
diff --git a/services/surfaceflinger/tests/unittests/TransactionTracingTest.cpp b/services/surfaceflinger/tests/unittests/TransactionTracingTest.cpp
new file mode 100644
index 0000000..61b72a0
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/TransactionTracingTest.cpp
@@ -0,0 +1,285 @@
+/*
+ * 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.
+ */
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include <gui/SurfaceComposerClient.h>
+
+#include "Tracing/RingBuffer.h"
+#include "Tracing/TransactionTracing.h"
+
+using namespace android::surfaceflinger;
+
+namespace android {
+
+class TransactionTracingTest : public testing::Test {
+protected:
+    static constexpr size_t SMALL_BUFFER_SIZE = 1024;
+    TransactionTracing mTracing;
+
+    void flush(int64_t vsyncId) { mTracing.flush(vsyncId); }
+    proto::TransactionTraceFile writeToProto() { return mTracing.writeToProto(); }
+
+    proto::TransactionTraceEntry bufferFront() {
+        std::scoped_lock<std::mutex> lock(mTracing.mTraceLock);
+        proto::TransactionTraceEntry entry;
+        entry.ParseFromString(mTracing.mBuffer.front());
+        return entry;
+    }
+
+    void queueAndCommitTransaction(int64_t vsyncId) {
+        TransactionState transaction;
+        transaction.id = static_cast<uint64_t>(vsyncId * 3);
+        transaction.originUid = 1;
+        transaction.originPid = 2;
+        mTracing.addQueuedTransaction(transaction);
+        std::vector<TransactionState> transactions;
+        transactions.emplace_back(transaction);
+        mTracing.addCommittedTransactions(transactions, vsyncId);
+        flush(vsyncId);
+    }
+
+    void verifyEntry(const proto::TransactionTraceEntry& actualProto,
+                     const std::vector<TransactionState>& expectedTransactions,
+                     int64_t expectedVsyncId) {
+        EXPECT_EQ(actualProto.vsync_id(), expectedVsyncId);
+        EXPECT_EQ(actualProto.transactions().size(),
+                  static_cast<int32_t>(expectedTransactions.size()));
+        for (uint32_t i = 0; i < expectedTransactions.size(); i++) {
+            EXPECT_EQ(actualProto.transactions(static_cast<int32_t>(i)).pid(),
+                      expectedTransactions[i].originPid);
+        }
+    }
+};
+
+TEST_F(TransactionTracingTest, addTransactions) {
+    std::vector<TransactionState> transactions;
+    transactions.reserve(100);
+    for (uint64_t i = 0; i < 100; i++) {
+        TransactionState transaction;
+        transaction.id = i;
+        transaction.originPid = static_cast<int32_t>(i);
+        transactions.emplace_back(transaction);
+        mTracing.addQueuedTransaction(transaction);
+    }
+
+    // Split incoming transactions into two and commit them in reverse order to test out of order
+    // commits.
+    std::vector<TransactionState> firstTransactionSet =
+            std::vector<TransactionState>(transactions.begin() + 50, transactions.end());
+    int64_t firstTransactionSetVsyncId = 42;
+    mTracing.addCommittedTransactions(firstTransactionSet, firstTransactionSetVsyncId);
+
+    int64_t secondTransactionSetVsyncId = 43;
+    std::vector<TransactionState> secondTransactionSet =
+            std::vector<TransactionState>(transactions.begin(), transactions.begin() + 50);
+    mTracing.addCommittedTransactions(secondTransactionSet, secondTransactionSetVsyncId);
+    flush(secondTransactionSetVsyncId);
+
+    proto::TransactionTraceFile proto = writeToProto();
+    EXPECT_EQ(proto.entry().size(), 2);
+    verifyEntry(proto.entry(0), firstTransactionSet, firstTransactionSetVsyncId);
+    verifyEntry(proto.entry(1), secondTransactionSet, secondTransactionSetVsyncId);
+}
+
+class TransactionTracingLayerHandlingTest : public TransactionTracingTest {
+protected:
+    void SetUp() override {
+        // add layers
+        mTracing.setBufferSize(SMALL_BUFFER_SIZE);
+        const sp<IBinder> fakeLayerHandle = new BBinder();
+        mTracing.onLayerAdded(fakeLayerHandle->localBinder(), mParentLayerId, "parent",
+                              123 /* flags */, -1 /* parentId */);
+        const sp<IBinder> fakeChildLayerHandle = new BBinder();
+        mTracing.onLayerAdded(fakeChildLayerHandle->localBinder(), mChildLayerId, "child",
+                              456 /* flags */, mParentLayerId);
+
+        // add some layer transaction
+        {
+            TransactionState transaction;
+            transaction.id = 50;
+            ComposerState layerState;
+            layerState.state.surface = fakeLayerHandle;
+            layerState.state.what = layer_state_t::eLayerChanged;
+            layerState.state.z = 42;
+            transaction.states.add(layerState);
+            ComposerState childState;
+            childState.state.surface = fakeChildLayerHandle;
+            childState.state.what = layer_state_t::eLayerChanged;
+            childState.state.z = 43;
+            transaction.states.add(childState);
+            mTracing.addQueuedTransaction(transaction);
+
+            std::vector<TransactionState> transactions;
+            transactions.emplace_back(transaction);
+            VSYNC_ID_FIRST_LAYER_CHANGE = ++mVsyncId;
+            mTracing.addCommittedTransactions(transactions, VSYNC_ID_FIRST_LAYER_CHANGE);
+            flush(VSYNC_ID_FIRST_LAYER_CHANGE);
+        }
+
+        // add transactions that modify the layer state further so we can test that layer state
+        // gets merged
+        {
+            TransactionState transaction;
+            transaction.id = 51;
+            ComposerState 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);
+            mTracing.addQueuedTransaction(transaction);
+
+            std::vector<TransactionState> transactions;
+            transactions.emplace_back(transaction);
+            VSYNC_ID_SECOND_LAYER_CHANGE = ++mVsyncId;
+            mTracing.addCommittedTransactions(transactions, VSYNC_ID_SECOND_LAYER_CHANGE);
+            flush(VSYNC_ID_SECOND_LAYER_CHANGE);
+        }
+
+        // remove child layer
+        mTracing.onLayerRemoved(2);
+        VSYNC_ID_CHILD_LAYER_REMOVED = ++mVsyncId;
+        queueAndCommitTransaction(VSYNC_ID_CHILD_LAYER_REMOVED);
+
+        // remove layer
+        mTracing.onLayerRemoved(1);
+        queueAndCommitTransaction(++mVsyncId);
+    }
+
+    int mParentLayerId = 1;
+    int mChildLayerId = 2;
+    int64_t mVsyncId = 0;
+    int64_t VSYNC_ID_FIRST_LAYER_CHANGE;
+    int64_t VSYNC_ID_SECOND_LAYER_CHANGE;
+    int64_t VSYNC_ID_CHILD_LAYER_REMOVED;
+};
+
+TEST_F(TransactionTracingLayerHandlingTest, addStartingState) {
+    // add transactions until we drop the transaction with the first layer change
+    while (bufferFront().vsync_id() <= VSYNC_ID_FIRST_LAYER_CHANGE) {
+        queueAndCommitTransaction(++mVsyncId);
+    }
+    proto::TransactionTraceFile proto = writeToProto();
+    // verify we can still retrieve the layer change from the first entry containing starting
+    // states.
+    EXPECT_GT(proto.entry().size(), 0);
+    EXPECT_EQ(proto.entry(0).transactions().size(), 1);
+    EXPECT_EQ(proto.entry(0).added_layers().size(), 2);
+    EXPECT_EQ(proto.entry(0).transactions(0).layer_changes().size(), 2);
+    EXPECT_EQ(proto.entry(0).transactions(0).layer_changes(0).layer_id(), mParentLayerId);
+    EXPECT_EQ(proto.entry(0).transactions(0).layer_changes(0).z(), 42);
+    EXPECT_EQ(proto.entry(0).transactions(0).layer_changes(1).layer_id(), mChildLayerId);
+    EXPECT_EQ(proto.entry(0).transactions(0).layer_changes(1).z(), 43);
+}
+
+TEST_F(TransactionTracingLayerHandlingTest, updateStartingState) {
+    // add transactions until we drop the transaction with the second layer change
+    while (bufferFront().vsync_id() <= VSYNC_ID_SECOND_LAYER_CHANGE) {
+        queueAndCommitTransaction(++mVsyncId);
+    }
+    proto::TransactionTraceFile proto = writeToProto();
+    // verify starting states are updated correctly
+    EXPECT_EQ(proto.entry(0).transactions(0).layer_changes(0).z(), 41);
+}
+
+TEST_F(TransactionTracingLayerHandlingTest, removeStartingState) {
+    // add transactions until we drop the transaction which removes the child layer
+    while (bufferFront().vsync_id() <= VSYNC_ID_CHILD_LAYER_REMOVED) {
+        queueAndCommitTransaction(++mVsyncId);
+    }
+    proto::TransactionTraceFile proto = writeToProto();
+    // verify the child layer has been removed from the trace
+    EXPECT_EQ(proto.entry(0).transactions(0).layer_changes().size(), 1);
+    EXPECT_EQ(proto.entry(0).transactions(0).layer_changes(0).layer_id(), mParentLayerId);
+}
+
+TEST_F(TransactionTracingLayerHandlingTest, startingStateSurvivesBufferFlush) {
+    // add transactions until we drop the transaction with the second layer change
+    while (bufferFront().vsync_id() <= VSYNC_ID_SECOND_LAYER_CHANGE) {
+        queueAndCommitTransaction(++mVsyncId);
+    }
+    proto::TransactionTraceFile proto = writeToProto();
+    // verify we have two starting states
+    EXPECT_EQ(proto.entry(0).transactions(0).layer_changes().size(), 2);
+
+    // Continue adding transactions until child layer is removed
+    while (bufferFront().vsync_id() <= VSYNC_ID_CHILD_LAYER_REMOVED) {
+        queueAndCommitTransaction(++mVsyncId);
+    }
+    proto = writeToProto();
+    // verify we still have the parent layer state
+    EXPECT_EQ(proto.entry(0).transactions(0).layer_changes().size(), 1);
+    EXPECT_EQ(proto.entry(0).transactions(0).layer_changes(0).layer_id(), mParentLayerId);
+}
+
+class TransactionTracingMirrorLayerTest : public TransactionTracingTest {
+protected:
+    void SetUp() override {
+        // add layers
+        mTracing.setBufferSize(SMALL_BUFFER_SIZE);
+        const sp<IBinder> fakeLayerHandle = new BBinder();
+        mTracing.onLayerAdded(fakeLayerHandle->localBinder(), mLayerId, "Test Layer",
+                              123 /* flags */, -1 /* parentId */);
+        const sp<IBinder> fakeMirrorLayerHandle = new BBinder();
+        mTracing.onMirrorLayerAdded(fakeMirrorLayerHandle->localBinder(), mMirrorLayerId, "Mirror",
+                                    mLayerId);
+
+        // add some layer transaction
+        {
+            TransactionState transaction;
+            transaction.id = 50;
+            ComposerState layerState;
+            layerState.state.surface = fakeLayerHandle;
+            layerState.state.what = layer_state_t::eLayerChanged;
+            layerState.state.z = 42;
+            transaction.states.add(layerState);
+            ComposerState mirrorState;
+            mirrorState.state.surface = fakeMirrorLayerHandle;
+            mirrorState.state.what = layer_state_t::eLayerChanged;
+            mirrorState.state.z = 43;
+            transaction.states.add(mirrorState);
+            mTracing.addQueuedTransaction(transaction);
+
+            std::vector<TransactionState> transactions;
+            transactions.emplace_back(transaction);
+            mTracing.addCommittedTransactions(transactions, ++mVsyncId);
+            flush(mVsyncId);
+        }
+    }
+
+    int mLayerId = 5;
+    int mMirrorLayerId = 55;
+    int64_t mVsyncId = 0;
+    int64_t VSYNC_ID_FIRST_LAYER_CHANGE;
+    int64_t VSYNC_ID_SECOND_LAYER_CHANGE;
+    int64_t VSYNC_ID_CHILD_LAYER_REMOVED;
+};
+
+TEST_F(TransactionTracingMirrorLayerTest, canAddMirrorLayers) {
+    proto::TransactionTraceFile proto = writeToProto();
+    // We don't have any starting states since no layer was removed from.
+    EXPECT_EQ(proto.entry().size(), 1);
+
+    // Verify the mirror layer was added
+    EXPECT_EQ(proto.entry(0).transactions().size(), 1);
+    EXPECT_EQ(proto.entry(0).added_layers().size(), 2);
+    EXPECT_EQ(proto.entry(0).added_layers(1).layer_id(), mMirrorLayerId);
+    EXPECT_EQ(proto.entry(0).transactions(0).layer_changes().size(), 2);
+    EXPECT_EQ(proto.entry(0).transactions(0).layer_changes(1).z(), 43);
+}
+} // namespace android
diff --git a/services/surfaceflinger/tests/unittests/TunnelModeEnabledReporterTest.cpp b/services/surfaceflinger/tests/unittests/TunnelModeEnabledReporterTest.cpp
index e4f7469..15fea9c 100644
--- a/services/surfaceflinger/tests/unittests/TunnelModeEnabledReporterTest.cpp
+++ b/services/surfaceflinger/tests/unittests/TunnelModeEnabledReporterTest.cpp
@@ -27,7 +27,6 @@
 #include "TunnelModeEnabledReporter.h"
 #include "mock/DisplayHardware/MockComposer.h"
 #include "mock/MockEventThread.h"
-#include "mock/MockMessageQueue.h"
 
 namespace android {
 
@@ -69,12 +68,12 @@
 
     TestableSurfaceFlinger mFlinger;
     Hwc2::mock::Composer* mComposer = nullptr;
-    sp<TestableTunnelModeEnabledListener> mTunnelModeEnabledListener =
-            new TestableTunnelModeEnabledListener();
-    sp<TunnelModeEnabledReporter> mTunnelModeEnabledReporter =
-            new TunnelModeEnabledReporter();
 
-    mock::MessageQueue* mMessageQueue = new mock::MessageQueue();
+    sp<TestableTunnelModeEnabledListener> mTunnelModeEnabledListener =
+            sp<TestableTunnelModeEnabledListener>::make();
+
+    sp<TunnelModeEnabledReporter> mTunnelModeEnabledReporter =
+            sp<TunnelModeEnabledReporter>::make();
 };
 
 TunnelModeEnabledReporterTest::TunnelModeEnabledReporterTest() {
@@ -82,7 +81,6 @@
             ::testing::UnitTest::GetInstance()->current_test_info();
     ALOGD("**** Setting up for %s.%s\n", test_info->test_case_name(), test_info->name());
 
-    mFlinger.mutableEventQueue().reset(mMessageQueue);
     setupScheduler();
     mFlinger.setupComposer(std::make_unique<Hwc2::mock::Composer>());
     mFlinger.flinger()->mTunnelModeEnabledReporter = mTunnelModeEnabledReporter;
@@ -100,8 +98,7 @@
 sp<BufferStateLayer> TunnelModeEnabledReporterTest::createBufferStateLayer(
         LayerMetadata metadata = {}) {
     sp<Client> client;
-    LayerCreationArgs args(mFlinger.flinger(), client, "buffer-state-layer", WIDTH, HEIGHT,
-                           LAYER_FLAGS, metadata);
+    LayerCreationArgs args(mFlinger.flinger(), client, "buffer-state-layer", LAYER_FLAGS, metadata);
     return new BufferStateLayer(args);
 }
 
diff --git a/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp b/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp
index 42b1993..2da266b 100644
--- a/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp
+++ b/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp
@@ -14,14 +14,15 @@
  * limitations under the License.
  */
 
-#include "Scheduler/TimeKeeper.h"
-#include "Scheduler/Timer.h"
-#include "Scheduler/VSyncDispatchTimerQueue.h"
-#include "Scheduler/VSyncTracker.h"
+#include <thread>
 
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
-#include <thread>
+
+#include <scheduler/Timer.h>
+
+#include "Scheduler/VSyncDispatchTimerQueue.h"
+#include "Scheduler/VSyncTracker.h"
 
 using namespace testing;
 using namespace std::literals;
diff --git a/services/surfaceflinger/tests/unittests/VSyncDispatchTimerQueueTest.cpp b/services/surfaceflinger/tests/unittests/VSyncDispatchTimerQueueTest.cpp
index ddc02bf..b7f968d 100644
--- a/services/surfaceflinger/tests/unittests/VSyncDispatchTimerQueueTest.cpp
+++ b/services/surfaceflinger/tests/unittests/VSyncDispatchTimerQueueTest.cpp
@@ -23,16 +23,19 @@
 #define LOG_TAG "LibSurfaceFlingerUnittests"
 #define LOG_NDEBUG 0
 
-#include "Scheduler/TimeKeeper.h"
-#include "Scheduler/VSyncDispatchTimerQueue.h"
-#include "Scheduler/VSyncTracker.h"
+#include <thread>
 
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
-#include <thread>
+
+#include <scheduler/TimeKeeper.h>
+
+#include "Scheduler/VSyncDispatchTimerQueue.h"
+#include "Scheduler/VSyncTracker.h"
 
 using namespace testing;
 using namespace std::literals;
+
 namespace android::scheduler {
 
 class MockVSyncTracker : public VSyncTracker {
@@ -71,10 +74,10 @@
         ON_CALL(*this, now()).WillByDefault(Invoke(this, &ControllableClock::fakeTime));
     }
 
-    MOCK_CONST_METHOD0(now, nsecs_t());
-    MOCK_METHOD2(alarmAt, void(std::function<void()> const&, nsecs_t time));
-    MOCK_METHOD0(alarmCancel, void());
-    MOCK_CONST_METHOD1(dump, void(std::string&));
+    MOCK_METHOD(nsecs_t, now, (), (const));
+    MOCK_METHOD(void, alarmAt, (std::function<void()>, nsecs_t), (override));
+    MOCK_METHOD(void, alarmCancel, (), (override));
+    MOCK_METHOD(void, dump, (std::string&), (const, override));
 
     void alarmAtDefaultBehavior(std::function<void()> const& callback, nsecs_t time) {
         mCallback = callback;
@@ -196,11 +199,14 @@
         class TimeKeeperWrapper : public TimeKeeper {
         public:
             TimeKeeperWrapper(TimeKeeper& control) : mControllableClock(control) {}
-            void alarmAt(std::function<void()> const& callback, nsecs_t time) final {
-                mControllableClock.alarmAt(callback, time);
-            }
-            void alarmCancel() final { mControllableClock.alarmCancel(); }
+
             nsecs_t now() const final { return mControllableClock.now(); }
+
+            void alarmAt(std::function<void()> callback, nsecs_t time) final {
+                mControllableClock.alarmAt(std::move(callback), time);
+            }
+
+            void alarmCancel() final { mControllableClock.alarmCancel(); }
             void dump(std::string&) const final {}
 
         private:
diff --git a/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp b/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp
index 37ecd7c..74d2b7d 100644
--- a/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp
+++ b/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp
@@ -464,16 +464,16 @@
     EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 100), Eq(mNow + mPeriod - bias));
     EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 990), Eq(mNow + 2 * mPeriod - bias));
 
-    const auto maxDivider = 5;
+    const auto maxDivisor = 5;
     const auto maxPeriods = 15;
-    for (int divider = 1; divider < maxDivider; divider++) {
+    for (int divisor = 1; divisor < maxDivisor; divisor++) {
         for (int i = 0; i < maxPeriods; i++) {
-            const bool expectedInPhase = (i % divider) == 0;
+            const bool expectedInPhase = (i % divisor) == 0;
             EXPECT_THAT(expectedInPhase,
                         tracker.isVSyncInPhase(mNow + i * mPeriod - bias,
-                                               Fps::fromPeriodNsecs(divider * mPeriod)))
+                                               Fps::fromPeriodNsecs(divisor * mPeriod)))
                     << "vsync at " << mNow + (i + 1) * mPeriod - bias << " is "
-                    << (expectedInPhase ? "not " : "") << "in phase for divider " << divider;
+                    << (expectedInPhase ? "not " : "") << "in phase for divisor " << divisor;
         }
     }
 }
diff --git a/services/surfaceflinger/tests/unittests/VSyncReactorTest.cpp b/services/surfaceflinger/tests/unittests/VSyncReactorTest.cpp
index 5826a9b..4eb9055 100644
--- a/services/surfaceflinger/tests/unittests/VSyncReactorTest.cpp
+++ b/services/surfaceflinger/tests/unittests/VSyncReactorTest.cpp
@@ -22,19 +22,22 @@
 #define LOG_TAG "LibSurfaceFlingerUnittests"
 #define LOG_NDEBUG 0
 
-#include "Scheduler/TimeKeeper.h"
-#include "Scheduler/VSyncDispatch.h"
-#include "Scheduler/VSyncReactor.h"
-#include "Scheduler/VSyncTracker.h"
+#include <array>
 
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
 #include <ui/Fence.h>
 #include <ui/FenceTime.h>
-#include <array>
+
+#include <scheduler/TimeKeeper.h>
+
+#include "Scheduler/VSyncDispatch.h"
+#include "Scheduler/VSyncReactor.h"
+#include "Scheduler/VSyncTracker.h"
 
 using namespace testing;
 using namespace std::literals;
+
 namespace android::scheduler {
 
 class MockVSyncTracker : public VSyncTracker {
@@ -65,14 +68,12 @@
     std::shared_ptr<Clock> const mClock;
 };
 
-class MockVSyncDispatch : public VSyncDispatch {
-public:
-    MOCK_METHOD2(registerCallback,
-                 CallbackToken(std::function<void(nsecs_t, nsecs_t, nsecs_t)> const&, std::string));
-    MOCK_METHOD1(unregisterCallback, void(CallbackToken));
-    MOCK_METHOD2(schedule, ScheduleResult(CallbackToken, ScheduleTiming));
-    MOCK_METHOD1(cancel, CancelResult(CallbackToken token));
-    MOCK_CONST_METHOD1(dump, void(std::string&));
+struct MockVSyncDispatch : VSyncDispatch {
+    MOCK_METHOD(CallbackToken, registerCallback, (Callback, std::string), (override));
+    MOCK_METHOD(void, unregisterCallback, (CallbackToken), (override));
+    MOCK_METHOD(ScheduleResult, schedule, (CallbackToken, ScheduleTiming), (override));
+    MOCK_METHOD(CancelResult, cancel, (CallbackToken), (override));
+    MOCK_METHOD(void, dump, (std::string&), (const, override));
 };
 
 std::shared_ptr<android::FenceTime> generateInvalidFence() {
@@ -497,4 +498,4 @@
 } // namespace android::scheduler
 
 // TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic pop // ignored "-Wextra"
\ No newline at end of file
+#pragma clang diagnostic pop // ignored "-Wextra"
diff --git a/services/surfaceflinger/tests/unittests/VsyncConfigurationTest.cpp b/services/surfaceflinger/tests/unittests/VsyncConfigurationTest.cpp
index 41a4d30..21ee071 100644
--- a/services/surfaceflinger/tests/unittests/VsyncConfigurationTest.cpp
+++ b/services/surfaceflinger/tests/unittests/VsyncConfigurationTest.cpp
@@ -44,7 +44,7 @@
 class WorkDurationTest : public testing::Test {
 protected:
     WorkDurationTest()
-          : mWorkDuration(Fps(60.0f), 10'500'000, 20'500'000, 16'000'000, 16'500'000, 13'500'000,
+          : mWorkDuration(60_Hz, 10'500'000, 20'500'000, 16'000'000, 16'500'000, 13'500'000,
                           21'000'000, 1234) {}
 
     ~WorkDurationTest() = default;
@@ -56,9 +56,9 @@
  * Test cases
  */
 TEST_F(WorkDurationTest, getConfigsForRefreshRate_60Hz) {
-    mWorkDuration.setRefreshRateFps(Fps(60.0f));
+    mWorkDuration.setRefreshRateFps(60_Hz);
     auto currentOffsets = mWorkDuration.getCurrentConfigs();
-    auto offsets = mWorkDuration.getConfigsForRefreshRate(Fps(60.0f));
+    auto offsets = mWorkDuration.getConfigsForRefreshRate(60_Hz);
 
     EXPECT_EQ(currentOffsets, offsets);
     EXPECT_EQ(offsets.late.sfOffset, 6'166'667);
@@ -81,9 +81,9 @@
 }
 
 TEST_F(WorkDurationTest, getConfigsForRefreshRate_90Hz) {
-    mWorkDuration.setRefreshRateFps(Fps(90.0f));
+    mWorkDuration.setRefreshRateFps(90_Hz);
     auto currentOffsets = mWorkDuration.getCurrentConfigs();
-    auto offsets = mWorkDuration.getConfigsForRefreshRate(Fps(90.0f));
+    auto offsets = mWorkDuration.getConfigsForRefreshRate(90_Hz);
 
     EXPECT_EQ(currentOffsets, offsets);
     EXPECT_EQ(offsets.late.sfOffset, 611'111);
@@ -106,7 +106,7 @@
 }
 
 TEST_F(WorkDurationTest, getConfigsForRefreshRate_DefaultOffsets) {
-    TestableWorkDuration phaseOffsetsWithDefaultValues(Fps(60.0f), -1, -1, -1, -1, -1, -1, 0);
+    TestableWorkDuration phaseOffsetsWithDefaultValues(60_Hz, -1, -1, -1, -1, -1, -1, 0);
 
     auto validateOffsets = [](const auto& offsets, std::chrono::nanoseconds vsyncPeriod) {
         EXPECT_EQ(offsets.late.sfOffset, 1'000'000);
@@ -138,12 +138,12 @@
         validateOffsets(offsets, std::chrono::nanoseconds(refreshRate.getPeriodNsecs()));
     };
 
-    testForRefreshRate(Fps(90.0f));
-    testForRefreshRate(Fps(60.0f));
+    testForRefreshRate(90_Hz);
+    testForRefreshRate(60_Hz);
 }
 
 TEST_F(WorkDurationTest, getConfigsForRefreshRate_unknownRefreshRate) {
-    auto offsets = mWorkDuration.getConfigsForRefreshRate(Fps(14.7f));
+    auto offsets = mWorkDuration.getConfigsForRefreshRate(14.7_Hz);
 
     EXPECT_EQ(offsets.late.sfOffset, 57'527'208);
     EXPECT_EQ(offsets.late.appOffset, 37'027'208);
@@ -181,13 +181,12 @@
                          std::optional<nsecs_t> highFpsEarlyAppOffsetNs,
                          std::optional<nsecs_t> highFpsEarlyGpuAppOffsetNs,
                          nsecs_t thresholdForNextVsync, nsecs_t hwcMinWorkDuration)
-          : impl::PhaseOffsets(Fps(60.0f), vsyncPhaseOffsetNs, sfVSyncPhaseOffsetNs,
-                               earlySfOffsetNs, earlyGpuSfOffsetNs, earlyAppOffsetNs,
-                               earlyGpuAppOffsetNs, highFpsVsyncPhaseOffsetNs,
-                               highFpsSfVSyncPhaseOffsetNs, highFpsEarlySfOffsetNs,
-                               highFpsEarlyGpuSfOffsetNs, highFpsEarlyAppOffsetNs,
-                               highFpsEarlyGpuAppOffsetNs, thresholdForNextVsync,
-                               hwcMinWorkDuration) {}
+          : impl::PhaseOffsets(60_Hz, vsyncPhaseOffsetNs, sfVSyncPhaseOffsetNs, earlySfOffsetNs,
+                               earlyGpuSfOffsetNs, earlyAppOffsetNs, earlyGpuAppOffsetNs,
+                               highFpsVsyncPhaseOffsetNs, highFpsSfVSyncPhaseOffsetNs,
+                               highFpsEarlySfOffsetNs, highFpsEarlyGpuSfOffsetNs,
+                               highFpsEarlyAppOffsetNs, highFpsEarlyGpuAppOffsetNs,
+                               thresholdForNextVsync, hwcMinWorkDuration) {}
 };
 
 class PhaseOffsetsTest : public testing::Test {
@@ -201,7 +200,7 @@
 };
 
 TEST_F(PhaseOffsetsTest, getConfigsForRefreshRate_unknownRefreshRate) {
-    auto offsets = mPhaseOffsets.getConfigsForRefreshRate(Fps(14.7f));
+    auto offsets = mPhaseOffsets.getConfigsForRefreshRate(14.7_Hz);
 
     EXPECT_EQ(offsets.late.sfOffset, 6'000'000);
     EXPECT_EQ(offsets.late.appOffset, 2'000'000);
@@ -223,7 +222,7 @@
 }
 
 TEST_F(PhaseOffsetsTest, getConfigsForRefreshRate_60Hz) {
-    auto offsets = mPhaseOffsets.getConfigsForRefreshRate(Fps(60.0f));
+    auto offsets = mPhaseOffsets.getConfigsForRefreshRate(60_Hz);
 
     EXPECT_EQ(offsets.late.sfOffset, 6'000'000);
     EXPECT_EQ(offsets.late.appOffset, 2'000'000);
@@ -245,7 +244,7 @@
 }
 
 TEST_F(PhaseOffsetsTest, getConfigsForRefreshRate_90Hz) {
-    auto offsets = mPhaseOffsets.getConfigsForRefreshRate(Fps(90.0f));
+    auto offsets = mPhaseOffsets.getConfigsForRefreshRate(90_Hz);
 
     EXPECT_EQ(offsets.late.sfOffset, 1'000'000);
     EXPECT_EQ(offsets.late.appOffset, 2'000'000);
@@ -269,7 +268,7 @@
 TEST_F(PhaseOffsetsTest, getConfigsForRefreshRate_DefaultValues_60Hz) {
     TestablePhaseOffsets phaseOffsets{1'000'000, 1'000'000, {}, {}, {}, {},         2'000'000,
                                       1'000'000, {},        {}, {}, {}, 10'000'000, 1234};
-    auto offsets = phaseOffsets.getConfigsForRefreshRate(Fps(60.0f));
+    auto offsets = phaseOffsets.getConfigsForRefreshRate(60_Hz);
 
     EXPECT_EQ(offsets.late.sfOffset, 1'000'000);
     EXPECT_EQ(offsets.late.appOffset, 1'000'000);
@@ -293,7 +292,7 @@
 TEST_F(PhaseOffsetsTest, getConfigsForRefreshRate_DefaultValues_90Hz) {
     TestablePhaseOffsets phaseOffsets{1'000'000, 1'000'000, {}, {}, {}, {},         2'000'000,
                                       1'000'000, {},        {}, {}, {}, 10'000'000, 1234};
-    auto offsets = phaseOffsets.getConfigsForRefreshRate(Fps(90.0f));
+    auto offsets = phaseOffsets.getConfigsForRefreshRate(90_Hz);
 
     EXPECT_EQ(offsets.late.sfOffset, 1'000'000);
     EXPECT_EQ(offsets.late.appOffset, 2'000'000);
diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.cpp b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.cpp
index 7de1872..20d41e6 100644
--- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.cpp
+++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.cpp
@@ -18,6 +18,7 @@
 #pragma clang diagnostic push
 #pragma clang diagnostic ignored "-Wconversion"
 
+#undef LOG_TAG
 #define LOG_TAG "MockComposer"
 #include "mock/DisplayHardware/MockComposer.h"
 
diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h
index 1ba3c0f..c1d41bb 100644
--- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h
+++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h
@@ -19,6 +19,7 @@
 #include <gmock/gmock.h>
 
 #include "DisplayHardware/ComposerHal.h"
+#include "DisplayHardware/HWC2.h"
 
 namespace android {
 
@@ -42,15 +43,19 @@
 using android::hardware::graphics::composer::V2_4::IComposerCallback;
 using android::hardware::graphics::composer::V2_4::IComposerClient;
 
+using aidl::android::hardware::graphics::common::DisplayDecorationSupport;
+
 class Composer : public Hwc2::Composer {
 public:
     using Display = android::hardware::graphics::composer::V2_1::Display;
     Composer();
     ~Composer() override;
 
-    MOCK_METHOD0(getCapabilities, std::vector<IComposer::Capability>());
+    MOCK_METHOD(bool, isSupported, (OptionalFeature), (const, override));
+    MOCK_METHOD0(getCapabilities,
+                 std::vector<aidl::android::hardware::graphics::composer3::Capability>());
     MOCK_METHOD0(dumpDebugInfo, std::string());
-    MOCK_METHOD1(registerCallback, void(const sp<IComposerCallback>&));
+    MOCK_METHOD1(registerCallback, void(HWC2::ComposerCallback&));
     MOCK_METHOD0(resetCommands, void());
     MOCK_METHOD0(executeCommands, Error());
     MOCK_METHOD0(getMaxVirtualDisplayCount, uint32_t());
@@ -61,7 +66,8 @@
     MOCK_METHOD2(destroyLayer, Error(Display, Layer));
     MOCK_METHOD2(getActiveConfig, Error(Display, Config*));
     MOCK_METHOD3(getChangedCompositionTypes,
-                 Error(Display, std::vector<Layer>*, std::vector<IComposerClient::Composition>*));
+                 Error(Display, std::vector<Layer>*,
+                       std::vector<aidl::android::hardware::graphics::composer3::Composition>*));
     MOCK_METHOD2(getColorModes, Error(Display, std::vector<ColorMode>*));
     MOCK_METHOD4(getDisplayAttribute,
                  Error(Display, Config config, IComposerClient::Attribute, int32_t*));
@@ -70,6 +76,7 @@
     MOCK_METHOD4(getDisplayRequests,
                  Error(Display, uint32_t*, std::vector<Layer>*, std::vector<uint32_t>*));
     MOCK_METHOD2(getDozeSupport, Error(Display, bool*));
+    MOCK_METHOD2(getKernelIdleTimerSupport, Error(Display, bool*));
     MOCK_METHOD5(getHdrCapabilities, Error(Display, std::vector<Hdr>*, float*, float*, float*));
     MOCK_METHOD1(getPerFrameMetadataKeys,
                  std::vector<IComposerClient::PerFrameMetadataKey>(Display));
@@ -82,20 +89,23 @@
                  Error(Display, uint32_t, const sp<GraphicBuffer>&, int, Dataspace,
                        const std::vector<IComposerClient::Rect>&));
     MOCK_METHOD3(setColorMode, Error(Display, ColorMode, RenderIntent));
-    MOCK_METHOD3(setColorTransform, Error(Display, const float*, ColorTransform));
+    MOCK_METHOD2(setColorTransform, Error(Display, const float*));
     MOCK_METHOD3(setOutputBuffer, Error(Display, const native_handle_t*, int));
     MOCK_METHOD2(setPowerMode, Error(Display, IComposerClient::PowerMode));
     MOCK_METHOD2(setVsyncEnabled, Error(Display, IComposerClient::Vsync));
     MOCK_METHOD1(setClientTargetSlotCount, Error(Display));
-    MOCK_METHOD3(validateDisplay, Error(Display, uint32_t*, uint32_t*));
-    MOCK_METHOD5(presentOrValidateDisplay, Error(Display, uint32_t*, uint32_t*, int*, uint32_t*));
+    MOCK_METHOD4(validateDisplay, Error(Display, nsecs_t, uint32_t*, uint32_t*));
+    MOCK_METHOD6(presentOrValidateDisplay,
+                 Error(Display, nsecs_t, uint32_t*, uint32_t*, int*, uint32_t*));
     MOCK_METHOD4(setCursorPosition, Error(Display, Layer, int32_t, int32_t));
     MOCK_METHOD5(setLayerBuffer, Error(Display, Layer, uint32_t, const sp<GraphicBuffer>&, int));
     MOCK_METHOD3(setLayerSurfaceDamage,
                  Error(Display, Layer, const std::vector<IComposerClient::Rect>&));
     MOCK_METHOD3(setLayerBlendMode, Error(Display, Layer, IComposerClient::BlendMode));
-    MOCK_METHOD3(setLayerColor, Error(Display, Layer, const IComposerClient::Color&));
-    MOCK_METHOD3(setLayerCompositionType, Error(Display, Layer, IComposerClient::Composition));
+    MOCK_METHOD3(setLayerColor,
+                 Error(Display, Layer, const aidl::android::hardware::graphics::composer3::Color&));
+    MOCK_METHOD3(setLayerCompositionType,
+                 Error(Display, Layer, aidl::android::hardware::graphics::composer3::Composition));
     MOCK_METHOD3(setLayerDataspace, Error(Display, Layer, Dataspace));
     MOCK_METHOD3(setLayerPerFrameMetadata,
                  Error(Display, Layer, const std::vector<IComposerClient::PerFrameMetadata>&));
@@ -116,9 +126,11 @@
                  Error(Display, uint64_t, uint64_t, DisplayedFrameStats*));
     MOCK_METHOD3(setLayerPerFrameMetadataBlobs,
                  Error(Display, Layer, const std::vector<IComposerClient::PerFrameMetadataBlob>&));
-    MOCK_METHOD2(setDisplayBrightness, Error(Display, float));
-    MOCK_METHOD0(isVsyncPeriodSwitchSupported, bool());
-    MOCK_METHOD2(getDisplayCapabilities, Error(Display, std::vector<DisplayCapability>*));
+    MOCK_METHOD3(setDisplayBrightness, Error(Display, float, const DisplayBrightnessOptions&));
+    MOCK_METHOD2(
+            getDisplayCapabilities,
+            Error(Display,
+                  std::vector<aidl::android::hardware::graphics::composer3::DisplayCapability>*));
     MOCK_METHOD2(getDisplayConnectionType,
                  V2_4::Error(Display, IComposerClient::DisplayConnectionType*));
     MOCK_METHOD3(getSupportedDisplayVsyncPeriods,
@@ -128,6 +140,9 @@
                  V2_4::Error(Display, Config, const IComposerClient::VsyncPeriodChangeConstraints&,
                              VsyncPeriodChangeTimeline*));
     MOCK_METHOD2(setAutoLowLatencyMode, V2_4::Error(Display, bool));
+    MOCK_METHOD2(setBootDisplayConfig, Error(Display, Config));
+    MOCK_METHOD1(clearBootDisplayConfig, Error(Display));
+    MOCK_METHOD2(getPreferredBootDisplayConfig, Error(Display, Config*));
     MOCK_METHOD2(getSupportedContentTypes,
                  V2_4::Error(Display, std::vector<IComposerClient::ContentType>*));
     MOCK_METHOD2(setContentType, V2_4::Error(Display, IComposerClient::ContentType));
@@ -136,7 +151,16 @@
                              const std::vector<uint8_t>&));
     MOCK_METHOD1(getLayerGenericMetadataKeys,
                  V2_4::Error(std::vector<IComposerClient::LayerGenericMetadataKey>*));
-    MOCK_METHOD2(getClientTargetProperty, Error(Display, IComposerClient::ClientTargetProperty*));
+    MOCK_METHOD3(getClientTargetProperty,
+                 Error(Display, IComposerClient::ClientTargetProperty*, float*));
+    MOCK_METHOD3(setLayerBrightness, Error(Display, Layer, float));
+    MOCK_METHOD3(setLayerBlockingRegion,
+                 Error(Display, Layer, const std::vector<IComposerClient::Rect>&));
+    MOCK_METHOD2(getDisplayDecorationSupport,
+                 Error(Display, std::optional<DisplayDecorationSupport>*));
+    MOCK_METHOD2(setIdleTimerEnabled, Error(Display, std::chrono::milliseconds));
+    MOCK_METHOD2(hasDisplayIdleTimerCapability, Error(Display, bool*));
+    MOCK_METHOD2(getPhysicalDisplayOrientation, Error(Display, AidlTransform*));
 };
 
 } // namespace Hwc2::mock
diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockDisplayMode.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockDisplayMode.h
new file mode 100644
index 0000000..a83ecbc
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockDisplayMode.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 "DisplayHardware/DisplayMode.h"
+
+namespace android::mock {
+
+inline DisplayModePtr createDisplayMode(
+        DisplayModeId modeId, Fps refreshRate, int32_t group = 0,
+        ui::Size resolution = ui::Size(1920, 1080),
+        PhysicalDisplayId displayId = PhysicalDisplayId::fromPort(0)) {
+    return DisplayMode::Builder(hal::HWConfigId(modeId.value()))
+            .setId(modeId)
+            .setPhysicalDisplayId(displayId)
+            .setVsyncPeriod(refreshRate.getPeriodNsecs())
+            .setGroup(group)
+            .setResolution(resolution)
+            .build();
+}
+
+} // namespace android::mock
diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockHWC2.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockHWC2.h
index c3919d9..ac2ab199c 100644
--- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockHWC2.h
+++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockHWC2.h
@@ -30,7 +30,8 @@
     MOCK_METHOD(hal::HWDisplayId, getId, (), (const, override));
     MOCK_METHOD(bool, isConnected, (), (const, override));
     MOCK_METHOD(void, setConnected, (bool), (override));
-    MOCK_METHOD(const std::unordered_set<hal::DisplayCapability> &, getCapabilities, (),
+    MOCK_METHOD(bool, hasCapability,
+                (aidl::android::hardware::graphics::composer3::DisplayCapability),
                 (const, override));
     MOCK_METHOD(bool, isVsyncPeriodSwitchSupported, (), (const, override));
     MOCK_METHOD(void, onLayerDestroyed, (hal::HWLayerId), (override));
@@ -39,7 +40,9 @@
     MOCK_METHOD((base::expected<std::shared_ptr<HWC2::Layer>, hal::Error>), createLayer, (),
                 (override));
     MOCK_METHOD(hal::Error, getChangedCompositionTypes,
-                ((std::unordered_map<Layer *, hal::Composition> *)), (override));
+                ((std::unordered_map<Layer *,
+                                     aidl::android::hardware::graphics::composer3::Composition> *)),
+                (override));
     MOCK_METHOD(hal::Error, getColorModes, (std::vector<hal::ColorMode> *), (const, override));
     MOCK_METHOD(int32_t, getSupportedPerFrameMetadata, (), (const, override));
     MOCK_METHOD(hal::Error, getRenderIntents, (hal::ColorMode, std::vector<hal::RenderIntent> *),
@@ -67,26 +70,39 @@
                  const android::sp<android::Fence> &, hal::Dataspace),
                 (override));
     MOCK_METHOD(hal::Error, setColorMode, (hal::ColorMode, hal::RenderIntent), (override));
-    MOCK_METHOD(hal::Error, setColorTransform, (const android::mat4 &, hal::ColorTransform),
-                (override));
+    MOCK_METHOD(hal::Error, setColorTransform, (const android::mat4 &), (override));
     MOCK_METHOD(hal::Error, setOutputBuffer,
                 (const android::sp<android::GraphicBuffer> &, const android::sp<android::Fence> &),
                 (override));
     MOCK_METHOD(hal::Error, setPowerMode, (hal::PowerMode), (override));
     MOCK_METHOD(hal::Error, setVsyncEnabled, (hal::Vsync), (override));
-    MOCK_METHOD(hal::Error, validate, (uint32_t *, uint32_t *), (override));
+    MOCK_METHOD(hal::Error, validate, (nsecs_t, uint32_t *, uint32_t *), (override));
     MOCK_METHOD(hal::Error, presentOrValidate,
-                (uint32_t *, uint32_t *, android::sp<android::Fence> *, uint32_t *), (override));
-    MOCK_METHOD(std::future<hal::Error>, setDisplayBrightness, (float), (override));
+                (nsecs_t, uint32_t *, uint32_t *, android::sp<android::Fence> *, uint32_t *),
+                (override));
+    MOCK_METHOD(std::future<hal::Error>, setDisplayBrightness,
+                (float, const Hwc2::Composer::DisplayBrightnessOptions &), (override));
     MOCK_METHOD(hal::Error, setActiveConfigWithConstraints,
                 (hal::HWConfigId, const hal::VsyncPeriodChangeConstraints &,
                  hal::VsyncPeriodChangeTimeline *),
                 (override));
+    MOCK_METHOD(hal::Error, setBootDisplayConfig, (hal::HWConfigId), (override));
+    MOCK_METHOD(hal::Error, clearBootDisplayConfig, (), (override));
+    MOCK_METHOD(hal::Error, getPreferredBootDisplayConfig, (hal::HWConfigId *), (const, override));
     MOCK_METHOD(hal::Error, setAutoLowLatencyMode, (bool), (override));
     MOCK_METHOD(hal::Error, getSupportedContentTypes, (std::vector<hal::ContentType> *),
                 (const, override));
     MOCK_METHOD(hal::Error, setContentType, (hal::ContentType), (override));
-    MOCK_METHOD(hal::Error, getClientTargetProperty, (hal::ClientTargetProperty *), (override));
+    MOCK_METHOD(hal::Error, getClientTargetProperty, (hal::ClientTargetProperty *, float *),
+                (override));
+    MOCK_METHOD(
+            hal::Error, getDisplayDecorationSupport,
+            (std::optional<aidl::android::hardware::graphics::common::DisplayDecorationSupport> *),
+            (override));
+    MOCK_METHOD(hal::Error, setIdleTimerEnabled, (std::chrono::milliseconds), (override));
+    MOCK_METHOD(bool, hasDisplayIdleTimerCapability, (), (const override));
+    MOCK_METHOD(hal::Error, getPhysicalDisplayOrientation, (Hwc2::AidlTransform *),
+                (const override));
 };
 
 class Layer : public HWC2::Layer {
@@ -102,8 +118,10 @@
                 (override));
     MOCK_METHOD(hal::Error, setSurfaceDamage, (const android::Region &), (override));
     MOCK_METHOD(hal::Error, setBlendMode, (hal::BlendMode), (override));
-    MOCK_METHOD(hal::Error, setColor, (hal::Color), (override));
-    MOCK_METHOD(hal::Error, setCompositionType, (hal::Composition), (override));
+    MOCK_METHOD(hal::Error, setColor, (aidl::android::hardware::graphics::composer3::Color),
+                (override));
+    MOCK_METHOD(hal::Error, setCompositionType,
+                (aidl::android::hardware::graphics::composer3::Composition), (override));
     MOCK_METHOD(hal::Error, setDataspace, (android::ui::Dataspace), (override));
     MOCK_METHOD(hal::Error, setPerFrameMetadata, (const int32_t, const android::HdrMetadata &),
                 (override));
@@ -117,6 +135,8 @@
     MOCK_METHOD(hal::Error, setColorTransform, (const android::mat4 &), (override));
     MOCK_METHOD(hal::Error, setLayerGenericMetadata,
                 (const std::string &, bool, const std::vector<uint8_t> &), (override));
+    MOCK_METHOD(hal::Error, setBrightness, (float), (override));
+    MOCK_METHOD(hal::Error, setBlockingRegion, (const android::Region &), (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 159bdf1..c598cbc 100644
--- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerAdvisor.h
+++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerAdvisor.h
@@ -27,11 +27,20 @@
     PowerAdvisor();
     ~PowerAdvisor() override;
 
-    MOCK_METHOD0(init, void());
-    MOCK_METHOD0(onBootFinished, void());
-    MOCK_METHOD2(setExpensiveRenderingExpected, void(DisplayId displayId, bool expected));
-    MOCK_METHOD0(isUsingExpensiveRendering, bool());
-    MOCK_METHOD0(notifyDisplayUpdateImminent, void());
+    MOCK_METHOD(void, init, (), (override));
+    MOCK_METHOD(void, onBootFinished, (), (override));
+    MOCK_METHOD(void, setExpensiveRenderingExpected, (DisplayId displayId, bool expected),
+                (override));
+    MOCK_METHOD(bool, isUsingExpensiveRendering, (), (override));
+    MOCK_METHOD(void, notifyDisplayUpdateImminent, (), (override));
+    MOCK_METHOD(bool, usePowerHintSession, (), (override));
+    MOCK_METHOD(bool, supportsPowerHintSession, (), (override));
+    MOCK_METHOD(bool, isPowerHintSessionRunning, (), (override));
+    MOCK_METHOD(void, setTargetWorkDuration, (int64_t targetDurationNanos), (override));
+    MOCK_METHOD(void, sendActualWorkDuration, (int64_t actualDurationNanos, nsecs_t timestamp),
+                (override));
+    MOCK_METHOD(void, enablePowerHint, (bool enabled), (override));
+    MOCK_METHOD(bool, startPowerHintSession, (const std::vector<int32_t>& threadIds), (override));
 };
 
 } // namespace android::Hwc2::mock
diff --git a/services/surfaceflinger/tests/unittests/mock/MockEventThread.h b/services/surfaceflinger/tests/unittests/mock/MockEventThread.h
index d25973e..c5ca86a 100644
--- a/services/surfaceflinger/tests/unittests/mock/MockEventThread.h
+++ b/services/surfaceflinger/tests/unittests/mock/MockEventThread.h
@@ -44,6 +44,8 @@
                  status_t(const sp<android::EventThreadConnection> &));
     MOCK_METHOD2(setVsyncRate, void(uint32_t, const sp<android::EventThreadConnection> &));
     MOCK_METHOD1(requestNextVsync, void(const sp<android::EventThreadConnection> &));
+    MOCK_METHOD(VsyncEventData, getLatestVsyncEventData,
+                (const sp<android::EventThreadConnection> &), (const));
     MOCK_METHOD1(requestLatestConfig, void(const sp<android::EventThreadConnection> &));
     MOCK_METHOD1(pauseVsyncCallback, void(bool));
     MOCK_METHOD0(getEventThreadConnectionCount, size_t());
diff --git a/services/surfaceflinger/tests/unittests/mock/MockLayer.h b/services/surfaceflinger/tests/unittests/mock/MockLayer.h
index ba2e4db..0840a2f 100644
--- a/services/surfaceflinger/tests/unittests/mock/MockLayer.h
+++ b/services/surfaceflinger/tests/unittests/mock/MockLayer.h
@@ -25,7 +25,7 @@
 class MockLayer : public Layer {
 public:
     MockLayer(SurfaceFlinger* flinger, std::string name)
-          : Layer(LayerCreationArgs(flinger, nullptr, std::move(name), 800, 600, 0, {})) {}
+          : Layer(LayerCreationArgs(flinger, nullptr, std::move(name), 0, {})) {}
     explicit MockLayer(SurfaceFlinger* flinger) : MockLayer(flinger, "TestLayer") {}
 
     MOCK_CONST_METHOD0(getType, const char*());
@@ -34,6 +34,7 @@
     MOCK_METHOD0(createClone, sp<Layer>());
     MOCK_CONST_METHOD0(getFrameRateForLayerTree, FrameRate());
     MOCK_CONST_METHOD0(getOwnerUid, uid_t());
+    MOCK_CONST_METHOD0(getDataSpace, ui::Dataspace());
 };
 
 } // namespace android::mock
diff --git a/services/surfaceflinger/tests/unittests/mock/MockMessageQueue.cpp b/services/surfaceflinger/tests/unittests/mock/MockMessageQueue.cpp
deleted file mode 100644
index 5fb06fd..0000000
--- a/services/surfaceflinger/tests/unittests/mock/MockMessageQueue.cpp
+++ /dev/null
@@ -1,30 +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.
- */
-
-#include "mock/MockMessageQueue.h"
-
-namespace android::mock {
-
-MessageQueue::MessageQueue() {
-    ON_CALL(*this, postMessage).WillByDefault([](sp<MessageHandler>&& handler) {
-        // Execute task to prevent broken promise exception on destruction.
-        handler->handleMessage(Message());
-    });
-}
-
-MessageQueue::~MessageQueue() = default;
-
-} // namespace android::mock
diff --git a/services/surfaceflinger/tests/unittests/mock/MockMessageQueue.h b/services/surfaceflinger/tests/unittests/mock/MockMessageQueue.h
deleted file mode 100644
index 0e7b320..0000000
--- a/services/surfaceflinger/tests/unittests/mock/MockMessageQueue.h
+++ /dev/null
@@ -1,45 +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.
- */
-
-#pragma once
-
-#include <gmock/gmock.h>
-
-#include "FrameTimeline.h"
-#include "Scheduler/EventThread.h"
-#include "Scheduler/MessageQueue.h"
-
-namespace android::mock {
-
-class MessageQueue : public android::MessageQueue {
-public:
-    MessageQueue();
-    ~MessageQueue() override;
-
-    MOCK_METHOD1(init, void(const sp<SurfaceFlinger>&));
-    MOCK_METHOD1(setInjector, void(sp<EventThreadConnection>));
-    MOCK_METHOD0(waitMessage, void());
-    MOCK_METHOD1(postMessage, void(sp<MessageHandler>&&));
-    MOCK_METHOD0(invalidate, void());
-    MOCK_METHOD0(refresh, void());
-    MOCK_METHOD3(initVsync,
-                 void(scheduler::VSyncDispatch&, frametimeline::TokenManager&,
-                      std::chrono::nanoseconds));
-    MOCK_METHOD1(setDuration, void(std::chrono::nanoseconds workDuration));
-    MOCK_METHOD0(nextExpectedInvalidate, std::optional<std::chrono::steady_clock::time_point>());
-};
-
-} // namespace android::mock
diff --git a/services/surfaceflinger/tests/unittests/mock/MockSchedulerCallback.h b/services/surfaceflinger/tests/unittests/mock/MockSchedulerCallback.h
index ab19886..5267586 100644
--- a/services/surfaceflinger/tests/unittests/mock/MockSchedulerCallback.h
+++ b/services/surfaceflinger/tests/unittests/mock/MockSchedulerCallback.h
@@ -20,25 +20,20 @@
 
 #include "Scheduler/Scheduler.h"
 
-namespace android::mock {
+namespace android::scheduler::mock {
 
 struct SchedulerCallback final : ISchedulerCallback {
-    MOCK_METHOD1(setVsyncEnabled, void(bool));
-    MOCK_METHOD2(changeRefreshRate,
-                 void(const scheduler::RefreshRateConfigs::RefreshRate&,
-                      scheduler::RefreshRateConfigEvent));
-    MOCK_METHOD0(repaintEverythingForHWC, void());
-    MOCK_METHOD1(kernelTimerChanged, void(bool));
-    MOCK_METHOD0(triggerOnFrameRateOverridesChanged, void());
+    MOCK_METHOD(void, setVsyncEnabled, (bool), (override));
+    MOCK_METHOD(void, requestDisplayMode, (DisplayModePtr, DisplayModeEvent), (override));
+    MOCK_METHOD(void, kernelTimerChanged, (bool), (override));
+    MOCK_METHOD(void, triggerOnFrameRateOverridesChanged, (), (override));
 };
 
 struct NoOpSchedulerCallback final : ISchedulerCallback {
     void setVsyncEnabled(bool) override {}
-    void changeRefreshRate(const scheduler::RefreshRateConfigs::RefreshRate&,
-                           scheduler::RefreshRateConfigEvent) override {}
-    void repaintEverythingForHWC() override {}
+    void requestDisplayMode(DisplayModePtr, DisplayModeEvent) override {}
     void kernelTimerChanged(bool) override {}
-    void triggerOnFrameRateOverridesChanged() {}
+    void triggerOnFrameRateOverridesChanged() override {}
 };
 
-} // namespace android::mock
+} // namespace android::scheduler::mock
diff --git a/services/surfaceflinger/tests/unittests/mock/MockTimeStats.h b/services/surfaceflinger/tests/unittests/mock/MockTimeStats.h
index 5aebd2f..0a69b56 100644
--- a/services/surfaceflinger/tests/unittests/mock/MockTimeStats.h
+++ b/services/surfaceflinger/tests/unittests/mock/MockTimeStats.h
@@ -41,19 +41,21 @@
     MOCK_METHOD2(recordFrameDuration, void(nsecs_t, nsecs_t));
     MOCK_METHOD2(recordRenderEngineDuration, void(nsecs_t, nsecs_t));
     MOCK_METHOD2(recordRenderEngineDuration, void(nsecs_t, const std::shared_ptr<FenceTime>&));
-    MOCK_METHOD6(setPostTime, void(int32_t, uint64_t, const std::string&, uid_t, nsecs_t, int32_t));
+    MOCK_METHOD(void, setPostTime,
+                (int32_t, uint64_t, const std::string&, uid_t, nsecs_t, GameMode), (override));
     MOCK_METHOD2(incrementLatchSkipped, void(int32_t layerId, LatchSkipReason reason));
     MOCK_METHOD1(incrementBadDesiredPresent, void(int32_t layerId));
     MOCK_METHOD3(setLatchTime, void(int32_t, uint64_t, nsecs_t));
     MOCK_METHOD3(setDesiredTime, void(int32_t, uint64_t, nsecs_t));
     MOCK_METHOD3(setAcquireTime, void(int32_t, uint64_t, nsecs_t));
     MOCK_METHOD3(setAcquireFence, void(int32_t, uint64_t, const std::shared_ptr<FenceTime>&));
-    MOCK_METHOD7(setPresentTime,
-                 void(int32_t, uint64_t, nsecs_t, Fps, std::optional<Fps>, SetFrameRateVote,
-                      int32_t));
-    MOCK_METHOD7(setPresentFence,
-                 void(int32_t, uint64_t, const std::shared_ptr<FenceTime>&, Fps, std::optional<Fps>,
-                      SetFrameRateVote, int32_t));
+    MOCK_METHOD(void, setPresentTime,
+                (int32_t, uint64_t, nsecs_t, Fps, std::optional<Fps>, SetFrameRateVote, GameMode),
+                (override));
+    MOCK_METHOD(void, setPresentFence,
+                (int32_t, uint64_t, const std::shared_ptr<FenceTime>&, Fps, std::optional<Fps>,
+                 SetFrameRateVote, GameMode),
+                (override));
     MOCK_METHOD1(incrementJankyFrames, void(const JankyFramesInfo&));
     MOCK_METHOD1(onDestroy, void(int32_t));
     MOCK_METHOD2(removeTimeRecord, void(int32_t, uint64_t));
diff --git a/services/surfaceflinger/tests/unittests/mock/MockVsyncController.h b/services/surfaceflinger/tests/unittests/mock/MockVsyncController.h
index 94d9966..314f681 100644
--- a/services/surfaceflinger/tests/unittests/mock/MockVsyncController.h
+++ b/services/surfaceflinger/tests/unittests/mock/MockVsyncController.h
@@ -27,7 +27,7 @@
     VsyncController();
     ~VsyncController() override;
 
-    MOCK_METHOD1(addPresentFence, bool(const std::shared_ptr<FenceTime>&));
+    MOCK_METHOD(bool, addPresentFence, (std::shared_ptr<FenceTime>), (override));
     MOCK_METHOD3(addHwVsyncTimestamp, bool(nsecs_t, std::optional<nsecs_t>, bool*));
     MOCK_METHOD1(startPeriodTransition, void(nsecs_t));
     MOCK_METHOD1(setIgnorePresentFences, void(bool));
diff --git a/services/surfaceflinger/tests/utils/ScreenshotUtils.h b/services/surfaceflinger/tests/utils/ScreenshotUtils.h
index cae7684..ee7e92c 100644
--- a/services/surfaceflinger/tests/utils/ScreenshotUtils.h
+++ b/services/surfaceflinger/tests/utils/ScreenshotUtils.h
@@ -16,6 +16,7 @@
 #pragma once
 
 #include <gui/SyncScreenCaptureListener.h>
+#include <private/gui/ComposerServiceAIDL.h>
 #include <ui/Rect.h>
 #include <utils/String8.h>
 #include <functional>
@@ -31,15 +32,15 @@
 public:
     static status_t captureDisplay(DisplayCaptureArgs& captureArgs,
                                    ScreenCaptureResults& captureResults) {
-        const auto sf = ComposerService::getComposerService();
+        const auto sf = ComposerServiceAIDL::getComposerService();
         SurfaceComposerClient::Transaction().apply(true);
 
         captureArgs.dataspace = ui::Dataspace::V0_SRGB;
         const sp<SyncScreenCaptureListener> captureListener = new SyncScreenCaptureListener();
-        status_t status = sf->captureDisplay(captureArgs, captureListener);
+        binder::Status status = sf->captureDisplay(captureArgs, captureListener);
 
-        if (status != NO_ERROR) {
-            return status;
+        if (status.transactionError() != NO_ERROR) {
+            return status.transactionError();
         }
         captureResults = captureListener->waitForResults();
         return captureResults.result;
@@ -64,14 +65,14 @@
 
     static status_t captureLayers(LayerCaptureArgs& captureArgs,
                                   ScreenCaptureResults& captureResults) {
-        const auto sf = ComposerService::getComposerService();
+        const auto sf = ComposerServiceAIDL::getComposerService();
         SurfaceComposerClient::Transaction().apply(true);
 
         captureArgs.dataspace = ui::Dataspace::V0_SRGB;
         const sp<SyncScreenCaptureListener> captureListener = new SyncScreenCaptureListener();
-        status_t status = sf->captureLayers(captureArgs, captureListener);
-        if (status != NO_ERROR) {
-            return status;
+        binder::Status status = sf->captureLayers(captureArgs, captureListener);
+        if (status.transactionError() != NO_ERROR) {
+            return status.transactionError();
         }
         captureResults = captureListener->waitForResults();
         return captureResults.result;
diff --git a/services/vibratorservice/VibratorHalWrapper.cpp b/services/vibratorservice/VibratorHalWrapper.cpp
index a375808..63ecaec 100644
--- a/services/vibratorservice/VibratorHalWrapper.cpp
+++ b/services/vibratorservice/VibratorHalWrapper.cpp
@@ -402,12 +402,21 @@
         auto primitiveIdx = static_cast<size_t>(primitive);
         if (primitiveIdx >= durations.size()) {
             // Safety check, should not happen if enum_range is correct.
+            ALOGE("Supported primitive %zu is outside range [0,%zu), skipping load duration",
+                  primitiveIdx, durations.size());
             continue;
         }
         int32_t duration = 0;
-        auto status = getHal()->getPrimitiveDuration(primitive, &duration);
-        if (!status.isOk()) {
-            return HalResult<std::vector<milliseconds>>::failed(status.toString8().c_str());
+        auto result = getHal()->getPrimitiveDuration(primitive, &duration);
+        auto halResult = HalResult<int32_t>::fromStatus(result, duration);
+        if (halResult.isUnsupported()) {
+            // Should not happen, supported primitives should always support requesting duration.
+            ALOGE("Supported primitive %zu returned unsupported for getPrimitiveDuration",
+                  primitiveIdx);
+        }
+        if (halResult.isFailed()) {
+            // Fail entire request if one request has failed.
+            return HalResult<std::vector<milliseconds>>::failed(result.toString8().c_str());
         }
         durations[primitiveIdx] = milliseconds(duration);
     }
diff --git a/services/vibratorservice/VibratorManagerHalController.cpp b/services/vibratorservice/VibratorManagerHalController.cpp
index 6bf6581..0df0bfa 100644
--- a/services/vibratorservice/VibratorManagerHalController.cpp
+++ b/services/vibratorservice/VibratorManagerHalController.cpp
@@ -45,7 +45,7 @@
 template <typename T>
 HalResult<T> ManagerHalController::processHalResult(HalResult<T> result, const char* functionName) {
     if (result.isFailed()) {
-        ALOGE("%s failed: %s", functionName, result.errorMessage());
+        ALOGE("VibratorManager HAL %s failed: %s", functionName, result.errorMessage());
         std::lock_guard<std::mutex> lock(mConnectedHalMutex);
         mConnectedHal->tryReconnect();
     }
diff --git a/services/vibratorservice/include/vibratorservice/VibratorHalController.h b/services/vibratorservice/include/vibratorservice/VibratorHalController.h
index 6c31e2b..6b73d17 100644
--- a/services/vibratorservice/include/vibratorservice/VibratorHalController.h
+++ b/services/vibratorservice/include/vibratorservice/VibratorHalController.h
@@ -103,7 +103,7 @@
 
         for (int i = 0; i < MAX_RETRIES; i++) {
             T result = halFn(hal.get());
-            if (result.checkAndLogFailure(functionName)) {
+            if (result.isFailedLogged(functionName)) {
                 tryReconnect();
             } else {
                 return result;
diff --git a/services/vibratorservice/include/vibratorservice/VibratorHalWrapper.h b/services/vibratorservice/include/vibratorservice/VibratorHalWrapper.h
index 68d6647..d2cc9ad 100644
--- a/services/vibratorservice/include/vibratorservice/VibratorHalWrapper.h
+++ b/services/vibratorservice/include/vibratorservice/VibratorHalWrapper.h
@@ -69,9 +69,9 @@
     bool isFailed() const { return !mUnsupported && !mValue.has_value(); }
     bool isUnsupported() const { return mUnsupported; }
     const char* errorMessage() const { return mErrorMessage.c_str(); }
-    bool checkAndLogFailure(const char* functionName) const {
+    bool isFailedLogged(const char* functionNameForLogging) const {
         if (isFailed()) {
-            ALOGE("%s failed: %s", functionName, errorMessage());
+            ALOGE("Vibrator HAL %s failed: %s", functionNameForLogging, errorMessage());
             return true;
         }
         return false;
@@ -107,9 +107,9 @@
     bool isFailed() const { return !mUnsupported && mFailed; }
     bool isUnsupported() const { return mUnsupported; }
     const char* errorMessage() const { return mErrorMessage.c_str(); }
-    bool checkAndLogFailure(const char* functionName) const {
+    bool isFailedLogged(const char* functionNameForLogging) const {
         if (isFailed()) {
-            ALOGE("%s failed: %s", functionName, errorMessage());
+            ALOGE("Vibrator HAL %s failed: %s", functionNameForLogging, errorMessage());
             return true;
         }
         return false;
@@ -192,21 +192,21 @@
     const HalResult<float> qFactor;
     const HalResult<std::vector<float>> maxAmplitudes;
 
-    bool checkAndLogFailure(const char*) const {
-        return capabilities.checkAndLogFailure("getCapabilities") ||
-                supportedEffects.checkAndLogFailure("getSupportedEffects") ||
-                supportedBraking.checkAndLogFailure("getSupportedBraking") ||
-                supportedPrimitives.checkAndLogFailure("getSupportedPrimitives") ||
-                primitiveDurations.checkAndLogFailure("getPrimitiveDuration") ||
-                primitiveDelayMax.checkAndLogFailure("getPrimitiveDelayMax") ||
-                pwlePrimitiveDurationMax.checkAndLogFailure("getPwlePrimitiveDurationMax") ||
-                compositionSizeMax.checkAndLogFailure("getCompositionSizeMax") ||
-                pwleSizeMax.checkAndLogFailure("getPwleSizeMax") ||
-                minFrequency.checkAndLogFailure("getMinFrequency") ||
-                resonantFrequency.checkAndLogFailure("getResonantFrequency") ||
-                frequencyResolution.checkAndLogFailure("getFrequencyResolution") ||
-                qFactor.checkAndLogFailure("getQFactor") ||
-                maxAmplitudes.checkAndLogFailure("getMaxAmplitudes");
+    bool isFailedLogged(const char*) const {
+        return capabilities.isFailedLogged("getCapabilities") ||
+                supportedEffects.isFailedLogged("getSupportedEffects") ||
+                supportedBraking.isFailedLogged("getSupportedBraking") ||
+                supportedPrimitives.isFailedLogged("getSupportedPrimitives") ||
+                primitiveDurations.isFailedLogged("getPrimitiveDuration") ||
+                primitiveDelayMax.isFailedLogged("getPrimitiveDelayMax") ||
+                pwlePrimitiveDurationMax.isFailedLogged("getPwlePrimitiveDurationMax") ||
+                compositionSizeMax.isFailedLogged("getCompositionSizeMax") ||
+                pwleSizeMax.isFailedLogged("getPwleSizeMax") ||
+                minFrequency.isFailedLogged("getMinFrequency") ||
+                resonantFrequency.isFailedLogged("getResonantFrequency") ||
+                frequencyResolution.isFailedLogged("getFrequencyResolution") ||
+                qFactor.isFailedLogged("getQFactor") ||
+                maxAmplitudes.isFailedLogged("getMaxAmplitudes");
     }
 };
 
diff --git a/vulkan/libvulkan/api.cpp b/vulkan/libvulkan/api.cpp
index fa3b260..c335e2a 100644
--- a/vulkan/libvulkan/api.cpp
+++ b/vulkan/libvulkan/api.cpp
@@ -1473,7 +1473,7 @@
     if (!EnsureInitialized())
         return VK_ERROR_OUT_OF_HOST_MEMORY;
 
-    *pApiVersion = VK_API_VERSION_1_1;
+    *pApiVersion = VK_API_VERSION_1_3;
     return VK_SUCCESS;
 }
 
diff --git a/vulkan/libvulkan/api_gen.cpp b/vulkan/libvulkan/api_gen.cpp
index 26052fb..df70bf4 100644
--- a/vulkan/libvulkan/api_gen.cpp
+++ b/vulkan/libvulkan/api_gen.cpp
@@ -179,6 +179,7 @@
     INIT_PROC(false, instance, GetPhysicalDeviceExternalFenceProperties);
     INIT_PROC(false, instance, EnumeratePhysicalDeviceGroups);
     INIT_PROC_EXT(KHR_swapchain, false, instance, GetPhysicalDevicePresentRectanglesKHR);
+    INIT_PROC(false, instance, GetPhysicalDeviceToolProperties);
     // clang-format on
 
     return success;
@@ -334,6 +335,9 @@
     INIT_PROC(false, dev, GetBufferMemoryRequirements2);
     INIT_PROC(false, dev, GetImageMemoryRequirements2);
     INIT_PROC(false, dev, GetImageSparseMemoryRequirements2);
+    INIT_PROC(false, dev, GetDeviceBufferMemoryRequirements);
+    INIT_PROC(false, dev, GetDeviceImageMemoryRequirements);
+    INIT_PROC(false, dev, GetDeviceImageSparseMemoryRequirements);
     INIT_PROC(false, dev, CreateSamplerYcbcrConversion);
     INIT_PROC(false, dev, DestroySamplerYcbcrConversion);
     INIT_PROC(false, dev, GetDeviceQueue2);
@@ -352,6 +356,39 @@
     INIT_PROC(false, dev, GetBufferOpaqueCaptureAddress);
     INIT_PROC(false, dev, GetBufferDeviceAddress);
     INIT_PROC(false, dev, GetDeviceMemoryOpaqueCaptureAddress);
+    INIT_PROC(false, dev, CmdSetCullMode);
+    INIT_PROC(false, dev, CmdSetFrontFace);
+    INIT_PROC(false, dev, CmdSetPrimitiveTopology);
+    INIT_PROC(false, dev, CmdSetViewportWithCount);
+    INIT_PROC(false, dev, CmdSetScissorWithCount);
+    INIT_PROC(false, dev, CmdBindVertexBuffers2);
+    INIT_PROC(false, dev, CmdSetDepthTestEnable);
+    INIT_PROC(false, dev, CmdSetDepthWriteEnable);
+    INIT_PROC(false, dev, CmdSetDepthCompareOp);
+    INIT_PROC(false, dev, CmdSetDepthBoundsTestEnable);
+    INIT_PROC(false, dev, CmdSetStencilTestEnable);
+    INIT_PROC(false, dev, CmdSetStencilOp);
+    INIT_PROC(false, dev, CmdSetRasterizerDiscardEnable);
+    INIT_PROC(false, dev, CmdSetDepthBiasEnable);
+    INIT_PROC(false, dev, CmdSetPrimitiveRestartEnable);
+    INIT_PROC(false, dev, CreatePrivateDataSlot);
+    INIT_PROC(false, dev, DestroyPrivateDataSlot);
+    INIT_PROC(false, dev, SetPrivateData);
+    INIT_PROC(false, dev, GetPrivateData);
+    INIT_PROC(false, dev, CmdCopyBuffer2);
+    INIT_PROC(false, dev, CmdCopyImage2);
+    INIT_PROC(false, dev, CmdBlitImage2);
+    INIT_PROC(false, dev, CmdCopyBufferToImage2);
+    INIT_PROC(false, dev, CmdCopyImageToBuffer2);
+    INIT_PROC(false, dev, CmdResolveImage2);
+    INIT_PROC(false, dev, CmdSetEvent2);
+    INIT_PROC(false, dev, CmdResetEvent2);
+    INIT_PROC(false, dev, CmdWaitEvents2);
+    INIT_PROC(false, dev, CmdPipelineBarrier2);
+    INIT_PROC(false, dev, QueueSubmit2);
+    INIT_PROC(false, dev, CmdWriteTimestamp2);
+    INIT_PROC(false, dev, CmdBeginRendering);
+    INIT_PROC(false, dev, CmdEndRendering);
     // clang-format on
 
     return success;
@@ -530,6 +567,9 @@
 VKAPI_ATTR void GetBufferMemoryRequirements2(VkDevice device, const VkBufferMemoryRequirementsInfo2* pInfo, VkMemoryRequirements2* pMemoryRequirements);
 VKAPI_ATTR void GetImageMemoryRequirements2(VkDevice device, const VkImageMemoryRequirementsInfo2* pInfo, VkMemoryRequirements2* pMemoryRequirements);
 VKAPI_ATTR void GetImageSparseMemoryRequirements2(VkDevice device, const VkImageSparseMemoryRequirementsInfo2* pInfo, uint32_t* pSparseMemoryRequirementCount, VkSparseImageMemoryRequirements2* pSparseMemoryRequirements);
+VKAPI_ATTR void GetDeviceBufferMemoryRequirements(VkDevice device, const VkDeviceBufferMemoryRequirements* pInfo, VkMemoryRequirements2* pMemoryRequirements);
+VKAPI_ATTR void GetDeviceImageMemoryRequirements(VkDevice device, const VkDeviceImageMemoryRequirements* pInfo, VkMemoryRequirements2* pMemoryRequirements);
+VKAPI_ATTR void GetDeviceImageSparseMemoryRequirements(VkDevice device, const VkDeviceImageMemoryRequirements* pInfo, uint32_t* pSparseMemoryRequirementCount, VkSparseImageMemoryRequirements2* pSparseMemoryRequirements);
 VKAPI_ATTR VkResult CreateSamplerYcbcrConversion(VkDevice device, const VkSamplerYcbcrConversionCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkSamplerYcbcrConversion* pYcbcrConversion);
 VKAPI_ATTR void DestroySamplerYcbcrConversion(VkDevice device, VkSamplerYcbcrConversion ycbcrConversion, const VkAllocationCallbacks* pAllocator);
 VKAPI_ATTR void GetDeviceQueue2(VkDevice device, const VkDeviceQueueInfo2* pQueueInfo, VkQueue* pQueue);
@@ -548,6 +588,40 @@
 VKAPI_ATTR uint64_t GetBufferOpaqueCaptureAddress(VkDevice device, const VkBufferDeviceAddressInfo* pInfo);
 VKAPI_ATTR VkDeviceAddress GetBufferDeviceAddress(VkDevice device, const VkBufferDeviceAddressInfo* pInfo);
 VKAPI_ATTR uint64_t GetDeviceMemoryOpaqueCaptureAddress(VkDevice device, const VkDeviceMemoryOpaqueCaptureAddressInfo* pInfo);
+VKAPI_ATTR VkResult GetPhysicalDeviceToolProperties(VkPhysicalDevice physicalDevice, uint32_t* pToolCount, VkPhysicalDeviceToolProperties* pToolProperties);
+VKAPI_ATTR void CmdSetCullMode(VkCommandBuffer commandBuffer, VkCullModeFlags cullMode);
+VKAPI_ATTR void CmdSetFrontFace(VkCommandBuffer commandBuffer, VkFrontFace frontFace);
+VKAPI_ATTR void CmdSetPrimitiveTopology(VkCommandBuffer commandBuffer, VkPrimitiveTopology primitiveTopology);
+VKAPI_ATTR void CmdSetViewportWithCount(VkCommandBuffer commandBuffer, uint32_t viewportCount, const VkViewport* pViewports);
+VKAPI_ATTR void CmdSetScissorWithCount(VkCommandBuffer commandBuffer, uint32_t scissorCount, const VkRect2D* pScissors);
+VKAPI_ATTR void CmdBindVertexBuffers2(VkCommandBuffer commandBuffer, uint32_t firstBinding, uint32_t bindingCount, const VkBuffer* pBuffers, const VkDeviceSize* pOffsets, const VkDeviceSize* pSizes, const VkDeviceSize* pStrides);
+VKAPI_ATTR void CmdSetDepthTestEnable(VkCommandBuffer commandBuffer, VkBool32 depthTestEnable);
+VKAPI_ATTR void CmdSetDepthWriteEnable(VkCommandBuffer commandBuffer, VkBool32 depthWriteEnable);
+VKAPI_ATTR void CmdSetDepthCompareOp(VkCommandBuffer commandBuffer, VkCompareOp depthCompareOp);
+VKAPI_ATTR void CmdSetDepthBoundsTestEnable(VkCommandBuffer commandBuffer, VkBool32 depthBoundsTestEnable);
+VKAPI_ATTR void CmdSetStencilTestEnable(VkCommandBuffer commandBuffer, VkBool32 stencilTestEnable);
+VKAPI_ATTR void CmdSetStencilOp(VkCommandBuffer commandBuffer, VkStencilFaceFlags faceMask, VkStencilOp failOp, VkStencilOp passOp, VkStencilOp depthFailOp, VkCompareOp compareOp);
+VKAPI_ATTR void CmdSetRasterizerDiscardEnable(VkCommandBuffer commandBuffer, VkBool32 rasterizerDiscardEnable);
+VKAPI_ATTR void CmdSetDepthBiasEnable(VkCommandBuffer commandBuffer, VkBool32 depthBiasEnable);
+VKAPI_ATTR void CmdSetPrimitiveRestartEnable(VkCommandBuffer commandBuffer, VkBool32 primitiveRestartEnable);
+VKAPI_ATTR VkResult CreatePrivateDataSlot(VkDevice device, const VkPrivateDataSlotCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkPrivateDataSlot* pPrivateDataSlot);
+VKAPI_ATTR void DestroyPrivateDataSlot(VkDevice device, VkPrivateDataSlot privateDataSlot, const VkAllocationCallbacks* pAllocator);
+VKAPI_ATTR VkResult SetPrivateData(VkDevice device, VkObjectType objectType, uint64_t objectHandle, VkPrivateDataSlot privateDataSlot, uint64_t data);
+VKAPI_ATTR void GetPrivateData(VkDevice device, VkObjectType objectType, uint64_t objectHandle, VkPrivateDataSlot privateDataSlot, uint64_t* pData);
+VKAPI_ATTR void CmdCopyBuffer2(VkCommandBuffer commandBuffer, const VkCopyBufferInfo2* pCopyBufferInfo);
+VKAPI_ATTR void CmdCopyImage2(VkCommandBuffer commandBuffer, const VkCopyImageInfo2* pCopyImageInfo);
+VKAPI_ATTR void CmdBlitImage2(VkCommandBuffer commandBuffer, const VkBlitImageInfo2* pBlitImageInfo);
+VKAPI_ATTR void CmdCopyBufferToImage2(VkCommandBuffer commandBuffer, const VkCopyBufferToImageInfo2* pCopyBufferToImageInfo);
+VKAPI_ATTR void CmdCopyImageToBuffer2(VkCommandBuffer commandBuffer, const VkCopyImageToBufferInfo2* pCopyImageToBufferInfo);
+VKAPI_ATTR void CmdResolveImage2(VkCommandBuffer commandBuffer, const VkResolveImageInfo2* pResolveImageInfo);
+VKAPI_ATTR void CmdSetEvent2(VkCommandBuffer commandBuffer, VkEvent event, const VkDependencyInfo* pDependencyInfo);
+VKAPI_ATTR void CmdResetEvent2(VkCommandBuffer commandBuffer, VkEvent event, VkPipelineStageFlags2 stageMask);
+VKAPI_ATTR void CmdWaitEvents2(VkCommandBuffer commandBuffer, uint32_t eventCount, const VkEvent* pEvents, const VkDependencyInfo* pDependencyInfos);
+VKAPI_ATTR void CmdPipelineBarrier2(VkCommandBuffer commandBuffer, const VkDependencyInfo* pDependencyInfo);
+VKAPI_ATTR VkResult QueueSubmit2(VkQueue queue, uint32_t submitCount, const VkSubmitInfo2* pSubmits, VkFence fence);
+VKAPI_ATTR void CmdWriteTimestamp2(VkCommandBuffer commandBuffer, VkPipelineStageFlags2 stage, VkQueryPool queryPool, uint32_t query);
+VKAPI_ATTR void CmdBeginRendering(VkCommandBuffer commandBuffer, const VkRenderingInfo* pRenderingInfo);
+VKAPI_ATTR void CmdEndRendering(VkCommandBuffer commandBuffer);
 
 VKAPI_ATTR VkResult EnumeratePhysicalDevices(VkInstance instance, uint32_t* pPhysicalDeviceCount, VkPhysicalDevice* pPhysicalDevices) {
     return GetData(instance).dispatch.EnumeratePhysicalDevices(instance, pPhysicalDeviceCount, pPhysicalDevices);
@@ -560,6 +634,7 @@
     }
 
     static const char* const known_non_device_names[] = {
+        "vkAcquireDrmDisplayEXT",
         "vkCreateAndroidSurfaceKHR",
         "vkCreateDebugReportCallbackEXT",
         "vkCreateDebugUtilsMessengerEXT",
@@ -581,6 +656,7 @@
         "vkEnumeratePhysicalDevices",
         "vkGetDisplayModeProperties2KHR",
         "vkGetDisplayPlaneCapabilities2KHR",
+        "vkGetDrmDisplayEXT",
         "vkGetInstanceProcAddr",
         "vkGetPhysicalDeviceCalibrateableTimeDomainsEXT",
         "vkGetPhysicalDeviceDisplayPlaneProperties2KHR",
@@ -623,7 +699,10 @@
         "vkGetPhysicalDeviceSurfaceFormatsKHR",
         "vkGetPhysicalDeviceSurfacePresentModesKHR",
         "vkGetPhysicalDeviceSurfaceSupportKHR",
+        "vkGetPhysicalDeviceToolProperties",
         "vkGetPhysicalDeviceToolPropertiesEXT",
+        "vkGetPhysicalDeviceVideoCapabilitiesKHR",
+        "vkGetPhysicalDeviceVideoFormatPropertiesKHR",
         "vkSubmitDebugUtilsMessageEXT",
     };
     // clang-format on
@@ -676,18 +755,25 @@
         { "vkCmdBeginQuery", reinterpret_cast<PFN_vkVoidFunction>(CmdBeginQuery) },
         { "vkCmdBeginRenderPass", reinterpret_cast<PFN_vkVoidFunction>(CmdBeginRenderPass) },
         { "vkCmdBeginRenderPass2", reinterpret_cast<PFN_vkVoidFunction>(CmdBeginRenderPass2) },
+        { "vkCmdBeginRendering", reinterpret_cast<PFN_vkVoidFunction>(CmdBeginRendering) },
         { "vkCmdBindDescriptorSets", reinterpret_cast<PFN_vkVoidFunction>(CmdBindDescriptorSets) },
         { "vkCmdBindIndexBuffer", reinterpret_cast<PFN_vkVoidFunction>(CmdBindIndexBuffer) },
         { "vkCmdBindPipeline", reinterpret_cast<PFN_vkVoidFunction>(CmdBindPipeline) },
         { "vkCmdBindVertexBuffers", reinterpret_cast<PFN_vkVoidFunction>(CmdBindVertexBuffers) },
+        { "vkCmdBindVertexBuffers2", reinterpret_cast<PFN_vkVoidFunction>(CmdBindVertexBuffers2) },
         { "vkCmdBlitImage", reinterpret_cast<PFN_vkVoidFunction>(CmdBlitImage) },
+        { "vkCmdBlitImage2", reinterpret_cast<PFN_vkVoidFunction>(CmdBlitImage2) },
         { "vkCmdClearAttachments", reinterpret_cast<PFN_vkVoidFunction>(CmdClearAttachments) },
         { "vkCmdClearColorImage", reinterpret_cast<PFN_vkVoidFunction>(CmdClearColorImage) },
         { "vkCmdClearDepthStencilImage", reinterpret_cast<PFN_vkVoidFunction>(CmdClearDepthStencilImage) },
         { "vkCmdCopyBuffer", reinterpret_cast<PFN_vkVoidFunction>(CmdCopyBuffer) },
+        { "vkCmdCopyBuffer2", reinterpret_cast<PFN_vkVoidFunction>(CmdCopyBuffer2) },
         { "vkCmdCopyBufferToImage", reinterpret_cast<PFN_vkVoidFunction>(CmdCopyBufferToImage) },
+        { "vkCmdCopyBufferToImage2", reinterpret_cast<PFN_vkVoidFunction>(CmdCopyBufferToImage2) },
         { "vkCmdCopyImage", reinterpret_cast<PFN_vkVoidFunction>(CmdCopyImage) },
+        { "vkCmdCopyImage2", reinterpret_cast<PFN_vkVoidFunction>(CmdCopyImage2) },
         { "vkCmdCopyImageToBuffer", reinterpret_cast<PFN_vkVoidFunction>(CmdCopyImageToBuffer) },
+        { "vkCmdCopyImageToBuffer2", reinterpret_cast<PFN_vkVoidFunction>(CmdCopyImageToBuffer2) },
         { "vkCmdCopyQueryPoolResults", reinterpret_cast<PFN_vkVoidFunction>(CmdCopyQueryPoolResults) },
         { "vkCmdDispatch", reinterpret_cast<PFN_vkVoidFunction>(CmdDispatch) },
         { "vkCmdDispatchBase", reinterpret_cast<PFN_vkVoidFunction>(CmdDispatchBase) },
@@ -701,29 +787,50 @@
         { "vkCmdEndQuery", reinterpret_cast<PFN_vkVoidFunction>(CmdEndQuery) },
         { "vkCmdEndRenderPass", reinterpret_cast<PFN_vkVoidFunction>(CmdEndRenderPass) },
         { "vkCmdEndRenderPass2", reinterpret_cast<PFN_vkVoidFunction>(CmdEndRenderPass2) },
+        { "vkCmdEndRendering", reinterpret_cast<PFN_vkVoidFunction>(CmdEndRendering) },
         { "vkCmdExecuteCommands", reinterpret_cast<PFN_vkVoidFunction>(CmdExecuteCommands) },
         { "vkCmdFillBuffer", reinterpret_cast<PFN_vkVoidFunction>(CmdFillBuffer) },
         { "vkCmdNextSubpass", reinterpret_cast<PFN_vkVoidFunction>(CmdNextSubpass) },
         { "vkCmdNextSubpass2", reinterpret_cast<PFN_vkVoidFunction>(CmdNextSubpass2) },
         { "vkCmdPipelineBarrier", reinterpret_cast<PFN_vkVoidFunction>(CmdPipelineBarrier) },
+        { "vkCmdPipelineBarrier2", reinterpret_cast<PFN_vkVoidFunction>(CmdPipelineBarrier2) },
         { "vkCmdPushConstants", reinterpret_cast<PFN_vkVoidFunction>(CmdPushConstants) },
         { "vkCmdResetEvent", reinterpret_cast<PFN_vkVoidFunction>(CmdResetEvent) },
+        { "vkCmdResetEvent2", reinterpret_cast<PFN_vkVoidFunction>(CmdResetEvent2) },
         { "vkCmdResetQueryPool", reinterpret_cast<PFN_vkVoidFunction>(CmdResetQueryPool) },
         { "vkCmdResolveImage", reinterpret_cast<PFN_vkVoidFunction>(CmdResolveImage) },
+        { "vkCmdResolveImage2", reinterpret_cast<PFN_vkVoidFunction>(CmdResolveImage2) },
         { "vkCmdSetBlendConstants", reinterpret_cast<PFN_vkVoidFunction>(CmdSetBlendConstants) },
+        { "vkCmdSetCullMode", reinterpret_cast<PFN_vkVoidFunction>(CmdSetCullMode) },
         { "vkCmdSetDepthBias", reinterpret_cast<PFN_vkVoidFunction>(CmdSetDepthBias) },
+        { "vkCmdSetDepthBiasEnable", reinterpret_cast<PFN_vkVoidFunction>(CmdSetDepthBiasEnable) },
         { "vkCmdSetDepthBounds", reinterpret_cast<PFN_vkVoidFunction>(CmdSetDepthBounds) },
+        { "vkCmdSetDepthBoundsTestEnable", reinterpret_cast<PFN_vkVoidFunction>(CmdSetDepthBoundsTestEnable) },
+        { "vkCmdSetDepthCompareOp", reinterpret_cast<PFN_vkVoidFunction>(CmdSetDepthCompareOp) },
+        { "vkCmdSetDepthTestEnable", reinterpret_cast<PFN_vkVoidFunction>(CmdSetDepthTestEnable) },
+        { "vkCmdSetDepthWriteEnable", reinterpret_cast<PFN_vkVoidFunction>(CmdSetDepthWriteEnable) },
         { "vkCmdSetDeviceMask", reinterpret_cast<PFN_vkVoidFunction>(CmdSetDeviceMask) },
         { "vkCmdSetEvent", reinterpret_cast<PFN_vkVoidFunction>(CmdSetEvent) },
+        { "vkCmdSetEvent2", reinterpret_cast<PFN_vkVoidFunction>(CmdSetEvent2) },
+        { "vkCmdSetFrontFace", reinterpret_cast<PFN_vkVoidFunction>(CmdSetFrontFace) },
         { "vkCmdSetLineWidth", reinterpret_cast<PFN_vkVoidFunction>(CmdSetLineWidth) },
+        { "vkCmdSetPrimitiveRestartEnable", reinterpret_cast<PFN_vkVoidFunction>(CmdSetPrimitiveRestartEnable) },
+        { "vkCmdSetPrimitiveTopology", reinterpret_cast<PFN_vkVoidFunction>(CmdSetPrimitiveTopology) },
+        { "vkCmdSetRasterizerDiscardEnable", reinterpret_cast<PFN_vkVoidFunction>(CmdSetRasterizerDiscardEnable) },
         { "vkCmdSetScissor", reinterpret_cast<PFN_vkVoidFunction>(CmdSetScissor) },
+        { "vkCmdSetScissorWithCount", reinterpret_cast<PFN_vkVoidFunction>(CmdSetScissorWithCount) },
         { "vkCmdSetStencilCompareMask", reinterpret_cast<PFN_vkVoidFunction>(CmdSetStencilCompareMask) },
+        { "vkCmdSetStencilOp", reinterpret_cast<PFN_vkVoidFunction>(CmdSetStencilOp) },
         { "vkCmdSetStencilReference", reinterpret_cast<PFN_vkVoidFunction>(CmdSetStencilReference) },
+        { "vkCmdSetStencilTestEnable", reinterpret_cast<PFN_vkVoidFunction>(CmdSetStencilTestEnable) },
         { "vkCmdSetStencilWriteMask", reinterpret_cast<PFN_vkVoidFunction>(CmdSetStencilWriteMask) },
         { "vkCmdSetViewport", reinterpret_cast<PFN_vkVoidFunction>(CmdSetViewport) },
+        { "vkCmdSetViewportWithCount", reinterpret_cast<PFN_vkVoidFunction>(CmdSetViewportWithCount) },
         { "vkCmdUpdateBuffer", reinterpret_cast<PFN_vkVoidFunction>(CmdUpdateBuffer) },
         { "vkCmdWaitEvents", reinterpret_cast<PFN_vkVoidFunction>(CmdWaitEvents) },
+        { "vkCmdWaitEvents2", reinterpret_cast<PFN_vkVoidFunction>(CmdWaitEvents2) },
         { "vkCmdWriteTimestamp", reinterpret_cast<PFN_vkVoidFunction>(CmdWriteTimestamp) },
+        { "vkCmdWriteTimestamp2", reinterpret_cast<PFN_vkVoidFunction>(CmdWriteTimestamp2) },
         { "vkCreateBuffer", reinterpret_cast<PFN_vkVoidFunction>(CreateBuffer) },
         { "vkCreateBufferView", reinterpret_cast<PFN_vkVoidFunction>(CreateBufferView) },
         { "vkCreateCommandPool", reinterpret_cast<PFN_vkVoidFunction>(CreateCommandPool) },
@@ -741,6 +848,7 @@
         { "vkCreateInstance", nullptr },
         { "vkCreatePipelineCache", reinterpret_cast<PFN_vkVoidFunction>(CreatePipelineCache) },
         { "vkCreatePipelineLayout", reinterpret_cast<PFN_vkVoidFunction>(CreatePipelineLayout) },
+        { "vkCreatePrivateDataSlot", reinterpret_cast<PFN_vkVoidFunction>(CreatePrivateDataSlot) },
         { "vkCreateQueryPool", reinterpret_cast<PFN_vkVoidFunction>(CreateQueryPool) },
         { "vkCreateRenderPass", reinterpret_cast<PFN_vkVoidFunction>(CreateRenderPass) },
         { "vkCreateRenderPass2", reinterpret_cast<PFN_vkVoidFunction>(CreateRenderPass2) },
@@ -765,6 +873,7 @@
         { "vkDestroyPipeline", reinterpret_cast<PFN_vkVoidFunction>(DestroyPipeline) },
         { "vkDestroyPipelineCache", reinterpret_cast<PFN_vkVoidFunction>(DestroyPipelineCache) },
         { "vkDestroyPipelineLayout", reinterpret_cast<PFN_vkVoidFunction>(DestroyPipelineLayout) },
+        { "vkDestroyPrivateDataSlot", reinterpret_cast<PFN_vkVoidFunction>(DestroyPrivateDataSlot) },
         { "vkDestroyQueryPool", reinterpret_cast<PFN_vkVoidFunction>(DestroyQueryPool) },
         { "vkDestroyRenderPass", reinterpret_cast<PFN_vkVoidFunction>(DestroyRenderPass) },
         { "vkDestroySampler", reinterpret_cast<PFN_vkVoidFunction>(DestroySampler) },
@@ -789,9 +898,12 @@
         { "vkGetBufferMemoryRequirements2", reinterpret_cast<PFN_vkVoidFunction>(GetBufferMemoryRequirements2) },
         { "vkGetBufferOpaqueCaptureAddress", reinterpret_cast<PFN_vkVoidFunction>(GetBufferOpaqueCaptureAddress) },
         { "vkGetDescriptorSetLayoutSupport", reinterpret_cast<PFN_vkVoidFunction>(GetDescriptorSetLayoutSupport) },
+        { "vkGetDeviceBufferMemoryRequirements", reinterpret_cast<PFN_vkVoidFunction>(GetDeviceBufferMemoryRequirements) },
         { "vkGetDeviceGroupPeerMemoryFeatures", reinterpret_cast<PFN_vkVoidFunction>(GetDeviceGroupPeerMemoryFeatures) },
         { "vkGetDeviceGroupPresentCapabilitiesKHR", reinterpret_cast<PFN_vkVoidFunction>(GetDeviceGroupPresentCapabilitiesKHR) },
         { "vkGetDeviceGroupSurfacePresentModesKHR", reinterpret_cast<PFN_vkVoidFunction>(GetDeviceGroupSurfacePresentModesKHR) },
+        { "vkGetDeviceImageMemoryRequirements", reinterpret_cast<PFN_vkVoidFunction>(GetDeviceImageMemoryRequirements) },
+        { "vkGetDeviceImageSparseMemoryRequirements", reinterpret_cast<PFN_vkVoidFunction>(GetDeviceImageSparseMemoryRequirements) },
         { "vkGetDeviceMemoryCommitment", reinterpret_cast<PFN_vkVoidFunction>(GetDeviceMemoryCommitment) },
         { "vkGetDeviceMemoryOpaqueCaptureAddress", reinterpret_cast<PFN_vkVoidFunction>(GetDeviceMemoryOpaqueCaptureAddress) },
         { "vkGetDeviceProcAddr", reinterpret_cast<PFN_vkVoidFunction>(GetDeviceProcAddr) },
@@ -807,6 +919,7 @@
         { "vkGetInstanceProcAddr", reinterpret_cast<PFN_vkVoidFunction>(GetInstanceProcAddr) },
         { "vkGetMemoryAndroidHardwareBufferANDROID", reinterpret_cast<PFN_vkVoidFunction>(GetMemoryAndroidHardwareBufferANDROID) },
         { "vkGetPipelineCacheData", reinterpret_cast<PFN_vkVoidFunction>(GetPipelineCacheData) },
+        { "vkGetPrivateData", reinterpret_cast<PFN_vkVoidFunction>(GetPrivateData) },
         { "vkGetQueryPoolResults", reinterpret_cast<PFN_vkVoidFunction>(GetQueryPoolResults) },
         { "vkGetRenderAreaGranularity", reinterpret_cast<PFN_vkVoidFunction>(GetRenderAreaGranularity) },
         { "vkGetSemaphoreCounterValue", reinterpret_cast<PFN_vkVoidFunction>(GetSemaphoreCounterValue) },
@@ -817,6 +930,7 @@
         { "vkQueueBindSparse", reinterpret_cast<PFN_vkVoidFunction>(QueueBindSparse) },
         { "vkQueuePresentKHR", reinterpret_cast<PFN_vkVoidFunction>(QueuePresentKHR) },
         { "vkQueueSubmit", reinterpret_cast<PFN_vkVoidFunction>(QueueSubmit) },
+        { "vkQueueSubmit2", reinterpret_cast<PFN_vkVoidFunction>(QueueSubmit2) },
         { "vkQueueWaitIdle", reinterpret_cast<PFN_vkVoidFunction>(QueueWaitIdle) },
         { "vkResetCommandBuffer", reinterpret_cast<PFN_vkVoidFunction>(ResetCommandBuffer) },
         { "vkResetCommandPool", reinterpret_cast<PFN_vkVoidFunction>(ResetCommandPool) },
@@ -825,6 +939,7 @@
         { "vkResetFences", reinterpret_cast<PFN_vkVoidFunction>(ResetFences) },
         { "vkResetQueryPool", reinterpret_cast<PFN_vkVoidFunction>(ResetQueryPool) },
         { "vkSetEvent", reinterpret_cast<PFN_vkVoidFunction>(SetEvent) },
+        { "vkSetPrivateData", reinterpret_cast<PFN_vkVoidFunction>(SetPrivateData) },
         { "vkSignalSemaphore", reinterpret_cast<PFN_vkVoidFunction>(SignalSemaphore) },
         { "vkTrimCommandPool", reinterpret_cast<PFN_vkVoidFunction>(TrimCommandPool) },
         { "vkUnmapMemory", reinterpret_cast<PFN_vkVoidFunction>(UnmapMemory) },
@@ -1511,6 +1626,18 @@
     GetData(device).dispatch.GetImageSparseMemoryRequirements2(device, pInfo, pSparseMemoryRequirementCount, pSparseMemoryRequirements);
 }
 
+VKAPI_ATTR void GetDeviceBufferMemoryRequirements(VkDevice device, const VkDeviceBufferMemoryRequirements* pInfo, VkMemoryRequirements2* pMemoryRequirements) {
+    GetData(device).dispatch.GetDeviceBufferMemoryRequirements(device, pInfo, pMemoryRequirements);
+}
+
+VKAPI_ATTR void GetDeviceImageMemoryRequirements(VkDevice device, const VkDeviceImageMemoryRequirements* pInfo, VkMemoryRequirements2* pMemoryRequirements) {
+    GetData(device).dispatch.GetDeviceImageMemoryRequirements(device, pInfo, pMemoryRequirements);
+}
+
+VKAPI_ATTR void GetDeviceImageSparseMemoryRequirements(VkDevice device, const VkDeviceImageMemoryRequirements* pInfo, uint32_t* pSparseMemoryRequirementCount, VkSparseImageMemoryRequirements2* pSparseMemoryRequirements) {
+    GetData(device).dispatch.GetDeviceImageSparseMemoryRequirements(device, pInfo, pSparseMemoryRequirementCount, pSparseMemoryRequirements);
+}
+
 VKAPI_ATTR VkResult CreateSamplerYcbcrConversion(VkDevice device, const VkSamplerYcbcrConversionCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkSamplerYcbcrConversion* pYcbcrConversion) {
     return GetData(device).dispatch.CreateSamplerYcbcrConversion(device, pCreateInfo, pAllocator, pYcbcrConversion);
 }
@@ -1583,6 +1710,142 @@
     return GetData(device).dispatch.GetDeviceMemoryOpaqueCaptureAddress(device, pInfo);
 }
 
+VKAPI_ATTR VkResult GetPhysicalDeviceToolProperties(VkPhysicalDevice physicalDevice, uint32_t* pToolCount, VkPhysicalDeviceToolProperties* pToolProperties) {
+    return GetData(physicalDevice).dispatch.GetPhysicalDeviceToolProperties(physicalDevice, pToolCount, pToolProperties);
+}
+
+VKAPI_ATTR void CmdSetCullMode(VkCommandBuffer commandBuffer, VkCullModeFlags cullMode) {
+    GetData(commandBuffer).dispatch.CmdSetCullMode(commandBuffer, cullMode);
+}
+
+VKAPI_ATTR void CmdSetFrontFace(VkCommandBuffer commandBuffer, VkFrontFace frontFace) {
+    GetData(commandBuffer).dispatch.CmdSetFrontFace(commandBuffer, frontFace);
+}
+
+VKAPI_ATTR void CmdSetPrimitiveTopology(VkCommandBuffer commandBuffer, VkPrimitiveTopology primitiveTopology) {
+    GetData(commandBuffer).dispatch.CmdSetPrimitiveTopology(commandBuffer, primitiveTopology);
+}
+
+VKAPI_ATTR void CmdSetViewportWithCount(VkCommandBuffer commandBuffer, uint32_t viewportCount, const VkViewport* pViewports) {
+    GetData(commandBuffer).dispatch.CmdSetViewportWithCount(commandBuffer, viewportCount, pViewports);
+}
+
+VKAPI_ATTR void CmdSetScissorWithCount(VkCommandBuffer commandBuffer, uint32_t scissorCount, const VkRect2D* pScissors) {
+    GetData(commandBuffer).dispatch.CmdSetScissorWithCount(commandBuffer, scissorCount, pScissors);
+}
+
+VKAPI_ATTR void CmdBindVertexBuffers2(VkCommandBuffer commandBuffer, uint32_t firstBinding, uint32_t bindingCount, const VkBuffer* pBuffers, const VkDeviceSize* pOffsets, const VkDeviceSize* pSizes, const VkDeviceSize* pStrides) {
+    GetData(commandBuffer).dispatch.CmdBindVertexBuffers2(commandBuffer, firstBinding, bindingCount, pBuffers, pOffsets, pSizes, pStrides);
+}
+
+VKAPI_ATTR void CmdSetDepthTestEnable(VkCommandBuffer commandBuffer, VkBool32 depthTestEnable) {
+    GetData(commandBuffer).dispatch.CmdSetDepthTestEnable(commandBuffer, depthTestEnable);
+}
+
+VKAPI_ATTR void CmdSetDepthWriteEnable(VkCommandBuffer commandBuffer, VkBool32 depthWriteEnable) {
+    GetData(commandBuffer).dispatch.CmdSetDepthWriteEnable(commandBuffer, depthWriteEnable);
+}
+
+VKAPI_ATTR void CmdSetDepthCompareOp(VkCommandBuffer commandBuffer, VkCompareOp depthCompareOp) {
+    GetData(commandBuffer).dispatch.CmdSetDepthCompareOp(commandBuffer, depthCompareOp);
+}
+
+VKAPI_ATTR void CmdSetDepthBoundsTestEnable(VkCommandBuffer commandBuffer, VkBool32 depthBoundsTestEnable) {
+    GetData(commandBuffer).dispatch.CmdSetDepthBoundsTestEnable(commandBuffer, depthBoundsTestEnable);
+}
+
+VKAPI_ATTR void CmdSetStencilTestEnable(VkCommandBuffer commandBuffer, VkBool32 stencilTestEnable) {
+    GetData(commandBuffer).dispatch.CmdSetStencilTestEnable(commandBuffer, stencilTestEnable);
+}
+
+VKAPI_ATTR void CmdSetStencilOp(VkCommandBuffer commandBuffer, VkStencilFaceFlags faceMask, VkStencilOp failOp, VkStencilOp passOp, VkStencilOp depthFailOp, VkCompareOp compareOp) {
+    GetData(commandBuffer).dispatch.CmdSetStencilOp(commandBuffer, faceMask, failOp, passOp, depthFailOp, compareOp);
+}
+
+VKAPI_ATTR void CmdSetRasterizerDiscardEnable(VkCommandBuffer commandBuffer, VkBool32 rasterizerDiscardEnable) {
+    GetData(commandBuffer).dispatch.CmdSetRasterizerDiscardEnable(commandBuffer, rasterizerDiscardEnable);
+}
+
+VKAPI_ATTR void CmdSetDepthBiasEnable(VkCommandBuffer commandBuffer, VkBool32 depthBiasEnable) {
+    GetData(commandBuffer).dispatch.CmdSetDepthBiasEnable(commandBuffer, depthBiasEnable);
+}
+
+VKAPI_ATTR void CmdSetPrimitiveRestartEnable(VkCommandBuffer commandBuffer, VkBool32 primitiveRestartEnable) {
+    GetData(commandBuffer).dispatch.CmdSetPrimitiveRestartEnable(commandBuffer, primitiveRestartEnable);
+}
+
+VKAPI_ATTR VkResult CreatePrivateDataSlot(VkDevice device, const VkPrivateDataSlotCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkPrivateDataSlot* pPrivateDataSlot) {
+    return GetData(device).dispatch.CreatePrivateDataSlot(device, pCreateInfo, pAllocator, pPrivateDataSlot);
+}
+
+VKAPI_ATTR void DestroyPrivateDataSlot(VkDevice device, VkPrivateDataSlot privateDataSlot, const VkAllocationCallbacks* pAllocator) {
+    GetData(device).dispatch.DestroyPrivateDataSlot(device, privateDataSlot, pAllocator);
+}
+
+VKAPI_ATTR VkResult SetPrivateData(VkDevice device, VkObjectType objectType, uint64_t objectHandle, VkPrivateDataSlot privateDataSlot, uint64_t data) {
+    return GetData(device).dispatch.SetPrivateData(device, objectType, objectHandle, privateDataSlot, data);
+}
+
+VKAPI_ATTR void GetPrivateData(VkDevice device, VkObjectType objectType, uint64_t objectHandle, VkPrivateDataSlot privateDataSlot, uint64_t* pData) {
+    GetData(device).dispatch.GetPrivateData(device, objectType, objectHandle, privateDataSlot, pData);
+}
+
+VKAPI_ATTR void CmdCopyBuffer2(VkCommandBuffer commandBuffer, const VkCopyBufferInfo2* pCopyBufferInfo) {
+    GetData(commandBuffer).dispatch.CmdCopyBuffer2(commandBuffer, pCopyBufferInfo);
+}
+
+VKAPI_ATTR void CmdCopyImage2(VkCommandBuffer commandBuffer, const VkCopyImageInfo2* pCopyImageInfo) {
+    GetData(commandBuffer).dispatch.CmdCopyImage2(commandBuffer, pCopyImageInfo);
+}
+
+VKAPI_ATTR void CmdBlitImage2(VkCommandBuffer commandBuffer, const VkBlitImageInfo2* pBlitImageInfo) {
+    GetData(commandBuffer).dispatch.CmdBlitImage2(commandBuffer, pBlitImageInfo);
+}
+
+VKAPI_ATTR void CmdCopyBufferToImage2(VkCommandBuffer commandBuffer, const VkCopyBufferToImageInfo2* pCopyBufferToImageInfo) {
+    GetData(commandBuffer).dispatch.CmdCopyBufferToImage2(commandBuffer, pCopyBufferToImageInfo);
+}
+
+VKAPI_ATTR void CmdCopyImageToBuffer2(VkCommandBuffer commandBuffer, const VkCopyImageToBufferInfo2* pCopyImageToBufferInfo) {
+    GetData(commandBuffer).dispatch.CmdCopyImageToBuffer2(commandBuffer, pCopyImageToBufferInfo);
+}
+
+VKAPI_ATTR void CmdResolveImage2(VkCommandBuffer commandBuffer, const VkResolveImageInfo2* pResolveImageInfo) {
+    GetData(commandBuffer).dispatch.CmdResolveImage2(commandBuffer, pResolveImageInfo);
+}
+
+VKAPI_ATTR void CmdSetEvent2(VkCommandBuffer commandBuffer, VkEvent event, const VkDependencyInfo* pDependencyInfo) {
+    GetData(commandBuffer).dispatch.CmdSetEvent2(commandBuffer, event, pDependencyInfo);
+}
+
+VKAPI_ATTR void CmdResetEvent2(VkCommandBuffer commandBuffer, VkEvent event, VkPipelineStageFlags2 stageMask) {
+    GetData(commandBuffer).dispatch.CmdResetEvent2(commandBuffer, event, stageMask);
+}
+
+VKAPI_ATTR void CmdWaitEvents2(VkCommandBuffer commandBuffer, uint32_t eventCount, const VkEvent* pEvents, const VkDependencyInfo* pDependencyInfos) {
+    GetData(commandBuffer).dispatch.CmdWaitEvents2(commandBuffer, eventCount, pEvents, pDependencyInfos);
+}
+
+VKAPI_ATTR void CmdPipelineBarrier2(VkCommandBuffer commandBuffer, const VkDependencyInfo* pDependencyInfo) {
+    GetData(commandBuffer).dispatch.CmdPipelineBarrier2(commandBuffer, pDependencyInfo);
+}
+
+VKAPI_ATTR VkResult QueueSubmit2(VkQueue queue, uint32_t submitCount, const VkSubmitInfo2* pSubmits, VkFence fence) {
+    return GetData(queue).dispatch.QueueSubmit2(queue, submitCount, pSubmits, fence);
+}
+
+VKAPI_ATTR void CmdWriteTimestamp2(VkCommandBuffer commandBuffer, VkPipelineStageFlags2 stage, VkQueryPool queryPool, uint32_t query) {
+    GetData(commandBuffer).dispatch.CmdWriteTimestamp2(commandBuffer, stage, queryPool, query);
+}
+
+VKAPI_ATTR void CmdBeginRendering(VkCommandBuffer commandBuffer, const VkRenderingInfo* pRenderingInfo) {
+    GetData(commandBuffer).dispatch.CmdBeginRendering(commandBuffer, pRenderingInfo);
+}
+
+VKAPI_ATTR void CmdEndRendering(VkCommandBuffer commandBuffer) {
+    GetData(commandBuffer).dispatch.CmdEndRendering(commandBuffer);
+}
+
 
 }  // anonymous namespace
 
@@ -2479,6 +2742,21 @@
 }
 
 __attribute__((visibility("default")))
+VKAPI_ATTR void vkGetDeviceBufferMemoryRequirements(VkDevice device, const VkDeviceBufferMemoryRequirements* pInfo, VkMemoryRequirements2* pMemoryRequirements) {
+    vulkan::api::GetDeviceBufferMemoryRequirements(device, pInfo, pMemoryRequirements);
+}
+
+__attribute__((visibility("default")))
+VKAPI_ATTR void vkGetDeviceImageMemoryRequirements(VkDevice device, const VkDeviceImageMemoryRequirements* pInfo, VkMemoryRequirements2* pMemoryRequirements) {
+    vulkan::api::GetDeviceImageMemoryRequirements(device, pInfo, pMemoryRequirements);
+}
+
+__attribute__((visibility("default")))
+VKAPI_ATTR void vkGetDeviceImageSparseMemoryRequirements(VkDevice device, const VkDeviceImageMemoryRequirements* pInfo, uint32_t* pSparseMemoryRequirementCount, VkSparseImageMemoryRequirements2* pSparseMemoryRequirements) {
+    vulkan::api::GetDeviceImageSparseMemoryRequirements(device, pInfo, pSparseMemoryRequirementCount, pSparseMemoryRequirements);
+}
+
+__attribute__((visibility("default")))
 VKAPI_ATTR VkResult vkCreateSamplerYcbcrConversion(VkDevice device, const VkSamplerYcbcrConversionCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkSamplerYcbcrConversion* pYcbcrConversion) {
     return vulkan::api::CreateSamplerYcbcrConversion(device, pCreateInfo, pAllocator, pYcbcrConversion);
 }
@@ -2568,4 +2846,174 @@
     return vulkan::api::GetDeviceMemoryOpaqueCaptureAddress(device, pInfo);
 }
 
+__attribute__((visibility("default")))
+VKAPI_ATTR VkResult vkGetPhysicalDeviceToolProperties(VkPhysicalDevice physicalDevice, uint32_t* pToolCount, VkPhysicalDeviceToolProperties* pToolProperties) {
+    return vulkan::api::GetPhysicalDeviceToolProperties(physicalDevice, pToolCount, pToolProperties);
+}
+
+__attribute__((visibility("default")))
+VKAPI_ATTR void vkCmdSetCullMode(VkCommandBuffer commandBuffer, VkCullModeFlags cullMode) {
+    vulkan::api::CmdSetCullMode(commandBuffer, cullMode);
+}
+
+__attribute__((visibility("default")))
+VKAPI_ATTR void vkCmdSetFrontFace(VkCommandBuffer commandBuffer, VkFrontFace frontFace) {
+    vulkan::api::CmdSetFrontFace(commandBuffer, frontFace);
+}
+
+__attribute__((visibility("default")))
+VKAPI_ATTR void vkCmdSetPrimitiveTopology(VkCommandBuffer commandBuffer, VkPrimitiveTopology primitiveTopology) {
+    vulkan::api::CmdSetPrimitiveTopology(commandBuffer, primitiveTopology);
+}
+
+__attribute__((visibility("default")))
+VKAPI_ATTR void vkCmdSetViewportWithCount(VkCommandBuffer commandBuffer, uint32_t viewportCount, const VkViewport* pViewports) {
+    vulkan::api::CmdSetViewportWithCount(commandBuffer, viewportCount, pViewports);
+}
+
+__attribute__((visibility("default")))
+VKAPI_ATTR void vkCmdSetScissorWithCount(VkCommandBuffer commandBuffer, uint32_t scissorCount, const VkRect2D* pScissors) {
+    vulkan::api::CmdSetScissorWithCount(commandBuffer, scissorCount, pScissors);
+}
+
+__attribute__((visibility("default")))
+VKAPI_ATTR void vkCmdBindVertexBuffers2(VkCommandBuffer commandBuffer, uint32_t firstBinding, uint32_t bindingCount, const VkBuffer* pBuffers, const VkDeviceSize* pOffsets, const VkDeviceSize* pSizes, const VkDeviceSize* pStrides) {
+    vulkan::api::CmdBindVertexBuffers2(commandBuffer, firstBinding, bindingCount, pBuffers, pOffsets, pSizes, pStrides);
+}
+
+__attribute__((visibility("default")))
+VKAPI_ATTR void vkCmdSetDepthTestEnable(VkCommandBuffer commandBuffer, VkBool32 depthTestEnable) {
+    vulkan::api::CmdSetDepthTestEnable(commandBuffer, depthTestEnable);
+}
+
+__attribute__((visibility("default")))
+VKAPI_ATTR void vkCmdSetDepthWriteEnable(VkCommandBuffer commandBuffer, VkBool32 depthWriteEnable) {
+    vulkan::api::CmdSetDepthWriteEnable(commandBuffer, depthWriteEnable);
+}
+
+__attribute__((visibility("default")))
+VKAPI_ATTR void vkCmdSetDepthCompareOp(VkCommandBuffer commandBuffer, VkCompareOp depthCompareOp) {
+    vulkan::api::CmdSetDepthCompareOp(commandBuffer, depthCompareOp);
+}
+
+__attribute__((visibility("default")))
+VKAPI_ATTR void vkCmdSetDepthBoundsTestEnable(VkCommandBuffer commandBuffer, VkBool32 depthBoundsTestEnable) {
+    vulkan::api::CmdSetDepthBoundsTestEnable(commandBuffer, depthBoundsTestEnable);
+}
+
+__attribute__((visibility("default")))
+VKAPI_ATTR void vkCmdSetStencilTestEnable(VkCommandBuffer commandBuffer, VkBool32 stencilTestEnable) {
+    vulkan::api::CmdSetStencilTestEnable(commandBuffer, stencilTestEnable);
+}
+
+__attribute__((visibility("default")))
+VKAPI_ATTR void vkCmdSetStencilOp(VkCommandBuffer commandBuffer, VkStencilFaceFlags faceMask, VkStencilOp failOp, VkStencilOp passOp, VkStencilOp depthFailOp, VkCompareOp compareOp) {
+    vulkan::api::CmdSetStencilOp(commandBuffer, faceMask, failOp, passOp, depthFailOp, compareOp);
+}
+
+__attribute__((visibility("default")))
+VKAPI_ATTR void vkCmdSetRasterizerDiscardEnable(VkCommandBuffer commandBuffer, VkBool32 rasterizerDiscardEnable) {
+    vulkan::api::CmdSetRasterizerDiscardEnable(commandBuffer, rasterizerDiscardEnable);
+}
+
+__attribute__((visibility("default")))
+VKAPI_ATTR void vkCmdSetDepthBiasEnable(VkCommandBuffer commandBuffer, VkBool32 depthBiasEnable) {
+    vulkan::api::CmdSetDepthBiasEnable(commandBuffer, depthBiasEnable);
+}
+
+__attribute__((visibility("default")))
+VKAPI_ATTR void vkCmdSetPrimitiveRestartEnable(VkCommandBuffer commandBuffer, VkBool32 primitiveRestartEnable) {
+    vulkan::api::CmdSetPrimitiveRestartEnable(commandBuffer, primitiveRestartEnable);
+}
+
+__attribute__((visibility("default")))
+VKAPI_ATTR VkResult vkCreatePrivateDataSlot(VkDevice device, const VkPrivateDataSlotCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkPrivateDataSlot* pPrivateDataSlot) {
+    return vulkan::api::CreatePrivateDataSlot(device, pCreateInfo, pAllocator, pPrivateDataSlot);
+}
+
+__attribute__((visibility("default")))
+VKAPI_ATTR void vkDestroyPrivateDataSlot(VkDevice device, VkPrivateDataSlot privateDataSlot, const VkAllocationCallbacks* pAllocator) {
+    vulkan::api::DestroyPrivateDataSlot(device, privateDataSlot, pAllocator);
+}
+
+__attribute__((visibility("default")))
+VKAPI_ATTR VkResult vkSetPrivateData(VkDevice device, VkObjectType objectType, uint64_t objectHandle, VkPrivateDataSlot privateDataSlot, uint64_t data) {
+    return vulkan::api::SetPrivateData(device, objectType, objectHandle, privateDataSlot, data);
+}
+
+__attribute__((visibility("default")))
+VKAPI_ATTR void vkGetPrivateData(VkDevice device, VkObjectType objectType, uint64_t objectHandle, VkPrivateDataSlot privateDataSlot, uint64_t* pData) {
+    vulkan::api::GetPrivateData(device, objectType, objectHandle, privateDataSlot, pData);
+}
+
+__attribute__((visibility("default")))
+VKAPI_ATTR void vkCmdCopyBuffer2(VkCommandBuffer commandBuffer, const VkCopyBufferInfo2* pCopyBufferInfo) {
+    vulkan::api::CmdCopyBuffer2(commandBuffer, pCopyBufferInfo);
+}
+
+__attribute__((visibility("default")))
+VKAPI_ATTR void vkCmdCopyImage2(VkCommandBuffer commandBuffer, const VkCopyImageInfo2* pCopyImageInfo) {
+    vulkan::api::CmdCopyImage2(commandBuffer, pCopyImageInfo);
+}
+
+__attribute__((visibility("default")))
+VKAPI_ATTR void vkCmdBlitImage2(VkCommandBuffer commandBuffer, const VkBlitImageInfo2* pBlitImageInfo) {
+    vulkan::api::CmdBlitImage2(commandBuffer, pBlitImageInfo);
+}
+
+__attribute__((visibility("default")))
+VKAPI_ATTR void vkCmdCopyBufferToImage2(VkCommandBuffer commandBuffer, const VkCopyBufferToImageInfo2* pCopyBufferToImageInfo) {
+    vulkan::api::CmdCopyBufferToImage2(commandBuffer, pCopyBufferToImageInfo);
+}
+
+__attribute__((visibility("default")))
+VKAPI_ATTR void vkCmdCopyImageToBuffer2(VkCommandBuffer commandBuffer, const VkCopyImageToBufferInfo2* pCopyImageToBufferInfo) {
+    vulkan::api::CmdCopyImageToBuffer2(commandBuffer, pCopyImageToBufferInfo);
+}
+
+__attribute__((visibility("default")))
+VKAPI_ATTR void vkCmdResolveImage2(VkCommandBuffer commandBuffer, const VkResolveImageInfo2* pResolveImageInfo) {
+    vulkan::api::CmdResolveImage2(commandBuffer, pResolveImageInfo);
+}
+
+__attribute__((visibility("default")))
+VKAPI_ATTR void vkCmdSetEvent2(VkCommandBuffer commandBuffer, VkEvent event, const VkDependencyInfo* pDependencyInfo) {
+    vulkan::api::CmdSetEvent2(commandBuffer, event, pDependencyInfo);
+}
+
+__attribute__((visibility("default")))
+VKAPI_ATTR void vkCmdResetEvent2(VkCommandBuffer commandBuffer, VkEvent event, VkPipelineStageFlags2 stageMask) {
+    vulkan::api::CmdResetEvent2(commandBuffer, event, stageMask);
+}
+
+__attribute__((visibility("default")))
+VKAPI_ATTR void vkCmdWaitEvents2(VkCommandBuffer commandBuffer, uint32_t eventCount, const VkEvent* pEvents, const VkDependencyInfo* pDependencyInfos) {
+    vulkan::api::CmdWaitEvents2(commandBuffer, eventCount, pEvents, pDependencyInfos);
+}
+
+__attribute__((visibility("default")))
+VKAPI_ATTR void vkCmdPipelineBarrier2(VkCommandBuffer commandBuffer, const VkDependencyInfo* pDependencyInfo) {
+    vulkan::api::CmdPipelineBarrier2(commandBuffer, pDependencyInfo);
+}
+
+__attribute__((visibility("default")))
+VKAPI_ATTR VkResult vkQueueSubmit2(VkQueue queue, uint32_t submitCount, const VkSubmitInfo2* pSubmits, VkFence fence) {
+    return vulkan::api::QueueSubmit2(queue, submitCount, pSubmits, fence);
+}
+
+__attribute__((visibility("default")))
+VKAPI_ATTR void vkCmdWriteTimestamp2(VkCommandBuffer commandBuffer, VkPipelineStageFlags2 stage, VkQueryPool queryPool, uint32_t query) {
+    vulkan::api::CmdWriteTimestamp2(commandBuffer, stage, queryPool, query);
+}
+
+__attribute__((visibility("default")))
+VKAPI_ATTR void vkCmdBeginRendering(VkCommandBuffer commandBuffer, const VkRenderingInfo* pRenderingInfo) {
+    vulkan::api::CmdBeginRendering(commandBuffer, pRenderingInfo);
+}
+
+__attribute__((visibility("default")))
+VKAPI_ATTR void vkCmdEndRendering(VkCommandBuffer commandBuffer) {
+    vulkan::api::CmdEndRendering(commandBuffer);
+}
+
 // clang-format on
diff --git a/vulkan/libvulkan/api_gen.h b/vulkan/libvulkan/api_gen.h
index ad5cc34..4998018 100644
--- a/vulkan/libvulkan/api_gen.h
+++ b/vulkan/libvulkan/api_gen.h
@@ -60,6 +60,7 @@
     PFN_vkGetPhysicalDeviceExternalFenceProperties GetPhysicalDeviceExternalFenceProperties;
     PFN_vkEnumeratePhysicalDeviceGroups EnumeratePhysicalDeviceGroups;
     PFN_vkGetPhysicalDevicePresentRectanglesKHR GetPhysicalDevicePresentRectanglesKHR;
+    PFN_vkGetPhysicalDeviceToolProperties GetPhysicalDeviceToolProperties;
     // clang-format on
 };
 
@@ -207,6 +208,9 @@
     PFN_vkGetBufferMemoryRequirements2 GetBufferMemoryRequirements2;
     PFN_vkGetImageMemoryRequirements2 GetImageMemoryRequirements2;
     PFN_vkGetImageSparseMemoryRequirements2 GetImageSparseMemoryRequirements2;
+    PFN_vkGetDeviceBufferMemoryRequirements GetDeviceBufferMemoryRequirements;
+    PFN_vkGetDeviceImageMemoryRequirements GetDeviceImageMemoryRequirements;
+    PFN_vkGetDeviceImageSparseMemoryRequirements GetDeviceImageSparseMemoryRequirements;
     PFN_vkCreateSamplerYcbcrConversion CreateSamplerYcbcrConversion;
     PFN_vkDestroySamplerYcbcrConversion DestroySamplerYcbcrConversion;
     PFN_vkGetDeviceQueue2 GetDeviceQueue2;
@@ -225,6 +229,39 @@
     PFN_vkGetBufferOpaqueCaptureAddress GetBufferOpaqueCaptureAddress;
     PFN_vkGetBufferDeviceAddress GetBufferDeviceAddress;
     PFN_vkGetDeviceMemoryOpaqueCaptureAddress GetDeviceMemoryOpaqueCaptureAddress;
+    PFN_vkCmdSetCullMode CmdSetCullMode;
+    PFN_vkCmdSetFrontFace CmdSetFrontFace;
+    PFN_vkCmdSetPrimitiveTopology CmdSetPrimitiveTopology;
+    PFN_vkCmdSetViewportWithCount CmdSetViewportWithCount;
+    PFN_vkCmdSetScissorWithCount CmdSetScissorWithCount;
+    PFN_vkCmdBindVertexBuffers2 CmdBindVertexBuffers2;
+    PFN_vkCmdSetDepthTestEnable CmdSetDepthTestEnable;
+    PFN_vkCmdSetDepthWriteEnable CmdSetDepthWriteEnable;
+    PFN_vkCmdSetDepthCompareOp CmdSetDepthCompareOp;
+    PFN_vkCmdSetDepthBoundsTestEnable CmdSetDepthBoundsTestEnable;
+    PFN_vkCmdSetStencilTestEnable CmdSetStencilTestEnable;
+    PFN_vkCmdSetStencilOp CmdSetStencilOp;
+    PFN_vkCmdSetRasterizerDiscardEnable CmdSetRasterizerDiscardEnable;
+    PFN_vkCmdSetDepthBiasEnable CmdSetDepthBiasEnable;
+    PFN_vkCmdSetPrimitiveRestartEnable CmdSetPrimitiveRestartEnable;
+    PFN_vkCreatePrivateDataSlot CreatePrivateDataSlot;
+    PFN_vkDestroyPrivateDataSlot DestroyPrivateDataSlot;
+    PFN_vkSetPrivateData SetPrivateData;
+    PFN_vkGetPrivateData GetPrivateData;
+    PFN_vkCmdCopyBuffer2 CmdCopyBuffer2;
+    PFN_vkCmdCopyImage2 CmdCopyImage2;
+    PFN_vkCmdBlitImage2 CmdBlitImage2;
+    PFN_vkCmdCopyBufferToImage2 CmdCopyBufferToImage2;
+    PFN_vkCmdCopyImageToBuffer2 CmdCopyImageToBuffer2;
+    PFN_vkCmdResolveImage2 CmdResolveImage2;
+    PFN_vkCmdSetEvent2 CmdSetEvent2;
+    PFN_vkCmdResetEvent2 CmdResetEvent2;
+    PFN_vkCmdWaitEvents2 CmdWaitEvents2;
+    PFN_vkCmdPipelineBarrier2 CmdPipelineBarrier2;
+    PFN_vkQueueSubmit2 QueueSubmit2;
+    PFN_vkCmdWriteTimestamp2 CmdWriteTimestamp2;
+    PFN_vkCmdBeginRendering CmdBeginRendering;
+    PFN_vkCmdEndRendering CmdEndRendering;
     // clang-format on
 };
 
diff --git a/vulkan/libvulkan/debug_report.h b/vulkan/libvulkan/debug_report.h
index e5b1587..416c0bc 100644
--- a/vulkan/libvulkan/debug_report.h
+++ b/vulkan/libvulkan/debug_report.h
@@ -78,8 +78,7 @@
         VkDebugReportCallbackEXT driver_handle;
     };
 
-    // TODO(b/143295577): use std::shared_mutex when available in libc++
-    mutable std::shared_timed_mutex rwmutex_;
+    mutable std::shared_mutex rwmutex_;
     Node head_;
 };
 
diff --git a/vulkan/libvulkan/driver.cpp b/vulkan/libvulkan/driver.cpp
index 0c5d61b..7664518 100644
--- a/vulkan/libvulkan/driver.cpp
+++ b/vulkan/libvulkan/driver.cpp
@@ -365,7 +365,7 @@
                                      const VkAllocationCallbacks& allocator)
     : is_instance_(true),
       allocator_(allocator),
-      loader_api_version_(VK_API_VERSION_1_1),
+      loader_api_version_(VK_API_VERSION_1_3),
       icd_api_version_(icd_api_version),
       physical_dev_(VK_NULL_HANDLE),
       instance_info_(create_info),
@@ -377,7 +377,7 @@
                                      const VkAllocationCallbacks& allocator)
     : is_instance_(false),
       allocator_(allocator),
-      loader_api_version_(VK_API_VERSION_1_1),
+      loader_api_version_(VK_API_VERSION_1_3),
       icd_api_version_(icd_api_version),
       physical_dev_(physical_dev),
       dev_info_(create_info),
@@ -519,6 +519,14 @@
         is_instance_ ? loader_api_version_
                      : std::min(icd_api_version_, loader_api_version_);
     switch (api_version) {
+        case VK_API_VERSION_1_3:
+            hook_extensions_.set(ProcHook::EXTENSION_CORE_1_3);
+            hal_extensions_.set(ProcHook::EXTENSION_CORE_1_3);
+            [[clang::fallthrough]];
+        case VK_API_VERSION_1_2:
+            hook_extensions_.set(ProcHook::EXTENSION_CORE_1_2);
+            hal_extensions_.set(ProcHook::EXTENSION_CORE_1_2);
+            [[clang::fallthrough]];
         case VK_API_VERSION_1_1:
             hook_extensions_.set(ProcHook::EXTENSION_CORE_1_1);
             hal_extensions_.set(ProcHook::EXTENSION_CORE_1_1);
@@ -655,6 +663,7 @@
             case ProcHook::EXTENSION_CORE_1_0:
             case ProcHook::EXTENSION_CORE_1_1:
             case ProcHook::EXTENSION_CORE_1_2:
+            case ProcHook::EXTENSION_CORE_1_3:
             case ProcHook::EXTENSION_COUNT:
                 // Device and meta extensions. If we ever get here it's a bug in
                 // our code. But enumerating them lets us avoid having a default
@@ -711,6 +720,7 @@
             case ProcHook::EXTENSION_CORE_1_0:
             case ProcHook::EXTENSION_CORE_1_1:
             case ProcHook::EXTENSION_CORE_1_2:
+            case ProcHook::EXTENSION_CORE_1_3:
             case ProcHook::EXTENSION_COUNT:
                 // Instance and meta extensions. If we ever get here it's a bug
                 // in our code. But enumerating them lets us avoid having a
@@ -914,9 +924,8 @@
     uint32_t* pPropertyCount,
     VkExtensionProperties* pProperties) {
     std::vector<VkExtensionProperties> loader_extensions;
-    loader_extensions.push_back({
-        VK_KHR_SURFACE_EXTENSION_NAME,
-        VK_KHR_SURFACE_SPEC_VERSION});
+    loader_extensions.push_back(
+        {VK_KHR_SURFACE_EXTENSION_NAME, VK_KHR_SURFACE_SPEC_VERSION});
     loader_extensions.push_back(
         {VK_KHR_SURFACE_PROTECTED_CAPABILITIES_EXTENSION_NAME,
          VK_KHR_SURFACE_PROTECTED_CAPABILITIES_SPEC_VERSION});
@@ -926,9 +935,9 @@
     loader_extensions.push_back({
         VK_EXT_SWAPCHAIN_COLOR_SPACE_EXTENSION_NAME,
         VK_EXT_SWAPCHAIN_COLOR_SPACE_SPEC_VERSION});
-    loader_extensions.push_back({
-        VK_KHR_GET_SURFACE_CAPABILITIES_2_EXTENSION_NAME,
-        VK_KHR_GET_SURFACE_CAPABILITIES_2_SPEC_VERSION});
+    loader_extensions.push_back(
+        {VK_KHR_GET_SURFACE_CAPABILITIES_2_EXTENSION_NAME,
+         VK_KHR_GET_SURFACE_CAPABILITIES_2_SPEC_VERSION});
     loader_extensions.push_back({VK_GOOGLE_SURFACELESS_QUERY_EXTENSION_NAME,
                                  VK_GOOGLE_SURFACELESS_QUERY_SPEC_VERSION});
 
@@ -1120,7 +1129,7 @@
         if (result != VK_SUCCESS)
             return result;
 
-        icd_api_version ^= VK_VERSION_PATCH(icd_api_version);
+        icd_api_version ^= VK_API_VERSION_PATCH(icd_api_version);
     }
 
     CreateInfoWrapper wrapper(*pCreateInfo, icd_api_version, data_allocator);
@@ -1204,7 +1213,7 @@
 
     CreateInfoWrapper wrapper(
         physicalDevice, *pCreateInfo,
-        properties.apiVersion ^ VK_VERSION_PATCH(properties.apiVersion),
+        properties.apiVersion ^ VK_API_VERSION_PATCH(properties.apiVersion),
         data_allocator);
     VkResult result = wrapper.Validate();
     if (result != VK_SUCCESS)
diff --git a/vulkan/libvulkan/driver_gen.h b/vulkan/libvulkan/driver_gen.h
index 688630c..079f9cc 100644
--- a/vulkan/libvulkan/driver_gen.h
+++ b/vulkan/libvulkan/driver_gen.h
@@ -60,6 +60,7 @@
         EXTENSION_CORE_1_0,
         EXTENSION_CORE_1_1,
         EXTENSION_CORE_1_2,
+        EXTENSION_CORE_1_3,
         EXTENSION_COUNT,
         EXTENSION_UNKNOWN,
     };
diff --git a/vulkan/libvulkan/libvulkan.map.txt b/vulkan/libvulkan/libvulkan.map.txt
index df97d7f..f49e8f3 100644
--- a/vulkan/libvulkan/libvulkan.map.txt
+++ b/vulkan/libvulkan/libvulkan.map.txt
@@ -11,20 +11,27 @@
     vkBindImageMemory;
     vkBindImageMemory2; # introduced=28
     vkCmdBeginQuery;
+    vkCmdBeginRendering; # introduced=33
     vkCmdBeginRenderPass;
     vkCmdBeginRenderPass2; # introduced=31
     vkCmdBindDescriptorSets;
     vkCmdBindIndexBuffer;
     vkCmdBindPipeline;
     vkCmdBindVertexBuffers;
+    vkCmdBindVertexBuffers2; #introduced=33
     vkCmdBlitImage;
+    vkCmdBlitImage2; #introduced=33
     vkCmdClearAttachments;
     vkCmdClearColorImage;
     vkCmdClearDepthStencilImage;
     vkCmdCopyBuffer;
+    vkCmdCopyBuffer2; #introduced=33
     vkCmdCopyBufferToImage;
+    vkCmdCopyBufferToImage2; #introduced=33
     vkCmdCopyImage;
+    vkCmdCopyImage2; #introduced=33
     vkCmdCopyImageToBuffer;
+    vkCmdCopyImageToBuffer2; #introduced=33
     vkCmdCopyQueryPoolResults;
     vkCmdDispatch;
     vkCmdDispatchBase; # introduced=28
@@ -36,6 +43,7 @@
     vkCmdDrawIndirect;
     vkCmdDrawIndirectCount; # introduced=31
     vkCmdEndQuery;
+    vkCmdEndRendering; #introduced=33
     vkCmdEndRenderPass;
     vkCmdEndRenderPass2; # introduced=31
     vkCmdExecuteCommands;
@@ -43,24 +51,44 @@
     vkCmdNextSubpass;
     vkCmdNextSubpass2; # introduced=31
     vkCmdPipelineBarrier;
+    vkCmdPipelineBarrier2; #introduced=33
     vkCmdPushConstants;
     vkCmdResetEvent;
+    vkCmdResetEvent2; #introduced=33
     vkCmdResetQueryPool;
     vkCmdResolveImage;
+    vkCmdResolveImage2; #introduced=33
     vkCmdSetBlendConstants;
+    vkCmdSetCullMode; #introduced=33
     vkCmdSetDepthBias;
+    vkCmdSetDepthBiasEnable; #introduced=33
     vkCmdSetDepthBounds;
+    vkCmdSetDepthBoundsTestEnable; #introduced=33
+    vkCmdSetDepthCompareOp; #introduced=33
+    vkCmdSetDepthTestEnable; #introduced=33
+    vkCmdSetDepthWriteEnable; #introduced=33
     vkCmdSetDeviceMask; # introduced=28
     vkCmdSetEvent;
+    vkCmdSetEvent2; #introduced=33
+    vkCmdSetFrontFace; #introduced=33
     vkCmdSetLineWidth;
+    vkCmdSetPrimitiveRestartEnable; #introduced=33
+    vkCmdSetPrimitiveTopology; #introduced=33
+    vkCmdSetRasterizerDiscardEnable; #introduced=33
     vkCmdSetScissor;
+    vkCmdSetScissorWithCount; #introduced=33
     vkCmdSetStencilCompareMask;
+    vkCmdSetStencilOp; #introduced=33
     vkCmdSetStencilReference;
+    vkCmdSetStencilTestEnable; #introduced=33
     vkCmdSetStencilWriteMask;
     vkCmdSetViewport;
+    vkCmdSetViewportWithCount; #introduced=33
     vkCmdUpdateBuffer;
     vkCmdWaitEvents;
+    vkCmdWaitEvents2; #introduced=33
     vkCmdWriteTimestamp;
+    vkCmdWriteTimestamp2; #introduced=33
     vkCreateAndroidSurfaceKHR;
     vkCreateBuffer;
     vkCreateBufferView;
@@ -79,6 +107,7 @@
     vkCreateInstance;
     vkCreatePipelineCache;
     vkCreatePipelineLayout;
+    vkCreatePrivateDataSlot; #introduced=33
     vkCreateQueryPool;
     vkCreateRenderPass;
     vkCreateRenderPass2; # introduced=31
@@ -103,6 +132,7 @@
     vkDestroyPipeline;
     vkDestroyPipelineCache;
     vkDestroyPipelineLayout;
+    vkDestroyPrivateDataSlot; #introduced=33
     vkDestroyQueryPool;
     vkDestroyRenderPass;
     vkDestroySampler;
@@ -130,9 +160,12 @@
     vkGetBufferMemoryRequirements2; # introduced=28
     vkGetBufferOpaqueCaptureAddress; # introduced=31
     vkGetDescriptorSetLayoutSupport; # introduced=28
+    vkGetDeviceBufferMemoryRequirements; #introduced=33
     vkGetDeviceGroupPeerMemoryFeatures; # introduced=28
     vkGetDeviceGroupPresentCapabilitiesKHR; # introduced=28
     vkGetDeviceGroupSurfacePresentModesKHR; # introduced=28
+    vkGetDeviceImageMemoryRequirements; #introduced=33
+    vkGetDeviceImageSparseMemoryRequirements; #introduced=33
     vkGetDeviceMemoryCommitment;
     vkGetDeviceMemoryOpaqueCaptureAddress; # introduced=31
     vkGetDeviceProcAddr;
@@ -169,7 +202,9 @@
     vkGetPhysicalDeviceSurfaceFormatsKHR;
     vkGetPhysicalDeviceSurfacePresentModesKHR;
     vkGetPhysicalDeviceSurfaceSupportKHR;
+    vkGetPhysicalDeviceToolProperties; #introduced=33
     vkGetPipelineCacheData;
+    vkGetPrivateData; #introduced=33
     vkGetQueryPoolResults;
     vkGetRenderAreaGranularity;
     vkGetSemaphoreCounterValue; # introduced=31
@@ -180,6 +215,7 @@
     vkQueueBindSparse;
     vkQueuePresentKHR;
     vkQueueSubmit;
+    vkQueueSubmit2; #introduced=33
     vkQueueWaitIdle;
     vkResetCommandBuffer;
     vkResetCommandPool;
@@ -188,6 +224,7 @@
     vkResetFences;
     vkResetQueryPool; # introduced=31
     vkSetEvent;
+    vkSetPrivateData; # introduced=33
     vkSignalSemaphore; # introduced=31
     vkTrimCommandPool; # introduced=28
     vkUnmapMemory;
diff --git a/vulkan/libvulkan/swapchain.cpp b/vulkan/libvulkan/swapchain.cpp
index 5e36adf..45bc4c9 100644
--- a/vulkan/libvulkan/swapchain.cpp
+++ b/vulkan/libvulkan/swapchain.cpp
@@ -23,6 +23,8 @@
 #include <sync/sync.h>
 #include <system/window.h>
 #include <ui/BufferQueueDefs.h>
+#include <ui/DebugUtils.h>
+#include <ui/PixelFormat.h>
 #include <utils/StrongPointer.h>
 #include <utils/Timers.h>
 #include <utils/Trace.h>
@@ -462,21 +464,24 @@
     *count = num_copied;
 }
 
-android_pixel_format GetNativePixelFormat(VkFormat format) {
-    android_pixel_format native_format = HAL_PIXEL_FORMAT_RGBA_8888;
+android::PixelFormat GetNativePixelFormat(VkFormat format) {
+    android::PixelFormat native_format = android::PIXEL_FORMAT_RGBA_8888;
     switch (format) {
         case VK_FORMAT_R8G8B8A8_UNORM:
         case VK_FORMAT_R8G8B8A8_SRGB:
-            native_format = HAL_PIXEL_FORMAT_RGBA_8888;
+            native_format = android::PIXEL_FORMAT_RGBA_8888;
             break;
         case VK_FORMAT_R5G6B5_UNORM_PACK16:
-            native_format = HAL_PIXEL_FORMAT_RGB_565;
+            native_format = android::PIXEL_FORMAT_RGB_565;
             break;
         case VK_FORMAT_R16G16B16A16_SFLOAT:
-            native_format = HAL_PIXEL_FORMAT_RGBA_FP16;
+            native_format = android::PIXEL_FORMAT_RGBA_FP16;
             break;
         case VK_FORMAT_A2B10G10R10_UNORM_PACK32:
-            native_format = HAL_PIXEL_FORMAT_RGBA_1010102;
+            native_format = android::PIXEL_FORMAT_RGBA_1010102;
+            break;
+        case VK_FORMAT_R8_UNORM:
+            native_format = android::PIXEL_FORMAT_R_8;
             break;
         default:
             ALOGV("unsupported swapchain format %d", format);
@@ -805,6 +810,17 @@
         }
     }
 
+    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});
+    }
+
+    // NOTE: Any new formats that are added must be coordinated across different
+    // Android users.  This includes the ANGLE team (a layered implementation of
+    // OpenGL-ES).
+
     VkResult result = VK_SUCCESS;
     if (formats) {
         uint32_t transfer_count = all_formats.size();
@@ -1107,7 +1123,7 @@
     if (!allocator)
         allocator = &GetData(device).allocator;
 
-    android_pixel_format native_pixel_format =
+    android::PixelFormat native_pixel_format =
         GetNativePixelFormat(create_info->imageFormat);
     android_dataspace native_dataspace =
         GetNativeDataspace(create_info->imageColorSpace);
@@ -1204,8 +1220,8 @@
 
     err = native_window_set_buffers_format(window, native_pixel_format);
     if (err != android::OK) {
-        ALOGE("native_window_set_buffers_format(%d) failed: %s (%d)",
-              native_pixel_format, strerror(-err), err);
+        ALOGE("native_window_set_buffers_format(%s) failed: %s (%d)",
+              decodePixelFormat(native_pixel_format).c_str(), strerror(-err), err);
         return VK_ERROR_SURFACE_LOST_KHR;
     }
     err = native_window_set_buffers_data_space(window, native_dataspace);
diff --git a/vulkan/nulldrv/null_driver.cpp b/vulkan/nulldrv/null_driver.cpp
index b94233b..3c91150 100644
--- a/vulkan/nulldrv/null_driver.cpp
+++ b/vulkan/nulldrv/null_driver.cpp
@@ -260,7 +260,7 @@
 
 VKAPI_ATTR
 VkResult EnumerateInstanceVersion(uint32_t* pApiVersion) {
-    *pApiVersion = VK_API_VERSION_1_1;
+    *pApiVersion = VK_API_VERSION_1_3;
     return VK_SUCCESS;
 }
 
@@ -397,8 +397,8 @@
 
 void GetPhysicalDeviceProperties(VkPhysicalDevice,
                                  VkPhysicalDeviceProperties* properties) {
-    properties->apiVersion = VK_MAKE_VERSION(1, 0, VK_HEADER_VERSION);
-    properties->driverVersion = VK_MAKE_VERSION(0, 0, 1);
+    properties->apiVersion = VK_MAKE_API_VERSION(0, 1, 2, VK_HEADER_VERSION);
+    properties->driverVersion = VK_MAKE_API_VERSION(0, 0, 0, 1);
     properties->vendorID = 0;
     properties->deviceID = 0;
     properties->deviceType = VK_PHYSICAL_DEVICE_TYPE_OTHER;
@@ -1625,6 +1625,125 @@
     return 0;
 }
 
+void CmdBeginRendering(VkCommandBuffer commandBuffer, const VkRenderingInfo* pRenderingInfo) {
+}
+
+void CmdEndRendering(VkCommandBuffer commandBuffer) {
+}
+
+void CmdBindVertexBuffers2(VkCommandBuffer commandBuffer, uint32_t firstBinding, uint32_t bindingCount, const VkBuffer* pBuffers, const VkDeviceSize* pOffsets, const VkDeviceSize* pSizes, const VkDeviceSize* pStrides) {
+}
+
+void CmdBlitImage2(VkCommandBuffer commandBuffer, const VkBlitImageInfo2* pBlitImageInfo) {
+}
+
+void CmdCopyBuffer2(VkCommandBuffer commandBuffer, const VkCopyBufferInfo2* pCopyBufferInfo) {
+}
+
+void CmdCopyImage2(VkCommandBuffer commandBuffer, const VkCopyImageInfo2* pCopyImageInfo) {
+}
+
+void CmdCopyBufferToImage2(VkCommandBuffer commandBuffer, const VkCopyBufferToImageInfo2* pCopyBufferToImageInfo) {
+}
+
+void CmdCopyImageToBuffer2(VkCommandBuffer commandBuffer, const VkCopyImageToBufferInfo2* pCopyImageToBufferInfo) {
+}
+
+void CmdPipelineBarrier2(VkCommandBuffer commandBuffer, const VkDependencyInfo* pDependencyInfo) {
+}
+
+void CmdResetEvent2(VkCommandBuffer commandBuffer, VkEvent event, VkPipelineStageFlags2 stageMask) {
+}
+
+void CmdResolveImage2(VkCommandBuffer commandBuffer, const VkResolveImageInfo2* pResolveImageInfo) {
+}
+
+void CmdSetCullMode(VkCommandBuffer commandBuffer, VkCullModeFlags cullMode) {
+}
+
+void CmdSetDepthBiasEnable(VkCommandBuffer commandBuffer, VkBool32 depthBiasEnable) {
+}
+
+void CmdSetDepthBoundsTestEnable(VkCommandBuffer commandBuffer, VkBool32 depthBoundsTestEnable) {
+}
+
+void CmdSetDepthCompareOp(VkCommandBuffer commandBuffer, VkCompareOp depthCompareOp) {
+}
+
+void CmdSetDepthTestEnable(VkCommandBuffer commandBuffer, VkBool32 depthTestEnable) {
+}
+
+void CmdSetDepthWriteEnable(VkCommandBuffer commandBuffer, VkBool32 depthWriteEnable) {
+}
+
+void CmdSetEvent2(VkCommandBuffer commandBuffer, VkEvent event, const VkDependencyInfo* pDependencyInfo) {
+}
+
+void CmdSetFrontFace(VkCommandBuffer commandBuffer, VkFrontFace frontFace) {
+}
+
+void CmdSetPrimitiveRestartEnable(VkCommandBuffer commandBuffer, VkBool32 primitiveRestartEnable) {
+}
+
+void CmdSetPrimitiveTopology(VkCommandBuffer commandBuffer, VkPrimitiveTopology primitiveTopology) {
+}
+
+void CmdSetRasterizerDiscardEnable(VkCommandBuffer commandBuffer, VkBool32 rasterizerDiscardEnable) {
+}
+
+void CmdSetScissorWithCount(VkCommandBuffer commandBuffer, uint32_t scissorCount, const VkRect2D* pScissors) {
+}
+
+void CmdSetStencilOp(VkCommandBuffer commandBuffer, VkStencilFaceFlags faceMask, VkStencilOp failOp, VkStencilOp passOp, VkStencilOp depthFailOp, VkCompareOp compareOp) {
+}
+
+void CmdSetStencilTestEnable(VkCommandBuffer commandBuffer, VkBool32 stencilTestEnable) {
+}
+
+void CmdSetViewportWithCount(VkCommandBuffer commandBuffer, uint32_t viewportCount, const VkViewport* pViewports) {
+}
+
+void CmdWaitEvents2(VkCommandBuffer commandBuffer, uint32_t eventCount, const VkEvent* pEvents, const VkDependencyInfo* pDependencyInfos) {
+}
+
+void CmdWriteTimestamp2(VkCommandBuffer commandBuffer, VkPipelineStageFlags2 stage, VkQueryPool queryPool, uint32_t query) {
+}
+
+VkResult CreatePrivateDataSlot(VkDevice device, const VkPrivateDataSlotCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkPrivateDataSlot* pPrivateDataSlot) {
+    ALOGV("TODO: vk%s", __FUNCTION__);
+    return VK_SUCCESS;
+}
+
+void DestroyPrivateDataSlot(VkDevice device, VkPrivateDataSlot privateDataSlot, const VkAllocationCallbacks* pAllocator) {
+}
+
+void GetDeviceBufferMemoryRequirements(VkDevice device, const VkDeviceBufferMemoryRequirements* pInfo, VkMemoryRequirements2* pMemoryRequirements) {
+}
+
+void GetDeviceImageMemoryRequirements(VkDevice device, const VkDeviceImageMemoryRequirements* pInfo, VkMemoryRequirements2* pMemoryRequirements) {
+}
+
+void GetDeviceImageSparseMemoryRequirements(VkDevice device, const VkDeviceImageMemoryRequirements* pInfo, uint32_t* pSparseMemoryRequirementCount, VkSparseImageMemoryRequirements2* pSparseMemoryRequirements) {
+}
+
+VkResult GetPhysicalDeviceToolProperties(VkPhysicalDevice physicalDevice, uint32_t* pToolCount, VkPhysicalDeviceToolProperties* pToolProperties) {
+    ALOGV("TODO: vk%s", __FUNCTION__);
+    return VK_SUCCESS;
+}
+
+void GetPrivateData(VkDevice device, VkObjectType objectType, uint64_t objectHandle, VkPrivateDataSlot privateDataSlot, uint64_t* pData) {
+}
+
+VkResult QueueSubmit2(VkQueue queue, uint32_t submitCount, const VkSubmitInfo2* pSubmits, VkFence fence) {
+    ALOGV("TODO: vk%s", __FUNCTION__);
+    return VK_SUCCESS;
+}
+
+VkResult SetPrivateData(VkDevice device, VkObjectType objectType, uint64_t objectHandle, VkPrivateDataSlot privateDataSlot, uint64_t data) {
+    ALOGV("TODO: vk%s", __FUNCTION__);
+    return VK_SUCCESS;
+}
+
 #pragma clang diagnostic pop
 // clang-format on
 
diff --git a/vulkan/nulldrv/null_driver_gen.cpp b/vulkan/nulldrv/null_driver_gen.cpp
index edda12c..f6dcf09 100644
--- a/vulkan/nulldrv/null_driver_gen.cpp
+++ b/vulkan/nulldrv/null_driver_gen.cpp
@@ -68,18 +68,25 @@
     {"vkCmdBeginQuery", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdBeginQuery>(CmdBeginQuery))},
     {"vkCmdBeginRenderPass", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdBeginRenderPass>(CmdBeginRenderPass))},
     {"vkCmdBeginRenderPass2", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdBeginRenderPass2>(CmdBeginRenderPass2))},
+    {"vkCmdBeginRendering", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdBeginRendering>(CmdBeginRendering))},
     {"vkCmdBindDescriptorSets", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdBindDescriptorSets>(CmdBindDescriptorSets))},
     {"vkCmdBindIndexBuffer", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdBindIndexBuffer>(CmdBindIndexBuffer))},
     {"vkCmdBindPipeline", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdBindPipeline>(CmdBindPipeline))},
     {"vkCmdBindVertexBuffers", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdBindVertexBuffers>(CmdBindVertexBuffers))},
+    {"vkCmdBindVertexBuffers2", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdBindVertexBuffers2>(CmdBindVertexBuffers2))},
     {"vkCmdBlitImage", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdBlitImage>(CmdBlitImage))},
+    {"vkCmdBlitImage2", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdBlitImage2>(CmdBlitImage2))},
     {"vkCmdClearAttachments", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdClearAttachments>(CmdClearAttachments))},
     {"vkCmdClearColorImage", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdClearColorImage>(CmdClearColorImage))},
     {"vkCmdClearDepthStencilImage", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdClearDepthStencilImage>(CmdClearDepthStencilImage))},
     {"vkCmdCopyBuffer", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdCopyBuffer>(CmdCopyBuffer))},
+    {"vkCmdCopyBuffer2", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdCopyBuffer2>(CmdCopyBuffer2))},
     {"vkCmdCopyBufferToImage", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdCopyBufferToImage>(CmdCopyBufferToImage))},
+    {"vkCmdCopyBufferToImage2", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdCopyBufferToImage2>(CmdCopyBufferToImage2))},
     {"vkCmdCopyImage", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdCopyImage>(CmdCopyImage))},
+    {"vkCmdCopyImage2", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdCopyImage2>(CmdCopyImage2))},
     {"vkCmdCopyImageToBuffer", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdCopyImageToBuffer>(CmdCopyImageToBuffer))},
+    {"vkCmdCopyImageToBuffer2", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdCopyImageToBuffer2>(CmdCopyImageToBuffer2))},
     {"vkCmdCopyQueryPoolResults", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdCopyQueryPoolResults>(CmdCopyQueryPoolResults))},
     {"vkCmdDispatch", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdDispatch>(CmdDispatch))},
     {"vkCmdDispatchBase", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdDispatchBase>(CmdDispatchBase))},
@@ -93,29 +100,50 @@
     {"vkCmdEndQuery", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdEndQuery>(CmdEndQuery))},
     {"vkCmdEndRenderPass", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdEndRenderPass>(CmdEndRenderPass))},
     {"vkCmdEndRenderPass2", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdEndRenderPass2>(CmdEndRenderPass2))},
+    {"vkCmdEndRendering", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdEndRendering>(CmdEndRendering))},
     {"vkCmdExecuteCommands", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdExecuteCommands>(CmdExecuteCommands))},
     {"vkCmdFillBuffer", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdFillBuffer>(CmdFillBuffer))},
     {"vkCmdNextSubpass", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdNextSubpass>(CmdNextSubpass))},
     {"vkCmdNextSubpass2", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdNextSubpass2>(CmdNextSubpass2))},
     {"vkCmdPipelineBarrier", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdPipelineBarrier>(CmdPipelineBarrier))},
+    {"vkCmdPipelineBarrier2", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdPipelineBarrier2>(CmdPipelineBarrier2))},
     {"vkCmdPushConstants", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdPushConstants>(CmdPushConstants))},
     {"vkCmdResetEvent", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdResetEvent>(CmdResetEvent))},
+    {"vkCmdResetEvent2", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdResetEvent2>(CmdResetEvent2))},
     {"vkCmdResetQueryPool", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdResetQueryPool>(CmdResetQueryPool))},
     {"vkCmdResolveImage", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdResolveImage>(CmdResolveImage))},
+    {"vkCmdResolveImage2", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdResolveImage2>(CmdResolveImage2))},
     {"vkCmdSetBlendConstants", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdSetBlendConstants>(CmdSetBlendConstants))},
+    {"vkCmdSetCullMode", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdSetCullMode>(CmdSetCullMode))},
     {"vkCmdSetDepthBias", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdSetDepthBias>(CmdSetDepthBias))},
+    {"vkCmdSetDepthBiasEnable", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdSetDepthBiasEnable>(CmdSetDepthBiasEnable))},
     {"vkCmdSetDepthBounds", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdSetDepthBounds>(CmdSetDepthBounds))},
+    {"vkCmdSetDepthBoundsTestEnable", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdSetDepthBoundsTestEnable>(CmdSetDepthBoundsTestEnable))},
+    {"vkCmdSetDepthCompareOp", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdSetDepthCompareOp>(CmdSetDepthCompareOp))},
+    {"vkCmdSetDepthTestEnable", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdSetDepthTestEnable>(CmdSetDepthTestEnable))},
+    {"vkCmdSetDepthWriteEnable", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdSetDepthWriteEnable>(CmdSetDepthWriteEnable))},
     {"vkCmdSetDeviceMask", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdSetDeviceMask>(CmdSetDeviceMask))},
     {"vkCmdSetEvent", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdSetEvent>(CmdSetEvent))},
+    {"vkCmdSetEvent2", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdSetEvent2>(CmdSetEvent2))},
+    {"vkCmdSetFrontFace", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdSetFrontFace>(CmdSetFrontFace))},
     {"vkCmdSetLineWidth", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdSetLineWidth>(CmdSetLineWidth))},
+    {"vkCmdSetPrimitiveRestartEnable", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdSetPrimitiveRestartEnable>(CmdSetPrimitiveRestartEnable))},
+    {"vkCmdSetPrimitiveTopology", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdSetPrimitiveTopology>(CmdSetPrimitiveTopology))},
+    {"vkCmdSetRasterizerDiscardEnable", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdSetRasterizerDiscardEnable>(CmdSetRasterizerDiscardEnable))},
     {"vkCmdSetScissor", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdSetScissor>(CmdSetScissor))},
+    {"vkCmdSetScissorWithCount", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdSetScissorWithCount>(CmdSetScissorWithCount))},
     {"vkCmdSetStencilCompareMask", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdSetStencilCompareMask>(CmdSetStencilCompareMask))},
+    {"vkCmdSetStencilOp", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdSetStencilOp>(CmdSetStencilOp))},
     {"vkCmdSetStencilReference", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdSetStencilReference>(CmdSetStencilReference))},
+    {"vkCmdSetStencilTestEnable", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdSetStencilTestEnable>(CmdSetStencilTestEnable))},
     {"vkCmdSetStencilWriteMask", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdSetStencilWriteMask>(CmdSetStencilWriteMask))},
     {"vkCmdSetViewport", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdSetViewport>(CmdSetViewport))},
+    {"vkCmdSetViewportWithCount", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdSetViewportWithCount>(CmdSetViewportWithCount))},
     {"vkCmdUpdateBuffer", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdUpdateBuffer>(CmdUpdateBuffer))},
     {"vkCmdWaitEvents", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdWaitEvents>(CmdWaitEvents))},
+    {"vkCmdWaitEvents2", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdWaitEvents2>(CmdWaitEvents2))},
     {"vkCmdWriteTimestamp", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdWriteTimestamp>(CmdWriteTimestamp))},
+    {"vkCmdWriteTimestamp2", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCmdWriteTimestamp2>(CmdWriteTimestamp2))},
     {"vkCreateBuffer", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCreateBuffer>(CreateBuffer))},
     {"vkCreateBufferView", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCreateBufferView>(CreateBufferView))},
     {"vkCreateCommandPool", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCreateCommandPool>(CreateCommandPool))},
@@ -134,6 +162,7 @@
     {"vkCreateInstance", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCreateInstance>(CreateInstance))},
     {"vkCreatePipelineCache", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCreatePipelineCache>(CreatePipelineCache))},
     {"vkCreatePipelineLayout", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCreatePipelineLayout>(CreatePipelineLayout))},
+    {"vkCreatePrivateDataSlot", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCreatePrivateDataSlot>(CreatePrivateDataSlot))},
     {"vkCreateQueryPool", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCreateQueryPool>(CreateQueryPool))},
     {"vkCreateRenderPass", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCreateRenderPass>(CreateRenderPass))},
     {"vkCreateRenderPass2", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkCreateRenderPass2>(CreateRenderPass2))},
@@ -159,6 +188,7 @@
     {"vkDestroyPipeline", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkDestroyPipeline>(DestroyPipeline))},
     {"vkDestroyPipelineCache", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkDestroyPipelineCache>(DestroyPipelineCache))},
     {"vkDestroyPipelineLayout", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkDestroyPipelineLayout>(DestroyPipelineLayout))},
+    {"vkDestroyPrivateDataSlot", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkDestroyPrivateDataSlot>(DestroyPrivateDataSlot))},
     {"vkDestroyQueryPool", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkDestroyQueryPool>(DestroyQueryPool))},
     {"vkDestroyRenderPass", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkDestroyRenderPass>(DestroyRenderPass))},
     {"vkDestroySampler", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkDestroySampler>(DestroySampler))},
@@ -183,7 +213,10 @@
     {"vkGetBufferMemoryRequirements2", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkGetBufferMemoryRequirements2>(GetBufferMemoryRequirements2))},
     {"vkGetBufferOpaqueCaptureAddress", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkGetBufferOpaqueCaptureAddress>(GetBufferOpaqueCaptureAddress))},
     {"vkGetDescriptorSetLayoutSupport", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkGetDescriptorSetLayoutSupport>(GetDescriptorSetLayoutSupport))},
+    {"vkGetDeviceBufferMemoryRequirements", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkGetDeviceBufferMemoryRequirements>(GetDeviceBufferMemoryRequirements))},
     {"vkGetDeviceGroupPeerMemoryFeatures", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkGetDeviceGroupPeerMemoryFeatures>(GetDeviceGroupPeerMemoryFeatures))},
+    {"vkGetDeviceImageMemoryRequirements", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkGetDeviceImageMemoryRequirements>(GetDeviceImageMemoryRequirements))},
+    {"vkGetDeviceImageSparseMemoryRequirements", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkGetDeviceImageSparseMemoryRequirements>(GetDeviceImageSparseMemoryRequirements))},
     {"vkGetDeviceMemoryCommitment", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkGetDeviceMemoryCommitment>(GetDeviceMemoryCommitment))},
     {"vkGetDeviceMemoryOpaqueCaptureAddress", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkGetDeviceMemoryOpaqueCaptureAddress>(GetDeviceMemoryOpaqueCaptureAddress))},
     {"vkGetDeviceProcAddr", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkGetDeviceProcAddr>(GetDeviceProcAddr))},
@@ -221,7 +254,9 @@
     {"vkGetPhysicalDeviceSparseImageFormatProperties", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkGetPhysicalDeviceSparseImageFormatProperties>(GetPhysicalDeviceSparseImageFormatProperties))},
     {"vkGetPhysicalDeviceSparseImageFormatProperties2", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkGetPhysicalDeviceSparseImageFormatProperties2>(GetPhysicalDeviceSparseImageFormatProperties2))},
     {"vkGetPhysicalDeviceSparseImageFormatProperties2KHR", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkGetPhysicalDeviceSparseImageFormatProperties2KHR>(GetPhysicalDeviceSparseImageFormatProperties2KHR))},
+    {"vkGetPhysicalDeviceToolProperties", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkGetPhysicalDeviceToolProperties>(GetPhysicalDeviceToolProperties))},
     {"vkGetPipelineCacheData", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkGetPipelineCacheData>(GetPipelineCacheData))},
+    {"vkGetPrivateData", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkGetPrivateData>(GetPrivateData))},
     {"vkGetQueryPoolResults", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkGetQueryPoolResults>(GetQueryPoolResults))},
     {"vkGetRenderAreaGranularity", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkGetRenderAreaGranularity>(GetRenderAreaGranularity))},
     {"vkGetSemaphoreCounterValue", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkGetSemaphoreCounterValue>(GetSemaphoreCounterValue))},
@@ -233,6 +268,7 @@
     {"vkQueueBindSparse", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkQueueBindSparse>(QueueBindSparse))},
     {"vkQueueSignalReleaseImageANDROID", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkQueueSignalReleaseImageANDROID>(QueueSignalReleaseImageANDROID))},
     {"vkQueueSubmit", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkQueueSubmit>(QueueSubmit))},
+    {"vkQueueSubmit2", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkQueueSubmit2>(QueueSubmit2))},
     {"vkQueueWaitIdle", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkQueueWaitIdle>(QueueWaitIdle))},
     {"vkResetCommandBuffer", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkResetCommandBuffer>(ResetCommandBuffer))},
     {"vkResetCommandPool", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkResetCommandPool>(ResetCommandPool))},
@@ -241,6 +277,7 @@
     {"vkResetFences", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkResetFences>(ResetFences))},
     {"vkResetQueryPool", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkResetQueryPool>(ResetQueryPool))},
     {"vkSetEvent", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkSetEvent>(SetEvent))},
+    {"vkSetPrivateData", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkSetPrivateData>(SetPrivateData))},
     {"vkSignalSemaphore", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkSignalSemaphore>(SignalSemaphore))},
     {"vkTrimCommandPool", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkTrimCommandPool>(TrimCommandPool))},
     {"vkUnmapMemory", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkUnmapMemory>(UnmapMemory))},
diff --git a/vulkan/nulldrv/null_driver_gen.h b/vulkan/nulldrv/null_driver_gen.h
index e59cae9..3e003e3 100644
--- a/vulkan/nulldrv/null_driver_gen.h
+++ b/vulkan/nulldrv/null_driver_gen.h
@@ -200,6 +200,9 @@
 VKAPI_ATTR void GetBufferMemoryRequirements2(VkDevice device, const VkBufferMemoryRequirementsInfo2* pInfo, VkMemoryRequirements2* pMemoryRequirements);
 VKAPI_ATTR void GetImageMemoryRequirements2(VkDevice device, const VkImageMemoryRequirementsInfo2* pInfo, VkMemoryRequirements2* pMemoryRequirements);
 VKAPI_ATTR void GetImageSparseMemoryRequirements2(VkDevice device, const VkImageSparseMemoryRequirementsInfo2* pInfo, uint32_t* pSparseMemoryRequirementCount, VkSparseImageMemoryRequirements2* pSparseMemoryRequirements);
+VKAPI_ATTR void GetDeviceBufferMemoryRequirements(VkDevice device, const VkDeviceBufferMemoryRequirements* pInfo, VkMemoryRequirements2* pMemoryRequirements);
+VKAPI_ATTR void GetDeviceImageMemoryRequirements(VkDevice device, const VkDeviceImageMemoryRequirements* pInfo, VkMemoryRequirements2* pMemoryRequirements);
+VKAPI_ATTR void GetDeviceImageSparseMemoryRequirements(VkDevice device, const VkDeviceImageMemoryRequirements* pInfo, uint32_t* pSparseMemoryRequirementCount, VkSparseImageMemoryRequirements2* pSparseMemoryRequirements);
 VKAPI_ATTR VkResult CreateSamplerYcbcrConversion(VkDevice device, const VkSamplerYcbcrConversionCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkSamplerYcbcrConversion* pYcbcrConversion);
 VKAPI_ATTR void DestroySamplerYcbcrConversion(VkDevice device, VkSamplerYcbcrConversion ycbcrConversion, const VkAllocationCallbacks* pAllocator);
 VKAPI_ATTR void GetDeviceQueue2(VkDevice device, const VkDeviceQueueInfo2* pQueueInfo, VkQueue* pQueue);
@@ -220,6 +223,40 @@
 VKAPI_ATTR uint64_t GetBufferOpaqueCaptureAddress(VkDevice device, const VkBufferDeviceAddressInfo* pInfo);
 VKAPI_ATTR VkDeviceAddress GetBufferDeviceAddress(VkDevice device, const VkBufferDeviceAddressInfo* pInfo);
 VKAPI_ATTR uint64_t GetDeviceMemoryOpaqueCaptureAddress(VkDevice device, const VkDeviceMemoryOpaqueCaptureAddressInfo* pInfo);
+VKAPI_ATTR VkResult GetPhysicalDeviceToolProperties(VkPhysicalDevice physicalDevice, uint32_t* pToolCount, VkPhysicalDeviceToolProperties* pToolProperties);
+VKAPI_ATTR void CmdSetCullMode(VkCommandBuffer commandBuffer, VkCullModeFlags cullMode);
+VKAPI_ATTR void CmdSetFrontFace(VkCommandBuffer commandBuffer, VkFrontFace frontFace);
+VKAPI_ATTR void CmdSetPrimitiveTopology(VkCommandBuffer commandBuffer, VkPrimitiveTopology primitiveTopology);
+VKAPI_ATTR void CmdSetViewportWithCount(VkCommandBuffer commandBuffer, uint32_t viewportCount, const VkViewport* pViewports);
+VKAPI_ATTR void CmdSetScissorWithCount(VkCommandBuffer commandBuffer, uint32_t scissorCount, const VkRect2D* pScissors);
+VKAPI_ATTR void CmdBindVertexBuffers2(VkCommandBuffer commandBuffer, uint32_t firstBinding, uint32_t bindingCount, const VkBuffer* pBuffers, const VkDeviceSize* pOffsets, const VkDeviceSize* pSizes, const VkDeviceSize* pStrides);
+VKAPI_ATTR void CmdSetDepthTestEnable(VkCommandBuffer commandBuffer, VkBool32 depthTestEnable);
+VKAPI_ATTR void CmdSetDepthWriteEnable(VkCommandBuffer commandBuffer, VkBool32 depthWriteEnable);
+VKAPI_ATTR void CmdSetDepthCompareOp(VkCommandBuffer commandBuffer, VkCompareOp depthCompareOp);
+VKAPI_ATTR void CmdSetDepthBoundsTestEnable(VkCommandBuffer commandBuffer, VkBool32 depthBoundsTestEnable);
+VKAPI_ATTR void CmdSetStencilTestEnable(VkCommandBuffer commandBuffer, VkBool32 stencilTestEnable);
+VKAPI_ATTR void CmdSetStencilOp(VkCommandBuffer commandBuffer, VkStencilFaceFlags faceMask, VkStencilOp failOp, VkStencilOp passOp, VkStencilOp depthFailOp, VkCompareOp compareOp);
+VKAPI_ATTR void CmdSetRasterizerDiscardEnable(VkCommandBuffer commandBuffer, VkBool32 rasterizerDiscardEnable);
+VKAPI_ATTR void CmdSetDepthBiasEnable(VkCommandBuffer commandBuffer, VkBool32 depthBiasEnable);
+VKAPI_ATTR void CmdSetPrimitiveRestartEnable(VkCommandBuffer commandBuffer, VkBool32 primitiveRestartEnable);
+VKAPI_ATTR VkResult CreatePrivateDataSlot(VkDevice device, const VkPrivateDataSlotCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkPrivateDataSlot* pPrivateDataSlot);
+VKAPI_ATTR void DestroyPrivateDataSlot(VkDevice device, VkPrivateDataSlot privateDataSlot, const VkAllocationCallbacks* pAllocator);
+VKAPI_ATTR VkResult SetPrivateData(VkDevice device, VkObjectType objectType, uint64_t objectHandle, VkPrivateDataSlot privateDataSlot, uint64_t data);
+VKAPI_ATTR void GetPrivateData(VkDevice device, VkObjectType objectType, uint64_t objectHandle, VkPrivateDataSlot privateDataSlot, uint64_t* pData);
+VKAPI_ATTR void CmdCopyBuffer2(VkCommandBuffer commandBuffer, const VkCopyBufferInfo2* pCopyBufferInfo);
+VKAPI_ATTR void CmdCopyImage2(VkCommandBuffer commandBuffer, const VkCopyImageInfo2* pCopyImageInfo);
+VKAPI_ATTR void CmdBlitImage2(VkCommandBuffer commandBuffer, const VkBlitImageInfo2* pBlitImageInfo);
+VKAPI_ATTR void CmdCopyBufferToImage2(VkCommandBuffer commandBuffer, const VkCopyBufferToImageInfo2* pCopyBufferToImageInfo);
+VKAPI_ATTR void CmdCopyImageToBuffer2(VkCommandBuffer commandBuffer, const VkCopyImageToBufferInfo2* pCopyImageToBufferInfo);
+VKAPI_ATTR void CmdResolveImage2(VkCommandBuffer commandBuffer, const VkResolveImageInfo2* pResolveImageInfo);
+VKAPI_ATTR void CmdSetEvent2(VkCommandBuffer commandBuffer, VkEvent event, const VkDependencyInfo* pDependencyInfo);
+VKAPI_ATTR void CmdResetEvent2(VkCommandBuffer commandBuffer, VkEvent event, VkPipelineStageFlags2 stageMask);
+VKAPI_ATTR void CmdWaitEvents2(VkCommandBuffer commandBuffer, uint32_t eventCount, const VkEvent* pEvents, const VkDependencyInfo* pDependencyInfos);
+VKAPI_ATTR void CmdPipelineBarrier2(VkCommandBuffer commandBuffer, const VkDependencyInfo* pDependencyInfo);
+VKAPI_ATTR VkResult QueueSubmit2(VkQueue queue, uint32_t submitCount, const VkSubmitInfo2* pSubmits, VkFence fence);
+VKAPI_ATTR void CmdWriteTimestamp2(VkCommandBuffer commandBuffer, VkPipelineStageFlags2 stage, VkQueryPool queryPool, uint32_t query);
+VKAPI_ATTR void CmdBeginRendering(VkCommandBuffer commandBuffer, const VkRenderingInfo* pRenderingInfo);
+VKAPI_ATTR void CmdEndRendering(VkCommandBuffer commandBuffer);
 // clang-format on
 
 }  // namespace null_driver
diff --git a/vulkan/scripts/generator_common.py b/vulkan/scripts/generator_common.py
index 72fd4fb..4176509 100644
--- a/vulkan/scripts/generator_common.py
+++ b/vulkan/scripts/generator_common.py
@@ -33,6 +33,7 @@
     'VK_EXT_metal_surface',
     'VK_FUCHSIA_imagepipe_surface',
     'VK_GGP_stream_descriptor_surface',
+    'VK_HUAWEI_subpass_shading',
     'VK_KHR_display',
     'VK_KHR_display_swapchain',
     'VK_KHR_external_fence_win32',
@@ -47,11 +48,13 @@
     'VK_MVK_ios_surface',
     'VK_MVK_macos_surface',
     'VK_NN_vi_surface',
+    'VK_NV_acquire_winrt_display',
     'VK_NV_cooperative_matrix',
     'VK_NV_coverage_reduction_mode',
     'VK_NV_external_memory_win32',
     'VK_NV_win32_keyed_mutex',
     'VK_NVX_image_view_handle',
+    'VK_QNX_screen_surface',
 ]
 
 # Extensions having functions exported by the loader.
diff --git a/vulkan/vkjson/vkjson.cc b/vulkan/vkjson/vkjson.cc
index 438e5dd..da6b00a 100644
--- a/vulkan/vkjson/vkjson.cc
+++ b/vulkan/vkjson/vkjson.cc
@@ -387,6 +387,19 @@
   }
 };
 
+template <>
+struct EnumTraits<VkShaderFloatControlsIndependence> {
+  static bool exist(uint32_t e) {
+    switch (e) {
+      case VK_SHADER_FLOAT_CONTROLS_INDEPENDENCE_32_BIT_ONLY:
+      case VK_SHADER_FLOAT_CONTROLS_INDEPENDENCE_ALL:
+      case VK_SHADER_FLOAT_CONTROLS_INDEPENDENCE_NONE:
+        return true;
+    }
+    return false;
+  }
+};
+
 // VkSparseImageFormatProperties
 
 template <typename Visitor>
@@ -407,6 +420,7 @@
     visitor->Visit("maxResourceSize", &properties->maxResourceSize);
 }
 
+// clang-format off
 template <typename Visitor>
 inline bool Iterate(Visitor* visitor, VkPhysicalDeviceLimits* limits) {
   return
@@ -605,6 +619,200 @@
 }
 
 template <typename Visitor>
+inline bool Iterate(Visitor* visitor, VkJsonCore12* core) {
+  return
+    visitor->Visit("features", &core->features) &&
+    visitor->Visit("properties", &core->properties);
+}
+
+template <typename Visitor>
+inline bool Iterate(Visitor* visitor, VkPhysicalDeviceVulkan12Properties* properties) {
+  return
+    visitor->Visit("driverID", &properties->driverID) &&
+    visitor->Visit("driverName", &properties->driverName) &&
+    visitor->Visit("driverInfo", &properties->driverInfo) &&
+    visitor->Visit("conformanceVersion", &properties->conformanceVersion) &&
+    visitor->Visit("denormBehaviorIndependence", &properties->denormBehaviorIndependence) &&
+    visitor->Visit("roundingModeIndependence", &properties->roundingModeIndependence) &&
+    visitor->Visit("shaderSignedZeroInfNanPreserveFloat16", &properties->shaderSignedZeroInfNanPreserveFloat16) &&
+    visitor->Visit("shaderSignedZeroInfNanPreserveFloat32", &properties->shaderSignedZeroInfNanPreserveFloat32) &&
+    visitor->Visit("shaderSignedZeroInfNanPreserveFloat64", &properties->shaderSignedZeroInfNanPreserveFloat64) &&
+    visitor->Visit("shaderDenormPreserveFloat16", &properties->shaderDenormPreserveFloat16) &&
+    visitor->Visit("shaderDenormPreserveFloat32", &properties->shaderDenormPreserveFloat32) &&
+    visitor->Visit("shaderDenormPreserveFloat64", &properties->shaderDenormPreserveFloat64) &&
+    visitor->Visit("shaderDenormFlushToZeroFloat16", &properties->shaderDenormFlushToZeroFloat16) &&
+    visitor->Visit("shaderDenormFlushToZeroFloat32", &properties->shaderDenormFlushToZeroFloat32) &&
+    visitor->Visit("shaderDenormFlushToZeroFloat64", &properties->shaderDenormFlushToZeroFloat64) &&
+    visitor->Visit("shaderRoundingModeRTEFloat16", &properties->shaderRoundingModeRTEFloat16) &&
+    visitor->Visit("shaderRoundingModeRTEFloat32", &properties->shaderRoundingModeRTEFloat32) &&
+    visitor->Visit("shaderRoundingModeRTEFloat64", &properties->shaderRoundingModeRTEFloat64) &&
+    visitor->Visit("shaderRoundingModeRTZFloat16", &properties->shaderRoundingModeRTZFloat16) &&
+    visitor->Visit("shaderRoundingModeRTZFloat32", &properties->shaderRoundingModeRTZFloat32) &&
+    visitor->Visit("shaderRoundingModeRTZFloat64", &properties->shaderRoundingModeRTZFloat64) &&
+    visitor->Visit("maxUpdateAfterBindDescriptorsInAllPools", &properties->maxUpdateAfterBindDescriptorsInAllPools) &&
+    visitor->Visit("shaderUniformBufferArrayNonUniformIndexingNative", &properties->shaderUniformBufferArrayNonUniformIndexingNative) &&
+    visitor->Visit("shaderSampledImageArrayNonUniformIndexingNative", &properties->shaderSampledImageArrayNonUniformIndexingNative) &&
+    visitor->Visit("shaderStorageBufferArrayNonUniformIndexingNative", &properties->shaderStorageBufferArrayNonUniformIndexingNative) &&
+    visitor->Visit("shaderStorageImageArrayNonUniformIndexingNative", &properties->shaderStorageImageArrayNonUniformIndexingNative) &&
+    visitor->Visit("shaderInputAttachmentArrayNonUniformIndexingNative", &properties->shaderInputAttachmentArrayNonUniformIndexingNative) &&
+    visitor->Visit("robustBufferAccessUpdateAfterBind", &properties->robustBufferAccessUpdateAfterBind) &&
+    visitor->Visit("quadDivergentImplicitLod", &properties->quadDivergentImplicitLod) &&
+    visitor->Visit("maxPerStageDescriptorUpdateAfterBindSamplers", &properties->maxPerStageDescriptorUpdateAfterBindSamplers) &&
+    visitor->Visit("maxPerStageDescriptorUpdateAfterBindUniformBuffers", &properties->maxPerStageDescriptorUpdateAfterBindUniformBuffers) &&
+    visitor->Visit("maxPerStageDescriptorUpdateAfterBindStorageBuffers", &properties->maxPerStageDescriptorUpdateAfterBindStorageBuffers) &&
+    visitor->Visit("maxPerStageDescriptorUpdateAfterBindSampledImages", &properties->maxPerStageDescriptorUpdateAfterBindSampledImages) &&
+    visitor->Visit("maxPerStageDescriptorUpdateAfterBindStorageImages", &properties->maxPerStageDescriptorUpdateAfterBindStorageImages) &&
+    visitor->Visit("maxPerStageDescriptorUpdateAfterBindInputAttachments", &properties->maxPerStageDescriptorUpdateAfterBindInputAttachments) &&
+    visitor->Visit("maxPerStageUpdateAfterBindResources", &properties->maxPerStageUpdateAfterBindResources) &&
+    visitor->Visit("maxDescriptorSetUpdateAfterBindSamplers", &properties->maxDescriptorSetUpdateAfterBindSamplers) &&
+    visitor->Visit("maxDescriptorSetUpdateAfterBindUniformBuffers", &properties->maxDescriptorSetUpdateAfterBindUniformBuffers) &&
+    visitor->Visit("maxDescriptorSetUpdateAfterBindUniformBuffersDynamic", &properties->maxDescriptorSetUpdateAfterBindUniformBuffersDynamic) &&
+    visitor->Visit("maxDescriptorSetUpdateAfterBindStorageBuffers", &properties->maxDescriptorSetUpdateAfterBindStorageBuffers) &&
+    visitor->Visit("maxDescriptorSetUpdateAfterBindStorageBuffersDynamic", &properties->maxDescriptorSetUpdateAfterBindStorageBuffersDynamic) &&
+    visitor->Visit("maxDescriptorSetUpdateAfterBindSampledImages", &properties->maxDescriptorSetUpdateAfterBindSampledImages) &&
+    visitor->Visit("maxDescriptorSetUpdateAfterBindStorageImages", &properties->maxDescriptorSetUpdateAfterBindStorageImages) &&
+    visitor->Visit("maxDescriptorSetUpdateAfterBindInputAttachments", &properties->maxDescriptorSetUpdateAfterBindInputAttachments) &&
+    visitor->Visit("supportedDepthResolveModes", &properties->supportedDepthResolveModes) &&
+    visitor->Visit("supportedStencilResolveModes", &properties->supportedStencilResolveModes) &&
+    visitor->Visit("independentResolveNone", &properties->independentResolveNone) &&
+    visitor->Visit("independentResolve", &properties->independentResolve) &&
+    visitor->Visit("filterMinmaxSingleComponentFormats", &properties->filterMinmaxSingleComponentFormats) &&
+    visitor->Visit("filterMinmaxImageComponentMapping", &properties->filterMinmaxImageComponentMapping) &&
+    visitor->Visit("maxTimelineSemaphoreValueDifference", &properties->maxTimelineSemaphoreValueDifference) &&
+    visitor->Visit("framebufferIntegerColorSampleCounts", &properties->framebufferIntegerColorSampleCounts);
+}
+
+template <typename Visitor>
+inline bool Iterate(Visitor* visitor, VkPhysicalDeviceVulkan12Features* features) {
+  return
+    visitor->Visit("samplerMirrorClampToEdge", &features->samplerMirrorClampToEdge) &&
+    visitor->Visit("drawIndirectCount", &features->drawIndirectCount) &&
+    visitor->Visit("storageBuffer8BitAccess", &features->storageBuffer8BitAccess) &&
+    visitor->Visit("uniformAndStorageBuffer8BitAccess", &features->uniformAndStorageBuffer8BitAccess) &&
+    visitor->Visit("storagePushConstant8", &features->storagePushConstant8) &&
+    visitor->Visit("shaderBufferInt64Atomics", &features->shaderBufferInt64Atomics) &&
+    visitor->Visit("shaderSharedInt64Atomics", &features->shaderSharedInt64Atomics) &&
+    visitor->Visit("shaderFloat16", &features->shaderFloat16) &&
+    visitor->Visit("shaderInt8", &features->shaderInt8) &&
+    visitor->Visit("descriptorIndexing", &features->descriptorIndexing) &&
+    visitor->Visit("shaderInputAttachmentArrayDynamicIndexing", &features->shaderInputAttachmentArrayDynamicIndexing) &&
+    visitor->Visit("shaderUniformTexelBufferArrayDynamicIndexing", &features->shaderUniformTexelBufferArrayDynamicIndexing) &&
+    visitor->Visit("shaderStorageTexelBufferArrayDynamicIndexing", &features->shaderStorageTexelBufferArrayDynamicIndexing) &&
+    visitor->Visit("shaderUniformBufferArrayNonUniformIndexing", &features->shaderUniformBufferArrayNonUniformIndexing) &&
+    visitor->Visit("shaderSampledImageArrayNonUniformIndexing", &features->shaderSampledImageArrayNonUniformIndexing) &&
+    visitor->Visit("shaderStorageBufferArrayNonUniformIndexing", &features->shaderStorageBufferArrayNonUniformIndexing) &&
+    visitor->Visit("shaderStorageImageArrayNonUniformIndexing", &features->shaderStorageImageArrayNonUniformIndexing) &&
+    visitor->Visit("shaderInputAttachmentArrayNonUniformIndexing", &features->shaderInputAttachmentArrayNonUniformIndexing) &&
+    visitor->Visit("shaderUniformTexelBufferArrayNonUniformIndexing", &features->shaderUniformTexelBufferArrayNonUniformIndexing) &&
+    visitor->Visit("shaderStorageTexelBufferArrayNonUniformIndexing", &features->shaderStorageTexelBufferArrayNonUniformIndexing) &&
+    visitor->Visit("descriptorBindingUniformBufferUpdateAfterBind", &features->descriptorBindingUniformBufferUpdateAfterBind) &&
+    visitor->Visit("descriptorBindingSampledImageUpdateAfterBind", &features->descriptorBindingSampledImageUpdateAfterBind) &&
+    visitor->Visit("descriptorBindingStorageImageUpdateAfterBind", &features->descriptorBindingStorageImageUpdateAfterBind) &&
+    visitor->Visit("descriptorBindingStorageBufferUpdateAfterBind", &features->descriptorBindingStorageBufferUpdateAfterBind) &&
+    visitor->Visit("descriptorBindingUniformTexelBufferUpdateAfterBind", &features->descriptorBindingUniformTexelBufferUpdateAfterBind) &&
+    visitor->Visit("descriptorBindingStorageTexelBufferUpdateAfterBind", &features->descriptorBindingStorageTexelBufferUpdateAfterBind) &&
+    visitor->Visit("descriptorBindingUpdateUnusedWhilePending", &features->descriptorBindingUpdateUnusedWhilePending) &&
+    visitor->Visit("descriptorBindingPartiallyBound", &features->descriptorBindingPartiallyBound) &&
+    visitor->Visit("descriptorBindingVariableDescriptorCount", &features->descriptorBindingVariableDescriptorCount) &&
+    visitor->Visit("runtimeDescriptorArray", &features->runtimeDescriptorArray) &&
+    visitor->Visit("samplerFilterMinmax", &features->samplerFilterMinmax) &&
+    visitor->Visit("scalarBlockLayout", &features->scalarBlockLayout) &&
+    visitor->Visit("imagelessFramebuffer", &features->imagelessFramebuffer) &&
+    visitor->Visit("uniformBufferStandardLayout", &features->uniformBufferStandardLayout) &&
+    visitor->Visit("shaderSubgroupExtendedTypes", &features->shaderSubgroupExtendedTypes) &&
+    visitor->Visit("separateDepthStencilLayouts", &features->separateDepthStencilLayouts) &&
+    visitor->Visit("hostQueryReset", &features->hostQueryReset) &&
+    visitor->Visit("timelineSemaphore", &features->timelineSemaphore) &&
+    visitor->Visit("bufferDeviceAddress", &features->bufferDeviceAddress) &&
+    visitor->Visit("bufferDeviceAddressCaptureReplay", &features->bufferDeviceAddressCaptureReplay) &&
+    visitor->Visit("bufferDeviceAddressMultiDevice", &features->bufferDeviceAddressMultiDevice) &&
+    visitor->Visit("vulkanMemoryModel", &features->vulkanMemoryModel) &&
+    visitor->Visit("vulkanMemoryModelDeviceScope", &features->vulkanMemoryModelDeviceScope) &&
+    visitor->Visit("vulkanMemoryModelAvailabilityVisibilityChains", &features->vulkanMemoryModelAvailabilityVisibilityChains) &&
+    visitor->Visit("shaderOutputViewportIndex", &features->shaderOutputViewportIndex) &&
+    visitor->Visit("shaderOutputLayer", &features->shaderOutputLayer) &&
+    visitor->Visit("shaderOutputLayer", &features->shaderOutputLayer);
+}
+
+template <typename Visitor>
+inline bool Iterate(Visitor* visitor, VkJsonCore13* core) {
+  return
+    visitor->Visit("features", &core->features) &&
+    visitor->Visit("properties", &core->properties);
+}
+
+template <typename Visitor>
+inline bool Iterate(Visitor* visitor, VkPhysicalDeviceVulkan13Properties* properties) {
+  return
+    visitor->Visit("minSubgroupSize", &properties->minSubgroupSize) &&
+    visitor->Visit("maxSubgroupSize", &properties->maxSubgroupSize) &&
+    visitor->Visit("maxComputeWorkgroupSubgroups", &properties->maxComputeWorkgroupSubgroups) &&
+    visitor->Visit("requiredSubgroupSizeStages", &properties->requiredSubgroupSizeStages) &&
+    visitor->Visit("maxInlineUniformBlockSize", &properties->maxInlineUniformBlockSize) &&
+    visitor->Visit("maxPerStageDescriptorInlineUniformBlocks", &properties->maxPerStageDescriptorInlineUniformBlocks) &&
+    visitor->Visit("maxPerStageDescriptorUpdateAfterBindInlineUniformBlocks", &properties->maxPerStageDescriptorUpdateAfterBindInlineUniformBlocks) &&
+    visitor->Visit("maxDescriptorSetInlineUniformBlocks", &properties->maxDescriptorSetInlineUniformBlocks) &&
+    visitor->Visit("maxDescriptorSetUpdateAfterBindInlineUniformBlocks", &properties->maxDescriptorSetUpdateAfterBindInlineUniformBlocks) &&
+    visitor->Visit("maxInlineUniformTotalSize", &properties->maxInlineUniformTotalSize) &&
+    visitor->Visit("integerDotProduct8BitUnsignedAccelerated", &properties->integerDotProduct8BitUnsignedAccelerated) &&
+    visitor->Visit("integerDotProduct8BitSignedAccelerated", &properties->integerDotProduct8BitSignedAccelerated) &&
+    visitor->Visit("integerDotProduct8BitMixedSignednessAccelerated", &properties->integerDotProduct8BitMixedSignednessAccelerated) &&
+    visitor->Visit("integerDotProduct4x8BitPackedUnsignedAccelerated", &properties->integerDotProduct4x8BitPackedUnsignedAccelerated) &&
+    visitor->Visit("integerDotProduct4x8BitPackedSignedAccelerated", &properties->integerDotProduct4x8BitPackedSignedAccelerated) &&
+    visitor->Visit("integerDotProduct4x8BitPackedMixedSignednessAccelerated", &properties->integerDotProduct4x8BitPackedMixedSignednessAccelerated) &&
+    visitor->Visit("integerDotProduct16BitUnsignedAccelerated", &properties->integerDotProduct16BitUnsignedAccelerated) &&
+    visitor->Visit("integerDotProduct16BitSignedAccelerated", &properties->integerDotProduct16BitSignedAccelerated) &&
+    visitor->Visit("integerDotProduct16BitMixedSignednessAccelerated", &properties->integerDotProduct16BitMixedSignednessAccelerated) &&
+    visitor->Visit("integerDotProduct32BitUnsignedAccelerated", &properties->integerDotProduct32BitUnsignedAccelerated) &&
+    visitor->Visit("integerDotProduct32BitSignedAccelerated", &properties->integerDotProduct32BitSignedAccelerated) &&
+    visitor->Visit("integerDotProduct32BitMixedSignednessAccelerated", &properties->integerDotProduct32BitMixedSignednessAccelerated) &&
+    visitor->Visit("integerDotProduct64BitUnsignedAccelerated", &properties->integerDotProduct64BitUnsignedAccelerated) &&
+    visitor->Visit("integerDotProduct64BitSignedAccelerated", &properties->integerDotProduct64BitSignedAccelerated) &&
+    visitor->Visit("integerDotProduct64BitMixedSignednessAccelerated", &properties->integerDotProduct64BitMixedSignednessAccelerated) &&
+    visitor->Visit("integerDotProductAccumulatingSaturating8BitUnsignedAccelerated", &properties->integerDotProductAccumulatingSaturating8BitUnsignedAccelerated) &&
+    visitor->Visit("integerDotProductAccumulatingSaturating8BitSignedAccelerated", &properties->integerDotProductAccumulatingSaturating8BitSignedAccelerated) &&
+    visitor->Visit("integerDotProductAccumulatingSaturating8BitMixedSignednessAccelerated", &properties->integerDotProductAccumulatingSaturating8BitMixedSignednessAccelerated) &&
+    visitor->Visit("integerDotProductAccumulatingSaturating4x8BitPackedUnsignedAccelerated", &properties->integerDotProductAccumulatingSaturating4x8BitPackedUnsignedAccelerated) &&
+    visitor->Visit("integerDotProductAccumulatingSaturating4x8BitPackedSignedAccelerated", &properties->integerDotProductAccumulatingSaturating4x8BitPackedSignedAccelerated) &&
+    visitor->Visit("integerDotProductAccumulatingSaturating4x8BitPackedMixedSignednessAccelerated", &properties->integerDotProductAccumulatingSaturating4x8BitPackedMixedSignednessAccelerated) &&
+    visitor->Visit("integerDotProductAccumulatingSaturating16BitUnsignedAccelerated", &properties->integerDotProductAccumulatingSaturating16BitUnsignedAccelerated) &&
+    visitor->Visit("integerDotProductAccumulatingSaturating16BitSignedAccelerated", &properties->integerDotProductAccumulatingSaturating16BitSignedAccelerated) &&
+    visitor->Visit("integerDotProductAccumulatingSaturating16BitMixedSignednessAccelerated", &properties->integerDotProductAccumulatingSaturating16BitMixedSignednessAccelerated) &&
+    visitor->Visit("integerDotProductAccumulatingSaturating32BitUnsignedAccelerated", &properties->integerDotProductAccumulatingSaturating32BitUnsignedAccelerated) &&
+    visitor->Visit("integerDotProductAccumulatingSaturating32BitSignedAccelerated", &properties->integerDotProductAccumulatingSaturating32BitSignedAccelerated) &&
+    visitor->Visit("integerDotProductAccumulatingSaturating32BitMixedSignednessAccelerated", &properties->integerDotProductAccumulatingSaturating32BitMixedSignednessAccelerated) &&
+    visitor->Visit("integerDotProductAccumulatingSaturating64BitUnsignedAccelerated", &properties->integerDotProductAccumulatingSaturating64BitUnsignedAccelerated) &&
+    visitor->Visit("integerDotProductAccumulatingSaturating64BitSignedAccelerated", &properties->integerDotProductAccumulatingSaturating64BitSignedAccelerated) &&
+    visitor->Visit("integerDotProductAccumulatingSaturating64BitMixedSignednessAccelerated", &properties->integerDotProductAccumulatingSaturating64BitMixedSignednessAccelerated) &&
+    visitor->Visit("storageTexelBufferOffsetAlignmentBytes", &properties->storageTexelBufferOffsetAlignmentBytes) &&
+    visitor->Visit("storageTexelBufferOffsetSingleTexelAlignment", &properties->storageTexelBufferOffsetSingleTexelAlignment) &&
+    visitor->Visit("uniformTexelBufferOffsetAlignmentBytes", &properties->uniformTexelBufferOffsetAlignmentBytes) &&
+    visitor->Visit("uniformTexelBufferOffsetSingleTexelAlignment", &properties->uniformTexelBufferOffsetSingleTexelAlignment) &&
+    visitor->Visit("maxBufferSize", &properties->maxBufferSize);
+}
+
+template <typename Visitor>
+inline bool Iterate(Visitor* visitor, VkPhysicalDeviceVulkan13Features* features) {
+  return
+    visitor->Visit("robustImageAccess", &features->robustImageAccess) &&
+    visitor->Visit("inlineUniformBlock", &features->inlineUniformBlock) &&
+    visitor->Visit("descriptorBindingInlineUniformBlockUpdateAfterBind", &features->descriptorBindingInlineUniformBlockUpdateAfterBind) &&
+    visitor->Visit("pipelineCreationCacheControl", &features->pipelineCreationCacheControl) &&
+    visitor->Visit("privateData", &features->privateData) &&
+    visitor->Visit("shaderDemoteToHelperInvocation", &features->shaderDemoteToHelperInvocation) &&
+    visitor->Visit("shaderTerminateInvocation", &features->shaderTerminateInvocation) &&
+    visitor->Visit("subgroupSizeControl", &features->subgroupSizeControl) &&
+    visitor->Visit("computeFullSubgroups", &features->computeFullSubgroups) &&
+    visitor->Visit("synchronization2", &features->synchronization2) &&
+    visitor->Visit("textureCompressionASTC_HDR", &features->textureCompressionASTC_HDR) &&
+    visitor->Visit("shaderZeroInitializeWorkgroupMemory", &features->shaderZeroInitializeWorkgroupMemory) &&
+    visitor->Visit("dynamicRendering", &features->dynamicRendering) &&
+    visitor->Visit("shaderIntegerDotProduct", &features->shaderIntegerDotProduct) &&
+    visitor->Visit("maintenance4", &features->maintenance4);
+}
+// clang-format on
+
+template <typename Visitor>
 inline bool Iterate(Visitor* visitor,
                     VkJsonExtDriverProperties* properties) {
   return visitor->Visit("driverPropertiesKHR",
@@ -841,8 +1049,12 @@
 inline bool Iterate(Visitor* visitor, VkJsonDevice* device) {
   bool ret = true;
   switch (device->properties.apiVersion ^
-          VK_VERSION_PATCH(device->properties.apiVersion)) {
+          VK_API_VERSION_PATCH(device->properties.apiVersion)) {
+    case VK_API_VERSION_1_3:
+      ret &= visitor->Visit("core13", &device->core13);
+      FALLTHROUGH_INTENDED;
     case VK_API_VERSION_1_2:
+      ret &= visitor->Visit("core12", &device->core12);
       FALLTHROUGH_INTENDED;
     case VK_API_VERSION_1_1:
       ret &=
@@ -897,16 +1109,22 @@
 template <typename Visitor>
 inline bool Iterate(Visitor* visitor, VkJsonInstance* instance) {
   bool ret = true;
-  switch (instance->api_version ^ VK_VERSION_PATCH(instance->api_version)) {
+  switch (instance->api_version ^ VK_API_VERSION_PATCH(instance->api_version)) {
+    case VK_API_VERSION_1_3:
+      FALLTHROUGH_INTENDED;
     case VK_API_VERSION_1_2:
+      ret &= visitor->Visit("apiVersion", &instance->api_version);
       FALLTHROUGH_INTENDED;
     case VK_API_VERSION_1_1:
       ret &= visitor->Visit("deviceGroups", &instance->device_groups);
       FALLTHROUGH_INTENDED;
     case VK_API_VERSION_1_0:
+      char depString[] =
+          "vkjson is deprecated, and will be replaced in a future release";
       ret &= visitor->Visit("layers", &instance->layers) &&
              visitor->Visit("extensions", &instance->extensions) &&
-             visitor->Visit("devices", &instance->devices);
+             visitor->Visit("devices", &instance->devices) &&
+             visitor->Visit("_comment", &depString);
   }
   return ret;
 }
diff --git a/vulkan/vkjson/vkjson.h b/vulkan/vkjson/vkjson.h
index 52e7bee..88f6e7d 100644
--- a/vulkan/vkjson/vkjson.h
+++ b/vulkan/vkjson/vkjson.h
@@ -33,14 +33,6 @@
 #undef max
 #endif
 
-#ifndef VK_API_VERSION_1_0
-#define VK_API_VERSION_1_0 VK_MAKE_VERSION(1, 0, 0)
-#endif
-
-#ifndef VK_API_VERSION_1_1
-#define VK_API_VERSION_1_1 VK_MAKE_VERSION(1, 1, 0)
-#endif
-
 /*
  * Annotation to tell clang that we intend to fall through from one case to
  * another in a switch. Sourced from android-base/macros.h.
@@ -82,6 +74,16 @@
   VkPhysicalDeviceShaderFloat16Int8FeaturesKHR shader_float16_int8_features_khr;
 };
 
+struct VkJsonCore12 {
+  VkPhysicalDeviceVulkan12Properties properties;
+  VkPhysicalDeviceVulkan12Features features;
+};
+
+struct VkJsonCore13 {
+  VkPhysicalDeviceVulkan13Properties properties;
+  VkPhysicalDeviceVulkan13Features features;
+};
+
 struct VkJsonDevice {
   VkJsonDevice() {
     memset(&properties, 0, sizeof(VkPhysicalDeviceProperties));
@@ -106,6 +108,8 @@
            sizeof(VkPhysicalDeviceSamplerYcbcrConversionFeatures));
     memset(&shader_draw_parameter_features, 0,
            sizeof(VkPhysicalDeviceShaderDrawParameterFeatures));
+    memset(&core12, 0, sizeof(VkJsonCore12));
+    memset(&core13, 0, sizeof(VkJsonCore13));
   }
   VkPhysicalDeviceProperties properties;
   VkPhysicalDeviceFeatures features;
@@ -133,6 +137,8 @@
       external_fence_properties;
   std::map<VkExternalSemaphoreHandleTypeFlagBits, VkExternalSemaphoreProperties>
       external_semaphore_properties;
+  VkJsonCore12 core12;
+  VkJsonCore13 core13;
 };
 
 struct VkJsonDeviceGroup {
diff --git a/vulkan/vkjson/vkjson_instance.cc b/vulkan/vkjson/vkjson_instance.cc
index 5872495..0ffe7e0 100644
--- a/vulkan/vkjson/vkjson_instance.cc
+++ b/vulkan/vkjson/vkjson_instance.cc
@@ -157,76 +157,64 @@
       }
     }
 
-    VkPhysicalDeviceProperties2 properties2 = {
-        VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2,
-        nullptr,
-        {},
-    };
-
     device.subgroup_properties.sType =
         VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SUBGROUP_PROPERTIES;
-    device.subgroup_properties.pNext = properties2.pNext;
-    properties2.pNext = &device.subgroup_properties;
+    device.subgroup_properties.pNext = properties.pNext;
+    properties.pNext = &device.subgroup_properties;
 
     device.point_clipping_properties.sType =
         VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_POINT_CLIPPING_PROPERTIES;
-    device.point_clipping_properties.pNext = properties2.pNext;
-    properties2.pNext = &device.point_clipping_properties;
+    device.point_clipping_properties.pNext = properties.pNext;
+    properties.pNext = &device.point_clipping_properties;
 
     device.multiview_properties.sType =
         VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTIVIEW_PROPERTIES;
-    device.multiview_properties.pNext = properties2.pNext;
-    properties2.pNext = &device.multiview_properties;
+    device.multiview_properties.pNext = properties.pNext;
+    properties.pNext = &device.multiview_properties;
 
     device.id_properties.sType =
         VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ID_PROPERTIES;
-    device.id_properties.pNext = properties2.pNext;
-    properties2.pNext = &device.id_properties;
+    device.id_properties.pNext = properties.pNext;
+    properties.pNext = &device.id_properties;
 
     device.maintenance3_properties.sType =
         VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MAINTENANCE_3_PROPERTIES;
-    device.maintenance3_properties.pNext = properties2.pNext;
-    properties2.pNext = &device.maintenance3_properties;
+    device.maintenance3_properties.pNext = properties.pNext;
+    properties.pNext = &device.maintenance3_properties;
 
-    vkGetPhysicalDeviceProperties2(physical_device, &properties2);
-
-    VkPhysicalDeviceFeatures2 features2 = {
-        VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2,
-        nullptr,
-        {},
-    };
+    vkGetPhysicalDeviceProperties2(physical_device, &properties);
 
     device.bit16_storage_features.sType =
         VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_16BIT_STORAGE_FEATURES;
-    device.bit16_storage_features.pNext = features2.pNext;
-    features2.pNext = &device.bit16_storage_features;
+    device.bit16_storage_features.pNext = features.pNext;
+    features.pNext = &device.bit16_storage_features;
 
     device.multiview_features.sType =
         VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTIVIEW_FEATURES;
-    device.multiview_features.pNext = features2.pNext;
-    features2.pNext = &device.multiview_features;
+    device.multiview_features.pNext = features.pNext;
+    features.pNext = &device.multiview_features;
 
     device.variable_pointer_features.sType =
         VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VARIABLE_POINTER_FEATURES;
-    device.variable_pointer_features.pNext = features2.pNext;
-    features2.pNext = &device.variable_pointer_features;
+    device.variable_pointer_features.pNext = features.pNext;
+    features.pNext = &device.variable_pointer_features;
 
     device.protected_memory_features.sType =
         VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROTECTED_MEMORY_FEATURES;
-    device.protected_memory_features.pNext = features2.pNext;
-    features2.pNext = &device.protected_memory_features;
+    device.protected_memory_features.pNext = features.pNext;
+    features.pNext = &device.protected_memory_features;
 
     device.sampler_ycbcr_conversion_features.sType =
         VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SAMPLER_YCBCR_CONVERSION_FEATURES;
-    device.sampler_ycbcr_conversion_features.pNext = features2.pNext;
-    features2.pNext = &device.sampler_ycbcr_conversion_features;
+    device.sampler_ycbcr_conversion_features.pNext = features.pNext;
+    features.pNext = &device.sampler_ycbcr_conversion_features;
 
     device.shader_draw_parameter_features.sType =
         VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_DRAW_PARAMETER_FEATURES;
-    device.shader_draw_parameter_features.pNext = features2.pNext;
-    features2.pNext = &device.shader_draw_parameter_features;
+    device.shader_draw_parameter_features.pNext = features.pNext;
+    features.pNext = &device.shader_draw_parameter_features;
 
-    vkGetPhysicalDeviceFeatures2(physical_device, &features2);
+    vkGetPhysicalDeviceFeatures2(physical_device, &features);
 
     VkPhysicalDeviceExternalFenceInfo external_fence_info = {
         VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_FENCE_INFO, nullptr,
@@ -272,6 +260,38 @@
     }
   }
 
+  if (device.properties.apiVersion >= VK_API_VERSION_1_2) {
+    device.core12.properties.sType =
+        VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_PROPERTIES;
+    device.core12.properties.pNext = properties.pNext;
+    properties.pNext = &device.core12.properties;
+
+    vkGetPhysicalDeviceProperties2(physical_device, &properties);
+
+    device.core12.features.sType =
+        VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES;
+    device.core12.features.pNext = features.pNext;
+    features.pNext = &device.core12.features;
+
+    vkGetPhysicalDeviceFeatures2(physical_device, &features);
+  }
+
+  if (device.properties.apiVersion >= VK_API_VERSION_1_3) {
+    device.core13.properties.sType =
+        VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_PROPERTIES;
+    device.core13.properties.pNext = properties.pNext;
+    properties.pNext = &device.core13.properties;
+
+    vkGetPhysicalDeviceProperties2(physical_device, &properties);
+
+    device.core13.features.sType =
+        VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES;
+    device.core13.features.pNext = features.pNext;
+    features.pNext = &device.core13.features;
+
+    vkGetPhysicalDeviceFeatures2(physical_device, &features);
+  }
+
   return device;
 }